@loomwork/ui-renderer-terminal 0.0.0-stage → 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 +202 -0
- package/NOTICE +8 -0
- package/README.md +369 -2
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/inert.js +13 -0
- package/dist/esm/inert.js.map +1 -0
- package/dist/esm/interactive.js +677 -0
- package/dist/esm/interactive.js.map +1 -0
- package/dist/esm/registry.js +428 -0
- package/dist/esm/registry.js.map +1 -0
- package/dist/esm/status.js +64 -0
- package/dist/esm/status.js.map +1 -0
- package/dist/esm/watch.js +17 -0
- package/dist/esm/watch.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/inert.d.ts +12 -0
- package/dist/inert.d.ts.map +1 -0
- package/dist/inert.js +17 -0
- package/dist/inert.js.map +1 -0
- package/dist/interactive.d.ts +142 -0
- package/dist/interactive.d.ts.map +1 -0
- package/dist/interactive.js +685 -0
- package/dist/interactive.js.map +1 -0
- package/dist/registry.d.ts +55 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +434 -0
- package/dist/registry.js.map +1 -0
- package/dist/status.d.ts +49 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +69 -0
- package/dist/status.js.map +1 -0
- package/dist/watch.d.ts +9 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +20 -0
- package/dist/watch.js.map +1 -0
- package/package.json +60 -3
- package/src/index.ts +25 -0
- package/src/inert.ts +14 -0
- package/src/interactive.ts +868 -0
- package/src/registry.ts +630 -0
- package/src/status.ts +103 -0
- package/src/watch.ts +22 -0
package/src/status.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Blueprint, RendererRegistry } from "@loomwork/blueprint-runtime";
|
|
2
|
+
import {
|
|
3
|
+
type TerminalWidgetRenderer,
|
|
4
|
+
type WidgetValueCheck,
|
|
5
|
+
renderBlueprintToString,
|
|
6
|
+
} from "@loomwork/plugin-renderer-terminal";
|
|
7
|
+
import { type ContractRegistry, checkKindValue } from "@loomwork/ui-core";
|
|
8
|
+
import { HYDRATION_PROJECTION_ID, type HydrationStore } from "@loomwork/ui-hydration/client";
|
|
9
|
+
import { inert } from "./inert";
|
|
10
|
+
import { type TerminalRegistryOptions, terminalRegistry } from "./registry";
|
|
11
|
+
|
|
12
|
+
// ── The status matrix is loom's ───────────────────────────────────────────────
|
|
13
|
+
// Since Blueprint IR 2.0.0 (ADR 0097 D2) the eight-row decision table lives in loom's own
|
|
14
|
+
// traversal: each binding DECLARES its status channel (`status.<handle>`, beside the value's
|
|
15
|
+
// `values.<handle>` in the same projection state), and `renderBlueprintToString` applies the
|
|
16
|
+
// matrix. The framework contributes exactly one thing to it — what a bound VALUE must look
|
|
17
|
+
// like, which is catalogue vocabulary loom deliberately knows nothing about — through the
|
|
18
|
+
// `check` seam loom leaves open between `error` and `stale`.
|
|
19
|
+
|
|
20
|
+
/** The framework's per-kind value contract, in loom's `check` seam (between `error` and `stale`). */
|
|
21
|
+
export const kindValueCheck: WidgetValueCheck = kindValueCheckFor();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* {@link kindValueCheck} that also checks the host's own kinds against `contracts` (a catalogue's
|
|
25
|
+
* `valueContracts`). The framework's contract wins for a framework kind. Absent = framework only.
|
|
26
|
+
*/
|
|
27
|
+
export function kindValueCheckFor(contracts?: ContractRegistry): WidgetValueCheck {
|
|
28
|
+
return (widget, value) => {
|
|
29
|
+
const c = checkKindValue(widget.kind, value, contracts);
|
|
30
|
+
return c.ok ? undefined : { code: c.finding.code, message: c.finding.message };
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RenderExperienceOptions extends TerminalRegistryOptions {
|
|
35
|
+
/** The catalogue's `valueContracts`: bound values of the host's own kinds are checked against them. */
|
|
36
|
+
readonly contracts?: ContractRegistry;
|
|
37
|
+
/**
|
|
38
|
+
* Overrides the registry. Passing it together with `chrome` throws — `chrome` only configures the
|
|
39
|
+
* default registry; build yours with `terminalRegistry({ chrome })` instead.
|
|
40
|
+
* NOTE for `chrome: "host"`: the framework kinds' own renderings carry no title, but the rows
|
|
41
|
+
* loom's traversal itself writes — `✗`/`○`/`~`/`⋯` status lines and loom's eight built-in kinds —
|
|
42
|
+
* are loom's output and keep theirs. Drive a widget renderer directly for a fully title-free body.
|
|
43
|
+
*/
|
|
44
|
+
readonly registry?: RendererRegistry<TerminalWidgetRenderer>;
|
|
45
|
+
/**
|
|
46
|
+
* The current state of any projection the blueprint declares OTHER than the store's own
|
|
47
|
+
* hydration projection (a node bound through `SemanticNode.projection`): `(id) => state`,
|
|
48
|
+
* `undefined` for one the host has no state for (its widgets render as loading). Typically
|
|
49
|
+
* `(id) => runtime.getState(id, executionId)`. Never consulted for the hydration id — the store
|
|
50
|
+
* is that projection's only source here.
|
|
51
|
+
*/
|
|
52
|
+
readonly projectionState?: (projectionId: string) => unknown;
|
|
53
|
+
/**
|
|
54
|
+
* How `watch` hears that a non-hydration projection changed: called once, returns an
|
|
55
|
+
* unsubscribe. Without it `watch` re-renders on hydration changes only.
|
|
56
|
+
*/
|
|
57
|
+
readonly projectionSubscribe?: (onChange: () => void) => () => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Render the store's projection through loom's terminal renderer: loom's matrix, the framework's
|
|
62
|
+
* contract check.
|
|
63
|
+
*
|
|
64
|
+
* The state map is keyed by `HYDRATION_PROJECTION_ID` — the one id a `HydrationStore` publishes
|
|
65
|
+
* under. A blueprint compiled with any OTHER `hydrationProjectionId` therefore matches no state
|
|
66
|
+
* here: every binding resolves to an undefined value and an undefined status, so the surface
|
|
67
|
+
* renders as loading rows. That is the honest rendering of "this store holds nothing for that
|
|
68
|
+
* projection", not an error — pass the store's own id to `compile` and the rows fill.
|
|
69
|
+
*/
|
|
70
|
+
export function renderExperience(
|
|
71
|
+
blueprint: Blueprint,
|
|
72
|
+
store: HydrationStore,
|
|
73
|
+
opts: RenderExperienceOptions = {}
|
|
74
|
+
): string {
|
|
75
|
+
if (opts.registry !== undefined && opts.chrome !== undefined)
|
|
76
|
+
throw new Error(
|
|
77
|
+
"renderExperience: `chrome` configures the default registry and would be ignored next to `registry` — pass one, or build your registry with terminalRegistry({ chrome })"
|
|
78
|
+
);
|
|
79
|
+
const state = store.state();
|
|
80
|
+
const stateMap = new Map<string, unknown>();
|
|
81
|
+
// `HydrationStore` publishes under one well-known projection id; every other projection the
|
|
82
|
+
// blueprint declares is somebody else's, and has state here only if the host supplies it. A
|
|
83
|
+
// throwing supplier is "no state yet" (a loading row), never a throw out of a render.
|
|
84
|
+
for (const id of blueprint.projections) {
|
|
85
|
+
if (id === HYDRATION_PROJECTION_ID) stateMap.set(id, state);
|
|
86
|
+
else stateMap.set(id, readProjection(opts.projectionState, id));
|
|
87
|
+
}
|
|
88
|
+
return inert(
|
|
89
|
+
renderBlueprintToString(blueprint, stateMap, {
|
|
90
|
+
registry: opts.registry ?? terminalRegistry(opts),
|
|
91
|
+
check: opts.contracts === undefined ? kindValueCheck : kindValueCheckFor(opts.contracts),
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readProjection(read: RenderExperienceOptions["projectionState"], id: string): unknown {
|
|
97
|
+
if (read === undefined) return undefined;
|
|
98
|
+
try {
|
|
99
|
+
return read(id);
|
|
100
|
+
} catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
package/src/watch.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Blueprint } from "@loomwork/blueprint-runtime";
|
|
2
|
+
import type { HydrationStore } from "@loomwork/ui-hydration/client";
|
|
3
|
+
import { type RenderExperienceOptions, renderExperience } from "./status";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Render now and on every change to the store's projection (and, with `projectionSubscribe`, to
|
|
7
|
+
* any other projection the blueprint binds). Returns an unsubscribe.
|
|
8
|
+
*/
|
|
9
|
+
export function watch(
|
|
10
|
+
blueprint: Blueprint,
|
|
11
|
+
store: HydrationStore,
|
|
12
|
+
onFrame: (frame: string) => void,
|
|
13
|
+
opts: RenderExperienceOptions = {}
|
|
14
|
+
): () => void {
|
|
15
|
+
onFrame(renderExperience(blueprint, store, opts));
|
|
16
|
+
const frame = (): void => onFrame(renderExperience(blueprint, store, opts));
|
|
17
|
+
const stops = [store.runtimeSubscribe(frame)];
|
|
18
|
+
if (opts.projectionSubscribe !== undefined) stops.push(opts.projectionSubscribe(frame));
|
|
19
|
+
return () => {
|
|
20
|
+
for (const stop of stops) stop();
|
|
21
|
+
};
|
|
22
|
+
}
|