@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
package/src/derived.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derived<T> -- computed reactive value.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Scope } from 'effect';
|
|
8
|
+
import { Effect, Stream, SubscriptionRef } from 'effect';
|
|
9
|
+
import { tupleMap } from './tuple.js';
|
|
10
|
+
import type { Cell } from './cell.js';
|
|
11
|
+
import { readAllCellValues } from './cell.js';
|
|
12
|
+
|
|
13
|
+
interface DerivedShape<T> {
|
|
14
|
+
readonly _tag: 'Derived';
|
|
15
|
+
readonly changes: Stream.Stream<T>;
|
|
16
|
+
readonly get: Effect.Effect<T>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const _make = <T>(
|
|
20
|
+
compute: Effect.Effect<T>,
|
|
21
|
+
sources: ReadonlyArray<Stream.Stream<unknown>> = [],
|
|
22
|
+
): Effect.Effect<DerivedShape<T>, never, Scope.Scope> =>
|
|
23
|
+
Effect.gen(function* () {
|
|
24
|
+
const initialValue = yield* compute;
|
|
25
|
+
const ref = yield* SubscriptionRef.make(initialValue);
|
|
26
|
+
|
|
27
|
+
if (sources.length > 0) {
|
|
28
|
+
const merged = Stream.mergeAll(sources, { concurrency: 'unbounded' });
|
|
29
|
+
yield* Effect.forkScoped(
|
|
30
|
+
Stream.runForEach(merged, () =>
|
|
31
|
+
Effect.gen(function* () {
|
|
32
|
+
const newValue = yield* compute;
|
|
33
|
+
yield* SubscriptionRef.set(ref, newValue);
|
|
34
|
+
}),
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
_tag: 'Derived' as const,
|
|
41
|
+
changes: SubscriptionRef.changes(ref),
|
|
42
|
+
get: SubscriptionRef.get(ref),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const _combine = <T extends readonly unknown[], U>(
|
|
47
|
+
cells: { [K in keyof T]: Cell.Shape<T[K]> },
|
|
48
|
+
combiner: (...args: T) => U,
|
|
49
|
+
): Effect.Effect<DerivedShape<U>, never, Scope.Scope> => {
|
|
50
|
+
const readAll = readAllCellValues<T>(cells);
|
|
51
|
+
|
|
52
|
+
return Effect.gen(function* () {
|
|
53
|
+
const initialValues = yield* readAll;
|
|
54
|
+
const initialResult = combiner(...initialValues);
|
|
55
|
+
const ref = yield* SubscriptionRef.make(initialResult);
|
|
56
|
+
|
|
57
|
+
const cellStreams = tupleMap(cells, (cell) => cell.changes);
|
|
58
|
+
const combinedStream = Stream.mergeAll(cellStreams, {
|
|
59
|
+
concurrency: 'unbounded',
|
|
60
|
+
}).pipe(
|
|
61
|
+
Stream.mapEffect(() =>
|
|
62
|
+
Effect.gen(function* () {
|
|
63
|
+
const currentValues = yield* readAll;
|
|
64
|
+
const result = combiner(...currentValues);
|
|
65
|
+
yield* SubscriptionRef.set(ref, result);
|
|
66
|
+
return result;
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
yield* Effect.forkScoped(Stream.runDrain(combinedStream));
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
_tag: 'Derived' as const,
|
|
75
|
+
changes: SubscriptionRef.changes(ref),
|
|
76
|
+
get: SubscriptionRef.get(ref),
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const _map = <A, B>(derived: DerivedShape<A>, f: (a: A) => B): Effect.Effect<DerivedShape<B>, never, Scope.Scope> =>
|
|
82
|
+
Effect.gen(function* () {
|
|
83
|
+
const initialValue = yield* derived.get;
|
|
84
|
+
const mappedValue = f(initialValue);
|
|
85
|
+
const ref = yield* SubscriptionRef.make(mappedValue);
|
|
86
|
+
|
|
87
|
+
const mappedStream = derived.changes.pipe(
|
|
88
|
+
Stream.map(f),
|
|
89
|
+
Stream.tap((value) => SubscriptionRef.set(ref, value)),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
yield* Effect.forkScoped(Stream.runDrain(mappedStream));
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
_tag: 'Derived' as const,
|
|
96
|
+
changes: SubscriptionRef.changes(ref),
|
|
97
|
+
get: SubscriptionRef.get(ref),
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const _flatten = <T>(nested: DerivedShape<DerivedShape<T>>): Effect.Effect<DerivedShape<T>, never, Scope.Scope> =>
|
|
102
|
+
Effect.gen(function* () {
|
|
103
|
+
const initialInner = yield* nested.get;
|
|
104
|
+
const initialValue = yield* initialInner.get;
|
|
105
|
+
const ref = yield* SubscriptionRef.make(initialValue);
|
|
106
|
+
|
|
107
|
+
const flattenedStream = nested.changes.pipe(
|
|
108
|
+
Stream.switchMap((inner) => {
|
|
109
|
+
let currentValue: T | undefined;
|
|
110
|
+
let hasCurrentValue = false;
|
|
111
|
+
let skippedReplay = false;
|
|
112
|
+
return Stream.concat(
|
|
113
|
+
Stream.make(inner).pipe(
|
|
114
|
+
Stream.mapEffect((currentInner) => currentInner.get),
|
|
115
|
+
Stream.tap((value) =>
|
|
116
|
+
Effect.sync(() => {
|
|
117
|
+
currentValue = value;
|
|
118
|
+
hasCurrentValue = true;
|
|
119
|
+
}),
|
|
120
|
+
),
|
|
121
|
+
),
|
|
122
|
+
inner.changes.pipe(
|
|
123
|
+
Stream.filter((value) => {
|
|
124
|
+
if (!skippedReplay && hasCurrentValue && Object.is(value, currentValue)) {
|
|
125
|
+
skippedReplay = true;
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
}),
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
}),
|
|
133
|
+
Stream.tap((value) => SubscriptionRef.set(ref, value)),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
yield* Effect.forkScoped(Stream.runDrain(flattenedStream));
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
_tag: 'Derived' as const,
|
|
140
|
+
changes: SubscriptionRef.changes(ref),
|
|
141
|
+
get: SubscriptionRef.get(ref),
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Derived — read-only reactive view computed from upstream {@link Cell}s.
|
|
147
|
+
* A `Derived` recomputes lazily and pushes the new value into its own stream
|
|
148
|
+
* when any dependency changes; composes via `combine`, `map`, and `flatten`.
|
|
149
|
+
*/
|
|
150
|
+
export const Derived = {
|
|
151
|
+
/** Build a derived cell from a factory computing against upstream sources. */
|
|
152
|
+
make: _make,
|
|
153
|
+
/** Combine multiple cells into a single derived cell of their tuple. */
|
|
154
|
+
combine: _combine,
|
|
155
|
+
/** Pure projection of an existing cell/derived. */
|
|
156
|
+
map: _map,
|
|
157
|
+
/** Flatten a derived-of-derived into a single derived of the inner value. */
|
|
158
|
+
flatten: _flatten,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export declare namespace Derived {
|
|
162
|
+
/** Structural shape of a {@link Derived}: `_tag`, `get`, `changes`. */
|
|
163
|
+
export type Shape<T> = DerivedShape<T>;
|
|
164
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostics -- centralized runtime warning/error emission.
|
|
3
|
+
*
|
|
4
|
+
* Provides typed warning/error helpers with a swappable sink so runtime
|
|
5
|
+
* boundaries can emit operator-visible diagnostics without hard-coding
|
|
6
|
+
* console calls throughout the codebase.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Severity level for a {@link DiagnosticEvent}. */
|
|
12
|
+
export type DiagnosticLevel = 'warn' | 'error';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Operator-facing payload shape for a single diagnostic emission: a stable
|
|
16
|
+
* `source`/`code` pair for filtering, a human message, plus optional structured
|
|
17
|
+
* detail and an underlying cause.
|
|
18
|
+
*/
|
|
19
|
+
export interface DiagnosticPayload {
|
|
20
|
+
readonly source: string;
|
|
21
|
+
readonly code: string;
|
|
22
|
+
readonly message: string;
|
|
23
|
+
readonly cause?: unknown;
|
|
24
|
+
readonly detail?: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A {@link DiagnosticPayload} enriched with severity and an emission timestamp. */
|
|
28
|
+
export interface DiagnosticEvent extends DiagnosticPayload {
|
|
29
|
+
readonly level: DiagnosticLevel;
|
|
30
|
+
readonly timestamp: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Swappable transport that receives {@link DiagnosticEvent}s from {@link Diagnostics}. */
|
|
34
|
+
export interface DiagnosticsSink {
|
|
35
|
+
emit(event: DiagnosticEvent): void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type ConsoleMethodName = Extract<DiagnosticLevel, 'warn' | 'error'>;
|
|
39
|
+
|
|
40
|
+
interface ConsoleLike {
|
|
41
|
+
readonly warn?: (...args: readonly unknown[]) => void;
|
|
42
|
+
readonly error?: (...args: readonly unknown[]) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function asConsoleLike(value: unknown): ConsoleLike | null {
|
|
46
|
+
if (typeof value !== 'object' || value === null) return null;
|
|
47
|
+
const v = value as Record<string, unknown>;
|
|
48
|
+
// Require at least one usable method; warn/error may be absent in stripped envs.
|
|
49
|
+
if (typeof v['warn'] !== 'function' && typeof v['error'] !== 'function') return null;
|
|
50
|
+
return value as ConsoleLike;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getConsoleMethod(level: ConsoleMethodName): ((...args: readonly unknown[]) => void) | null {
|
|
54
|
+
const consoleLike = asConsoleLike(globalThis.console);
|
|
55
|
+
const method = consoleLike?.[level];
|
|
56
|
+
return typeof method === 'function' ? method.bind(consoleLike) : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function formatHeadline(event: DiagnosticEvent): string {
|
|
60
|
+
return `[${event.source}] ${event.code}: ${event.message}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function toArgs(event: DiagnosticEvent): readonly unknown[] {
|
|
64
|
+
const args: unknown[] = [formatHeadline(event)];
|
|
65
|
+
|
|
66
|
+
if (event.detail !== undefined) {
|
|
67
|
+
args.push(event.detail);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (event.cause !== undefined) {
|
|
71
|
+
args.push(event.cause);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return args;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const defaultSink: DiagnosticsSink = {
|
|
78
|
+
emit(event) {
|
|
79
|
+
const method = getConsoleMethod(event.level);
|
|
80
|
+
if (method) {
|
|
81
|
+
method(...toArgs(event));
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
let currentSink: DiagnosticsSink = defaultSink;
|
|
87
|
+
const onceKeys = new Set<string>();
|
|
88
|
+
|
|
89
|
+
function toEvent(level: DiagnosticLevel, payload: DiagnosticPayload): DiagnosticEvent {
|
|
90
|
+
return {
|
|
91
|
+
...payload,
|
|
92
|
+
level,
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function emit(level: DiagnosticLevel, payload: DiagnosticPayload): DiagnosticEvent {
|
|
98
|
+
const event = toEvent(level, payload);
|
|
99
|
+
currentSink.emit(event);
|
|
100
|
+
return event;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildOnceKey(payload: DiagnosticPayload): string {
|
|
104
|
+
return `${payload.source}:${payload.code}:${payload.message}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function warn(payload: DiagnosticPayload): DiagnosticEvent {
|
|
108
|
+
return emit('warn', payload);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function error(payload: DiagnosticPayload): DiagnosticEvent {
|
|
112
|
+
return emit('error', payload);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function warnOnce(payload: DiagnosticPayload): DiagnosticEvent | null {
|
|
116
|
+
const key = buildOnceKey(payload);
|
|
117
|
+
if (onceKeys.has(key)) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
onceKeys.add(key);
|
|
122
|
+
return warn(payload);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function setSink(sink: DiagnosticsSink): void {
|
|
126
|
+
currentSink = sink;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function resetSink(): void {
|
|
130
|
+
currentSink = defaultSink;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function clearOnce(): void {
|
|
134
|
+
onceKeys.clear();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function reset(): void {
|
|
138
|
+
resetSink();
|
|
139
|
+
clearOnce();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function createBufferSink(): { readonly sink: DiagnosticsSink; readonly events: DiagnosticEvent[] } {
|
|
143
|
+
const events: DiagnosticEvent[] = [];
|
|
144
|
+
return {
|
|
145
|
+
sink: {
|
|
146
|
+
emit(event) {
|
|
147
|
+
events.push(event);
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
events,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Diagnostics facade — runtime boundaries call {@link Diagnostics.warn} / {@link Diagnostics.error}
|
|
156
|
+
* instead of `console.*` so hosts can redirect or capture every diagnostic via {@link Diagnostics.setSink}.
|
|
157
|
+
*/
|
|
158
|
+
export const Diagnostics = {
|
|
159
|
+
/** Emit a `warn`-level {@link DiagnosticEvent} to the current sink. */
|
|
160
|
+
warn,
|
|
161
|
+
/** Emit an `error`-level {@link DiagnosticEvent} to the current sink. */
|
|
162
|
+
error,
|
|
163
|
+
/** {@link Diagnostics.warn}, but deduplicated by `source:code:message`. */
|
|
164
|
+
warnOnce,
|
|
165
|
+
/** Replace the active sink (e.g. for tests or hosted environments). */
|
|
166
|
+
setSink,
|
|
167
|
+
/** Restore the default sink that writes through `console`. */
|
|
168
|
+
resetSink,
|
|
169
|
+
/** Clear the deduplication set used by {@link Diagnostics.warnOnce}. */
|
|
170
|
+
clearOnce,
|
|
171
|
+
/** Convenience for `resetSink()` + `clearOnce()` — mostly for test teardown. */
|
|
172
|
+
reset,
|
|
173
|
+
/** Build an in-memory sink that collects events into an array — useful for tests. */
|
|
174
|
+
createBufferSink,
|
|
175
|
+
} as const;
|
|
176
|
+
|
|
177
|
+
export declare namespace Diagnostics {
|
|
178
|
+
/** Alias for {@link DiagnosticPayload}. */
|
|
179
|
+
export type Payload = DiagnosticPayload;
|
|
180
|
+
/** Alias for {@link DiagnosticEvent}. */
|
|
181
|
+
export type Event = DiagnosticEvent;
|
|
182
|
+
/** Alias for {@link DiagnosticLevel}. */
|
|
183
|
+
export type Level = DiagnosticLevel;
|
|
184
|
+
/** Alias for {@link DiagnosticsSink}. */
|
|
185
|
+
export type Sink = DiagnosticsSink;
|
|
186
|
+
}
|
package/src/dirty.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DirtyFlags -- bitmask dirty tracking.
|
|
3
|
+
*
|
|
4
|
+
* Maps string keys to bit positions in a 32-bit integer mask,
|
|
5
|
+
* enabling O(1) mark/clear/check operations for change tracking.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { DIRTY_FLAGS_MAX } from './defaults.js';
|
|
11
|
+
|
|
12
|
+
interface DirtyFlagsShape<K extends string = string> {
|
|
13
|
+
mark(key: K): void;
|
|
14
|
+
clear(key: K): void;
|
|
15
|
+
clearAll(): void;
|
|
16
|
+
isDirty(key: K): boolean;
|
|
17
|
+
getDirty(): readonly K[];
|
|
18
|
+
readonly mask: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a bitmask-based dirty tracker for the given keys (max 31).
|
|
23
|
+
* Enables O(1) mark, clear, and check operations for change tracking.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const flags = DirtyFlags.make(['position', 'color', 'opacity'] as const);
|
|
28
|
+
* flags.mark('position');
|
|
29
|
+
* flags.mark('color');
|
|
30
|
+
* flags.isDirty('position'); // true
|
|
31
|
+
* flags.isDirty('opacity'); // false
|
|
32
|
+
* flags.getDirty(); // ['position', 'color']
|
|
33
|
+
* flags.clearAll();
|
|
34
|
+
* flags.mask; // 0
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
function _make<K extends string>(keys: readonly K[]): DirtyFlagsShape<K> {
|
|
38
|
+
if (keys.length > DIRTY_FLAGS_MAX) {
|
|
39
|
+
throw new RangeError(`DirtyFlags supports at most ${DIRTY_FLAGS_MAX} keys, got ${keys.length}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const bitMap = new Map<K, number>();
|
|
43
|
+
keys.forEach((key, i) => {
|
|
44
|
+
bitMap.set(key, 1 << i);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
let mask = 0;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
mark(key: K): void {
|
|
51
|
+
const bit = bitMap.get(key);
|
|
52
|
+
if (bit !== undefined) mask |= bit;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
clear(key: K): void {
|
|
56
|
+
const bit = bitMap.get(key);
|
|
57
|
+
if (bit !== undefined) mask &= ~bit;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
clearAll(): void {
|
|
61
|
+
mask = 0;
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
isDirty(key: K): boolean {
|
|
65
|
+
const bit = bitMap.get(key);
|
|
66
|
+
return bit !== undefined ? (mask & bit) !== 0 : false;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
getDirty(): readonly K[] {
|
|
70
|
+
const dirty: K[] = [];
|
|
71
|
+
for (const [key, bit] of bitMap) {
|
|
72
|
+
if ((mask & bit) !== 0) dirty.push(key);
|
|
73
|
+
}
|
|
74
|
+
return dirty;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
get mask(): number {
|
|
78
|
+
return mask;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* DirtyFlags -- bitmask-based dirty tracking for up to 31 named keys.
|
|
85
|
+
* O(1) mark/clear/check operations using bitwise integer operations.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const flags = DirtyFlags.make(['transform', 'style'] as const);
|
|
90
|
+
* flags.mark('transform');
|
|
91
|
+
* flags.isDirty('transform'); // true
|
|
92
|
+
* flags.clear('transform');
|
|
93
|
+
* flags.isDirty('transform'); // false
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export const DirtyFlags = { make: _make };
|
|
97
|
+
|
|
98
|
+
export declare namespace DirtyFlags {
|
|
99
|
+
/** Structural shape of a {@link DirtyFlags} instance keyed by flag name `K`. */
|
|
100
|
+
export type Shape<K extends string = string> = DirtyFlagsShape<K>;
|
|
101
|
+
}
|