@graphorin/protocol 0.6.0 → 0.7.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.
@@ -1 +1 @@
1
- {"version":3,"file":"server-message.js","names":[],"sources":["../src/server-message.ts"],"sourcesContent":["/**\n * `ServerMessage` - discriminated union of every frame a Graphorin\n * server may push to a client. Three families share the channel:\n *\n * 1. **RPC responses** (`{ jsonrpc, id, result | error }`) -\n * correlate with a previously-issued client request.\n * 2. **Typed push events** (`{ kind: 'event', subject, type,\n * payload, eventId }`) - the streaming-first data plane;\n * consumers ignore unknown `type` strings per the agent-event\n * extensibility convention.\n * 3. **Asynchronous server frames** (`{ kind: 'lifecycle' | 'error'\n * | 'pong' | 'subscribed' | 'unsubscribed' | 'replay-marker' }`)\n * - server-initiated messages that do not correlate with a\n * single client RPC id.\n *\n * Every frame carries the `v: '1'` literal so future revisions can\n * negotiate forward-compatible additions without a subprotocol bump.\n *\n * @packageDocumentation\n */\n\nimport { z } from 'zod';\n\nconst RpcId = z.union([z.string().min(1), z.number().int()]);\n\nconst RpcError = z\n .object({\n code: z.number().int(),\n message: z.string().min(1),\n data: z.unknown().optional(),\n })\n .strict();\n\nconst RpcSuccess = z\n .object({\n v: z.literal('1'),\n jsonrpc: z.literal('2.0'),\n id: RpcId,\n result: z.unknown(),\n })\n .strict();\n\nconst RpcFailure = z\n .object({\n v: z.literal('1'),\n jsonrpc: z.literal('2.0'),\n id: RpcId,\n error: RpcError,\n })\n .strict();\n\nconst SubscribedFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('subscribed'),\n subscriptionId: z.string().min(1),\n subject: z.string().min(1),\n snapshotEventId: z.string().min(1).optional(),\n })\n .strict();\n\nconst UnsubscribedFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('unsubscribed'),\n subscriptionId: z.string().min(1),\n })\n .strict();\n\nconst EventFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('event'),\n eventId: z.string().min(1),\n subscriptionId: z.string().min(1),\n subject: z.string().min(1),\n type: z.string().min(1),\n payload: z.unknown(),\n })\n .strict();\n\nconst LifecycleFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('lifecycle'),\n subscriptionId: z.string().min(1),\n status: z.enum(['running', 'paused', 'completed', 'aborted', 'failed']),\n reason: z.string().min(1).optional(),\n })\n .strict();\n\nconst ErrorFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('error'),\n code: z.string().min(1),\n message: z.string().min(1),\n fatal: z.boolean().optional(),\n subscriptionId: z.string().min(1).optional(),\n data: z.unknown().optional(),\n })\n .strict();\n\nconst PongFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('pong'),\n nonce: z.string().min(1).optional(),\n })\n .strict();\n\nconst ReplayMarkerFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('replay-marker'),\n subscriptionId: z.string().min(1),\n eventId: z.string().min(1),\n droppedCount: z.number().int().nonnegative().optional(),\n note: z.string().min(1).optional(),\n })\n .strict();\n\nconst KindedFrame = z.discriminatedUnion('kind', [\n SubscribedFrame,\n UnsubscribedFrame,\n EventFrame,\n LifecycleFrame,\n ErrorFrame,\n PongFrame,\n ReplayMarkerFrame,\n]);\n\nconst RpcFrame = z.union([RpcSuccess, RpcFailure]);\n\n/**\n * Zod schema for every legal server → client frame. Validation runs\n * twice in the server pipeline: first when a route handler enqueues\n * the frame onto the dispatcher's send queue (so a malformed frame\n * never escapes the process), then again on the client side to\n * defend against protocol drift.\n *\n * @stable\n */\nexport const ServerMessageSchema = z.union([RpcFrame, KindedFrame]);\n\n/**\n * Inferred TypeScript union for the `ServerMessage` discriminator.\n *\n * @stable\n */\nexport type ServerMessage = z.infer<typeof ServerMessageSchema>;\n\n/**\n * Convenience type aliases for callers that want to reference an\n * individual variant without `z.infer<typeof X>`.\n *\n * @stable\n */\nexport type ServerEventFrame = z.infer<typeof EventFrame>;\n/** @stable */\nexport type ServerLifecycleFrame = z.infer<typeof LifecycleFrame>;\n/** @stable */\nexport type ServerErrorFrame = z.infer<typeof ErrorFrame>;\n/** @stable */\nexport type ServerSubscribedFrame = z.infer<typeof SubscribedFrame>;\n/** @stable */\nexport type ServerUnsubscribedFrame = z.infer<typeof UnsubscribedFrame>;\n/** @stable */\nexport type ServerPongFrame = z.infer<typeof PongFrame>;\n/** @stable */\nexport type ServerReplayMarkerFrame = z.infer<typeof ReplayMarkerFrame>;\n/** @stable */\nexport type ServerRpcSuccess = z.infer<typeof RpcSuccess>;\n/** @stable */\nexport type ServerRpcFailure = z.infer<typeof RpcFailure>;\n\n/**\n * Type guard helpers, one per discriminator. The narrow over the\n * `ServerMessage` union without forcing consumers to memorize the\n * exact field names.\n *\n * @stable\n */\nexport function isEventFrame(message: ServerMessage): message is ServerEventFrame {\n return 'kind' in message && message.kind === 'event';\n}\n\n/** @stable */\nexport function isLifecycleFrame(message: ServerMessage): message is ServerLifecycleFrame {\n return 'kind' in message && message.kind === 'lifecycle';\n}\n\n/** @stable */\nexport function isErrorFrame(message: ServerMessage): message is ServerErrorFrame {\n return 'kind' in message && message.kind === 'error';\n}\n\n/** @stable */\nexport function isSubscribedFrame(message: ServerMessage): message is ServerSubscribedFrame {\n return 'kind' in message && message.kind === 'subscribed';\n}\n\n/** @stable */\nexport function isUnsubscribedFrame(message: ServerMessage): message is ServerUnsubscribedFrame {\n return 'kind' in message && message.kind === 'unsubscribed';\n}\n\n/** @stable */\nexport function isPongFrame(message: ServerMessage): message is ServerPongFrame {\n return 'kind' in message && message.kind === 'pong';\n}\n\n/** @stable */\nexport function isReplayMarkerFrame(message: ServerMessage): message is ServerReplayMarkerFrame {\n return 'kind' in message && message.kind === 'replay-marker';\n}\n\n/** @stable */\nexport function isRpcSuccess(message: ServerMessage): message is ServerRpcSuccess {\n return 'jsonrpc' in message && message.jsonrpc === '2.0' && 'result' in message;\n}\n\n/** @stable */\nexport function isRpcFailure(message: ServerMessage): message is ServerRpcFailure {\n return 'jsonrpc' in message && message.jsonrpc === '2.0' && 'error' in message;\n}\n\n/**\n * Stable JSON-RPC error code catalogue used by the server when\n * surfacing routine failures (per JSON-RPC 2.0 § 5.1 + Graphorin\n * extensions). Application-level errors use codes in the\n * implementation-defined range (`-32000` … `-32099`).\n *\n * @stable\n */\nexport const RPC_ERROR_CODES = Object.freeze({\n PARSE_ERROR: -32700,\n INVALID_REQUEST: -32600,\n METHOD_NOT_FOUND: -32601,\n INVALID_PARAMS: -32602,\n INTERNAL_ERROR: -32603,\n AUTH_REQUIRED: -32001,\n AUTH_INVALID: -32002,\n SCOPE_DENIED: -32003,\n RATE_LIMITED: -32004,\n PROTOCOL_VIOLATION: -32005,\n RUN_NOT_FOUND: -32010,\n SUBSCRIPTION_NOT_FOUND: -32011,\n} as const);\n"],"mappings":"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"}
1
+ {"version":3,"file":"server-message.js","names":[],"sources":["../src/server-message.ts"],"sourcesContent":["/**\n * `ServerMessage` - discriminated union of every frame a Graphorin\n * server may push to a client. Three families share the channel:\n *\n * 1. **RPC responses** (`{ jsonrpc, id, result | error }`) -\n * correlate with a previously-issued client request.\n * 2. **Typed push events** (`{ kind: 'event', subject, type,\n * payload, eventId }`) - the streaming-first data plane;\n * consumers ignore unknown `type` strings per the agent-event\n * extensibility convention.\n * 3. **Asynchronous server frames** (`{ kind: 'lifecycle' | 'error'\n * | 'pong' | 'subscribed' | 'unsubscribed' | 'replay-marker' }`)\n * - server-initiated messages that do not correlate with a\n * single client RPC id.\n *\n * Versioning contract (W-109, honest edition): the frame ENVELOPE -\n * the set of `kind`s, the fields of the control frames, and the\n * `v: '1'` literal - is validated strictly (`.strict()`, literal `v`)\n * on BOTH server and client, and evolves only in lockstep with both\n * sides shipping together: there is NO negotiation, and a frame with\n * `v: '2'` is rejected outright. That is the deployment model of the\n * 0.x line. The additive extension points are deliberate and inside\n * the envelope: the `type` string + `payload: z.unknown()` of event\n * frames (consumers ignore unknown `type`s per the agent-event\n * convention), `result: z.unknown()` of RPC responses, and the\n * `capabilities` record of the `initialize` result (which today the\n * shipped client does not consume). If additive envelope evolution is\n * ever promised publicly, that is a separate negotiation feature -\n * do not loosen `.strict()` for it piecemeal.\n *\n * @packageDocumentation\n */\n\nimport { z } from 'zod';\n\nconst RpcId = z.union([z.string().min(1), z.number().int()]);\n\nconst RpcError = z\n .object({\n code: z.number().int(),\n message: z.string().min(1),\n data: z.unknown().optional(),\n })\n .strict();\n\nconst RpcSuccess = z\n .object({\n v: z.literal('1'),\n jsonrpc: z.literal('2.0'),\n id: RpcId,\n result: z.unknown(),\n })\n .strict();\n\nconst RpcFailure = z\n .object({\n v: z.literal('1'),\n jsonrpc: z.literal('2.0'),\n id: RpcId,\n error: RpcError,\n })\n .strict();\n\nconst SubscribedFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('subscribed'),\n subscriptionId: z.string().min(1),\n subject: z.string().min(1),\n snapshotEventId: z.string().min(1).optional(),\n })\n .strict();\n\nconst UnsubscribedFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('unsubscribed'),\n subscriptionId: z.string().min(1),\n })\n .strict();\n\n// The runtime schema keeps `payload` opaque so this package stays\n// zod-only (no `@graphorin/core` dependency, not even type-only - it\n// would force a resolvable dependency for the d.ts). For agent-run\n// subjects the actual payload shape is the JSON-safe `WireAgentEvent`\n// union documented in `@graphorin/core` (decode with\n// `fromWireAgentEvent`); the cross-package round-trip is pinned by a\n// gate test in core.\nconst EventFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('event'),\n eventId: z.string().min(1),\n subscriptionId: z.string().min(1),\n subject: z.string().min(1),\n type: z.string().min(1),\n payload: z.unknown(),\n })\n .strict();\n\nconst LifecycleFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('lifecycle'),\n subscriptionId: z.string().min(1),\n status: z.enum(['running', 'paused', 'completed', 'aborted', 'failed']),\n reason: z.string().min(1).optional(),\n })\n .strict();\n\nconst ErrorFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('error'),\n code: z.string().min(1),\n message: z.string().min(1),\n fatal: z.boolean().optional(),\n subscriptionId: z.string().min(1).optional(),\n data: z.unknown().optional(),\n })\n .strict();\n\nconst PongFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('pong'),\n nonce: z.string().min(1).optional(),\n })\n .strict();\n\nconst ReplayMarkerFrame = z\n .object({\n v: z.literal('1'),\n kind: z.literal('replay-marker'),\n subscriptionId: z.string().min(1),\n eventId: z.string().min(1),\n droppedCount: z.number().int().nonnegative().optional(),\n note: z.string().min(1).optional(),\n })\n .strict();\n\nconst KindedFrame = z.discriminatedUnion('kind', [\n SubscribedFrame,\n UnsubscribedFrame,\n EventFrame,\n LifecycleFrame,\n ErrorFrame,\n PongFrame,\n ReplayMarkerFrame,\n]);\n\nconst RpcFrame = z.union([RpcSuccess, RpcFailure]);\n\n/**\n * Zod schema for every legal server → client frame. Validation runs\n * twice in the server pipeline: first when a route handler enqueues\n * the frame onto the dispatcher's send queue (so a malformed frame\n * never escapes the process), then again on the client side to\n * defend against protocol drift.\n *\n * @stable\n */\nexport const ServerMessageSchema = z.union([RpcFrame, KindedFrame]);\n\n/**\n * Inferred TypeScript union for the `ServerMessage` discriminator.\n *\n * @stable\n */\nexport type ServerMessage = z.infer<typeof ServerMessageSchema>;\n\n/**\n * Convenience type aliases for callers that want to reference an\n * individual variant without `z.infer<typeof X>`.\n *\n * @stable\n */\nexport type ServerEventFrame = z.infer<typeof EventFrame>;\n/** @stable */\nexport type ServerLifecycleFrame = z.infer<typeof LifecycleFrame>;\n/** @stable */\nexport type ServerErrorFrame = z.infer<typeof ErrorFrame>;\n/** @stable */\nexport type ServerSubscribedFrame = z.infer<typeof SubscribedFrame>;\n/** @stable */\nexport type ServerUnsubscribedFrame = z.infer<typeof UnsubscribedFrame>;\n/** @stable */\nexport type ServerPongFrame = z.infer<typeof PongFrame>;\n/** @stable */\nexport type ServerReplayMarkerFrame = z.infer<typeof ReplayMarkerFrame>;\n/** @stable */\nexport type ServerRpcSuccess = z.infer<typeof RpcSuccess>;\n/** @stable */\nexport type ServerRpcFailure = z.infer<typeof RpcFailure>;\n\n/**\n * Type guard helpers, one per discriminator. The narrow over the\n * `ServerMessage` union without forcing consumers to memorize the\n * exact field names.\n *\n * @stable\n */\nexport function isEventFrame(message: ServerMessage): message is ServerEventFrame {\n return 'kind' in message && message.kind === 'event';\n}\n\n/** @stable */\nexport function isLifecycleFrame(message: ServerMessage): message is ServerLifecycleFrame {\n return 'kind' in message && message.kind === 'lifecycle';\n}\n\n/** @stable */\nexport function isErrorFrame(message: ServerMessage): message is ServerErrorFrame {\n return 'kind' in message && message.kind === 'error';\n}\n\n/** @stable */\nexport function isSubscribedFrame(message: ServerMessage): message is ServerSubscribedFrame {\n return 'kind' in message && message.kind === 'subscribed';\n}\n\n/** @stable */\nexport function isUnsubscribedFrame(message: ServerMessage): message is ServerUnsubscribedFrame {\n return 'kind' in message && message.kind === 'unsubscribed';\n}\n\n/** @stable */\nexport function isPongFrame(message: ServerMessage): message is ServerPongFrame {\n return 'kind' in message && message.kind === 'pong';\n}\n\n/** @stable */\nexport function isReplayMarkerFrame(message: ServerMessage): message is ServerReplayMarkerFrame {\n return 'kind' in message && message.kind === 'replay-marker';\n}\n\n/** @stable */\nexport function isRpcSuccess(message: ServerMessage): message is ServerRpcSuccess {\n return 'jsonrpc' in message && message.jsonrpc === '2.0' && 'result' in message;\n}\n\n/** @stable */\nexport function isRpcFailure(message: ServerMessage): message is ServerRpcFailure {\n return 'jsonrpc' in message && message.jsonrpc === '2.0' && 'error' in message;\n}\n\n/**\n * Stable JSON-RPC error code catalogue used by the server when\n * surfacing routine failures (per JSON-RPC 2.0 § 5.1 + Graphorin\n * extensions). Application-level errors use codes in the\n * implementation-defined range (`-32000` … `-32099`).\n *\n * @stable\n */\nexport const RPC_ERROR_CODES = Object.freeze({\n PARSE_ERROR: -32700,\n INVALID_REQUEST: -32600,\n METHOD_NOT_FOUND: -32601,\n INVALID_PARAMS: -32602,\n INTERNAL_ERROR: -32603,\n AUTH_REQUIRED: -32001,\n AUTH_INVALID: -32002,\n SCOPE_DENIED: -32003,\n RATE_LIMITED: -32004,\n PROTOCOL_VIOLATION: -32005,\n RUN_NOT_FOUND: -32010,\n SUBSCRIPTION_NOT_FOUND: -32011,\n} as const);\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphorin/protocol",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Wire protocol contract for the Graphorin framework: Zod schemas + TypeScript types for the WebSocket subprotocol `graphorin.protocol.v1`. Defines the `ClientMessage` and `ServerMessage` discriminated unions, the JSON-RPC-shaped control channel (`initialize` / `subscription.subscribe` / `subscription.unsubscribe` / `run.cancel` / `ping`), the typed event push frames (`{ kind: 'event', subject, type, payload, eventId }`), the asynchronous server-initiated error frames (`{ kind: 'error', code, message }`), the subprotocol negotiation helpers, and the close-code taxonomy (4001 auth.required, 4002 auth.invalid, 4003 auth.revoked, 4004 auth.scope_denied, 4005 rate.limited, 4006 flow.throttled, 4007 server.shutdown, 4008 protocol.violation). Browser-friendly: zero Node-only dependencies; the only runtime dependency is `zod`. Created and maintained by Oleksiy Stepurenko.",
5
5
  "license": "MIT",
6
6
  "author": "Oleksiy Stepurenko",
@@ -27,7 +27,7 @@
27
27
  ],
28
28
  "type": "module",
29
29
  "engines": {
30
- "node": ">=22.0.0"
30
+ "node": ">=22.12.0"
31
31
  },
32
32
  "main": "./dist/index.js",
33
33
  "module": "./dist/index.js",
@@ -36,28 +36,29 @@
36
36
  "exports": {
37
37
  ".": {
38
38
  "types": "./dist/index.d.ts",
39
- "import": "./dist/index.js"
39
+ "default": "./dist/index.js"
40
40
  },
41
41
  "./client-message": {
42
42
  "types": "./dist/client-message.d.ts",
43
- "import": "./dist/client-message.js"
43
+ "default": "./dist/client-message.js"
44
44
  },
45
45
  "./server-message": {
46
46
  "types": "./dist/server-message.d.ts",
47
- "import": "./dist/server-message.js"
47
+ "default": "./dist/server-message.js"
48
48
  },
49
49
  "./subprotocol": {
50
50
  "types": "./dist/subprotocol.d.ts",
51
- "import": "./dist/subprotocol.js"
51
+ "default": "./dist/subprotocol.js"
52
52
  },
53
53
  "./close-codes": {
54
54
  "types": "./dist/close-codes.d.ts",
55
- "import": "./dist/close-codes.js"
55
+ "default": "./dist/close-codes.js"
56
56
  },
57
57
  "./package.json": "./package.json"
58
58
  },
59
59
  "files": [
60
60
  "dist",
61
+ "src",
61
62
  "README.md",
62
63
  "CHANGELOG.md",
63
64
  "LICENSE"
@@ -0,0 +1,200 @@
1
+ /**
2
+ * `ClientMessage` - discriminated union of every frame a Graphorin
3
+ * WebSocket client may send to the server. The wire is hybrid: the
4
+ * control plane uses JSON-RPC-shaped requests / notifications; the
5
+ * data plane uses typed push events emitted exclusively by the server
6
+ * (see `./server-message.ts`).
7
+ *
8
+ * Every frame carries the `v: '1'` literal so future revisions can
9
+ * negotiate forward-compatible additions without a subprotocol bump.
10
+ * The matching subprotocol identifier is `graphorin.protocol.v1`
11
+ * (see `./subprotocol.ts`).
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+
16
+ import { z } from 'zod';
17
+
18
+ const RpcId = z.union([z.string().min(1), z.number().int()]);
19
+
20
+ const InitializeParams = z
21
+ .object({
22
+ clientInfo: z
23
+ .object({
24
+ name: z.string().min(1),
25
+ version: z.string().min(1),
26
+ })
27
+ .strict(),
28
+ capabilities: z.record(z.string(), z.unknown()).optional(),
29
+ })
30
+ .strict();
31
+
32
+ const SubscribeParams = z
33
+ .object({
34
+ subject: z.string().min(1),
35
+ // IP-21: `sinceEventId` is the resumption cursor. `lastSequenceId` was a
36
+ // dead wire field - the client never set it and the server never read it.
37
+ sinceEventId: z.string().min(1).optional(),
38
+ })
39
+ .strict();
40
+
41
+ const UnsubscribeParams = z
42
+ .object({
43
+ subscriptionId: z.string().min(1),
44
+ })
45
+ .strict();
46
+
47
+ const RunCancelParams = z
48
+ .object({
49
+ runId: z.string().min(1),
50
+ reason: z.string().min(1).optional(),
51
+ drain: z.boolean().optional(),
52
+ onPendingApprovals: z.enum(['deny', 'preserve']).optional(),
53
+ })
54
+ .strict();
55
+
56
+ const PingParams = z
57
+ .object({
58
+ nonce: z.string().min(1).optional(),
59
+ })
60
+ .strict()
61
+ .optional();
62
+
63
+ const InitializeRequest = z
64
+ .object({
65
+ v: z.literal('1'),
66
+ jsonrpc: z.literal('2.0'),
67
+ id: RpcId,
68
+ method: z.literal('initialize'),
69
+ params: InitializeParams,
70
+ })
71
+ .strict();
72
+
73
+ const SubscribeRequest = z
74
+ .object({
75
+ v: z.literal('1'),
76
+ jsonrpc: z.literal('2.0'),
77
+ id: RpcId,
78
+ method: z.literal('subscription.subscribe'),
79
+ params: SubscribeParams,
80
+ })
81
+ .strict();
82
+
83
+ const UnsubscribeRequest = z
84
+ .object({
85
+ v: z.literal('1'),
86
+ jsonrpc: z.literal('2.0'),
87
+ id: RpcId,
88
+ method: z.literal('subscription.unsubscribe'),
89
+ params: UnsubscribeParams,
90
+ })
91
+ .strict();
92
+
93
+ const RunCancelRequest = z
94
+ .object({
95
+ v: z.literal('1'),
96
+ jsonrpc: z.literal('2.0'),
97
+ id: RpcId,
98
+ method: z.literal('run.cancel'),
99
+ params: RunCancelParams,
100
+ })
101
+ .strict();
102
+
103
+ const PingRequest = z
104
+ .object({
105
+ v: z.literal('1'),
106
+ jsonrpc: z.literal('2.0'),
107
+ id: RpcId,
108
+ method: z.literal('ping'),
109
+ params: PingParams,
110
+ })
111
+ .strict();
112
+
113
+ const CancelledNotification = z
114
+ .object({
115
+ v: z.literal('1'),
116
+ jsonrpc: z.literal('2.0'),
117
+ method: z.literal('notifications/cancelled'),
118
+ params: z.object({ requestId: z.string().min(1) }).strict(),
119
+ })
120
+ .strict();
121
+
122
+ /**
123
+ * Zod schema for every legal client → server frame. Use
124
+ * {@link ClientMessageSchema}.safeParse() inside the server upgrade
125
+ * handler before dispatching to the corresponding subscription /
126
+ * cancel / ping handler.
127
+ *
128
+ * @stable
129
+ */
130
+ export const ClientMessageSchema = z.discriminatedUnion('method', [
131
+ InitializeRequest,
132
+ SubscribeRequest,
133
+ UnsubscribeRequest,
134
+ RunCancelRequest,
135
+ PingRequest,
136
+ CancelledNotification,
137
+ ]);
138
+
139
+ /**
140
+ * Inferred TypeScript union for the `ClientMessage` discriminator. A
141
+ * value satisfying this type round-trips through
142
+ * {@link ClientMessageSchema} without throwing.
143
+ *
144
+ * @stable
145
+ */
146
+ export type ClientMessage = z.infer<typeof ClientMessageSchema>;
147
+
148
+ /**
149
+ * Convenience type for the JSON-RPC `id` slot. Matches the Graphorin
150
+ * subset (string + integer; no `null`, no float).
151
+ *
152
+ * @stable
153
+ */
154
+ export type ClientMessageId = z.infer<typeof RpcId>;
155
+
156
+ /**
157
+ * Type guard helpers - one per `method` literal - so consumers can
158
+ * narrow the `ClientMessage` union without re-stringifying the
159
+ * discriminator.
160
+ *
161
+ * @stable
162
+ */
163
+ export function isInitializeRequest(
164
+ message: ClientMessage,
165
+ ): message is z.infer<typeof InitializeRequest> {
166
+ return message.method === 'initialize';
167
+ }
168
+
169
+ /** @stable */
170
+ export function isSubscribeRequest(
171
+ message: ClientMessage,
172
+ ): message is z.infer<typeof SubscribeRequest> {
173
+ return message.method === 'subscription.subscribe';
174
+ }
175
+
176
+ /** @stable */
177
+ export function isUnsubscribeRequest(
178
+ message: ClientMessage,
179
+ ): message is z.infer<typeof UnsubscribeRequest> {
180
+ return message.method === 'subscription.unsubscribe';
181
+ }
182
+
183
+ /** @stable */
184
+ export function isRunCancelRequest(
185
+ message: ClientMessage,
186
+ ): message is z.infer<typeof RunCancelRequest> {
187
+ return message.method === 'run.cancel';
188
+ }
189
+
190
+ /** @stable */
191
+ export function isPingRequest(message: ClientMessage): message is z.infer<typeof PingRequest> {
192
+ return message.method === 'ping';
193
+ }
194
+
195
+ /** @stable */
196
+ export function isCancelledNotification(
197
+ message: ClientMessage,
198
+ ): message is z.infer<typeof CancelledNotification> {
199
+ return message.method === 'notifications/cancelled';
200
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Custom WebSocket close-code taxonomy used by `@graphorin/server`
3
+ * and `@graphorin/client`. The numeric values live in the 4xxx
4
+ * application-private range per RFC 6455 § 7.4.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ /**
10
+ * Discriminator for every Graphorin-defined close code. The
11
+ * matching numeric value is exposed via {@link CLOSE_CODE_VALUES}.
12
+ *
13
+ * @stable
14
+ */
15
+ export type GraphorinCloseReason =
16
+ | 'auth.required'
17
+ | 'auth.invalid'
18
+ | 'auth.revoked'
19
+ | 'auth.scope_denied'
20
+ | 'rate.limited'
21
+ | 'flow.throttled'
22
+ | 'server.shutdown'
23
+ | 'protocol.violation';
24
+
25
+ /**
26
+ * Numeric close-code constants. The pair `(value, reason)` round-trips
27
+ * via {@link closeCodeReason} / {@link closeCodeFor}.
28
+ *
29
+ * @stable
30
+ */
31
+ export const CLOSE_CODE_VALUES = Object.freeze({
32
+ 'auth.required': 4001,
33
+ 'auth.invalid': 4002,
34
+ 'auth.revoked': 4003,
35
+ 'auth.scope_denied': 4004,
36
+ 'rate.limited': 4005,
37
+ 'flow.throttled': 4006,
38
+ 'server.shutdown': 4007,
39
+ 'protocol.violation': 4008,
40
+ } as const satisfies Record<GraphorinCloseReason, number>);
41
+
42
+ /**
43
+ * Return the numeric close code for a Graphorin reason discriminator.
44
+ *
45
+ * @stable
46
+ */
47
+ export function closeCodeFor(reason: GraphorinCloseReason): number {
48
+ return CLOSE_CODE_VALUES[reason];
49
+ }
50
+
51
+ const REVERSE_LOOKUP: ReadonlyMap<number, GraphorinCloseReason> = new Map(
52
+ (Object.entries(CLOSE_CODE_VALUES) as ReadonlyArray<[GraphorinCloseReason, number]>).map(
53
+ ([reason, code]) => [code, reason],
54
+ ),
55
+ );
56
+
57
+ /**
58
+ * Resolve a numeric close code back to its Graphorin reason
59
+ * discriminator. Returns `undefined` for codes outside the
60
+ * Graphorin range so callers can still surface the raw RFC 6455
61
+ * reason for unrelated codes.
62
+ *
63
+ * @stable
64
+ */
65
+ export function closeCodeReason(code: number): GraphorinCloseReason | undefined {
66
+ return REVERSE_LOOKUP.get(code);
67
+ }
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `@graphorin/protocol` - wire-format contract for the Graphorin
3
+ * WebSocket subprotocol.
4
+ *
5
+ * The package is the **single source of truth** for the shape of
6
+ * every frame exchanged over `wss://.../v1/ws`. Both
7
+ * `@graphorin/server` and `@graphorin/client` import their schemas
8
+ * from here so the two implementations cannot drift.
9
+ *
10
+ * Browser-friendly: the only runtime dependency is
11
+ * [`zod`](https://zod.dev). No Node-only imports, no DOM-only
12
+ * imports.
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+
17
+ /** Canonical version constant, derived from `package.json` at build time. */
18
+ import pkg from '../package.json' with { type: 'json' };
19
+
20
+ export const VERSION: string = pkg.version;
21
+
22
+ export * from './client-message.js';
23
+ export * from './close-codes.js';
24
+ export * from './server-message.js';
25
+ export * from './subprotocol.js';
@@ -0,0 +1,268 @@
1
+ /**
2
+ * `ServerMessage` - discriminated union of every frame a Graphorin
3
+ * server may push to a client. Three families share the channel:
4
+ *
5
+ * 1. **RPC responses** (`{ jsonrpc, id, result | error }`) -
6
+ * correlate with a previously-issued client request.
7
+ * 2. **Typed push events** (`{ kind: 'event', subject, type,
8
+ * payload, eventId }`) - the streaming-first data plane;
9
+ * consumers ignore unknown `type` strings per the agent-event
10
+ * extensibility convention.
11
+ * 3. **Asynchronous server frames** (`{ kind: 'lifecycle' | 'error'
12
+ * | 'pong' | 'subscribed' | 'unsubscribed' | 'replay-marker' }`)
13
+ * - server-initiated messages that do not correlate with a
14
+ * single client RPC id.
15
+ *
16
+ * Versioning contract (W-109, honest edition): the frame ENVELOPE -
17
+ * the set of `kind`s, the fields of the control frames, and the
18
+ * `v: '1'` literal - is validated strictly (`.strict()`, literal `v`)
19
+ * on BOTH server and client, and evolves only in lockstep with both
20
+ * sides shipping together: there is NO negotiation, and a frame with
21
+ * `v: '2'` is rejected outright. That is the deployment model of the
22
+ * 0.x line. The additive extension points are deliberate and inside
23
+ * the envelope: the `type` string + `payload: z.unknown()` of event
24
+ * frames (consumers ignore unknown `type`s per the agent-event
25
+ * convention), `result: z.unknown()` of RPC responses, and the
26
+ * `capabilities` record of the `initialize` result (which today the
27
+ * shipped client does not consume). If additive envelope evolution is
28
+ * ever promised publicly, that is a separate negotiation feature -
29
+ * do not loosen `.strict()` for it piecemeal.
30
+ *
31
+ * @packageDocumentation
32
+ */
33
+
34
+ import { z } from 'zod';
35
+
36
+ const RpcId = z.union([z.string().min(1), z.number().int()]);
37
+
38
+ const RpcError = z
39
+ .object({
40
+ code: z.number().int(),
41
+ message: z.string().min(1),
42
+ data: z.unknown().optional(),
43
+ })
44
+ .strict();
45
+
46
+ const RpcSuccess = z
47
+ .object({
48
+ v: z.literal('1'),
49
+ jsonrpc: z.literal('2.0'),
50
+ id: RpcId,
51
+ result: z.unknown(),
52
+ })
53
+ .strict();
54
+
55
+ const RpcFailure = z
56
+ .object({
57
+ v: z.literal('1'),
58
+ jsonrpc: z.literal('2.0'),
59
+ id: RpcId,
60
+ error: RpcError,
61
+ })
62
+ .strict();
63
+
64
+ const SubscribedFrame = z
65
+ .object({
66
+ v: z.literal('1'),
67
+ kind: z.literal('subscribed'),
68
+ subscriptionId: z.string().min(1),
69
+ subject: z.string().min(1),
70
+ snapshotEventId: z.string().min(1).optional(),
71
+ })
72
+ .strict();
73
+
74
+ const UnsubscribedFrame = z
75
+ .object({
76
+ v: z.literal('1'),
77
+ kind: z.literal('unsubscribed'),
78
+ subscriptionId: z.string().min(1),
79
+ })
80
+ .strict();
81
+
82
+ // The runtime schema keeps `payload` opaque so this package stays
83
+ // zod-only (no `@graphorin/core` dependency, not even type-only - it
84
+ // would force a resolvable dependency for the d.ts). For agent-run
85
+ // subjects the actual payload shape is the JSON-safe `WireAgentEvent`
86
+ // union documented in `@graphorin/core` (decode with
87
+ // `fromWireAgentEvent`); the cross-package round-trip is pinned by a
88
+ // gate test in core.
89
+ const EventFrame = z
90
+ .object({
91
+ v: z.literal('1'),
92
+ kind: z.literal('event'),
93
+ eventId: z.string().min(1),
94
+ subscriptionId: z.string().min(1),
95
+ subject: z.string().min(1),
96
+ type: z.string().min(1),
97
+ payload: z.unknown(),
98
+ })
99
+ .strict();
100
+
101
+ const LifecycleFrame = z
102
+ .object({
103
+ v: z.literal('1'),
104
+ kind: z.literal('lifecycle'),
105
+ subscriptionId: z.string().min(1),
106
+ status: z.enum(['running', 'paused', 'completed', 'aborted', 'failed']),
107
+ reason: z.string().min(1).optional(),
108
+ })
109
+ .strict();
110
+
111
+ const ErrorFrame = z
112
+ .object({
113
+ v: z.literal('1'),
114
+ kind: z.literal('error'),
115
+ code: z.string().min(1),
116
+ message: z.string().min(1),
117
+ fatal: z.boolean().optional(),
118
+ subscriptionId: z.string().min(1).optional(),
119
+ data: z.unknown().optional(),
120
+ })
121
+ .strict();
122
+
123
+ const PongFrame = z
124
+ .object({
125
+ v: z.literal('1'),
126
+ kind: z.literal('pong'),
127
+ nonce: z.string().min(1).optional(),
128
+ })
129
+ .strict();
130
+
131
+ const ReplayMarkerFrame = z
132
+ .object({
133
+ v: z.literal('1'),
134
+ kind: z.literal('replay-marker'),
135
+ subscriptionId: z.string().min(1),
136
+ eventId: z.string().min(1),
137
+ droppedCount: z.number().int().nonnegative().optional(),
138
+ note: z.string().min(1).optional(),
139
+ })
140
+ .strict();
141
+
142
+ const KindedFrame = z.discriminatedUnion('kind', [
143
+ SubscribedFrame,
144
+ UnsubscribedFrame,
145
+ EventFrame,
146
+ LifecycleFrame,
147
+ ErrorFrame,
148
+ PongFrame,
149
+ ReplayMarkerFrame,
150
+ ]);
151
+
152
+ const RpcFrame = z.union([RpcSuccess, RpcFailure]);
153
+
154
+ /**
155
+ * Zod schema for every legal server → client frame. Validation runs
156
+ * twice in the server pipeline: first when a route handler enqueues
157
+ * the frame onto the dispatcher's send queue (so a malformed frame
158
+ * never escapes the process), then again on the client side to
159
+ * defend against protocol drift.
160
+ *
161
+ * @stable
162
+ */
163
+ export const ServerMessageSchema = z.union([RpcFrame, KindedFrame]);
164
+
165
+ /**
166
+ * Inferred TypeScript union for the `ServerMessage` discriminator.
167
+ *
168
+ * @stable
169
+ */
170
+ export type ServerMessage = z.infer<typeof ServerMessageSchema>;
171
+
172
+ /**
173
+ * Convenience type aliases for callers that want to reference an
174
+ * individual variant without `z.infer<typeof X>`.
175
+ *
176
+ * @stable
177
+ */
178
+ export type ServerEventFrame = z.infer<typeof EventFrame>;
179
+ /** @stable */
180
+ export type ServerLifecycleFrame = z.infer<typeof LifecycleFrame>;
181
+ /** @stable */
182
+ export type ServerErrorFrame = z.infer<typeof ErrorFrame>;
183
+ /** @stable */
184
+ export type ServerSubscribedFrame = z.infer<typeof SubscribedFrame>;
185
+ /** @stable */
186
+ export type ServerUnsubscribedFrame = z.infer<typeof UnsubscribedFrame>;
187
+ /** @stable */
188
+ export type ServerPongFrame = z.infer<typeof PongFrame>;
189
+ /** @stable */
190
+ export type ServerReplayMarkerFrame = z.infer<typeof ReplayMarkerFrame>;
191
+ /** @stable */
192
+ export type ServerRpcSuccess = z.infer<typeof RpcSuccess>;
193
+ /** @stable */
194
+ export type ServerRpcFailure = z.infer<typeof RpcFailure>;
195
+
196
+ /**
197
+ * Type guard helpers, one per discriminator. The narrow over the
198
+ * `ServerMessage` union without forcing consumers to memorize the
199
+ * exact field names.
200
+ *
201
+ * @stable
202
+ */
203
+ export function isEventFrame(message: ServerMessage): message is ServerEventFrame {
204
+ return 'kind' in message && message.kind === 'event';
205
+ }
206
+
207
+ /** @stable */
208
+ export function isLifecycleFrame(message: ServerMessage): message is ServerLifecycleFrame {
209
+ return 'kind' in message && message.kind === 'lifecycle';
210
+ }
211
+
212
+ /** @stable */
213
+ export function isErrorFrame(message: ServerMessage): message is ServerErrorFrame {
214
+ return 'kind' in message && message.kind === 'error';
215
+ }
216
+
217
+ /** @stable */
218
+ export function isSubscribedFrame(message: ServerMessage): message is ServerSubscribedFrame {
219
+ return 'kind' in message && message.kind === 'subscribed';
220
+ }
221
+
222
+ /** @stable */
223
+ export function isUnsubscribedFrame(message: ServerMessage): message is ServerUnsubscribedFrame {
224
+ return 'kind' in message && message.kind === 'unsubscribed';
225
+ }
226
+
227
+ /** @stable */
228
+ export function isPongFrame(message: ServerMessage): message is ServerPongFrame {
229
+ return 'kind' in message && message.kind === 'pong';
230
+ }
231
+
232
+ /** @stable */
233
+ export function isReplayMarkerFrame(message: ServerMessage): message is ServerReplayMarkerFrame {
234
+ return 'kind' in message && message.kind === 'replay-marker';
235
+ }
236
+
237
+ /** @stable */
238
+ export function isRpcSuccess(message: ServerMessage): message is ServerRpcSuccess {
239
+ return 'jsonrpc' in message && message.jsonrpc === '2.0' && 'result' in message;
240
+ }
241
+
242
+ /** @stable */
243
+ export function isRpcFailure(message: ServerMessage): message is ServerRpcFailure {
244
+ return 'jsonrpc' in message && message.jsonrpc === '2.0' && 'error' in message;
245
+ }
246
+
247
+ /**
248
+ * Stable JSON-RPC error code catalogue used by the server when
249
+ * surfacing routine failures (per JSON-RPC 2.0 § 5.1 + Graphorin
250
+ * extensions). Application-level errors use codes in the
251
+ * implementation-defined range (`-32000` … `-32099`).
252
+ *
253
+ * @stable
254
+ */
255
+ export const RPC_ERROR_CODES = Object.freeze({
256
+ PARSE_ERROR: -32700,
257
+ INVALID_REQUEST: -32600,
258
+ METHOD_NOT_FOUND: -32601,
259
+ INVALID_PARAMS: -32602,
260
+ INTERNAL_ERROR: -32603,
261
+ AUTH_REQUIRED: -32001,
262
+ AUTH_INVALID: -32002,
263
+ SCOPE_DENIED: -32003,
264
+ RATE_LIMITED: -32004,
265
+ PROTOCOL_VIOLATION: -32005,
266
+ RUN_NOT_FOUND: -32010,
267
+ SUBSCRIPTION_NOT_FOUND: -32011,
268
+ } as const);