@spearwolf/shadow-objects 0.33.0 → 0.35.0

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 (312) hide show
  1. package/CHANGELOG.md +350 -61
  2. package/README.md +66 -13
  3. package/bundle.js +6 -53
  4. package/package.json +15 -3
  5. package/src/ChangeTrailRefusedError.d.ts +27 -0
  6. package/src/ChangeTrailRefusedError.d.ts.map +1 -0
  7. package/src/ChangeTrailRefusedError.js +12 -0
  8. package/src/ChangeTrailRefusedError.js.map +7 -0
  9. package/src/EntityUuidInUseError.d.ts +19 -0
  10. package/src/EntityUuidInUseError.d.ts.map +1 -0
  11. package/src/EntityUuidInUseError.js +11 -0
  12. package/src/EntityUuidInUseError.js.map +7 -0
  13. package/src/WorkerTimeoutError.d.ts +26 -0
  14. package/src/WorkerTimeoutError.d.ts.map +1 -0
  15. package/src/WorkerTimeoutError.js +12 -0
  16. package/src/WorkerTimeoutError.js.map +7 -0
  17. package/src/bundle.d.ts +1 -1
  18. package/src/bundle.d.ts.map +1 -1
  19. package/src/bundle.js +1 -1
  20. package/src/bundle.js.map +2 -2
  21. package/src/constants.d.ts +13 -2
  22. package/src/constants.d.ts.map +1 -1
  23. package/src/constants.js +7 -1
  24. package/src/constants.js.map +2 -2
  25. package/src/create-worker.bundle.d.ts +1 -1
  26. package/src/create-worker.bundle.d.ts.map +1 -1
  27. package/src/create-worker.bundle.js.map +1 -1
  28. package/src/create-worker.d.ts +1 -1
  29. package/src/create-worker.d.ts.map +1 -1
  30. package/src/create-worker.js.map +1 -1
  31. package/src/elements/ShaeElement.d.ts +70 -2
  32. package/src/elements/ShaeElement.d.ts.map +1 -1
  33. package/src/elements/ShaeElement.js +129 -25
  34. package/src/elements/ShaeElement.js.map +3 -3
  35. package/src/elements/ShaeEntElement.d.ts +39 -1
  36. package/src/elements/ShaeEntElement.d.ts.map +1 -1
  37. package/src/elements/ShaeEntElement.js +397 -167
  38. package/src/elements/ShaeEntElement.js.map +2 -2
  39. package/src/elements/ShaeLifecycleElement.d.ts +71 -0
  40. package/src/elements/ShaeLifecycleElement.d.ts.map +1 -0
  41. package/src/elements/ShaeLifecycleElement.js +83 -0
  42. package/src/elements/ShaeLifecycleElement.js.map +7 -0
  43. package/src/elements/ShaePropElement.d.ts +61 -2
  44. package/src/elements/ShaePropElement.d.ts.map +1 -1
  45. package/src/elements/ShaePropElement.js +264 -219
  46. package/src/elements/ShaePropElement.js.map +2 -2
  47. package/src/elements/ShaeWorkerElement.d.ts +59 -5
  48. package/src/elements/ShaeWorkerElement.d.ts.map +1 -1
  49. package/src/elements/ShaeWorkerElement.js +271 -73
  50. package/src/elements/ShaeWorkerElement.js.map +2 -2
  51. package/src/elements/constants.d.ts +9 -0
  52. package/src/elements/constants.d.ts.map +1 -1
  53. package/src/elements/constants.js +18 -0
  54. package/src/elements/constants.js.map +2 -2
  55. package/src/elements/deferredTeardown.d.ts +24 -0
  56. package/src/elements/deferredTeardown.d.ts.map +1 -0
  57. package/src/elements/deferredTeardown.js +30 -0
  58. package/src/elements/deferredTeardown.js.map +7 -0
  59. package/src/elements/displayContentsRule.d.ts +25 -0
  60. package/src/elements/displayContentsRule.d.ts.map +1 -0
  61. package/src/elements/displayContentsRule.js +37 -0
  62. package/src/elements/displayContentsRule.js.map +7 -0
  63. package/src/elements/events.d.ts +10 -7
  64. package/src/elements/events.d.ts.map +1 -1
  65. package/src/elements/forwardCustomEvents.d.ts +19 -0
  66. package/src/elements/forwardCustomEvents.d.ts.map +1 -0
  67. package/src/elements/forwardCustomEvents.js +47 -0
  68. package/src/elements/forwardCustomEvents.js.map +7 -0
  69. package/src/elements/hostedSlots.d.ts +36 -0
  70. package/src/elements/hostedSlots.d.ts.map +1 -0
  71. package/src/elements/hostedSlots.js +118 -0
  72. package/src/elements/hostedSlots.js.map +7 -0
  73. package/src/elements/parentRemoval.d.ts +18 -0
  74. package/src/elements/parentRemoval.d.ts.map +1 -0
  75. package/src/elements/parentRemoval.js +60 -0
  76. package/src/elements/parentRemoval.js.map +7 -0
  77. package/src/elements/propValueConverters.d.ts +3 -0
  78. package/src/elements/propValueConverters.d.ts.map +1 -0
  79. package/src/elements/propValueConverters.js +54 -0
  80. package/src/elements/propValueConverters.js.map +7 -0
  81. package/src/elements/requestEntAncestor.d.ts +32 -0
  82. package/src/elements/requestEntAncestor.d.ts.map +1 -0
  83. package/src/elements/requestEntAncestor.js +16 -0
  84. package/src/elements/requestEntAncestor.js.map +7 -0
  85. package/src/elements.js.map +1 -1
  86. package/src/in-the-dark/Entity.d.ts +143 -3
  87. package/src/in-the-dark/Entity.d.ts.map +1 -1
  88. package/src/in-the-dark/Entity.js +330 -63
  89. package/src/in-the-dark/Entity.js.map +3 -3
  90. package/src/in-the-dark/Kernel.d.ts +98 -16
  91. package/src/in-the-dark/Kernel.d.ts.map +1 -1
  92. package/src/in-the-dark/Kernel.js +466 -379
  93. package/src/in-the-dark/Kernel.js.map +3 -3
  94. package/src/in-the-dark/Registry.d.ts +7 -1
  95. package/src/in-the-dark/Registry.d.ts.map +1 -1
  96. package/src/in-the-dark/Registry.js +112 -14
  97. package/src/in-the-dark/Registry.js.map +2 -2
  98. package/src/in-the-dark/ShadowObject.d.ts.map +1 -1
  99. package/src/in-the-dark/ShadowObject.js +1 -0
  100. package/src/in-the-dark/ShadowObject.js.map +2 -2
  101. package/src/in-the-dark/ShadowObjectCreationScope.d.ts +133 -0
  102. package/src/in-the-dark/ShadowObjectCreationScope.d.ts.map +1 -0
  103. package/src/in-the-dark/ShadowObjectCreationScope.js +603 -0
  104. package/src/in-the-dark/ShadowObjectCreationScope.js.map +7 -0
  105. package/src/in-the-dark/SignalsPath.d.ts +2 -0
  106. package/src/in-the-dark/SignalsPath.d.ts.map +1 -1
  107. package/src/in-the-dark/SignalsPath.js +6 -2
  108. package/src/in-the-dark/SignalsPath.js.map +2 -2
  109. package/src/in-the-dark/displayName.d.ts +4 -0
  110. package/src/in-the-dark/displayName.d.ts.map +1 -0
  111. package/src/in-the-dark/displayName.js +5 -0
  112. package/src/in-the-dark/displayName.js.map +7 -0
  113. package/src/in-the-dark/events.js.map +1 -1
  114. package/src/in-the-dark/importModule.d.ts +7 -0
  115. package/src/in-the-dark/importModule.d.ts.map +1 -1
  116. package/src/in-the-dark/importModule.js +5 -2
  117. package/src/in-the-dark/importModule.js.map +2 -2
  118. package/src/index.d.ts +9 -1
  119. package/src/index.d.ts.map +1 -1
  120. package/src/index.js +9 -1
  121. package/src/index.js.map +2 -2
  122. package/src/inspect/createKernelSnapshot.d.ts +17 -0
  123. package/src/inspect/createKernelSnapshot.d.ts.map +1 -0
  124. package/src/inspect/createKernelSnapshot.js +247 -0
  125. package/src/inspect/createKernelSnapshot.js.map +7 -0
  126. package/src/inspect/createViewSnapshot.d.ts +10 -0
  127. package/src/inspect/createViewSnapshot.d.ts.map +1 -0
  128. package/src/inspect/createViewSnapshot.js +143 -0
  129. package/src/inspect/createViewSnapshot.js.map +7 -0
  130. package/src/inspect/normalizeInspectRequest.d.ts +32 -0
  131. package/src/inspect/normalizeInspectRequest.d.ts.map +1 -0
  132. package/src/inspect/normalizeInspectRequest.js +55 -0
  133. package/src/inspect/normalizeInspectRequest.js.map +7 -0
  134. package/src/inspect/serializeValue.d.ts +13 -0
  135. package/src/inspect/serializeValue.d.ts.map +1 -0
  136. package/src/inspect/serializeValue.js +149 -0
  137. package/src/inspect/serializeValue.js.map +7 -0
  138. package/src/inspect/types.d.ts +260 -0
  139. package/src/inspect/types.d.ts.map +1 -0
  140. package/src/inspect/types.js +1 -0
  141. package/src/inspect/types.js.map +7 -0
  142. package/src/model-context/ModelContextLike.d.ts +59 -0
  143. package/src/model-context/ModelContextLike.d.ts.map +1 -0
  144. package/src/model-context/ModelContextLike.js +15 -0
  145. package/src/model-context/ModelContextLike.js.map +7 -0
  146. package/src/model-context/exposeShadowEnvsToModelContext.d.ts +48 -0
  147. package/src/model-context/exposeShadowEnvsToModelContext.d.ts.map +1 -0
  148. package/src/model-context/exposeShadowEnvsToModelContext.js +39 -0
  149. package/src/model-context/exposeShadowEnvsToModelContext.js.map +7 -0
  150. package/src/model-context/redactProps.d.ts +14 -0
  151. package/src/model-context/redactProps.d.ts.map +1 -0
  152. package/src/model-context/redactProps.js +22 -0
  153. package/src/model-context/redactProps.js.map +7 -0
  154. package/src/model-context/sharedExposure.d.ts +32 -0
  155. package/src/model-context/sharedExposure.d.ts.map +1 -0
  156. package/src/model-context/sharedExposure.js +108 -0
  157. package/src/model-context/sharedExposure.js.map +7 -0
  158. package/src/model-context/toolSupport.d.ts +72 -0
  159. package/src/model-context/toolSupport.d.ts.map +1 -0
  160. package/src/model-context/toolSupport.js +145 -0
  161. package/src/model-context/toolSupport.js.map +7 -0
  162. package/src/model-context/tools/findEntities.d.ts +15 -0
  163. package/src/model-context/tools/findEntities.d.ts.map +1 -0
  164. package/src/model-context/tools/findEntities.js +71 -0
  165. package/src/model-context/tools/findEntities.js.map +7 -0
  166. package/src/model-context/tools/getEntity.d.ts +16 -0
  167. package/src/model-context/tools/getEntity.d.ts.map +1 -0
  168. package/src/model-context/tools/getEntity.js +65 -0
  169. package/src/model-context/tools/getEntity.js.map +7 -0
  170. package/src/model-context/tools/getEntityTree.d.ts +4 -0
  171. package/src/model-context/tools/getEntityTree.d.ts.map +1 -0
  172. package/src/model-context/tools/getEntityTree.js +58 -0
  173. package/src/model-context/tools/getEntityTree.js.map +7 -0
  174. package/src/model-context/tools/getRegistry.d.ts +14 -0
  175. package/src/model-context/tools/getRegistry.d.ts.map +1 -0
  176. package/src/model-context/tools/getRegistry.js +39 -0
  177. package/src/model-context/tools/getRegistry.js.map +7 -0
  178. package/src/model-context/tools/index.d.ts +9 -0
  179. package/src/model-context/tools/index.d.ts.map +1 -0
  180. package/src/model-context/tools/index.js +16 -0
  181. package/src/model-context/tools/index.js.map +7 -0
  182. package/src/model-context/tools/listEnvs.d.ts +33 -0
  183. package/src/model-context/tools/listEnvs.d.ts.map +1 -0
  184. package/src/model-context/tools/listEnvs.js +37 -0
  185. package/src/model-context/tools/listEnvs.js.map +7 -0
  186. package/src/model-context.d.ts +13 -0
  187. package/src/model-context.d.ts.map +1 -0
  188. package/src/model-context.js +15 -0
  189. package/src/model-context.js.map +7 -0
  190. package/src/shadow-objects.d.ts +4 -0
  191. package/src/shadow-objects.d.ts.map +1 -1
  192. package/src/shadow-objects.js +6 -0
  193. package/src/shadow-objects.js.map +2 -2
  194. package/src/shadow-objects.worker.js +0 -1
  195. package/src/shadow-objects.worker.js.map +2 -2
  196. package/src/shae-ent.js.map +1 -1
  197. package/src/shae-prop.js +2 -2
  198. package/src/shae-prop.js.map +2 -2
  199. package/src/shae-worker.js.map +1 -1
  200. package/src/testing/TestEntity.d.ts +79 -0
  201. package/src/testing/TestEntity.d.ts.map +1 -0
  202. package/src/testing/TestEntity.js +135 -0
  203. package/src/testing/TestEntity.js.map +7 -0
  204. package/src/testing/createTestKernel.d.ts +11 -0
  205. package/src/testing/createTestKernel.d.ts.map +1 -0
  206. package/src/testing/createTestKernel.js +132 -0
  207. package/src/testing/createTestKernel.js.map +7 -0
  208. package/src/testing/mountShadowObject.d.ts +21 -0
  209. package/src/testing/mountShadowObject.d.ts.map +1 -0
  210. package/src/testing/mountShadowObject.js +72 -0
  211. package/src/testing/mountShadowObject.js.map +7 -0
  212. package/src/testing/recordKernelErrors.d.ts +21 -0
  213. package/src/testing/recordKernelErrors.d.ts.map +1 -0
  214. package/src/testing/recordKernelErrors.js +39 -0
  215. package/src/testing/recordKernelErrors.js.map +7 -0
  216. package/src/testing/settle.d.ts +15 -0
  217. package/src/testing/settle.d.ts.map +1 -0
  218. package/src/testing/settle.js +19 -0
  219. package/src/testing/settle.js.map +7 -0
  220. package/src/testing/types.d.ts +119 -0
  221. package/src/testing/types.d.ts.map +1 -0
  222. package/src/testing/types.js +1 -0
  223. package/src/testing/types.js.map +7 -0
  224. package/src/testing.d.ts +14 -0
  225. package/src/testing.d.ts.map +1 -0
  226. package/src/testing.js +11 -0
  227. package/src/testing.js.map +7 -0
  228. package/src/types.d.ts +185 -14
  229. package/src/types.d.ts.map +1 -1
  230. package/src/utils/ConsoleLogger.d.ts +45 -10
  231. package/src/utils/ConsoleLogger.d.ts.map +1 -1
  232. package/src/utils/ConsoleLogger.js +51 -26
  233. package/src/utils/ConsoleLogger.js.map +2 -2
  234. package/src/utils/FrameLoop.d.ts +42 -3
  235. package/src/utils/FrameLoop.d.ts.map +1 -1
  236. package/src/utils/FrameLoop.js +71 -10
  237. package/src/utils/FrameLoop.js.map +2 -2
  238. package/src/utils/MicrotaskCollector.d.ts +45 -0
  239. package/src/utils/MicrotaskCollector.d.ts.map +1 -0
  240. package/src/utils/MicrotaskCollector.js +33 -0
  241. package/src/utils/MicrotaskCollector.js.map +7 -0
  242. package/src/utils/MicrotaskGate.d.ts +17 -0
  243. package/src/utils/MicrotaskGate.d.ts.map +1 -0
  244. package/src/utils/MicrotaskGate.js +20 -0
  245. package/src/utils/MicrotaskGate.js.map +7 -0
  246. package/src/utils/array-utils.js.map +1 -1
  247. package/src/utils/attr-utils.d.ts +11 -0
  248. package/src/utils/attr-utils.d.ts.map +1 -1
  249. package/src/utils/attr-utils.js +5 -1
  250. package/src/utils/attr-utils.js.map +2 -2
  251. package/src/utils/constants.js.map +1 -1
  252. package/src/utils/generateUUID.d.ts +11 -0
  253. package/src/utils/generateUUID.d.ts.map +1 -1
  254. package/src/utils/generateUUID.js +31 -266
  255. package/src/utils/generateUUID.js.map +2 -2
  256. package/src/utils/props-utils.d.ts +3 -2
  257. package/src/utils/props-utils.d.ts.map +1 -1
  258. package/src/utils/props-utils.js +13 -24
  259. package/src/utils/props-utils.js.map +2 -2
  260. package/src/utils/runGuarded.d.ts +27 -0
  261. package/src/utils/runGuarded.d.ts.map +1 -0
  262. package/src/utils/runGuarded.js +11 -0
  263. package/src/utils/runGuarded.js.map +7 -0
  264. package/src/utils/toMaybe.d.ts.map +1 -1
  265. package/src/utils/toMaybe.js.map +1 -1
  266. package/src/utils/toNamespace.d.ts.map +1 -1
  267. package/src/utils/toNamespace.js.map +1 -1
  268. package/src/utils/toUrlString.d.ts +12 -0
  269. package/src/utils/toUrlString.d.ts.map +1 -1
  270. package/src/utils/toUrlString.js.map +2 -2
  271. package/src/utils/waitForMessageOfType.d.ts +12 -1
  272. package/src/utils/waitForMessageOfType.d.ts.map +1 -1
  273. package/src/utils/waitForMessageOfType.js +16 -1
  274. package/src/utils/waitForMessageOfType.js.map +2 -2
  275. package/src/view/ComponentChanges.d.ts +88 -1
  276. package/src/view/ComponentChanges.d.ts.map +1 -1
  277. package/src/view/ComponentChanges.js +243 -73
  278. package/src/view/ComponentChanges.js.map +2 -2
  279. package/src/view/ComponentContext.d.ts +206 -16
  280. package/src/view/ComponentContext.d.ts.map +1 -1
  281. package/src/view/ComponentContext.js +548 -91
  282. package/src/view/ComponentContext.js.map +2 -2
  283. package/src/view/ComponentMemory.d.ts +3 -3
  284. package/src/view/ComponentMemory.d.ts.map +1 -1
  285. package/src/view/ComponentMemory.js.map +1 -1
  286. package/src/view/IShadowObjectEnvProxy.d.ts +38 -3
  287. package/src/view/IShadowObjectEnvProxy.d.ts.map +1 -1
  288. package/src/view/LocalShadowObjectEnv.d.ts +8 -1
  289. package/src/view/LocalShadowObjectEnv.d.ts.map +1 -1
  290. package/src/view/LocalShadowObjectEnv.js +23 -7
  291. package/src/view/LocalShadowObjectEnv.js.map +2 -2
  292. package/src/view/RemoteWorkerEnv.d.ts +108 -5
  293. package/src/view/RemoteWorkerEnv.d.ts.map +1 -1
  294. package/src/view/RemoteWorkerEnv.js +244 -77
  295. package/src/view/RemoteWorkerEnv.js.map +2 -2
  296. package/src/view/ShadowEnv.d.ts +79 -2
  297. package/src/view/ShadowEnv.d.ts.map +1 -1
  298. package/src/view/ShadowEnv.js +323 -59
  299. package/src/view/ShadowEnv.js.map +3 -3
  300. package/src/view/ViewComponent.d.ts +78 -14
  301. package/src/view/ViewComponent.d.ts.map +1 -1
  302. package/src/view/ViewComponent.js +90 -14
  303. package/src/view/ViewComponent.js.map +2 -2
  304. package/src/view/cloneChangeTrail.js.map +2 -2
  305. package/src/worker/MessageRouter.d.ts +14 -0
  306. package/src/worker/MessageRouter.d.ts.map +1 -1
  307. package/src/worker/MessageRouter.js +98 -15
  308. package/src/worker/MessageRouter.js.map +2 -2
  309. package/src/worker/WorkerRuntime.d.ts +22 -1
  310. package/src/worker/WorkerRuntime.d.ts.map +1 -1
  311. package/src/worker/WorkerRuntime.js +60 -8
  312. package/src/worker/WorkerRuntime.js.map +2 -2
@@ -1,9 +1,20 @@
1
+ import type { WorkerReplyType } from '../constants.js';
2
+ /**
3
+ * The largest delay a timer actually keeps. `setTimeout()` truncates its delay into a signed
4
+ * 32-bit field, so a larger number comes back out as some other, shorter one: a millisecond
5
+ * past this bound already fires at once. Whatever it lands on, it is not the wait that was
6
+ * asked for, and nothing says so.
7
+ */
8
+ export declare const MaxWorkerTimeout = 2147483647;
9
+ export declare const isTimeout: (value: unknown) => value is number;
1
10
  /**
2
11
  * Wait for a message of a specific type or reject after a timeout.
3
12
  *
13
+ * A deadline that runs out rejects with a `WorkerTimeoutError`.
14
+ *
4
15
  * A `signal` rejects the promise the moment it is aborted, with the abort reason,
5
16
  * so that a caller does not sit out the timeout waiting for a reply that can no
6
17
  * longer arrive.
7
18
  */
8
- export declare const waitForMessageOfType: (worker: Worker, type: string, timeout?: number, guard?: (data: any) => boolean, signal?: AbortSignal) => Promise<void>;
19
+ export declare const waitForMessageOfType: <T = unknown>(worker: Worker, type: WorkerReplyType, timeout?: number, guard?: (data: T) => boolean, signal?: AbortSignal) => Promise<void>;
9
20
  //# sourceMappingURL=waitForMessageOfType.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"waitForMessageOfType.d.ts","sourceRoot":"","sources":["../../../src/utils/waitForMessageOfType.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,MAAM,MAAM,EACZ,gBAAc,EACd,QAAQ,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,EAC9B,SAAS,WAAW,kBAiDlB,CAAC"}
1
+ {"version":3,"file":"waitForMessageOfType.d.ts","sourceRoot":"","sources":["../../../src/utils/waitForMessageOfType.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,iBAAiB,CAAC;AAIrD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,aAAgB,CAAC;AAE9C,eAAO,MAAM,SAAS,UAAW,OAAO,KAAG,KAAK,IAAI,MAC2C,CAAC;AAEhG;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,GAAG,OAAO,UACtC,MAAM,QACR,eAAe,4BAEb,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,WACnB,WAAW,kBA6DlB,CAAC"}
@@ -1,4 +1,16 @@
1
+ import { WorkerTimeoutError } from "../WorkerTimeoutError.js";
2
+ import { isReadableMessageData } from "../worker/MessageRouter.js";
3
+ const MaxWorkerTimeout = 2147483647;
4
+ const isTimeout = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 && value <= MaxWorkerTimeout;
1
5
  const waitForMessageOfType = (worker, type, timeout = 1e3, guard, signal) => new Promise((resolve, reject) => {
6
+ if (timeout !== 0 && timeout !== Infinity && !isTimeout(timeout)) {
7
+ reject(
8
+ new TypeError(
9
+ `cannot wait for a ${type} message: expected a timeout of 1 to ${MaxWorkerTimeout} milliseconds, or 0 or Infinity for no deadline, got ${String(timeout)}`
10
+ )
11
+ );
12
+ return;
13
+ }
2
14
  if (signal?.aborted) {
3
15
  reject(signal.reason);
4
16
  return;
@@ -17,10 +29,11 @@ const waitForMessageOfType = (worker, type, timeout = 1e3, guard, signal) => new
17
29
  if (timeout !== 0 && timeout !== Infinity) {
18
30
  timeoutId = setTimeout(() => {
19
31
  cleanup();
20
- reject(new Error(`Timeout waiting for message of type: ${type}`));
32
+ reject(new WorkerTimeoutError(type, timeout));
21
33
  }, timeout);
22
34
  }
23
35
  listener = (event) => {
36
+ if (!isReadableMessageData(event.data)) return;
24
37
  if (event.data.type === type) {
25
38
  try {
26
39
  if (!guard || guard(event.data)) {
@@ -37,6 +50,8 @@ const waitForMessageOfType = (worker, type, timeout = 1e3, guard, signal) => new
37
50
  worker.addEventListener("message", listener);
38
51
  });
39
52
  export {
53
+ MaxWorkerTimeout,
54
+ isTimeout,
40
55
  waitForMessageOfType
41
56
  };
42
57
  //# sourceMappingURL=waitForMessageOfType.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/utils/waitForMessageOfType.ts"],
4
- "sourcesContent": ["/**\n * Wait for a message of a specific type or reject after a timeout.\n *\n * A `signal` rejects the promise the moment it is aborted, with the abort reason,\n * so that a caller does not sit out the timeout waiting for a reply that can no\n * longer arrive.\n */\nexport const waitForMessageOfType = (\n worker: Worker,\n type: string,\n timeout = 1000,\n guard?: (data: any) => boolean,\n signal?: AbortSignal,\n) =>\n new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason);\n return;\n }\n\n let timeoutId: number;\n // eslint-disable-next-line prefer-const\n let listener: (event: MessageEvent) => void;\n\n const cleanup = () => {\n clearTimeout(timeoutId);\n worker.removeEventListener('message', listener);\n signal?.removeEventListener('abort', onAbort);\n };\n\n // a function declaration, so that `cleanup` may reference it above its own definition;\n // it only ever runs when a signal was passed in, since that is the only case where it gets registered below\n function onAbort() {\n cleanup();\n reject(signal!.reason);\n }\n\n if (timeout !== 0 && timeout !== Infinity) {\n timeoutId = setTimeout(() => {\n cleanup();\n reject(new Error(`Timeout waiting for message of type: ${type}`));\n }, timeout) as any;\n }\n\n listener = (event) => {\n if (event.data.type === type) {\n try {\n if (!guard || guard(event.data)) {\n cleanup();\n resolve();\n }\n } catch (error) {\n cleanup();\n reject(error);\n }\n }\n };\n\n signal?.addEventListener('abort', onAbort, {once: true});\n\n worker.addEventListener('message', listener);\n });"],
5
- "mappings": "AAOO,MAAM,uBAAuB,CAClC,QACA,MACA,UAAU,KACV,OACA,WAEA,IAAI,QAAc,CAAC,SAAS,WAAW;AACrC,MAAI,QAAQ,SAAS;AACnB,WAAO,OAAO,MAAM;AACpB;AAAA,EACF;AAEA,MAAI;AAEJ,MAAI;AAEJ,QAAM,UAAU,MAAM;AACpB,iBAAa,SAAS;AACtB,WAAO,oBAAoB,WAAW,QAAQ;AAC9C,YAAQ,oBAAoB,SAAS,OAAO;AAAA,EAC9C;AAIA,WAAS,UAAU;AACjB,YAAQ;AACR,WAAO,OAAQ,MAAM;AAAA,EACvB;AAEA,MAAI,YAAY,KAAK,YAAY,UAAU;AACzC,gBAAY,WAAW,MAAM;AAC3B,cAAQ;AACR,aAAO,IAAI,MAAM,wCAAwC,IAAI,EAAE,CAAC;AAAA,IAClE,GAAG,OAAO;AAAA,EACZ;AAEA,aAAW,CAAC,UAAU;AACpB,QAAI,MAAM,KAAK,SAAS,MAAM;AAC5B,UAAI;AACF,YAAI,CAAC,SAAS,MAAM,MAAM,IAAI,GAAG;AAC/B,kBAAQ;AACR,kBAAQ;AAAA,QACV;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ;AACR,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,iBAAiB,SAAS,SAAS,EAAC,MAAM,KAAI,CAAC;AAEvD,SAAO,iBAAiB,WAAW,QAAQ;AAC7C,CAAC;",
4
+ "sourcesContent": ["import type {WorkerReplyType} from '../constants.js';\nimport {WorkerTimeoutError} from '../WorkerTimeoutError.js';\nimport {isReadableMessageData} from '../worker/MessageRouter.js';\n\n/**\n * The largest delay a timer actually keeps. `setTimeout()` truncates its delay into a signed\n * 32-bit field, so a larger number comes back out as some other, shorter one: a millisecond\n * past this bound already fires at once. Whatever it lands on, it is not the wait that was\n * asked for, and nothing says so.\n */\nexport const MaxWorkerTimeout = 2_147_483_647;\n\nexport const isTimeout = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0 && value <= MaxWorkerTimeout;\n\n/**\n * Wait for a message of a specific type or reject after a timeout.\n *\n * A deadline that runs out rejects with a `WorkerTimeoutError`.\n *\n * A `signal` rejects the promise the moment it is aborted, with the abort reason,\n * so that a caller does not sit out the timeout waiting for a reply that can no\n * longer arrive.\n */\nexport const waitForMessageOfType = <T = unknown>(\n worker: Worker,\n type: WorkerReplyType,\n timeout = 1000,\n guard?: (data: T) => boolean,\n signal?: AbortSignal,\n) =>\n new Promise<void>((resolve, reject) => {\n if (timeout !== 0 && timeout !== Infinity && !isTimeout(timeout)) {\n reject(\n new TypeError(\n `cannot wait for a ${type} message: expected a timeout of 1 to ${MaxWorkerTimeout} milliseconds, or 0 or Infinity for no deadline, got ${String(timeout)}`,\n ),\n );\n return;\n }\n\n if (signal?.aborted) {\n reject(signal.reason);\n return;\n }\n\n let timeoutId: ReturnType<typeof setTimeout>;\n let listener: (event: MessageEvent) => void;\n\n const cleanup = () => {\n clearTimeout(timeoutId);\n worker.removeEventListener('message', listener);\n signal?.removeEventListener('abort', onAbort);\n };\n\n // a function declaration, so that `cleanup` may reference it above its own definition;\n // it only ever runs when a signal was passed in, since that is the only case where it gets registered below\n function onAbort() {\n cleanup();\n reject(signal!.reason);\n }\n\n if (timeout !== 0 && timeout !== Infinity) {\n timeoutId = setTimeout(() => {\n cleanup();\n reject(new WorkerTimeoutError(type, timeout));\n }, timeout);\n }\n\n listener = (event) => {\n // three readers listen on this channel and answer the same way: a payload that is not\n // an object carries no `type` to route on, and reading through it takes down whoever\n // reads it -- here, into the listener of every request still waiting\n if (!isReadableMessageData(event.data)) return;\n if (event.data.type === type) {\n try {\n if (!guard || guard(event.data)) {\n cleanup();\n resolve();\n }\n } catch (error) {\n cleanup();\n reject(error);\n }\n }\n };\n\n signal?.addEventListener('abort', onAbort, {once: true});\n\n worker.addEventListener('message', listener);\n });\n"],
5
+ "mappings": "AACA,SAAQ,0BAAyB;AACjC,SAAQ,6BAA4B;AAQ7B,MAAM,mBAAmB;AAEzB,MAAM,YAAY,CAAC,UACxB,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,QAAQ,KAAK,SAAS;AAWxE,MAAM,uBAAuB,CAClC,QACA,MACA,UAAU,KACV,OACA,WAEA,IAAI,QAAc,CAAC,SAAS,WAAW;AACrC,MAAI,YAAY,KAAK,YAAY,YAAY,CAAC,UAAU,OAAO,GAAG;AAChE;AAAA,MACE,IAAI;AAAA,QACF,qBAAqB,IAAI,wCAAwC,gBAAgB,wDAAwD,OAAO,OAAO,CAAC;AAAA,MAC1J;AAAA,IACF;AACA;AAAA,EACF;AAEA,MAAI,QAAQ,SAAS;AACnB,WAAO,OAAO,MAAM;AACpB;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AAEJ,QAAM,UAAU,MAAM;AACpB,iBAAa,SAAS;AACtB,WAAO,oBAAoB,WAAW,QAAQ;AAC9C,YAAQ,oBAAoB,SAAS,OAAO;AAAA,EAC9C;AAIA,WAAS,UAAU;AACjB,YAAQ;AACR,WAAO,OAAQ,MAAM;AAAA,EACvB;AAEA,MAAI,YAAY,KAAK,YAAY,UAAU;AACzC,gBAAY,WAAW,MAAM;AAC3B,cAAQ;AACR,aAAO,IAAI,mBAAmB,MAAM,OAAO,CAAC;AAAA,IAC9C,GAAG,OAAO;AAAA,EACZ;AAEA,aAAW,CAAC,UAAU;AAIpB,QAAI,CAAC,sBAAsB,MAAM,IAAI,EAAG;AACxC,QAAI,MAAM,KAAK,SAAS,MAAM;AAC5B,UAAI;AACF,YAAI,CAAC,SAAS,MAAM,MAAM,IAAI,GAAG;AAC/B,kBAAQ;AACR,kBAAQ;AAAA,QACV;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ;AACR,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,iBAAiB,SAAS,SAAS,EAAC,MAAM,KAAI,CAAC;AAEvD,SAAO,iBAAiB,WAAW,QAAQ;AAC7C,CAAC;",
6
6
  "names": []
7
7
  }
@@ -1,24 +1,100 @@
1
1
  import { ChangeTrailPhase } from '../constants.js';
2
2
  import type { IChangeToken, IComponentChangeType, ICreateEntitiesChange, IDestroyEntitiesChange, IPropertiesChange, ISendEvents, ISetParentChange, IUpdateOrderChange } from '../types.js';
3
+ /**
4
+ * The value a property carries in this bookkeeping when it is set without one — the third form
5
+ * of {@link ComponentPropertiesType}, which travels as a one-element `[key]`. `undefined`
6
+ * cannot say it: that is what a removal reads as, and the two have to stay apart all the way
7
+ * to the entry.
8
+ *
9
+ * It never leaves the View Layer. A change trail runs through `structuredClone()` on its way
10
+ * to a worker, and a symbol on that wire is a `DataCloneError` — so every method that builds
11
+ * an entry turns it back into the one-element form, and the one reader outside this class,
12
+ * `ComponentContext.transferPropertiesTo()`, tests for it before it hands a value on.
13
+ */
14
+ export declare const PropertyWithoutValue: unique symbol;
15
+ /**
16
+ * Tracks one component's outstanding changes between two change trails as four written ↔
17
+ * pending pairs: `#token`/`#nextToken`, `#parentUuid`/`#nextParentUuid`, `#order`/`#nextOrder`
18
+ * and `#properties`/`#nextProperties`. The `make*` methods read the pending half and leave it
19
+ * where it is: a trail entry is a request, and until the Shadow Environment has applied it the
20
+ * change is still owed. {@link ComponentChanges.commitChange} is what moves the line — it folds
21
+ * one applied entry into the written half and releases exactly the pending values that entry
22
+ * carried, so a value that changed again while the entry was on its way goes out with the next
23
+ * trail instead of being lost.
24
+ *
25
+ * Whether anything is outstanding follows from those pairs rather than from a counter, which is
26
+ * what lets a partly applied trail leave the rest of the component untouched.
27
+ *
28
+ * The create/destroy counts behind {@link ComponentChanges.isCreated} and {@link
29
+ * ComponentChanges.isDestroyed} belong to the uuid, not to a `ViewComponent` instance. They
30
+ * carry the rule `ComponentContext.addComponent` keeps: a uuid names one component of a
31
+ * `ComponentContext` at a time, so a later component takes this bookkeeping over only once
32
+ * its predecessor has counted its own `destroy()`. A count pair that outlives its component
33
+ * therefore describes an entity, and the successor on that uuid continues the same entity
34
+ * rather than starting a second.
35
+ */
3
36
  export declare class ComponentChanges {
4
37
  #private;
5
38
  get uuid(): string;
6
39
  constructor(uuid: string);
40
+ /**
41
+ * Whether this component owes the next change trail anything: a creation, a destruction, or a
42
+ * pending value in one of the four written ↔ pending pairs.
43
+ */
7
44
  hasChanges(): boolean;
8
45
  get isNew(): boolean;
9
46
  get isCreated(): boolean;
10
47
  get isDestroyed(): boolean;
11
48
  create(token?: string, parentUuid?: string, order?: number, autoDestructionOnParentRemoval?: boolean): void;
12
49
  destroy(): void;
50
+ /**
51
+ * Drop every pending value without writing any of them forward.
52
+ *
53
+ * This is the way out for a component whose bookkeeping is about to be replaced wholesale —
54
+ * `ComponentContext.reCreateChanges` does exactly that. A trail that has gone out is
55
+ * settled with {@link ComponentChanges.commitChange} instead.
56
+ */
13
57
  clear(): void;
58
+ /**
59
+ * The three scalar pairs below all answer the same question the same way: is anything owed once
60
+ * the entry that is on its way out has landed?
61
+ *
62
+ * The written half alone cannot answer it. It is the last *confirmed* value and stays behind
63
+ * until the entry is committed, so a value the caller sets back to it while the entry travels
64
+ * would look like "nothing to do" — and the entry would then be the last word, leaving the
65
+ * Shadow Environment holding a value the view has already moved on from. So the pending half is
66
+ * only released where there is nothing in flight to contradict it; where there is, the new value
67
+ * takes the pending slot and the emitting side decides, against the written half it settles on.
68
+ */
14
69
  changeToken(token: string | undefined): void;
15
70
  setParent(parentUuid?: string): void;
16
71
  changeOrder(order: number): void;
17
72
  /**
18
- * @returns `true` if the value differs from the last value written to a change trail
73
+ * @returns `true` if the value differs from the last value this component asked for
19
74
  */
20
75
  changeProperty<T = unknown>(key: string, value: T, isEqual?: (a: T, b: T) => boolean): boolean;
76
+ /**
77
+ * Mark `key` as set without giving it a value: the next trail carries an entry that names
78
+ * only the key. A later {@link ComponentChanges.changeProperty} replaces it with a value,
79
+ * and {@link ComponentChanges.removeProperty} takes it away like any other property.
80
+ *
81
+ * @returns `true` if this differs from the last value this component asked for
82
+ */
83
+ setPropertyWithoutValue(key: string): boolean;
21
84
  removeProperty(key: string): void;
85
+ /**
86
+ * The properties this component holds at this moment: the ones a change trail has already
87
+ * carried, overlaid with everything that has accrued since.
88
+ *
89
+ * The overlay is what makes the result the current state instead of the state of the last
90
+ * trail — `#properties` is only written forward while a trail is being built
91
+ * ({@link ComponentChanges.makeCreateEntityChange},
92
+ * {@link ComponentChanges.makeChangePropertyChange}). A key whose accrued value is `undefined`
93
+ * and a key queued for change without an accrued value are both removals, and neither appears
94
+ * in the result. A key that is set without a value stands in the result with
95
+ * {@link PropertyWithoutValue}.
96
+ */
97
+ getProperties(): Map<string, unknown>;
22
98
  createEvent(type: string, data: unknown, transferables?: Transferable[]): void;
23
99
  transferEventsTo(changes: ComponentChanges): void;
24
100
  buildChangeTrail(trail: IComponentChangeType[], trailPhase: ChangeTrailPhase): void;
@@ -29,5 +105,16 @@ export declare class ComponentChanges {
29
105
  makeUpdateOrderChange(): IUpdateOrderChange;
30
106
  makeChangeToken(): IChangeToken;
31
107
  makeChangePropertyChange(): IPropertiesChange;
108
+ /**
109
+ * Fold one trail entry into the written half of this component's bookkeeping: the values the
110
+ * entry carries become the values the next diff is taken against, and the pending half that
111
+ * produced them is released. Called once per entry the Shadow Environment has applied.
112
+ *
113
+ * A pending value is released only while it is still the value the entry carried. Between the
114
+ * build of a trail and its confirmation lies a round trip, and whatever changed in that window
115
+ * is owed to the Shadow Environment — releasing it here would leave the two sides holding
116
+ * different values with nothing left to correct them.
117
+ */
118
+ commitChange(entry: IComponentChangeType): void;
32
119
  }
33
120
  //# sourceMappingURL=ComponentChanges.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ComponentChanges.d.ts","sourceRoot":"","sources":["../../../src/view/ComponentChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,gBAAgB,EAAiC,MAAM,iBAAiB,CAAC;AACjF,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EAEpB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAKrB,qBAAa,gBAAgB;;IAG3B,IAAI,IAAI,IAAI,MAAM,CAEjB;gBAIW,IAAI,EAAE,MAAM;IAQxB,UAAU,IAAI,OAAO;IAIrB,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAWD,MAAM,CAAC,KAAK,GAAE,MAAkB,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,EAAE,8BAA8B,UAAQ;IAUhH,OAAO;IAKP,KAAK;IAgBL,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS;IAgBrC,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM;IAS7B,WAAW,CAAC,KAAK,EAAE,MAAM;IAazB;;OAEG;IACH,cAAc,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,GAAG,OAAO;IAgB9F,cAAc,CAAC,GAAG,EAAE,MAAM;IAgB1B,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,YAAY,EAAE;IAQvE,gBAAgB,CAAC,OAAO,EAAE,gBAAgB;IAW1C,gBAAgB,CAAC,KAAK,EAAE,oBAAoB,EAAE,EAAE,UAAU,EAAE,gBAAgB;IAsC5E,UAAU,IAAI,WAAW;IAYzB,sBAAsB,IAAI,qBAAqB;IAuC/C,uBAAuB,IAAI,sBAAsB;IAOjD,mBAAmB,IAAI,gBAAgB;IAgBvC,qBAAqB,IAAI,kBAAkB;IAU3C,eAAe,IAAI,YAAY;IAU/B,wBAAwB,IAAI,iBAAiB;CAwB9C"}
1
+ {"version":3,"file":"ComponentChanges.d.ts","sourceRoot":"","sources":["../../../src/view/ComponentChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,gBAAgB,EAAiC,MAAM,iBAAiB,CAAC;AACjF,OAAO,KAAK,EAEV,YAAY,EACZ,oBAAoB,EAEpB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAKrB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,eAAkD,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,gBAAgB;;IAG3B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,YAAY,IAAI,EAAE,MAAM,EAEvB;IAMD;;;OAGG;IACH,UAAU,IAAI,OAAO,CAUpB;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAWD,MAAM,CAAC,KAAK,GAAE,MAAkB,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,EAAE,8BAA8B,UAAQ,QAO/G;IAED,OAAO,SAEN;IAWD;;;;;;OAMG;IACH,KAAK,SAWJ;IAED;;;;;;;;;;OAUG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,QAWpC;IAED,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM,QAM5B;IAED,WAAW,CAAC,KAAK,EAAE,MAAM,QAIxB;IAwBD;;OAEG;IACH,cAAc,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,GAAG,OAAO,CA+C7F;IAED;;;;;;OAMG;IACH,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE5C;IAED,cAAc,CAAC,GAAG,EAAE,MAAM,QAgBzB;IAED;;;;;;;;;;;OAWG;IACH,aAAa,IAAI,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAapC;IAKD,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,YAAY,EAAE,QAKtE;IAED,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,QASzC;IAED,gBAAgB,CAAC,KAAK,EAAE,oBAAoB,EAAE,EAAE,UAAU,EAAE,gBAAgB,QAoC3E;IAED,UAAU,IAAI,WAAW,CAUxB;IAED,sBAAsB,IAAI,qBAAqB,CA2C9C;IAED,uBAAuB,IAAI,sBAAsB,CAKhD;IAED,mBAAmB,IAAI,gBAAgB,CAYtC;IAED,qBAAqB,IAAI,kBAAkB,CAM1C;IAED,eAAe,IAAI,YAAY,CAM9B;IAED,wBAAwB,IAAI,iBAAiB,CAgB5C;IAaD;;;;;;;;;OASG;IACH,YAAY,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,CA4D9C;CA0CF"}
@@ -1,20 +1,24 @@
1
1
  import { ChangeTrailPhase, ComponentChangeType, VoidToken } from "../constants.js";
2
2
  import { appendToEnd, removeFrom } from "../utils/array-utils.js";
3
3
  const ROOT = "#root";
4
+ const PropertyWithoutValue = /* @__PURE__ */ Symbol("shadow-objects/property-without-value");
4
5
  class ComponentChanges {
5
6
  #uuid;
6
7
  get uuid() {
7
8
  return this.#uuid;
8
9
  }
9
- #serial = 0;
10
10
  constructor(uuid) {
11
11
  this.#uuid = uuid;
12
12
  }
13
13
  #isNew = true;
14
14
  #createCount = 0;
15
15
  #destroyCount = 0;
16
+ /**
17
+ * Whether this component owes the next change trail anything: a creation, a destruction, or a
18
+ * pending value in one of the four written ↔ pending pairs.
19
+ */
16
20
  hasChanges() {
17
- return this.#serial > 0;
21
+ return this.#isNew && this.isCreated || this.isDestroyed || this.#nextToken !== void 0 && this.#nextToken !== this.#token || this.#hasPendingParent() || this.#nextOrder !== void 0 && this.#nextOrder !== this.#order || this.#propsChangeOrder.length > 0 || this.#events.length > 0;
18
22
  }
19
23
  get isNew() {
20
24
  return this.#isNew;
@@ -33,7 +37,6 @@ class ComponentChanges {
33
37
  #nextParentUuid;
34
38
  #nextOrder;
35
39
  create(token = VoidToken, parentUuid, order = 0, autoDestructionOnParentRemoval = false) {
36
- this.#serial++;
37
40
  this.#createCount++;
38
41
  this.#nextToken = token;
39
42
  this.#nextParentUuid = parentUuid ?? ROOT;
@@ -42,76 +45,154 @@ class ComponentChanges {
42
45
  }
43
46
  destroy() {
44
47
  this.#destroyCount++;
45
- this.#serial++;
46
48
  }
49
+ /**
50
+ * Whether the pending parent asks for something the confirmed one is not already. A pending
51
+ * value that has come back to the confirmed parent is a change that cancelled itself out.
52
+ */
53
+ #hasPendingParent() {
54
+ if (this.#nextParentUuid === void 0) return false;
55
+ return (this.#nextParentUuid === ROOT ? void 0 : this.#nextParentUuid) !== this.#parentUuid;
56
+ }
57
+ /**
58
+ * Drop every pending value without writing any of them forward.
59
+ *
60
+ * This is the way out for a component whose bookkeeping is about to be replaced wholesale —
61
+ * `ComponentContext.reCreateChanges` does exactly that. A trail that has gone out is
62
+ * settled with {@link ComponentChanges.commitChange} instead.
63
+ */
47
64
  clear() {
48
- this.#serial = 0;
49
- this.#isNew = false;
50
65
  this.#nextToken = void 0;
51
66
  this.#nextParentUuid = void 0;
52
67
  this.#nextOrder = void 0;
53
68
  this.#nextProperties.clear();
54
69
  this.#propsChangeOrder.length = 0;
70
+ this.#travellingProperties = void 0;
55
71
  this.#events.length = 0;
56
72
  this.#transferables.clear();
57
73
  }
74
+ /**
75
+ * The three scalar pairs below all answer the same question the same way: is anything owed once
76
+ * the entry that is on its way out has landed?
77
+ *
78
+ * The written half alone cannot answer it. It is the last *confirmed* value and stays behind
79
+ * until the entry is committed, so a value the caller sets back to it while the entry travels
80
+ * would look like "nothing to do" — and the entry would then be the last word, leaving the
81
+ * Shadow Environment holding a value the view has already moved on from. So the pending half is
82
+ * only released where there is nothing in flight to contradict it; where there is, the new value
83
+ * takes the pending slot and the emitting side decides, against the written half it settles on.
84
+ */
58
85
  changeToken(token) {
59
86
  token ??= VoidToken;
60
- if (token === this.#token) {
61
- this.#nextToken = this.#isNew ? token : void 0;
62
- } else {
63
- this.#nextToken = token;
64
- this.#serial++;
65
- }
87
+ if (token === this.#token && this.#nextToken === void 0 && !this.#isNew) return;
88
+ this.#nextToken = token;
66
89
  }
67
90
  setParent(parentUuid) {
68
- if (parentUuid === this.#parentUuid) {
69
- this.#nextParentUuid = void 0;
70
- } else {
71
- this.#nextParentUuid = parentUuid ?? ROOT;
72
- this.#serial++;
73
- }
91
+ const nextParentUuid = parentUuid ?? ROOT;
92
+ if (nextParentUuid === (this.#parentUuid ?? ROOT) && this.#nextParentUuid === void 0) return;
93
+ this.#nextParentUuid = nextParentUuid;
74
94
  }
75
95
  changeOrder(order) {
76
- if (order === this.#order) {
77
- this.#nextOrder = void 0;
78
- } else {
79
- this.#nextOrder = order;
80
- this.#serial++;
81
- }
96
+ if (order === this.#order && this.#nextOrder === void 0) return;
97
+ this.#nextOrder = order;
82
98
  }
83
99
  #properties = /* @__PURE__ */ new Map();
84
100
  #nextProperties = /* @__PURE__ */ new Map();
101
+ /**
102
+ * The keys with a queued change, in the order their changes happened — the order an entry
103
+ * carries them in, and the reason this is a list and not a set.
104
+ *
105
+ * A set beside it would answer membership in one step, and per call it does win from about eight
106
+ * keys on: 8.0 ns for the set against 10.7 ns for the list at eight keys, 10.4 ns against 43.9 ns
107
+ * at 64. The distance is what decides against it. At the upper stop this repository can produce —
108
+ * 64 keys, every one of them changed in every frame at 60 fps, so 3840 calls per second — that is
109
+ * 0.13 ms saved per second of animation. And membership is the smaller half: `removeFrom()` and
110
+ * `appendToEnd()` are an `indexOf` plus a `splice`, and the splice stays linear no matter what
111
+ * answers the membership. Numbers measured 2026-08-31 on node v25.9.0.
112
+ */
85
113
  #propsChangeOrder = [];
86
- // we use an Array here and not a Set, because we want to keep the change order
87
114
  /**
88
- * @returns `true` if the value differs from the last value written to a change trail
115
+ * The keys an entry on its way out carries, until that entry is settled. For those keys the
116
+ * written half is behind, and {@link ComponentChanges.changeProperty} must not cancel against it.
117
+ */
118
+ #travellingProperties;
119
+ /**
120
+ * @returns `true` if the value differs from the last value this component asked for
89
121
  */
90
122
  changeProperty(key, value, isEqual) {
91
- const prevValue = this.#properties.get(key);
92
- const valueChanged = isEqual == null && value !== prevValue || isEqual != null && !isEqual(value, prevValue);
93
- if (valueChanged) {
94
- this.#nextProperties.set(key, value);
95
- appendToEnd(this.#propsChangeOrder, key);
96
- this.#serial++;
123
+ const equals = (a, b) => (
124
+ // a registered rule compares values, and the marker is not one -- handed to it, a rule
125
+ // would be asked about a property it never saw, and one that reaches into its arguments
126
+ // throws on a symbol. Where either side is the marker the only question left is whether
127
+ // it is still the same third form, and identity answers that.
128
+ a === PropertyWithoutValue || b === PropertyWithoutValue ? a === b : isEqual == null ? a === b : isEqual(a, b)
129
+ );
130
+ const isQueued = this.#propsChangeOrder.includes(key);
131
+ const isTravelling = this.#travellingProperties?.has(key) ?? false;
132
+ let prevValue;
133
+ if (isQueued) {
134
+ prevValue = this.#nextProperties.get(key);
135
+ } else if (isTravelling) {
136
+ prevValue = this.#travellingProperties.get(key);
97
137
  } else {
138
+ prevValue = this.#properties.get(key);
139
+ }
140
+ const valueChanged = !equals(value, prevValue);
141
+ const nothingToSend = !isTravelling && (isQueued ? equals(value, this.#properties.get(key)) : !valueChanged);
142
+ if (nothingToSend) {
98
143
  this.#nextProperties.delete(key);
99
144
  removeFrom(this.#propsChangeOrder, key);
145
+ } else {
146
+ this.#nextProperties.set(key, value);
147
+ appendToEnd(this.#propsChangeOrder, key);
100
148
  }
101
149
  return valueChanged;
102
150
  }
151
+ /**
152
+ * Mark `key` as set without giving it a value: the next trail carries an entry that names
153
+ * only the key. A later {@link ComponentChanges.changeProperty} replaces it with a value,
154
+ * and {@link ComponentChanges.removeProperty} takes it away like any other property.
155
+ *
156
+ * @returns `true` if this differs from the last value this component asked for
157
+ */
158
+ setPropertyWithoutValue(key) {
159
+ return this.changeProperty(key, PropertyWithoutValue);
160
+ }
103
161
  removeProperty(key) {
104
- const propExists = this.#properties.has(key);
105
- if (this.#nextProperties.has(key)) {
106
- this.#nextProperties.delete(key);
107
- if (!propExists) {
108
- removeFrom(this.#propsChangeOrder, key);
109
- }
110
- } else if (propExists) {
162
+ const isQueued = this.#propsChangeOrder.includes(key);
163
+ this.#nextProperties.delete(key);
164
+ if (!this.#properties.has(key) && !this.#travellingProperties?.has(key)) {
165
+ removeFrom(this.#propsChangeOrder, key);
166
+ return;
167
+ }
168
+ if (!isQueued) {
111
169
  appendToEnd(this.#propsChangeOrder, key);
112
- this.#serial++;
113
170
  }
114
171
  }
172
+ /**
173
+ * The properties this component holds at this moment: the ones a change trail has already
174
+ * carried, overlaid with everything that has accrued since.
175
+ *
176
+ * The overlay is what makes the result the current state instead of the state of the last
177
+ * trail — `#properties` is only written forward while a trail is being built
178
+ * ({@link ComponentChanges.makeCreateEntityChange},
179
+ * {@link ComponentChanges.makeChangePropertyChange}). A key whose accrued value is `undefined`
180
+ * and a key queued for change without an accrued value are both removals, and neither appears
181
+ * in the result. A key that is set without a value stands in the result with
182
+ * {@link PropertyWithoutValue}.
183
+ */
184
+ getProperties() {
185
+ const properties = new Map(this.#properties);
186
+ for (const key of this.#propsChangeOrder) {
187
+ const value = this.#nextProperties.get(key);
188
+ if (value === void 0) {
189
+ properties.delete(key);
190
+ } else {
191
+ properties.set(key, value);
192
+ }
193
+ }
194
+ return properties;
195
+ }
115
196
  #events = [];
116
197
  #transferables = /* @__PURE__ */ new Set();
117
198
  createEvent(type, data, transferables) {
@@ -119,7 +200,6 @@ class ComponentChanges {
119
200
  for (const transferable of transferables ?? []) {
120
201
  this.#transferables.add(transferable);
121
202
  }
122
- this.#serial++;
123
203
  }
124
204
  transferEventsTo(changes) {
125
205
  if (this.#events.length > 0) {
@@ -139,7 +219,7 @@ class ComponentChanges {
139
219
  if (isNew) {
140
220
  trail.push(this.makeCreateEntityChange());
141
221
  } else if (!isDestroyed) {
142
- if (this.#nextParentUuid !== void 0 && !(this.#nextParentUuid === ROOT && this.#parentUuid === void 0)) {
222
+ if (this.#hasPendingParent()) {
143
223
  trail.push(this.makeSetParentChange());
144
224
  } else if (this.#nextOrder !== void 0 && this.#nextOrder !== this.#order) {
145
225
  trail.push(this.makeUpdateOrderChange());
@@ -182,22 +262,25 @@ class ComponentChanges {
182
262
  uuid: this.#uuid,
183
263
  token
184
264
  };
185
- this.#token = token;
186
- if (this.#nextParentUuid !== void 0) {
187
- const nextParentUuid = this.#nextParentUuid === ROOT ? void 0 : this.#nextParentUuid;
188
- this.#parentUuid = nextParentUuid;
189
- if (nextParentUuid !== void 0) {
190
- entry.parentUuid = nextParentUuid;
191
- }
265
+ if (this.#nextParentUuid !== void 0 && this.#nextParentUuid !== ROOT) {
266
+ entry.parentUuid = this.#nextParentUuid;
192
267
  }
193
268
  if (this.#nextProperties.size > 0) {
194
- entry.properties = Array.from(this.#nextProperties.entries()).filter(([, value]) => value !== void 0);
195
- for (const [key, value] of entry.properties) {
196
- this.#properties.set(key, value);
269
+ const properties = [];
270
+ for (const [key, value] of this.#nextProperties) {
271
+ if (value === PropertyWithoutValue) {
272
+ properties.push([key]);
273
+ } else if (value !== void 0) {
274
+ properties.push([key, value]);
275
+ }
276
+ }
277
+ this.#noteTravellingProperties(properties);
278
+ if (properties.length > 0) {
279
+ entry.properties = properties;
197
280
  }
198
281
  }
199
282
  if (this.#nextOrder !== void 0 && this.#nextOrder !== this.#order) {
200
- entry.order = this.#order = this.#nextOrder;
283
+ entry.order = this.#nextOrder;
201
284
  }
202
285
  if (this.#autoDestructionOnParentRemoval) {
203
286
  entry.autoDestructionOnParentRemoval = true;
@@ -211,56 +294,143 @@ class ComponentChanges {
211
294
  };
212
295
  }
213
296
  makeSetParentChange() {
214
- this.#parentUuid = this.#nextParentUuid === ROOT ? void 0 : this.#nextParentUuid;
215
297
  const entry = {
216
298
  type: ComponentChangeType.SetParent,
217
299
  uuid: this.#uuid,
218
- parentUuid: this.#parentUuid
300
+ parentUuid: this.#nextParentUuid === ROOT ? void 0 : this.#nextParentUuid
219
301
  };
220
302
  if (this.#nextOrder !== void 0 && this.#nextOrder !== this.#order) {
221
- entry.order = this.#order = this.#nextOrder;
303
+ entry.order = this.#nextOrder;
222
304
  }
223
305
  return entry;
224
306
  }
225
307
  makeUpdateOrderChange() {
226
- this.#order = this.#nextOrder ?? 0;
227
308
  return {
228
309
  type: ComponentChangeType.UpdateOrder,
229
310
  uuid: this.#uuid,
230
- order: this.#order
311
+ order: this.#nextOrder ?? 0
231
312
  };
232
313
  }
233
314
  makeChangeToken() {
234
- this.#token = this.#nextToken ?? VoidToken;
235
315
  return {
236
316
  type: ComponentChangeType.ChangeToken,
237
317
  uuid: this.#uuid,
238
- token: this.#token
318
+ token: this.#nextToken ?? VoidToken
239
319
  };
240
320
  }
241
321
  makeChangePropertyChange() {
242
322
  const properties = this.#propsChangeOrder.map((key) => {
243
- if (this.#nextProperties.has(key)) {
244
- const nextValue = this.#nextProperties.get(key);
245
- if (nextValue === void 0) {
246
- this.#properties.delete(key);
247
- } else {
248
- this.#properties.set(key, nextValue);
249
- }
250
- return [key, nextValue];
251
- } else {
252
- this.#properties.delete(key);
253
- return [key, void 0];
254
- }
323
+ const value = this.#nextProperties.get(key);
324
+ return value === PropertyWithoutValue ? [key] : [key, value];
255
325
  });
326
+ this.#noteTravellingProperties(properties);
256
327
  return {
257
328
  type: ComponentChangeType.ChangeProperties,
258
329
  uuid: this.#uuid,
259
330
  properties
260
331
  };
261
332
  }
333
+ /**
334
+ * Records the keys of an entry that is going out, so a later change knows the written half
335
+ * is behind.
336
+ */
337
+ #noteTravellingProperties(properties) {
338
+ this.#travellingProperties = new Map(
339
+ properties.map((entry) => [entry[0], entry.length === 1 ? PropertyWithoutValue : entry[1]])
340
+ );
341
+ }
342
+ /**
343
+ * Fold one trail entry into the written half of this component's bookkeeping: the values the
344
+ * entry carries become the values the next diff is taken against, and the pending half that
345
+ * produced them is released. Called once per entry the Shadow Environment has applied.
346
+ *
347
+ * A pending value is released only while it is still the value the entry carried. Between the
348
+ * build of a trail and its confirmation lies a round trip, and whatever changed in that window
349
+ * is owed to the Shadow Environment — releasing it here would leave the two sides holding
350
+ * different values with nothing left to correct them.
351
+ */
352
+ commitChange(entry) {
353
+ switch (entry.type) {
354
+ case ComponentChangeType.CreateEntities:
355
+ this.#commitToken(entry.token);
356
+ this.#commitParentUuid(entry.parentUuid);
357
+ this.#commitOrder(entry.order ?? this.#order);
358
+ this.#commitProperties(entry.properties);
359
+ for (const key of this.#propsChangeOrder.slice(0)) {
360
+ if (this.#nextProperties.get(key) === void 0 && !this.#properties.has(key)) {
361
+ this.#nextProperties.delete(key);
362
+ removeFrom(this.#propsChangeOrder, key);
363
+ }
364
+ }
365
+ this.#travellingProperties = void 0;
366
+ this.#isNew = false;
367
+ break;
368
+ case ComponentChangeType.SetParent:
369
+ this.#commitParentUuid(entry.parentUuid);
370
+ this.#commitOrder(entry.order ?? this.#order);
371
+ break;
372
+ case ComponentChangeType.UpdateOrder:
373
+ this.#commitOrder(entry.order);
374
+ break;
375
+ case ComponentChangeType.ChangeToken:
376
+ this.#commitToken(entry.token);
377
+ break;
378
+ case ComponentChangeType.ChangeProperties:
379
+ this.#commitProperties(entry.properties);
380
+ this.#travellingProperties = void 0;
381
+ break;
382
+ case ComponentChangeType.SendEvents:
383
+ this.#events.splice(0, entry.events.length);
384
+ for (const transferable of entry.transferables ?? []) {
385
+ this.#transferables.delete(transferable);
386
+ }
387
+ break;
388
+ case ComponentChangeType.DestroyEntities:
389
+ this.#token = VoidToken;
390
+ this.#parentUuid = void 0;
391
+ this.#order = 0;
392
+ this.#properties.clear();
393
+ this.#travellingProperties = void 0;
394
+ this.#isNew = true;
395
+ break;
396
+ }
397
+ }
398
+ #commitToken(token) {
399
+ this.#token = token;
400
+ if (this.#nextToken === token) {
401
+ this.#nextToken = void 0;
402
+ }
403
+ }
404
+ #commitParentUuid(parentUuid) {
405
+ this.#parentUuid = parentUuid;
406
+ if ((this.#nextParentUuid === ROOT ? void 0 : this.#nextParentUuid) === parentUuid) {
407
+ this.#nextParentUuid = void 0;
408
+ }
409
+ }
410
+ #commitOrder(order) {
411
+ this.#order = order;
412
+ if (this.#nextOrder === order) {
413
+ this.#nextOrder = void 0;
414
+ }
415
+ }
416
+ #commitProperties(properties) {
417
+ for (const entry of properties ?? []) {
418
+ const key = entry[0];
419
+ const value = entry.length === 1 ? PropertyWithoutValue : entry[1];
420
+ if (value === void 0) {
421
+ this.#properties.delete(key);
422
+ } else {
423
+ this.#properties.set(key, value);
424
+ }
425
+ if (this.#nextProperties.get(key) === value) {
426
+ this.#nextProperties.delete(key);
427
+ removeFrom(this.#propsChangeOrder, key);
428
+ }
429
+ }
430
+ }
262
431
  }
263
432
  export {
264
- ComponentChanges
433
+ ComponentChanges,
434
+ PropertyWithoutValue
265
435
  };
266
436
  //# sourceMappingURL=ComponentChanges.js.map