@theruntime/protocol 0.0.0 → 0.0.1

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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +14 -4
  3. package/src/error.ts +54 -0
  4. package/src/events/index.ts +51 -0
  5. package/src/guidance/index.ts +3 -0
  6. package/src/index.ts +46 -0
  7. package/src/logs/index.ts +46 -0
  8. package/src/notifications/event.ts +62 -0
  9. package/src/notifications.ts +5 -0
  10. package/src/operations/add-component.ts +121 -0
  11. package/src/operations/add-package.ts +58 -0
  12. package/src/operations/app-record.ts +29 -0
  13. package/src/operations/archive-route.ts +48 -0
  14. package/src/operations/capture-snapshot.ts +46 -0
  15. package/src/operations/declaration.ts +115 -0
  16. package/src/operations/declare-route.ts +66 -0
  17. package/src/operations/delete-app.ts +47 -0
  18. package/src/operations/delete-record.ts +46 -0
  19. package/src/operations/describe-route.ts +122 -0
  20. package/src/operations/emit-event.ts +66 -0
  21. package/src/operations/get-record.ts +44 -0
  22. package/src/operations/get-snapshot.ts +64 -0
  23. package/src/operations/guidance.ts +91 -0
  24. package/src/operations/heartbeat.ts +27 -0
  25. package/src/operations/identify.ts +50 -0
  26. package/src/operations/list-packages.ts +72 -0
  27. package/src/operations/list-presence.ts +64 -0
  28. package/src/operations/list-quick-actions.ts +60 -0
  29. package/src/operations/list-records.ts +55 -0
  30. package/src/operations/list-route-logs.ts +98 -0
  31. package/src/operations/list-routes.ts +101 -0
  32. package/src/operations/list-subscribers.ts +75 -0
  33. package/src/operations/list-subscriptions.ts +98 -0
  34. package/src/operations/list-templates.ts +71 -0
  35. package/src/operations/log-line.ts +222 -0
  36. package/src/operations/parse-helpers.ts +136 -0
  37. package/src/operations/query-selection.ts +44 -0
  38. package/src/operations/read-daemon-log.ts +29 -0
  39. package/src/operations/read-route-log.ts +39 -0
  40. package/src/operations/reboot-route.ts +45 -0
  41. package/src/operations/record-status.ts +18 -0
  42. package/src/operations/register-route.ts +135 -0
  43. package/src/operations/save-record.ts +55 -0
  44. package/src/operations/set-record-status.ts +63 -0
  45. package/src/operations/set-route-visibility.ts +48 -0
  46. package/src/operations/set-selection.ts +53 -0
  47. package/src/operations/subscribe.ts +47 -0
  48. package/src/operations/unregister-route.ts +36 -0
  49. package/src/operations/unsubscribe.ts +44 -0
  50. package/src/operations/visibility.ts +9 -0
  51. package/src/operations.ts +39 -0
  52. package/src/payloads/app-ready.ts +24 -0
  53. package/src/payloads/refusal.ts +44 -0
  54. package/src/payloads.ts +11 -0
  55. package/src/quick-actions/index.ts +12 -0
  56. package/src/readiness.ts +52 -0
  57. package/src/records/index.ts +46 -0
  58. package/src/routes/index.ts +104 -0
  59. package/src/selection/index.ts +16 -0
  60. package/src/sessions/index.ts +24 -0
  61. package/src/snapshots/index.ts +20 -0
  62. package/src/templates/index.ts +14 -0
  63. package/src/toolchain/index.ts +36 -0
  64. package/src/version.ts +3 -0
  65. package/README.md +0 -3
@@ -0,0 +1,18 @@
1
+ export const RECORD_STATUSES = [
2
+ "captured",
3
+ "submitted",
4
+ "broadcasted",
5
+ "read",
6
+ "working",
7
+ "problem",
8
+ "resolved",
9
+ "cancelled",
10
+ ] as const;
11
+
12
+ export type RecordStatus = (typeof RECORD_STATUSES)[number];
13
+
14
+ export function isRecordStatus(value: unknown): value is RecordStatus {
15
+ if (typeof value !== "string") return false;
16
+ const statuses: readonly string[] = RECORD_STATUSES;
17
+ return statuses.includes(value);
18
+ }
@@ -0,0 +1,135 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import { optionalDeclarationPatch, type DeclarationPatch } from "./declaration";
3
+ import { parseGuidance, type Guidance } from "./guidance";
4
+ import {
5
+ asRecord,
6
+ optionalBoolean,
7
+ optionalNonNegativeInteger,
8
+ optionalString,
9
+ requireOperation,
10
+ requireProtocol,
11
+ requireString,
12
+ } from "./parse-helpers";
13
+
14
+ const NOUN = "a registerRoute input";
15
+
16
+ export interface RegisterRouteInput {
17
+ readonly operation: "registerRoute";
18
+ readonly protocol: ProtocolVersion;
19
+ readonly dir: string;
20
+ readonly name?: string;
21
+ /** What the caller claims for itself, the same role subscribe's `sid` plays. Only needed
22
+ * when the transport carries no observed identity of its own (the local socket, an HTTP
23
+ * caller) or to name a session explicitly; an MCP caller with a resolvable observed identity
24
+ * needs neither. */
25
+ readonly sid?: string;
26
+ /** Whether this call also subscribes the registering session to the route's events. Default
27
+ * true. Passing false never unsubscribes a session already subscribed; it only skips a fresh
28
+ * subscribe. */
29
+ readonly subscribe?: boolean;
30
+ /** A host project's root directory, which the app may read through the @host seam. Three
31
+ * states, so a re-register of a route that already has one never blanks it by omission:
32
+ * omitted keeps the stored value, "" clears it, a path sets it. */
33
+ readonly host?: string;
34
+ /** The template to create the app from, by id. listTemplates names the set. An id outside it
35
+ * is refused rather than answered with the plain shell. Only read when the directory has no
36
+ * App.tsx of its own; the scaffold never overwrites a file that is already there. */
37
+ readonly template?: string;
38
+ /** What this app's setup is: whether it is host connected, which React copy it runs, and
39
+ * whose components it uses. Each field has the same three states host does, so a re-register
40
+ * that names none of them leaves the stored declaration alone. */
41
+ readonly declaration?: DeclarationPatch;
42
+ }
43
+
44
+ export function parseRegisterRouteInput(value: unknown): RegisterRouteInput {
45
+ const record = asRecord(value, NOUN);
46
+ requireOperation(record, "registerRoute", NOUN);
47
+ requireProtocol(record, NOUN);
48
+ const dir = requireString(record, "dir", NOUN);
49
+ const name = optionalString(record, "name", NOUN);
50
+ const sid = optionalString(record, "sid", NOUN);
51
+ const subscribe = optionalBoolean(record, "subscribe", NOUN);
52
+ const host = optionalString(record, "host", NOUN);
53
+ const template = optionalString(record, "template", NOUN);
54
+ const declaration = optionalDeclarationPatch(record, "declaration", NOUN);
55
+ // Refused rather than dropped: a caller that named a theme and got an app themed by something
56
+ // else has no way to tell, which is the same reason the theme name it used to carry was
57
+ // checked against a set.
58
+ if (record.theme !== undefined) {
59
+ throw new Error(
60
+ `${NOUN} carries no theme. The design register above the app decides its theme, through the tokens.css in the app's own directory.`,
61
+ );
62
+ }
63
+ return {
64
+ operation: "registerRoute",
65
+ protocol: PROTOCOL_VERSION,
66
+ dir,
67
+ ...(name !== undefined ? { name } : {}),
68
+ ...(sid !== undefined ? { sid } : {}),
69
+ ...(subscribe !== undefined ? { subscribe } : {}),
70
+ ...(host !== undefined ? { host } : {}),
71
+ ...(template !== undefined ? { template } : {}),
72
+ ...(declaration !== undefined ? { declaration } : {}),
73
+ };
74
+ }
75
+
76
+ export function serializeRegisterRouteInput(input: RegisterRouteInput): string {
77
+ return JSON.stringify(input);
78
+ }
79
+
80
+ export interface RegisterRouteResult {
81
+ readonly name: string;
82
+ readonly dir: string;
83
+ readonly url: string;
84
+ readonly reused: boolean;
85
+ readonly mismatch: boolean;
86
+ /** Whether the registering session is subscribed to this route's events after this call,
87
+ * whether this call did the subscribing or it already was. */
88
+ readonly subscribed: boolean;
89
+ /** The identity the subscription was filed under. Present iff subscribed is true. */
90
+ readonly sid?: string;
91
+ /** The subscription's replay cursor. Present iff subscribed is true. */
92
+ readonly cursor?: number;
93
+ /** The template this app was created from. Present on every later register of the same route,
94
+ * read off the stored row, so provenance survives the call that established it. */
95
+ readonly template?: string;
96
+ /** The template's manifest version at the moment the app was created. Present iff template is. */
97
+ readonly templateVersion?: string;
98
+ /** What this registration teaches the calling session. */
99
+ readonly guidance: Guidance;
100
+ }
101
+
102
+ export function parseRegisterRouteResult(value: unknown): RegisterRouteResult {
103
+ const record = asRecord(value, "a registerRoute result");
104
+ const name = requireString(record, "name", "a registerRoute result");
105
+ const dir = requireString(record, "dir", "a registerRoute result");
106
+ const url = requireString(record, "url", "a registerRoute result");
107
+ if (typeof record.reused !== "boolean")
108
+ throw new Error("a registerRoute result states whether the route was reused");
109
+ if (typeof record.mismatch !== "boolean")
110
+ throw new Error("a registerRoute result states whether the requested dir was a mismatch");
111
+ if (typeof record.subscribed !== "boolean")
112
+ throw new Error("a registerRoute result states whether the session is subscribed");
113
+ const sid = optionalString(record, "sid", "a registerRoute result");
114
+ const cursor = optionalNonNegativeInteger(record, "cursor", "a registerRoute result");
115
+ const template = optionalString(record, "template", "a registerRoute result");
116
+ const templateVersion = optionalString(record, "templateVersion", "a registerRoute result");
117
+ const guidance = parseGuidance(record.guidance);
118
+ return {
119
+ name,
120
+ dir,
121
+ url,
122
+ reused: record.reused,
123
+ mismatch: record.mismatch,
124
+ subscribed: record.subscribed,
125
+ ...(sid !== undefined ? { sid } : {}),
126
+ ...(cursor !== undefined ? { cursor } : {}),
127
+ ...(template !== undefined ? { template } : {}),
128
+ ...(templateVersion !== undefined ? { templateVersion } : {}),
129
+ guidance,
130
+ };
131
+ }
132
+
133
+ export function serializeRegisterRouteResult(result: RegisterRouteResult): string {
134
+ return JSON.stringify(result);
135
+ }
@@ -0,0 +1,55 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import {
3
+ asRecord,
4
+ requireOperation,
5
+ requirePositiveInteger,
6
+ requireProtocol,
7
+ requireString,
8
+ } from "./parse-helpers";
9
+
10
+ const NOUN = "a saveRecord input";
11
+
12
+ export interface SaveRecordInput {
13
+ readonly operation: "saveRecord";
14
+ readonly protocol: ProtocolVersion;
15
+ readonly name: string;
16
+ readonly action: string;
17
+ // Payload is JSON, validated at the boundary, same as emitEvent's.
18
+ readonly payload: unknown;
19
+ }
20
+
21
+ export function parseSaveRecordInput(value: unknown): SaveRecordInput {
22
+ const record = asRecord(value, NOUN);
23
+ requireOperation(record, "saveRecord", NOUN);
24
+ requireProtocol(record, NOUN);
25
+ const name = requireString(record, "name", NOUN);
26
+ const action = requireString(record, "action", NOUN);
27
+ if (!("payload" in record)) throw new Error(`${NOUN} carries a payload`);
28
+ return {
29
+ operation: "saveRecord",
30
+ protocol: PROTOCOL_VERSION,
31
+ name,
32
+ action,
33
+ payload: record.payload,
34
+ };
35
+ }
36
+
37
+ export function serializeSaveRecordInput(input: SaveRecordInput): string {
38
+ return JSON.stringify(input);
39
+ }
40
+
41
+ export interface SaveRecordResult {
42
+ readonly name: string;
43
+ readonly id: number;
44
+ }
45
+
46
+ export function parseSaveRecordResult(value: unknown): SaveRecordResult {
47
+ const record = asRecord(value, "a saveRecord result");
48
+ const name = requireString(record, "name", "a saveRecord result");
49
+ const id = requirePositiveInteger(record, "id", "a saveRecord result");
50
+ return { name, id };
51
+ }
52
+
53
+ export function serializeSaveRecordResult(result: SaveRecordResult): string {
54
+ return JSON.stringify(result);
55
+ }
@@ -0,0 +1,63 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import {
3
+ asRecord,
4
+ optionalString,
5
+ requireOperation,
6
+ requirePositiveInteger,
7
+ requireProtocol,
8
+ requireString,
9
+ } from "./parse-helpers";
10
+ import { isRecordStatus, RECORD_STATUSES, type RecordStatus } from "./record-status";
11
+
12
+ const NOUN = "a setRecordStatus input";
13
+
14
+ export interface SetRecordStatusInput {
15
+ readonly operation: "setRecordStatus";
16
+ readonly protocol: ProtocolVersion;
17
+ readonly name: string;
18
+ readonly id: number;
19
+ readonly status: RecordStatus;
20
+ readonly note?: string;
21
+ }
22
+
23
+ export function parseSetRecordStatusInput(value: unknown): SetRecordStatusInput {
24
+ const record = asRecord(value, NOUN);
25
+ requireOperation(record, "setRecordStatus", NOUN);
26
+ requireProtocol(record, NOUN);
27
+ const name = requireString(record, "name", NOUN);
28
+ const id = requirePositiveInteger(record, "id", NOUN);
29
+ if (!isRecordStatus(record.status))
30
+ throw new Error(`${NOUN} names a status, one of: ${RECORD_STATUSES.join(", ")}`);
31
+ const note = optionalString(record, "note", NOUN);
32
+ return {
33
+ operation: "setRecordStatus",
34
+ protocol: PROTOCOL_VERSION,
35
+ name,
36
+ id,
37
+ status: record.status,
38
+ ...(note !== undefined ? { note } : {}),
39
+ };
40
+ }
41
+
42
+ export function serializeSetRecordStatusInput(input: SetRecordStatusInput): string {
43
+ return JSON.stringify(input);
44
+ }
45
+
46
+ export interface SetRecordStatusResult {
47
+ readonly name: string;
48
+ readonly id: number;
49
+ readonly status: RecordStatus;
50
+ }
51
+
52
+ export function parseSetRecordStatusResult(value: unknown): SetRecordStatusResult {
53
+ const record = asRecord(value, "a setRecordStatus result");
54
+ const name = requireString(record, "name", "a setRecordStatus result");
55
+ const id = requirePositiveInteger(record, "id", "a setRecordStatus result");
56
+ if (!isRecordStatus(record.status))
57
+ throw new Error("a setRecordStatus result names a known status");
58
+ return { name, id, status: record.status };
59
+ }
60
+
61
+ export function serializeSetRecordStatusResult(result: SetRecordStatusResult): string {
62
+ return JSON.stringify(result);
63
+ }
@@ -0,0 +1,48 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import { asRecord, requireOperation, requireProtocol, requireString } from "./parse-helpers";
3
+ import { isVisibility, type Visibility } from "./visibility";
4
+
5
+ const NOUN = "a setRouteVisibility input";
6
+
7
+ export interface SetRouteVisibilityInput {
8
+ readonly operation: "setRouteVisibility";
9
+ readonly protocol: ProtocolVersion;
10
+ readonly name: string;
11
+ // A free string, not narrowed to Visibility: the daemon refuses a reserved or unrecognized
12
+ // value with a policy message of its own, which needs to see the value the caller actually
13
+ // sent rather than have it rejected here as an unparsable shape.
14
+ readonly visibility: string;
15
+ }
16
+
17
+ export function parseSetRouteVisibilityInput(value: unknown): SetRouteVisibilityInput {
18
+ const record = asRecord(value, NOUN);
19
+ requireOperation(record, "setRouteVisibility", NOUN);
20
+ requireProtocol(record, NOUN);
21
+ const name = requireString(record, "name", NOUN);
22
+ const visibility = requireString(record, "visibility", NOUN);
23
+ return { operation: "setRouteVisibility", protocol: PROTOCOL_VERSION, name, visibility };
24
+ }
25
+
26
+ export function serializeSetRouteVisibilityInput(input: SetRouteVisibilityInput): string {
27
+ return JSON.stringify(input);
28
+ }
29
+
30
+ export interface SetRouteVisibilityResult {
31
+ readonly name: string;
32
+ readonly visibility: Visibility;
33
+ readonly changed: boolean;
34
+ }
35
+
36
+ export function parseSetRouteVisibilityResult(value: unknown): SetRouteVisibilityResult {
37
+ const record = asRecord(value, "a setRouteVisibility result");
38
+ const name = requireString(record, "name", "a setRouteVisibility result");
39
+ if (!isVisibility(record.visibility))
40
+ throw new Error("a setRouteVisibility result names a known visibility");
41
+ if (typeof record.changed !== "boolean")
42
+ throw new Error("a setRouteVisibility result states whether the visibility changed");
43
+ return { name, visibility: record.visibility, changed: record.changed };
44
+ }
45
+
46
+ export function serializeSetRouteVisibilityResult(result: SetRouteVisibilityResult): string {
47
+ return JSON.stringify(result);
48
+ }
@@ -0,0 +1,53 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import {
3
+ asRecord,
4
+ requireNonNegativeInteger,
5
+ requireOperation,
6
+ requireProtocol,
7
+ requireString,
8
+ } from "./parse-helpers";
9
+
10
+ const NOUN = "a setSelection input";
11
+
12
+ export interface SetSelectionInput {
13
+ readonly operation: "setSelection";
14
+ readonly protocol: ProtocolVersion;
15
+ readonly name: string;
16
+ // Each element is an opaque JSON capture (selector, tag, identity, …) the SDK's selection
17
+ // client owns the shape of; the protocol carries it, never inspects it.
18
+ readonly elements: readonly unknown[];
19
+ }
20
+
21
+ export function parseSetSelectionInput(value: unknown): SetSelectionInput {
22
+ const record = asRecord(value, NOUN);
23
+ requireOperation(record, "setSelection", NOUN);
24
+ requireProtocol(record, NOUN);
25
+ const name = requireString(record, "name", NOUN);
26
+ if (!Array.isArray(record.elements)) throw new Error(`${NOUN} carries an array of elements`);
27
+ return {
28
+ operation: "setSelection",
29
+ protocol: PROTOCOL_VERSION,
30
+ name,
31
+ elements: record.elements,
32
+ };
33
+ }
34
+
35
+ export function serializeSetSelectionInput(input: SetSelectionInput): string {
36
+ return JSON.stringify(input);
37
+ }
38
+
39
+ export interface SetSelectionResult {
40
+ readonly name: string;
41
+ readonly count: number;
42
+ }
43
+
44
+ export function parseSetSelectionResult(value: unknown): SetSelectionResult {
45
+ const record = asRecord(value, "a setSelection result");
46
+ const name = requireString(record, "name", "a setSelection result");
47
+ const count = requireNonNegativeInteger(record, "count", "a setSelection result");
48
+ return { name, count };
49
+ }
50
+
51
+ export function serializeSetSelectionResult(result: SetSelectionResult): string {
52
+ return JSON.stringify(result);
53
+ }
@@ -0,0 +1,47 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import { asRecord, requireOperation, requireProtocol, requireString } from "./parse-helpers";
3
+ import { parseGuidance, type Guidance } from "./guidance";
4
+
5
+ const NOUN = "a subscribe input";
6
+
7
+ export interface SubscribeInput {
8
+ readonly operation: "subscribe";
9
+ readonly protocol: ProtocolVersion;
10
+ readonly sid: string;
11
+ readonly name: string;
12
+ }
13
+
14
+ export function parseSubscribeInput(value: unknown): SubscribeInput {
15
+ const record = asRecord(value, NOUN);
16
+ requireOperation(record, "subscribe", NOUN);
17
+ requireProtocol(record, NOUN);
18
+ const sid = requireString(record, "sid", NOUN);
19
+ const name = requireString(record, "name", NOUN);
20
+ return { operation: "subscribe", protocol: PROTOCOL_VERSION, sid, name };
21
+ }
22
+
23
+ export function serializeSubscribeInput(input: SubscribeInput): string {
24
+ return JSON.stringify(input);
25
+ }
26
+
27
+ export interface SubscribeResult {
28
+ readonly sid: string;
29
+ readonly name: string;
30
+ readonly cursor: number;
31
+ /** What this subscription teaches the calling session. */
32
+ readonly guidance: Guidance;
33
+ }
34
+
35
+ export function parseSubscribeResult(value: unknown): SubscribeResult {
36
+ const record = asRecord(value, "a subscribe result");
37
+ const sid = requireString(record, "sid", "a subscribe result");
38
+ const name = requireString(record, "name", "a subscribe result");
39
+ if (typeof record.cursor !== "number" || !Number.isInteger(record.cursor)) {
40
+ throw new Error("a subscribe result names an integer cursor");
41
+ }
42
+ return { sid, name, cursor: record.cursor, guidance: parseGuidance(record.guidance) };
43
+ }
44
+
45
+ export function serializeSubscribeResult(result: SubscribeResult): string {
46
+ return JSON.stringify(result);
47
+ }
@@ -0,0 +1,36 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import { asRecord, requireOperation, requireProtocol, requireString } from "./parse-helpers";
3
+
4
+ const NOUN = "an unregisterRoute input";
5
+
6
+ export interface UnregisterRouteInput {
7
+ readonly operation: "unregisterRoute";
8
+ readonly protocol: ProtocolVersion;
9
+ readonly name: string;
10
+ }
11
+
12
+ export function parseUnregisterRouteInput(value: unknown): UnregisterRouteInput {
13
+ const record = asRecord(value, NOUN);
14
+ requireOperation(record, "unregisterRoute", NOUN);
15
+ requireProtocol(record, NOUN);
16
+ const name = requireString(record, "name", NOUN);
17
+ return { operation: "unregisterRoute", protocol: PROTOCOL_VERSION, name };
18
+ }
19
+
20
+ export function serializeUnregisterRouteInput(input: UnregisterRouteInput): string {
21
+ return JSON.stringify(input);
22
+ }
23
+
24
+ export interface UnregisterRouteResult {
25
+ readonly name: string;
26
+ }
27
+
28
+ export function parseUnregisterRouteResult(value: unknown): UnregisterRouteResult {
29
+ const record = asRecord(value, "an unregisterRoute result");
30
+ const name = requireString(record, "name", "an unregisterRoute result");
31
+ return { name };
32
+ }
33
+
34
+ export function serializeUnregisterRouteResult(result: UnregisterRouteResult): string {
35
+ return JSON.stringify(result);
36
+ }
@@ -0,0 +1,44 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import { asRecord, requireOperation, requireProtocol, requireString } from "./parse-helpers";
3
+
4
+ const NOUN = "an unsubscribe input";
5
+
6
+ export interface UnsubscribeInput {
7
+ readonly operation: "unsubscribe";
8
+ readonly protocol: ProtocolVersion;
9
+ readonly sid: string;
10
+ readonly name: string;
11
+ }
12
+
13
+ export function parseUnsubscribeInput(value: unknown): UnsubscribeInput {
14
+ const record = asRecord(value, NOUN);
15
+ requireOperation(record, "unsubscribe", NOUN);
16
+ requireProtocol(record, NOUN);
17
+ const sid = requireString(record, "sid", NOUN);
18
+ const name = requireString(record, "name", NOUN);
19
+ return { operation: "unsubscribe", protocol: PROTOCOL_VERSION, sid, name };
20
+ }
21
+
22
+ export function serializeUnsubscribeInput(input: UnsubscribeInput): string {
23
+ return JSON.stringify(input);
24
+ }
25
+
26
+ export interface UnsubscribeResult {
27
+ readonly sid: string;
28
+ readonly name: string;
29
+ readonly removed: boolean;
30
+ }
31
+
32
+ export function parseUnsubscribeResult(value: unknown): UnsubscribeResult {
33
+ const record = asRecord(value, "an unsubscribe result");
34
+ const sid = requireString(record, "sid", "an unsubscribe result");
35
+ const name = requireString(record, "name", "an unsubscribe result");
36
+ if (typeof record.removed !== "boolean") {
37
+ throw new Error("an unsubscribe result states whether a subscription was removed");
38
+ }
39
+ return { sid, name, removed: record.removed };
40
+ }
41
+
42
+ export function serializeUnsubscribeResult(result: UnsubscribeResult): string {
43
+ return JSON.stringify(result);
44
+ }
@@ -0,0 +1,9 @@
1
+ export const VISIBILITIES = ["private", "link"] as const;
2
+
3
+ export type Visibility = (typeof VISIBILITIES)[number];
4
+
5
+ export function isVisibility(value: unknown): value is Visibility {
6
+ if (typeof value !== "string") return false;
7
+ const visibilities: readonly string[] = VISIBILITIES;
8
+ return visibilities.includes(value);
9
+ }
@@ -0,0 +1,39 @@
1
+ // Every operation the protocol defines. Each name's shape lives in its own file under ./operations.
2
+ export const OPERATIONS = [
3
+ "heartbeat",
4
+ "identify",
5
+ "registerRoute",
6
+ "unregisterRoute",
7
+ "listRoutes",
8
+ "describeRoute",
9
+ "declareRoute",
10
+ "archiveRoute",
11
+ "deleteApp",
12
+ "rebootRoute",
13
+ "listRecords",
14
+ "getRecord",
15
+ "setRecordStatus",
16
+ "deleteRecord",
17
+ "subscribe",
18
+ "unsubscribe",
19
+ "emitEvent",
20
+ "addPackage",
21
+ "addComponent",
22
+ "captureSnapshot",
23
+ "getSnapshot",
24
+ "setRouteVisibility",
25
+ "saveRecord",
26
+ "setSelection",
27
+ "querySelection",
28
+ "listSubscribers",
29
+ "listSubscriptions",
30
+ "listPresence",
31
+ "listQuickActions",
32
+ "listPackages",
33
+ "listTemplates",
34
+ "readRouteLog",
35
+ "listRouteLogs",
36
+ "readDaemonLog",
37
+ ] as const;
38
+
39
+ export type OperationName = (typeof OPERATIONS)[number];
@@ -0,0 +1,24 @@
1
+ import { asRecord, requireNonNegativeInteger, requireString } from "../operations/parse-helpers";
2
+
3
+ const NOUN = "an app_ready payload";
4
+
5
+ /** What a mounted app reports once its first frame has committed and painted. `at` is the app's
6
+ * own clock; the daemon measures age from its own receipt instead. */
7
+ export interface AppReadyPayload {
8
+ readonly route: string;
9
+ readonly at: number;
10
+ readonly rootChildren: number;
11
+ }
12
+
13
+ export function parseAppReadyPayload(value: unknown): AppReadyPayload {
14
+ const record = asRecord(value, NOUN);
15
+ return {
16
+ route: requireString(record, "route", NOUN),
17
+ at: requireNonNegativeInteger(record, "at", NOUN),
18
+ rootChildren: requireNonNegativeInteger(record, "rootChildren", NOUN),
19
+ };
20
+ }
21
+
22
+ export function serializeAppReadyPayload(payload: AppReadyPayload): string {
23
+ return JSON.stringify(payload);
24
+ }
@@ -0,0 +1,44 @@
1
+ import {
2
+ asRecord,
3
+ optionalString,
4
+ requireNonNegativeInteger,
5
+ requireString,
6
+ } from "../operations/parse-helpers";
7
+
8
+ const NOUN = "a route refusal";
9
+
10
+ /** The action a route-kind event carries when the supervisor stops restarting a route. */
11
+ export const CRASH_REFUSED_ACTION = "crash_refused";
12
+
13
+ /**
14
+ * Why a route's process is not running: the sentence a caller of the route is answered with,
15
+ * how many crashes fell inside the window, the window they fell inside, when the supervisor
16
+ * gave up, and the last output the process wrote. `stderr` is absent for a process that
17
+ * crashed silently.
18
+ *
19
+ * One shape, carried two ways: it is the payload of the crash_refused event, and it is the
20
+ * refusal a listRoutes summary carries for the same route.
21
+ */
22
+ export interface RouteRefusal {
23
+ readonly reason: string;
24
+ readonly attempts: number;
25
+ readonly windowMs: number;
26
+ readonly at: string;
27
+ readonly stderr?: string;
28
+ }
29
+
30
+ export function parseRouteRefusal(value: unknown): RouteRefusal {
31
+ const record = asRecord(value, NOUN);
32
+ const stderr = optionalString(record, "stderr", NOUN);
33
+ return {
34
+ reason: requireString(record, "reason", NOUN),
35
+ attempts: requireNonNegativeInteger(record, "attempts", NOUN),
36
+ windowMs: requireNonNegativeInteger(record, "windowMs", NOUN),
37
+ at: requireString(record, "at", NOUN),
38
+ ...(stderr !== undefined ? { stderr } : {}),
39
+ };
40
+ }
41
+
42
+ export function serializeRouteRefusal(refusal: RouteRefusal): string {
43
+ return JSON.stringify(refusal);
44
+ }
@@ -0,0 +1,11 @@
1
+ // Every action the protocol defines a payload shape for. Each name's shape lives in its own file
2
+ // under ./payloads. An action an app emits with no shape here is carried as opaque JSON.
3
+ export const APP_ACTIONS = ["app_ready"] as const;
4
+
5
+ export type AppActionName = (typeof APP_ACTIONS)[number];
6
+
7
+ export function isAppActionName(value: unknown): value is AppActionName {
8
+ if (typeof value !== "string") return false;
9
+ const actions: readonly string[] = APP_ACTIONS;
10
+ return actions.includes(value);
11
+ }
@@ -0,0 +1,12 @@
1
+ /** The quick action surface, re-exported by the package barrel as one line. */
2
+ export {
3
+ parseListQuickActionsInput,
4
+ serializeListQuickActionsInput,
5
+ parseListQuickActionsResult,
6
+ serializeListQuickActionsResult,
7
+ } from "../operations/list-quick-actions";
8
+ export type {
9
+ ListQuickActionsInput,
10
+ ListQuickActionsResult,
11
+ QuickActionSummary,
12
+ } from "../operations/list-quick-actions";