@graphorin/protocol 0.5.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.
@@ -0,0 +1,70 @@
1
+ //#region src/subprotocol.d.ts
2
+ /**
3
+ * Subprotocol identifier + negotiation helpers for the Graphorin
4
+ * WebSocket protocol.
5
+ *
6
+ * Clients announce supported subprotocols in the
7
+ * `Sec-WebSocket-Protocol` upgrade header. The server is expected to
8
+ * pick exactly one and echo it back; mismatches abort the handshake
9
+ * per RFC 6455 § 4. Browsers also accept additional comma-separated
10
+ * tokens — Graphorin uses this slot to attach a single-use ticket
11
+ * via the `ticket.<value>` form (the WebSocket browser API does not
12
+ * accept arbitrary headers, so the ticket has to ride the
13
+ * subprotocol channel).
14
+ *
15
+ * @packageDocumentation
16
+ */
17
+ /**
18
+ * Canonical subprotocol identifier for the v1 wire format.
19
+ *
20
+ * @stable
21
+ */
22
+ declare const SUBPROTOCOL_NAME = "graphorin.protocol.v1";
23
+ /**
24
+ * Wire-format major version literal carried on every message body.
25
+ * The pair `(SUBPROTOCOL_NAME, PROTOCOL_VERSION)` is the binding
26
+ * contract a client commits to when it receives a successful upgrade.
27
+ *
28
+ * @stable
29
+ */
30
+ declare const PROTOCOL_VERSION = "1";
31
+ /**
32
+ * Prefix for the single-use ticket that browser clients attach to
33
+ * the `Sec-WebSocket-Protocol` header. The server's upgrade handler
34
+ * splits the comma-separated list, finds the first
35
+ * `ticket.<value>` token, and validates the value against the
36
+ * in-memory ticket store before granting the connection.
37
+ *
38
+ * @stable
39
+ */
40
+ declare const TICKET_SUBPROTOCOL_PREFIX = "ticket.";
41
+ /**
42
+ * Format a ticket value as a `Sec-WebSocket-Protocol` token suitable
43
+ * for browser clients (which cannot attach an `Authorization`
44
+ * header on the WebSocket upgrade). The companion server helper is
45
+ * {@link parseTicketSubprotocol}.
46
+ *
47
+ * @stable
48
+ */
49
+ declare function formatTicketSubprotocol(ticket: string): string;
50
+ /**
51
+ * Extract the ticket value from a single comma-separated client list
52
+ * (e.g. `'graphorin.protocol.v1, ticket.abc-123'`). Returns
53
+ * `undefined` if no `ticket.*` token is present. Whitespace around
54
+ * each comma-separated token is ignored.
55
+ *
56
+ * @stable
57
+ */
58
+ declare function parseTicketSubprotocol(clientList: string | ReadonlyArray<string>): string | undefined;
59
+ /**
60
+ * Pick the single subprotocol the server should echo back. Returns
61
+ * `SUBPROTOCOL_NAME` when the client offered it, or `null` when no
62
+ * compatible variant was advertised. The function ignores `ticket.*`
63
+ * tokens — those are handled separately via {@link parseTicketSubprotocol}.
64
+ *
65
+ * @stable
66
+ */
67
+ declare function negotiateSubprotocol(clientList: string | ReadonlyArray<string>): string | null;
68
+ //#endregion
69
+ export { PROTOCOL_VERSION, SUBPROTOCOL_NAME, TICKET_SUBPROTOCOL_PREFIX, formatTicketSubprotocol, negotiateSubprotocol, parseTicketSubprotocol };
70
+ //# sourceMappingURL=subprotocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subprotocol.d.ts","names":[],"sources":["../src/subprotocol.ts"],"sourcesContent":[],"mappings":";;AAqBA;AASA;AAWA;AAUA;AAeA;AAoBA;;;;;;;;;;;;;;cAjEa,gBAAA;;;;;;;;cASA,gBAAA;;;;;;;;;;cAWA,yBAAA;;;;;;;;;iBAUG,uBAAA;;;;;;;;;iBAeA,sBAAA,sBACO;;;;;;;;;iBAmBP,oBAAA,sBAA0C"}
@@ -0,0 +1,87 @@
1
+ //#region src/subprotocol.ts
2
+ /**
3
+ * Subprotocol identifier + negotiation helpers for the Graphorin
4
+ * WebSocket protocol.
5
+ *
6
+ * Clients announce supported subprotocols in the
7
+ * `Sec-WebSocket-Protocol` upgrade header. The server is expected to
8
+ * pick exactly one and echo it back; mismatches abort the handshake
9
+ * per RFC 6455 § 4. Browsers also accept additional comma-separated
10
+ * tokens — Graphorin uses this slot to attach a single-use ticket
11
+ * via the `ticket.<value>` form (the WebSocket browser API does not
12
+ * accept arbitrary headers, so the ticket has to ride the
13
+ * subprotocol channel).
14
+ *
15
+ * @packageDocumentation
16
+ */
17
+ /**
18
+ * Canonical subprotocol identifier for the v1 wire format.
19
+ *
20
+ * @stable
21
+ */
22
+ const SUBPROTOCOL_NAME = "graphorin.protocol.v1";
23
+ /**
24
+ * Wire-format major version literal carried on every message body.
25
+ * The pair `(SUBPROTOCOL_NAME, PROTOCOL_VERSION)` is the binding
26
+ * contract a client commits to when it receives a successful upgrade.
27
+ *
28
+ * @stable
29
+ */
30
+ const PROTOCOL_VERSION = "1";
31
+ /**
32
+ * Prefix for the single-use ticket that browser clients attach to
33
+ * the `Sec-WebSocket-Protocol` header. The server's upgrade handler
34
+ * splits the comma-separated list, finds the first
35
+ * `ticket.<value>` token, and validates the value against the
36
+ * in-memory ticket store before granting the connection.
37
+ *
38
+ * @stable
39
+ */
40
+ const TICKET_SUBPROTOCOL_PREFIX = "ticket.";
41
+ /**
42
+ * Format a ticket value as a `Sec-WebSocket-Protocol` token suitable
43
+ * for browser clients (which cannot attach an `Authorization`
44
+ * header on the WebSocket upgrade). The companion server helper is
45
+ * {@link parseTicketSubprotocol}.
46
+ *
47
+ * @stable
48
+ */
49
+ function formatTicketSubprotocol(ticket) {
50
+ if (typeof ticket !== "string" || ticket.length === 0) throw new TypeError("formatTicketSubprotocol: ticket must be a non-empty string.");
51
+ return `${TICKET_SUBPROTOCOL_PREFIX}${ticket}`;
52
+ }
53
+ /**
54
+ * Extract the ticket value from a single comma-separated client list
55
+ * (e.g. `'graphorin.protocol.v1, ticket.abc-123'`). Returns
56
+ * `undefined` if no `ticket.*` token is present. Whitespace around
57
+ * each comma-separated token is ignored.
58
+ *
59
+ * @stable
60
+ */
61
+ function parseTicketSubprotocol(clientList) {
62
+ for (const token of normalizeTokens(clientList)) if (token.startsWith(TICKET_SUBPROTOCOL_PREFIX)) {
63
+ const value = token.slice(7);
64
+ if (value.length > 0) return value;
65
+ }
66
+ }
67
+ /**
68
+ * Pick the single subprotocol the server should echo back. Returns
69
+ * `SUBPROTOCOL_NAME` when the client offered it, or `null` when no
70
+ * compatible variant was advertised. The function ignores `ticket.*`
71
+ * tokens — those are handled separately via {@link parseTicketSubprotocol}.
72
+ *
73
+ * @stable
74
+ */
75
+ function negotiateSubprotocol(clientList) {
76
+ for (const token of normalizeTokens(clientList)) if (token === SUBPROTOCOL_NAME) return SUBPROTOCOL_NAME;
77
+ return null;
78
+ }
79
+ function normalizeTokens(input) {
80
+ if (Array.isArray(input)) return input.flatMap((entry) => entry.split(",")).map((token) => token.trim()).filter((token) => token.length > 0);
81
+ if (typeof input !== "string") return [];
82
+ return input.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
83
+ }
84
+
85
+ //#endregion
86
+ export { PROTOCOL_VERSION, SUBPROTOCOL_NAME, TICKET_SUBPROTOCOL_PREFIX, formatTicketSubprotocol, negotiateSubprotocol, parseTicketSubprotocol };
87
+ //# sourceMappingURL=subprotocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subprotocol.js","names":[],"sources":["../src/subprotocol.ts"],"sourcesContent":["/**\n * Subprotocol identifier + negotiation helpers for the Graphorin\n * WebSocket protocol.\n *\n * Clients announce supported subprotocols in the\n * `Sec-WebSocket-Protocol` upgrade header. The server is expected to\n * pick exactly one and echo it back; mismatches abort the handshake\n * per RFC 6455 § 4. Browsers also accept additional comma-separated\n * tokens — Graphorin uses this slot to attach a single-use ticket\n * via the `ticket.<value>` form (the WebSocket browser API does not\n * accept arbitrary headers, so the ticket has to ride the\n * subprotocol channel).\n *\n * @packageDocumentation\n */\n\n/**\n * Canonical subprotocol identifier for the v1 wire format.\n *\n * @stable\n */\nexport const SUBPROTOCOL_NAME = 'graphorin.protocol.v1';\n\n/**\n * Wire-format major version literal carried on every message body.\n * The pair `(SUBPROTOCOL_NAME, PROTOCOL_VERSION)` is the binding\n * contract a client commits to when it receives a successful upgrade.\n *\n * @stable\n */\nexport const PROTOCOL_VERSION = '1';\n\n/**\n * Prefix for the single-use ticket that browser clients attach to\n * the `Sec-WebSocket-Protocol` header. The server's upgrade handler\n * splits the comma-separated list, finds the first\n * `ticket.<value>` token, and validates the value against the\n * in-memory ticket store before granting the connection.\n *\n * @stable\n */\nexport const TICKET_SUBPROTOCOL_PREFIX = 'ticket.';\n\n/**\n * Format a ticket value as a `Sec-WebSocket-Protocol` token suitable\n * for browser clients (which cannot attach an `Authorization`\n * header on the WebSocket upgrade). The companion server helper is\n * {@link parseTicketSubprotocol}.\n *\n * @stable\n */\nexport function formatTicketSubprotocol(ticket: string): string {\n if (typeof ticket !== 'string' || ticket.length === 0) {\n throw new TypeError('formatTicketSubprotocol: ticket must be a non-empty string.');\n }\n return `${TICKET_SUBPROTOCOL_PREFIX}${ticket}`;\n}\n\n/**\n * Extract the ticket value from a single comma-separated client list\n * (e.g. `'graphorin.protocol.v1, ticket.abc-123'`). Returns\n * `undefined` if no `ticket.*` token is present. Whitespace around\n * each comma-separated token is ignored.\n *\n * @stable\n */\nexport function parseTicketSubprotocol(\n clientList: string | ReadonlyArray<string>,\n): string | undefined {\n for (const token of normalizeTokens(clientList)) {\n if (token.startsWith(TICKET_SUBPROTOCOL_PREFIX)) {\n const value = token.slice(TICKET_SUBPROTOCOL_PREFIX.length);\n if (value.length > 0) return value;\n }\n }\n return undefined;\n}\n\n/**\n * Pick the single subprotocol the server should echo back. Returns\n * `SUBPROTOCOL_NAME` when the client offered it, or `null` when no\n * compatible variant was advertised. The function ignores `ticket.*`\n * tokens — those are handled separately via {@link parseTicketSubprotocol}.\n *\n * @stable\n */\nexport function negotiateSubprotocol(clientList: string | ReadonlyArray<string>): string | null {\n for (const token of normalizeTokens(clientList)) {\n if (token === SUBPROTOCOL_NAME) return SUBPROTOCOL_NAME;\n }\n return null;\n}\n\nfunction normalizeTokens(input: string | ReadonlyArray<string>): ReadonlyArray<string> {\n if (Array.isArray(input)) {\n return input\n .flatMap((entry) => entry.split(','))\n .map((token) => token.trim())\n .filter((token) => token.length > 0);\n }\n if (typeof input !== 'string') return [];\n return input\n .split(',')\n .map((token) => token.trim())\n .filter((token) => token.length > 0);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,mBAAmB;;;;;;;;AAShC,MAAa,mBAAmB;;;;;;;;;;AAWhC,MAAa,4BAA4B;;;;;;;;;AAUzC,SAAgB,wBAAwB,QAAwB;AAC9D,KAAI,OAAO,WAAW,YAAY,OAAO,WAAW,EAClD,OAAM,IAAI,UAAU,8DAA8D;AAEpF,QAAO,GAAG,4BAA4B;;;;;;;;;;AAWxC,SAAgB,uBACd,YACoB;AACpB,MAAK,MAAM,SAAS,gBAAgB,WAAW,CAC7C,KAAI,MAAM,WAAW,0BAA0B,EAAE;EAC/C,MAAM,QAAQ,MAAM,MAAM,EAAiC;AAC3D,MAAI,MAAM,SAAS,EAAG,QAAO;;;;;;;;;;;AAcnC,SAAgB,qBAAqB,YAA2D;AAC9F,MAAK,MAAM,SAAS,gBAAgB,WAAW,CAC7C,KAAI,UAAU,iBAAkB,QAAO;AAEzC,QAAO;;AAGT,SAAS,gBAAgB,OAA8D;AACrF,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MACJ,SAAS,UAAU,MAAM,MAAM,IAAI,CAAC,CACpC,KAAK,UAAU,MAAM,MAAM,CAAC,CAC5B,QAAQ,UAAU,MAAM,SAAS,EAAE;AAExC,KAAI,OAAO,UAAU,SAAU,QAAO,EAAE;AACxC,QAAO,MACJ,MAAM,IAAI,CACV,KAAK,UAAU,MAAM,MAAM,CAAC,CAC5B,QAAQ,UAAU,MAAM,SAAS,EAAE"}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@graphorin/protocol",
3
+ "version": "0.5.0",
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
+ "license": "MIT",
6
+ "author": "Oleksiy Stepurenko",
7
+ "homepage": "https://github.com/o-stepper/graphorin/tree/main/packages/protocol",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/o-stepper/graphorin.git",
11
+ "directory": "packages/protocol"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/o-stepper/graphorin/issues"
15
+ },
16
+ "keywords": [
17
+ "graphorin",
18
+ "ai",
19
+ "agents",
20
+ "framework",
21
+ "protocol",
22
+ "websocket",
23
+ "subprotocol",
24
+ "zod",
25
+ "wire-format",
26
+ "browser-friendly"
27
+ ],
28
+ "type": "module",
29
+ "engines": {
30
+ "node": ">=22.0.0"
31
+ },
32
+ "main": "./dist/index.js",
33
+ "module": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
35
+ "sideEffects": false,
36
+ "exports": {
37
+ ".": {
38
+ "types": "./dist/index.d.ts",
39
+ "import": "./dist/index.js"
40
+ },
41
+ "./client-message": {
42
+ "types": "./dist/client-message.d.ts",
43
+ "import": "./dist/client-message.js"
44
+ },
45
+ "./server-message": {
46
+ "types": "./dist/server-message.d.ts",
47
+ "import": "./dist/server-message.js"
48
+ },
49
+ "./subprotocol": {
50
+ "types": "./dist/subprotocol.d.ts",
51
+ "import": "./dist/subprotocol.js"
52
+ },
53
+ "./close-codes": {
54
+ "types": "./dist/close-codes.d.ts",
55
+ "import": "./dist/close-codes.js"
56
+ },
57
+ "./package.json": "./package.json"
58
+ },
59
+ "files": [
60
+ "dist",
61
+ "README.md",
62
+ "CHANGELOG.md",
63
+ "LICENSE"
64
+ ],
65
+ "dependencies": {
66
+ "zod": "^3.25.0"
67
+ },
68
+ "publishConfig": {
69
+ "access": "public",
70
+ "provenance": true
71
+ },
72
+ "scripts": {
73
+ "build": "tsdown",
74
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
75
+ "test": "vitest run",
76
+ "lint": "biome check .",
77
+ "clean": "rimraf dist .turbo *.tsbuildinfo"
78
+ }
79
+ }