@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/op.ts
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Op<A, E, R>` — `Effect.Effect` wrapper with named factories.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Scope, Result } from 'effect';
|
|
8
|
+
import { Effect, Schedule, Duration } from 'effect';
|
|
9
|
+
import type { Millis } from './brands.js';
|
|
10
|
+
|
|
11
|
+
interface OpShape<A, E = never, R = never> {
|
|
12
|
+
readonly _tag: 'Op';
|
|
13
|
+
readonly effect: Effect.Effect<A, E, R>;
|
|
14
|
+
run(): Effect.Effect<A, E, R | Scope.Scope>;
|
|
15
|
+
map<B>(f: (a: A) => B): OpShape<B, E, R>;
|
|
16
|
+
flatMap<B, E2, R2>(f: (a: A) => OpShape<B, E2, R2>): OpShape<B, E | E2, R | R2>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type OpValue<T extends OpShape<unknown, unknown, unknown>> = T extends OpShape<infer A, unknown, unknown> ? A : never;
|
|
20
|
+
type OpError<T extends OpShape<unknown, unknown, unknown>> = T extends OpShape<unknown, infer E, unknown> ? E : never;
|
|
21
|
+
type OpRequirement<T extends OpShape<unknown, unknown, unknown>> =
|
|
22
|
+
T extends OpShape<unknown, unknown, infer R> ? R : never;
|
|
23
|
+
type OpValues<T extends readonly OpShape<unknown, unknown, unknown>[]> = { [K in keyof T]: OpValue<T[K]> };
|
|
24
|
+
type SettledOpValues<T extends readonly OpShape<unknown, unknown, unknown>[]> = {
|
|
25
|
+
[K in keyof T]: Result.Result<OpValue<T[K]>, OpError<T[K]>>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Wraps an Effect into an Op, providing `.map()` and `.flatMap()` chaining.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const op = Op.make(Effect.succeed(42));
|
|
34
|
+
* const doubled = op.map(n => n * 2);
|
|
35
|
+
* const result = Effect.runSync(doubled.run()); // 84
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
const _make = <A, E = never, R = never>(effect: Effect.Effect<A, E, R>): OpShape<A, E, R> => ({
|
|
39
|
+
_tag: 'Op' as const,
|
|
40
|
+
effect,
|
|
41
|
+
run: () => effect,
|
|
42
|
+
map: <B>(fn: (a: A) => B): OpShape<B, E, R> => _make(Effect.map(effect, fn)),
|
|
43
|
+
flatMap: <B, E2, R2>(fn: (a: A) => OpShape<B, E2, R2>): OpShape<B, E | E2, R | R2> =>
|
|
44
|
+
_make(Effect.flatMap(effect, (a) => fn(a).effect)),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates an Op from a Promise-returning function, catching errors as `Error`.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* const op = Op.fromPromise(() => fetch('/api/data').then(r => r.json()));
|
|
53
|
+
* const result = await Effect.runPromise(op.run());
|
|
54
|
+
* console.log(result); // parsed JSON response
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
const _fromPromise = <A>(f: () => Promise<A>): OpShape<A, Error> =>
|
|
58
|
+
_make(
|
|
59
|
+
Effect.tryPromise({
|
|
60
|
+
try: f,
|
|
61
|
+
catch: (error) => (error instanceof Error ? error : new Error(String(error))),
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Creates an Op that immediately succeeds with the given value.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* const op = Op.succeed({ name: 'dark', contrast: 0.9 });
|
|
71
|
+
* const result = Effect.runSync(op.run()); // { name: 'dark', contrast: 0.9 }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
const _succeed = <A>(value: A): OpShape<A> => _make(Effect.succeed(value));
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Creates an Op that immediately fails with the given error.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const op = Op.fail(new Error('GPU not available'));
|
|
82
|
+
* // Effect.runSync(op.run()) would throw
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
const _fail = <E>(error: E): OpShape<never, E> => _make(Effect.fail(error));
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Runs all Ops concurrently and returns their results as a tuple.
|
|
89
|
+
* Fails if any Op fails.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* const a = Op.succeed(10);
|
|
94
|
+
* const b = Op.succeed('hello');
|
|
95
|
+
* const combined = Op.all([a, b] as const);
|
|
96
|
+
* const [num, str] = Effect.runSync(combined.run()); // [10, 'hello']
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
// Effect.all's overloads infer tuple results only when the input shape itself is tuple-typed.
|
|
100
|
+
// `tasks.map(...)` widens to unknown[], so we contain one cast into a typed wrapper and
|
|
101
|
+
// apply it at the boundary where the tuple→Op product is materialized.
|
|
102
|
+
const _all = <T extends readonly OpShape<unknown, unknown, unknown>[]>(
|
|
103
|
+
tasks: T,
|
|
104
|
+
): OpShape<OpValues<T>, OpError<T[number]>, OpRequirement<T[number]>> => {
|
|
105
|
+
const effects = tasks.map((task) => task.effect);
|
|
106
|
+
const combined = Effect.all(effects, { concurrency: 'unbounded' }) as unknown as Effect.Effect<
|
|
107
|
+
OpValues<T>,
|
|
108
|
+
OpError<T[number]>,
|
|
109
|
+
OpRequirement<T[number]>
|
|
110
|
+
>;
|
|
111
|
+
return _make(combined);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Runs all Ops concurrently and returns a Result for each, never failing.
|
|
116
|
+
* Each result is either a success or a failure.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* const a = Op.succeed(1);
|
|
121
|
+
* const b = Op.fail(new Error('oops'));
|
|
122
|
+
* const settled = Op.allSettled([a, b] as const);
|
|
123
|
+
* const results = Effect.runSync(settled.run());
|
|
124
|
+
* // results[0] is Result.success(1), results[1] is Result.failure(Error)
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
// Mirrors _all: one boundary cast from unknown[] back to the tuple-projected product.
|
|
128
|
+
const _allSettled = <T extends readonly OpShape<unknown, unknown, unknown>[]>(
|
|
129
|
+
tasks: T,
|
|
130
|
+
): OpShape<SettledOpValues<T>, never, OpRequirement<T[number]>> => {
|
|
131
|
+
const resultEffects = tasks.map((task) => Effect.result(task.effect));
|
|
132
|
+
const combined = Effect.all(resultEffects, { concurrency: 'unbounded' }) as unknown as Effect.Effect<
|
|
133
|
+
SettledOpValues<T>,
|
|
134
|
+
never,
|
|
135
|
+
OpRequirement<T[number]>
|
|
136
|
+
>;
|
|
137
|
+
return _make(combined);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Races multiple Ops concurrently, returning the first to complete.
|
|
142
|
+
* Fails with an error if the array is empty.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* const fast = Op.succeed('fast');
|
|
147
|
+
* const slow = Op.fromPromise(() => new Promise(r => setTimeout(() => r('slow'), 100)));
|
|
148
|
+
* const winner = Op.race([fast, slow]);
|
|
149
|
+
* const result = Effect.runSync(winner.run()); // 'fast'
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
const _race = <A, E, R>(tasks: ReadonlyArray<OpShape<A, E, R>>): OpShape<A, E | Error, R> => {
|
|
153
|
+
if (tasks.length === 0) {
|
|
154
|
+
return _fail(new Error('Op.race: empty array'));
|
|
155
|
+
}
|
|
156
|
+
if (tasks.length === 1) {
|
|
157
|
+
return tasks[0]!;
|
|
158
|
+
}
|
|
159
|
+
const effects = tasks.map((task) => task.effect);
|
|
160
|
+
const raced = effects.reduce((acc, effect) => Effect.race(acc, effect));
|
|
161
|
+
return _make(raced);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Retries a failing Op with exponential backoff.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* const flaky = Op.fromPromise(() => fetch('/unstable-api').then(r => r.json()));
|
|
170
|
+
* const resilient = Op.retry(flaky, { times: 3, delay: Millis(200), factor: 2 });
|
|
171
|
+
* const result = await Effect.runPromise(resilient.run());
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
const _retry = <A, E, R>(
|
|
175
|
+
task: OpShape<A, E, R>,
|
|
176
|
+
options: { times: number; delay?: Millis; factor?: number },
|
|
177
|
+
): OpShape<A, E, R> => {
|
|
178
|
+
const delay = options.delay ?? 100;
|
|
179
|
+
const factor = options.factor ?? 2;
|
|
180
|
+
|
|
181
|
+
const schedule = Schedule.exponential(Duration.millis(delay), factor).pipe(
|
|
182
|
+
Schedule.both(Schedule.recurs(options.times)),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
return _make(Effect.retry(task.effect, schedule));
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Wraps an Op with a timeout, failing with an Error if it exceeds the given duration.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* const slow = Op.fromPromise(() => new Promise(r => setTimeout(() => r('done'), 5000)));
|
|
194
|
+
* const bounded = Op.timeout(slow, Millis(1000));
|
|
195
|
+
* // Will fail with Error('Op timed out after 1000ms') if not resolved in time
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
const _timeout = <A, E, R>(task: OpShape<A, E, R>, ms: Millis): OpShape<A, E | Error, R> =>
|
|
199
|
+
_make(
|
|
200
|
+
Effect.timeout(task.effect, Duration.millis(ms)).pipe(
|
|
201
|
+
Effect.catchTag('TimeoutError', () => Effect.fail(new Error(`Op timed out after ${ms}ms`))),
|
|
202
|
+
),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Op -- Effect.Effect wrapper providing named factories and combinators
|
|
207
|
+
* for async operations with retry, timeout, race, and parallel execution.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* const op = Op.succeed(42).map(n => n * 2);
|
|
212
|
+
* const result = Effect.runSync(op.run()); // 84
|
|
213
|
+
*
|
|
214
|
+
* const tasks = Op.all([Op.succeed(1), Op.succeed(2)] as const);
|
|
215
|
+
* const [a, b] = Effect.runSync(tasks.run()); // [1, 2]
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
export const Op = {
|
|
219
|
+
make: _make,
|
|
220
|
+
fromPromise: _fromPromise,
|
|
221
|
+
succeed: _succeed,
|
|
222
|
+
fail: _fail,
|
|
223
|
+
all: _all,
|
|
224
|
+
allSettled: _allSettled,
|
|
225
|
+
race: _race,
|
|
226
|
+
retry: _retry,
|
|
227
|
+
timeout: _timeout,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export declare namespace Op {
|
|
231
|
+
/** Structural shape of an {@link Op}: a thin alias over `Effect.Effect<A, E, R>` produced by the `Op.*` factories. */
|
|
232
|
+
export type Shape<A, E = never, R = never> = OpShape<A, E, R>;
|
|
233
|
+
}
|
package/src/plan.ts
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan -- plan IR builder for universal execution DAG.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Discriminated union describing the kind of work a `PlanStep` performs.
|
|
9
|
+
*
|
|
10
|
+
* `pure` and `effect` name an executable function; `spawn` references a child
|
|
11
|
+
* fiber/worker keyed by `key`; `domain` dispatches to an external domain's
|
|
12
|
+
* named operation; `choice` marks a branch point; `noop` is an explicit
|
|
13
|
+
* placeholder.
|
|
14
|
+
*/
|
|
15
|
+
export type OpType =
|
|
16
|
+
| { readonly type: 'pure'; readonly fn?: string }
|
|
17
|
+
| { readonly type: 'effect'; readonly fn?: string }
|
|
18
|
+
| { readonly type: 'spawn'; readonly key: string; readonly spec: Record<string, unknown> }
|
|
19
|
+
| { readonly type: 'domain'; readonly domain: string; readonly op: string }
|
|
20
|
+
| { readonly type: 'choice'; readonly condition: unknown }
|
|
21
|
+
| { readonly type: 'noop' };
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Edge flavor in a plan DAG: sequential (`seq`), parallel (`par`), or the two
|
|
25
|
+
* branches of a `choice` step (`choice_then` / `choice_else`).
|
|
26
|
+
*/
|
|
27
|
+
export type EdgeType = 'seq' | 'par' | 'choice_then' | 'choice_else';
|
|
28
|
+
|
|
29
|
+
/** A single node in a {@link PlanIR}: an identifier, a display name, and its {@link OpType}. */
|
|
30
|
+
export interface PlanStep {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly opType: OpType;
|
|
34
|
+
readonly metadata?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A directed edge between two {@link PlanStep}s, tagged by {@link EdgeType}. */
|
|
38
|
+
export interface PlanEdge {
|
|
39
|
+
readonly from: string;
|
|
40
|
+
readonly to: string;
|
|
41
|
+
readonly type: EdgeType;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Intermediate representation of a plan: named steps plus directed edges. */
|
|
45
|
+
export interface PlanIR {
|
|
46
|
+
readonly name: string;
|
|
47
|
+
readonly steps: readonly PlanStep[];
|
|
48
|
+
readonly edges: readonly PlanEdge[];
|
|
49
|
+
readonly metadata?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Structural failure from {@link Plan.validate}: either a cycle or an edge pointing at a missing step. */
|
|
53
|
+
export type PlanValidationError =
|
|
54
|
+
| { readonly type: 'cycle'; readonly message: string; readonly stepIds?: readonly string[] }
|
|
55
|
+
| { readonly type: 'missing_step'; readonly message: string; readonly stepIds?: readonly string[] };
|
|
56
|
+
|
|
57
|
+
/** Result of {@link Plan.validate}: either the validated plan or a list of errors. */
|
|
58
|
+
export type PlanValidationResult =
|
|
59
|
+
| { readonly ok: true; readonly plan: PlanIR }
|
|
60
|
+
| { readonly ok: false; readonly errors: readonly PlanValidationError[] };
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Result of {@link Plan.topoSort}: the sorted step IDs, optionally accompanied by
|
|
64
|
+
* the IDs that participated in a detected cycle.
|
|
65
|
+
*/
|
|
66
|
+
export type TopoSortResult =
|
|
67
|
+
| { readonly sorted: readonly string[]; readonly cycle?: undefined }
|
|
68
|
+
| { readonly sorted: readonly string[]; readonly cycle: readonly string[] };
|
|
69
|
+
|
|
70
|
+
interface PlanBuilder {
|
|
71
|
+
step(name: string, opType: OpType, metadata?: Record<string, unknown>): PlanBuilder;
|
|
72
|
+
seq(fromId: string, toId: string): PlanBuilder;
|
|
73
|
+
par(fromId: string, toId: string): PlanBuilder;
|
|
74
|
+
choice(fromId: string, thenId: string, elseId: string): PlanBuilder;
|
|
75
|
+
build(): PlanIR;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class PlanBuilderImpl implements PlanBuilder {
|
|
79
|
+
private steps: PlanStep[] = [];
|
|
80
|
+
private edges: PlanEdge[] = [];
|
|
81
|
+
|
|
82
|
+
constructor(private readonly planName: string) {}
|
|
83
|
+
|
|
84
|
+
step(name: string, opType: OpType, metadata?: Record<string, unknown>): PlanBuilder {
|
|
85
|
+
const id = `step-${this.steps.length + 1}`;
|
|
86
|
+
this.steps.push({ id, name, opType, metadata });
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
seq(fromId: string, toId: string): PlanBuilder {
|
|
91
|
+
this.edges.push({ from: fromId, to: toId, type: 'seq' });
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
par(fromId: string, toId: string): PlanBuilder {
|
|
96
|
+
this.edges.push({ from: fromId, to: toId, type: 'par' });
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
choice(fromId: string, thenId: string, elseId: string): PlanBuilder {
|
|
101
|
+
this.edges.push({ from: fromId, to: thenId, type: 'choice_then' });
|
|
102
|
+
this.edges.push({ from: fromId, to: elseId, type: 'choice_else' });
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
build(): PlanIR {
|
|
107
|
+
return {
|
|
108
|
+
name: this.planName,
|
|
109
|
+
steps: [...this.steps],
|
|
110
|
+
edges: [...this.edges],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create a new PlanBuilder with the given plan name.
|
|
117
|
+
*
|
|
118
|
+
* Returns a fluent builder that supports chaining `.step()`, `.seq()`,
|
|
119
|
+
* `.par()`, and `.choice()` calls. Call `.build()` to produce the PlanIR.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* const plan = Plan.make('my-pipeline')
|
|
124
|
+
* .step('fetch', { type: 'effect' })
|
|
125
|
+
* .step('transform', { type: 'pure' })
|
|
126
|
+
* .seq('step-1', 'step-2')
|
|
127
|
+
* .build();
|
|
128
|
+
* // plan.name === 'my-pipeline'
|
|
129
|
+
* // plan.steps.length === 2
|
|
130
|
+
* // plan.edges.length === 1
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
function _make(name: string): PlanBuilder {
|
|
134
|
+
return new PlanBuilderImpl(name);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function hasCycle(planIR: PlanIR): boolean {
|
|
138
|
+
const visited = new Set<string>();
|
|
139
|
+
const recStack = new Set<string>();
|
|
140
|
+
const adjList = new Map<string, string[]>();
|
|
141
|
+
|
|
142
|
+
for (const step of planIR.steps) {
|
|
143
|
+
adjList.set(step.id, []);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const edge of planIR.edges) {
|
|
147
|
+
adjList.get(edge.from)?.push(edge.to);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function dfs(node: string): boolean {
|
|
151
|
+
visited.add(node);
|
|
152
|
+
recStack.add(node);
|
|
153
|
+
for (const neighbor of adjList.get(node) ?? []) {
|
|
154
|
+
if (!visited.has(neighbor)) {
|
|
155
|
+
if (dfs(neighbor)) return true;
|
|
156
|
+
} else if (recStack.has(neighbor)) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
recStack.delete(node);
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (const step of planIR.steps) {
|
|
165
|
+
if (!visited.has(step.id) && dfs(step.id)) return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Validate a PlanIR for structural correctness.
|
|
173
|
+
*
|
|
174
|
+
* Checks that all edges reference existing steps and that the graph is acyclic.
|
|
175
|
+
* Returns `{ ok: true, plan }` on success or `{ ok: false, errors }` with
|
|
176
|
+
* detailed validation errors.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```ts
|
|
180
|
+
* const plan = Plan.make('test').step('a', { type: 'noop' }).build();
|
|
181
|
+
* const result = Plan.validate(plan);
|
|
182
|
+
* // result.ok === true
|
|
183
|
+
* // result.plan === plan
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
function _validate(planIR: PlanIR): PlanValidationResult {
|
|
187
|
+
const errors: PlanValidationError[] = [];
|
|
188
|
+
const stepIds = new Set(planIR.steps.map((s) => s.id));
|
|
189
|
+
|
|
190
|
+
for (const edge of planIR.edges) {
|
|
191
|
+
if (!stepIds.has(edge.from)) {
|
|
192
|
+
errors.push({
|
|
193
|
+
type: 'missing_step',
|
|
194
|
+
message: `Edge references unknown step: ${edge.from}`,
|
|
195
|
+
stepIds: [edge.from],
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (!stepIds.has(edge.to)) {
|
|
199
|
+
errors.push({
|
|
200
|
+
type: 'missing_step',
|
|
201
|
+
message: `Edge references unknown step: ${edge.to}`,
|
|
202
|
+
stepIds: [edge.to],
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (hasCycle(planIR)) {
|
|
208
|
+
errors.push({ type: 'cycle', message: 'Plan contains a cycle' });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return errors.length === 0 ? { ok: true, plan: planIR } : { ok: false, errors };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Topologically sort the steps of a PlanIR using Kahn's algorithm.
|
|
216
|
+
*
|
|
217
|
+
* Returns `{ sorted }` on success. If a cycle exists, returns
|
|
218
|
+
* `{ sorted, cycle }` where `cycle` lists the step IDs involved.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* const plan = Plan.make('pipeline')
|
|
223
|
+
* .step('a', { type: 'pure' })
|
|
224
|
+
* .step('b', { type: 'pure' })
|
|
225
|
+
* .seq('step-1', 'step-2')
|
|
226
|
+
* .build();
|
|
227
|
+
* const result = Plan.topoSort(plan);
|
|
228
|
+
* // result.sorted === ['step-1', 'step-2']
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
function _topoSort(planIR: PlanIR): TopoSortResult {
|
|
232
|
+
const adjList = new Map<string, string[]>();
|
|
233
|
+
const inDegree = new Map<string, number>();
|
|
234
|
+
|
|
235
|
+
for (const step of planIR.steps) {
|
|
236
|
+
adjList.set(step.id, []);
|
|
237
|
+
inDegree.set(step.id, 0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
for (const edge of planIR.edges) {
|
|
241
|
+
adjList.get(edge.from)?.push(edge.to);
|
|
242
|
+
inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const queue: string[] = [];
|
|
246
|
+
const result: string[] = [];
|
|
247
|
+
|
|
248
|
+
for (const [id, degree] of inDegree) {
|
|
249
|
+
if (degree === 0) queue.push(id);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
while (queue.length > 0) {
|
|
253
|
+
const current = queue.shift()!;
|
|
254
|
+
result.push(current);
|
|
255
|
+
for (const neighbor of adjList.get(current) ?? []) {
|
|
256
|
+
const newDegree = inDegree.get(neighbor)! - 1;
|
|
257
|
+
inDegree.set(neighbor, newDegree);
|
|
258
|
+
if (newDegree === 0) queue.push(neighbor);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (result.length !== planIR.steps.length) {
|
|
263
|
+
const resultSet = new Set(result);
|
|
264
|
+
const cycleNodes = planIR.steps.filter((s) => !resultSet.has(s.id)).map((s) => s.id);
|
|
265
|
+
return { sorted: result, cycle: cycleNodes };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return { sorted: result };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Plan namespace -- plan IR builder for universal execution DAG.
|
|
273
|
+
*
|
|
274
|
+
* Build, validate, and topologically sort execution plans. Plans model
|
|
275
|
+
* computation graphs with sequential, parallel, and conditional edges.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```ts
|
|
279
|
+
* import { Plan } from '@czap/core';
|
|
280
|
+
*
|
|
281
|
+
* const plan = Plan.make('render-pipeline')
|
|
282
|
+
* .step('load', { type: 'effect' })
|
|
283
|
+
* .step('compile', { type: 'pure' })
|
|
284
|
+
* .step('emit', { type: 'effect' })
|
|
285
|
+
* .seq('step-1', 'step-2')
|
|
286
|
+
* .seq('step-2', 'step-3')
|
|
287
|
+
* .build();
|
|
288
|
+
* const valid = Plan.validate(plan);
|
|
289
|
+
* const order = Plan.topoSort(plan);
|
|
290
|
+
* // order.sorted === ['step-1', 'step-2', 'step-3']
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
export const Plan = {
|
|
294
|
+
/** Start a new fluent {@link Plan.Builder} with the given display name. */
|
|
295
|
+
make: _make,
|
|
296
|
+
/** Check that every edge references a known step and that the graph is acyclic. */
|
|
297
|
+
validate: _validate,
|
|
298
|
+
/** Kahn's-algorithm topological sort; surfaces cycle participants if the plan is not a DAG. */
|
|
299
|
+
topoSort: _topoSort,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
export declare namespace Plan {
|
|
303
|
+
/** Alias for `PlanIR`. */
|
|
304
|
+
export type IR = PlanIR;
|
|
305
|
+
/** Alias for `PlanStep`. */
|
|
306
|
+
export type Step = PlanStep;
|
|
307
|
+
/** Alias for `PlanEdge`. */
|
|
308
|
+
export type Edge = PlanEdge;
|
|
309
|
+
/** Alias for `PlanValidationError`. */
|
|
310
|
+
export type ValidationError = PlanValidationError;
|
|
311
|
+
/** Alias for `PlanValidationResult`. */
|
|
312
|
+
export type ValidationResult = PlanValidationResult;
|
|
313
|
+
/** Alias for `TopoSortResult`. */
|
|
314
|
+
export type TopoSort = TopoSortResult;
|
|
315
|
+
/** Fluent builder interface returned by `Plan.make`. */
|
|
316
|
+
export type Builder = PlanBuilder;
|
|
317
|
+
}
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol types -- CellEnvelope, CellKind, CellMeta.
|
|
3
|
+
*
|
|
4
|
+
* These types form the wire-level protocol for cells in the czap system.
|
|
5
|
+
* Every cell has a kind, content address, metadata, and value.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ContentAddress, HLC } from './brands.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Discriminator tagging what a {@link CellEnvelope} carries — a boundary, a
|
|
14
|
+
* discrete state, a target output (CSS/GLSL/WGSL/ARIA/AI), or one of the
|
|
15
|
+
* other reactive shapes produced along the pipeline.
|
|
16
|
+
*/
|
|
17
|
+
export type CellKind =
|
|
18
|
+
| 'boundary'
|
|
19
|
+
| 'state'
|
|
20
|
+
| 'output'
|
|
21
|
+
| 'signal'
|
|
22
|
+
| 'transition'
|
|
23
|
+
| 'timeline'
|
|
24
|
+
| 'compositor'
|
|
25
|
+
| 'blend'
|
|
26
|
+
| 'css'
|
|
27
|
+
| 'glsl'
|
|
28
|
+
| 'wgsl'
|
|
29
|
+
| 'aria'
|
|
30
|
+
| 'ai';
|
|
31
|
+
|
|
32
|
+
/** Protocol metadata attached to every {@link CellEnvelope}: HLC timestamps + monotonic version counter. */
|
|
33
|
+
export interface CellMeta {
|
|
34
|
+
readonly created: HLC;
|
|
35
|
+
readonly updated: HLC;
|
|
36
|
+
readonly version: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Wire-level envelope for a cell value: tagged by {@link CellKind}, identified
|
|
41
|
+
* by its content address, stamped with {@link CellMeta}, carrying the typed
|
|
42
|
+
* payload in `value`.
|
|
43
|
+
*/
|
|
44
|
+
export interface CellEnvelope<K extends CellKind = CellKind, T = unknown> {
|
|
45
|
+
readonly kind: K;
|
|
46
|
+
readonly id: ContentAddress;
|
|
47
|
+
readonly meta: CellMeta;
|
|
48
|
+
readonly value: T;
|
|
49
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quantizer interface -- the base contract for quantizer implementations.
|
|
3
|
+
*
|
|
4
|
+
* The canonical implementation lives in `@czap/quantizer` (`Q.from()` builder API).
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Effect, Stream } from 'effect';
|
|
10
|
+
import type { Boundary } from './boundary.js';
|
|
11
|
+
import type { StateUnion, BoundaryCrossing } from './type-utils.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Quantizer contract — the live evaluator that binds a {@link Boundary} to a signal source.
|
|
15
|
+
*
|
|
16
|
+
* A quantizer holds a boundary definition plus the reactive machinery to observe
|
|
17
|
+
* its current state and emit crossings when the underlying signal moves between
|
|
18
|
+
* bands. The concrete implementation is produced by `@czap/quantizer`'s `Q.from()`
|
|
19
|
+
* builder; consumers interact only via this structural interface.
|
|
20
|
+
*/
|
|
21
|
+
export interface Quantizer<B extends Boundary.Shape = Boundary.Shape> {
|
|
22
|
+
readonly _tag: 'Quantizer';
|
|
23
|
+
readonly boundary: B;
|
|
24
|
+
readonly state: Effect.Effect<StateUnion<B>>;
|
|
25
|
+
/** Synchronous state accessor for hot paths (avoids Effect overhead). */
|
|
26
|
+
readonly stateSync?: () => StateUnion<B>;
|
|
27
|
+
readonly changes: Stream.Stream<BoundaryCrossing<StateUnion<B> & string>>;
|
|
28
|
+
evaluate(value: number): StateUnion<B>;
|
|
29
|
+
}
|