@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.
- package/LICENSE +21 -0
- package/README.md +19 -0
- package/dist/addressed-digest.d.ts +15 -0
- package/dist/addressed-digest.d.ts.map +1 -0
- package/dist/addressed-digest.js +35 -0
- package/dist/addressed-digest.js.map +1 -0
- package/dist/animation.d.ts +46 -0
- package/dist/animation.d.ts.map +1 -0
- package/dist/animation.js +70 -0
- package/dist/animation.js.map +1 -0
- package/dist/assembly.d.ts +25 -0
- package/dist/assembly.d.ts.map +1 -0
- package/dist/assembly.js +58 -0
- package/dist/assembly.js.map +1 -0
- package/dist/av-bridge.d.ts +74 -0
- package/dist/av-bridge.d.ts.map +1 -0
- package/dist/av-bridge.js +107 -0
- package/dist/av-bridge.js.map +1 -0
- package/dist/av-renderer.d.ts +56 -0
- package/dist/av-renderer.d.ts.map +1 -0
- package/dist/av-renderer.js +65 -0
- package/dist/av-renderer.js.map +1 -0
- package/dist/blend.d.ts +61 -0
- package/dist/blend.d.ts.map +1 -0
- package/dist/blend.js +100 -0
- package/dist/blend.js.map +1 -0
- package/dist/boundary.d.ts +154 -0
- package/dist/boundary.d.ts.map +1 -0
- package/dist/boundary.js +269 -0
- package/dist/boundary.js.map +1 -0
- package/dist/brands.d.ts +63 -0
- package/dist/brands.d.ts.map +1 -0
- package/dist/brands.js +31 -0
- package/dist/brands.js.map +1 -0
- package/dist/caps.d.ts +49 -0
- package/dist/caps.d.ts.map +1 -0
- package/dist/caps.js +73 -0
- package/dist/caps.js.map +1 -0
- package/dist/capsule.d.ts +77 -0
- package/dist/capsule.d.ts.map +1 -0
- package/dist/capsule.js +18 -0
- package/dist/capsule.js.map +1 -0
- package/dist/capsules/boundary-evaluate.d.ts +28 -0
- package/dist/capsules/boundary-evaluate.d.ts.map +1 -0
- package/dist/capsules/boundary-evaluate.js +117 -0
- package/dist/capsules/boundary-evaluate.js.map +1 -0
- package/dist/capsules/canonical-cbor.d.ts +13 -0
- package/dist/capsules/canonical-cbor.d.ts.map +1 -0
- package/dist/capsules/canonical-cbor.js +60 -0
- package/dist/capsules/canonical-cbor.js.map +1 -0
- package/dist/capsules/token-buffer.d.ts +24 -0
- package/dist/capsules/token-buffer.d.ts.map +1 -0
- package/dist/capsules/token-buffer.js +53 -0
- package/dist/capsules/token-buffer.js.map +1 -0
- package/dist/capture.d.ts +40 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +10 -0
- package/dist/capture.js.map +1 -0
- package/dist/cbor.d.ts +33 -0
- package/dist/cbor.d.ts.map +1 -0
- package/dist/cbor.js +179 -0
- package/dist/cbor.js.map +1 -0
- package/dist/cell.d.ts +53 -0
- package/dist/cell.d.ts.map +1 -0
- package/dist/cell.js +83 -0
- package/dist/cell.js.map +1 -0
- package/dist/codec.d.ts +30 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +25 -0
- package/dist/codec.js.map +1 -0
- package/dist/component.d.ts +52 -0
- package/dist/component.d.ts.map +1 -0
- package/dist/component.js +44 -0
- package/dist/component.js.map +1 -0
- package/dist/composable.d.ts +76 -0
- package/dist/composable.d.ts.map +1 -0
- package/dist/composable.js +221 -0
- package/dist/composable.js.map +1 -0
- package/dist/compositor-pool.d.ts +74 -0
- package/dist/compositor-pool.d.ts.map +1 -0
- package/dist/compositor-pool.js +119 -0
- package/dist/compositor-pool.js.map +1 -0
- package/dist/compositor.d.ts +90 -0
- package/dist/compositor.d.ts.map +1 -0
- package/dist/compositor.js +278 -0
- package/dist/compositor.js.map +1 -0
- package/dist/config.d.ts +72 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +97 -0
- package/dist/config.js.map +1 -0
- package/dist/dag.d.ts +251 -0
- package/dist/dag.d.ts.map +1 -0
- package/dist/dag.js +450 -0
- package/dist/dag.js.map +1 -0
- package/dist/defaults.d.ts +45 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +45 -0
- package/dist/defaults.js.map +1 -0
- package/dist/derived.d.ts +34 -0
- package/dist/derived.d.ts.map +1 -0
- package/dist/derived.js +101 -0
- package/dist/derived.js.map +1 -0
- package/dist/diagnostics.d.ts +77 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +122 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/dirty.d.ts +55 -0
- package/dist/dirty.d.ts.map +1 -0
- package/dist/dirty.js +80 -0
- package/dist/dirty.js.map +1 -0
- package/dist/easing.d.ts +55 -0
- package/dist/easing.d.ts.map +1 -0
- package/dist/easing.js +291 -0
- package/dist/easing.js.map +1 -0
- package/dist/ecs.d.ts +105 -0
- package/dist/ecs.d.ts.map +1 -0
- package/dist/ecs.js +245 -0
- package/dist/ecs.js.map +1 -0
- package/dist/fnv.d.ts +14 -0
- package/dist/fnv.d.ts.map +1 -0
- package/dist/fnv.js +28 -0
- package/dist/fnv.js.map +1 -0
- package/dist/frame-budget.d.ts +73 -0
- package/dist/frame-budget.d.ts.map +1 -0
- package/dist/frame-budget.js +114 -0
- package/dist/frame-budget.js.map +1 -0
- package/dist/gen-frame.d.ts +102 -0
- package/dist/gen-frame.d.ts.map +1 -0
- package/dist/gen-frame.js +121 -0
- package/dist/gen-frame.js.map +1 -0
- package/dist/harness/arbitrary-from-schema.d.ts +28 -0
- package/dist/harness/arbitrary-from-schema.d.ts.map +1 -0
- package/dist/harness/arbitrary-from-schema.js +262 -0
- package/dist/harness/arbitrary-from-schema.js.map +1 -0
- package/dist/harness/cached-projection.d.ts +19 -0
- package/dist/harness/cached-projection.d.ts.map +1 -0
- package/dist/harness/cached-projection.js +39 -0
- package/dist/harness/cached-projection.js.map +1 -0
- package/dist/harness/index.d.ts +16 -0
- package/dist/harness/index.d.ts.map +1 -0
- package/dist/harness/index.js +15 -0
- package/dist/harness/index.js.map +1 -0
- package/dist/harness/policy-gate.d.ts +18 -0
- package/dist/harness/policy-gate.d.ts.map +1 -0
- package/dist/harness/policy-gate.js +46 -0
- package/dist/harness/policy-gate.js.map +1 -0
- package/dist/harness/pure-transform.d.ts +42 -0
- package/dist/harness/pure-transform.d.ts.map +1 -0
- package/dist/harness/pure-transform.js +76 -0
- package/dist/harness/pure-transform.js.map +1 -0
- package/dist/harness/receipted-mutation.d.ts +23 -0
- package/dist/harness/receipted-mutation.d.ts.map +1 -0
- package/dist/harness/receipted-mutation.js +52 -0
- package/dist/harness/receipted-mutation.js.map +1 -0
- package/dist/harness/scene-composition.d.ts +19 -0
- package/dist/harness/scene-composition.d.ts.map +1 -0
- package/dist/harness/scene-composition.js +47 -0
- package/dist/harness/scene-composition.js.map +1 -0
- package/dist/harness/site-adapter.d.ts +18 -0
- package/dist/harness/site-adapter.d.ts.map +1 -0
- package/dist/harness/site-adapter.js +38 -0
- package/dist/harness/site-adapter.js.map +1 -0
- package/dist/harness/state-machine.d.ts +19 -0
- package/dist/harness/state-machine.d.ts.map +1 -0
- package/dist/harness/state-machine.js +44 -0
- package/dist/harness/state-machine.js.map +1 -0
- package/dist/hlc.d.ts +99 -0
- package/dist/hlc.d.ts.map +1 -0
- package/dist/hlc.js +219 -0
- package/dist/hlc.js.map +1 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +137 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolate.d.ts +14 -0
- package/dist/interpolate.d.ts.map +1 -0
- package/dist/interpolate.js +31 -0
- package/dist/interpolate.js.map +1 -0
- package/dist/live-cell.d.ts +46 -0
- package/dist/live-cell.d.ts.map +1 -0
- package/dist/live-cell.js +154 -0
- package/dist/live-cell.js.map +1 -0
- package/dist/op.d.ts +58 -0
- package/dist/op.d.ts.map +1 -0
- package/dist/op.js +171 -0
- package/dist/op.js.map +1 -0
- package/dist/plan.d.ts +195 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +211 -0
- package/dist/plan.js.map +1 -0
- package/dist/protocol.d.ts +33 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +10 -0
- package/dist/protocol.js.map +1 -0
- package/dist/quantizer-types.d.ts +28 -0
- package/dist/quantizer-types.d.ts.map +1 -0
- package/dist/quantizer-types.js +9 -0
- package/dist/quantizer-types.js.map +1 -0
- package/dist/receipt.d.ts +294 -0
- package/dist/receipt.d.ts.map +1 -0
- package/dist/receipt.js +352 -0
- package/dist/receipt.js.map +1 -0
- package/dist/runtime-coordinator.d.ts +75 -0
- package/dist/runtime-coordinator.d.ts.map +1 -0
- package/dist/runtime-coordinator.js +149 -0
- package/dist/runtime-coordinator.js.map +1 -0
- package/dist/scheduler.d.ts +58 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +109 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/ship-capsule.d.ts +54 -0
- package/dist/ship-capsule.d.ts.map +1 -0
- package/dist/ship-capsule.js +142 -0
- package/dist/ship-capsule.js.map +1 -0
- package/dist/ship-manifest.d.ts +45 -0
- package/dist/ship-manifest.d.ts.map +1 -0
- package/dist/ship-manifest.js +175 -0
- package/dist/ship-manifest.js.map +1 -0
- package/dist/signal.d.ts +149 -0
- package/dist/signal.d.ts.map +1 -0
- package/dist/signal.js +277 -0
- package/dist/signal.js.map +1 -0
- package/dist/speculative.d.ts +67 -0
- package/dist/speculative.d.ts.map +1 -0
- package/dist/speculative.js +139 -0
- package/dist/speculative.js.map +1 -0
- package/dist/store.d.ts +39 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +42 -0
- package/dist/store.js.map +1 -0
- package/dist/style.d.ts +119 -0
- package/dist/style.d.ts.map +1 -0
- package/dist/style.js +168 -0
- package/dist/style.js.map +1 -0
- package/dist/testing.d.ts +14 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +14 -0
- package/dist/testing.js.map +1 -0
- package/dist/theme.d.ts +78 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +109 -0
- package/dist/theme.js.map +1 -0
- package/dist/timeline.d.ts +45 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +101 -0
- package/dist/timeline.js.map +1 -0
- package/dist/token-buffer.d.ts +43 -0
- package/dist/token-buffer.d.ts.map +1 -0
- package/dist/token-buffer.js +112 -0
- package/dist/token-buffer.js.map +1 -0
- package/dist/token.d.ts +107 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +143 -0
- package/dist/token.js.map +1 -0
- package/dist/tuple.d.ts +16 -0
- package/dist/tuple.d.ts.map +1 -0
- package/dist/tuple.js +16 -0
- package/dist/tuple.js.map +1 -0
- package/dist/type-utils.d.ts +41 -0
- package/dist/type-utils.d.ts.map +1 -0
- package/dist/type-utils.js +10 -0
- package/dist/type-utils.js.map +1 -0
- package/dist/typed-ref.d.ts +50 -0
- package/dist/typed-ref.d.ts.map +1 -0
- package/dist/typed-ref.js +59 -0
- package/dist/typed-ref.js.map +1 -0
- package/dist/ui-quality.d.ts +50 -0
- package/dist/ui-quality.d.ts.map +1 -0
- package/dist/ui-quality.js +64 -0
- package/dist/ui-quality.js.map +1 -0
- package/dist/validation-error.d.ts +25 -0
- package/dist/validation-error.d.ts.map +1 -0
- package/dist/validation-error.js +32 -0
- package/dist/validation-error.js.map +1 -0
- package/dist/vector-clock.d.ts +46 -0
- package/dist/vector-clock.d.ts.map +1 -0
- package/dist/vector-clock.js +91 -0
- package/dist/vector-clock.js.map +1 -0
- package/dist/video.d.ts +62 -0
- package/dist/video.d.ts.map +1 -0
- package/dist/video.js +59 -0
- package/dist/video.js.map +1 -0
- package/dist/wasm-dispatch.d.ts +52 -0
- package/dist/wasm-dispatch.d.ts.map +1 -0
- package/dist/wasm-dispatch.js +204 -0
- package/dist/wasm-dispatch.js.map +1 -0
- package/dist/wasm-fallback.d.ts +19 -0
- package/dist/wasm-fallback.d.ts.map +1 -0
- package/dist/wasm-fallback.js +93 -0
- package/dist/wasm-fallback.js.map +1 -0
- package/dist/wire.d.ts +49 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +201 -0
- package/dist/wire.js.map +1 -0
- package/dist/zap.d.ts +42 -0
- package/dist/zap.d.ts.map +1 -0
- package/dist/zap.js +172 -0
- package/dist/zap.js.map +1 -0
- package/package.json +71 -0
- package/src/addressed-digest.ts +48 -0
- package/src/animation.ts +103 -0
- package/src/assembly.ts +76 -0
- package/src/av-bridge.ts +161 -0
- package/src/av-renderer.ts +118 -0
- package/src/blend.ts +135 -0
- package/src/boundary.ts +363 -0
- package/src/brands.ts +86 -0
- package/src/caps.ts +100 -0
- package/src/capsule.ts +95 -0
- package/src/capsules/boundary-evaluate.ts +128 -0
- package/src/capsules/canonical-cbor.ts +60 -0
- package/src/capsules/token-buffer.ts +57 -0
- package/src/capture.ts +48 -0
- package/src/cbor.ts +199 -0
- package/src/cell.ts +130 -0
- package/src/codec.ts +39 -0
- package/src/component.ts +102 -0
- package/src/composable.ts +328 -0
- package/src/compositor-pool.ts +162 -0
- package/src/compositor.ts +387 -0
- package/src/config.ts +157 -0
- package/src/dag.ts +527 -0
- package/src/defaults.ts +60 -0
- package/src/derived.ts +164 -0
- package/src/diagnostics.ts +186 -0
- package/src/dirty.ts +101 -0
- package/src/easing.ts +334 -0
- package/src/ecs.ts +382 -0
- package/src/fnv.ts +31 -0
- package/src/frame-budget.ts +149 -0
- package/src/gen-frame.ts +229 -0
- package/src/harness/arbitrary-from-schema.ts +270 -0
- package/src/harness/cached-projection.ts +46 -0
- package/src/harness/index.ts +16 -0
- package/src/harness/policy-gate.ts +51 -0
- package/src/harness/pure-transform.ts +121 -0
- package/src/harness/receipted-mutation.ts +59 -0
- package/src/harness/scene-composition.ts +54 -0
- package/src/harness/site-adapter.ts +43 -0
- package/src/harness/state-machine.ts +49 -0
- package/src/hlc.ts +238 -0
- package/src/index.ts +274 -0
- package/src/interpolate.ts +37 -0
- package/src/live-cell.ts +199 -0
- package/src/op.ts +233 -0
- package/src/plan.ts +317 -0
- package/src/protocol.ts +49 -0
- package/src/quantizer-types.ts +29 -0
- package/src/receipt.ts +444 -0
- package/src/runtime-coordinator.ts +230 -0
- package/src/scheduler.ts +161 -0
- package/src/ship-capsule.ts +191 -0
- package/src/signal.ts +345 -0
- package/src/speculative.ts +186 -0
- package/src/store.ts +77 -0
- package/src/style.ts +249 -0
- package/src/testing.ts +14 -0
- package/src/theme.ts +153 -0
- package/src/timeline.ts +146 -0
- package/src/token-buffer.ts +151 -0
- package/src/token.ts +197 -0
- package/src/tuple.ts +19 -0
- package/src/type-utils.ts +48 -0
- package/src/typed-ref.ts +79 -0
- package/src/ui-quality.ts +105 -0
- package/src/validation-error.ts +34 -0
- package/src/vector-clock.ts +111 -0
- package/src/video.ts +106 -0
- package/src/wasm-dispatch.ts +300 -0
- package/src/wasm-fallback.ts +102 -0
- package/src/wire.ts +274 -0
- package/src/zap.ts +241 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure TypeScript fallback kernels for WASM compute functions.
|
|
3
|
+
*
|
|
4
|
+
* These produce bit-identical results (within float precision) to the
|
|
5
|
+
* Rust WASM kernels. When WASM is unavailable, these are used automatically.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { WASMKernels } from './wasm-dispatch.js';
|
|
11
|
+
|
|
12
|
+
function springCurve(stiffness: number, damping: number, mass: number, samples: number): Float32Array {
|
|
13
|
+
const m = mass <= 0 ? 1 : mass;
|
|
14
|
+
const count = Math.min(Math.max(0, samples | 0), 255);
|
|
15
|
+
const omega = Math.sqrt(stiffness / m);
|
|
16
|
+
const zeta = damping / (2 * Math.sqrt(stiffness * m));
|
|
17
|
+
const out = new Float32Array(count + 1);
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i <= count; i++) {
|
|
20
|
+
const t = i / count;
|
|
21
|
+
if (t <= 0) {
|
|
22
|
+
out[i] = 0;
|
|
23
|
+
} else if (t >= 1) {
|
|
24
|
+
out[i] = 1;
|
|
25
|
+
} else if (zeta < 1) {
|
|
26
|
+
const omegaD = omega * Math.sqrt(1 - zeta * zeta);
|
|
27
|
+
out[i] =
|
|
28
|
+
1 - Math.exp(-zeta * omega * t) * (Math.cos(omegaD * t) + ((zeta * omega) / omegaD) * Math.sin(omegaD * t));
|
|
29
|
+
} else if (zeta === 1) {
|
|
30
|
+
out[i] = 1 - (1 + omega * t) * Math.exp(-omega * t);
|
|
31
|
+
} else {
|
|
32
|
+
const s = Math.sqrt(zeta * zeta - 1);
|
|
33
|
+
const r1 = -omega * (zeta + s);
|
|
34
|
+
const r2 = -omega * (zeta - s);
|
|
35
|
+
const c1 = r2 / (r2 - r1);
|
|
36
|
+
const c2 = -r1 / (r2 - r1);
|
|
37
|
+
out[i] = 1 - (c1 * Math.exp(r1 * t) + c2 * Math.exp(r2 * t));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function batchBoundaryEval(thresholds: Float64Array, values: Float64Array): Uint32Array {
|
|
45
|
+
const tLen = thresholds.length;
|
|
46
|
+
const vLen = values.length;
|
|
47
|
+
const out = new Uint32Array(vLen);
|
|
48
|
+
|
|
49
|
+
for (let vi = 0; vi < vLen; vi++) {
|
|
50
|
+
const value = values[vi]!;
|
|
51
|
+
let stateIdx = 0;
|
|
52
|
+
|
|
53
|
+
for (let ti = tLen - 1; ti >= 0; ti--) {
|
|
54
|
+
if (value >= thresholds[ti]!) {
|
|
55
|
+
stateIdx = ti;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
out[vi] = stateIdx;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function blendNormalize(weights: Float32Array): Float32Array {
|
|
67
|
+
const len = weights.length;
|
|
68
|
+
if (len === 0) return weights;
|
|
69
|
+
|
|
70
|
+
let total = 0;
|
|
71
|
+
for (let i = 0; i < len; i++) {
|
|
72
|
+
let w = weights[i]!;
|
|
73
|
+
if (w < 0) {
|
|
74
|
+
w = 0;
|
|
75
|
+
weights[i] = 0;
|
|
76
|
+
}
|
|
77
|
+
total += w;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (total > 0) {
|
|
81
|
+
const inv = 1 / total;
|
|
82
|
+
for (let i = 0; i < len; i++) {
|
|
83
|
+
weights[i] = weights[i]! * inv;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return weights;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Pure-JS implementation of the {@link WASMKernels} contract.
|
|
92
|
+
*
|
|
93
|
+
* Selected automatically by {@link WASMDispatch} when the Rust compute crate
|
|
94
|
+
* cannot be instantiated (e.g. missing `WebAssembly`, CSP restrictions, or
|
|
95
|
+
* startup failure). Produces results bit-identical to the WASM build within
|
|
96
|
+
* IEEE-754 precision limits.
|
|
97
|
+
*/
|
|
98
|
+
export const fallbackKernels: WASMKernels = {
|
|
99
|
+
springCurve,
|
|
100
|
+
batchBoundaryEval,
|
|
101
|
+
blendNormalize,
|
|
102
|
+
};
|
package/src/wire.ts
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Wire<T, E>` — fluent stream wrapper.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Effect, Stream, Queue } from 'effect';
|
|
8
|
+
import type { Millis } from './brands.js';
|
|
9
|
+
|
|
10
|
+
// Fluent composition: chainable transform pipeline for Effect streams, enabling .map/.filter/.merge without breaking the Effect fiber model.
|
|
11
|
+
|
|
12
|
+
interface WireShape<T, E = never> {
|
|
13
|
+
readonly _tag: 'Wire';
|
|
14
|
+
readonly stream: Stream.Stream<T, E>;
|
|
15
|
+
map<B>(f: (a: T) => B): WireShape<B, E>;
|
|
16
|
+
filter(f: (a: T) => boolean): WireShape<T, E>;
|
|
17
|
+
take(n: number): WireShape<T, E>;
|
|
18
|
+
takeUntil(predicate: (a: T) => boolean): WireShape<T, E>;
|
|
19
|
+
debounce(ms: Millis): WireShape<T, E>;
|
|
20
|
+
throttle(ms: Millis): WireShape<T, E>;
|
|
21
|
+
scan<B>(initial: B, f: (acc: B, value: T) => B): WireShape<B, E>;
|
|
22
|
+
flatMap<B, E2>(f: (a: T) => WireShape<B, E2>): WireShape<B, E | E2>;
|
|
23
|
+
merge<B, E2>(other: WireShape<B, E2>): WireShape<T | B, E | E2>;
|
|
24
|
+
run(): Effect.Effect<void, E>;
|
|
25
|
+
runCollect(): Effect.Effect<T[], E>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function wrap<T, E = never>(stream: Stream.Stream<T, E>): WireShape<T, E> {
|
|
29
|
+
const wireStream: WireShape<T, E> = {
|
|
30
|
+
_tag: 'Wire' as const,
|
|
31
|
+
stream,
|
|
32
|
+
|
|
33
|
+
map: <B>(f: (a: T) => B) => wrap<B, E>(Stream.map(stream, f)),
|
|
34
|
+
|
|
35
|
+
filter: (f: (a: T) => boolean) => wrap<T, E>(Stream.filter(stream, f)),
|
|
36
|
+
|
|
37
|
+
take: (n: number) => wrap<T, E>(Stream.take(stream, n)),
|
|
38
|
+
|
|
39
|
+
takeUntil: (predicate: (a: T) => boolean) => wrap<T, E>(Stream.takeUntil(stream, predicate)),
|
|
40
|
+
|
|
41
|
+
debounce: (ms: Millis) => wrap<T, E>(Stream.debounce(stream, ms)),
|
|
42
|
+
|
|
43
|
+
throttle: (ms: Millis) =>
|
|
44
|
+
wrap<T, E>(
|
|
45
|
+
Stream.throttle(stream, {
|
|
46
|
+
cost: () => 1,
|
|
47
|
+
units: 1,
|
|
48
|
+
duration: ms,
|
|
49
|
+
strategy: 'enforce',
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
|
|
53
|
+
scan: <B>(initial: B, f: (acc: B, value: T) => B) => wrap<B, E>(Stream.scan(stream, initial, f)),
|
|
54
|
+
|
|
55
|
+
flatMap: <B, E2>(f: (a: T) => WireShape<B, E2>) => wrap<B, E | E2>(Stream.flatMap(stream, (a) => f(a).stream)),
|
|
56
|
+
|
|
57
|
+
merge: <B, E2>(other: WireShape<B, E2>) => wrap<T | B, E | E2>(Stream.merge(stream, other.stream)),
|
|
58
|
+
|
|
59
|
+
run: () => Stream.runDrain(stream),
|
|
60
|
+
|
|
61
|
+
runCollect: () => Effect.map(Stream.runCollect(stream), (chunk) => Array.from<T>(chunk)),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return wireStream;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Wraps an Effect Stream into a fluent Wire with chainable operators.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const wire = Wire.from(Stream.make(1, 2, 3));
|
|
73
|
+
* const doubled = wire.map(n => n * 2).filter(n => n > 2);
|
|
74
|
+
* const results = Effect.runSync(doubled.runCollect()); // [4, 6]
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
const _from = <T, E = never>(stream: Stream.Stream<T, E>): WireShape<T, E> => wrap(stream);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Creates a Wire from a Server-Sent Events endpoint.
|
|
81
|
+
* The EventSource is cleaned up when the stream finalizes.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const wire = Wire.fromSSE('/api/events');
|
|
86
|
+
* const parsed = wire.map(evt => JSON.parse(evt.data));
|
|
87
|
+
* await Effect.runPromise(Wire.runForEach(parsed, msg => Effect.log(msg)));
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
const _fromSSE = (url: string, options?: EventSourceInit): WireShape<MessageEvent, Error> => {
|
|
91
|
+
const stream = Stream.callback<MessageEvent, Error>((queue) =>
|
|
92
|
+
Effect.gen(function* () {
|
|
93
|
+
const eventSource = new EventSource(url, options);
|
|
94
|
+
let closed = false;
|
|
95
|
+
|
|
96
|
+
const shutdown = (): void => {
|
|
97
|
+
if (closed) return;
|
|
98
|
+
closed = true;
|
|
99
|
+
Queue.shutdown(queue).pipe(Effect.runSync);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
eventSource.onmessage = (event) => {
|
|
103
|
+
Queue.offerUnsafe(queue, event);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
eventSource.onerror = () => {
|
|
107
|
+
eventSource.close();
|
|
108
|
+
shutdown();
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
yield* Effect.addFinalizer(() =>
|
|
112
|
+
Effect.sync(() => {
|
|
113
|
+
eventSource.close();
|
|
114
|
+
shutdown();
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
yield* Effect.never;
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return wrap(stream);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Creates a Wire from a WebSocket connection.
|
|
127
|
+
* The socket is closed when the stream finalizes.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* const wire = Wire.fromWebSocket('wss://example.com/ws');
|
|
132
|
+
* const messages = wire.map(evt => evt.data as string);
|
|
133
|
+
* await Effect.runPromise(Wire.runForEach(messages, m => Effect.log(m)));
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
const _fromWebSocket = (url: string, protocols?: string | string[]): WireShape<MessageEvent, Error> => {
|
|
137
|
+
const stream = Stream.callback<MessageEvent, Error>((queue) =>
|
|
138
|
+
Effect.gen(function* () {
|
|
139
|
+
const ws = new WebSocket(url, protocols);
|
|
140
|
+
let closed = false;
|
|
141
|
+
|
|
142
|
+
const shutdown = (): void => {
|
|
143
|
+
if (closed) return;
|
|
144
|
+
closed = true;
|
|
145
|
+
Queue.shutdown(queue).pipe(Effect.runSync);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
ws.onmessage = (event) => {
|
|
149
|
+
Queue.offerUnsafe(queue, event);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
ws.onerror = () => {
|
|
153
|
+
shutdown();
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
ws.onclose = () => {
|
|
157
|
+
shutdown();
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
yield* Effect.addFinalizer(() =>
|
|
161
|
+
Effect.sync(() => {
|
|
162
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
163
|
+
ws.close();
|
|
164
|
+
}
|
|
165
|
+
shutdown();
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
yield* Effect.never;
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return wrap(stream);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Creates a Wire from any AsyncIterable source.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* async function* gen() { yield 1; yield 2; yield 3; }
|
|
182
|
+
* const wire = Wire.fromAsyncIterable(gen());
|
|
183
|
+
* const results = await Effect.runPromise(wire.runCollect()); // [1, 2, 3]
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
const _fromAsyncIterable = <T>(iterable: AsyncIterable<T>): WireShape<T, Error> => {
|
|
187
|
+
const stream = Stream.fromAsyncIterable(iterable, (e) => (e instanceof Error ? e : new Error(String(e))));
|
|
188
|
+
return wrap(stream);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Zips two Wires into a Wire of tuples, pairing elements pairwise.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* const a = Wire.from(Stream.make(1, 2));
|
|
197
|
+
* const b = Wire.from(Stream.make('a', 'b'));
|
|
198
|
+
* const zipped = Wire.zip(a, b);
|
|
199
|
+
* const results = Effect.runSync(zipped.runCollect()); // [[1,'a'], [2,'b']]
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
const _zip = <A, B>(a: WireShape<A>, b: WireShape<B>): WireShape<readonly [A, B]> =>
|
|
203
|
+
wrap(Stream.zip(a.stream, b.stream));
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Merges multiple Wires into a single Wire, interleaving their emissions.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* const a = Wire.from(Stream.make(1, 2));
|
|
211
|
+
* const b = Wire.from(Stream.make(3, 4));
|
|
212
|
+
* const merged = Wire.merge([a, b]);
|
|
213
|
+
* const results = Effect.runSync(merged.runCollect()); // [1, 2, 3, 4] (order varies)
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
const _merge = <T, E>(streams: ReadonlyArray<WireShape<T, E>>): WireShape<T, E> => {
|
|
217
|
+
const effectStreams = streams.map((s) => s.stream);
|
|
218
|
+
const merged = Stream.mergeAll(effectStreams, { concurrency: 'unbounded' });
|
|
219
|
+
return wrap(merged);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Collects all values from a Wire into an array.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```ts
|
|
227
|
+
* const wire = Wire.from(Stream.make(10, 20, 30));
|
|
228
|
+
* const values = Effect.runSync(Wire.runCollect(wire)); // [10, 20, 30]
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
const _runCollect = <T, E>(stream: WireShape<T, E>): Effect.Effect<ReadonlyArray<T>, E> =>
|
|
232
|
+
Effect.map(Stream.runCollect(stream.stream), (chunk) => Array.from(chunk));
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Runs an effectful function for each value emitted by the Wire.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```ts
|
|
239
|
+
* const wire = Wire.from(Stream.make('hello', 'world'));
|
|
240
|
+
* await Effect.runPromise(Wire.runForEach(wire, s => Effect.log(s)));
|
|
241
|
+
* // Logs: hello, world
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
const _runForEach = <T, SE, E, R>(
|
|
245
|
+
stream: WireShape<T, SE>,
|
|
246
|
+
fn: (t: T) => Effect.Effect<void, E, R>,
|
|
247
|
+
): Effect.Effect<void, SE | E, R> => Stream.runForEach(stream.stream, fn);
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Wire -- fluent stream wrapper with chainable operators for map, filter,
|
|
251
|
+
* scan, debounce, throttle, merge, and more. Wraps Effect Streams.
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* ```ts
|
|
255
|
+
* const wire = Wire.from(Stream.make(1, 2, 3, 4, 5));
|
|
256
|
+
* const result = wire.filter(n => n > 2).map(n => n * 10);
|
|
257
|
+
* const values = Effect.runSync(result.runCollect()); // [30, 40, 50]
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
export const Wire = {
|
|
261
|
+
from: _from,
|
|
262
|
+
fromSSE: _fromSSE,
|
|
263
|
+
fromWebSocket: _fromWebSocket,
|
|
264
|
+
fromAsyncIterable: _fromAsyncIterable,
|
|
265
|
+
zip: _zip,
|
|
266
|
+
merge: _merge,
|
|
267
|
+
runCollect: _runCollect,
|
|
268
|
+
runForEach: _runForEach,
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export declare namespace Wire {
|
|
272
|
+
/** Structural shape of a {@link Wire}: a fluent wrapper over `Stream.Stream<T, E>`. */
|
|
273
|
+
export type Shape<T, E = never> = WireShape<T, E>;
|
|
274
|
+
}
|
package/src/zap.ts
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zap<T> -- push-based event channel via PubSub.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Effect, Stream, PubSub, Fiber, Duration } from 'effect';
|
|
8
|
+
import type { Scope } from 'effect';
|
|
9
|
+
import type { Millis } from './brands.js';
|
|
10
|
+
|
|
11
|
+
interface ZapShape<T> {
|
|
12
|
+
readonly _tag: 'Zap';
|
|
13
|
+
readonly stream: Stream.Stream<T>;
|
|
14
|
+
emit(value: T): Effect.Effect<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new push-based event channel backed by an unbounded PubSub.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const zap = await Effect.runPromise(Effect.scoped(Zap.make<number>()));
|
|
23
|
+
* Effect.runSync(zap.emit(42));
|
|
24
|
+
* // Subscribers on zap.stream will receive 42
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
const _make = <T>(): Effect.Effect<ZapShape<T>, never, Scope.Scope> =>
|
|
28
|
+
Effect.gen(function* () {
|
|
29
|
+
const pubsub = yield* PubSub.unbounded<T>();
|
|
30
|
+
|
|
31
|
+
yield* Effect.addFinalizer(() => PubSub.shutdown(pubsub));
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
_tag: 'Zap' as const,
|
|
35
|
+
stream: Stream.fromPubSub(pubsub),
|
|
36
|
+
emit: (value: T) => PubSub.publish(pubsub, value),
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates a Zap from a DOM event, auto-managing listener lifecycle via Scope.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* const program = Effect.scoped(Effect.gen(function* () {
|
|
46
|
+
* const btn = document.getElementById('btn');
|
|
47
|
+
* if (!(btn instanceof HTMLElement)) return;
|
|
48
|
+
* const clicks = yield* Zap.fromDOMEvent(btn, 'click');
|
|
49
|
+
* // clicks.stream emits MouseEvents; listener removed when scope closes
|
|
50
|
+
* }));
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
const _fromDOMEvent = <K extends keyof HTMLElementEventMap>(
|
|
54
|
+
element: HTMLElement,
|
|
55
|
+
event: K,
|
|
56
|
+
): Effect.Effect<ZapShape<HTMLElementEventMap[K]>, never, Scope.Scope> =>
|
|
57
|
+
Effect.gen(function* () {
|
|
58
|
+
const zap = yield* _make<HTMLElementEventMap[K]>();
|
|
59
|
+
|
|
60
|
+
const listener = (e: HTMLElementEventMap[K]): void => {
|
|
61
|
+
Effect.runSync(zap.emit(e));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
yield* Effect.acquireRelease(
|
|
65
|
+
Effect.sync(() => element.addEventListener(event, listener)),
|
|
66
|
+
() => Effect.sync(() => element.removeEventListener(event, listener)),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return zap;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Merges multiple Zaps of the same type into a single Zap.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const program = Effect.scoped(Effect.gen(function* () {
|
|
78
|
+
* const a = yield* Zap.make<number>();
|
|
79
|
+
* const b = yield* Zap.make<number>();
|
|
80
|
+
* const merged = yield* Zap.merge([a, b]);
|
|
81
|
+
* // merged.stream receives events from both a and b
|
|
82
|
+
* }));
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
const _merge = <T>(events: ReadonlyArray<ZapShape<T>>): Effect.Effect<ZapShape<T>, never, Scope.Scope> =>
|
|
86
|
+
Effect.gen(function* () {
|
|
87
|
+
const merged = yield* _make<T>();
|
|
88
|
+
|
|
89
|
+
yield* Effect.forkScoped(
|
|
90
|
+
Effect.all(
|
|
91
|
+
events.map((event) => Stream.runForEach(event.stream, (value) => merged.emit(value))),
|
|
92
|
+
{ concurrency: 'unbounded' },
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return merged;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Transforms each value emitted by a Zap through a mapping function.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* const program = Effect.scoped(Effect.gen(function* () {
|
|
105
|
+
* const nums = yield* Zap.make<number>();
|
|
106
|
+
* const strs = yield* Zap.map(nums, n => `value: ${n}`);
|
|
107
|
+
* // strs.stream emits transformed strings
|
|
108
|
+
* }));
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
const _map = <A, B>(event: ZapShape<A>, f: (a: A) => B): Effect.Effect<ZapShape<B>, never, Scope.Scope> =>
|
|
112
|
+
Effect.gen(function* () {
|
|
113
|
+
const mapped = yield* _make<B>();
|
|
114
|
+
|
|
115
|
+
yield* Effect.forkScoped(Stream.runForEach(event.stream, (value) => mapped.emit(f(value))));
|
|
116
|
+
|
|
117
|
+
return mapped;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Filters a Zap, only forwarding values that satisfy the predicate.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* const program = Effect.scoped(Effect.gen(function* () {
|
|
126
|
+
* const nums = yield* Zap.make<number>();
|
|
127
|
+
* const evens = yield* Zap.filter(nums, n => n % 2 === 0);
|
|
128
|
+
* // evens.stream only receives even numbers
|
|
129
|
+
* }));
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
const _filter = <T>(
|
|
133
|
+
event: ZapShape<T>,
|
|
134
|
+
predicate: (value: T) => boolean,
|
|
135
|
+
): Effect.Effect<ZapShape<T>, never, Scope.Scope> =>
|
|
136
|
+
Effect.gen(function* () {
|
|
137
|
+
const filtered = yield* _make<T>();
|
|
138
|
+
|
|
139
|
+
yield* Effect.forkScoped(
|
|
140
|
+
Stream.runForEach(event.stream, (value) => (predicate(value) ? filtered.emit(value) : Effect.void)),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return filtered;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Debounces a Zap, only emitting after `ms` milliseconds of silence.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* const program = Effect.scoped(Effect.gen(function* () {
|
|
152
|
+
* const input = yield* Zap.make<string>();
|
|
153
|
+
* const debounced = yield* Zap.debounce(input, Millis(300));
|
|
154
|
+
* // debounced.stream emits only after 300ms pause in input
|
|
155
|
+
* }));
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
const _debounce = <T>(event: ZapShape<T>, ms: Millis): Effect.Effect<ZapShape<T>, never, Scope.Scope> =>
|
|
159
|
+
Effect.gen(function* () {
|
|
160
|
+
const debounced = yield* _make<T>();
|
|
161
|
+
let pendingFiber: Fiber.Fiber<void> | null = null;
|
|
162
|
+
|
|
163
|
+
yield* Effect.forkScoped(
|
|
164
|
+
Stream.runForEach(event.stream, (value) =>
|
|
165
|
+
Effect.gen(function* () {
|
|
166
|
+
if (pendingFiber) {
|
|
167
|
+
yield* Fiber.interrupt(pendingFiber);
|
|
168
|
+
}
|
|
169
|
+
pendingFiber = yield* Effect.forkChild(
|
|
170
|
+
Effect.gen(function* () {
|
|
171
|
+
yield* Effect.sleep(Duration.millis(ms));
|
|
172
|
+
yield* debounced.emit(value);
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
}),
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
return debounced;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Throttles a Zap, allowing at most one emission per `ms` milliseconds.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* const program = Effect.scoped(Effect.gen(function* () {
|
|
188
|
+
* const scroll = yield* Zap.make<number>();
|
|
189
|
+
* const throttled = yield* Zap.throttle(scroll, Millis(16));
|
|
190
|
+
* // throttled.stream emits at most once every 16ms (~60fps)
|
|
191
|
+
* }));
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
const _throttle = <T>(event: ZapShape<T>, ms: Millis): Effect.Effect<ZapShape<T>, never, Scope.Scope> =>
|
|
195
|
+
Effect.gen(function* () {
|
|
196
|
+
const throttled = yield* _make<T>();
|
|
197
|
+
let lastEmitTime = 0;
|
|
198
|
+
|
|
199
|
+
yield* Effect.forkScoped(
|
|
200
|
+
Stream.runForEach(event.stream, (value) =>
|
|
201
|
+
Effect.gen(function* () {
|
|
202
|
+
const now = Date.now();
|
|
203
|
+
if (now - lastEmitTime >= ms) {
|
|
204
|
+
lastEmitTime = now;
|
|
205
|
+
yield* throttled.emit(value);
|
|
206
|
+
}
|
|
207
|
+
}),
|
|
208
|
+
),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
return throttled;
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Zap -- push-based event channel backed by Effect PubSub.
|
|
216
|
+
* Provides reactive event streams with map, filter, merge, debounce, and throttle.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```ts
|
|
220
|
+
* const program = Effect.scoped(Effect.gen(function* () {
|
|
221
|
+
* const zap = yield* Zap.make<number>();
|
|
222
|
+
* const doubled = yield* Zap.map(zap, n => n * 2);
|
|
223
|
+
* yield* zap.emit(5);
|
|
224
|
+
* // doubled.stream receives 10
|
|
225
|
+
* }));
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
export const Zap = {
|
|
229
|
+
make: _make,
|
|
230
|
+
fromDOMEvent: _fromDOMEvent,
|
|
231
|
+
merge: _merge,
|
|
232
|
+
map: _map,
|
|
233
|
+
filter: _filter,
|
|
234
|
+
debounce: _debounce,
|
|
235
|
+
throttle: _throttle,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export declare namespace Zap {
|
|
239
|
+
/** Structural shape of a {@link Zap}: event-sourced reactive primitive exposing a discrete stream. */
|
|
240
|
+
export type Shape<T> = ZapShape<T>;
|
|
241
|
+
}
|