@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,146 @@
1
+ /**
2
+ * Timeline -- quantizer over time with play/pause/seek/scrub/reverse.
3
+ *
4
+ * A Timeline wraps a BoundaryDef and drives it from a time-based signal,
5
+ * producing discrete state transitions as the elapsed time crosses thresholds.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { Stream, Scope } from 'effect';
11
+ import { Effect, SubscriptionRef, Ref } from 'effect';
12
+ import type { Millis } from './brands.js';
13
+ import { Millis as mkMillis } from './brands.js';
14
+ import { Boundary } from './boundary.js';
15
+ import type { StateUnion } from './type-utils.js';
16
+ import type { Scheduler } from './scheduler.js';
17
+ import { Scheduler as SchedulerImpl } from './scheduler.js';
18
+
19
+ interface TimelineShape<B extends Boundary.Shape = Boundary.Shape> {
20
+ readonly boundary: B;
21
+ readonly state: Effect.Effect<StateUnion<B>>;
22
+ readonly progress: Effect.Effect<number>;
23
+ readonly elapsed: Effect.Effect<Millis>;
24
+ readonly changes: Stream.Stream<StateUnion<B>>;
25
+ play(): Effect.Effect<void>;
26
+ pause(): Effect.Effect<void>;
27
+ reverse(): Effect.Effect<void>;
28
+ seek(ms: Millis): Effect.Effect<void>;
29
+ scrub(progress: number): Effect.Effect<void>;
30
+ }
31
+
32
+ interface TimelineFactory {
33
+ from<B extends Boundary.Shape>(
34
+ boundary: B,
35
+ config?: { duration?: Millis; loop?: boolean; scheduler?: Scheduler.Shape },
36
+ ): Effect.Effect<TimelineShape<B>, never, Scope.Scope>;
37
+ }
38
+
39
+ /**
40
+ * Timeline — scheduler-driven advancement over a {@link Boundary}.
41
+ * Produces a scoped reactive timeline that seeks or plays between boundary
42
+ * states; pluggable clock via {@link Scheduler}.
43
+ */
44
+ export const Timeline: TimelineFactory = {
45
+ from<B extends Boundary.Shape>(
46
+ boundary: B,
47
+ config?: { duration?: Millis; loop?: boolean; scheduler?: Scheduler.Shape },
48
+ ): Effect.Effect<TimelineShape<B>, never, Scope.Scope> {
49
+ const duration =
50
+ config?.duration ??
51
+ (boundary.thresholds.length > 0 ? boundary.thresholds[boundary.thresholds.length - 1]! * 1.2 : 1000);
52
+ const loop = config?.loop ?? false;
53
+
54
+ return Effect.gen(function* () {
55
+ const elapsedRef = yield* SubscriptionRef.make(0);
56
+ const playingRef = yield* Ref.make(false);
57
+ const directionRef = yield* Ref.make<1 | -1>(1);
58
+ const initialState: StateUnion<B> = Boundary.evaluate(boundary, 0);
59
+ const stateRef = yield* SubscriptionRef.make<StateUnion<B>>(initialState);
60
+
61
+ const sched =
62
+ config?.scheduler ??
63
+ (typeof requestAnimationFrame !== 'undefined' ? SchedulerImpl.raf() : SchedulerImpl.noop());
64
+
65
+ let lastTime: number | null = null;
66
+ let playing = false;
67
+ let direction: 1 | -1 = 1;
68
+ let currentElapsed = 0;
69
+
70
+ const step = (now: number): void => {
71
+ if (lastTime !== null && playing) {
72
+ const dt = (now - lastTime) * direction;
73
+ let next = currentElapsed + dt;
74
+ if (loop) {
75
+ next = ((next % duration) + duration) % duration;
76
+ } else {
77
+ next = Math.max(0, Math.min(duration, next));
78
+ }
79
+ currentElapsed = next;
80
+ Effect.runSync(SubscriptionRef.set(elapsedRef, next));
81
+ const newState: StateUnion<B> = Boundary.evaluate(boundary, next);
82
+ const oldState = Effect.runSync(SubscriptionRef.get(stateRef));
83
+ if (newState !== oldState) {
84
+ Effect.runSync(SubscriptionRef.set(stateRef, newState));
85
+ }
86
+ }
87
+ lastTime = now;
88
+ schedId = sched.schedule(step);
89
+ };
90
+ let schedId = sched.schedule(step);
91
+ yield* Effect.addFinalizer(() => Effect.sync(() => sched.cancel(schedId)));
92
+
93
+ const timeline: TimelineShape<B> = {
94
+ boundary,
95
+ state: SubscriptionRef.get(stateRef),
96
+ progress: Effect.map(SubscriptionRef.get(elapsedRef), (e) => Math.max(0, Math.min(e / duration, 1))),
97
+ elapsed: Effect.map(SubscriptionRef.get(elapsedRef), (e) => mkMillis(e)),
98
+ changes: SubscriptionRef.changes(stateRef),
99
+ play: () =>
100
+ Effect.gen(function* () {
101
+ playing = true;
102
+ yield* Ref.set(playingRef, true);
103
+ }),
104
+ pause: () =>
105
+ Effect.gen(function* () {
106
+ playing = false;
107
+ yield* Ref.set(playingRef, false);
108
+ }),
109
+ reverse: () =>
110
+ Effect.gen(function* () {
111
+ direction = direction === 1 ? -1 : 1;
112
+ yield* Ref.update(directionRef, (d) => (d === 1 ? -1 : 1));
113
+ }),
114
+ seek: (ms: number) =>
115
+ Effect.gen(function* () {
116
+ const clamped = Math.max(0, Math.min(duration, ms));
117
+ currentElapsed = clamped;
118
+ yield* SubscriptionRef.set(elapsedRef, clamped);
119
+ const newState: StateUnion<B> = Boundary.evaluate(boundary, clamped);
120
+ const oldState = yield* SubscriptionRef.get(stateRef);
121
+ if (newState !== oldState) {
122
+ yield* SubscriptionRef.set(stateRef, newState);
123
+ }
124
+ }),
125
+ scrub: (progress: number) =>
126
+ Effect.gen(function* () {
127
+ const val = Math.max(0, Math.min(1, progress)) * duration;
128
+ currentElapsed = val;
129
+ yield* SubscriptionRef.set(elapsedRef, val);
130
+ const newState: StateUnion<B> = Boundary.evaluate(boundary, val);
131
+ const oldState = yield* SubscriptionRef.get(stateRef);
132
+ if (newState !== oldState) {
133
+ yield* SubscriptionRef.set(stateRef, newState);
134
+ }
135
+ }),
136
+ };
137
+
138
+ return timeline;
139
+ });
140
+ },
141
+ };
142
+
143
+ export declare namespace Timeline {
144
+ /** Structural shape of a timeline instance for a given {@link Boundary}. */
145
+ export type Shape<B extends Boundary.Shape = Boundary.Shape> = TimelineShape<B>;
146
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * TokenBuffer -- ring buffer that absorbs bursty LLM token arrival
3
+ * and emits at smooth cadence.
4
+ *
5
+ * Backed by pre-allocated array (zero-alloc push/drain).
6
+ * EMA (exponential moving average) for rate estimation.
7
+ * Stall detection: buffer empty + `gen < consume`.
8
+ *
9
+ * @module
10
+ */
11
+
12
+ interface TokenBufferShape<T = string> {
13
+ push(token: T): void;
14
+ drain(maxCount?: number): T[];
15
+ reset(): void;
16
+ readonly occupancy: number;
17
+ readonly generationRate: number;
18
+ readonly consumptionRate: number;
19
+ readonly isStalled: boolean;
20
+ readonly length: number;
21
+ readonly capacity: number;
22
+ }
23
+
24
+ interface TokenBufferConfig {
25
+ readonly capacity?: number;
26
+ readonly emaAlpha?: number;
27
+ }
28
+
29
+ function _make<T = string>(config?: TokenBufferConfig): TokenBufferShape<T> {
30
+ const capacity = config?.capacity ?? 256;
31
+ const alpha = config?.emaAlpha ?? 0.1;
32
+
33
+ // Ring buffer backing store
34
+ const buffer: (T | undefined)[] = new Array(capacity);
35
+ let head = 0; // next write position
36
+ let tail = 0; // next read position
37
+ let count = 0;
38
+
39
+ // Rate estimation
40
+ let genRate = 0; // tokens/sec EMA
41
+ let consumeRate = 0;
42
+ let lastPushTime = 0;
43
+ let lastDrainTime = 0;
44
+
45
+ function now(): number {
46
+ return typeof performance !== 'undefined' ? performance.now() : Date.now();
47
+ }
48
+
49
+ return {
50
+ push(token: T): void {
51
+ const t = now();
52
+ if (lastPushTime > 0) {
53
+ const dt = (t - lastPushTime) / 1000;
54
+ if (dt > 0) {
55
+ const instantRate = 1 / dt;
56
+ genRate = genRate === 0 ? instantRate : genRate * (1 - alpha) + instantRate * alpha;
57
+ }
58
+ }
59
+ lastPushTime = t;
60
+
61
+ if (count < capacity) {
62
+ buffer[head] = token;
63
+ head = (head + 1) % capacity;
64
+ count++;
65
+ } else {
66
+ // Overflow: overwrite oldest (drop tail)
67
+ buffer[head] = token;
68
+ head = (head + 1) % capacity;
69
+ tail = (tail + 1) % capacity;
70
+ }
71
+ },
72
+
73
+ drain(maxCount?: number): T[] {
74
+ const max = maxCount ?? count;
75
+ const drainSize = Math.min(max, count);
76
+ if (drainSize === 0) return [];
77
+
78
+ const t = now();
79
+ if (lastDrainTime > 0) {
80
+ const dt = (t - lastDrainTime) / 1000;
81
+ if (dt > 0) {
82
+ const instantRate = drainSize / dt;
83
+ consumeRate = consumeRate === 0 ? instantRate : consumeRate * (1 - alpha) + instantRate * alpha;
84
+ }
85
+ }
86
+ lastDrainTime = t;
87
+
88
+ const result: T[] = [];
89
+ for (let i = 0; i < drainSize; i++) {
90
+ result.push(buffer[tail]!);
91
+ buffer[tail] = undefined;
92
+ tail = (tail + 1) % capacity;
93
+ count--;
94
+ }
95
+
96
+ return result;
97
+ },
98
+
99
+ reset(): void {
100
+ head = 0;
101
+ tail = 0;
102
+ count = 0;
103
+ genRate = 0;
104
+ consumeRate = 0;
105
+ lastPushTime = 0;
106
+ lastDrainTime = 0;
107
+ buffer.fill(undefined);
108
+ },
109
+
110
+ get occupancy(): number {
111
+ return count / capacity;
112
+ },
113
+
114
+ get generationRate(): number {
115
+ return genRate;
116
+ },
117
+
118
+ get consumptionRate(): number {
119
+ return consumeRate;
120
+ },
121
+
122
+ get isStalled(): boolean {
123
+ return count === 0 && genRate > 0 && genRate < consumeRate;
124
+ },
125
+
126
+ get length(): number {
127
+ return count;
128
+ },
129
+
130
+ get capacity(): number {
131
+ return capacity;
132
+ },
133
+ };
134
+ }
135
+
136
+ /**
137
+ * TokenBuffer — zero-alloc ring buffer that absorbs bursty LLM token arrival
138
+ * and hands tokens out at a smooth cadence. Reports stall via `isStalled`
139
+ * and rate via an internal EMA.
140
+ */
141
+ export const TokenBuffer = {
142
+ /** Build a new buffer — pass capacity or reuse defaults. */
143
+ make: _make,
144
+ };
145
+
146
+ export declare namespace TokenBuffer {
147
+ /** Structural shape of a token buffer: `push`, `drain`, `reset`, stall/rate accessors. */
148
+ export type Shape<T = string> = TokenBufferShape<T>;
149
+ /** Configuration accepted by {@link TokenBuffer.make}. */
150
+ export type Config = TokenBufferConfig;
151
+ }
package/src/token.ts ADDED
@@ -0,0 +1,197 @@
1
+ /**
2
+ * TokenDef -- design token primitive for constraint-based adaptive rendering.
3
+ *
4
+ * A token defines a named design value that varies across axes (e.g. theme,
5
+ * density, contrast). Content-addressed via FNV-1a.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { ContentAddress } from './brands.js';
11
+ import { CanonicalCbor } from './cbor.js';
12
+ import { fnv1aBytes } from './fnv.js';
13
+ import { CzapValidationError } from './validation-error.js';
14
+
15
+ /** Design-system category of a {@link Token} — governs compilation strategy and CSS property prefix. */
16
+ export type TokenCategory = 'color' | 'spacing' | 'typography' | 'shadow' | 'radius' | 'animation' | 'effect';
17
+
18
+ interface TokenDef<N extends string = string, Axes extends readonly string[] = readonly string[]> {
19
+ readonly _tag: 'TokenDef';
20
+ readonly _version: 1;
21
+ readonly id: ContentAddress;
22
+ readonly name: N;
23
+ readonly category: TokenCategory;
24
+ readonly axes: Axes;
25
+ readonly values: Record<string, unknown>;
26
+ readonly fallback: unknown;
27
+ readonly cssProperty: `--${string}`;
28
+ }
29
+
30
+ interface TokenFactory {
31
+ make<N extends string, const A extends readonly [string, ...string[]]>(config: {
32
+ readonly name: N;
33
+ readonly category: TokenCategory;
34
+ readonly axes: A;
35
+ readonly values: Record<string, unknown>;
36
+ readonly fallback: unknown;
37
+ }): TokenDef<N, A>;
38
+ }
39
+
40
+ function deterministicId(
41
+ name: string,
42
+ category: string,
43
+ axes: readonly string[],
44
+ values: Record<string, unknown>,
45
+ fallback: unknown,
46
+ ): ContentAddress {
47
+ return fnv1aBytes(
48
+ CanonicalCbor.encode({
49
+ _tag: 'TokenDef',
50
+ _version: 1,
51
+ name,
52
+ category,
53
+ axes,
54
+ values,
55
+ fallback,
56
+ }),
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Resolve a token's value for the given axis values. Builds a sorted lookup key.
62
+ *
63
+ * Axes are sorted alphabetically and joined with ':' to form the lookup key.
64
+ * Falls back to the token's fallback value if no match is found.
65
+ *
66
+ * The optional type parameter `T` lets callers narrow the return value when
67
+ * they know the value shape; without it, the return is `unknown` (the
68
+ * underlying `TokenDef.values` is `Record<string, unknown>` because token
69
+ * values can be any JSON shape — colors as strings, spacing as numbers,
70
+ * shadow records as objects). Pass `Token.tap<string>(...)` for a color
71
+ * token, etc.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * const token = Token.make({
76
+ * name: 'primary', category: 'color',
77
+ * axes: ['theme'] as const,
78
+ * values: { 'light': '#000', 'dark': '#fff' },
79
+ * fallback: '#888',
80
+ * });
81
+ * const value = Token.tap<string>(token, { theme: 'dark' });
82
+ * // value === '#fff' (typed as string)
83
+ * ```
84
+ */
85
+ function _tap<T = unknown>(token: TokenDef, axisValues: Record<string, string>): T {
86
+ const key = [...token.axes]
87
+ .sort()
88
+ .map((axis) => axisValues[axis] ?? '')
89
+ .join(':');
90
+ return (token.values[key] ?? token.fallback) as T;
91
+ }
92
+
93
+ /**
94
+ * Generate a CSS var() reference for a token.
95
+ *
96
+ * Returns a `var(--czap-<name>)` string suitable for use in CSS properties.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const token = Token.make({
101
+ * name: 'primary', category: 'color',
102
+ * axes: ['theme'] as const,
103
+ * values: { 'light': '#000' },
104
+ * fallback: '#888',
105
+ * });
106
+ * const ref = Token.cssVar(token);
107
+ * // ref === 'var(--czap-primary)'
108
+ * ```
109
+ */
110
+ function _cssVar<N extends string>(token: TokenDef<N>): `var(--czap-${N})` {
111
+ return `var(--czap-${token.name})` as `var(--czap-${N})`;
112
+ }
113
+
114
+ /**
115
+ * Token namespace -- design token primitive for adaptive rendering.
116
+ *
117
+ * Create named design values that vary across axes (theme, density, contrast).
118
+ * Tokens are content-addressed and produce CSS custom property references.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * import { Token } from '@czap/core';
123
+ *
124
+ * const spacing = Token.make({
125
+ * name: 'gap', category: 'spacing',
126
+ * axes: ['density'] as const,
127
+ * values: { 'compact': '4px', 'comfortable': '8px' },
128
+ * fallback: '6px',
129
+ * });
130
+ * const resolved = Token.tap(spacing, { density: 'compact' });
131
+ * // resolved === '4px'
132
+ * const cssRef = Token.cssVar(spacing);
133
+ * // cssRef === 'var(--czap-gap)'
134
+ * ```
135
+ */
136
+ export const Token: TokenFactory & {
137
+ tap: typeof _tap;
138
+ cssVar: typeof _cssVar;
139
+ } = {
140
+ /**
141
+ * Create a new TokenDef from a configuration object.
142
+ *
143
+ * The token is content-addressed via FNV-1a hash of its name, category,
144
+ * axes, and values. The resulting object is frozen.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * const token = Token.make({
149
+ * name: 'bg', category: 'color',
150
+ * axes: ['theme', 'contrast'] as const,
151
+ * values: { 'light:normal': '#fff', 'dark:normal': '#111' },
152
+ * fallback: '#ccc',
153
+ * });
154
+ * // token._tag === 'TokenDef'
155
+ * // token.cssProperty === '--czap-bg'
156
+ * ```
157
+ */
158
+ make<N extends string, const A extends readonly [string, ...string[]]>(config: {
159
+ readonly name: N;
160
+ readonly category: TokenCategory;
161
+ readonly axes: A;
162
+ readonly values: Record<string, unknown>;
163
+ readonly fallback: unknown;
164
+ }): TokenDef<N, A> {
165
+ if (config.name === '') {
166
+ throw new CzapValidationError('Token.make', 'Token name must not be empty.');
167
+ }
168
+ const seen = new Set<string>();
169
+ for (const axis of config.axes) {
170
+ if (seen.has(axis)) {
171
+ throw new CzapValidationError('Token.make', `duplicate axis "${axis}". Each axis must have a unique name.`);
172
+ }
173
+ seen.add(axis);
174
+ }
175
+
176
+ const id = deterministicId(config.name, config.category, config.axes, config.values, config.fallback);
177
+
178
+ return Object.freeze({
179
+ _tag: 'TokenDef' as const,
180
+ _version: 1 as const,
181
+ id,
182
+ name: config.name,
183
+ category: config.category,
184
+ axes: config.axes,
185
+ values: config.values,
186
+ fallback: config.fallback,
187
+ cssProperty: `--czap-${config.name}` as const,
188
+ });
189
+ },
190
+ tap: _tap,
191
+ cssVar: _cssVar,
192
+ };
193
+
194
+ export declare namespace Token {
195
+ /** Structural shape of a token definition parameterized by its name `N` and axis tuple `Axes`. */
196
+ export type Shape<N extends string = string, Axes extends readonly string[] = readonly string[]> = TokenDef<N, Axes>;
197
+ }
package/src/tuple.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Map each element of a readonly tuple, preserving tuple arity and ordering.
3
+ *
4
+ * TypeScript's Array.prototype.map returns U[], erasing tuple structure.
5
+ * This helper reintroduces the mapped tuple type via one narrow cast,
6
+ * provably safe: the map is total over the input and the output element
7
+ * type is uniform.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const types = tupleMap([1, 'two', true] as const, (el) => typeof el);
12
+ * // types: readonly ['number', 'string', 'boolean']
13
+ * ```
14
+ */
15
+ export const tupleMap = <T extends readonly unknown[], U>(
16
+ tuple: T,
17
+ fn: (element: T[number], index: number) => U,
18
+ ): { readonly [K in keyof T]: U } =>
19
+ tuple.map(fn) as { readonly [K in keyof T]: U };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Type-level utilities for `@czap/core`.
3
+ *
4
+ * Mapped types, conditional helpers, and structural utilities
5
+ * used across boundary definitions and compositor outputs.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { Boundary } from './boundary.js';
11
+ import type { StateName, HLC } from './brands.js';
12
+ import type { Effect as EffectType } from 'effect';
13
+
14
+ /** Flatten branded intersections for clean IDE hints */
15
+ export type Prettify<T> = { [K in keyof T]: T[K] } & {};
16
+
17
+ /** Extract literal union of state names from a Boundary.Shape */
18
+ export type StateUnion<B extends Boundary.Shape> = B['states'][number];
19
+
20
+ /** Generate valid output shapes per state */
21
+ export type OutputsFor<B extends Boundary.Shape, T> = {
22
+ readonly [S in StateUnion<B>]: T;
23
+ };
24
+
25
+ /** Discriminated union of boundary crossings */
26
+ export type BoundaryCrossing<S extends string = string> = {
27
+ readonly from: StateName<S>;
28
+ readonly to: StateName<S>;
29
+ readonly timestamp: HLC;
30
+ readonly value: number;
31
+ };
32
+
33
+ /** Extract the value type from an Effect */
34
+ export type EffectValue<T> = T extends EffectType.Effect<infer A, unknown, unknown> ? A : never;
35
+
36
+ /** Extract the error type from an Effect */
37
+ export type EffectError<T> = T extends EffectType.Effect<unknown, infer E, unknown> ? E : never;
38
+
39
+ /** Require at least one key of T */
40
+ export type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> &
41
+ { [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys];
42
+
43
+ /** Deep readonly */
44
+ export type DeepReadonly<T> = T extends (infer U)[]
45
+ ? ReadonlyArray<DeepReadonly<U>>
46
+ : T extends Record<string, unknown>
47
+ ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
48
+ : T;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * TypedRef -- content-addressed payload references.
3
+ *
4
+ * Uses CBOR canonical encoding (cborg) and SHA-256 via crypto.subtle.
5
+ *
6
+ * @module
7
+ */
8
+
9
+ import { Effect } from 'effect';
10
+ import { encode } from 'cborg';
11
+
12
+ // Content-addressed identity: CBOR-canonical payload → FNV-1a hash. Same definition = same address.
13
+
14
+ interface TypedRefShape {
15
+ readonly schema_hash: string;
16
+ readonly content_hash: string;
17
+ }
18
+
19
+ /** Canonicalize value to CBOR bytes using canonical (deterministic) encoding. */
20
+ export const canonicalize = (value: unknown): Uint8Array => encode(value);
21
+
22
+ /**
23
+ * Hash data using SHA-256. Returns "sha256:hex" formatted hash.
24
+ *
25
+ * The `bytes as BufferSource` assertion is the single sanctioned cast in this
26
+ * file. `Uint8Array` is structurally a BufferSource, but TS's DOM lib types
27
+ * `bytes.buffer` as potentially-SharedArrayBuffer, preventing direct assignment.
28
+ * Safe: cborg encodes into fresh ArrayBuffer and TextEncoder.encode returns
29
+ * ArrayBuffer-backed views. No data copy.
30
+ *
31
+ * Hash-primitive failures are unrecoverable in practice (crypto.subtle errors
32
+ * are environment-level, not user-recoverable), so we `Effect.orDie` to fold
33
+ * the Error channel into a defect and keep the `Effect<string>` signature that
34
+ * the content-addressing pipeline relies on.
35
+ */
36
+ export const hash = (data: string | Uint8Array): Effect.Effect<string> =>
37
+ Effect.tryPromise({
38
+ try: async () => {
39
+ const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
40
+ const buffer = await crypto.subtle.digest('SHA-256', bytes as BufferSource);
41
+ const hashHex = Array.from(new Uint8Array(buffer))
42
+ .map((b) => b.toString(16).padStart(2, '0'))
43
+ .join('');
44
+ return `sha256:${hashHex}`;
45
+ },
46
+ catch: (error) => new Error(`SHA-256 hash failed: ${error instanceof Error ? error.message : String(error)}`),
47
+ }).pipe(Effect.orDie);
48
+
49
+ /** Create a TypedRef from schema hash and payload. */
50
+ const _create = (schemaHash: string, payload: unknown): Effect.Effect<TypedRefShape> =>
51
+ Effect.gen(function* () {
52
+ const contentHash = yield* hash(canonicalize(payload));
53
+ return { schema_hash: schemaHash, content_hash: contentHash };
54
+ });
55
+
56
+ /** Compare two TypedRefs for structural equality. */
57
+ const _equals = (a: TypedRefShape, b: TypedRefShape): boolean =>
58
+ a.schema_hash === b.schema_hash && a.content_hash === b.content_hash;
59
+
60
+ /**
61
+ * TypedRef — schema-plus-content-hash pointer used by the receipt pipeline.
62
+ * Lets a receipt reference a payload by its content address without embedding
63
+ * the payload itself, while still binding it to a schema identity.
64
+ */
65
+ export const TypedRef = {
66
+ /** Build a {@link TypedRef} from a schema hash and an arbitrary payload. */
67
+ create: _create,
68
+ /** Structural equality over schema + content hashes. */
69
+ equals: _equals,
70
+ /** Canonical-CBOR-ish serialization used to compute the content hash. */
71
+ canonicalize,
72
+ /** Hash a canonicalized payload to its content address. */
73
+ hash,
74
+ };
75
+
76
+ export declare namespace TypedRef {
77
+ /** Structural shape of a typed reference: schema hash + content hash. */
78
+ export type Shape = TypedRefShape;
79
+ }