@pascal-app/core 0.9.1 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/README.md +17 -10
  2. package/dist/events/bus.d.ts +49 -2
  3. package/dist/events/bus.d.ts.map +1 -1
  4. package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
  5. package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
  6. package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
  7. package/dist/hooks/scene-registry/scene-registry.d.ts +8 -1
  8. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  9. package/dist/hooks/scene-registry/scene-registry.js +27 -1
  10. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +17 -1
  11. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.js +61 -8
  13. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +135 -28
  14. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  15. package/dist/hooks/spatial-grid/spatial-grid-manager.js +427 -271
  16. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +46 -1
  17. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  18. package/dist/hooks/spatial-grid/spatial-grid-sync.js +208 -9
  19. package/dist/hooks/spatial-grid/support-host-id.d.ts +3 -0
  20. package/dist/hooks/spatial-grid/support-host-id.d.ts.map +1 -0
  21. package/dist/hooks/spatial-grid/support-host-id.js +2 -0
  22. package/dist/hooks/spatial-grid/support-host-patch.d.ts +51 -0
  23. package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -0
  24. package/dist/hooks/spatial-grid/support-host-patch.js +164 -0
  25. package/dist/index.d.ts +33 -11
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +32 -10
  28. package/dist/lib/level-name.d.ts +4 -0
  29. package/dist/lib/level-name.d.ts.map +1 -0
  30. package/dist/lib/level-name.js +10 -0
  31. package/dist/lib/measurement-geometry.d.ts +23 -0
  32. package/dist/lib/measurement-geometry.d.ts.map +1 -0
  33. package/dist/lib/measurement-geometry.js +249 -0
  34. package/dist/lib/polygon-geometry.d.ts +0 -1
  35. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  36. package/dist/lib/polygon-geometry.js +0 -18
  37. package/dist/lib/selection-proxy.d.ts +4 -0
  38. package/dist/lib/selection-proxy.d.ts.map +1 -0
  39. package/dist/lib/selection-proxy.js +12 -0
  40. package/dist/lib/slab-polygon.d.ts +31 -2
  41. package/dist/lib/slab-polygon.d.ts.map +1 -1
  42. package/dist/lib/slab-polygon.js +557 -31
  43. package/dist/lib/slots.d.ts +14 -0
  44. package/dist/lib/slots.d.ts.map +1 -0
  45. package/dist/lib/slots.js +26 -0
  46. package/dist/lib/space-detection.d.ts +40 -6
  47. package/dist/lib/space-detection.d.ts.map +1 -1
  48. package/dist/lib/space-detection.js +574 -192
  49. package/dist/lib/terrain-brush.d.ts +163 -0
  50. package/dist/lib/terrain-brush.d.ts.map +1 -0
  51. package/dist/lib/terrain-brush.js +359 -0
  52. package/dist/lib/terrain-codec.d.ts +51 -0
  53. package/dist/lib/terrain-codec.d.ts.map +1 -0
  54. package/dist/lib/terrain-codec.js +167 -0
  55. package/dist/lib/terrain-field.d.ts +150 -0
  56. package/dist/lib/terrain-field.d.ts.map +1 -0
  57. package/dist/lib/terrain-field.js +248 -0
  58. package/dist/lib/terrain-raycast.d.ts +44 -0
  59. package/dist/lib/terrain-raycast.d.ts.map +1 -0
  60. package/dist/lib/terrain-raycast.js +188 -0
  61. package/dist/lib/terrain-source.d.ts +60 -0
  62. package/dist/lib/terrain-source.d.ts.map +1 -0
  63. package/dist/lib/terrain-source.js +90 -0
  64. package/dist/lib/terrain-support.d.ts +60 -0
  65. package/dist/lib/terrain-support.d.ts.map +1 -0
  66. package/dist/lib/terrain-support.js +150 -0
  67. package/dist/lib/wall-distance.d.ts +60 -0
  68. package/dist/lib/wall-distance.d.ts.map +1 -0
  69. package/dist/lib/wall-distance.js +85 -0
  70. package/dist/lib/zone-quantities.d.ts +21 -0
  71. package/dist/lib/zone-quantities.d.ts.map +1 -0
  72. package/dist/lib/zone-quantities.js +428 -0
  73. package/dist/material-library.d.ts +28 -1
  74. package/dist/material-library.d.ts.map +1 -1
  75. package/dist/material-library.js +2233 -441
  76. package/dist/registry/handles.d.ts +91 -3
  77. package/dist/registry/handles.d.ts.map +1 -1
  78. package/dist/registry/index.d.ts +3 -3
  79. package/dist/registry/index.d.ts.map +1 -1
  80. package/dist/registry/index.js +1 -1
  81. package/dist/registry/registry.d.ts +46 -3
  82. package/dist/registry/registry.d.ts.map +1 -1
  83. package/dist/registry/registry.js +82 -1
  84. package/dist/registry/subtree.d.ts.map +1 -1
  85. package/dist/registry/subtree.js +8 -1
  86. package/dist/registry/types.d.ts +781 -39
  87. package/dist/registry/types.d.ts.map +1 -1
  88. package/dist/schema/index.d.ts +27 -8
  89. package/dist/schema/index.d.ts.map +1 -1
  90. package/dist/schema/index.js +23 -6
  91. package/dist/schema/material.d.ts +3 -2
  92. package/dist/schema/material.d.ts.map +1 -1
  93. package/dist/schema/material.js +7 -5
  94. package/dist/schema/nodes/box-vent.d.ts +2 -2
  95. package/dist/schema/nodes/cabinet.d.ts +536 -0
  96. package/dist/schema/nodes/cabinet.d.ts.map +1 -0
  97. package/dist/schema/nodes/cabinet.js +139 -0
  98. package/dist/schema/nodes/ceiling.d.ts +4 -3
  99. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  100. package/dist/schema/nodes/ceiling.js +11 -1
  101. package/dist/schema/nodes/chimney.d.ts +4 -4
  102. package/dist/schema/nodes/column.d.ts +4 -2
  103. package/dist/schema/nodes/column.d.ts.map +1 -1
  104. package/dist/schema/nodes/column.js +5 -0
  105. package/dist/schema/nodes/construction-dimension.d.ts +195 -0
  106. package/dist/schema/nodes/construction-dimension.d.ts.map +1 -0
  107. package/dist/schema/nodes/construction-dimension.js +151 -0
  108. package/dist/schema/nodes/cupola.d.ts +2 -2
  109. package/dist/schema/nodes/door.d.ts +42 -5
  110. package/dist/schema/nodes/door.d.ts.map +1 -1
  111. package/dist/schema/nodes/door.js +32 -0
  112. package/dist/schema/nodes/dormer.d.ts +8 -8
  113. package/dist/schema/nodes/dormer.js +1 -1
  114. package/dist/schema/nodes/downspout.d.ts +2 -2
  115. package/dist/schema/nodes/duct-fitting.d.ts +91 -0
  116. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
  117. package/dist/schema/nodes/duct-fitting.js +92 -0
  118. package/dist/schema/nodes/duct-segment.d.ts +61 -0
  119. package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
  120. package/dist/schema/nodes/duct-segment.js +73 -0
  121. package/dist/schema/nodes/duct-terminal.d.ts +58 -0
  122. package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
  123. package/dist/schema/nodes/duct-terminal.js +53 -0
  124. package/dist/schema/nodes/elevator.d.ts +3 -2
  125. package/dist/schema/nodes/elevator.d.ts.map +1 -1
  126. package/dist/schema/nodes/elevator.js +3 -0
  127. package/dist/schema/nodes/eyebrow-vent.d.ts +2 -2
  128. package/dist/schema/nodes/fence.d.ts +20 -2
  129. package/dist/schema/nodes/fence.d.ts.map +1 -1
  130. package/dist/schema/nodes/fence.js +33 -2
  131. package/dist/schema/nodes/gutter.d.ts +2 -2
  132. package/dist/schema/nodes/hvac-equipment.d.ts +64 -0
  133. package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
  134. package/dist/schema/nodes/hvac-equipment.js +57 -0
  135. package/dist/schema/nodes/item.d.ts +9 -0
  136. package/dist/schema/nodes/item.d.ts.map +1 -1
  137. package/dist/schema/nodes/item.js +24 -0
  138. package/dist/schema/nodes/level.d.ts +2 -1
  139. package/dist/schema/nodes/level.d.ts.map +1 -1
  140. package/dist/schema/nodes/level.js +12 -31
  141. package/dist/schema/nodes/lineset.d.ts +42 -0
  142. package/dist/schema/nodes/lineset.d.ts.map +1 -0
  143. package/dist/schema/nodes/lineset.js +38 -0
  144. package/dist/schema/nodes/liquid-line.d.ts +34 -0
  145. package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
  146. package/dist/schema/nodes/liquid-line.js +24 -0
  147. package/dist/schema/nodes/measurement.d.ts +306 -0
  148. package/dist/schema/nodes/measurement.d.ts.map +1 -0
  149. package/dist/schema/nodes/measurement.js +87 -0
  150. package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
  151. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
  152. package/dist/schema/nodes/pipe-fitting.js +45 -0
  153. package/dist/schema/nodes/pipe-segment.d.ts +48 -0
  154. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
  155. package/dist/schema/nodes/pipe-segment.js +36 -0
  156. package/dist/schema/nodes/pipe-trap.d.ts +43 -0
  157. package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
  158. package/dist/schema/nodes/pipe-trap.js +36 -0
  159. package/dist/schema/nodes/ridge-vent.d.ts +18 -3
  160. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
  161. package/dist/schema/nodes/ridge-vent.js +221 -3
  162. package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
  163. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
  164. package/dist/schema/nodes/roof-segment-shape.js +318 -0
  165. package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
  166. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
  167. package/dist/schema/nodes/roof-segment-walls.js +330 -0
  168. package/dist/schema/nodes/roof-segment.d.ts +84 -8
  169. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  170. package/dist/schema/nodes/roof-segment.js +290 -5
  171. package/dist/schema/nodes/roof.d.ts +8 -8
  172. package/dist/schema/nodes/shelf.d.ts +4 -2
  173. package/dist/schema/nodes/shelf.d.ts.map +1 -1
  174. package/dist/schema/nodes/shelf.js +8 -0
  175. package/dist/schema/nodes/site.d.ts +9 -0
  176. package/dist/schema/nodes/site.d.ts.map +1 -1
  177. package/dist/schema/nodes/site.js +8 -6
  178. package/dist/schema/nodes/skylight.d.ts +10 -10
  179. package/dist/schema/nodes/slab.d.ts +8 -2
  180. package/dist/schema/nodes/slab.d.ts.map +1 -1
  181. package/dist/schema/nodes/slab.js +18 -2
  182. package/dist/schema/nodes/solar-panel.d.ts +4 -4
  183. package/dist/schema/nodes/spawn.d.ts +1 -0
  184. package/dist/schema/nodes/spawn.d.ts.map +1 -1
  185. package/dist/schema/nodes/spawn.js +2 -0
  186. package/dist/schema/nodes/stair-segment.d.ts +4 -4
  187. package/dist/schema/nodes/stair.d.ts +16 -13
  188. package/dist/schema/nodes/stair.d.ts.map +1 -1
  189. package/dist/schema/nodes/stair.js +11 -1
  190. package/dist/schema/nodes/structural-grid.d.ts +27 -0
  191. package/dist/schema/nodes/structural-grid.d.ts.map +1 -0
  192. package/dist/schema/nodes/structural-grid.js +17 -0
  193. package/dist/schema/nodes/turbine-vent.d.ts +2 -2
  194. package/dist/schema/nodes/wall.d.ts +248 -6
  195. package/dist/schema/nodes/wall.d.ts.map +1 -1
  196. package/dist/schema/nodes/wall.js +266 -0
  197. package/dist/schema/nodes/window.d.ts +40 -3
  198. package/dist/schema/nodes/window.d.ts.map +1 -1
  199. package/dist/schema/nodes/window.js +30 -0
  200. package/dist/schema/nodes/zone.d.ts +22 -0
  201. package/dist/schema/nodes/zone.d.ts.map +1 -1
  202. package/dist/schema/nodes/zone.js +21 -0
  203. package/dist/schema/scene-material.d.ts +41 -0
  204. package/dist/schema/scene-material.d.ts.map +1 -0
  205. package/dist/schema/scene-material.js +9 -0
  206. package/dist/schema/terrain.d.ts +28 -0
  207. package/dist/schema/terrain.d.ts.map +1 -0
  208. package/dist/schema/terrain.js +30 -0
  209. package/dist/schema/types.d.ts +1300 -97
  210. package/dist/schema/types.d.ts.map +1 -1
  211. package/dist/schema/types.js +27 -0
  212. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts +80 -0
  213. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts.map +1 -0
  214. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.js +89 -0
  215. package/dist/services/alignment-anchors.d.ts.map +1 -1
  216. package/dist/services/alignment-anchors.js +30 -1
  217. package/dist/services/alignment.d.ts +5 -0
  218. package/dist/services/alignment.d.ts.map +1 -1
  219. package/dist/services/alignment.js +2 -0
  220. package/dist/services/hosting.d.ts +8 -0
  221. package/dist/services/hosting.d.ts.map +1 -1
  222. package/dist/services/hosting.js +13 -4
  223. package/dist/services/index.d.ts +9 -2
  224. package/dist/services/index.d.ts.map +1 -1
  225. package/dist/services/index.js +9 -2
  226. package/dist/services/level-height.d.ts +27 -0
  227. package/dist/services/level-height.d.ts.map +1 -0
  228. package/dist/services/level-height.js +94 -0
  229. package/dist/services/opening-guides.d.ts +141 -0
  230. package/dist/services/opening-guides.d.ts.map +1 -0
  231. package/dist/services/opening-guides.js +267 -0
  232. package/dist/services/port-connectivity.d.ts +115 -0
  233. package/dist/services/port-connectivity.d.ts.map +1 -0
  234. package/dist/services/port-connectivity.js +300 -0
  235. package/dist/services/riser-diagram.d.ts +40 -0
  236. package/dist/services/riser-diagram.d.ts.map +1 -0
  237. package/dist/services/riser-diagram.js +110 -0
  238. package/dist/services/snap.d.ts +9 -1
  239. package/dist/services/snap.d.ts.map +1 -1
  240. package/dist/services/snap.js +21 -1
  241. package/dist/services/storey.d.ts +103 -0
  242. package/dist/services/storey.d.ts.map +1 -0
  243. package/dist/services/storey.js +296 -0
  244. package/dist/services/system-graph.d.ts +30 -0
  245. package/dist/services/system-graph.d.ts.map +1 -0
  246. package/dist/services/system-graph.js +164 -0
  247. package/dist/services/validate-dwv.d.ts +32 -0
  248. package/dist/services/validate-dwv.d.ts.map +1 -0
  249. package/dist/services/validate-dwv.js +118 -0
  250. package/dist/store/actions/node-actions.d.ts.map +1 -1
  251. package/dist/store/actions/node-actions.js +141 -4
  252. package/dist/store/history-control.d.ts +31 -0
  253. package/dist/store/history-control.d.ts.map +1 -1
  254. package/dist/store/history-control.js +130 -0
  255. package/dist/store/use-live-terrain.d.ts +65 -0
  256. package/dist/store/use-live-terrain.d.ts.map +1 -0
  257. package/dist/store/use-live-terrain.js +31 -0
  258. package/dist/store/use-live-transforms.d.ts +8 -0
  259. package/dist/store/use-live-transforms.d.ts.map +1 -1
  260. package/dist/store/use-scene.d.ts +47 -3
  261. package/dist/store/use-scene.d.ts.map +1 -1
  262. package/dist/store/use-scene.js +992 -55
  263. package/dist/systems/elevator/elevator-opening-system.d.ts +1 -0
  264. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -1
  265. package/dist/systems/elevator/elevator-opening-system.js +27 -21
  266. package/dist/systems/elevator/elevator-service.d.ts +0 -2
  267. package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
  268. package/dist/systems/elevator/elevator-service.js +2 -24
  269. package/dist/systems/fence/fence-centerline.d.ts +12 -0
  270. package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
  271. package/dist/systems/fence/fence-centerline.js +37 -0
  272. package/dist/systems/fence/fence-spline.d.ts +39 -0
  273. package/dist/systems/fence/fence-spline.d.ts.map +1 -0
  274. package/dist/systems/fence/fence-spline.js +196 -0
  275. package/dist/systems/slab/slab-placement.d.ts +11 -0
  276. package/dist/systems/slab/slab-placement.d.ts.map +1 -0
  277. package/dist/systems/slab/slab-placement.js +14 -0
  278. package/dist/systems/slab/slab-support.d.ts +137 -0
  279. package/dist/systems/slab/slab-support.d.ts.map +1 -0
  280. package/dist/systems/slab/slab-support.js +557 -0
  281. package/dist/systems/stair/stair-footprint.d.ts.map +1 -1
  282. package/dist/systems/stair/stair-footprint.js +21 -11
  283. package/dist/systems/stair/stair-opening-preview.d.ts +1300 -505
  284. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  285. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  286. package/dist/systems/stair/stair-opening-sync.js +19 -33
  287. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
  288. package/dist/systems/stair/stair-opening-system.js +32 -4
  289. package/dist/systems/stair/stair-rise.d.ts +22 -0
  290. package/dist/systems/stair/stair-rise.d.ts.map +1 -0
  291. package/dist/systems/stair/stair-rise.js +79 -0
  292. package/dist/systems/wall/wall-curve.d.ts +10 -0
  293. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  294. package/dist/systems/wall/wall-curve.js +1 -1
  295. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  296. package/dist/systems/wall/wall-mitering.js +44 -8
  297. package/dist/systems/wall/wall-top.d.ts +40 -0
  298. package/dist/systems/wall/wall-top.d.ts.map +1 -0
  299. package/dist/systems/wall/wall-top.js +46 -0
  300. package/dist/utils/clone-scene-graph.d.ts +1 -0
  301. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  302. package/dist/utils/clone-scene-graph.js +56 -5
  303. package/dist/utils/heal-scene-graph.d.ts +18 -0
  304. package/dist/utils/heal-scene-graph.d.ts.map +1 -0
  305. package/dist/utils/heal-scene-graph.js +106 -0
  306. package/dist/validation/validate-build-json.d.ts +3 -0
  307. package/dist/validation/validate-build-json.d.ts.map +1 -1
  308. package/dist/validation/validate-build-json.js +90 -5
  309. package/package.json +4 -4
  310. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +0 -2
  311. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +0 -1
  312. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +0 -273
  313. package/dist/lib/space-detection-pause.test.d.ts +0 -2
  314. package/dist/lib/space-detection-pause.test.d.ts.map +0 -1
  315. package/dist/lib/space-detection-pause.test.js +0 -42
  316. package/dist/lib/space-detection.test.d.ts +0 -2
  317. package/dist/lib/space-detection.test.d.ts.map +0 -1
  318. package/dist/lib/space-detection.test.js +0 -77
  319. package/dist/registry/registry.test.d.ts +0 -2
  320. package/dist/registry/registry.test.d.ts.map +0 -1
  321. package/dist/registry/registry.test.js +0 -183
  322. package/dist/registry/relations-resolver.test.d.ts +0 -2
  323. package/dist/registry/relations-resolver.test.d.ts.map +0 -1
  324. package/dist/registry/relations-resolver.test.js +0 -183
  325. package/dist/registry/scene-api.test.d.ts +0 -2
  326. package/dist/registry/scene-api.test.d.ts.map +0 -1
  327. package/dist/registry/scene-api.test.js +0 -183
  328. package/dist/registry/subtree.test.d.ts +0 -2
  329. package/dist/registry/subtree.test.d.ts.map +0 -1
  330. package/dist/registry/subtree.test.js +0 -133
  331. package/dist/schema/asset-url.test.d.ts +0 -2
  332. package/dist/schema/asset-url.test.d.ts.map +0 -1
  333. package/dist/schema/asset-url.test.js +0 -134
  334. package/dist/services/alignment-anchors.test.d.ts +0 -2
  335. package/dist/services/alignment-anchors.test.d.ts.map +0 -1
  336. package/dist/services/alignment-anchors.test.js +0 -383
  337. package/dist/services/alignment.test.d.ts +0 -2
  338. package/dist/services/alignment.test.d.ts.map +0 -1
  339. package/dist/services/alignment.test.js +0 -92
  340. package/dist/services/drag-session.test.d.ts +0 -2
  341. package/dist/services/drag-session.test.d.ts.map +0 -1
  342. package/dist/services/drag-session.test.js +0 -219
  343. package/dist/services/hosting.test.d.ts +0 -2
  344. package/dist/services/hosting.test.d.ts.map +0 -1
  345. package/dist/services/hosting.test.js +0 -199
  346. package/dist/services/movement.test.d.ts +0 -2
  347. package/dist/services/movement.test.d.ts.map +0 -1
  348. package/dist/services/movement.test.js +0 -133
  349. package/dist/services/single-undo-dance.test.d.ts +0 -2
  350. package/dist/services/single-undo-dance.test.d.ts.map +0 -1
  351. package/dist/services/single-undo-dance.test.js +0 -210
  352. package/dist/services/snap.test.d.ts +0 -2
  353. package/dist/services/snap.test.d.ts.map +0 -1
  354. package/dist/services/snap.test.js +0 -95
  355. package/dist/store/actions/node-mutation-sanitize.test.d.ts +0 -2
  356. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +0 -1
  357. package/dist/store/actions/node-mutation-sanitize.test.js +0 -147
  358. package/dist/store/actions/reparent.test.d.ts +0 -2
  359. package/dist/store/actions/reparent.test.d.ts.map +0 -1
  360. package/dist/store/actions/reparent.test.js +0 -212
  361. package/dist/store/use-scene-elevator-migration.test.d.ts +0 -2
  362. package/dist/store/use-scene-elevator-migration.test.d.ts.map +0 -1
  363. package/dist/store/use-scene-elevator-migration.test.js +0 -109
  364. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +0 -2
  365. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +0 -1
  366. package/dist/systems/elevator/elevator-opening-sync.test.js +0 -146
  367. package/dist/systems/elevator/elevator-runtime.test.d.ts +0 -2
  368. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +0 -1
  369. package/dist/systems/elevator/elevator-runtime.test.js +0 -64
  370. package/dist/systems/stair/stair-opening-preview.test.d.ts +0 -2
  371. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +0 -1
  372. package/dist/systems/stair/stair-opening-preview.test.js +0 -80
  373. package/dist/systems/stair/stair-opening-sync.test.d.ts +0 -2
  374. package/dist/systems/stair/stair-opening-sync.test.d.ts.map +0 -1
  375. package/dist/systems/stair/stair-opening-sync.test.js +0 -446
  376. package/dist/utils/clone-scene-graph.test.d.ts +0 -2
  377. package/dist/utils/clone-scene-graph.test.d.ts.map +0 -1
  378. package/dist/utils/clone-scene-graph.test.js +0 -59
@@ -1,183 +0,0 @@
1
- import { beforeEach, describe, expect, test } from 'bun:test';
2
- import { z } from 'zod';
3
- import { nodeRegistry, registerNode } from './registry';
4
- import { cascadeDirty, collectDescendants } from './relations-resolver';
5
- const id = (s) => s;
6
- function makeDef(kind, relations, overrides = {}) {
7
- return {
8
- kind,
9
- schemaVersion: 1,
10
- schema: z.object({ type: z.literal(kind) }),
11
- category: 'utility',
12
- defaults: () => ({}),
13
- capabilities: {},
14
- relations,
15
- renderer: { kind: 'parametric', module: async () => ({ default: () => null }) },
16
- ...overrides,
17
- };
18
- }
19
- function makeNode(kind, idStr, extra = {}) {
20
- return {
21
- id: id(idStr),
22
- type: kind,
23
- parentId: null,
24
- visible: true,
25
- ...extra,
26
- };
27
- }
28
- function makeFakeScene(nodes) {
29
- return {
30
- get: ((nid) => nodes[nid]),
31
- nodes: () => nodes,
32
- update: () => { },
33
- upsert: () => id(''),
34
- delete: () => { },
35
- restore: () => { },
36
- restoreAll: () => { },
37
- markDirty: () => { },
38
- pauseHistory: () => { },
39
- resumeHistory: () => { },
40
- getSubtree: () => null,
41
- cloneNodesInto: () => null,
42
- };
43
- }
44
- describe('cascadeDirty', () => {
45
- beforeEach(() => {
46
- nodeRegistry._reset();
47
- });
48
- test('starting node alone when registry is empty', () => {
49
- const scene = makeFakeScene({ a: makeNode('unknown', 'a') });
50
- const dirty = cascadeDirty(id('a'), { scene });
51
- expect(Array.from(dirty)).toEqual([id('a')]);
52
- });
53
- test('starting node alone when no relations declared', () => {
54
- registerNode(makeDef('thing'));
55
- const scene = makeFakeScene({ a: makeNode('thing', 'a') });
56
- const dirty = cascadeDirty(id('a'), { scene });
57
- expect(Array.from(dirty)).toEqual([id('a')]);
58
- });
59
- test('hosts cascade marks matching children dirty', () => {
60
- registerNode(makeDef('wall', { hosts: ['door', 'window'] }));
61
- registerNode(makeDef('door'));
62
- registerNode(makeDef('window'));
63
- registerNode(makeDef('lamp'));
64
- const wall = makeNode('wall', 'w1', {
65
- children: [id('d1'), id('w2'), id('l1')],
66
- });
67
- const scene = makeFakeScene({
68
- w1: wall,
69
- d1: makeNode('door', 'd1', { parentId: id('w1') }),
70
- w2: makeNode('window', 'w2', { parentId: id('w1') }),
71
- l1: makeNode('lamp', 'l1', { parentId: id('w1') }), // not in hosts list
72
- });
73
- const dirty = cascadeDirty(id('w1'), { scene });
74
- const ids = Array.from(dirty).sort();
75
- expect(ids).toEqual([id('d1'), id('w1'), id('w2')]); // l1 excluded
76
- });
77
- test('hosts cascade is recursive but bounded by maxDepth', () => {
78
- registerNode(makeDef('a', { hosts: ['a'] })); // a hosts more a
79
- const nodes = {};
80
- for (let i = 0; i < 25; i++) {
81
- const childId = i < 24 ? id(`a${i + 1}`) : undefined;
82
- nodes[`a${i}`] = makeNode('a', `a${i}`, {
83
- children: childId ? [childId] : [],
84
- });
85
- }
86
- const scene = makeFakeScene(nodes);
87
- const dirty = cascadeDirty(id('a0'), { scene, maxDepth: 5 });
88
- expect(dirty.size).toBe(6); // a0 + 5 descendants
89
- });
90
- test('affectsSpatial cascade uses spatialQuery to find neighbors', () => {
91
- registerNode(makeDef('wall', { affectsSpatial: ['slab', 'zone'] }));
92
- registerNode(makeDef('slab'));
93
- registerNode(makeDef('zone'));
94
- const scene = makeFakeScene({
95
- w1: makeNode('wall', 'w1'),
96
- s1: makeNode('slab', 's1'),
97
- z1: makeNode('zone', 'z1'),
98
- unrelated: makeNode('door', 'unrelated'),
99
- });
100
- const spatialQuery = (node, kinds) => {
101
- if (node.id !== id('w1'))
102
- return [];
103
- const matches = [];
104
- if (kinds.includes('slab'))
105
- matches.push(id('s1'));
106
- if (kinds.includes('zone'))
107
- matches.push(id('z1'));
108
- return matches;
109
- };
110
- const dirty = cascadeDirty(id('w1'), { scene, spatialQuery });
111
- expect(Array.from(dirty).sort()).toEqual([id('s1'), id('w1'), id('z1')]);
112
- });
113
- test('affectsSpatial is a no-op when no spatialQuery is provided', () => {
114
- registerNode(makeDef('wall', { affectsSpatial: ['slab'] }));
115
- const scene = makeFakeScene({ w1: makeNode('wall', 'w1') });
116
- const dirty = cascadeDirty(id('w1'), { scene });
117
- expect(Array.from(dirty)).toEqual([id('w1')]); // spatial branch silently skipped
118
- });
119
- test('cycle in hosts cascade does not loop forever', () => {
120
- registerNode(makeDef('a', { hosts: ['a'] }));
121
- const nodes = {
122
- a1: makeNode('a', 'a1', { children: [id('a2')] }),
123
- a2: makeNode('a', 'a2', { children: [id('a1')] }), // cycle
124
- };
125
- const scene = makeFakeScene(nodes);
126
- const dirty = cascadeDirty(id('a1'), { scene });
127
- expect(dirty.size).toBe(2);
128
- expect(dirty.has(id('a1'))).toBe(true);
129
- expect(dirty.has(id('a2'))).toBe(true);
130
- });
131
- test('custom childQuery overrides the default node.children lookup', () => {
132
- registerNode(makeDef('wall', { hosts: ['door'] }));
133
- registerNode(makeDef('door'));
134
- const scene = makeFakeScene({
135
- w1: makeNode('wall', 'w1'), // no children field
136
- d1: makeNode('door', 'd1', { parentId: id('w1') }),
137
- });
138
- // childQuery iterates the scene to find parentId matches — what you would
139
- // do for kinds that don't carry an explicit children array.
140
- const childQuery = (node) => {
141
- const result = [];
142
- for (const candidate of [id('d1')]) {
143
- const c = scene.get(candidate);
144
- if (c && c.parentId === node.id)
145
- result.push(c.id);
146
- }
147
- return result;
148
- };
149
- const dirty = cascadeDirty(id('w1'), { scene, childQuery });
150
- expect(Array.from(dirty).sort()).toEqual([id('d1'), id('w1')]);
151
- });
152
- });
153
- describe('collectDescendants', () => {
154
- beforeEach(() => {
155
- nodeRegistry._reset();
156
- });
157
- test('returns just the start when no children', () => {
158
- const scene = makeFakeScene({ a: makeNode('thing', 'a') });
159
- const result = collectDescendants(id('a'), { scene });
160
- expect(Array.from(result)).toEqual([id('a')]);
161
- });
162
- test('returns full subtree regardless of relations declarations', () => {
163
- // No def registered — descendants still found via the children array.
164
- const scene = makeFakeScene({
165
- root: makeNode('thing', 'root', { children: [id('c1'), id('c2')] }),
166
- c1: makeNode('thing', 'c1', { children: [id('g1')] }),
167
- c2: makeNode('thing', 'c2'),
168
- g1: makeNode('thing', 'g1'),
169
- });
170
- const result = collectDescendants(id('root'), { scene });
171
- expect(Array.from(result).sort()).toEqual([id('c1'), id('c2'), id('g1'), id('root')]);
172
- });
173
- test('respects maxDepth', () => {
174
- const scene = makeFakeScene({
175
- a: makeNode('thing', 'a', { children: [id('b')] }),
176
- b: makeNode('thing', 'b', { children: [id('c')] }),
177
- c: makeNode('thing', 'c', { children: [id('d')] }),
178
- d: makeNode('thing', 'd'),
179
- });
180
- const result = collectDescendants(id('a'), { scene, maxDepth: 2 });
181
- expect(Array.from(result).sort()).toEqual([id('a'), id('b'), id('c')]); // d truncated
182
- });
183
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=scene-api.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scene-api.test.d.ts","sourceRoot":"","sources":["../../src/registry/scene-api.test.ts"],"names":[],"mappings":""}
@@ -1,183 +0,0 @@
1
- import { beforeEach, describe, expect, test } from 'bun:test';
2
- import { resetSceneHistoryPauseDepth } from '../store/history-control';
3
- import { createSceneApi } from './scene-api';
4
- function makeFakeStore(initial = {}) {
5
- const state = {
6
- nodes: { ...initial },
7
- rootNodeIds: [],
8
- dirtyNodes: new Set(),
9
- createNode(node) {
10
- state.nodes[node.id] = node;
11
- },
12
- updateNode(id, data) {
13
- const existing = state.nodes[id];
14
- if (existing)
15
- state.nodes[id] = { ...existing, ...data };
16
- },
17
- deleteNode(id) {
18
- delete state.nodes[id];
19
- },
20
- markDirty(id) {
21
- state.dirtyNodes.add(id);
22
- },
23
- };
24
- let paused = 0;
25
- const temporal = {
26
- getState: () => ({
27
- pause: () => {
28
- paused += 1;
29
- },
30
- resume: () => {
31
- paused -= 1;
32
- },
33
- }),
34
- };
35
- const store = {
36
- getState: () => state,
37
- temporal,
38
- _state: state,
39
- _pausedCount: () => paused,
40
- };
41
- return store;
42
- }
43
- function makeNode(id, extra = {}) {
44
- return { id, type: 'site', parentId: null, visible: true, ...extra };
45
- }
46
- // Tests use short string IDs ("a", "b") for readability. The store's
47
- // AnyNodeId is a branded template-literal type — cast at the boundary.
48
- const id = (s) => s;
49
- const nodes = (store) => store._state.nodes;
50
- describe('SceneApi', () => {
51
- beforeEach(() => {
52
- resetSceneHistoryPauseDepth();
53
- });
54
- test('get reads node from store', () => {
55
- const store = makeFakeStore({ a: makeNode('a') });
56
- const api = createSceneApi(store);
57
- expect(api.get(id('a'))).toEqual(makeNode('a'));
58
- expect(api.get(id('missing'))).toBeUndefined();
59
- });
60
- test('update applies patch via store.updateNode', () => {
61
- const store = makeFakeStore({ a: makeNode('a', { visible: true }) });
62
- const api = createSceneApi(store);
63
- api.update(id('a'), { visible: false });
64
- expect(nodes(store)['a']).toMatchObject({ visible: false });
65
- });
66
- test('upsert calls createNode and returns the id', () => {
67
- const store = makeFakeStore();
68
- const api = createSceneApi(store);
69
- const returnedId = api.upsert(makeNode('a'));
70
- expect(returnedId).toBe(id('a'));
71
- expect(nodes(store)['a']).toBeDefined();
72
- });
73
- test('delete removes node from store', () => {
74
- const store = makeFakeStore({ a: makeNode('a') });
75
- const api = createSceneApi(store);
76
- api.delete(id('a'));
77
- expect(nodes(store)['a']).toBeUndefined();
78
- });
79
- test('markDirty forwards to store', () => {
80
- const store = makeFakeStore({ a: makeNode('a') });
81
- const api = createSceneApi(store);
82
- api.markDirty(id('a'));
83
- expect(store._state.dirtyNodes.has(id('a'))).toBe(true);
84
- });
85
- test('pauseHistory and resumeHistory bracket store.temporal pause/resume', () => {
86
- const store = makeFakeStore();
87
- const api = createSceneApi(store);
88
- expect(store._pausedCount()).toBe(0);
89
- api.pauseHistory();
90
- expect(store._pausedCount()).toBe(1);
91
- api.resumeHistory();
92
- expect(store._pausedCount()).toBe(0);
93
- });
94
- test('nested pause/resume use a depth counter (single pause call to temporal)', () => {
95
- const store = makeFakeStore();
96
- const api = createSceneApi(store);
97
- api.pauseHistory();
98
- api.pauseHistory();
99
- expect(store._pausedCount()).toBe(1); // only one actual pause
100
- api.resumeHistory();
101
- expect(store._pausedCount()).toBe(1); // still paused — inner resume
102
- api.resumeHistory();
103
- expect(store._pausedCount()).toBe(0);
104
- });
105
- });
106
- describe('SceneApi snapshot / restore', () => {
107
- beforeEach(() => {
108
- resetSceneHistoryPauseDepth();
109
- });
110
- test('restore returns a touched node to its pre-pause state', () => {
111
- const store = makeFakeStore({ a: makeNode('a', { visible: true }) });
112
- const api = createSceneApi(store);
113
- api.pauseHistory();
114
- api.update(id('a'), { visible: false });
115
- expect(nodes(store)['a']).toMatchObject({ visible: false });
116
- api.restore(id('a'));
117
- expect(nodes(store)['a']).toMatchObject({ visible: true });
118
- api.resumeHistory();
119
- });
120
- test('restore on a node never touched is a no-op', () => {
121
- const store = makeFakeStore({ a: makeNode('a', { visible: true }) });
122
- const api = createSceneApi(store);
123
- api.pauseHistory();
124
- api.restore(id('a'));
125
- expect(nodes(store)['a']).toMatchObject({ visible: true });
126
- api.resumeHistory();
127
- });
128
- test('restoreAll reverts every touched node', () => {
129
- const store = makeFakeStore({
130
- a: makeNode('a', { visible: true }),
131
- b: makeNode('b', { visible: true }),
132
- });
133
- const api = createSceneApi(store);
134
- api.pauseHistory();
135
- api.update(id('a'), { visible: false });
136
- api.update(id('b'), { visible: false });
137
- api.restoreAll();
138
- expect(nodes(store)['a']).toMatchObject({ visible: true });
139
- expect(nodes(store)['b']).toMatchObject({ visible: true });
140
- api.resumeHistory();
141
- });
142
- test('restore re-creates a node that was deleted mid-pause', () => {
143
- const original = makeNode('a', { visible: true });
144
- const store = makeFakeStore({ a: original });
145
- const api = createSceneApi(store);
146
- api.pauseHistory();
147
- api.delete(id('a'));
148
- expect(nodes(store)['a']).toBeUndefined();
149
- api.restore(id('a'));
150
- expect(nodes(store)['a']).toEqual(original);
151
- api.resumeHistory();
152
- });
153
- test('restore deletes a node that was upserted mid-pause', () => {
154
- const store = makeFakeStore();
155
- const api = createSceneApi(store);
156
- api.pauseHistory();
157
- api.upsert(makeNode('a'));
158
- expect(nodes(store)['a']).toBeDefined();
159
- api.restore(id('a'));
160
- expect(nodes(store)['a']).toBeUndefined();
161
- api.resumeHistory();
162
- });
163
- test('snapshot is dropped on resumeHistory; restore after resume is a no-op', () => {
164
- const store = makeFakeStore({ a: makeNode('a', { visible: true }) });
165
- const api = createSceneApi(store);
166
- api.pauseHistory();
167
- api.update(id('a'), { visible: false });
168
- api.resumeHistory();
169
- api.restore(id('a')); // snapshot gone — no effect
170
- expect(nodes(store)['a']).toMatchObject({ visible: false });
171
- });
172
- test('only the first mutation in a pause window captures the original', () => {
173
- const store = makeFakeStore({ a: makeNode('a', { visible: true }) });
174
- const api = createSceneApi(store);
175
- api.pauseHistory();
176
- api.update(id('a'), { visible: false });
177
- api.update(id('a'), { visible: true }); // second update — must not overwrite snapshot
178
- api.update(id('a'), { visible: false });
179
- api.restore(id('a'));
180
- expect(nodes(store)['a']).toMatchObject({ visible: true }); // the *first* pre-pause value
181
- api.resumeHistory();
182
- });
183
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=subtree.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"subtree.test.d.ts","sourceRoot":"","sources":["../../src/registry/subtree.test.ts"],"names":[],"mappings":""}
@@ -1,133 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { cloneNodesInto, collectSubtree } from './subtree';
3
- function makeNode(id, type, extra = {}) {
4
- return {
5
- object: 'node',
6
- id,
7
- type,
8
- parentId: null,
9
- visible: true,
10
- metadata: {},
11
- ...extra,
12
- };
13
- }
14
- describe('collectSubtree', () => {
15
- test('returns null for missing root', () => {
16
- expect(collectSubtree({}, 'missing')).toBeNull();
17
- });
18
- test('returns just the root for a leaf node', () => {
19
- const root = makeNode('shelf_1', 'shelf', { width: 1 });
20
- const sub = collectSubtree({ ['shelf_1']: root }, 'shelf_1');
21
- expect(sub?.root).toBe(root);
22
- expect(sub?.descendants).toEqual([]);
23
- });
24
- test('walks descendants in BFS / declaration order', () => {
25
- const nodes = {
26
- ['shelf_1']: makeNode('shelf_1', 'shelf', {
27
- position: [0, 0, 0],
28
- children: ['item_a', 'item_b'],
29
- width: 1,
30
- }),
31
- ['item_a']: makeNode('item_a', 'item', {
32
- parentId: 'shelf_1',
33
- position: [0, 0, 0],
34
- }),
35
- ['item_b']: makeNode('item_b', 'item', {
36
- parentId: 'shelf_1',
37
- position: [0.3, 0, 0],
38
- }),
39
- };
40
- const sub = collectSubtree(nodes, 'shelf_1');
41
- expect(sub?.descendants.map((n) => n.id)).toEqual(['item_a', 'item_b']);
42
- });
43
- test('returned nodes are live references — no cloning', () => {
44
- const item = makeNode('item_a', 'item', { parentId: 'shelf_1', position: [0, 0, 0] });
45
- const nodes = {
46
- ['shelf_1']: makeNode('shelf_1', 'shelf', { children: ['item_a'] }),
47
- ['item_a']: item,
48
- };
49
- const sub = collectSubtree(nodes, 'shelf_1');
50
- expect(sub?.descendants[0]).toBe(item);
51
- });
52
- });
53
- describe('cloneNodesInto', () => {
54
- test('clones a single root with fresh id and supplied position', () => {
55
- const original = makeNode('door_orig', 'door', {
56
- position: [1, 2, 3],
57
- wallId: 'wall_x',
58
- width: 0.9,
59
- });
60
- const { rootId, nodes } = cloneNodesInto([original], {
61
- rootId: 'door_orig',
62
- position: [10, 0, -4],
63
- });
64
- expect(nodes).toHaveLength(1);
65
- const cloned = nodes[0];
66
- expect(cloned.id).toBe(rootId);
67
- expect(cloned.id).not.toBe('door_orig');
68
- expect(cloned.id.startsWith('door_')).toBe(true);
69
- expect(cloned.position).toEqual([10, 0, -4]);
70
- expect(cloned.width).toBe(0.9);
71
- // cloneNodesInto is host-ref-agnostic — wallId is preserved
72
- // verbatim. Stripping is the caller's job (see getHostRefFields).
73
- expect(cloned.wallId).toBe('wall_x');
74
- });
75
- test('preserves root position when none is supplied', () => {
76
- const original = makeNode('shelf_orig', 'shelf', { position: [5, 0, 5] });
77
- const { nodes } = cloneNodesInto([original], { rootId: 'shelf_orig' });
78
- expect(nodes[0].position).toEqual([5, 0, 5]);
79
- });
80
- test('preserves parent/child subtree with remapped ids and relative positions', () => {
81
- const shelf = makeNode('shelf_1', 'shelf', {
82
- position: [5, 0, 5],
83
- children: ['item_a', 'item_b'],
84
- });
85
- const itemA = makeNode('item_a', 'item', { parentId: 'shelf_1', position: [0, 0, 0] });
86
- const itemB = makeNode('item_b', 'item', { parentId: 'shelf_1', position: [0.3, 0, 0] });
87
- const { rootId, nodes: out } = cloneNodesInto([shelf, itemA, itemB], {
88
- rootId: 'shelf_1',
89
- position: [99, 0, -99],
90
- });
91
- expect(out).toHaveLength(3);
92
- const root = out[0];
93
- expect(root.id).toBe(rootId);
94
- expect(root.id).not.toBe('shelf_1');
95
- expect(root.position).toEqual([99, 0, -99]);
96
- // Root's children rewritten to fresh ids; descendants' parentIds
97
- // point at the new root id.
98
- const ids = new Set(out.map((n) => n.id));
99
- expect(root.children).toHaveLength(2);
100
- for (const cid of root.children)
101
- expect(ids.has(cid)).toBe(true);
102
- for (let i = 1; i < out.length; i += 1) {
103
- const desc = out[i];
104
- expect(desc.parentId).toBe(rootId);
105
- expect(Array.isArray(desc.position)).toBe(true);
106
- }
107
- });
108
- test('parents the cloned root under opts.parentId when supplied', () => {
109
- const orig = makeNode('shelf_1', 'shelf', { parentId: 'level_old' });
110
- const { nodes } = cloneNodesInto([orig], {
111
- rootId: 'shelf_1',
112
- parentId: 'level_new',
113
- });
114
- expect(nodes[0].parentId).toBe('level_new');
115
- });
116
- test('two clones produce disjoint id sets', () => {
117
- const orig = makeNode('shelf_1', 'shelf', {
118
- position: [0, 0, 0],
119
- children: ['item_a'],
120
- });
121
- const child = makeNode('item_a', 'item', { parentId: 'shelf_1', position: [0, 0, 0] });
122
- const first = cloneNodesInto([orig, child], { rootId: 'shelf_1' });
123
- const second = cloneNodesInto([orig, child], { rootId: 'shelf_1' });
124
- const idsA = new Set(first.nodes.map((n) => n.id));
125
- const idsB = new Set(second.nodes.map((n) => n.id));
126
- for (const id of idsA)
127
- expect(idsB.has(id)).toBe(false);
128
- });
129
- test('throws if rootId is missing from the input array', () => {
130
- const orig = makeNode('shelf_1', 'shelf', {});
131
- expect(() => cloneNodesInto([orig], { rootId: 'shelf_other' })).toThrow(/rootId/);
132
- });
133
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=asset-url.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"asset-url.test.d.ts","sourceRoot":"","sources":["../../src/schema/asset-url.test.ts"],"names":[],"mappings":""}
@@ -1,134 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
2
- import { ALLOWED_ORIGINS_ENV, AssetUrl } from './asset-url';
3
- function isValid(url) {
4
- return AssetUrl.safeParse(url).success;
5
- }
6
- describe('AssetUrl', () => {
7
- describe('allowed URLs', () => {
8
- const cases = [
9
- ['asset://abc', 'internal asset handle'],
10
- ['asset://catalog/items/chair-1', 'nested asset handle'],
11
- ['blob:http://example.com/uuid-1234', 'blob URL with http inner'],
12
- ['blob:https://example.com/uuid-5678', 'blob URL with https inner'],
13
- ['https://cdn.example.com/a.glb', 'https CDN URL'],
14
- ['https://cdn.example.com/models/chair.glb?v=2', 'https URL with query string'],
15
- ['http://localhost:3000/x', 'http localhost with port'],
16
- ['http://localhost/x', 'http localhost without port'],
17
- ['http://127.0.0.1:8080/texture.png', 'http 127.0.0.1 loopback'],
18
- ['/public/a.glb', 'app-relative path'],
19
- ['/material/wood1/albedoMap_basecolor.jpg', 'relative path deep'],
20
- ['data:image/png;base64,AAA', 'inline PNG data URL'],
21
- ['data:image/jpeg;base64,/9j/', 'inline JPEG data URL'],
22
- ['data:image/webp;base64,UklGR', 'inline WebP data URL'],
23
- ['data:image/svg+xml,%3Csvg%3E', 'inline SVG data URL'],
24
- ];
25
- for (const [url, label] of cases) {
26
- test(`accepts ${label}: ${url}`, () => {
27
- expect(isValid(url)).toBe(true);
28
- });
29
- }
30
- });
31
- describe('rejected URLs', () => {
32
- const cases = [
33
- ['javascript:alert(1)', 'javascript scheme'],
34
- ['JAVASCRIPT:alert(1)', 'javascript scheme uppercase'],
35
- ['file:///etc/passwd', 'file scheme'],
36
- ['file://C:/Windows/System32/config', 'file scheme Windows'],
37
- ['http://evil.com/', 'non-loopback http'],
38
- ['http://example.com:3000/x', 'http on non-loopback host'],
39
- ['http://169.254.169.254/latest/meta-data/', 'http on link-local (cloud metadata)'],
40
- ['data:text/html,<script>alert(1)</script>', 'data text/html'],
41
- ['data:application/javascript,alert(1)', 'data application/javascript'],
42
- ['data:text/plain,hi', 'data text/plain'],
43
- ['ftp://a.b.com', 'ftp scheme'],
44
- ['ws://example.com/', 'websocket scheme'],
45
- ['vbscript:msgbox', 'vbscript scheme'],
46
- ['', 'empty string'],
47
- ['not a url at all', 'non-url string'],
48
- ['://missing-scheme', 'malformed'],
49
- ];
50
- for (const [url, label] of cases) {
51
- test(`rejects ${label}: ${url}`, () => {
52
- expect(isValid(url)).toBe(false);
53
- });
54
- }
55
- });
56
- describe(`env allowlist via ${ALLOWED_ORIGINS_ENV}`, () => {
57
- const g = globalThis;
58
- const original = g.process?.env?.[ALLOWED_ORIGINS_ENV];
59
- beforeEach(() => {
60
- if (g.process?.env)
61
- delete g.process.env[ALLOWED_ORIGINS_ENV];
62
- });
63
- afterEach(() => {
64
- if (!g.process?.env)
65
- return;
66
- if (original === undefined) {
67
- delete g.process.env[ALLOWED_ORIGINS_ENV];
68
- }
69
- else {
70
- g.process.env[ALLOWED_ORIGINS_ENV] = original;
71
- }
72
- });
73
- test('single origin allowlist accepts matching https URL', () => {
74
- if (!g.process?.env)
75
- return; // browser-only runtime
76
- g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app';
77
- expect(isValid('https://cdn.pascal.app/a.glb')).toBe(true);
78
- expect(isValid('https://cdn.pascal.app/deep/path?q=1')).toBe(true);
79
- });
80
- test('single origin allowlist rejects non-matching https URL', () => {
81
- if (!g.process?.env)
82
- return;
83
- g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app';
84
- expect(isValid('https://cdn.other.com/a.glb')).toBe(false);
85
- expect(isValid('https://attacker.example.com/x')).toBe(false);
86
- });
87
- test('multi-origin allowlist accepts any listed origin', () => {
88
- if (!g.process?.env)
89
- return;
90
- g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app, https://assets.pascal.app';
91
- expect(isValid('https://cdn.pascal.app/a.glb')).toBe(true);
92
- expect(isValid('https://assets.pascal.app/tex.webp')).toBe(true);
93
- expect(isValid('https://third.example.com/x')).toBe(false);
94
- });
95
- test('allowlist ignores trailing / in URL path (origin match only)', () => {
96
- if (!g.process?.env)
97
- return;
98
- g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app';
99
- expect(isValid('https://cdn.pascal.app/')).toBe(true);
100
- expect(isValid('https://cdn.pascal.app')).toBe(true);
101
- });
102
- test('empty allowlist behaves like unset', () => {
103
- if (!g.process?.env)
104
- return;
105
- g.process.env[ALLOWED_ORIGINS_ENV] = '';
106
- expect(isValid('https://cdn.other.com/a.glb')).toBe(true);
107
- });
108
- test('allowlist does not restrict non-https schemes', () => {
109
- if (!g.process?.env)
110
- return;
111
- g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app';
112
- // these should still pass because they match earlier scheme-based branches
113
- expect(isValid('asset://x')).toBe(true);
114
- expect(isValid('blob:https://example.com/abc')).toBe(true);
115
- expect(isValid('data:image/png;base64,AAA')).toBe(true);
116
- expect(isValid('/public/a.glb')).toBe(true);
117
- expect(isValid('http://localhost:3000/x')).toBe(true);
118
- });
119
- test('allowlist rejects subdomain spoofing', () => {
120
- if (!g.process?.env)
121
- return;
122
- g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app';
123
- expect(isValid('https://cdn.pascal.app.evil.com/x')).toBe(false);
124
- expect(isValid('https://evil.com/cdn.pascal.app')).toBe(false);
125
- });
126
- test('allowlist respects ports', () => {
127
- if (!g.process?.env)
128
- return;
129
- g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app:8443';
130
- expect(isValid('https://cdn.pascal.app:8443/x')).toBe(true);
131
- expect(isValid('https://cdn.pascal.app/x')).toBe(false);
132
- });
133
- });
134
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=alignment-anchors.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"alignment-anchors.test.d.ts","sourceRoot":"","sources":["../../src/services/alignment-anchors.test.ts"],"names":[],"mappings":""}