@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright 2026 Digital Pine
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ 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/package.json CHANGED
@@ -1,9 +1,19 @@
1
1
  {
2
2
  "name": "@theruntime/protocol",
3
- "version": "0.0.0",
4
- "description": "Placeholder. The first release replaces it.",
3
+ "version": "0.0.1",
5
4
  "license": "MIT",
6
- "publishConfig": {
7
- "access": "public"
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/DigitalPine/runtime.git",
8
+ "directory": "packages/protocol"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "!**/*.test.ts",
13
+ "!**/*.test.tsx"
14
+ ],
15
+ "type": "module",
16
+ "exports": {
17
+ ".": "./src/index.ts"
8
18
  }
9
19
  }
package/src/error.ts ADDED
@@ -0,0 +1,54 @@
1
+ export const ERROR_CODES = [
2
+ "NOT_FOUND",
3
+ "INVALID_INPUT",
4
+ "CONFLICT",
5
+ "FORBIDDEN",
6
+ "INTERNAL",
7
+ ] as const;
8
+
9
+ export type ErrorCode = (typeof ERROR_CODES)[number];
10
+
11
+ export interface ProtocolError {
12
+ readonly code: ErrorCode;
13
+ readonly message: string;
14
+ readonly details?: unknown;
15
+ readonly retryable: boolean;
16
+ }
17
+
18
+ export function makeProtocolError(
19
+ code: ErrorCode,
20
+ message: string,
21
+ options: { readonly retryable: boolean; readonly details?: unknown },
22
+ ): ProtocolError {
23
+ return options.details !== undefined
24
+ ? { code, message, details: options.details, retryable: options.retryable }
25
+ : { code, message, retryable: options.retryable };
26
+ }
27
+
28
+ export function parseProtocolError(value: unknown): ProtocolError {
29
+ if (typeof value !== "object" || value === null) {
30
+ throw new Error("a protocol error is an object");
31
+ }
32
+ if (!("code" in value) || typeof value.code !== "string" || !isErrorCode(value.code)) {
33
+ throw new Error(`a protocol error names one of: ${ERROR_CODES.join(", ")}`);
34
+ }
35
+ if (!("message" in value) || typeof value.message !== "string" || value.message.length === 0) {
36
+ throw new Error("a protocol error carries a message");
37
+ }
38
+ if (!("retryable" in value) || typeof value.retryable !== "boolean") {
39
+ throw new Error("a protocol error states whether it is retryable");
40
+ }
41
+ return makeProtocolError(value.code, value.message, {
42
+ retryable: value.retryable,
43
+ details: "details" in value ? value.details : undefined,
44
+ });
45
+ }
46
+
47
+ export function serializeProtocolError(error: ProtocolError): string {
48
+ return JSON.stringify(error);
49
+ }
50
+
51
+ function isErrorCode(value: string): value is ErrorCode {
52
+ const codes: readonly string[] = ERROR_CODES;
53
+ return codes.includes(value);
54
+ }
@@ -0,0 +1,51 @@
1
+ /** The event surface, re-exported by the package barrel as one line. */
2
+ export { EVENT_KINDS, isEventKind, parseEvent, serializeEvent } from "../notifications/event";
3
+ export type { Event, EventKind } from "../notifications/event";
4
+
5
+ export {
6
+ parseEmitEventInput,
7
+ serializeEmitEventInput,
8
+ parseEmitEventResult,
9
+ serializeEmitEventResult,
10
+ } from "../operations/emit-event";
11
+ export type { EmitEventInput, EmitEventResult } from "../operations/emit-event";
12
+
13
+ export {
14
+ parseSubscribeInput,
15
+ serializeSubscribeInput,
16
+ parseSubscribeResult,
17
+ serializeSubscribeResult,
18
+ } from "../operations/subscribe";
19
+ export type { SubscribeInput, SubscribeResult } from "../operations/subscribe";
20
+
21
+ export {
22
+ parseUnsubscribeInput,
23
+ serializeUnsubscribeInput,
24
+ parseUnsubscribeResult,
25
+ serializeUnsubscribeResult,
26
+ } from "../operations/unsubscribe";
27
+ export type { UnsubscribeInput, UnsubscribeResult } from "../operations/unsubscribe";
28
+
29
+ export {
30
+ parseListSubscribersInput,
31
+ serializeListSubscribersInput,
32
+ parseListSubscribersResult,
33
+ serializeListSubscribersResult,
34
+ } from "../operations/list-subscribers";
35
+ export type {
36
+ ListSubscribersInput,
37
+ ListSubscribersResult,
38
+ SubscriberSummary,
39
+ } from "../operations/list-subscribers";
40
+
41
+ export {
42
+ parseListSubscriptionsInput,
43
+ serializeListSubscriptionsInput,
44
+ parseListSubscriptionsResult,
45
+ serializeListSubscriptionsResult,
46
+ } from "../operations/list-subscriptions";
47
+ export type {
48
+ ListSubscriptionsInput,
49
+ ListSubscriptionsResult,
50
+ SubscriptionSummary,
51
+ } from "../operations/list-subscriptions";
@@ -0,0 +1,3 @@
1
+ /** The guidance surface, re-exported by the package barrel as one line. */
2
+ export { parseGuidance, parseRegister, serializeGuidance } from "../operations/guidance";
3
+ export type { Guidance, Register } from "../operations/guidance";
package/src/index.ts ADDED
@@ -0,0 +1,46 @@
1
+ export { OPERATIONS } from "./operations";
2
+ export type { OperationName } from "./operations";
3
+
4
+ export { NOTIFICATIONS } from "./notifications";
5
+ export type { NotificationName } from "./notifications";
6
+
7
+ export { APP_ACTIONS, isAppActionName } from "./payloads";
8
+ export type { AppActionName } from "./payloads";
9
+
10
+ export { parseAppReadyPayload, serializeAppReadyPayload } from "./payloads/app-ready";
11
+ export type { AppReadyPayload } from "./payloads/app-ready";
12
+
13
+ export { CRASH_REFUSED_ACTION, parseRouteRefusal, type RouteRefusal } from "./payloads/refusal";
14
+ export { serializeRouteRefusal } from "./payloads/refusal";
15
+
16
+ export {
17
+ APP_READINESS_STATES,
18
+ APP_READY_WINDOW_MS,
19
+ isAppReadinessState,
20
+ parseAppReadiness,
21
+ serializeAppReadiness,
22
+ } from "./readiness";
23
+ export type { AppReadiness, AppReadinessState } from "./readiness";
24
+
25
+ export { PROTOCOL_VERSION } from "./version";
26
+ export type { ProtocolVersion } from "./version";
27
+
28
+ export {
29
+ ERROR_CODES,
30
+ makeProtocolError,
31
+ parseProtocolError,
32
+ serializeProtocolError,
33
+ } from "./error";
34
+ export type { ErrorCode, ProtocolError } from "./error";
35
+
36
+ export * from "./events";
37
+ export * from "./guidance";
38
+ export * from "./logs";
39
+ export * from "./quick-actions";
40
+ export * from "./records";
41
+ export * from "./routes";
42
+ export * from "./selection";
43
+ export * from "./sessions";
44
+ export * from "./snapshots";
45
+ export * from "./templates";
46
+ export * from "./toolchain";
@@ -0,0 +1,46 @@
1
+ /** The log surface, re-exported by the package barrel as one line. */
2
+ export {
3
+ isLogLevel,
4
+ isLogTag,
5
+ LOG_LEVELS,
6
+ LOG_READ_LIMIT,
7
+ LOG_READ_LIMIT_MAX,
8
+ LOG_TAGS,
9
+ meetsLevel,
10
+ parseLogLine,
11
+ parseLogReadResult,
12
+ serializeLogLine,
13
+ serializeLogReadResult,
14
+ } from "../operations/log-line";
15
+ export type {
16
+ LogEntry,
17
+ LogLevel,
18
+ LogLine,
19
+ LogReadFilter,
20
+ LogReadResult,
21
+ LogTag,
22
+ LogValue,
23
+ } from "../operations/log-line";
24
+
25
+ export { parseReadRouteLogInput, serializeReadRouteLogInput } from "../operations/read-route-log";
26
+ export type { ReadRouteLogInput } from "../operations/read-route-log";
27
+
28
+ export {
29
+ parseReadDaemonLogInput,
30
+ serializeReadDaemonLogInput,
31
+ } from "../operations/read-daemon-log";
32
+ export type { ReadDaemonLogInput } from "../operations/read-daemon-log";
33
+
34
+ export {
35
+ parseListRouteLogsInput,
36
+ serializeListRouteLogsInput,
37
+ parseListRouteLogsResult,
38
+ serializeListRouteLogsResult,
39
+ ROUTE_LOGS_LIMIT,
40
+ ROUTE_LOGS_LIMIT_MAX,
41
+ } from "../operations/list-route-logs";
42
+ export type {
43
+ ListRouteLogsInput,
44
+ ListRouteLogsResult,
45
+ RouteLogSummary,
46
+ } from "../operations/list-route-logs";
@@ -0,0 +1,62 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import {
3
+ asRecord,
4
+ requireNotification,
5
+ requireProtocol,
6
+ requireString,
7
+ } from "../operations/parse-helpers";
8
+
9
+ const NOUN = "an event";
10
+
11
+ // "app" is something the app's own code emitted. "route" is what the daemon reports about a
12
+ // route's process. "missed" is the daemon's own signal, synthesized when a subscription's
13
+ // cursor falls behind the retained log.
14
+ export const EVENT_KINDS = ["app", "route", "missed"] as const;
15
+ export type EventKind = (typeof EVENT_KINDS)[number];
16
+
17
+ export function isEventKind(value: unknown): value is EventKind {
18
+ if (typeof value !== "string") return false;
19
+ const kinds: readonly string[] = EVENT_KINDS;
20
+ return kinds.includes(value);
21
+ }
22
+
23
+ export interface Event {
24
+ readonly notification: "event";
25
+ readonly protocol: ProtocolVersion;
26
+ // The log position this event occupies. For a "missed" event, the position a
27
+ // subscription's cursor should advance to, past the pruned gap.
28
+ readonly id: number;
29
+ readonly name: string;
30
+ readonly kind: EventKind;
31
+ readonly action: string;
32
+ // Payload is JSON, validated at the boundary.
33
+ readonly payload: unknown;
34
+ }
35
+
36
+ export function parseEvent(value: unknown): Event {
37
+ const record = asRecord(value, NOUN);
38
+ requireNotification(record, "event", NOUN);
39
+ requireProtocol(record, NOUN);
40
+ if (typeof record.id !== "number" || !Number.isInteger(record.id) || record.id < 0) {
41
+ throw new Error(`${NOUN} names a non-negative integer id`);
42
+ }
43
+ const name = requireString(record, "name", NOUN);
44
+ if (!isEventKind(record.kind)) {
45
+ throw new Error(`${NOUN} names a kind of: ${EVENT_KINDS.join(", ")}`);
46
+ }
47
+ const action = requireString(record, "action", NOUN);
48
+ if (!("payload" in record)) throw new Error(`${NOUN} carries a payload`);
49
+ return {
50
+ notification: "event",
51
+ protocol: PROTOCOL_VERSION,
52
+ id: record.id,
53
+ name,
54
+ kind: record.kind,
55
+ action,
56
+ payload: record.payload,
57
+ };
58
+ }
59
+
60
+ export function serializeEvent(event: Event): string {
61
+ return JSON.stringify(event);
62
+ }
@@ -0,0 +1,5 @@
1
+ // Every notification the protocol defines. Each name's shape lives in its own file under
2
+ // ./notifications.
3
+ export const NOTIFICATIONS = ["event"] as const;
4
+
5
+ export type NotificationName = (typeof NOTIFICATIONS)[number];
@@ -0,0 +1,121 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import {
3
+ asRecord,
4
+ optionalString,
5
+ requireOperation,
6
+ requireProtocol,
7
+ requireString,
8
+ } from "./parse-helpers";
9
+
10
+ const NOUN = "an addComponent input";
11
+ const RESULT_NOUN = "an addComponent result";
12
+
13
+ /** Where a component is copied from. The runtime's own package, or the route's host project. */
14
+ export const COMPONENT_SOURCES = ["runtime", "host"] as const;
15
+
16
+ export type ComponentSource = (typeof COMPONENT_SOURCES)[number];
17
+
18
+ export function isComponentSource(value: unknown): value is ComponentSource {
19
+ if (typeof value !== "string") return false;
20
+ const sources: readonly string[] = COMPONENT_SOURCES;
21
+ return sources.includes(value);
22
+ }
23
+
24
+ export interface AddComponentInput {
25
+ readonly operation: "addComponent";
26
+ readonly protocol: ProtocolVersion;
27
+ readonly name: string;
28
+ readonly component: string;
29
+ readonly source: ComponentSource;
30
+ }
31
+
32
+ export function parseAddComponentInput(value: unknown): AddComponentInput {
33
+ const record = asRecord(value, NOUN);
34
+ requireOperation(record, "addComponent", NOUN);
35
+ requireProtocol(record, NOUN);
36
+ const name = requireString(record, "name", NOUN);
37
+ const component = requireString(record, "component", NOUN);
38
+ const source = optionalString(record, "source", NOUN);
39
+ if (source !== undefined && !isComponentSource(source)) {
40
+ throw new Error(`${NOUN}'s source is one of ${COMPONENT_SOURCES.join(", ")}`);
41
+ }
42
+ return {
43
+ operation: "addComponent",
44
+ protocol: PROTOCOL_VERSION,
45
+ name,
46
+ component,
47
+ source: source ?? "runtime",
48
+ };
49
+ }
50
+
51
+ export function serializeAddComponentInput(input: AddComponentInput): string {
52
+ return JSON.stringify(input);
53
+ }
54
+
55
+ /**
56
+ * One components-package import an app file still names after the copy. A symbol the barrel does
57
+ * not re-export as a value, a component the app does not own, and a namespace, default, or
58
+ * re-export form all stay on the package. A namespace reads as "*" and a default as "default".
59
+ */
60
+ export interface LeftImport {
61
+ /** The app's own file that carries it, relative to the app directory. */
62
+ readonly file: string;
63
+ /** The package specifier the import still names. */
64
+ readonly specifier: string;
65
+ readonly symbols: readonly string[];
66
+ }
67
+
68
+ export interface AddComponentResult {
69
+ readonly name: string;
70
+ readonly component: string;
71
+ readonly source: ComponentSource;
72
+ /** The files this call wrote, relative to the app directory. */
73
+ readonly copied: readonly string[];
74
+ /** The files the app already owned, left byte-identical. */
75
+ readonly present: readonly string[];
76
+ /** The app's own files whose imports this call pointed at the copy. */
77
+ readonly rewritten: readonly string[];
78
+ /** What the app still imports from the package, so a caller sees what the copy did not take. */
79
+ readonly left: readonly LeftImport[];
80
+ }
81
+
82
+ function stringArray(record: Record<string, unknown>, field: string): readonly string[] {
83
+ const value = record[field];
84
+ if (!Array.isArray(value) || !value.every((entry): entry is string => typeof entry === "string"))
85
+ throw new Error(`${RESULT_NOUN} names the ${field} files`);
86
+ return value;
87
+ }
88
+
89
+ function leftImports(record: Record<string, unknown>): readonly LeftImport[] {
90
+ const value = record.left;
91
+ if (!Array.isArray(value)) throw new Error(`${RESULT_NOUN} names the left imports`);
92
+ return value.map((entry) => {
93
+ const one = asRecord(entry, `${RESULT_NOUN}'s left import`);
94
+ return {
95
+ file: requireString(one, "file", RESULT_NOUN),
96
+ specifier: requireString(one, "specifier", RESULT_NOUN),
97
+ symbols: stringArray(one, "symbols"),
98
+ };
99
+ });
100
+ }
101
+
102
+ export function parseAddComponentResult(value: unknown): AddComponentResult {
103
+ const record = asRecord(value, RESULT_NOUN);
104
+ const name = requireString(record, "name", RESULT_NOUN);
105
+ const component = requireString(record, "component", RESULT_NOUN);
106
+ if (!isComponentSource(record.source))
107
+ throw new Error(`${RESULT_NOUN} names a known component source`);
108
+ return {
109
+ name,
110
+ component,
111
+ source: record.source,
112
+ copied: stringArray(record, "copied"),
113
+ present: stringArray(record, "present"),
114
+ rewritten: stringArray(record, "rewritten"),
115
+ left: leftImports(record),
116
+ };
117
+ }
118
+
119
+ export function serializeAddComponentResult(result: AddComponentResult): string {
120
+ return JSON.stringify(result);
121
+ }
@@ -0,0 +1,58 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import {
3
+ asRecord,
4
+ optionalString,
5
+ requireOperation,
6
+ requireProtocol,
7
+ requireString,
8
+ } from "./parse-helpers";
9
+
10
+ const NOUN = "an addPackage input";
11
+
12
+ export interface AddPackageInput {
13
+ readonly operation: "addPackage";
14
+ readonly protocol: ProtocolVersion;
15
+ readonly name: string;
16
+ readonly id?: string;
17
+ }
18
+
19
+ export function parseAddPackageInput(value: unknown): AddPackageInput {
20
+ const record = asRecord(value, NOUN);
21
+ requireOperation(record, "addPackage", NOUN);
22
+ requireProtocol(record, NOUN);
23
+ const name = requireString(record, "name", NOUN);
24
+ const id = optionalString(record, "id", NOUN);
25
+ return id !== undefined
26
+ ? { operation: "addPackage", protocol: PROTOCOL_VERSION, name, id }
27
+ : { operation: "addPackage", protocol: PROTOCOL_VERSION, name };
28
+ }
29
+
30
+ export function serializeAddPackageInput(input: AddPackageInput): string {
31
+ return JSON.stringify(input);
32
+ }
33
+
34
+ export interface AddPackageResult {
35
+ readonly name: string;
36
+ readonly id: string;
37
+ readonly installed: boolean;
38
+ readonly rebooted: readonly string[];
39
+ }
40
+
41
+ export function parseAddPackageResult(value: unknown): AddPackageResult {
42
+ const record = asRecord(value, "an addPackage result");
43
+ const name = requireString(record, "name", "an addPackage result");
44
+ const id = requireString(record, "id", "an addPackage result");
45
+ if (typeof record.installed !== "boolean")
46
+ throw new Error("an addPackage result states whether bun add actually ran");
47
+ if (
48
+ !Array.isArray(record.rebooted) ||
49
+ !record.rebooted.every((entry): entry is string => typeof entry === "string")
50
+ ) {
51
+ throw new Error("an addPackage result names the rebooted routes");
52
+ }
53
+ return { name, id, installed: record.installed, rebooted: record.rebooted };
54
+ }
55
+
56
+ export function serializeAddPackageResult(result: AddPackageResult): string {
57
+ return JSON.stringify(result);
58
+ }
@@ -0,0 +1,29 @@
1
+ import { asRecord, optionalString, requirePositiveInteger, requireString } from "./parse-helpers";
2
+ import { isRecordStatus, type RecordStatus } from "./record-status";
3
+
4
+ export interface AppRecord {
5
+ readonly id: number;
6
+ readonly name: string;
7
+ readonly action: string;
8
+ readonly status: RecordStatus;
9
+ readonly note?: string;
10
+ readonly payload: unknown;
11
+ }
12
+
13
+ export function parseAppRecord(value: unknown): AppRecord {
14
+ const record = asRecord(value, "a record");
15
+ const id = requirePositiveInteger(record, "id", "a record");
16
+ const name = requireString(record, "name", "a record");
17
+ const action = requireString(record, "action", "a record");
18
+ if (!isRecordStatus(record.status)) throw new Error("a record names a known status");
19
+ const note = optionalString(record, "note", "a record");
20
+ if (!("payload" in record)) throw new Error("a record carries a payload");
21
+ return {
22
+ id,
23
+ name,
24
+ action,
25
+ status: record.status,
26
+ ...(note !== undefined ? { note } : {}),
27
+ payload: record.payload,
28
+ };
29
+ }
@@ -0,0 +1,48 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import {
3
+ asRecord,
4
+ optionalBoolean,
5
+ requireOperation,
6
+ requireProtocol,
7
+ requireString,
8
+ } from "./parse-helpers";
9
+
10
+ const NOUN = "an archiveRoute input";
11
+
12
+ export interface ArchiveRouteInput {
13
+ readonly operation: "archiveRoute";
14
+ readonly protocol: ProtocolVersion;
15
+ readonly name: string;
16
+ // Defaults to true, as in the previous engine's archive_route tool (`args.archived !== false`).
17
+ readonly archived: boolean;
18
+ }
19
+
20
+ export function parseArchiveRouteInput(value: unknown): ArchiveRouteInput {
21
+ const record = asRecord(value, NOUN);
22
+ requireOperation(record, "archiveRoute", NOUN);
23
+ requireProtocol(record, NOUN);
24
+ const name = requireString(record, "name", NOUN);
25
+ const archived = optionalBoolean(record, "archived", NOUN) ?? true;
26
+ return { operation: "archiveRoute", protocol: PROTOCOL_VERSION, name, archived };
27
+ }
28
+
29
+ export function serializeArchiveRouteInput(input: ArchiveRouteInput): string {
30
+ return JSON.stringify(input);
31
+ }
32
+
33
+ export interface ArchiveRouteResult {
34
+ readonly name: string;
35
+ readonly archived: boolean;
36
+ }
37
+
38
+ export function parseArchiveRouteResult(value: unknown): ArchiveRouteResult {
39
+ const record = asRecord(value, "an archiveRoute result");
40
+ const name = requireString(record, "name", "an archiveRoute result");
41
+ if (typeof record.archived !== "boolean")
42
+ throw new Error("an archiveRoute result states whether the route is archived");
43
+ return { name, archived: record.archived };
44
+ }
45
+
46
+ export function serializeArchiveRouteResult(result: ArchiveRouteResult): string {
47
+ return JSON.stringify(result);
48
+ }
@@ -0,0 +1,46 @@
1
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../version";
2
+ import { asRecord, requireOperation, requireProtocol, requireString } from "./parse-helpers";
3
+
4
+ const NOUN = "a captureSnapshot input";
5
+
6
+ export interface CaptureSnapshotInput {
7
+ readonly operation: "captureSnapshot";
8
+ readonly protocol: ProtocolVersion;
9
+ readonly name: string;
10
+ }
11
+
12
+ export function parseCaptureSnapshotInput(value: unknown): CaptureSnapshotInput {
13
+ const record = asRecord(value, NOUN);
14
+ requireOperation(record, "captureSnapshot", NOUN);
15
+ requireProtocol(record, NOUN);
16
+ const name = requireString(record, "name", NOUN);
17
+ return { operation: "captureSnapshot", protocol: PROTOCOL_VERSION, name };
18
+ }
19
+
20
+ export function serializeCaptureSnapshotInput(input: CaptureSnapshotInput): string {
21
+ return JSON.stringify(input);
22
+ }
23
+
24
+ export interface CaptureSnapshotResult {
25
+ readonly name: string;
26
+ readonly dir: string;
27
+ readonly files: number;
28
+ readonly bytes: number;
29
+ }
30
+
31
+ export function parseCaptureSnapshotResult(value: unknown): CaptureSnapshotResult {
32
+ const record = asRecord(value, "a captureSnapshot result");
33
+ const name = requireString(record, "name", "a captureSnapshot result");
34
+ const dir = requireString(record, "dir", "a captureSnapshot result");
35
+ if (typeof record.files !== "number" || !Number.isInteger(record.files) || record.files < 0) {
36
+ throw new Error("a captureSnapshot result names how many files it captured");
37
+ }
38
+ if (typeof record.bytes !== "number" || !Number.isInteger(record.bytes) || record.bytes < 0) {
39
+ throw new Error("a captureSnapshot result names how many bytes it captured");
40
+ }
41
+ return { name, dir, files: record.files, bytes: record.bytes };
42
+ }
43
+
44
+ export function serializeCaptureSnapshotResult(result: CaptureSnapshotResult): string {
45
+ return JSON.stringify(result);
46
+ }