@xandreed/plugin-render 0.8.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/dist/domain/conformance.entity.d.ts +9 -0
  4. package/dist/domain/conformance.entity.js +7 -0
  5. package/dist/domain/feed-frame.entity.d.ts +63 -0
  6. package/dist/domain/feed-frame.entity.functions.d.ts +20 -0
  7. package/dist/domain/feed-frame.entity.functions.js +38 -0
  8. package/dist/domain/feed-frame.entity.js +21 -0
  9. package/dist/domain/render-output.entity.d.ts +51 -0
  10. package/dist/domain/render-output.entity.js +33 -0
  11. package/dist/domain/render-surface.entity.d.ts +164 -0
  12. package/dist/domain/render-surface.entity.functions.d.ts +22 -0
  13. package/dist/domain/render-surface.entity.functions.js +36 -0
  14. package/dist/domain/render-surface.entity.js +63 -0
  15. package/dist/index.d.ts +17 -0
  16. package/dist/index.js +15 -0
  17. package/dist/output.d.ts +5 -0
  18. package/dist/output.js +4 -0
  19. package/dist/plugin.adapter.d.ts +28 -0
  20. package/dist/plugin.adapter.js +27 -0
  21. package/dist/ports/render-output.port.d.ts +40 -0
  22. package/dist/ports/render-output.port.js +16 -0
  23. package/dist/ports/render.port.d.ts +86 -0
  24. package/dist/ports/render.port.js +13 -0
  25. package/dist/render-feed.adapter.d.ts +14 -0
  26. package/dist/render-feed.adapter.js +40 -0
  27. package/dist/render-output.adapter.d.ts +10 -0
  28. package/dist/render-output.adapter.js +28 -0
  29. package/dist/render-output.test.d.ts +1 -0
  30. package/dist/render-output.tools.d.ts +59 -0
  31. package/dist/render-output.tools.js +10 -0
  32. package/dist/render-store.memory.adapter.d.ts +69 -0
  33. package/dist/render-store.memory.adapter.js +47 -0
  34. package/dist/render-surface.adapter.d.ts +14 -0
  35. package/dist/render-surface.adapter.js +128 -0
  36. package/dist/render-surface.conformance.d.ts +18 -0
  37. package/dist/render-surface.conformance.js +173 -0
  38. package/dist/render-surface.test.d.ts +1 -0
  39. package/dist/render-tail.sessions.adapter.d.ts +12 -0
  40. package/dist/render-tail.sessions.adapter.js +15 -0
  41. package/dist/render-tail.sessions.test.d.ts +1 -0
  42. package/dist/render-transport.adapter.d.ts +15 -0
  43. package/dist/render-transport.adapter.js +27 -0
  44. package/dist/render-transport.conformance.d.ts +24 -0
  45. package/dist/render-transport.conformance.js +71 -0
  46. package/dist/render-transport.test.d.ts +1 -0
  47. package/package.json +34 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Xand Reed
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @xandreed/plugin-render
2
+
3
+ Efferent composable agent framework, 0.8.0-next.0. Bun on Linux.
4
+
5
+ Documentation: https://github.com/xandreeddev/efferent
@@ -0,0 +1,9 @@
1
+ import { Schema } from "effect";
2
+ declare const ConformanceFailure_base: Schema.Class<ConformanceFailure, Schema.TaggedStruct<"ConformanceFailure", {
3
+ readonly check: Schema.String;
4
+ readonly message: Schema.String;
5
+ }>, import("effect/Cause").YieldableError>;
6
+ /** A conformance check that did not hold, naming the check. */
7
+ export declare class ConformanceFailure extends ConformanceFailure_base {
8
+ }
9
+ export {};
@@ -0,0 +1,7 @@
1
+ import { Schema } from "effect";
2
+
3
+ export class ConformanceFailure extends Schema.TaggedError()("ConformanceFailure", {
4
+ check: Schema.String,
5
+ message: Schema.String
6
+ }) {
7
+ }
@@ -0,0 +1,63 @@
1
+ import { Schema } from "effect";
2
+ /** Whose journal a feed reads. The host authorizes the principal before opening one. */
3
+ export declare const FeedScope: Schema.Struct<{
4
+ readonly threadId: Schema.Trimmed;
5
+ readonly principalId: Schema.Trimmed;
6
+ }>;
7
+ export type FeedScope = typeof FeedScope.Type;
8
+ /** One durable journal record, as the host's tail returns it, in sequence order. */
9
+ export declare const JournalRecord: Schema.Struct<{
10
+ readonly sequence: Schema.Int;
11
+ readonly kind: Schema.Trimmed;
12
+ readonly data: Schema.$Record<Schema.String, Schema.Unknown>;
13
+ /** The turn the record belongs to, when the journal has turns (a session's does; null outside a turn). */
14
+ readonly turn: Schema.optional<Schema.NullOr<Schema.Int>>;
15
+ }>;
16
+ export type JournalRecord = typeof JournalRecord.Type;
17
+ /** What a projection makes of a record: the event name and payload a client receives. */
18
+ export declare const FeedPayload: Schema.Struct<{
19
+ readonly event: Schema.Trimmed;
20
+ readonly data: Schema.$Record<Schema.String, Schema.Unknown>;
21
+ }>;
22
+ export type FeedPayload = typeof FeedPayload.Type;
23
+ export declare const FeedRecord: Schema.TaggedStruct<"FeedRecord", {
24
+ readonly sequence: Schema.Int;
25
+ readonly event: Schema.Trimmed;
26
+ readonly data: Schema.$Record<Schema.String, Schema.Unknown>;
27
+ }>;
28
+ /** Sent once, after the first poll finds nothing new: the client has caught up. */
29
+ export declare const FeedReady: Schema.TaggedStruct<"FeedReady", {}>;
30
+ /** Sent when the feed has been idle for the heartbeat interval. */
31
+ export declare const FeedHeartbeat: Schema.TaggedStruct<"FeedHeartbeat", {}>;
32
+ export declare const FeedFrame: Schema.Union<readonly [Schema.TaggedStruct<"FeedRecord", {
33
+ readonly sequence: Schema.Int;
34
+ readonly event: Schema.Trimmed;
35
+ readonly data: Schema.$Record<Schema.String, Schema.Unknown>;
36
+ }>, Schema.TaggedStruct<"FeedReady", {}>, Schema.TaggedStruct<"FeedHeartbeat", {}>]>;
37
+ export type FeedFrame = typeof FeedFrame.Type;
38
+ export declare const FeedOptions: Schema.Struct<{
39
+ /** Poll interval after new records. */
40
+ readonly pollMs: Schema.Int;
41
+ /** Upper bound the idle interval backs off to. */
42
+ readonly maxPollMs: Schema.Int;
43
+ readonly heartbeatMs: Schema.Int;
44
+ /** The feed ends after this long; clients reconnect with their cursor. */
45
+ readonly maxDurationMs: Schema.Int;
46
+ }>;
47
+ export type FeedOptions = typeof FeedOptions.Type;
48
+ /** WebSocket text frames: one JSON object per frame. */
49
+ export declare const SocketFrame: Schema.Union<readonly [Schema.Struct<{
50
+ readonly type: Schema.Literal<"record">;
51
+ readonly sequence: Schema.Int;
52
+ readonly event: Schema.Trimmed;
53
+ readonly data: Schema.$Record<Schema.String, Schema.Unknown>;
54
+ }>, Schema.Struct<{
55
+ readonly type: Schema.Literal<"ready">;
56
+ }>, Schema.Struct<{
57
+ readonly type: Schema.Literal<"heartbeat">;
58
+ }>]>;
59
+ export type SocketFrame = typeof SocketFrame.Type;
60
+ /** The first message a WebSocket client may send to resume after a cursor. */
61
+ export declare const SocketResume: Schema.Struct<{
62
+ readonly after: Schema.Int;
63
+ }>;
@@ -0,0 +1,20 @@
1
+ import { Option } from "effect";
2
+ import type { FeedFrame, FeedOptions } from "./feed-frame.entity.js";
3
+ /**
4
+ * SSE wire format, one block per frame:
5
+ * - once per connection: `retry: 1000`
6
+ * - record: `id: <sequence>` `event: <event>` `data: <json>`
7
+ * - ready: `event: ready` `data: {}`
8
+ * - heartbeat: `: heartbeat` (a comment)
9
+ * Clients resume by sending `Last-Event-ID: <sequence>`.
10
+ */
11
+ export declare const ssePreamble = "retry: 1000\n\n";
12
+ export declare const encodeSse: (frame: FeedFrame) => string;
13
+ /** The inverse of `encodeSse` over a whole stream, for clients and tests. */
14
+ export declare const decodeSse: (text: string) => ReadonlyArray<FeedFrame>;
15
+ export declare const encodeSocketFrame: (frame: FeedFrame) => string;
16
+ export declare const decodeSocketFrame: (text: string) => Option.Option<FeedFrame>;
17
+ /** The cursor a WebSocket client asks to resume after (`{"after": n}`). */
18
+ export declare const socketResumeOf: (text: string) => Option.Option<number>;
19
+ /** Back off while idle; poll promptly again once records arrive. */
20
+ export declare const nextPollMs: (current: number, options: FeedOptions, received: boolean) => number;
@@ -0,0 +1,38 @@
1
+ import { Result, Match, Option, Schema } from "effect";
2
+ import { FeedHeartbeat, FeedReady, FeedRecord, SocketFrame, SocketResume } from "./feed-frame.entity.js";
3
+ export const ssePreamble = `retry: 1000
4
+
5
+ `, encodeSse = (frame) => Match.valueTags(frame, {
6
+ FeedRecord: (record) => `id: ${record.sequence}
7
+ event: ${record.event}
8
+ data: ${JSON.stringify(record.data)}
9
+
10
+ `,
11
+ FeedReady: () => `event: ready
12
+ data: {}
13
+
14
+ `,
15
+ FeedHeartbeat: () => `: heartbeat
16
+
17
+ `
18
+ });
19
+ const fieldsOf = (block) => new Map(block.split(`
20
+ `).flatMap((line) => {
21
+ const colon = line.indexOf(":");
22
+ return colon <= 0 ? [] : [[line.slice(0, colon), line.slice(colon + 1).trimStart()]];
23
+ })), parseObject = (text) => Result.getSuccess(Schema.decodeUnknownResult(Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown)))(text)), decodeSseBlock = (block) => {
24
+ if (block.startsWith(": heartbeat"))
25
+ return [FeedHeartbeat.make({})];
26
+ const fields = fieldsOf(block), event = fields.get("event");
27
+ if (event === "ready")
28
+ return [FeedReady.make({})];
29
+ const id = Number(fields.get("id")), data = parseObject(fields.get("data") ?? "");
30
+ return event === void 0 || !Number.isInteger(id) || Option.isNone(data) ? [] : [FeedRecord.make({ sequence: id, event, data: data.value })];
31
+ };
32
+ export const decodeSse = (text) => text.split(`
33
+
34
+ `).filter((block) => block.length > 0).flatMap(decodeSseBlock), encodeSocketFrame = (frame) => JSON.stringify(Match.valueTags(frame, {
35
+ FeedRecord: (record) => ({ type: "record", sequence: record.sequence, event: record.event, data: record.data }),
36
+ FeedReady: () => ({ type: "ready" }),
37
+ FeedHeartbeat: () => ({ type: "heartbeat" })
38
+ })), decodeSocketFrame = (text) => Result.getSuccess(Schema.decodeUnknownResult(Schema.fromJsonString(SocketFrame))(text)).pipe(Option.map((frame) => Match.value(frame).pipe(Match.when({ type: "record" }, (record) => FeedRecord.make({ sequence: record.sequence, event: record.event, data: record.data })), Match.when({ type: "ready" }, () => FeedReady.make({})), Match.orElse(() => FeedHeartbeat.make({}))))), socketResumeOf = (text) => Result.getSuccess(Schema.decodeUnknownResult(Schema.fromJsonString(SocketResume))(text)).pipe(Option.map((resume) => resume.after)), nextPollMs = (current, options, received) => received ? options.pollMs : Math.min(options.maxPollMs, Math.max(options.pollMs, current * 2));
@@ -0,0 +1,21 @@
1
+ import { Schema } from "effect";
2
+ const Id = Schema.Trimmed.check(Schema.isNonEmpty()), Json = Schema.Record(Schema.String, Schema.Unknown);
3
+ export const FeedScope = Schema.Struct({ threadId: Id, principalId: Id }), JournalRecord = Schema.Struct({
4
+ sequence: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0))),
5
+ kind: Id,
6
+ data: Json,
7
+ turn: Schema.optional(Schema.NullOr(Schema.Int))
8
+ }), FeedPayload = Schema.Struct({ event: Id, data: Json }), FeedRecord = Schema.TaggedStruct("FeedRecord", {
9
+ sequence: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0))),
10
+ event: Id,
11
+ data: Json
12
+ }), FeedReady = Schema.TaggedStruct("FeedReady", {}), FeedHeartbeat = Schema.TaggedStruct("FeedHeartbeat", {}), FeedFrame = Schema.Union([FeedRecord, FeedReady, FeedHeartbeat]), FeedOptions = Schema.Struct({
13
+ pollMs: Schema.Int.pipe(Schema.check(Schema.isGreaterThan(0))),
14
+ maxPollMs: Schema.Int.pipe(Schema.check(Schema.isGreaterThan(0))),
15
+ heartbeatMs: Schema.Int.pipe(Schema.check(Schema.isGreaterThan(0))),
16
+ maxDurationMs: Schema.Int.pipe(Schema.check(Schema.isGreaterThan(0)))
17
+ }), SocketFrame = Schema.Union([
18
+ Schema.Struct({ type: Schema.Literal("record"), sequence: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0))), event: Id, data: Json }),
19
+ Schema.Struct({ type: Schema.Literal("ready") }),
20
+ Schema.Struct({ type: Schema.Literal("heartbeat") })
21
+ ]), SocketResume = Schema.Struct({ after: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0))) });
@@ -0,0 +1,51 @@
1
+ import { Schema } from "effect";
2
+ /** Immutable references: a release is never resolved through a latest alias. */
3
+ export declare const UiRelease: Schema.Struct<{
4
+ readonly component: Schema.Trimmed;
5
+ readonly version: Schema.Trimmed;
6
+ readonly definitionHash: Schema.String;
7
+ readonly rendererRelease: Schema.Trimmed;
8
+ readonly tokensHash: Schema.String;
9
+ readonly layoutVersion: Schema.Trimmed;
10
+ }>;
11
+ export type UiRelease = typeof UiRelease.Type;
12
+ /** IDs of facts/artifacts are resolved by the host, never executable content. */
13
+ export declare const UiOutputProposal: Schema.Struct<{
14
+ readonly operationId: Schema.Trimmed;
15
+ readonly nodeId: Schema.Trimmed;
16
+ readonly release: Schema.Struct<{
17
+ readonly component: Schema.Trimmed;
18
+ readonly version: Schema.Trimmed;
19
+ readonly definitionHash: Schema.String;
20
+ readonly rendererRelease: Schema.Trimmed;
21
+ readonly tokensHash: Schema.String;
22
+ readonly layoutVersion: Schema.Trimmed;
23
+ }>;
24
+ readonly props: Schema.$Record<Schema.String, Schema.Unknown>;
25
+ readonly evidence: Schema.$Array<Schema.Trimmed>;
26
+ }>;
27
+ export type UiOutputProposal = typeof UiOutputProposal.Type;
28
+ /** The host supplies these fields; the model cannot choose another principal/run. */
29
+ export declare const UiOutputScope: Schema.Struct<{
30
+ readonly threadId: Schema.Trimmed;
31
+ readonly runId: Schema.Trimmed;
32
+ readonly messageId: Schema.Trimmed;
33
+ readonly principalId: Schema.Trimmed;
34
+ readonly fence: Schema.Int;
35
+ }>;
36
+ export type UiOutputScope = typeof UiOutputScope.Type;
37
+ export declare const UiOutputReceipt: Schema.Struct<{
38
+ readonly threadId: Schema.Trimmed;
39
+ readonly messageId: Schema.Trimmed;
40
+ readonly revisionId: Schema.Trimmed;
41
+ readonly sequence: Schema.Int;
42
+ readonly nodeId: Schema.Trimmed;
43
+ }>;
44
+ export type UiOutputReceipt = typeof UiOutputReceipt.Type;
45
+ declare const UiOutputError_base: Schema.Class<UiOutputError, Schema.TaggedStruct<"UiOutputError", {
46
+ readonly code: Schema.Literals<readonly ["invalid", "forbidden", "unavailable", "conflict", "storage"]>;
47
+ readonly message: Schema.String;
48
+ }>, import("effect/Cause").YieldableError>;
49
+ export declare class UiOutputError extends UiOutputError_base {
50
+ }
51
+ export {};
@@ -0,0 +1,33 @@
1
+ import { Schema } from "effect";
2
+ export const UiRelease = Schema.Struct({
3
+ component: Schema.Trimmed.check(Schema.isNonEmpty()),
4
+ version: Schema.Trimmed.check(Schema.isNonEmpty()),
5
+ definitionHash: Schema.String.pipe(Schema.check(Schema.isPattern(/^[a-f0-9]{64}$/))),
6
+ rendererRelease: Schema.Trimmed.check(Schema.isNonEmpty()),
7
+ tokensHash: Schema.String.pipe(Schema.check(Schema.isPattern(/^[a-f0-9]{64}$/))),
8
+ layoutVersion: Schema.Trimmed.check(Schema.isNonEmpty())
9
+ }), UiOutputProposal = Schema.Struct({
10
+ operationId: Schema.Trimmed.check(Schema.isNonEmpty()),
11
+ nodeId: Schema.Trimmed.check(Schema.isNonEmpty()),
12
+ release: UiRelease,
13
+ props: Schema.Record(Schema.String, Schema.Unknown),
14
+ evidence: Schema.Array(Schema.Trimmed.check(Schema.isNonEmpty()))
15
+ }), UiOutputScope = Schema.Struct({
16
+ threadId: Schema.Trimmed.check(Schema.isNonEmpty()),
17
+ runId: Schema.Trimmed.check(Schema.isNonEmpty()),
18
+ messageId: Schema.Trimmed.check(Schema.isNonEmpty()),
19
+ principalId: Schema.Trimmed.check(Schema.isNonEmpty()),
20
+ fence: Schema.Int.pipe(Schema.check(Schema.isGreaterThan(0)))
21
+ }), UiOutputReceipt = Schema.Struct({
22
+ threadId: Schema.Trimmed.check(Schema.isNonEmpty()),
23
+ messageId: Schema.Trimmed.check(Schema.isNonEmpty()),
24
+ revisionId: Schema.Trimmed.check(Schema.isNonEmpty()),
25
+ sequence: Schema.Int.pipe(Schema.check(Schema.isGreaterThan(0))),
26
+ nodeId: Schema.Trimmed.check(Schema.isNonEmpty())
27
+ });
28
+
29
+ export class UiOutputError extends Schema.TaggedError()("UiOutputError", {
30
+ code: Schema.Literals(["invalid", "forbidden", "unavailable", "conflict", "storage"]),
31
+ message: Schema.String
32
+ }) {
33
+ }
@@ -0,0 +1,164 @@
1
+ import { Schema } from "effect";
2
+ /** One addressable visual region a host renders into (a page, a canvas, a panel). */
3
+ export declare const SurfaceScope: Schema.Struct<{
4
+ readonly surfaceId: Schema.Trimmed;
5
+ /** The version this render builds on, e.g. the page the user was looking at. */
6
+ readonly baseVersion: Schema.OptionFromNullOr<Schema.String>;
7
+ readonly threadId: Schema.Trimmed;
8
+ readonly runId: Schema.Trimmed;
9
+ readonly messageId: Schema.Trimmed;
10
+ readonly principalId: Schema.Trimmed;
11
+ readonly fence: Schema.Int;
12
+ }>;
13
+ export type SurfaceScope = typeof SurfaceScope.Type;
14
+ /** One node of a snapshot: an exact component release, JSON props and the evidence ids behind them. */
15
+ export declare const RenderNode: Schema.Struct<{
16
+ readonly nodeId: Schema.Trimmed;
17
+ readonly release: Schema.Struct<{
18
+ readonly component: Schema.Trimmed;
19
+ readonly version: Schema.Trimmed;
20
+ readonly definitionHash: Schema.String;
21
+ readonly rendererRelease: Schema.Trimmed;
22
+ readonly tokensHash: Schema.String;
23
+ readonly layoutVersion: Schema.Trimmed;
24
+ }>;
25
+ readonly props: Schema.$Record<Schema.String, Schema.Unknown>;
26
+ readonly evidence: Schema.$Array<Schema.Trimmed>;
27
+ }>;
28
+ export type RenderNode = typeof RenderNode.Type;
29
+ /**
30
+ * The host's whole visual representation at one moment. `spec` is opaque JSON
31
+ * (a layout, a tree, sections); `nodes` are committed one by one; placeholders
32
+ * are node ids a later `fill` may commit even after the surface is frozen.
33
+ */
34
+ export declare const RenderSnapshot: Schema.Struct<{
35
+ readonly phase: Schema.Literals<readonly ["partial", "complete"]>;
36
+ readonly spec: Schema.$Record<Schema.String, Schema.Unknown>;
37
+ readonly nodes: Schema.$Array<Schema.Struct<{
38
+ readonly nodeId: Schema.Trimmed;
39
+ readonly release: Schema.Struct<{
40
+ readonly component: Schema.Trimmed;
41
+ readonly version: Schema.Trimmed;
42
+ readonly definitionHash: Schema.String;
43
+ readonly rendererRelease: Schema.Trimmed;
44
+ readonly tokensHash: Schema.String;
45
+ readonly layoutVersion: Schema.Trimmed;
46
+ }>;
47
+ readonly props: Schema.$Record<Schema.String, Schema.Unknown>;
48
+ readonly evidence: Schema.$Array<Schema.Trimmed>;
49
+ }>>;
50
+ readonly placeholders: Schema.withConstructorDefault<Schema.withDecodingDefaultType<Schema.$Array<Schema.Trimmed>, never>>;
51
+ }>;
52
+ export type RenderSnapshot = typeof RenderSnapshot.Type;
53
+ export type RenderSnapshotInput = typeof RenderSnapshot.Encoded;
54
+ export declare const FreezeReason: Schema.Literals<readonly ["interaction", "completion"]>;
55
+ export type FreezeReason = typeof FreezeReason.Type;
56
+ /** What a store persists and hydrates. Every record names its surface and the message it was rendered for. */
57
+ export declare const SurfacePlanned: Schema.TaggedStruct<"SurfacePlanned", {
58
+ readonly surfaceId: Schema.Trimmed;
59
+ readonly messageId: Schema.Trimmed;
60
+ readonly versionId: Schema.Trimmed;
61
+ readonly generation: Schema.Int;
62
+ readonly baseVersionId: Schema.OptionFromNullOr<Schema.String>;
63
+ readonly phase: Schema.Literals<readonly ["partial", "complete"]>;
64
+ readonly spec: Schema.$Record<Schema.String, Schema.Unknown>;
65
+ readonly nodes: Schema.$Array<Schema.Trimmed>;
66
+ readonly placeholders: Schema.$Array<Schema.Trimmed>;
67
+ readonly signature: Schema.String;
68
+ }>;
69
+ export type SurfacePlanned = typeof SurfacePlanned.Type;
70
+ export declare const SurfaceCommitted: Schema.TaggedStruct<"SurfaceCommitted", {
71
+ readonly surfaceId: Schema.Trimmed;
72
+ readonly messageId: Schema.Trimmed;
73
+ readonly versionId: Schema.Trimmed;
74
+ readonly nodeId: Schema.Trimmed;
75
+ readonly component: Schema.Trimmed;
76
+ readonly receipt: Schema.Struct<{
77
+ readonly threadId: Schema.Trimmed;
78
+ readonly messageId: Schema.Trimmed;
79
+ readonly revisionId: Schema.Trimmed;
80
+ readonly sequence: Schema.Int;
81
+ readonly nodeId: Schema.Trimmed;
82
+ }>;
83
+ }>;
84
+ export type SurfaceCommitted = typeof SurfaceCommitted.Type;
85
+ export declare const SurfaceCompleted: Schema.TaggedStruct<"SurfaceCompleted", {
86
+ readonly surfaceId: Schema.Trimmed;
87
+ readonly messageId: Schema.Trimmed;
88
+ readonly versionId: Schema.Trimmed;
89
+ readonly generation: Schema.Int;
90
+ readonly phase: Schema.Literals<readonly ["partial", "complete"]>;
91
+ readonly frozen: Schema.Boolean;
92
+ }>;
93
+ export type SurfaceCompleted = typeof SurfaceCompleted.Type;
94
+ export declare const SurfaceFrozen: Schema.TaggedStruct<"SurfaceFrozen", {
95
+ readonly surfaceId: Schema.Trimmed;
96
+ readonly messageId: Schema.Trimmed;
97
+ readonly versionId: Schema.Trimmed;
98
+ readonly reason: Schema.Literals<readonly ["interaction", "completion"]>;
99
+ }>;
100
+ export type SurfaceFrozen = typeof SurfaceFrozen.Type;
101
+ export declare const SurfaceRecord: Schema.Union<readonly [Schema.TaggedStruct<"SurfacePlanned", {
102
+ readonly surfaceId: Schema.Trimmed;
103
+ readonly messageId: Schema.Trimmed;
104
+ readonly versionId: Schema.Trimmed;
105
+ readonly generation: Schema.Int;
106
+ readonly baseVersionId: Schema.OptionFromNullOr<Schema.String>;
107
+ readonly phase: Schema.Literals<readonly ["partial", "complete"]>;
108
+ readonly spec: Schema.$Record<Schema.String, Schema.Unknown>;
109
+ readonly nodes: Schema.$Array<Schema.Trimmed>;
110
+ readonly placeholders: Schema.$Array<Schema.Trimmed>;
111
+ readonly signature: Schema.String;
112
+ }>, Schema.TaggedStruct<"SurfaceCommitted", {
113
+ readonly surfaceId: Schema.Trimmed;
114
+ readonly messageId: Schema.Trimmed;
115
+ readonly versionId: Schema.Trimmed;
116
+ readonly nodeId: Schema.Trimmed;
117
+ readonly component: Schema.Trimmed;
118
+ readonly receipt: Schema.Struct<{
119
+ readonly threadId: Schema.Trimmed;
120
+ readonly messageId: Schema.Trimmed;
121
+ readonly revisionId: Schema.Trimmed;
122
+ readonly sequence: Schema.Int;
123
+ readonly nodeId: Schema.Trimmed;
124
+ }>;
125
+ }>, Schema.TaggedStruct<"SurfaceCompleted", {
126
+ readonly surfaceId: Schema.Trimmed;
127
+ readonly messageId: Schema.Trimmed;
128
+ readonly versionId: Schema.Trimmed;
129
+ readonly generation: Schema.Int;
130
+ readonly phase: Schema.Literals<readonly ["partial", "complete"]>;
131
+ readonly frozen: Schema.Boolean;
132
+ }>, Schema.TaggedStruct<"SurfaceFrozen", {
133
+ readonly surfaceId: Schema.Trimmed;
134
+ readonly messageId: Schema.Trimmed;
135
+ readonly versionId: Schema.Trimmed;
136
+ readonly reason: Schema.Literals<readonly ["interaction", "completion"]>;
137
+ }>]>;
138
+ export type SurfaceRecord = typeof SurfaceRecord.Type;
139
+ /** The current message's view of a surface. `frozen` is advisory: the store's guard is authoritative. */
140
+ export declare const SurfaceState: Schema.Struct<{
141
+ readonly surfaceId: Schema.Trimmed;
142
+ readonly version: Schema.OptionFromNullOr<Schema.String>;
143
+ readonly generation: Schema.Int;
144
+ readonly frozen: Schema.Boolean;
145
+ readonly completed: Schema.Boolean;
146
+ readonly components: Schema.$Array<Schema.String>;
147
+ readonly placeholders: Schema.$Array<Schema.String>;
148
+ readonly pending: Schema.Boolean;
149
+ }>;
150
+ export type SurfaceState = typeof SurfaceState.Type;
151
+ export declare const PublishResult: Schema.Struct<{
152
+ readonly version: Schema.OptionFromNullOr<Schema.String>;
153
+ readonly generation: Schema.Int;
154
+ readonly changed: Schema.Boolean;
155
+ readonly frozen: Schema.Boolean;
156
+ }>;
157
+ export type PublishResult = typeof PublishResult.Type;
158
+ declare const RenderError_base: Schema.Class<RenderError, Schema.TaggedStruct<"RenderError", {
159
+ readonly code: Schema.Literals<readonly ["invalid", "frozen", "conflict", "forbidden", "storage", "unavailable"]>;
160
+ readonly message: Schema.String;
161
+ }>, import("effect/Cause").YieldableError>;
162
+ export declare class RenderError extends RenderError_base {
163
+ }
164
+ export {};
@@ -0,0 +1,22 @@
1
+ import { Option } from "effect";
2
+ import type { UiOutputError, UiOutputProposal, UiOutputScope } from "./render-output.entity.js";
3
+ import { RenderError } from "./render-surface.entity.js";
4
+ import type { RenderNode, RenderSnapshot, SurfaceRecord, SurfaceScope, SurfaceState } from "./render-surface.entity.js";
5
+ /** A surface's durable progress for the current message, rebuilt from its records. */
6
+ export type SurfaceProgress = {
7
+ readonly state: Omit<SurfaceState, "pending">;
8
+ readonly signature: Option.Option<string>;
9
+ readonly frozenRecorded: boolean;
10
+ };
11
+ /** Identical content has an identical signature, whatever the phase or key order. */
12
+ export declare const signatureOf: (snapshot: RenderSnapshot) => string;
13
+ export declare const versionIdOf: (messageId: string, generation: number) => string;
14
+ /** Stable per generation and node: a retried publish reuses the id, a new generation never collides. */
15
+ export declare const operationIdOf: (runId: string, generation: number, nodeId: string) => string;
16
+ export declare const fillOperationIdOf: (runId: string, placeholder: string) => string;
17
+ export declare const outputScopeOf: (scope: SurfaceScope) => UiOutputScope;
18
+ export declare const proposalOf: (operationId: string, node: RenderNode) => UiOutputProposal;
19
+ export declare const fromOutputError: (error: UiOutputError) => RenderError;
20
+ /** Fold the records of one surface into the current message's progress. */
21
+ export declare const progressOf: (surfaceId: string, messageId: string, records: ReadonlyArray<SurfaceRecord>) => SurfaceProgress;
22
+ export declare const describeError: (error: unknown) => string;
@@ -0,0 +1,36 @@
1
+ import { Option, Schema } from "effect";
2
+ import { canonicalJson, fingerprintOf } from "@xandreed/core";
3
+ import { RenderError, SurfaceCommitted, SurfaceCompleted, SurfaceFrozen, SurfacePlanned } from "./render-surface.entity.js";
4
+ export const signatureOf = (snapshot) => fingerprintOf(canonicalJson({ spec: snapshot.spec, nodes: snapshot.nodes, placeholders: snapshot.placeholders })), versionIdOf = (messageId, generation) => `${messageId}:v${generation}`, operationIdOf = (runId, generation, nodeId) => `${runId}:render:${generation}:${nodeId}`, fillOperationIdOf = (runId, placeholder) => `${runId}:render:fill:${placeholder}`, outputScopeOf = (scope) => ({
5
+ threadId: scope.threadId,
6
+ runId: scope.runId,
7
+ messageId: scope.messageId,
8
+ principalId: scope.principalId,
9
+ fence: scope.fence
10
+ }), proposalOf = (operationId, node) => ({
11
+ operationId,
12
+ nodeId: node.nodeId,
13
+ release: node.release,
14
+ props: node.props,
15
+ evidence: node.evidence
16
+ }), fromOutputError = (error) => new RenderError({ code: error.code, message: error.message });
17
+ const isPlanned = Schema.is(SurfacePlanned), isCommitted = Schema.is(SurfaceCommitted), isCompleted = Schema.is(SurfaceCompleted), isFrozen = Schema.is(SurfaceFrozen), latestPlanned = (records) => records.reduce((best, record) => isPlanned(record) && Option.match(best, {
18
+ onNone: () => !0,
19
+ onSome: (current) => record.generation > current.generation
20
+ }) ? Option.some(record) : best, Option.none());
21
+ export const progressOf = (surfaceId, messageId, records) => {
22
+ const mine = records.filter((record) => record.surfaceId === surfaceId && record.messageId === messageId), latest = latestPlanned(mine), version = Option.map(latest, (planned) => planned.versionId), ofVersion = (versionId) => mine.filter((record) => record.versionId === versionId), current = Option.match(version, { onNone: () => [], onSome: ofVersion });
23
+ return {
24
+ state: {
25
+ surfaceId,
26
+ version,
27
+ generation: Option.match(latest, { onNone: () => 0, onSome: (planned) => planned.generation }),
28
+ frozen: mine.some(isFrozen),
29
+ completed: current.some((record) => isCompleted(record) && !record.frozen),
30
+ components: [...new Set(current.flatMap((record) => isCommitted(record) ? [record.component] : []))],
31
+ placeholders: Option.match(latest, { onNone: () => [], onSome: (planned) => planned.placeholders })
32
+ },
33
+ signature: Option.map(latest, (planned) => planned.signature),
34
+ frozenRecorded: mine.some((record) => isCompleted(record) && record.frozen)
35
+ };
36
+ }, describeError = (error) => typeof error === "object" && error !== null && ("message" in error) && typeof error.message === "string" ? error.message : String(error);
@@ -0,0 +1,63 @@
1
+ import { Schema, Effect } from "effect";
2
+ import { UiOutputReceipt, UiOutputScope, UiRelease } from "./render-output.entity.js";
3
+ const Id = Schema.Trimmed.check(Schema.isNonEmpty()), Json = Schema.Record(Schema.String, Schema.Unknown), Generation = Schema.Int.pipe(Schema.check(Schema.isGreaterThan(0)));
4
+ export const SurfaceScope = Schema.Struct({
5
+ ...UiOutputScope.fields,
6
+ surfaceId: Id,
7
+ baseVersion: Schema.OptionFromNullOr(Schema.String)
8
+ }), RenderNode = Schema.Struct({ nodeId: Id, release: UiRelease, props: Json, evidence: Schema.Array(Id) }), RenderSnapshot = Schema.Struct({
9
+ phase: Schema.Literals(["partial", "complete"]),
10
+ spec: Json,
11
+ nodes: Schema.Array(RenderNode),
12
+ placeholders: Schema.Array(Id).pipe(Schema.withDecodingDefaultType(Effect.sync(() => [])), Schema.withConstructorDefault(Effect.sync(() => [])))
13
+ }), FreezeReason = Schema.Literals(["interaction", "completion"]), SurfacePlanned = Schema.TaggedStruct("SurfacePlanned", {
14
+ surfaceId: Id,
15
+ messageId: Id,
16
+ versionId: Id,
17
+ generation: Generation,
18
+ baseVersionId: Schema.OptionFromNullOr(Schema.String),
19
+ phase: Schema.Literals(["partial", "complete"]),
20
+ spec: Json,
21
+ nodes: Schema.Array(Id),
22
+ placeholders: Schema.Array(Id),
23
+ signature: Schema.String
24
+ }), SurfaceCommitted = Schema.TaggedStruct("SurfaceCommitted", {
25
+ surfaceId: Id,
26
+ messageId: Id,
27
+ versionId: Id,
28
+ nodeId: Id,
29
+ component: Id,
30
+ receipt: UiOutputReceipt
31
+ }), SurfaceCompleted = Schema.TaggedStruct("SurfaceCompleted", {
32
+ surfaceId: Id,
33
+ messageId: Id,
34
+ versionId: Id,
35
+ generation: Generation,
36
+ phase: Schema.Literals(["partial", "complete"]),
37
+ frozen: Schema.Boolean
38
+ }), SurfaceFrozen = Schema.TaggedStruct("SurfaceFrozen", {
39
+ surfaceId: Id,
40
+ messageId: Id,
41
+ versionId: Id,
42
+ reason: FreezeReason
43
+ }), SurfaceRecord = Schema.Union([SurfacePlanned, SurfaceCommitted, SurfaceCompleted, SurfaceFrozen]), SurfaceState = Schema.Struct({
44
+ surfaceId: Id,
45
+ version: Schema.OptionFromNullOr(Schema.String),
46
+ generation: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0))),
47
+ frozen: Schema.Boolean,
48
+ completed: Schema.Boolean,
49
+ components: Schema.Array(Schema.String),
50
+ placeholders: Schema.Array(Schema.String),
51
+ pending: Schema.Boolean
52
+ }), PublishResult = Schema.Struct({
53
+ version: Schema.OptionFromNullOr(Schema.String),
54
+ generation: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0))),
55
+ changed: Schema.Boolean,
56
+ frozen: Schema.Boolean
57
+ });
58
+
59
+ export class RenderError extends Schema.TaggedError()("RenderError", {
60
+ code: Schema.Literals(["invalid", "frozen", "conflict", "forbidden", "storage", "unavailable"]),
61
+ message: Schema.String
62
+ }) {
63
+ }
@@ -0,0 +1,17 @@
1
+ export * from "./output.js";
2
+ export * from "./domain/render-surface.entity.js";
3
+ export * from "./domain/render-surface.entity.functions.js";
4
+ export * from "./domain/feed-frame.entity.js";
5
+ export * from "./domain/feed-frame.entity.functions.js";
6
+ export * from "./domain/conformance.entity.js";
7
+ export * from "./ports/render.port.js";
8
+ export { openSurface, RenderLive } from "./render-surface.adapter.js";
9
+ export { makeMemoryRenderStore, MemoryRenderStoreLive } from "./render-store.memory.adapter.js";
10
+ export type { MemoryAnnotation } from "./render-store.memory.adapter.js";
11
+ export { makeRenderFeed, RenderFeedLive } from "./render-feed.adapter.js";
12
+ export { SessionsJournalTailLive } from "./render-tail.sessions.adapter.js";
13
+ export { SseTransport, sseHeaders, sseNodePeer, WebSocketTransport } from "./render-transport.adapter.js";
14
+ export { renderFeedDefaults, renderFeedPlugin, renderSurfaceDefaults, renderSurfacePlugin } from "./plugin.adapter.js";
15
+ export { renderSurfaceConformance } from "./render-surface.conformance.js";
16
+ export type { ConformanceCheck, SurfaceSubject } from "./render-surface.conformance.js";
17
+ export { makeMemoryJournal, renderFeedConformance, renderTransportConformance } from "./render-transport.conformance.js";
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ export * from "./output.js";
2
+ export * from "./domain/render-surface.entity.js";
3
+ export * from "./domain/render-surface.entity.functions.js";
4
+ export * from "./domain/feed-frame.entity.js";
5
+ export * from "./domain/feed-frame.entity.functions.js";
6
+ export * from "./domain/conformance.entity.js";
7
+ export * from "./ports/render.port.js";
8
+ export { openSurface, RenderLive } from "./render-surface.adapter.js";
9
+ export { makeMemoryRenderStore, MemoryRenderStoreLive } from "./render-store.memory.adapter.js";
10
+ export { makeRenderFeed, RenderFeedLive } from "./render-feed.adapter.js";
11
+ export { SessionsJournalTailLive } from "./render-tail.sessions.adapter.js";
12
+ export { SseTransport, sseHeaders, sseNodePeer, WebSocketTransport } from "./render-transport.adapter.js";
13
+ export { renderFeedDefaults, renderFeedPlugin, renderSurfaceDefaults, renderSurfacePlugin } from "./plugin.adapter.js";
14
+ export { renderSurfaceConformance } from "./render-surface.conformance.js";
15
+ export { makeMemoryJournal, renderFeedConformance, renderTransportConformance } from "./render-transport.conformance.js";
@@ -0,0 +1,5 @@
1
+ /** The per-component output layer: exact releases, admission, durable commit and the render_component tool. */
2
+ export * from "./domain/render-output.entity.js";
3
+ export * from "./ports/render-output.port.js";
4
+ export * from "./render-output.tools.js";
5
+ export * from "./render-output.adapter.js";
package/dist/output.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./domain/render-output.entity.js";
2
+ export * from "./ports/render-output.port.js";
3
+ export * from "./render-output.tools.js";
4
+ export * from "./render-output.adapter.js";
@@ -0,0 +1,28 @@
1
+ import { Schema } from "effect";
2
+ import { FeedOptions } from "./domain/feed-frame.entity.js";
3
+ import { UiOutputAdmission } from "./ports/render-output.port.js";
4
+ import { JournalTail, Render, RenderFeed, RenderStore } from "./ports/render.port.js";
5
+ declare const SurfaceConfig: Schema.Struct<{
6
+ readonly maxBytes: Schema.Int;
7
+ }>;
8
+ export declare const renderSurfaceDefaults: typeof SurfaceConfig.Type;
9
+ /** Surfaces over the host's RenderStore, with the host's admission rules. */
10
+ export declare const renderSurfacePlugin: import("@xandreed/core").TypedPlugin<{
11
+ readonly maxBytes: number;
12
+ }, {
13
+ readonly maxBytes: number;
14
+ }, Render, never, UiOutputAdmission | RenderStore>;
15
+ export declare const renderFeedDefaults: typeof FeedOptions.Type;
16
+ /** The journal as client frames; pair it with SseTransport or WebSocketTransport at the host's HTTP edge. */
17
+ export declare const renderFeedPlugin: import("@xandreed/core").TypedPlugin<{
18
+ readonly pollMs: number;
19
+ readonly maxPollMs: number;
20
+ readonly heartbeatMs: number;
21
+ readonly maxDurationMs: number;
22
+ }, {
23
+ readonly pollMs: number;
24
+ readonly maxPollMs: number;
25
+ readonly heartbeatMs: number;
26
+ readonly maxDurationMs: number;
27
+ }, RenderFeed, never, JournalTail>;
28
+ export {};