@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,27 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+
3
+ export interface Heartbeat {
4
+ readonly operation: "heartbeat";
5
+ readonly protocol: ProtocolVersion;
6
+ readonly client: string;
7
+ }
8
+
9
+ export function parseHeartbeat(value: unknown): Heartbeat {
10
+ if (typeof value !== "object" || value === null) {
11
+ throw new Error("a heartbeat is an object");
12
+ }
13
+ if (!("operation" in value) || value.operation !== "heartbeat") {
14
+ throw new Error("a heartbeat names the heartbeat operation");
15
+ }
16
+ if (!("protocol" in value) || value.protocol !== PROTOCOL_VERSION) {
17
+ throw new Error(`a heartbeat carries protocol ${PROTOCOL_VERSION}`);
18
+ }
19
+ if (!("client" in value) || typeof value.client !== "string" || value.client.length === 0) {
20
+ throw new Error("a heartbeat names the client it comes from");
21
+ }
22
+ return { operation: "heartbeat", protocol: PROTOCOL_VERSION, client: value.client };
23
+ }
24
+
25
+ export function serializeHeartbeat(heartbeat: Heartbeat): string {
26
+ return JSON.stringify(heartbeat);
27
+ }
@@ -0,0 +1,50 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import { asRecord, requireOperation, requireProtocol, requireString } from "./parse-helpers";
3
+
4
+ const NOUN = "an identify input";
5
+
6
+ export interface IdentifyInput {
7
+ readonly operation: "identify";
8
+ readonly protocol: ProtocolVersion;
9
+ readonly claimedId: string;
10
+ }
11
+
12
+ export function parseIdentifyInput(value: unknown): IdentifyInput {
13
+ const record = asRecord(value, NOUN);
14
+ requireOperation(record, "identify", NOUN);
15
+ requireProtocol(record, NOUN);
16
+ const claimedId = requireString(record, "claimedId", NOUN);
17
+ return { operation: "identify", protocol: PROTOCOL_VERSION, claimedId };
18
+ }
19
+
20
+ export function serializeIdentifyInput(input: IdentifyInput): string {
21
+ return JSON.stringify(input);
22
+ }
23
+
24
+ export const IDENTITY_VIAS = ["observed", "claimed"] as const;
25
+ export type IdentityVia = (typeof IDENTITY_VIAS)[number];
26
+
27
+ export interface IdentifyResult {
28
+ readonly claimedId: string;
29
+ readonly key: string;
30
+ readonly via: IdentityVia;
31
+ }
32
+
33
+ function isIdentityVia(value: unknown): value is IdentityVia {
34
+ const vias: readonly unknown[] = IDENTITY_VIAS;
35
+ return vias.includes(value);
36
+ }
37
+
38
+ export function parseIdentifyResult(value: unknown): IdentifyResult {
39
+ const record = asRecord(value, "an identify result");
40
+ const claimedId = requireString(record, "claimedId", "an identify result");
41
+ const key = requireString(record, "key", "an identify result");
42
+ if (!isIdentityVia(record.via)) {
43
+ throw new Error("an identify result names its via as observed or claimed");
44
+ }
45
+ return { claimedId, key, via: record.via };
46
+ }
47
+
48
+ export function serializeIdentifyResult(result: IdentifyResult): string {
49
+ return JSON.stringify(result);
50
+ }
@@ -0,0 +1,72 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import { asRecord, requireOperation, requireProtocol, requireString } from "./parse-helpers";
3
+
4
+ const NOUN = "a listPackages input";
5
+
6
+ export interface ListPackagesInput {
7
+ readonly operation: "listPackages";
8
+ readonly protocol: ProtocolVersion;
9
+ }
10
+
11
+ export function parseListPackagesInput(value: unknown): ListPackagesInput {
12
+ const record = asRecord(value, NOUN);
13
+ requireOperation(record, "listPackages", NOUN);
14
+ requireProtocol(record, NOUN);
15
+ return { operation: "listPackages", protocol: PROTOCOL_VERSION };
16
+ }
17
+
18
+ export function serializeListPackagesInput(input: ListPackagesInput): string {
19
+ return JSON.stringify(input);
20
+ }
21
+
22
+ // The one shape this operation carries regardless of transport: an array of entries, each
23
+ // naming where it came from and its resolved version, or null when none is known. The previous
24
+ // engine answered this question two ways depending on transport (an object keyed by id over MCP,
25
+ // an array over HTTP); this protocol carries one operation, one result shape, over every
26
+ // transport.
27
+ //
28
+ // runtime is the runtime's own committed manifest. dependency is a package a route serves because
29
+ // something the runtime ships depends on it. user is a session's addPackage call.
30
+ export type PackageSource = "runtime" | "dependency" | "user";
31
+
32
+ const PACKAGE_SOURCES: readonly PackageSource[] = ["runtime", "dependency", "user"];
33
+
34
+ export interface PackageSummary {
35
+ readonly id: string;
36
+ readonly source: PackageSource;
37
+ readonly version: string | null;
38
+ }
39
+
40
+ function parsePackageSource(value: unknown, noun: string): PackageSource {
41
+ const source = PACKAGE_SOURCES.find((candidate) => candidate === value);
42
+ if (source === undefined) {
43
+ throw new Error(`${noun} names a source of "runtime", "dependency", or "user"`);
44
+ }
45
+ return source;
46
+ }
47
+
48
+ function parsePackageSummary(value: unknown): PackageSummary {
49
+ const record = asRecord(value, "a package summary");
50
+ const id = requireString(record, "id", "a package summary");
51
+ const source = parsePackageSource(record.source, "a package summary");
52
+ if (record.version !== null && typeof record.version !== "string") {
53
+ throw new Error("a package summary's version is a string or null");
54
+ }
55
+ return { id, source, version: record.version };
56
+ }
57
+
58
+ export interface ListPackagesResult {
59
+ readonly packages: readonly PackageSummary[];
60
+ }
61
+
62
+ export function parseListPackagesResult(value: unknown): ListPackagesResult {
63
+ const record = asRecord(value, "a listPackages result");
64
+ if (!Array.isArray(record.packages)) {
65
+ throw new Error("a listPackages result carries an array of packages");
66
+ }
67
+ return { packages: record.packages.map(parsePackageSummary) };
68
+ }
69
+
70
+ export function serializeListPackagesResult(result: ListPackagesResult): string {
71
+ return JSON.stringify(result);
72
+ }
@@ -0,0 +1,64 @@
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 listPresence input";
11
+
12
+ export interface ListPresenceInput {
13
+ readonly operation: "listPresence";
14
+ readonly protocol: ProtocolVersion;
15
+ readonly name: string;
16
+ }
17
+
18
+ export function parseListPresenceInput(value: unknown): ListPresenceInput {
19
+ const record = asRecord(value, NOUN);
20
+ requireOperation(record, "listPresence", NOUN);
21
+ requireProtocol(record, NOUN);
22
+ const name = requireString(record, "name", NOUN);
23
+ return { operation: "listPresence", protocol: PROTOCOL_VERSION, name };
24
+ }
25
+
26
+ export function serializeListPresenceInput(input: ListPresenceInput): string {
27
+ return JSON.stringify(input);
28
+ }
29
+
30
+ export interface PresenceBeatSummary {
31
+ readonly sid: string;
32
+ readonly shortId: string;
33
+ readonly lastBeat: number;
34
+ readonly ageMs: number;
35
+ }
36
+
37
+ function parsePresenceBeatSummary(value: unknown): PresenceBeatSummary {
38
+ const record = asRecord(value, "a presence beat");
39
+ const sid = requireString(record, "sid", "a presence beat");
40
+ const shortId = requireString(record, "shortId", "a presence beat");
41
+ const lastBeat = requireNonNegativeInteger(record, "lastBeat", "a presence beat");
42
+ const ageMs = requireNonNegativeInteger(record, "ageMs", "a presence beat");
43
+ return { sid, shortId, lastBeat, ageMs };
44
+ }
45
+
46
+ export interface ListPresenceResult {
47
+ readonly name: string;
48
+ readonly now: number;
49
+ readonly beats: readonly PresenceBeatSummary[];
50
+ }
51
+
52
+ export function parseListPresenceResult(value: unknown): ListPresenceResult {
53
+ const record = asRecord(value, "a listPresence result");
54
+ const name = requireString(record, "name", "a listPresence result");
55
+ const now = requireNonNegativeInteger(record, "now", "a listPresence result");
56
+ if (!Array.isArray(record.beats)) {
57
+ throw new Error("a listPresence result carries an array of beats");
58
+ }
59
+ return { name, now, beats: record.beats.map(parsePresenceBeatSummary) };
60
+ }
61
+
62
+ export function serializeListPresenceResult(result: ListPresenceResult): string {
63
+ return JSON.stringify(result);
64
+ }
@@ -0,0 +1,60 @@
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 listQuickActions input";
11
+
12
+ export interface ListQuickActionsInput {
13
+ readonly operation: "listQuickActions";
14
+ readonly protocol: ProtocolVersion;
15
+ }
16
+
17
+ export function parseListQuickActionsInput(value: unknown): ListQuickActionsInput {
18
+ const record = asRecord(value, NOUN);
19
+ requireOperation(record, "listQuickActions", NOUN);
20
+ requireProtocol(record, NOUN);
21
+ return { operation: "listQuickActions", protocol: PROTOCOL_VERSION };
22
+ }
23
+
24
+ export function serializeListQuickActionsInput(input: ListQuickActionsInput): string {
25
+ return JSON.stringify(input);
26
+ }
27
+
28
+ export interface QuickActionSummary {
29
+ readonly id: number;
30
+ readonly icon: string;
31
+ readonly label: string;
32
+ readonly prompt: string;
33
+ readonly createdAt: string;
34
+ }
35
+
36
+ function parseQuickActionSummary(value: unknown): QuickActionSummary {
37
+ const record = asRecord(value, "a quick-action summary");
38
+ const id = requirePositiveInteger(record, "id", "a quick-action summary");
39
+ const icon = requireString(record, "icon", "a quick-action summary");
40
+ const label = requireString(record, "label", "a quick-action summary");
41
+ const prompt = requireString(record, "prompt", "a quick-action summary");
42
+ const createdAt = requireString(record, "createdAt", "a quick-action summary");
43
+ return { id, icon, label, prompt, createdAt };
44
+ }
45
+
46
+ export interface ListQuickActionsResult {
47
+ readonly actions: readonly QuickActionSummary[];
48
+ }
49
+
50
+ export function parseListQuickActionsResult(value: unknown): ListQuickActionsResult {
51
+ const record = asRecord(value, "a listQuickActions result");
52
+ if (!Array.isArray(record.actions)) {
53
+ throw new Error("a listQuickActions result carries an array of actions");
54
+ }
55
+ return { actions: record.actions.map(parseQuickActionSummary) };
56
+ }
57
+
58
+ export function serializeListQuickActionsResult(result: ListQuickActionsResult): string {
59
+ return JSON.stringify(result);
60
+ }
@@ -0,0 +1,55 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import { parseAppRecord, type AppRecord } from "./app-record";
3
+ import {
4
+ asRecord,
5
+ optionalPositiveInteger,
6
+ optionalString,
7
+ requireOperation,
8
+ requireProtocol,
9
+ requireString,
10
+ } from "./parse-helpers";
11
+
12
+ const NOUN = "a listRecords input";
13
+
14
+ export interface ListRecordsInput {
15
+ readonly operation: "listRecords";
16
+ readonly protocol: ProtocolVersion;
17
+ readonly name: string;
18
+ readonly limit?: number;
19
+ readonly action?: string;
20
+ }
21
+
22
+ export function parseListRecordsInput(value: unknown): ListRecordsInput {
23
+ const record = asRecord(value, NOUN);
24
+ requireOperation(record, "listRecords", NOUN);
25
+ requireProtocol(record, NOUN);
26
+ const name = requireString(record, "name", NOUN);
27
+ const limit = optionalPositiveInteger(record, "limit", NOUN);
28
+ const action = optionalString(record, "action", NOUN);
29
+ return {
30
+ operation: "listRecords",
31
+ protocol: PROTOCOL_VERSION,
32
+ name,
33
+ ...(limit !== undefined ? { limit } : {}),
34
+ ...(action !== undefined ? { action } : {}),
35
+ };
36
+ }
37
+
38
+ export function serializeListRecordsInput(input: ListRecordsInput): string {
39
+ return JSON.stringify(input);
40
+ }
41
+
42
+ export interface ListRecordsResult {
43
+ readonly records: readonly AppRecord[];
44
+ }
45
+
46
+ export function parseListRecordsResult(value: unknown): ListRecordsResult {
47
+ const record = asRecord(value, "a listRecords result");
48
+ if (!Array.isArray(record.records))
49
+ throw new Error("a listRecords result carries an array of records");
50
+ return { records: record.records.map(parseAppRecord) };
51
+ }
52
+
53
+ export function serializeListRecordsResult(result: ListRecordsResult): string {
54
+ return JSON.stringify(result);
55
+ }
@@ -0,0 +1,98 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import { isLogTag, parseLogLine, serializeLogLine, type LogLine, type LogTag } from "./log-line";
3
+ import {
4
+ asRecord,
5
+ optionalPositiveInteger,
6
+ optionalString,
7
+ requireOperation,
8
+ requireProtocol,
9
+ requireString,
10
+ } from "./parse-helpers";
11
+
12
+ const NOUN = "a listRouteLogs input";
13
+
14
+ /** How many routes one answer carries, and the ceiling a caller may ask for. A route's log is
15
+ * read whole to answer, so the bound is what keeps the question cheap on a daemon serving many. */
16
+ export const ROUTE_LOGS_LIMIT = 50;
17
+ export const ROUTE_LOGS_LIMIT_MAX = 200;
18
+
19
+ export interface ListRouteLogsInput {
20
+ readonly operation: "listRouteLogs";
21
+ readonly protocol: ProtocolVersion;
22
+ /** The tag every answered route's log carries. */
23
+ readonly tag: LogTag;
24
+ readonly limit: number;
25
+ readonly since?: string;
26
+ }
27
+
28
+ export function parseListRouteLogsInput(value: unknown): ListRouteLogsInput {
29
+ const record = asRecord(value, NOUN);
30
+ requireOperation(record, "listRouteLogs", NOUN);
31
+ requireProtocol(record, NOUN);
32
+ const tag = requireString(record, "tag", NOUN);
33
+ if (!isLogTag(tag)) throw new Error(`${NOUN}'s tag is one the protocol defines`);
34
+ const limit = optionalPositiveInteger(record, "limit", NOUN);
35
+ if (limit !== undefined && limit > ROUTE_LOGS_LIMIT_MAX)
36
+ throw new Error(`${NOUN}'s limit is at most ${ROUTE_LOGS_LIMIT_MAX}`);
37
+ const since = optionalString(record, "since", NOUN);
38
+ if (since !== undefined && Number.isNaN(Date.parse(since)))
39
+ throw new Error(`${NOUN}'s since is an ISO timestamp`);
40
+ return {
41
+ operation: "listRouteLogs",
42
+ protocol: PROTOCOL_VERSION,
43
+ tag,
44
+ limit: limit ?? ROUTE_LOGS_LIMIT,
45
+ ...(since !== undefined ? { since } : {}),
46
+ };
47
+ }
48
+
49
+ export function serializeListRouteLogsInput(input: ListRouteLogsInput): string {
50
+ return JSON.stringify({
51
+ operation: input.operation,
52
+ protocol: input.protocol,
53
+ tag: input.tag,
54
+ ...(input.limit !== ROUTE_LOGS_LIMIT ? { limit: input.limit } : {}),
55
+ ...(input.since !== undefined ? { since: input.since } : {}),
56
+ });
57
+ }
58
+
59
+ /** One route that carries the tag, and the newest line it carries it on, whole. */
60
+ export interface RouteLogSummary {
61
+ readonly name: string;
62
+ readonly last: LogLine;
63
+ }
64
+
65
+ export interface ListRouteLogsResult {
66
+ /** Newest line first, ties broken by route name. */
67
+ readonly routes: readonly RouteLogSummary[];
68
+ /** True when the limit cut routes that carry the tag. What was cut carries an older line
69
+ * than what was returned. */
70
+ readonly truncated: boolean;
71
+ }
72
+
73
+ function parseRouteLogSummary(value: unknown): RouteLogSummary {
74
+ const record = asRecord(value, "a route log summary");
75
+ return {
76
+ name: requireString(record, "name", "a route log summary"),
77
+ last: parseLogLine(record.last),
78
+ };
79
+ }
80
+
81
+ export function parseListRouteLogsResult(value: unknown): ListRouteLogsResult {
82
+ const record = asRecord(value, "a listRouteLogs result");
83
+ if (!Array.isArray(record.routes))
84
+ throw new Error("a listRouteLogs result carries an array of routes");
85
+ if (typeof record.truncated !== "boolean")
86
+ throw new Error("a listRouteLogs result says whether routes were cut");
87
+ return { routes: record.routes.map(parseRouteLogSummary), truncated: record.truncated };
88
+ }
89
+
90
+ export function serializeListRouteLogsResult(result: ListRouteLogsResult): string {
91
+ return JSON.stringify({
92
+ routes: result.routes.map((route): unknown => ({
93
+ name: route.name,
94
+ last: JSON.parse(serializeLogLine(route.last)),
95
+ })),
96
+ truncated: result.truncated,
97
+ });
98
+ }
@@ -0,0 +1,101 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import {
3
+ asRecord,
4
+ optionalBoolean,
5
+ optionalPositiveInteger,
6
+ requireOperation,
7
+ requireProtocol,
8
+ requireString,
9
+ } from "./parse-helpers";
10
+ import { parseRouteRefusal, type RouteRefusal } from "../payloads/refusal";
11
+ import { parseAppReadiness, type AppReadiness } from "../readiness";
12
+ import { parseDeclaration, type Declaration } from "./declaration";
13
+
14
+ const NOUN = "a listRoutes input";
15
+
16
+ export interface ListRoutesInput {
17
+ readonly operation: "listRoutes";
18
+ readonly protocol: ProtocolVersion;
19
+ readonly liveOnly?: boolean;
20
+ readonly includeArchived?: boolean;
21
+ readonly limit?: number;
22
+ }
23
+
24
+ export function parseListRoutesInput(value: unknown): ListRoutesInput {
25
+ const record = asRecord(value, NOUN);
26
+ requireOperation(record, "listRoutes", NOUN);
27
+ requireProtocol(record, NOUN);
28
+ const liveOnly = optionalBoolean(record, "liveOnly", NOUN);
29
+ const includeArchived = optionalBoolean(record, "includeArchived", NOUN);
30
+ const limit = optionalPositiveInteger(record, "limit", NOUN);
31
+ return {
32
+ operation: "listRoutes",
33
+ protocol: PROTOCOL_VERSION,
34
+ ...(liveOnly !== undefined ? { liveOnly } : {}),
35
+ ...(includeArchived !== undefined ? { includeArchived } : {}),
36
+ ...(limit !== undefined ? { limit } : {}),
37
+ };
38
+ }
39
+
40
+ export function serializeListRoutesInput(input: ListRoutesInput): string {
41
+ return JSON.stringify(input);
42
+ }
43
+
44
+ export interface RouteSummary {
45
+ readonly name: string;
46
+ readonly dir: string;
47
+ readonly url: string;
48
+ /** The supervisor's process truth: a pid this daemon is tracking right now. */
49
+ readonly live: boolean;
50
+ readonly archived: boolean;
51
+ /** What the route's page has reported about its own rendering. A live route whose page never
52
+ * painted reads live true and readiness silent. */
53
+ readonly readiness: AppReadiness;
54
+ /** What the route declares about its setup. Absent for a route that declares nothing, so a
55
+ * reader sees the difference between an undeclared app and one declared standalone. */
56
+ readonly declaration?: Declaration;
57
+ /** Present when the route is not live because the supervisor gave up restarting it. A reboot
58
+ * clears it. */
59
+ readonly refusal?: RouteRefusal;
60
+ }
61
+
62
+ function parseRouteSummary(value: unknown): RouteSummary {
63
+ const record = asRecord(value, "a route summary");
64
+ const name = requireString(record, "name", "a route summary");
65
+ const dir = requireString(record, "dir", "a route summary");
66
+ const url = requireString(record, "url", "a route summary");
67
+ if (typeof record.live !== "boolean")
68
+ throw new Error("a route summary states whether the route is live");
69
+ if (typeof record.archived !== "boolean")
70
+ throw new Error("a route summary states whether the route is archived");
71
+ if (!("readiness" in record)) throw new Error("a route summary carries a readiness");
72
+ const declared = "declaration" in record && record.declaration !== undefined;
73
+ const refusal = record.refusal === undefined ? undefined : parseRouteRefusal(record.refusal);
74
+ return {
75
+ name,
76
+ dir,
77
+ url,
78
+ live: record.live,
79
+ archived: record.archived,
80
+ readiness: parseAppReadiness(record.readiness),
81
+ ...(declared
82
+ ? { declaration: parseDeclaration(record.declaration, "a route summary's declaration") }
83
+ : {}),
84
+ ...(refusal !== undefined ? { refusal } : {}),
85
+ };
86
+ }
87
+
88
+ export interface ListRoutesResult {
89
+ readonly routes: readonly RouteSummary[];
90
+ }
91
+
92
+ export function parseListRoutesResult(value: unknown): ListRoutesResult {
93
+ const record = asRecord(value, "a listRoutes result");
94
+ if (!Array.isArray(record.routes))
95
+ throw new Error("a listRoutes result carries an array of routes");
96
+ return { routes: record.routes.map(parseRouteSummary) };
97
+ }
98
+
99
+ export function serializeListRoutesResult(result: ListRoutesResult): string {
100
+ return JSON.stringify(result);
101
+ }
@@ -0,0 +1,75 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import {
3
+ asRecord,
4
+ optionalString,
5
+ requireNonNegativeInteger,
6
+ requireOperation,
7
+ requireProtocol,
8
+ requireString,
9
+ } from "./parse-helpers";
10
+
11
+ const NOUN = "a listSubscribers input";
12
+
13
+ export interface ListSubscribersInput {
14
+ readonly operation: "listSubscribers";
15
+ readonly protocol: ProtocolVersion;
16
+ readonly name: string;
17
+ }
18
+
19
+ export function parseListSubscribersInput(value: unknown): ListSubscribersInput {
20
+ const record = asRecord(value, NOUN);
21
+ requireOperation(record, "listSubscribers", NOUN);
22
+ requireProtocol(record, NOUN);
23
+ const name = requireString(record, "name", NOUN);
24
+ return { operation: "listSubscribers", protocol: PROTOCOL_VERSION, name };
25
+ }
26
+
27
+ export function serializeListSubscribersInput(input: ListSubscribersInput): string {
28
+ return JSON.stringify(input);
29
+ }
30
+
31
+ export interface SubscriberSummary {
32
+ readonly sid: string;
33
+ readonly shortId: string;
34
+ /** The session's self-chosen name (subscribe's `as`, session_names), when it ever set one. */
35
+ readonly as?: string;
36
+ /** The directory the session launched from (session_dirs), when the daemon has observed one. */
37
+ readonly launchDir?: string;
38
+ readonly updatedAt: string;
39
+ }
40
+
41
+ function parseSubscriberSummary(value: unknown): SubscriberSummary {
42
+ const record = asRecord(value, "a subscriber summary");
43
+ const sid = requireString(record, "sid", "a subscriber summary");
44
+ const shortId = requireString(record, "shortId", "a subscriber summary");
45
+ const as = optionalString(record, "as", "a subscriber summary");
46
+ const launchDir = optionalString(record, "launchDir", "a subscriber summary");
47
+ const updatedAt = requireString(record, "updatedAt", "a subscriber summary");
48
+ return {
49
+ sid,
50
+ shortId,
51
+ ...(as !== undefined ? { as } : {}),
52
+ ...(launchDir !== undefined ? { launchDir } : {}),
53
+ updatedAt,
54
+ };
55
+ }
56
+
57
+ export interface ListSubscribersResult {
58
+ readonly name: string;
59
+ readonly count: number;
60
+ readonly subscribers: readonly SubscriberSummary[];
61
+ }
62
+
63
+ export function parseListSubscribersResult(value: unknown): ListSubscribersResult {
64
+ const record = asRecord(value, "a listSubscribers result");
65
+ const name = requireString(record, "name", "a listSubscribers result");
66
+ const count = requireNonNegativeInteger(record, "count", "a listSubscribers result");
67
+ if (!Array.isArray(record.subscribers)) {
68
+ throw new Error("a listSubscribers result carries an array of subscribers");
69
+ }
70
+ return { name, count, subscribers: record.subscribers.map(parseSubscriberSummary) };
71
+ }
72
+
73
+ export function serializeListSubscribersResult(result: ListSubscribersResult): string {
74
+ return JSON.stringify(result);
75
+ }