@czap/core 0.1.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 (372) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -0
  3. package/dist/addressed-digest.d.ts +15 -0
  4. package/dist/addressed-digest.d.ts.map +1 -0
  5. package/dist/addressed-digest.js +35 -0
  6. package/dist/addressed-digest.js.map +1 -0
  7. package/dist/animation.d.ts +46 -0
  8. package/dist/animation.d.ts.map +1 -0
  9. package/dist/animation.js +70 -0
  10. package/dist/animation.js.map +1 -0
  11. package/dist/assembly.d.ts +25 -0
  12. package/dist/assembly.d.ts.map +1 -0
  13. package/dist/assembly.js +58 -0
  14. package/dist/assembly.js.map +1 -0
  15. package/dist/av-bridge.d.ts +74 -0
  16. package/dist/av-bridge.d.ts.map +1 -0
  17. package/dist/av-bridge.js +107 -0
  18. package/dist/av-bridge.js.map +1 -0
  19. package/dist/av-renderer.d.ts +56 -0
  20. package/dist/av-renderer.d.ts.map +1 -0
  21. package/dist/av-renderer.js +65 -0
  22. package/dist/av-renderer.js.map +1 -0
  23. package/dist/blend.d.ts +61 -0
  24. package/dist/blend.d.ts.map +1 -0
  25. package/dist/blend.js +100 -0
  26. package/dist/blend.js.map +1 -0
  27. package/dist/boundary.d.ts +154 -0
  28. package/dist/boundary.d.ts.map +1 -0
  29. package/dist/boundary.js +269 -0
  30. package/dist/boundary.js.map +1 -0
  31. package/dist/brands.d.ts +63 -0
  32. package/dist/brands.d.ts.map +1 -0
  33. package/dist/brands.js +31 -0
  34. package/dist/brands.js.map +1 -0
  35. package/dist/caps.d.ts +49 -0
  36. package/dist/caps.d.ts.map +1 -0
  37. package/dist/caps.js +73 -0
  38. package/dist/caps.js.map +1 -0
  39. package/dist/capsule.d.ts +77 -0
  40. package/dist/capsule.d.ts.map +1 -0
  41. package/dist/capsule.js +18 -0
  42. package/dist/capsule.js.map +1 -0
  43. package/dist/capsules/boundary-evaluate.d.ts +28 -0
  44. package/dist/capsules/boundary-evaluate.d.ts.map +1 -0
  45. package/dist/capsules/boundary-evaluate.js +117 -0
  46. package/dist/capsules/boundary-evaluate.js.map +1 -0
  47. package/dist/capsules/canonical-cbor.d.ts +13 -0
  48. package/dist/capsules/canonical-cbor.d.ts.map +1 -0
  49. package/dist/capsules/canonical-cbor.js +60 -0
  50. package/dist/capsules/canonical-cbor.js.map +1 -0
  51. package/dist/capsules/token-buffer.d.ts +24 -0
  52. package/dist/capsules/token-buffer.d.ts.map +1 -0
  53. package/dist/capsules/token-buffer.js +53 -0
  54. package/dist/capsules/token-buffer.js.map +1 -0
  55. package/dist/capture.d.ts +40 -0
  56. package/dist/capture.d.ts.map +1 -0
  57. package/dist/capture.js +10 -0
  58. package/dist/capture.js.map +1 -0
  59. package/dist/cbor.d.ts +33 -0
  60. package/dist/cbor.d.ts.map +1 -0
  61. package/dist/cbor.js +179 -0
  62. package/dist/cbor.js.map +1 -0
  63. package/dist/cell.d.ts +53 -0
  64. package/dist/cell.d.ts.map +1 -0
  65. package/dist/cell.js +83 -0
  66. package/dist/cell.js.map +1 -0
  67. package/dist/codec.d.ts +30 -0
  68. package/dist/codec.d.ts.map +1 -0
  69. package/dist/codec.js +25 -0
  70. package/dist/codec.js.map +1 -0
  71. package/dist/component.d.ts +52 -0
  72. package/dist/component.d.ts.map +1 -0
  73. package/dist/component.js +44 -0
  74. package/dist/component.js.map +1 -0
  75. package/dist/composable.d.ts +76 -0
  76. package/dist/composable.d.ts.map +1 -0
  77. package/dist/composable.js +221 -0
  78. package/dist/composable.js.map +1 -0
  79. package/dist/compositor-pool.d.ts +74 -0
  80. package/dist/compositor-pool.d.ts.map +1 -0
  81. package/dist/compositor-pool.js +119 -0
  82. package/dist/compositor-pool.js.map +1 -0
  83. package/dist/compositor.d.ts +90 -0
  84. package/dist/compositor.d.ts.map +1 -0
  85. package/dist/compositor.js +278 -0
  86. package/dist/compositor.js.map +1 -0
  87. package/dist/config.d.ts +72 -0
  88. package/dist/config.d.ts.map +1 -0
  89. package/dist/config.js +97 -0
  90. package/dist/config.js.map +1 -0
  91. package/dist/dag.d.ts +251 -0
  92. package/dist/dag.d.ts.map +1 -0
  93. package/dist/dag.js +450 -0
  94. package/dist/dag.js.map +1 -0
  95. package/dist/defaults.d.ts +45 -0
  96. package/dist/defaults.d.ts.map +1 -0
  97. package/dist/defaults.js +45 -0
  98. package/dist/defaults.js.map +1 -0
  99. package/dist/derived.d.ts +34 -0
  100. package/dist/derived.d.ts.map +1 -0
  101. package/dist/derived.js +101 -0
  102. package/dist/derived.js.map +1 -0
  103. package/dist/diagnostics.d.ts +77 -0
  104. package/dist/diagnostics.d.ts.map +1 -0
  105. package/dist/diagnostics.js +122 -0
  106. package/dist/diagnostics.js.map +1 -0
  107. package/dist/dirty.d.ts +55 -0
  108. package/dist/dirty.d.ts.map +1 -0
  109. package/dist/dirty.js +80 -0
  110. package/dist/dirty.js.map +1 -0
  111. package/dist/easing.d.ts +55 -0
  112. package/dist/easing.d.ts.map +1 -0
  113. package/dist/easing.js +291 -0
  114. package/dist/easing.js.map +1 -0
  115. package/dist/ecs.d.ts +105 -0
  116. package/dist/ecs.d.ts.map +1 -0
  117. package/dist/ecs.js +245 -0
  118. package/dist/ecs.js.map +1 -0
  119. package/dist/fnv.d.ts +14 -0
  120. package/dist/fnv.d.ts.map +1 -0
  121. package/dist/fnv.js +28 -0
  122. package/dist/fnv.js.map +1 -0
  123. package/dist/frame-budget.d.ts +73 -0
  124. package/dist/frame-budget.d.ts.map +1 -0
  125. package/dist/frame-budget.js +114 -0
  126. package/dist/frame-budget.js.map +1 -0
  127. package/dist/gen-frame.d.ts +102 -0
  128. package/dist/gen-frame.d.ts.map +1 -0
  129. package/dist/gen-frame.js +121 -0
  130. package/dist/gen-frame.js.map +1 -0
  131. package/dist/harness/arbitrary-from-schema.d.ts +28 -0
  132. package/dist/harness/arbitrary-from-schema.d.ts.map +1 -0
  133. package/dist/harness/arbitrary-from-schema.js +262 -0
  134. package/dist/harness/arbitrary-from-schema.js.map +1 -0
  135. package/dist/harness/cached-projection.d.ts +19 -0
  136. package/dist/harness/cached-projection.d.ts.map +1 -0
  137. package/dist/harness/cached-projection.js +39 -0
  138. package/dist/harness/cached-projection.js.map +1 -0
  139. package/dist/harness/index.d.ts +16 -0
  140. package/dist/harness/index.d.ts.map +1 -0
  141. package/dist/harness/index.js +15 -0
  142. package/dist/harness/index.js.map +1 -0
  143. package/dist/harness/policy-gate.d.ts +18 -0
  144. package/dist/harness/policy-gate.d.ts.map +1 -0
  145. package/dist/harness/policy-gate.js +46 -0
  146. package/dist/harness/policy-gate.js.map +1 -0
  147. package/dist/harness/pure-transform.d.ts +42 -0
  148. package/dist/harness/pure-transform.d.ts.map +1 -0
  149. package/dist/harness/pure-transform.js +76 -0
  150. package/dist/harness/pure-transform.js.map +1 -0
  151. package/dist/harness/receipted-mutation.d.ts +23 -0
  152. package/dist/harness/receipted-mutation.d.ts.map +1 -0
  153. package/dist/harness/receipted-mutation.js +52 -0
  154. package/dist/harness/receipted-mutation.js.map +1 -0
  155. package/dist/harness/scene-composition.d.ts +19 -0
  156. package/dist/harness/scene-composition.d.ts.map +1 -0
  157. package/dist/harness/scene-composition.js +47 -0
  158. package/dist/harness/scene-composition.js.map +1 -0
  159. package/dist/harness/site-adapter.d.ts +18 -0
  160. package/dist/harness/site-adapter.d.ts.map +1 -0
  161. package/dist/harness/site-adapter.js +38 -0
  162. package/dist/harness/site-adapter.js.map +1 -0
  163. package/dist/harness/state-machine.d.ts +19 -0
  164. package/dist/harness/state-machine.d.ts.map +1 -0
  165. package/dist/harness/state-machine.js +44 -0
  166. package/dist/harness/state-machine.js.map +1 -0
  167. package/dist/hlc.d.ts +99 -0
  168. package/dist/hlc.d.ts.map +1 -0
  169. package/dist/hlc.js +219 -0
  170. package/dist/hlc.js.map +1 -0
  171. package/dist/index.d.ts +104 -0
  172. package/dist/index.d.ts.map +1 -0
  173. package/dist/index.js +137 -0
  174. package/dist/index.js.map +1 -0
  175. package/dist/interpolate.d.ts +14 -0
  176. package/dist/interpolate.d.ts.map +1 -0
  177. package/dist/interpolate.js +31 -0
  178. package/dist/interpolate.js.map +1 -0
  179. package/dist/live-cell.d.ts +46 -0
  180. package/dist/live-cell.d.ts.map +1 -0
  181. package/dist/live-cell.js +154 -0
  182. package/dist/live-cell.js.map +1 -0
  183. package/dist/op.d.ts +58 -0
  184. package/dist/op.d.ts.map +1 -0
  185. package/dist/op.js +171 -0
  186. package/dist/op.js.map +1 -0
  187. package/dist/plan.d.ts +195 -0
  188. package/dist/plan.d.ts.map +1 -0
  189. package/dist/plan.js +211 -0
  190. package/dist/plan.js.map +1 -0
  191. package/dist/protocol.d.ts +33 -0
  192. package/dist/protocol.d.ts.map +1 -0
  193. package/dist/protocol.js +10 -0
  194. package/dist/protocol.js.map +1 -0
  195. package/dist/quantizer-types.d.ts +28 -0
  196. package/dist/quantizer-types.d.ts.map +1 -0
  197. package/dist/quantizer-types.js +9 -0
  198. package/dist/quantizer-types.js.map +1 -0
  199. package/dist/receipt.d.ts +294 -0
  200. package/dist/receipt.d.ts.map +1 -0
  201. package/dist/receipt.js +352 -0
  202. package/dist/receipt.js.map +1 -0
  203. package/dist/runtime-coordinator.d.ts +75 -0
  204. package/dist/runtime-coordinator.d.ts.map +1 -0
  205. package/dist/runtime-coordinator.js +149 -0
  206. package/dist/runtime-coordinator.js.map +1 -0
  207. package/dist/scheduler.d.ts +58 -0
  208. package/dist/scheduler.d.ts.map +1 -0
  209. package/dist/scheduler.js +109 -0
  210. package/dist/scheduler.js.map +1 -0
  211. package/dist/ship-capsule.d.ts +54 -0
  212. package/dist/ship-capsule.d.ts.map +1 -0
  213. package/dist/ship-capsule.js +142 -0
  214. package/dist/ship-capsule.js.map +1 -0
  215. package/dist/ship-manifest.d.ts +45 -0
  216. package/dist/ship-manifest.d.ts.map +1 -0
  217. package/dist/ship-manifest.js +175 -0
  218. package/dist/ship-manifest.js.map +1 -0
  219. package/dist/signal.d.ts +149 -0
  220. package/dist/signal.d.ts.map +1 -0
  221. package/dist/signal.js +277 -0
  222. package/dist/signal.js.map +1 -0
  223. package/dist/speculative.d.ts +67 -0
  224. package/dist/speculative.d.ts.map +1 -0
  225. package/dist/speculative.js +139 -0
  226. package/dist/speculative.js.map +1 -0
  227. package/dist/store.d.ts +39 -0
  228. package/dist/store.d.ts.map +1 -0
  229. package/dist/store.js +42 -0
  230. package/dist/store.js.map +1 -0
  231. package/dist/style.d.ts +119 -0
  232. package/dist/style.d.ts.map +1 -0
  233. package/dist/style.js +168 -0
  234. package/dist/style.js.map +1 -0
  235. package/dist/testing.d.ts +14 -0
  236. package/dist/testing.d.ts.map +1 -0
  237. package/dist/testing.js +14 -0
  238. package/dist/testing.js.map +1 -0
  239. package/dist/theme.d.ts +78 -0
  240. package/dist/theme.d.ts.map +1 -0
  241. package/dist/theme.js +109 -0
  242. package/dist/theme.js.map +1 -0
  243. package/dist/timeline.d.ts +45 -0
  244. package/dist/timeline.d.ts.map +1 -0
  245. package/dist/timeline.js +101 -0
  246. package/dist/timeline.js.map +1 -0
  247. package/dist/token-buffer.d.ts +43 -0
  248. package/dist/token-buffer.d.ts.map +1 -0
  249. package/dist/token-buffer.js +112 -0
  250. package/dist/token-buffer.js.map +1 -0
  251. package/dist/token.d.ts +107 -0
  252. package/dist/token.d.ts.map +1 -0
  253. package/dist/token.js +143 -0
  254. package/dist/token.js.map +1 -0
  255. package/dist/tuple.d.ts +16 -0
  256. package/dist/tuple.d.ts.map +1 -0
  257. package/dist/tuple.js +16 -0
  258. package/dist/tuple.js.map +1 -0
  259. package/dist/type-utils.d.ts +41 -0
  260. package/dist/type-utils.d.ts.map +1 -0
  261. package/dist/type-utils.js +10 -0
  262. package/dist/type-utils.js.map +1 -0
  263. package/dist/typed-ref.d.ts +50 -0
  264. package/dist/typed-ref.d.ts.map +1 -0
  265. package/dist/typed-ref.js +59 -0
  266. package/dist/typed-ref.js.map +1 -0
  267. package/dist/ui-quality.d.ts +50 -0
  268. package/dist/ui-quality.d.ts.map +1 -0
  269. package/dist/ui-quality.js +64 -0
  270. package/dist/ui-quality.js.map +1 -0
  271. package/dist/validation-error.d.ts +25 -0
  272. package/dist/validation-error.d.ts.map +1 -0
  273. package/dist/validation-error.js +32 -0
  274. package/dist/validation-error.js.map +1 -0
  275. package/dist/vector-clock.d.ts +46 -0
  276. package/dist/vector-clock.d.ts.map +1 -0
  277. package/dist/vector-clock.js +91 -0
  278. package/dist/vector-clock.js.map +1 -0
  279. package/dist/video.d.ts +62 -0
  280. package/dist/video.d.ts.map +1 -0
  281. package/dist/video.js +59 -0
  282. package/dist/video.js.map +1 -0
  283. package/dist/wasm-dispatch.d.ts +52 -0
  284. package/dist/wasm-dispatch.d.ts.map +1 -0
  285. package/dist/wasm-dispatch.js +204 -0
  286. package/dist/wasm-dispatch.js.map +1 -0
  287. package/dist/wasm-fallback.d.ts +19 -0
  288. package/dist/wasm-fallback.d.ts.map +1 -0
  289. package/dist/wasm-fallback.js +93 -0
  290. package/dist/wasm-fallback.js.map +1 -0
  291. package/dist/wire.d.ts +49 -0
  292. package/dist/wire.d.ts.map +1 -0
  293. package/dist/wire.js +201 -0
  294. package/dist/wire.js.map +1 -0
  295. package/dist/zap.d.ts +42 -0
  296. package/dist/zap.d.ts.map +1 -0
  297. package/dist/zap.js +172 -0
  298. package/dist/zap.js.map +1 -0
  299. package/package.json +71 -0
  300. package/src/addressed-digest.ts +48 -0
  301. package/src/animation.ts +103 -0
  302. package/src/assembly.ts +76 -0
  303. package/src/av-bridge.ts +161 -0
  304. package/src/av-renderer.ts +118 -0
  305. package/src/blend.ts +135 -0
  306. package/src/boundary.ts +363 -0
  307. package/src/brands.ts +86 -0
  308. package/src/caps.ts +100 -0
  309. package/src/capsule.ts +95 -0
  310. package/src/capsules/boundary-evaluate.ts +128 -0
  311. package/src/capsules/canonical-cbor.ts +60 -0
  312. package/src/capsules/token-buffer.ts +57 -0
  313. package/src/capture.ts +48 -0
  314. package/src/cbor.ts +199 -0
  315. package/src/cell.ts +130 -0
  316. package/src/codec.ts +39 -0
  317. package/src/component.ts +102 -0
  318. package/src/composable.ts +328 -0
  319. package/src/compositor-pool.ts +162 -0
  320. package/src/compositor.ts +387 -0
  321. package/src/config.ts +157 -0
  322. package/src/dag.ts +527 -0
  323. package/src/defaults.ts +60 -0
  324. package/src/derived.ts +164 -0
  325. package/src/diagnostics.ts +186 -0
  326. package/src/dirty.ts +101 -0
  327. package/src/easing.ts +334 -0
  328. package/src/ecs.ts +382 -0
  329. package/src/fnv.ts +31 -0
  330. package/src/frame-budget.ts +149 -0
  331. package/src/gen-frame.ts +229 -0
  332. package/src/harness/arbitrary-from-schema.ts +270 -0
  333. package/src/harness/cached-projection.ts +46 -0
  334. package/src/harness/index.ts +16 -0
  335. package/src/harness/policy-gate.ts +51 -0
  336. package/src/harness/pure-transform.ts +121 -0
  337. package/src/harness/receipted-mutation.ts +59 -0
  338. package/src/harness/scene-composition.ts +54 -0
  339. package/src/harness/site-adapter.ts +43 -0
  340. package/src/harness/state-machine.ts +49 -0
  341. package/src/hlc.ts +238 -0
  342. package/src/index.ts +274 -0
  343. package/src/interpolate.ts +37 -0
  344. package/src/live-cell.ts +199 -0
  345. package/src/op.ts +233 -0
  346. package/src/plan.ts +317 -0
  347. package/src/protocol.ts +49 -0
  348. package/src/quantizer-types.ts +29 -0
  349. package/src/receipt.ts +444 -0
  350. package/src/runtime-coordinator.ts +230 -0
  351. package/src/scheduler.ts +161 -0
  352. package/src/ship-capsule.ts +191 -0
  353. package/src/signal.ts +345 -0
  354. package/src/speculative.ts +186 -0
  355. package/src/store.ts +77 -0
  356. package/src/style.ts +249 -0
  357. package/src/testing.ts +14 -0
  358. package/src/theme.ts +153 -0
  359. package/src/timeline.ts +146 -0
  360. package/src/token-buffer.ts +151 -0
  361. package/src/token.ts +197 -0
  362. package/src/tuple.ts +19 -0
  363. package/src/type-utils.ts +48 -0
  364. package/src/typed-ref.ts +79 -0
  365. package/src/ui-quality.ts +105 -0
  366. package/src/validation-error.ts +34 -0
  367. package/src/vector-clock.ts +111 -0
  368. package/src/video.ts +106 -0
  369. package/src/wasm-dispatch.ts +300 -0
  370. package/src/wasm-fallback.ts +102 -0
  371. package/src/wire.ts +274 -0
  372. package/src/zap.ts +241 -0
@@ -0,0 +1,102 @@
1
+ /**
2
+ * ComponentDef -- adaptive component primitive for constraint-based rendering.
3
+ *
4
+ * A component binds a boundary, styles, and named slots into a single
5
+ * content-addressed unit. Content-addressed via FNV-1a.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { ContentAddress } from './brands.js';
11
+ import type { Boundary } from './boundary.js';
12
+ import type { Style } from './style.js';
13
+ import { CanonicalCbor } from './cbor.js';
14
+ import { fnv1aBytes } from './fnv.js';
15
+
16
+ /** Per-slot configuration on a component — whether the slot must be provided, plus optional description. */
17
+ export interface SlotConfig {
18
+ readonly required: boolean;
19
+ readonly description?: string;
20
+ }
21
+
22
+ interface ComponentDef<
23
+ B extends Boundary.Shape = Boundary.Shape,
24
+ SlotNames extends readonly string[] = readonly string[],
25
+ > {
26
+ readonly _tag: 'ComponentDef';
27
+ readonly _version: 1;
28
+ readonly id: ContentAddress;
29
+ readonly name: string;
30
+ readonly boundary?: B;
31
+ readonly styles: Style.Shape<B>;
32
+ readonly slots: { readonly [K in SlotNames[number]]: SlotConfig };
33
+ readonly defaultSlot?: SlotNames[number];
34
+ }
35
+
36
+ interface ComponentFactory {
37
+ make<B extends Boundary.Shape, const SN extends readonly [string, ...string[]]>(config: {
38
+ readonly name: string;
39
+ readonly boundary?: B;
40
+ readonly styles: Style.Shape<B>;
41
+ readonly slots: { readonly [K in SN[number]]: SlotConfig };
42
+ readonly defaultSlot?: SN[number];
43
+ }): ComponentDef<B, SN>;
44
+ }
45
+
46
+ function deterministicId<SlotNames extends readonly string[]>(
47
+ name: string,
48
+ boundaryId: string | undefined,
49
+ stylesId: string,
50
+ slots: { readonly [K in SlotNames[number]]: SlotConfig },
51
+ defaultSlot?: string,
52
+ ): ContentAddress {
53
+ return fnv1aBytes(
54
+ CanonicalCbor.encode({
55
+ _tag: 'ComponentDef',
56
+ _version: 1,
57
+ name,
58
+ boundaryId: boundaryId ?? null,
59
+ stylesId,
60
+ slots,
61
+ defaultSlot: defaultSlot ?? null,
62
+ }),
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Component — the content-addressed unit that binds a {@link Boundary}, a
68
+ * {@link Style}, and named slots into a single declaration compilers can
69
+ * target. The optional boundary gates style variants; the slots describe
70
+ * the consumer-facing API.
71
+ */
72
+ export const Component: ComponentFactory = {
73
+ make<B extends Boundary.Shape, const SN extends readonly [string, ...string[]]>(config: {
74
+ readonly name: string;
75
+ readonly boundary?: B;
76
+ readonly styles: Style.Shape<B>;
77
+ readonly slots: { readonly [K in SN[number]]: SlotConfig };
78
+ readonly defaultSlot?: SN[number];
79
+ }): ComponentDef<B, SN> {
80
+ const id = deterministicId<SN>(config.name, config.boundary?.id, config.styles.id, config.slots, config.defaultSlot);
81
+
82
+ const def: ComponentDef<B, SN> = {
83
+ _tag: 'ComponentDef',
84
+ _version: 1,
85
+ id,
86
+ name: config.name,
87
+ ...(config.boundary !== undefined ? { boundary: config.boundary } : {}),
88
+ styles: config.styles,
89
+ slots: config.slots,
90
+ ...(config.defaultSlot !== undefined ? { defaultSlot: config.defaultSlot } : {}),
91
+ };
92
+ return Object.freeze(def);
93
+ },
94
+ };
95
+
96
+ export declare namespace Component {
97
+ /** Structural shape of a component definition, parameterized by its boundary and slot names. */
98
+ export type Shape<
99
+ B extends Boundary.Shape = Boundary.Shape,
100
+ SN extends readonly string[] = readonly string[],
101
+ > = ComponentDef<B, SN>;
102
+ }
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Composable -- ECS Composition over Existing Primitives
3
+ *
4
+ * Universal composition API leveraging existing deterministic primitives.
5
+ * Zero boilerplate, type-safe, content-addressed entity composition.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { ContentAddress } from './brands.js';
11
+ import type { Token } from './token.js';
12
+ import type { Style } from './style.js';
13
+ import type { World } from './ecs.js';
14
+ import type { EntityId } from './ecs.js';
15
+ import { Token as TokenNS } from './token.js';
16
+ import { Style as StyleNS } from './style.js';
17
+ import { Boundary } from './boundary.js';
18
+ import { Part } from './ecs.js';
19
+ import { fnv1aBytes } from './fnv.js';
20
+ import { TypedRef } from './typed-ref.js';
21
+ import { Effect } from 'effect';
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Entity Composition Types
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /**
28
+ * Component map for a {@link ComposableEntity} — well-known slots for czap
29
+ * primitives plus arbitrary user-defined keys.
30
+ */
31
+ export interface EntityComponents {
32
+ readonly boundary?: Boundary.Shape;
33
+ readonly token?: Token.Shape;
34
+ readonly style?: Style.Shape;
35
+ readonly [key: string]: unknown;
36
+ }
37
+
38
+ /**
39
+ * Content-addressed entity: the identity is an FNV-1a hash over its components,
40
+ * so two entities with structurally equal components share the same `id`.
41
+ */
42
+ export interface ComposableEntity<T extends EntityComponents = EntityComponents> {
43
+ readonly id: ContentAddress;
44
+ readonly components: T;
45
+ readonly _tag: 'ComposableEntity';
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Composable Factory
50
+ // ---------------------------------------------------------------------------
51
+
52
+ interface ComposableFactory {
53
+ make<T extends EntityComponents>(components: T): ComposableEntity<T>;
54
+ compose<T extends EntityComponents>(entity1: ComposableEntity<T>, entity2: ComposableEntity<T>): ComposableEntity<T>;
55
+ merge<T extends EntityComponents>(...entities: ComposableEntity<T>[]): ComposableEntity<T>;
56
+ }
57
+
58
+ function makeEntityId(components: EntityComponents): ContentAddress {
59
+ const canonical = canonicalizeForAddress(components);
60
+ return fnv1aBytes(TypedRef.canonicalize(canonical));
61
+ }
62
+
63
+ function canonicalizeForAddress(value: unknown): unknown {
64
+ if (value === undefined) {
65
+ return undefined;
66
+ }
67
+
68
+ if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
69
+ return value;
70
+ }
71
+
72
+ if (Array.isArray(value)) {
73
+ return value.map((entry) => {
74
+ const canonical = canonicalizeForAddress(entry);
75
+ return canonical === undefined ? null : canonical;
76
+ });
77
+ }
78
+
79
+ if (typeof value === 'object') {
80
+ const entries = Object.entries(value)
81
+ .filter(([, entry]) => entry !== undefined)
82
+ .sort(([left], [right]) => left.localeCompare(right))
83
+ .map(([key, entry]) => [key, canonicalizeForAddress(entry)]);
84
+
85
+ return Object.fromEntries(entries);
86
+ }
87
+
88
+ return String(value);
89
+ }
90
+
91
+ function _make<T extends EntityComponents>(components: T): ComposableEntity<T> {
92
+ const id = makeEntityId(components);
93
+
94
+ return {
95
+ id,
96
+ components,
97
+ _tag: 'ComposableEntity',
98
+ };
99
+ }
100
+
101
+ function _compose<T extends EntityComponents>(
102
+ entity1: ComposableEntity<T>,
103
+ entity2: ComposableEntity<T>,
104
+ ): ComposableEntity<T> {
105
+ // Merge components with entity2 taking precedence
106
+ const merged = { ...entity1.components, ...entity2.components };
107
+ return _make(merged);
108
+ }
109
+
110
+ function _merge<T extends EntityComponents>(...entities: ComposableEntity<T>[]): ComposableEntity<T> {
111
+ if (entities.length === 0) {
112
+ throw new Error('Cannot merge zero entities');
113
+ }
114
+ const first = entities[0];
115
+ if (!first) {
116
+ throw new Error('First entity is undefined');
117
+ }
118
+ return entities.slice(1).reduce((acc, entity) => _compose(acc, entity), first);
119
+ }
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // ECS Integration
123
+ // ---------------------------------------------------------------------------
124
+
125
+ /**
126
+ * Convert a runtime `Map<string, unknown>` (from ECS query results) into a typed
127
+ * `Pick<Schema, K>`. The ECS query filters guarantee the required keys are present;
128
+ * this helper contains the one boundary cast where runtime shape joins the type lattice.
129
+ */
130
+ function entriesToPick<Schema extends EntityComponents, K extends keyof Schema>(
131
+ components: ReadonlyMap<string, unknown>,
132
+ ): Pick<Schema, K> {
133
+ return Object.fromEntries(components) as Pick<Schema, K>;
134
+ }
135
+
136
+ interface TypedComposableWorld<Schema extends EntityComponents = EntityComponents> {
137
+ spawn<T extends Schema>(components: T): Effect.Effect<ComposableEntity<T>>;
138
+ spawnWith<T extends Schema>(entity: ComposableEntity<T>): Effect.Effect<ComposableEntity<T>>;
139
+ query<K extends keyof Schema>(...componentTypes: K[]): Effect.Effect<readonly ComposableEntity<Pick<Schema, K>>[]>;
140
+ evaluate<T extends Schema>(
141
+ entity: ComposableEntity<T>,
142
+ input: Record<string, number>,
143
+ ): Effect.Effect<Record<string, string>>;
144
+ }
145
+
146
+ function makeComposableWorld<Schema extends EntityComponents = EntityComponents>(
147
+ world: World.Shape,
148
+ ): TypedComposableWorld<Schema> {
149
+ // Mapping from ContentAddress to ECS EntityId for query reconstruction
150
+ const addressToEntityId = new Map<ContentAddress, EntityId>();
151
+
152
+ return {
153
+ spawn<T extends Schema>(components: T): Effect.Effect<ComposableEntity<T>> {
154
+ return Effect.gen(function* () {
155
+ const entity = _make(components);
156
+ const ecsId = yield* world.spawn(components);
157
+ addressToEntityId.set(entity.id, ecsId);
158
+ return entity;
159
+ });
160
+ },
161
+
162
+ spawnWith<T extends Schema>(entity: ComposableEntity<T>): Effect.Effect<ComposableEntity<T>> {
163
+ return Effect.gen(function* () {
164
+ const ecsId = yield* world.spawn(entity.components);
165
+ addressToEntityId.set(entity.id, ecsId);
166
+ return entity;
167
+ });
168
+ },
169
+
170
+ query<K extends keyof Schema>(...componentTypes: K[]): Effect.Effect<readonly ComposableEntity<Pick<Schema, K>>[]> {
171
+ return Effect.gen(function* () {
172
+ const names = [...componentTypes].map((k) => String(k)).sort();
173
+ const entities = yield* world.query(...names);
174
+ return [...entities]
175
+ .sort((left, right) => left.id.localeCompare(right.id))
176
+ .map((entityShape) => {
177
+ // world.query guarantees entityShape.components contains at least the K keys
178
+ // that were queried for; convert the runtime Map<string, unknown> to the typed
179
+ // Pick<Schema, K> via a single contained cast (runtime shape is validated by
180
+ // the ECS query filter).
181
+ const components = entriesToPick<Schema, K>(entityShape.components);
182
+ return _make(components);
183
+ });
184
+ });
185
+ },
186
+
187
+ evaluate<T extends Schema>(
188
+ entity: ComposableEntity<T>,
189
+ input: Record<string, number>,
190
+ ): Effect.Effect<Record<string, string>> {
191
+ return Effect.gen(function* () {
192
+ const results: Record<string, string> = {};
193
+
194
+ // Evaluate boundary component: quantize continuous input to discrete state
195
+ let boundaryState: string | undefined;
196
+ if (entity.components.boundary) {
197
+ const boundary = entity.components.boundary;
198
+ const boundaryInput = input[boundary.input] ?? 0;
199
+ const state = Boundary.evaluate(boundary, boundaryInput);
200
+ results[boundary.input] = state;
201
+ boundaryState = state;
202
+ }
203
+
204
+ // Evaluate token component: resolve axis values or fall back
205
+ if (entity.components.token) {
206
+ const token = entity.components.token;
207
+ // Build axis values from input keys. Token.tap expects string axis values,
208
+ // so we convert matching numeric inputs to strings.
209
+ const axisValues: Record<string, string> = {};
210
+ for (const axis of token.axes) {
211
+ if (axis in input) {
212
+ axisValues[axis] = String(input[axis]);
213
+ }
214
+ }
215
+ // Use Token.tap for proper axis-key lookup with fallback
216
+ const resolved = TokenNS.tap(token, axisValues);
217
+ results[token.name] = String(resolved);
218
+ }
219
+
220
+ // Evaluate style component: resolve properties for the current boundary state
221
+ if (entity.components.style) {
222
+ const style = entity.components.style;
223
+ const resolvedProps = StyleNS.tap(style, boundaryState);
224
+ for (const [prop, val] of Object.entries(resolvedProps)) {
225
+ results[prop] = val;
226
+ }
227
+ }
228
+
229
+ return results;
230
+ });
231
+ },
232
+ };
233
+ }
234
+
235
+ // ---------------------------------------------------------------------------
236
+ // Dense Store Integration
237
+ // ---------------------------------------------------------------------------
238
+
239
+ interface ComposableDenseStore {
240
+ create(name: string, capacity: number): Effect.Effect<Part.Dense>;
241
+ store<T extends EntityComponents>(entity: ComposableEntity<T>, value: number): Effect.Effect<void>;
242
+ retrieve<T extends EntityComponents>(entity: ComposableEntity<T>): Effect.Effect<number | undefined>;
243
+ }
244
+
245
+ function makeComposableDenseStore(world: World.Shape): ComposableDenseStore {
246
+ // Maintain a mapping from ContentAddress to ECS EntityId for dense store ops
247
+ const addressToEntityId = new Map<ContentAddress, EntityId>();
248
+ let denseStore: Part.Dense | undefined;
249
+
250
+ return {
251
+ create(name: string, capacity: number): Effect.Effect<Part.Dense> {
252
+ return Effect.gen(function* () {
253
+ const store = Part.dense(name, capacity);
254
+ yield* world.addDenseStore(store);
255
+ denseStore = store;
256
+ return store;
257
+ });
258
+ },
259
+
260
+ store<T extends EntityComponents>(entity: ComposableEntity<T>, value: number): Effect.Effect<void> {
261
+ return Effect.gen(function* () {
262
+ if (!denseStore) {
263
+ throw new Error('No dense store created. Call create() first.');
264
+ }
265
+ // Ensure we have an ECS EntityId for this composable entity
266
+ let ecsId = addressToEntityId.get(entity.id);
267
+ if (!ecsId) {
268
+ // Spawn into the world to get an EntityId, then track mapping
269
+ ecsId = yield* world.spawn(entity.components);
270
+ addressToEntityId.set(entity.id, ecsId);
271
+ }
272
+ denseStore.set(ecsId, value);
273
+ });
274
+ },
275
+
276
+ retrieve<T extends EntityComponents>(entity: ComposableEntity<T>): Effect.Effect<number | undefined> {
277
+ return Effect.gen(function* () {
278
+ if (!denseStore) {
279
+ return undefined;
280
+ }
281
+ const ecsId = addressToEntityId.get(entity.id);
282
+ if (!ecsId) {
283
+ return undefined;
284
+ }
285
+ return denseStore.get(ecsId);
286
+ });
287
+ },
288
+ };
289
+ }
290
+
291
+ // ---------------------------------------------------------------------------
292
+ // Exports
293
+ // ---------------------------------------------------------------------------
294
+
295
+ /**
296
+ * Composable — content-addressed entity algebra over czap primitives.
297
+ *
298
+ * Build entities from a bag of components (boundaries, tokens, styles, …),
299
+ * merge them associatively via `Composable.compose` / `Composable.merge`, and
300
+ * rely on the content address to deduplicate structurally-equal entities.
301
+ */
302
+ export const Composable: ComposableFactory = {
303
+ /** Content-address a component bag into a {@link ComposableEntity}. */
304
+ make: _make,
305
+ /** Pairwise merge — right-biased; produces a new entity with a fresh content address. */
306
+ compose: _compose,
307
+ /** Variadic `Composable.compose`. Throws if called with zero entities. */
308
+ merge: _merge,
309
+ };
310
+
311
+ /**
312
+ * Bridge between a raw ECS {@link World} and typed {@link ComposableEntity}
313
+ * operations (`spawn`, `query`, `evaluate`) plus a thin dense-store integration.
314
+ */
315
+ export const ComposableWorld = {
316
+ /** Wrap a {@link World} with the typed composable-entity API. */
317
+ make: makeComposableWorld,
318
+ /** Build a dense-store bridge over a {@link World} for per-entity numeric data. */
319
+ dense: makeComposableDenseStore,
320
+ };
321
+
322
+ export declare namespace ComposableWorld {
323
+ /** Structural shape of the typed world returned by {@link ComposableWorld.make}. */
324
+ export type Shape<Schema extends EntityComponents = EntityComponents> = TypedComposableWorld<Schema>;
325
+ }
326
+
327
+ // Type exports -- keep legacy alias for backward compatibility
328
+ export type { TypedComposableWorld as ComposableWorldShape };
@@ -0,0 +1,162 @@
1
+ /**
2
+ * CompositorStatePool -- ring buffer of pre-allocated CompositeState objects.
3
+ *
4
+ * The compositor grabs one, writes into it, hands it to the renderer,
5
+ * which returns it after DOM application. Zero-allocation hot path.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { CompositeState } from './compositor.js';
11
+ import { COMPOSITOR_POOL_CAP } from './defaults.js';
12
+
13
+ // Zero-allocation ring buffer: pre-allocated fixed-size pool with index-wrapping reads/writes. No GC pressure on hot path.
14
+
15
+ interface CompositorStatePoolShape {
16
+ acquire(): CompositeState;
17
+ release(state: CompositeState): void;
18
+ readonly size: number;
19
+ readonly available: number;
20
+ }
21
+
22
+ /**
23
+ * Mutable views into a CompositeState's fields. `CompositeState` declares
24
+ * its fields as `readonly Record<...>`, but the compositor and the pool
25
+ * both need to mutate those records in place for the zero-allocation
26
+ * hot path. This helper centralises the single cast that strips
27
+ * `readonly` so callers can stay type-safe without re-casting at each
28
+ * write site.
29
+ */
30
+ export interface MutableCompositeStateViews {
31
+ readonly discrete: Record<string, string>;
32
+ readonly blend: Record<string, Record<string, number>>;
33
+ readonly css: Record<string, number | string>;
34
+ readonly glsl: Record<string, number>;
35
+ readonly aria: Record<string, string>;
36
+ }
37
+
38
+ type MutableCompositeState = {
39
+ -readonly [K in keyof CompositeState]: CompositeState[K] extends Readonly<infer U> ? U : CompositeState[K];
40
+ } & {
41
+ outputs: {
42
+ -readonly [K in keyof CompositeState['outputs']]: CompositeState['outputs'][K];
43
+ };
44
+ };
45
+
46
+ /**
47
+ * Expose the inner `Record<…>`s of a {@link CompositeState} as mutable views
48
+ * for the zero-allocation hot path. Single sanctioned site that strips
49
+ * `readonly` — do not cast at other call sites.
50
+ */
51
+ export function accessCompositeState(state: CompositeState): MutableCompositeStateViews {
52
+ const mutable = state as MutableCompositeState;
53
+ return {
54
+ discrete: mutable.discrete,
55
+ blend: mutable.blend,
56
+ css: mutable.outputs.css,
57
+ glsl: mutable.outputs.glsl,
58
+ aria: mutable.outputs.aria,
59
+ };
60
+ }
61
+
62
+ function createMutableState(): CompositeState {
63
+ return {
64
+ discrete: {},
65
+ blend: {},
66
+ outputs: { css: {}, glsl: {}, aria: {} },
67
+ };
68
+ }
69
+
70
+ function resetState(state: CompositeState): void {
71
+ // Clear all fields in-place (cheaper than allocation)
72
+ const { discrete, blend, css, glsl, aria } = accessCompositeState(state);
73
+ for (const k of Object.keys(discrete)) delete discrete[k];
74
+ for (const k of Object.keys(blend)) delete blend[k];
75
+ for (const k of Object.keys(css)) delete css[k];
76
+ for (const k of Object.keys(glsl)) delete glsl[k];
77
+ for (const k of Object.keys(aria)) delete aria[k];
78
+ }
79
+
80
+ /**
81
+ * Creates a ring-buffer pool of pre-allocated CompositeState objects.
82
+ * Acquire/release pattern avoids GC allocations on the hot render path.
83
+ * Default 8 slots -- enough for typical compositor with 4-6 quantizers + headroom.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * const pool = CompositorStatePool.make(4);
88
+ * const state = pool.acquire();
89
+ * state.discrete['theme'] = 'dark';
90
+ * state.outputs.css['--bg'] = '#000';
91
+ * pool.release(state); // resets and returns to pool
92
+ * pool.available; // 4
93
+ * ```
94
+ */
95
+ function _make(capacity = COMPOSITOR_POOL_CAP): CompositorStatePoolShape {
96
+ const buffer: CompositeState[] = [];
97
+ for (let i = 0; i < capacity; i++) {
98
+ buffer.push(createMutableState());
99
+ }
100
+
101
+ const free: boolean[] = new Array(capacity).fill(true);
102
+ let acquirePtr = 0;
103
+
104
+ return {
105
+ acquire(): CompositeState {
106
+ // Scan from current pointer for a free slot
107
+ for (let i = 0; i < capacity; i++) {
108
+ const idx = (acquirePtr + i) % capacity;
109
+ if (free[idx]) {
110
+ free[idx] = false;
111
+ acquirePtr = (idx + 1) % capacity;
112
+ return buffer[idx]!;
113
+ }
114
+ }
115
+ // All slots in use — allocate overflow (cold path)
116
+ const overflow = createMutableState();
117
+ buffer.push(overflow);
118
+ free.push(false);
119
+ return overflow;
120
+ },
121
+
122
+ release(state: CompositeState): void {
123
+ const idx = buffer.indexOf(state);
124
+ if (idx !== -1) {
125
+ resetState(state);
126
+ free[idx] = true;
127
+ }
128
+ },
129
+
130
+ get size(): number {
131
+ return buffer.length;
132
+ },
133
+
134
+ get available(): number {
135
+ let count = 0;
136
+ for (let i = 0; i < free.length; i++) {
137
+ if (free[i]) count++;
138
+ }
139
+ return count;
140
+ },
141
+ };
142
+ }
143
+
144
+ /**
145
+ * CompositorStatePool -- ring buffer of pre-allocated CompositeState objects.
146
+ * Zero-allocation hot path: acquire a state, write into it, render, then release.
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * const pool = CompositorStatePool.make(8);
151
+ * const state = pool.acquire();
152
+ * // Write compositor output into state.discrete, state.blend, state.outputs
153
+ * pool.release(state); // resets and returns to pool
154
+ * console.log(pool.size, pool.available); // 8, 8
155
+ * ```
156
+ */
157
+ export const CompositorStatePool = { make: _make };
158
+
159
+ export declare namespace CompositorStatePool {
160
+ /** Structural shape of a pool instance: `acquire`, `release`, `size`, `available`. */
161
+ export type Shape = CompositorStatePoolShape;
162
+ }