@forgeax/engine-assets-runtime 0.0.0-dev.8d955ade1c79

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 (320) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +491 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/aabb.unit.test.d.ts +2 -0
  5. package/dist/__tests__/aabb.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/artifact-integrity.unit.test.d.ts +2 -0
  7. package/dist/__tests__/artifact-integrity.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/artifact-io.unit.test.d.ts +2 -0
  9. package/dist/__tests__/artifact-io.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/asset-evidence-sdk.integration.test.d.ts +2 -0
  11. package/dist/__tests__/asset-evidence-sdk.integration.test.d.ts.map +1 -0
  12. package/dist/__tests__/asset-evidence.unit.test.d.ts +2 -0
  13. package/dist/__tests__/asset-evidence.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/asset-kind.unit.test.d.ts +2 -0
  15. package/dist/__tests__/asset-kind.unit.test.d.ts.map +1 -0
  16. package/dist/__tests__/asset-registry-catalog-reconcile.integration.test.d.ts +2 -0
  17. package/dist/__tests__/asset-registry-catalog-reconcile.integration.test.d.ts.map +1 -0
  18. package/dist/__tests__/asset-registry-catalog-reconcile.unit.test.d.ts +2 -0
  19. package/dist/__tests__/asset-registry-catalog-reconcile.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/asset-registry-catalog.unit.test.d.ts +2 -0
  21. package/dist/__tests__/asset-registry-catalog.unit.test.d.ts.map +1 -0
  22. package/dist/__tests__/asset-registry.pack-cache.test.d.ts +2 -0
  23. package/dist/__tests__/asset-registry.pack-cache.test.d.ts.map +1 -0
  24. package/dist/__tests__/asset-registry.unit.test.d.ts +2 -0
  25. package/dist/__tests__/asset-registry.unit.test.d.ts.map +1 -0
  26. package/dist/__tests__/asset-runtime-error-code-owner.test-d.d.ts +2 -0
  27. package/dist/__tests__/asset-runtime-error-code-owner.test-d.d.ts.map +1 -0
  28. package/dist/__tests__/catalog-replica.unit.test.d.ts +2 -0
  29. package/dist/__tests__/catalog-replica.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/catalog-source-v2.unit.test.d.ts +2 -0
  31. package/dist/__tests__/catalog-source-v2.unit.test.d.ts.map +1 -0
  32. package/dist/__tests__/catalog-source.unit.test.d.ts +2 -0
  33. package/dist/__tests__/catalog-source.unit.test.d.ts.map +1 -0
  34. package/dist/__tests__/catalog-subscription-order.unit.test.d.ts +2 -0
  35. package/dist/__tests__/catalog-subscription-order.unit.test.d.ts.map +1 -0
  36. package/dist/__tests__/catalog-subscription.unit.test.d.ts +2 -0
  37. package/dist/__tests__/catalog-subscription.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/decode-image-bytes-pass-through.test.d.ts +2 -0
  39. package/dist/__tests__/decode-image-bytes-pass-through.test.d.ts.map +1 -0
  40. package/dist/__tests__/decode-image-bytes.browser.test.d.ts +2 -0
  41. package/dist/__tests__/decode-image-bytes.browser.test.d.ts.map +1 -0
  42. package/dist/__tests__/decode-image-bytes.test-d.d.ts +2 -0
  43. package/dist/__tests__/decode-image-bytes.test-d.d.ts.map +1 -0
  44. package/dist/__tests__/decode-image-bytes.test.d.ts +2 -0
  45. package/dist/__tests__/decode-image-bytes.test.d.ts.map +1 -0
  46. package/dist/__tests__/decode-image-mime-owner.test.d.ts +2 -0
  47. package/dist/__tests__/decode-image-mime-owner.test.d.ts.map +1 -0
  48. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts +2 -0
  49. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/errors-asset.unit.test.d.ts +2 -0
  51. package/dist/__tests__/errors-asset.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/inline-pack-loaders.unit.test.d.ts +2 -0
  53. package/dist/__tests__/inline-pack-loaders.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/instantiate-context.unit.test.d.ts +2 -0
  55. package/dist/__tests__/instantiate-context.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/instantiate-publication-fence.unit.test.d.ts +2 -0
  57. package/dist/__tests__/instantiate-publication-fence.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/instantiate.unit.test.d.ts +2 -0
  59. package/dist/__tests__/instantiate.unit.test.d.ts.map +1 -0
  60. package/dist/__tests__/loader-input-contract.unit.test.d.ts +2 -0
  61. package/dist/__tests__/loader-input-contract.unit.test.d.ts.map +1 -0
  62. package/dist/__tests__/loader-registry.unit.test.d.ts +2 -0
  63. package/dist/__tests__/loader-registry.unit.test.d.ts.map +1 -0
  64. package/dist/__tests__/material-dependency-invalidation.unit.test.d.ts +2 -0
  65. package/dist/__tests__/material-dependency-invalidation.unit.test.d.ts.map +1 -0
  66. package/dist/__tests__/material-generation-cache.unit.test.d.ts +2 -0
  67. package/dist/__tests__/material-generation-cache.unit.test.d.ts.map +1 -0
  68. package/dist/__tests__/material-load-consumer.test-d.d.ts +2 -0
  69. package/dist/__tests__/material-load-consumer.test-d.d.ts.map +1 -0
  70. package/dist/__tests__/material-ready.integration.test.d.ts +2 -0
  71. package/dist/__tests__/material-ready.integration.test.d.ts.map +1 -0
  72. package/dist/__tests__/material-stale-generation.unit.test.d.ts +2 -0
  73. package/dist/__tests__/material-stale-generation.unit.test.d.ts.map +1 -0
  74. package/dist/__tests__/mesh-bin-morph-v3.test.d.ts +2 -0
  75. package/dist/__tests__/mesh-bin-morph-v3.test.d.ts.map +1 -0
  76. package/dist/__tests__/mesh-bin.unit.test.d.ts +2 -0
  77. package/dist/__tests__/mesh-bin.unit.test.d.ts.map +1 -0
  78. package/dist/__tests__/mipmap-generator.unit.test.d.ts +2 -0
  79. package/dist/__tests__/mipmap-generator.unit.test.d.ts.map +1 -0
  80. package/dist/__tests__/pack-artifact-transcode-model-owner.test.d.ts +2 -0
  81. package/dist/__tests__/pack-artifact-transcode-model-owner.test.d.ts.map +1 -0
  82. package/dist/__tests__/pack-basis-load.integration.test.d.ts +2 -0
  83. package/dist/__tests__/pack-basis-load.integration.test.d.ts.map +1 -0
  84. package/dist/__tests__/payload-validate.unit.test.d.ts +2 -0
  85. package/dist/__tests__/payload-validate.unit.test.d.ts.map +1 -0
  86. package/dist/__tests__/plugin.test.d.ts +2 -0
  87. package/dist/__tests__/plugin.test.d.ts.map +1 -0
  88. package/dist/__tests__/registry-concurrency.bench.test.d.ts +2 -0
  89. package/dist/__tests__/registry-concurrency.bench.test.d.ts.map +1 -0
  90. package/dist/__tests__/registry-failure-retry.integration.test.d.ts +2 -0
  91. package/dist/__tests__/registry-failure-retry.integration.test.d.ts.map +1 -0
  92. package/dist/__tests__/registry-load-graph.integration.test.d.ts +2 -0
  93. package/dist/__tests__/registry-load-graph.integration.test.d.ts.map +1 -0
  94. package/dist/__tests__/registry-load-state.unit.test.d.ts +2 -0
  95. package/dist/__tests__/registry-load-state.unit.test.d.ts.map +1 -0
  96. package/dist/__tests__/registry-package-cache.unit.test.d.ts +2 -0
  97. package/dist/__tests__/registry-package-cache.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/registry-public-ready.unit.test.d.ts +2 -0
  99. package/dist/__tests__/registry-public-ready.unit.test.d.ts.map +1 -0
  100. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts +2 -0
  101. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts.map +1 -0
  102. package/dist/__tests__/scene-handle-fields.unit.test.d.ts +2 -0
  103. package/dist/__tests__/scene-handle-fields.unit.test.d.ts.map +1 -0
  104. package/dist/__tests__/scene-payload.unit.test.d.ts +2 -0
  105. package/dist/__tests__/scene-payload.unit.test.d.ts.map +1 -0
  106. package/dist/__tests__/scene-publication-fence.test.d.ts +2 -0
  107. package/dist/__tests__/scene-publication-fence.test.d.ts.map +1 -0
  108. package/dist/__tests__/scriptable-pack-public-surface.test-d.d.ts +2 -0
  109. package/dist/__tests__/scriptable-pack-public-surface.test-d.d.ts.map +1 -0
  110. package/dist/__tests__/static-rig.integration.test.d.ts +2 -0
  111. package/dist/__tests__/static-rig.integration.test.d.ts.map +1 -0
  112. package/dist/__tests__/validate-material.unit.test.d.ts +2 -0
  113. package/dist/__tests__/validate-material.unit.test.d.ts.map +1 -0
  114. package/dist/__tests__/video-pack-loader.integration.test.d.ts +2 -0
  115. package/dist/__tests__/video-pack-loader.integration.test.d.ts.map +1 -0
  116. package/dist/aabb.d.ts +3 -0
  117. package/dist/aabb.d.ts.map +1 -0
  118. package/dist/asset-kind.d.ts +4 -0
  119. package/dist/asset-kind.d.ts.map +1 -0
  120. package/dist/asset-registry.d.ts +554 -0
  121. package/dist/asset-registry.d.ts.map +1 -0
  122. package/dist/builtin-asset-registry.d.ts +22 -0
  123. package/dist/builtin-asset-registry.d.ts.map +1 -0
  124. package/dist/catalog-source.d.ts +24 -0
  125. package/dist/catalog-source.d.ts.map +1 -0
  126. package/dist/decode-image-bytes.d.ts +57 -0
  127. package/dist/decode-image-bytes.d.ts.map +1 -0
  128. package/dist/dynamic-texture-store.d.ts +89 -0
  129. package/dist/dynamic-texture-store.d.ts.map +1 -0
  130. package/dist/errors/asset.d.ts +201 -0
  131. package/dist/errors/asset.d.ts.map +1 -0
  132. package/dist/handles.d.ts +143 -0
  133. package/dist/handles.d.ts.map +1 -0
  134. package/dist/image-error.d.ts +6 -0
  135. package/dist/image-error.d.ts.map +1 -0
  136. package/dist/index.d.ts +34 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.mjs +8359 -0
  139. package/dist/index.mjs.map +1 -0
  140. package/dist/internal/artifact-cache.d.ts +17 -0
  141. package/dist/internal/artifact-cache.d.ts.map +1 -0
  142. package/dist/internal/asset-graph.d.ts +70 -0
  143. package/dist/internal/asset-graph.d.ts.map +1 -0
  144. package/dist/internal/catalog-session.d.ts +60 -0
  145. package/dist/internal/catalog-session.d.ts.map +1 -0
  146. package/dist/internal/decoder-registry.d.ts +19 -0
  147. package/dist/internal/decoder-registry.d.ts.map +1 -0
  148. package/dist/internal/immutable-payload.d.ts +9 -0
  149. package/dist/internal/immutable-payload.d.ts.map +1 -0
  150. package/dist/internal/load-asset.d.ts +31 -0
  151. package/dist/internal/load-asset.d.ts.map +1 -0
  152. package/dist/internal/pack-reader.d.ts +18 -0
  153. package/dist/internal/pack-reader.d.ts.map +1 -0
  154. package/dist/internal/validate-runtime-row.d.ts +7 -0
  155. package/dist/internal/validate-runtime-row.d.ts.map +1 -0
  156. package/dist/internal.d.ts +3 -0
  157. package/dist/internal.d.ts.map +1 -0
  158. package/dist/internal.mjs +329 -0
  159. package/dist/internal.mjs.map +1 -0
  160. package/dist/loader-registry.d.ts +63 -0
  161. package/dist/loader-registry.d.ts.map +1 -0
  162. package/dist/loaders/inline-pack.d.ts +72 -0
  163. package/dist/loaders/inline-pack.d.ts.map +1 -0
  164. package/dist/loaders/mesh-bin.d.ts +21 -0
  165. package/dist/loaders/mesh-bin.d.ts.map +1 -0
  166. package/dist/loaders/pack-artifact.d.ts +20 -0
  167. package/dist/loaders/pack-artifact.d.ts.map +1 -0
  168. package/dist/material/dependency-graph.d.ts +9 -0
  169. package/dist/material/dependency-graph.d.ts.map +1 -0
  170. package/dist/material/generation-cache.d.ts +24 -0
  171. package/dist/material/generation-cache.d.ts.map +1 -0
  172. package/dist/material/inspection.d.ts +24 -0
  173. package/dist/material/inspection.d.ts.map +1 -0
  174. package/dist/material/loader.d.ts +59 -0
  175. package/dist/material/loader.d.ts.map +1 -0
  176. package/dist/material/runtime-shader.d.ts +14 -0
  177. package/dist/material/runtime-shader.d.ts.map +1 -0
  178. package/dist/mesh-bin.d.ts +3 -0
  179. package/dist/mesh-bin.d.ts.map +1 -0
  180. package/dist/mipmap-generator.d.ts +132 -0
  181. package/dist/mipmap-generator.d.ts.map +1 -0
  182. package/dist/payload-validate.d.ts +60 -0
  183. package/dist/payload-validate.d.ts.map +1 -0
  184. package/dist/plugin.d.ts +16 -0
  185. package/dist/plugin.d.ts.map +1 -0
  186. package/dist/registry/artifact-io.d.ts +16 -0
  187. package/dist/registry/artifact-io.d.ts.map +1 -0
  188. package/dist/registry/asset-evidence.d.ts +13 -0
  189. package/dist/registry/asset-evidence.d.ts.map +1 -0
  190. package/dist/registry/catalog-state.d.ts +47 -0
  191. package/dist/registry/catalog-state.d.ts.map +1 -0
  192. package/dist/registry/catalog.d.ts +48 -0
  193. package/dist/registry/catalog.d.ts.map +1 -0
  194. package/dist/registry/instantiate.d.ts +153 -0
  195. package/dist/registry/instantiate.d.ts.map +1 -0
  196. package/dist/registry/load-by-guid.d.ts +247 -0
  197. package/dist/registry/load-by-guid.d.ts.map +1 -0
  198. package/dist/registry/load-state.d.ts +26 -0
  199. package/dist/registry/load-state.d.ts.map +1 -0
  200. package/dist/registry/runtime-projection.d.ts +16 -0
  201. package/dist/registry/runtime-projection.d.ts.map +1 -0
  202. package/dist/registry/scene-publication-fence.d.ts +38 -0
  203. package/dist/registry/scene-publication-fence.d.ts.map +1 -0
  204. package/dist/registry/validate-material.d.ts +77 -0
  205. package/dist/registry/validate-material.d.ts.map +1 -0
  206. package/dist/resolve-asset-handle.d.ts +43 -0
  207. package/dist/resolve-asset-handle.d.ts.map +1 -0
  208. package/dist/resolve-tileset-runtime.d.ts +23 -0
  209. package/dist/resolve-tileset-runtime.d.ts.map +1 -0
  210. package/dist/scene-handle-fields.d.ts +88 -0
  211. package/dist/scene-handle-fields.d.ts.map +1 -0
  212. package/dist/scene-payload.d.ts +15 -0
  213. package/dist/scene-payload.d.ts.map +1 -0
  214. package/dist/wire-default-loaders.d.ts +29 -0
  215. package/dist/wire-default-loaders.d.ts.map +1 -0
  216. package/package.json +76 -0
  217. package/src/__tests__/aabb.unit.test.ts +97 -0
  218. package/src/__tests__/artifact-integrity.unit.test.ts +99 -0
  219. package/src/__tests__/artifact-io.unit.test.ts +127 -0
  220. package/src/__tests__/asset-evidence-sdk.integration.test.ts +32 -0
  221. package/src/__tests__/asset-evidence.unit.test.ts +32 -0
  222. package/src/__tests__/asset-kind.unit.test.ts +14 -0
  223. package/src/__tests__/asset-registry-catalog-reconcile.integration.test.ts +74 -0
  224. package/src/__tests__/asset-registry-catalog-reconcile.unit.test.ts +89 -0
  225. package/src/__tests__/asset-registry-catalog.unit.test.ts +170 -0
  226. package/src/__tests__/asset-registry.pack-cache.test.ts +96 -0
  227. package/src/__tests__/asset-registry.unit.test.ts +511 -0
  228. package/src/__tests__/asset-runtime-error-code-owner.test-d.ts +63 -0
  229. package/src/__tests__/catalog-replica.unit.test.ts +196 -0
  230. package/src/__tests__/catalog-source-v2.unit.test.ts +158 -0
  231. package/src/__tests__/catalog-source.unit.test.ts +126 -0
  232. package/src/__tests__/catalog-subscription-order.unit.test.ts +65 -0
  233. package/src/__tests__/catalog-subscription.unit.test.ts +28 -0
  234. package/src/__tests__/decode-image-bytes-pass-through.test.ts +34 -0
  235. package/src/__tests__/decode-image-bytes.browser.test.ts +102 -0
  236. package/src/__tests__/decode-image-bytes.test-d.ts +108 -0
  237. package/src/__tests__/decode-image-bytes.test.ts +90 -0
  238. package/src/__tests__/decode-image-mime-owner.test.ts +38 -0
  239. package/src/__tests__/dynamic-texture-store.unit.test.ts +115 -0
  240. package/src/__tests__/errors-asset.unit.test.ts +92 -0
  241. package/src/__tests__/inline-pack-loaders.unit.test.ts +548 -0
  242. package/src/__tests__/instantiate-context.unit.test.ts +65 -0
  243. package/src/__tests__/instantiate-publication-fence.unit.test.ts +158 -0
  244. package/src/__tests__/instantiate.unit.test.ts +684 -0
  245. package/src/__tests__/loader-input-contract.unit.test.ts +89 -0
  246. package/src/__tests__/loader-registry.unit.test.ts +101 -0
  247. package/src/__tests__/material-dependency-invalidation.unit.test.ts +14 -0
  248. package/src/__tests__/material-generation-cache.unit.test.ts +46 -0
  249. package/src/__tests__/material-load-consumer.test-d.ts +42 -0
  250. package/src/__tests__/material-ready.integration.test.ts +508 -0
  251. package/src/__tests__/material-stale-generation.unit.test.ts +147 -0
  252. package/src/__tests__/mesh-bin-morph-v3.test.ts +22 -0
  253. package/src/__tests__/mesh-bin.unit.test.ts +152 -0
  254. package/src/__tests__/mipmap-generator.unit.test.ts +202 -0
  255. package/src/__tests__/pack-artifact-transcode-model-owner.test.ts +32 -0
  256. package/src/__tests__/pack-basis-load.integration.test.ts +168 -0
  257. package/src/__tests__/payload-validate.unit.test.ts +285 -0
  258. package/src/__tests__/plugin.test.ts +55 -0
  259. package/src/__tests__/registry-concurrency.bench.test.ts +40 -0
  260. package/src/__tests__/registry-failure-retry.integration.test.ts +69 -0
  261. package/src/__tests__/registry-load-graph.integration.test.ts +246 -0
  262. package/src/__tests__/registry-load-state.unit.test.ts +35 -0
  263. package/src/__tests__/registry-package-cache.unit.test.ts +53 -0
  264. package/src/__tests__/registry-public-ready.unit.test.ts +38 -0
  265. package/src/__tests__/resolve-asset-handle.unit.test.ts +238 -0
  266. package/src/__tests__/scene-handle-fields.unit.test.ts +129 -0
  267. package/src/__tests__/scene-payload.unit.test.ts +151 -0
  268. package/src/__tests__/scene-publication-fence.test.ts +129 -0
  269. package/src/__tests__/scriptable-pack-public-surface.test-d.ts +27 -0
  270. package/src/__tests__/static-rig.integration.test.ts +8 -0
  271. package/src/__tests__/validate-material.unit.test.ts +359 -0
  272. package/src/__tests__/video-pack-loader.integration.test.ts +83 -0
  273. package/src/aabb.ts +107 -0
  274. package/src/asset-kind.ts +6 -0
  275. package/src/asset-registry.ts +2045 -0
  276. package/src/builtin-asset-registry.ts +164 -0
  277. package/src/catalog-source.ts +89 -0
  278. package/src/decode-image-bytes.ts +126 -0
  279. package/src/dynamic-texture-store.ts +234 -0
  280. package/src/errors/asset.ts +347 -0
  281. package/src/handles.ts +186 -0
  282. package/src/image-error.ts +45 -0
  283. package/src/index.ts +189 -0
  284. package/src/internal/artifact-cache.ts +65 -0
  285. package/src/internal/asset-graph.ts +420 -0
  286. package/src/internal/catalog-session.ts +345 -0
  287. package/src/internal/decoder-registry.ts +175 -0
  288. package/src/internal/immutable-payload.ts +20 -0
  289. package/src/internal/load-asset.ts +254 -0
  290. package/src/internal/pack-reader.ts +183 -0
  291. package/src/internal/validate-runtime-row.ts +51 -0
  292. package/src/internal.ts +6 -0
  293. package/src/loader-registry.ts +157 -0
  294. package/src/loaders/inline-pack.ts +837 -0
  295. package/src/loaders/mesh-bin.ts +304 -0
  296. package/src/loaders/pack-artifact.ts +665 -0
  297. package/src/material/dependency-graph.ts +46 -0
  298. package/src/material/generation-cache.ts +177 -0
  299. package/src/material/inspection.ts +46 -0
  300. package/src/material/loader.ts +326 -0
  301. package/src/material/runtime-shader.ts +133 -0
  302. package/src/mesh-bin.ts +5 -0
  303. package/src/mipmap-generator.ts +450 -0
  304. package/src/payload-validate.ts +573 -0
  305. package/src/plugin.ts +48 -0
  306. package/src/registry/artifact-io.ts +259 -0
  307. package/src/registry/asset-evidence.ts +51 -0
  308. package/src/registry/catalog-state.ts +409 -0
  309. package/src/registry/catalog.ts +369 -0
  310. package/src/registry/instantiate.ts +1056 -0
  311. package/src/registry/load-by-guid.ts +1657 -0
  312. package/src/registry/load-state.ts +115 -0
  313. package/src/registry/runtime-projection.ts +63 -0
  314. package/src/registry/scene-publication-fence.ts +256 -0
  315. package/src/registry/validate-material.ts +385 -0
  316. package/src/resolve-asset-handle.ts +147 -0
  317. package/src/resolve-tileset-runtime.ts +72 -0
  318. package/src/scene-handle-fields.ts +221 -0
  319. package/src/scene-payload.ts +207 -0
  320. package/src/wire-default-loaders.ts +79 -0
@@ -0,0 +1,196 @@
1
+ import type { CatalogDelta, CatalogEntry } from '@forgeax/engine-types';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { CatalogReplica } from '../registry/catalog-state.js';
4
+
5
+ type EnumerationResult = { readonly ok: true; readonly value: readonly CatalogEntry[] };
6
+
7
+ const base = {
8
+ guid: '11111111-1111-4111-8111-111111111111',
9
+ packageUrl: '/preview/base',
10
+ packageId: 'fixture-package',
11
+ kind: 'mesh',
12
+ sourcePath: 'model.source',
13
+ sourceKey: 'mesh/main',
14
+ sourceIndex: 0,
15
+ provenance: { provider: 'fixture', version: '1' },
16
+ revision: { digest: 'digest-1', observedAt: 1, rootId: 'fixture-root' },
17
+ relations: [],
18
+ lifecycle: 'current' as const,
19
+ diagnostics: [{ code: 'fixture-note', severity: 'info' as const, hint: 'none' }],
20
+ };
21
+
22
+ const other = {
23
+ ...base,
24
+ guid: '22222222-2222-4222-8222-222222222222',
25
+ sourceKey: 'mesh/other',
26
+ sourceIndex: 1,
27
+ packageUrl: '/preview/other',
28
+ };
29
+
30
+ function source() {
31
+ let listener: ((delta: CatalogDelta) => void) | undefined;
32
+ let enumerate: (() => Promise<EnumerationResult>) | undefined;
33
+ const source = {
34
+ subscribe(next: (delta: CatalogDelta) => void) {
35
+ listener = next;
36
+ return () => {
37
+ listener = undefined;
38
+ };
39
+ },
40
+ enumerate() {
41
+ return enumerate?.() ?? Promise.resolve({ ok: true as const, value: [base] });
42
+ },
43
+ };
44
+ return {
45
+ source,
46
+ emit(delta: CatalogDelta) {
47
+ listener?.(delta);
48
+ },
49
+ deferEnumeration() {
50
+ let resolve!: (result: EnumerationResult) => void;
51
+ enumerate = () => new Promise((next) => (resolve = next));
52
+ return (result: EnumerationResult) => resolve(result);
53
+ },
54
+ };
55
+ }
56
+
57
+ describe('CatalogReplica', () => {
58
+ it('subscribes before enumerate and folds an early delta after the baseline', async () => {
59
+ const fixture = source();
60
+ const release = fixture.deferEnumeration();
61
+ const replica = new CatalogReplica(fixture.source as never);
62
+ const started = replica.start();
63
+
64
+ fixture.emit({ added: [other], changed: [], removed: [] });
65
+ release({ ok: true, value: [base] });
66
+ await expect(started).resolves.toMatchObject({ ok: true });
67
+ expect(replica.snapshot().entries.map((entry) => entry.guid)).toEqual([base.guid, other.guid]);
68
+ });
69
+
70
+ it('is idempotent, rejects older revisions, and preserves complete producer facts', async () => {
71
+ const fixture = source();
72
+ const replica = new CatalogReplica(fixture.source as never);
73
+ await replica.start();
74
+ const delta = {
75
+ added: [],
76
+ changed: [
77
+ { ...base, packageUrl: '/preview/new', revision: { ...base.revision, observedAt: 2 } },
78
+ ],
79
+ removed: [],
80
+ revisions: {
81
+ baseline: [{ rootId: 'fixture-root', revision: 1 }],
82
+ current: [{ rootId: 'fixture-root', revision: 2 }],
83
+ },
84
+ };
85
+
86
+ fixture.emit(delta);
87
+ fixture.emit(delta);
88
+ fixture.emit({
89
+ ...delta,
90
+ changed: [
91
+ { ...base, packageUrl: '/preview/old', revision: { ...base.revision, observedAt: 1 } },
92
+ ],
93
+ revisions: {
94
+ baseline: [{ rootId: 'fixture-root', revision: 2 }],
95
+ current: [{ rootId: 'fixture-root', revision: 1 }],
96
+ },
97
+ });
98
+
99
+ const row = replica.snapshot().entries.find((entry) => entry.guid === base.guid);
100
+ expect(row).toMatchObject({
101
+ packageUrl: '/preview/new',
102
+ sourceKey: 'mesh/main',
103
+ provenance: base.provenance,
104
+ relations: base.relations,
105
+ diagnostics: base.diagnostics,
106
+ });
107
+ expect(replica.snapshot().version).toBe(1);
108
+ });
109
+
110
+ it('marks degraded gaps stale and reconcile restores the authoritative set', async () => {
111
+ const fixture = source();
112
+ const release = fixture.deferEnumeration();
113
+ const replica = new CatalogReplica(fixture.source as never);
114
+ const started = replica.start();
115
+ release({ ok: true, value: [base] });
116
+ await started;
117
+
118
+ fixture.emit({
119
+ added: [],
120
+ changed: [],
121
+ removed: [],
122
+ authority: 'degraded',
123
+ diagnostics: [{ code: 'catalog-gap', severity: 'blocking', hint: 'reconcile' }],
124
+ });
125
+ expect(replica.snapshot()).toMatchObject({ stale: true });
126
+
127
+ const reconciling = replica.reconcile();
128
+ release({ ok: true, value: [other] });
129
+ await expect(reconciling).resolves.toMatchObject({ ok: true });
130
+ expect(replica.snapshot()).toMatchObject({ stale: false, entries: [other] });
131
+ });
132
+
133
+ it('does not publish or apply a changed row whose revision window is stale', async () => {
134
+ const fixture = source();
135
+ const replica = new CatalogReplica(fixture.source as never);
136
+ await replica.start();
137
+ const received: CatalogDelta[] = [];
138
+ replica.subscribe((delta) => received.push(delta));
139
+ const staleEntry = {
140
+ ...base,
141
+ packageUrl: '/preview/stale',
142
+ revision: { ...base.revision, observedAt: 3 },
143
+ };
144
+
145
+ fixture.emit({
146
+ added: [],
147
+ changed: [staleEntry],
148
+ removed: [],
149
+ revisions: {
150
+ baseline: [{ rootId: 'fixture-root', revision: 2 }],
151
+ current: [{ rootId: 'fixture-root', revision: 1 }],
152
+ },
153
+ });
154
+
155
+ expect(replica.snapshot()).toMatchObject({
156
+ stale: true,
157
+ entries: [{ guid: base.guid, packageUrl: base.packageUrl }],
158
+ diagnostics: [{ code: 'catalog-revision-stale', severity: 'blocking' }],
159
+ });
160
+ expect(received).toHaveLength(1);
161
+ expect(received[0]).toMatchObject({
162
+ added: [],
163
+ changed: [],
164
+ removed: [],
165
+ authority: 'degraded',
166
+ diagnostics: [{ code: 'catalog-revision-stale', severity: 'blocking' }],
167
+ });
168
+ });
169
+
170
+ it('rejects a changed payload that reuses the verified row revision', async () => {
171
+ const fixture = source();
172
+ const replica = new CatalogReplica(fixture.source as never);
173
+ await replica.start();
174
+ const received: CatalogDelta[] = [];
175
+ replica.subscribe((delta) => received.push(delta));
176
+
177
+ fixture.emit({
178
+ added: [],
179
+ changed: [{ ...base, packageUrl: '/preview/unchanged-revision' }],
180
+ removed: [],
181
+ });
182
+
183
+ expect(replica.snapshot()).toMatchObject({
184
+ stale: true,
185
+ entries: [{ guid: base.guid, packageUrl: base.packageUrl }],
186
+ diagnostics: [{ code: 'catalog-revision-conflict', severity: 'blocking' }],
187
+ });
188
+ expect(received[0]).toMatchObject({
189
+ added: [],
190
+ changed: [],
191
+ removed: [],
192
+ authority: 'degraded',
193
+ diagnostics: [{ code: 'catalog-revision-conflict', severity: 'blocking' }],
194
+ });
195
+ });
196
+ });
@@ -0,0 +1,158 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { createCatalogSource } from '../catalog-source.js';
3
+ import { parseCatalog } from '../registry/catalog.js';
4
+
5
+ const packageRow = {
6
+ guid: '11111111-1111-4111-8111-111111111111',
7
+ kind: 'mesh',
8
+ sourcePath: 'model.glb',
9
+ packageUrl: '/preview/packages/model',
10
+ };
11
+
12
+ describe('catalog source v2', () => {
13
+ it('parses packageUrl navigation without reading artifact bytes', () => {
14
+ const resolveUrl = vi.fn((url: string) => `https://example.test${url}`);
15
+ const result = parseCatalog([packageRow], resolveUrl);
16
+
17
+ expect(result).toEqual({
18
+ ok: true,
19
+ value: new Map([
20
+ [
21
+ packageRow.guid,
22
+ {
23
+ packageUrl: 'https://example.test/preview/packages/model',
24
+ kind: 'mesh',
25
+ sourcePath: 'model.glb',
26
+ },
27
+ ],
28
+ ]),
29
+ });
30
+ expect(resolveUrl).toHaveBeenCalledWith('/preview/packages/model');
31
+ });
32
+
33
+ it.each([
34
+ ['missing packageUrl', { ...packageRow, packageUrl: undefined }],
35
+ ['legacy relativeUrl', { ...packageRow, packageUrl: undefined, relativeUrl: '/asset.bin' }],
36
+ ['legacy compression', { ...packageRow, compression: 'zstd' }],
37
+ ['raw source package', { ...packageRow, packageUrl: '/assets/model.glb' }],
38
+ [
39
+ 'raw particle effect source',
40
+ { ...packageRow, packageUrl: '/assets/effects/boss.particle-effect.json' },
41
+ ],
42
+ ])('rejects %s as a structured catalog failure', (_label, row) => {
43
+ const result = parseCatalog([row]);
44
+
45
+ expect(result.ok).toBe(false);
46
+ if (!result.ok) expect(result.error.code).toBe('asset-parse-failed');
47
+ });
48
+
49
+ it('does not fetch an artifact while parsing catalog rows', () => {
50
+ const fetchArtifact = vi.fn();
51
+ const result = parseCatalog([packageRow]);
52
+
53
+ expect(result.ok).toBe(true);
54
+ expect(fetchArtifact).not.toHaveBeenCalled();
55
+ });
56
+
57
+ it('preserves producer-owned source override authoring facts', () => {
58
+ const sourceOverrides = {
59
+ 'mesh/main': {
60
+ materialSlots: [
61
+ {
62
+ slotName: 'Body',
63
+ sourceKey: 'material/body',
64
+ },
65
+ ],
66
+ materialSlotDefaultOverrides: {
67
+ 'material/body': '22222222-2222-4222-8222-222222222222',
68
+ },
69
+ },
70
+ } as const;
71
+ const sourceOverrideDescriptors = [
72
+ { sourceKey: 'mesh/main', payloadSchema: { type: 'object' } },
73
+ ] as const;
74
+ const result = parseCatalog([
75
+ { ...packageRow, sourceKey: 'mesh/main', sourceOverrides, sourceOverrideDescriptors },
76
+ ]);
77
+
78
+ expect(result).toEqual({
79
+ ok: true,
80
+ value: new Map([
81
+ [
82
+ packageRow.guid,
83
+ {
84
+ packageUrl: packageRow.packageUrl,
85
+ kind: packageRow.kind,
86
+ sourcePath: packageRow.sourcePath,
87
+ sourceKey: 'mesh/main',
88
+ sourceOverrides,
89
+ sourceOverrideDescriptors,
90
+ },
91
+ ],
92
+ ]),
93
+ });
94
+ });
95
+
96
+ it('preserves the complete source publication tuple for runtime fence consumers', () => {
97
+ const publication = {
98
+ schemaVersion: 'asset-publication/1' as const,
99
+ sourcePath: 'assets/showcase.pack.ts',
100
+ sourceRevision: 'sha256:source',
101
+ generation: 3,
102
+ digest: 'sha256:output',
103
+ outputSetDigest: 'sha256:output-set',
104
+ outputs: [
105
+ {
106
+ guid: packageRow.guid,
107
+ sourceKey: 'mesh/main',
108
+ kind: 'mesh',
109
+ digest: 'sha256:mesh',
110
+ refs: [],
111
+ },
112
+ ],
113
+ receipt: {
114
+ schemaVersion: 'asset-publication-receipt/1' as const,
115
+ sourcePath: 'assets/showcase.pack.ts',
116
+ sourceRevision: 'sha256:source',
117
+ inputFingerprint: 'sha256:source',
118
+ outputDigest: 'sha256:output',
119
+ outputSetDigest: 'sha256:output-set',
120
+ externalEvidence: [],
121
+ },
122
+ externalEvidence: [],
123
+ current: {
124
+ generation: 3,
125
+ digest: 'sha256:output',
126
+ outputSetDigest: 'sha256:output-set',
127
+ packageUrl: packageRow.packageUrl,
128
+ receiptKey: 'sha256:source',
129
+ },
130
+ };
131
+ const result = parseCatalog([{ ...packageRow, publication }]);
132
+
133
+ expect(result).toEqual({
134
+ ok: true,
135
+ value: new Map([
136
+ [
137
+ packageRow.guid,
138
+ {
139
+ kind: packageRow.kind,
140
+ packageUrl: packageRow.packageUrl,
141
+ sourcePath: packageRow.sourcePath,
142
+ publication,
143
+ },
144
+ ],
145
+ ]),
146
+ });
147
+ });
148
+
149
+ it('fetches only the catalog source and preserves package navigation', async () => {
150
+ const fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => [packageRow] });
151
+ const source = createCatalogSource({ url: '/preview/pack-index.json', fetch });
152
+
153
+ const result = await source.enumerate();
154
+
155
+ expect(result).toEqual({ ok: true, value: [packageRow] });
156
+ expect(fetch).toHaveBeenCalledWith('/preview/pack-index.json');
157
+ });
158
+ });
@@ -0,0 +1,126 @@
1
+ import type { CatalogEntry } from '@forgeax/engine-types';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { createCatalogSource } from '../catalog-source';
4
+ import { parseCatalog } from '../registry/catalog';
5
+
6
+ const entry: CatalogEntry = {
7
+ guid: '11111111-1111-4111-8111-111111111111',
8
+ kind: 'mesh',
9
+ name: 'unloaded',
10
+ packageUrl: '/assets/unloaded.pack.json',
11
+ sourcePath: 'assets/unloaded.glb',
12
+ };
13
+
14
+ const richEntry: CatalogEntry = {
15
+ ...entry,
16
+ packageId: 'pkg/runtime',
17
+ provenance: { provider: 'runtime-fixture', version: '1.0.0' },
18
+ revision: { digest: 'sha256:runtime', observedAt: 3, rootId: 'root-runtime' },
19
+ sourceKey: 'runtime/main',
20
+ sourceIndex: 0,
21
+ relations: [
22
+ {
23
+ from: { type: 'asset', id: '11111111-1111-4111-8111-111111111111' },
24
+ to: { type: 'asset', id: '22222222-2222-4222-8222-222222222222' },
25
+ type: 'references',
26
+ provenance: { provider: 'runtime-fixture', version: '1.0.0' },
27
+ },
28
+ ],
29
+ diagnostics: [{ code: 'runtime-warning', severity: 'warning', hint: 'refresh catalog' }],
30
+ };
31
+
32
+ describe('CatalogSource', () => {
33
+ it('enumerates every development or build row without loading its payload', async () => {
34
+ for (const url of ['/__pack/index', '/pack-index.json']) {
35
+ const fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => [entry] });
36
+ const source = createCatalogSource({ url, fetch });
37
+ const result = await source.enumerate();
38
+
39
+ expect(result).toEqual({ ok: true, value: [entry] });
40
+ expect(fetch).toHaveBeenCalledWith(url);
41
+ }
42
+ });
43
+
44
+ it('reports an unconfigured source instead of an empty catalog', async () => {
45
+ const result = await createCatalogSource({}).enumerate();
46
+
47
+ expect(result.ok).toBe(false);
48
+ if (!result.ok) expect(result.error.code).toBe('catalog-source-unconfigured');
49
+ });
50
+
51
+ it('uses the shared parser to distinguish malformed JSON from a fetch failure', async () => {
52
+ const fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ entries: [] }) });
53
+
54
+ const result = await createCatalogSource({ url: '/pack-index.json', fetch }).enumerate();
55
+
56
+ expect(result.ok).toBe(false);
57
+ if (!result.ok) expect(result.error.code).toBe('asset-parse-failed');
58
+ });
59
+
60
+ it('keeps static and URL catalog rows in the same producer-fact shape', async () => {
61
+ const fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => [richEntry] });
62
+ const source = createCatalogSource({ url: '/pack-index.json', fetch });
63
+ const urlResult = await source.enumerate();
64
+ const staticResult = await createCatalogSource({ entries: [richEntry] }).enumerate();
65
+
66
+ expect(urlResult).toEqual(staticResult);
67
+ expect(urlResult).toEqual({ ok: true, value: [richEntry] });
68
+ });
69
+
70
+ it('rejects a catalog row without a locator instead of returning an empty catalog', () => {
71
+ const result = parseCatalog([
72
+ {
73
+ guid: richEntry.guid,
74
+ kind: richEntry.kind,
75
+ packageId: richEntry.packageId,
76
+ },
77
+ ]);
78
+
79
+ expect(result.ok).toBe(false);
80
+ if (!result.ok) {
81
+ expect(result.error.code).toBe('asset-parse-failed');
82
+ expect(result.error.expected).toContain('packageUrl');
83
+ expect(result.error.hint).toBeTruthy();
84
+ }
85
+ });
86
+
87
+ it('reports a stale revision through structured fields', async () => {
88
+ const fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => [richEntry] });
89
+ const result = await createCatalogSource({
90
+ url: '/pack-index.json',
91
+ fetch,
92
+ expectedRevision: { digest: 'sha256:expected', observedAt: 4, rootId: 'root-runtime' },
93
+ }).enumerate();
94
+
95
+ expect(result.ok).toBe(false);
96
+ if (!result.ok) {
97
+ expect(result.error.code).toBe('asset-parse-failed');
98
+ expect(result.error.expected).toContain('revision');
99
+ expect(result.error.hint).toContain('verified');
100
+ expect(result.error.detail).toMatchObject({
101
+ expectedRevision: { digest: 'sha256:expected' },
102
+ actualRevisions: [{ digest: 'sha256:runtime' }],
103
+ });
104
+ }
105
+ });
106
+
107
+ it('uses an idempotent no-op subscription for static catalogs', () => {
108
+ const source = createCatalogSource({ entries: [entry] });
109
+ const listener = vi.fn();
110
+ const dispose = source.subscribe(listener);
111
+
112
+ dispose();
113
+ dispose();
114
+ expect(listener).not.toHaveBeenCalled();
115
+ });
116
+
117
+ it('forwards an optional catalog subscription', () => {
118
+ const listener = vi.fn();
119
+ const unsubscribe = vi.fn();
120
+ const subscribe = vi.fn(() => unsubscribe);
121
+ const source = createCatalogSource({ subscribe });
122
+
123
+ expect(source.subscribe(listener)).toBe(unsubscribe);
124
+ expect(subscribe).toHaveBeenCalledWith(listener);
125
+ });
126
+ });
@@ -0,0 +1,65 @@
1
+ import type { CatalogDelta, CatalogEntry } from '@forgeax/engine-types';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { CatalogReplica } from '../registry/catalog-state.js';
4
+
5
+ const entry: CatalogEntry = {
6
+ guid: '11111111-1111-4111-8111-111111111111',
7
+ packageUrl: '/preview/fixture',
8
+ kind: 'mesh',
9
+ sourcePath: 'fixture.source',
10
+ };
11
+
12
+ describe('CatalogReplica subscription order', () => {
13
+ it('does not lose an early delta and does not double-deliver a listener', async () => {
14
+ const listeners = new Set<(delta: CatalogDelta) => void>();
15
+ const source = {
16
+ enumerate: async () => ({ ok: true as const, value: [entry] }),
17
+ subscribe: vi.fn((listener: (delta: CatalogDelta) => void) => {
18
+ listeners.add(listener);
19
+ return () => listeners.delete(listener);
20
+ }),
21
+ };
22
+ const replica = new CatalogReplica(source as never);
23
+ const received: CatalogDelta[] = [];
24
+ const dispose = replica.subscribe((delta) => received.push(delta));
25
+ await replica.start();
26
+
27
+ const delta: CatalogDelta = { added: [], changed: [entry], removed: [] };
28
+ for (const listener of listeners) listener(delta);
29
+ expect(received).toHaveLength(1);
30
+ dispose();
31
+ dispose();
32
+ for (const listener of listeners) listener(delta);
33
+ expect(received).toHaveLength(1);
34
+ expect(source.subscribe).toHaveBeenCalledTimes(1);
35
+ });
36
+
37
+ it('returns structured enumerate failure and can recover through reconcile', async () => {
38
+ const enumerate = vi
39
+ .fn()
40
+ .mockResolvedValueOnce({
41
+ ok: false as const,
42
+ error: {
43
+ code: 'catalog-enumerate-failed',
44
+ expected: 'a readable producer catalog',
45
+ hint: 'call reconcile after restoring the source',
46
+ },
47
+ })
48
+ .mockResolvedValueOnce({ ok: true as const, value: [entry] });
49
+ const replica = new CatalogReplica({ enumerate, subscribe: () => () => {} } as never);
50
+
51
+ const first = await replica.start();
52
+ expect(first).toMatchObject({
53
+ ok: false,
54
+ error: {
55
+ code: 'catalog-enumerate-failed',
56
+ expected: 'a readable producer catalog',
57
+ hint: expect.any(String),
58
+ },
59
+ });
60
+ expect(replica.snapshot().stale).toBe(true);
61
+ await expect(replica.reconcile()).resolves.toMatchObject({ ok: true });
62
+ expect(replica.snapshot()).toMatchObject({ stale: false, entries: [entry] });
63
+ expect(enumerate).toHaveBeenCalledTimes(2);
64
+ });
65
+ });
@@ -0,0 +1,28 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { AssetRegistry } from '../asset-registry';
3
+
4
+ describe('AssetRegistry.subscribeCatalog', () => {
5
+ it('isolates throwing listeners and makes unsubscribe idempotent', () => {
6
+ let emit: ((delta: { added: []; changed: []; removed: [] }) => void) | undefined;
7
+ const source = {
8
+ enumerate: async () => ({ ok: true as const, value: [] }),
9
+ subscribe: (listener: typeof emit) => {
10
+ emit = listener;
11
+ return () => {};
12
+ },
13
+ };
14
+ const assets = new AssetRegistry({} as never);
15
+ assets.setCatalogSource(source as never);
16
+ const broken = vi.fn(() => {
17
+ throw new Error('listener failed');
18
+ });
19
+ const received = vi.fn();
20
+ const dispose = assets.subscribeCatalog(broken);
21
+ assets.subscribeCatalog(received);
22
+
23
+ emit?.({ added: [], changed: [], removed: [] });
24
+ expect(received).toHaveBeenCalledTimes(1);
25
+ dispose();
26
+ dispose();
27
+ });
28
+ });
@@ -0,0 +1,34 @@
1
+ // @forgeax/engine-assets-runtime -- decoder error pass-through regression
2
+
3
+ import type { ImageError } from '@forgeax/engine-types';
4
+ import { err } from '@forgeax/engine-types';
5
+ import { describe, expect, it, vi } from 'vitest';
6
+ import { decodeImageBytes } from '../decode-image-bytes';
7
+
8
+ vi.mock('@forgeax/engine-image', () => ({
9
+ decodeImageInBrowser: vi.fn(),
10
+ }));
11
+
12
+ describe('decodeImageBytes decoder error pass-through', () => {
13
+ it('returns the decoder ImageError object unchanged', async () => {
14
+ const { decodeImageInBrowser } = await import('@forgeax/engine-image');
15
+ const decoderError = {
16
+ name: 'ImageError',
17
+ message: 'decoder failure',
18
+ code: 'image-decode-failed',
19
+ expected: 'decoder expected',
20
+ hint: 'decoder hint',
21
+ detail: {
22
+ code: 'image-decode-failed',
23
+ reason: 'decoder failure',
24
+ },
25
+ } as ImageError;
26
+ vi.mocked(decodeImageInBrowser).mockResolvedValue(err(decoderError));
27
+
28
+ const result = await decodeImageBytes(new Uint8Array([1]), 'image/png');
29
+
30
+ expect(result.ok).toBe(false);
31
+ if (result.ok) return;
32
+ expect(result.error).toBe(decoderError);
33
+ });
34
+ });