@super-line/core 0.1.0 → 0.3.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.
- package/README.md +1 -1
- package/dist/index.cjs +85 -8
- package/dist/index.d.cts +311 -25
- package/dist/index.d.ts +311 -25
- package/dist/index.js +80 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @super-line/core
|
|
2
2
|
|
|
3
|
-
Shared core for [**super-line**](https://mertdogar.github.io/super-line/) — end-to-end typesafe WebSockets for TypeScript. This package holds the pieces both ends import: `defineContract`, runtime validation, the `
|
|
3
|
+
Shared core for [**super-line**](https://mertdogar.github.io/super-line/) — end-to-end typesafe WebSockets for TypeScript. This package holds the pieces both ends import: `defineContract`, runtime validation, the `SuperLineError` model, and the `Serializer` / `Adapter` interfaces.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
pnpm add @super-line/core zod
|
package/dist/index.cjs
CHANGED
|
@@ -20,8 +20,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
INSPECTOR_ROLE: () => INSPECTOR_ROLE,
|
|
24
|
+
INSPECTOR_SUBPROTOCOL: () => INSPECTOR_SUBPROTOCOL,
|
|
25
|
+
InspectorContract: () => InspectorContract,
|
|
23
26
|
PROTOCOL: () => PROTOCOL,
|
|
24
|
-
|
|
27
|
+
SuperLineError: () => SuperLineError,
|
|
28
|
+
classifyContract: () => classifyContract,
|
|
25
29
|
defineContract: () => defineContract,
|
|
26
30
|
jsonSerializer: () => jsonSerializer,
|
|
27
31
|
validate: () => validate,
|
|
@@ -30,19 +34,19 @@ __export(index_exports, {
|
|
|
30
34
|
module.exports = __toCommonJS(index_exports);
|
|
31
35
|
|
|
32
36
|
// src/errors.ts
|
|
33
|
-
var
|
|
37
|
+
var SuperLineError = class extends Error {
|
|
34
38
|
/** The typed error code (e.g. `'FORBIDDEN'`), available on the client. */
|
|
35
39
|
code;
|
|
36
40
|
/** Optional structured data attached to the error, delivered to the client. */
|
|
37
41
|
data;
|
|
38
42
|
/**
|
|
39
|
-
* @param code - a {@link
|
|
43
|
+
* @param code - a {@link SuperLineErrorCode} or custom string.
|
|
40
44
|
* @param message - human-readable message (defaults to `code`).
|
|
41
45
|
* @param data - optional structured payload delivered to the client.
|
|
42
46
|
*/
|
|
43
47
|
constructor(code, message, data) {
|
|
44
48
|
super(message ?? code);
|
|
45
|
-
this.name = "
|
|
49
|
+
this.name = "SuperLineError";
|
|
46
50
|
this.code = code;
|
|
47
51
|
this.data = data;
|
|
48
52
|
}
|
|
@@ -63,27 +67,100 @@ async function validate(schema, value) {
|
|
|
63
67
|
let result = schema["~standard"].validate(value);
|
|
64
68
|
if (result instanceof Promise) result = await result;
|
|
65
69
|
if (result.issues) {
|
|
66
|
-
throw new
|
|
70
|
+
throw new SuperLineError("VALIDATION", "Validation failed", result.issues);
|
|
67
71
|
}
|
|
68
72
|
return result.value;
|
|
69
73
|
}
|
|
70
74
|
function validateSync(schema, value) {
|
|
71
75
|
const result = schema["~standard"].validate(value);
|
|
72
76
|
if (result instanceof Promise) {
|
|
73
|
-
throw new
|
|
77
|
+
throw new SuperLineError("INTERNAL", "Async schema not supported for synchronous validation");
|
|
74
78
|
}
|
|
75
79
|
if (result.issues) {
|
|
76
|
-
throw new
|
|
80
|
+
throw new SuperLineError("VALIDATION", "Validation failed", result.issues);
|
|
77
81
|
}
|
|
78
82
|
return result.value;
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
// src/inspector.ts
|
|
86
|
+
var INSPECTOR_SUBPROTOCOL = "superline.inspector.v1";
|
|
87
|
+
var INSPECTOR_ROLE = "inspector";
|
|
88
|
+
function s() {
|
|
89
|
+
return {
|
|
90
|
+
"~standard": {
|
|
91
|
+
version: 1,
|
|
92
|
+
vendor: "super-line-inspector",
|
|
93
|
+
validate: (value) => ({ value })
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
var InspectorContract = defineContract({
|
|
98
|
+
roles: {
|
|
99
|
+
inspector: {
|
|
100
|
+
clientToServer: {
|
|
101
|
+
getContract: { input: s(), output: s() },
|
|
102
|
+
getTopology: { input: s(), output: s() },
|
|
103
|
+
listConnections: { input: s(), output: s() },
|
|
104
|
+
getNode: { input: s(), output: s() },
|
|
105
|
+
getConn: { input: s(), output: s() }
|
|
106
|
+
},
|
|
107
|
+
serverToClient: {
|
|
108
|
+
events: { payload: s(), subscribe: true }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
function withSchemas(msg, schemas, convert) {
|
|
114
|
+
if (!convert) return msg;
|
|
115
|
+
for (const [key, schema] of Object.entries(schemas)) {
|
|
116
|
+
const value = convert(schema);
|
|
117
|
+
if (value !== void 0) msg[key] = value;
|
|
118
|
+
}
|
|
119
|
+
return msg;
|
|
120
|
+
}
|
|
121
|
+
function classifyDirectional(d, convert) {
|
|
122
|
+
const clientToServer = [];
|
|
123
|
+
const serverToClient = [];
|
|
124
|
+
for (const [name, def] of Object.entries(d?.clientToServer ?? {})) {
|
|
125
|
+
clientToServer.push(
|
|
126
|
+
withSchemas({ name, flavor: "request" }, { input: def.input, output: def.output }, convert)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
for (const [name, def] of Object.entries(d?.serverToClient ?? {})) {
|
|
130
|
+
if ("input" in def) {
|
|
131
|
+
serverToClient.push(
|
|
132
|
+
withSchemas({ name, flavor: "serverRequest" }, { input: def.input, output: def.output }, convert)
|
|
133
|
+
);
|
|
134
|
+
} else {
|
|
135
|
+
serverToClient.push(
|
|
136
|
+
withSchemas(
|
|
137
|
+
{ name, flavor: def.subscribe === true ? "topic" : "event" },
|
|
138
|
+
{ payload: def.payload },
|
|
139
|
+
convert
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { clientToServer, serverToClient };
|
|
145
|
+
}
|
|
146
|
+
function classifyContract(contract, convert) {
|
|
147
|
+
const roles = {};
|
|
148
|
+
for (const [role, block] of Object.entries(contract.roles)) {
|
|
149
|
+
roles[role] = classifyDirectional(block, convert);
|
|
150
|
+
}
|
|
151
|
+
return { shared: classifyDirectional(contract.shared, convert), roles };
|
|
152
|
+
}
|
|
153
|
+
|
|
81
154
|
// src/wire.ts
|
|
82
155
|
var PROTOCOL = "superline.v1";
|
|
83
156
|
// Annotate the CommonJS export names for ESM import in node:
|
|
84
157
|
0 && (module.exports = {
|
|
158
|
+
INSPECTOR_ROLE,
|
|
159
|
+
INSPECTOR_SUBPROTOCOL,
|
|
160
|
+
InspectorContract,
|
|
85
161
|
PROTOCOL,
|
|
86
|
-
|
|
162
|
+
SuperLineError,
|
|
163
|
+
classifyContract,
|
|
87
164
|
defineContract,
|
|
88
165
|
jsonSerializer,
|
|
89
166
|
validate,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
2
|
|
|
3
3
|
/** The built-in error codes super-line uses across the wire. */
|
|
4
|
-
type
|
|
4
|
+
type SuperLineErrorCode = 'BAD_REQUEST' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'NOT_FOUND' | 'TIMEOUT' | 'VALIDATION' | 'DISCONNECTED' | 'INTERNAL';
|
|
5
5
|
/** A built-in code or any custom string (autocomplete keeps the known set). */
|
|
6
|
-
type ErrorCode =
|
|
6
|
+
type ErrorCode = SuperLineErrorCode | (string & {});
|
|
7
7
|
/**
|
|
8
8
|
* The error type carried end-to-end. Throw one from a handler and the client's
|
|
9
9
|
* promise rejects with the same `code` (and optional `data`). Unknown throws
|
|
10
10
|
* become `INTERNAL` so server internals aren't leaked.
|
|
11
11
|
*/
|
|
12
|
-
declare class
|
|
12
|
+
declare class SuperLineError<Data = unknown> extends Error {
|
|
13
13
|
/** The typed error code (e.g. `'FORBIDDEN'`), available on the client. */
|
|
14
14
|
readonly code: ErrorCode;
|
|
15
15
|
/** Optional structured data attached to the error, delivered to the client. */
|
|
16
16
|
readonly data?: Data;
|
|
17
17
|
/**
|
|
18
|
-
* @param code - a {@link
|
|
18
|
+
* @param code - a {@link SuperLineErrorCode} or custom string.
|
|
19
19
|
* @param message - human-readable message (defaults to `code`).
|
|
20
20
|
* @param data - optional structured payload delivered to the client.
|
|
21
21
|
*/
|
|
@@ -36,8 +36,8 @@ interface Serializer {
|
|
|
36
36
|
declare const jsonSerializer: Serializer;
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* Cross-node fan-out seam. Rooms, topics, and
|
|
40
|
-
* channel pub/sub. A node subscribes to a channel only while it has a local member,
|
|
39
|
+
* Cross-node fan-out seam. Rooms, topics, and the cluster event bus all compile down
|
|
40
|
+
* to channel pub/sub. A node subscribes to a channel only while it has a local member,
|
|
41
41
|
* and publishes always go through the adapter (the in-memory adapter loops back),
|
|
42
42
|
* so a node delivers to its local members on receipt — one code path, no double-send.
|
|
43
43
|
*
|
|
@@ -55,6 +55,75 @@ interface Adapter {
|
|
|
55
55
|
onMessage(handler: (channel: string, payload: string | Uint8Array) => void): void;
|
|
56
56
|
/** Optional teardown (e.g. close Redis connections). */
|
|
57
57
|
close?(): void | Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Optional cluster-wide presence directory. Powers `srv.cluster.*` and
|
|
60
|
+
* `srv.isOnline`. The in-memory and Redis adapters implement it; cluster
|
|
61
|
+
* queries throw a clear error on an adapter that doesn't.
|
|
62
|
+
*/
|
|
63
|
+
presence?: PresenceStore;
|
|
64
|
+
}
|
|
65
|
+
/** A serializable snapshot of a connection, shared cluster-wide via the {@link PresenceStore}. */
|
|
66
|
+
interface ConnDescriptor {
|
|
67
|
+
/** The connection's server-assigned id. */
|
|
68
|
+
id: string;
|
|
69
|
+
/** The connection's role. */
|
|
70
|
+
role: string;
|
|
71
|
+
/** The node that holds this connection. */
|
|
72
|
+
nodeId: string;
|
|
73
|
+
/** The node's friendly name (defaults to a short slice of `nodeId`). */
|
|
74
|
+
nodeName: string;
|
|
75
|
+
/** When the connection was accepted (`Date.now()`). */
|
|
76
|
+
connectedAt: number;
|
|
77
|
+
/** The stable user key from the server's `identify` hook, if any. */
|
|
78
|
+
userId?: string;
|
|
79
|
+
/** Room memberships (topics and node-local `lastPongAt` are not included). */
|
|
80
|
+
rooms: string[];
|
|
81
|
+
/** Extra fields contributed by the server's `describeConn` hook. */
|
|
82
|
+
[extra: string]: unknown;
|
|
83
|
+
}
|
|
84
|
+
/** Per-node aggregate, returned by {@link PresenceStore.topology}. */
|
|
85
|
+
interface NodeStat {
|
|
86
|
+
/** The node's id. */
|
|
87
|
+
nodeId: string;
|
|
88
|
+
/** The node's friendly name (defaults to a short slice of `nodeId`). */
|
|
89
|
+
nodeName: string;
|
|
90
|
+
/** Number of connections on the node. */
|
|
91
|
+
connections: number;
|
|
92
|
+
/** Number of distinct rooms with members on the node. */
|
|
93
|
+
rooms: number;
|
|
94
|
+
/** Whether the node is currently live (heartbeat fresh). */
|
|
95
|
+
alive: boolean;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Cluster-wide presence directory: a query/addressbook layer kept in the shared
|
|
99
|
+
* substrate (in-memory bus or Redis). Live message delivery does NOT read this —
|
|
100
|
+
* it exists only to answer `srv.cluster.*` / `srv.isOnline`.
|
|
101
|
+
*/
|
|
102
|
+
interface PresenceStore {
|
|
103
|
+
/** Record (or replace) a connection's descriptor. */
|
|
104
|
+
set(descriptor: ConnDescriptor): void | Promise<void>;
|
|
105
|
+
/** Remove a connection's descriptor. */
|
|
106
|
+
del(connId: string): void | Promise<void>;
|
|
107
|
+
/** Refresh this node's liveness (heartbeat). */
|
|
108
|
+
beat(nodeId: string): void | Promise<void>;
|
|
109
|
+
/** Remove all of a node's connections + liveness (graceful shutdown cleanup). */
|
|
110
|
+
clearNode(nodeId: string): void | Promise<void>;
|
|
111
|
+
/** Add a room to a connection's membership. */
|
|
112
|
+
addRoom(connId: string, room: string): void | Promise<void>;
|
|
113
|
+
/** Remove a room from a connection's membership. */
|
|
114
|
+
removeRoom(connId: string, room: string): void | Promise<void>;
|
|
115
|
+
/** All live connection descriptors across the cluster. */
|
|
116
|
+
list(): ConnDescriptor[] | Promise<ConnDescriptor[]>;
|
|
117
|
+
/** One connection's descriptor, if present. */
|
|
118
|
+
get(connId: string): (ConnDescriptor | undefined) | Promise<ConnDescriptor | undefined>;
|
|
119
|
+
/** Descriptors for a given user key. */
|
|
120
|
+
byUser(userId: string): ConnDescriptor[] | Promise<ConnDescriptor[]>;
|
|
121
|
+
/** Descriptors that are members of `room`. */
|
|
122
|
+
roomMembers(room: string): ConnDescriptor[] | Promise<ConnDescriptor[]>;
|
|
123
|
+
/** Total live connection count across the cluster. */
|
|
124
|
+
count(): number | Promise<number>;
|
|
125
|
+
/** Per-node aggregates. */
|
|
126
|
+
topology(): NodeStat[] | Promise<NodeStat[]>;
|
|
58
127
|
}
|
|
59
128
|
|
|
60
129
|
/** Any [Standard Schema](https://standardschema.dev) validator (Zod, Valibot, ArkType…). */
|
|
@@ -80,25 +149,41 @@ interface ServerMessageDef {
|
|
|
80
149
|
/** When `true`, clients opt in via `client.subscribe(...)` (a topic). Omit for a push event. */
|
|
81
150
|
subscribe?: boolean;
|
|
82
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* A server→client request (request/response). The server sends `input`; the
|
|
154
|
+
* client's `implement` handler returns `output`. Lives in `serverToClient`
|
|
155
|
+
* alongside events and topics, distinguished by having `input`.
|
|
156
|
+
*/
|
|
157
|
+
interface ServerRequestDef {
|
|
158
|
+
/** Schema for the request payload the server sends. */
|
|
159
|
+
input: Schema;
|
|
160
|
+
/** Schema for the reply the client returns. */
|
|
161
|
+
output: Schema;
|
|
162
|
+
}
|
|
163
|
+
/** A `serverToClient` entry: a push event, a subscribable topic, or a server→client request. */
|
|
164
|
+
type ServerEntry = ServerMessageDef | ServerRequestDef;
|
|
83
165
|
/** The two directions within a `shared` or role block. */
|
|
84
166
|
interface Directional {
|
|
85
167
|
/** Requests this side may call (client→server). */
|
|
86
168
|
clientToServer?: Record<string, RequestDef>;
|
|
87
|
-
/** Events
|
|
88
|
-
serverToClient?: Record<string,
|
|
169
|
+
/** Events, topics, and server→client requests this side may receive. */
|
|
170
|
+
serverToClient?: Record<string, ServerEntry>;
|
|
171
|
+
}
|
|
172
|
+
/** A role block: its directions plus an optional `data` schema typing `conn.data`. */
|
|
173
|
+
interface RoleBlock extends Directional {
|
|
174
|
+
/** Schema for this role's mutable per-connection `conn.data` (server-side scratch state). */
|
|
175
|
+
data?: Schema;
|
|
89
176
|
}
|
|
90
177
|
/**
|
|
91
178
|
* The single source of truth, imported by both server and client. Split by
|
|
92
179
|
* **direction** and scoped by **role**: a `shared` base every role inherits,
|
|
93
|
-
* plus one block per role.
|
|
180
|
+
* plus one block per role.
|
|
94
181
|
*/
|
|
95
182
|
interface Contract {
|
|
96
183
|
/** Surface common to every role (merged into each role's effective surface). */
|
|
97
184
|
shared?: Directional;
|
|
98
185
|
/** Per-role surfaces. A connection's role selects which one (plus `shared`) it sees. */
|
|
99
|
-
roles: Record<string,
|
|
100
|
-
/** Typed node-to-node event payloads, for {@link "@super-line/server"!}'s `emitServer`/`onServer`. */
|
|
101
|
-
serverToServer?: Record<string, Schema>;
|
|
186
|
+
roles: Record<string, RoleBlock>;
|
|
102
187
|
}
|
|
103
188
|
/**
|
|
104
189
|
* Define a contract. An identity function — `const` preserves literal keys and
|
|
@@ -118,7 +203,6 @@ interface Contract {
|
|
|
118
203
|
* user: { clientToServer: { say: { input: z.object({ text: z.string() }), output: z.object({ id: z.string() }) } } },
|
|
119
204
|
* agent: { clientToServer: { announce: { input: z.object({ text: z.string() }), output: z.object({ id: z.string() }) } } },
|
|
120
205
|
* },
|
|
121
|
-
* serverToServer: { rebalance: z.object({ shard: z.number() }) },
|
|
122
206
|
* })
|
|
123
207
|
* ```
|
|
124
208
|
*/
|
|
@@ -129,10 +213,12 @@ type CtsOf<D> = D extends {
|
|
|
129
213
|
clientToServer: infer M extends Record<string, RequestDef>;
|
|
130
214
|
} ? M : {};
|
|
131
215
|
type StcOf<D> = D extends {
|
|
132
|
-
serverToClient: infer M extends Record<string,
|
|
216
|
+
serverToClient: infer M extends Record<string, ServerEntry>;
|
|
133
217
|
} ? M : {};
|
|
134
218
|
type EventsOf<M> = {
|
|
135
219
|
[K in keyof M as M[K] extends {
|
|
220
|
+
input: Schema;
|
|
221
|
+
} ? never : M[K] extends {
|
|
136
222
|
subscribe: true;
|
|
137
223
|
} ? never : K]: M[K];
|
|
138
224
|
};
|
|
@@ -141,6 +227,11 @@ type TopicsOf<M> = {
|
|
|
141
227
|
subscribe: true;
|
|
142
228
|
} ? K : never]: M[K];
|
|
143
229
|
};
|
|
230
|
+
type ServerReqOf<M> = {
|
|
231
|
+
[K in keyof M as M[K] extends {
|
|
232
|
+
input: Schema;
|
|
233
|
+
} ? K : never]: M[K];
|
|
234
|
+
};
|
|
144
235
|
/** A role's effective request map: `shared` ∪ `roles[R]` client→server requests. */
|
|
145
236
|
type Requests<C extends Contract, R extends RoleOf<C>> = CtsOf<C['shared']> & CtsOf<C['roles'][R]>;
|
|
146
237
|
/** A role's effective server→client map (events and topics combined). */
|
|
@@ -159,8 +250,16 @@ type SharedEvents<C extends Contract> = EventsOf<StcOf<C['shared']>>;
|
|
|
159
250
|
type SharedTopics<C extends Contract> = TopicsOf<StcOf<C['shared']>>;
|
|
160
251
|
/** Subscribable topics in one role's block (published via `srv.forRole(r).publish`). */
|
|
161
252
|
type RoleTopics<C extends Contract, R extends RoleOf<C>> = TopicsOf<StcOf<C['roles'][R]>>;
|
|
162
|
-
/**
|
|
163
|
-
type
|
|
253
|
+
/** A role's effective server→client requests (`shared` ∪ `roles[R]`), answered by `client.implement`. */
|
|
254
|
+
type ServerRequests<C extends Contract, R extends RoleOf<C>> = ServerReqOf<ServerMessages<C, R>>;
|
|
255
|
+
/** Server→client requests in the `shared` block (the surface `srv.toConn(id).request` can call). */
|
|
256
|
+
type SharedServerRequests<C extends Contract> = ServerReqOf<StcOf<C['shared']>>;
|
|
257
|
+
/** The typed shape of `conn.data` for role `R` (its `data` schema, or an empty object). */
|
|
258
|
+
type DataOf<C extends Contract, R extends RoleOf<C>> = C['roles'][R] extends {
|
|
259
|
+
data: infer S extends Schema;
|
|
260
|
+
} ? InferOut<S> : Record<string, never>;
|
|
261
|
+
/** Union of every role's `conn.data` shape (used where the role isn't narrowed, e.g. shared handlers). */
|
|
262
|
+
type AnyData<C extends Contract> = DataOf<C, RoleOf<C>>;
|
|
164
263
|
/** The input type a client passes for a request (pre-validation). */
|
|
165
264
|
type ClientInput<T> = T extends RequestDef ? InferIn<T['input']> : never;
|
|
166
265
|
/** The input type a server handler receives for a request (post-validation). */
|
|
@@ -171,10 +270,6 @@ type Output<T> = T extends RequestDef ? InferOut<T['output']> : never;
|
|
|
171
270
|
type EventData<T> = T extends ServerMessageDef ? InferOut<T['payload']> : never;
|
|
172
271
|
/** The data a server sends for an event/topic (pre-validation). */
|
|
173
272
|
type EmitData<T> = T extends ServerMessageDef ? InferIn<T['payload']> : never;
|
|
174
|
-
/** The data a server sends for a serverToServer event. */
|
|
175
|
-
type ServerEmit<T> = T extends Schema ? InferIn<T> : never;
|
|
176
|
-
/** The data a server receives for a serverToServer event. */
|
|
177
|
-
type ServerData<T> = T extends Schema ? InferOut<T> : never;
|
|
178
273
|
/** Infer a schema's **input** type (what you pass into the validator). */
|
|
179
274
|
type InferIn<S extends Schema> = StandardSchemaV1.InferInput<S>;
|
|
180
275
|
/** Infer a schema's **output** type (the validated result). */
|
|
@@ -185,7 +280,7 @@ type InferOut<S extends Schema> = StandardSchemaV1.InferOutput<S>;
|
|
|
185
280
|
* @param schema - the validator to run.
|
|
186
281
|
* @param value - the untrusted value to validate.
|
|
187
282
|
* @returns the parsed, typed value.
|
|
188
|
-
* @throws {@link
|
|
283
|
+
* @throws {@link SuperLineError} with code `VALIDATION` if the value doesn't match.
|
|
189
284
|
*/
|
|
190
285
|
declare function validate<S extends Schema>(schema: S, value: unknown): Promise<StandardSchemaV1.InferOutput<S>>;
|
|
191
286
|
/**
|
|
@@ -194,10 +289,182 @@ declare function validate<S extends Schema>(schema: S, value: unknown): Promise<
|
|
|
194
289
|
* @param schema - the validator to run.
|
|
195
290
|
* @param value - the untrusted value to validate.
|
|
196
291
|
* @returns the parsed, typed value.
|
|
197
|
-
* @throws {@link
|
|
292
|
+
* @throws {@link SuperLineError} with code `VALIDATION` on mismatch, or `INTERNAL` if the schema is async.
|
|
198
293
|
*/
|
|
199
294
|
declare function validateSync<S extends Schema>(schema: S, value: unknown): StandardSchemaV1.InferOutput<S>;
|
|
200
295
|
|
|
296
|
+
/** WS subprotocol the Control Center connects with; the server short-circuits auth for it. */
|
|
297
|
+
declare const INSPECTOR_SUBPROTOCOL = "superline.inspector.v1";
|
|
298
|
+
/** The reserved role minted for an inspector connection. */
|
|
299
|
+
declare const INSPECTOR_ROLE = "inspector";
|
|
300
|
+
/** How a contract message is used on the wire. */
|
|
301
|
+
type MessageFlavor = 'request' | 'event' | 'topic' | 'serverRequest';
|
|
302
|
+
/** One message in an {@link InspectedContract}. Schemas are best-effort JSON Schema, omitted when unavailable. */
|
|
303
|
+
interface InspectedMessage {
|
|
304
|
+
/** The message name (its key in the contract). */
|
|
305
|
+
name: string;
|
|
306
|
+
/** How the message is used. */
|
|
307
|
+
flavor: MessageFlavor;
|
|
308
|
+
/** Best-effort JSON Schema of the request/server-request input. */
|
|
309
|
+
input?: unknown;
|
|
310
|
+
/** Best-effort JSON Schema of the request/server-request output. */
|
|
311
|
+
output?: unknown;
|
|
312
|
+
/** Best-effort JSON Schema of an event/topic payload. */
|
|
313
|
+
payload?: unknown;
|
|
314
|
+
}
|
|
315
|
+
/** The two directions of a `shared` or role block, flattened for display. */
|
|
316
|
+
interface InspectedDirectional {
|
|
317
|
+
clientToServer: InspectedMessage[];
|
|
318
|
+
serverToClient: InspectedMessage[];
|
|
319
|
+
}
|
|
320
|
+
/** A serializable projection of a {@link Contract}'s structure — what `getContract` returns. */
|
|
321
|
+
interface InspectedContract {
|
|
322
|
+
shared: InspectedDirectional;
|
|
323
|
+
roles: Record<string, InspectedDirectional>;
|
|
324
|
+
}
|
|
325
|
+
/** The connected node's local view — what `getNode` returns. */
|
|
326
|
+
interface NodeView {
|
|
327
|
+
nodeId: string;
|
|
328
|
+
nodeName: string;
|
|
329
|
+
rooms: string[];
|
|
330
|
+
topics: string[];
|
|
331
|
+
}
|
|
332
|
+
/** A connection's detail — what `getConn` returns. ctx/data are node-local and best-effort safe-serialized. */
|
|
333
|
+
interface ConnView {
|
|
334
|
+
descriptor: ConnDescriptor;
|
|
335
|
+
/** Safe-serialized auth ctx; present only when the conn is on the queried node. */
|
|
336
|
+
ctx?: unknown;
|
|
337
|
+
/** Safe-serialized `conn.data`; present only when the conn is on the queried node. */
|
|
338
|
+
data?: unknown;
|
|
339
|
+
/** Whether ctx/data could be read (false for conns on another node). */
|
|
340
|
+
ctxAvailable: boolean;
|
|
341
|
+
}
|
|
342
|
+
/** A failed response/reply, carried on `msg.response` / `msg.serverReply`. */
|
|
343
|
+
interface MessageError {
|
|
344
|
+
code: string;
|
|
345
|
+
message: string;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* A live event pushed on the `events` topic, fanned out cluster-wide. Lifecycle events
|
|
349
|
+
* (connect/disconnect/room/topic) are always emitted when inspector is on; `msg.*` events
|
|
350
|
+
* carry actual message traffic and are only emitted when inspector is on. Message payloads are
|
|
351
|
+
* safe-serialized and field-redacted (via the `inspector.redact` list) before they cross the bus.
|
|
352
|
+
*/
|
|
353
|
+
type InspectorEvent = {
|
|
354
|
+
type: 'connect';
|
|
355
|
+
descriptor: ConnDescriptor;
|
|
356
|
+
} | {
|
|
357
|
+
type: 'disconnect';
|
|
358
|
+
connId: string;
|
|
359
|
+
nodeId: string;
|
|
360
|
+
userId?: string;
|
|
361
|
+
} | {
|
|
362
|
+
type: 'room.add';
|
|
363
|
+
connId: string;
|
|
364
|
+
room: string;
|
|
365
|
+
} | {
|
|
366
|
+
type: 'room.remove';
|
|
367
|
+
connId: string;
|
|
368
|
+
room: string;
|
|
369
|
+
} | {
|
|
370
|
+
type: 'topic.sub';
|
|
371
|
+
connId: string;
|
|
372
|
+
topic: string;
|
|
373
|
+
} | {
|
|
374
|
+
type: 'topic.unsub';
|
|
375
|
+
connId: string;
|
|
376
|
+
topic: string;
|
|
377
|
+
} | {
|
|
378
|
+
type: 'msg.request';
|
|
379
|
+
connId: string;
|
|
380
|
+
role: string;
|
|
381
|
+
name: string;
|
|
382
|
+
input: unknown;
|
|
383
|
+
} | {
|
|
384
|
+
type: 'msg.response';
|
|
385
|
+
connId: string;
|
|
386
|
+
name: string;
|
|
387
|
+
ok: boolean;
|
|
388
|
+
output?: unknown;
|
|
389
|
+
error?: MessageError;
|
|
390
|
+
} | {
|
|
391
|
+
type: 'msg.event';
|
|
392
|
+
target: string;
|
|
393
|
+
name: string;
|
|
394
|
+
data: unknown;
|
|
395
|
+
} | {
|
|
396
|
+
type: 'msg.broadcast';
|
|
397
|
+
room: string;
|
|
398
|
+
name: string;
|
|
399
|
+
data: unknown;
|
|
400
|
+
} | {
|
|
401
|
+
type: 'msg.publish';
|
|
402
|
+
topic: string;
|
|
403
|
+
data: unknown;
|
|
404
|
+
} | {
|
|
405
|
+
type: 'msg.serverRequest';
|
|
406
|
+
target: string;
|
|
407
|
+
name: string;
|
|
408
|
+
input: unknown;
|
|
409
|
+
} | {
|
|
410
|
+
type: 'msg.serverReply';
|
|
411
|
+
target: string;
|
|
412
|
+
name: string;
|
|
413
|
+
ok: boolean;
|
|
414
|
+
output?: unknown;
|
|
415
|
+
error?: MessageError;
|
|
416
|
+
};
|
|
417
|
+
/**
|
|
418
|
+
* The fixed, library-owned contract describing the inspector surface. Identical for every
|
|
419
|
+
* super-line app, so it is NOT merged into the user's contract — inbound dispatch routes an
|
|
420
|
+
* inspector connection against this instead, which keeps the user's `RoleOf<C>` clean.
|
|
421
|
+
*/
|
|
422
|
+
declare const InspectorContract: {
|
|
423
|
+
readonly roles: {
|
|
424
|
+
readonly inspector: {
|
|
425
|
+
readonly clientToServer: {
|
|
426
|
+
readonly getContract: {
|
|
427
|
+
readonly input: StandardSchemaV1<void, void>;
|
|
428
|
+
readonly output: StandardSchemaV1<InspectedContract, InspectedContract>;
|
|
429
|
+
};
|
|
430
|
+
readonly getTopology: {
|
|
431
|
+
readonly input: StandardSchemaV1<void, void>;
|
|
432
|
+
readonly output: StandardSchemaV1<NodeStat[], NodeStat[]>;
|
|
433
|
+
};
|
|
434
|
+
readonly listConnections: {
|
|
435
|
+
readonly input: StandardSchemaV1<void, void>;
|
|
436
|
+
readonly output: StandardSchemaV1<ConnDescriptor[], ConnDescriptor[]>;
|
|
437
|
+
};
|
|
438
|
+
readonly getNode: {
|
|
439
|
+
readonly input: StandardSchemaV1<void, void>;
|
|
440
|
+
readonly output: StandardSchemaV1<NodeView, NodeView>;
|
|
441
|
+
};
|
|
442
|
+
readonly getConn: {
|
|
443
|
+
readonly input: StandardSchemaV1<{
|
|
444
|
+
id: string;
|
|
445
|
+
}, {
|
|
446
|
+
id: string;
|
|
447
|
+
}>;
|
|
448
|
+
readonly output: StandardSchemaV1<ConnView, ConnView>;
|
|
449
|
+
};
|
|
450
|
+
};
|
|
451
|
+
readonly serverToClient: {
|
|
452
|
+
readonly events: {
|
|
453
|
+
readonly payload: StandardSchemaV1<InspectorEvent, InspectorEvent>;
|
|
454
|
+
readonly subscribe: true;
|
|
455
|
+
};
|
|
456
|
+
};
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
};
|
|
460
|
+
/** A schema → JSON Schema converter (best-effort). Supplied by the server in slice 3. */
|
|
461
|
+
type SchemaConverter = (schema: Schema) => unknown;
|
|
462
|
+
/**
|
|
463
|
+
* Walk a contract and project its structure: roles × directions × message names × flavors.
|
|
464
|
+
* Pass `convert` to attach best-effort JSON Schema to each message; omit it for structure only.
|
|
465
|
+
*/
|
|
466
|
+
declare function classifyContract(contract: Contract, convert?: SchemaConverter): InspectedContract;
|
|
467
|
+
|
|
201
468
|
/**
|
|
202
469
|
* The wire protocol below is an implementation detail — you rarely touch frames
|
|
203
470
|
* directly. It's exported for adapters, custom transports, and tooling.
|
|
@@ -219,7 +486,19 @@ interface UnsubFrame {
|
|
|
219
486
|
t: 'unsub';
|
|
220
487
|
c: string;
|
|
221
488
|
}
|
|
222
|
-
|
|
489
|
+
interface SResFrame {
|
|
490
|
+
t: 'sres';
|
|
491
|
+
i: number;
|
|
492
|
+
d: unknown;
|
|
493
|
+
}
|
|
494
|
+
interface SErrFrame {
|
|
495
|
+
t: 'serr';
|
|
496
|
+
i: number;
|
|
497
|
+
code: string;
|
|
498
|
+
m: string;
|
|
499
|
+
d?: unknown;
|
|
500
|
+
}
|
|
501
|
+
type ClientFrame = ReqFrame | SubFrame | UnsubFrame | SResFrame | SErrFrame;
|
|
223
502
|
interface ResFrame {
|
|
224
503
|
t: 'res';
|
|
225
504
|
i: number;
|
|
@@ -241,8 +520,15 @@ interface PubFrame {
|
|
|
241
520
|
t: 'pub';
|
|
242
521
|
c: string;
|
|
243
522
|
d: unknown;
|
|
523
|
+
i?: string;
|
|
524
|
+
}
|
|
525
|
+
interface SReqFrame {
|
|
526
|
+
t: 'sreq';
|
|
527
|
+
i: number;
|
|
528
|
+
m: string;
|
|
529
|
+
d: unknown;
|
|
244
530
|
}
|
|
245
|
-
type ServerFrame = ResFrame | ErrFrame | EvtFrame | PubFrame;
|
|
531
|
+
type ServerFrame = ResFrame | ErrFrame | EvtFrame | PubFrame | SReqFrame;
|
|
246
532
|
type Frame = ClientFrame | ServerFrame;
|
|
247
533
|
|
|
248
|
-
export { type Adapter, type ClientFrame, type ClientInput, type Contract, type Directional, type EmitData, type ErrFrame, type ErrorCode, type EventData, type Events, type EvtFrame, type Frame, type InferIn, type InferOut, type Output, PROTOCOL, type PubFrame, type ReqFrame, type RequestDef, type Requests, type ResFrame, type RoleOf, type RoleRequests, type RoleTopics, type
|
|
534
|
+
export { type Adapter, type AnyData, type ClientFrame, type ClientInput, type ConnDescriptor, type ConnView, type Contract, type DataOf, type Directional, type EmitData, type ErrFrame, type ErrorCode, type EventData, type Events, type EvtFrame, type Frame, INSPECTOR_ROLE, INSPECTOR_SUBPROTOCOL, type InferIn, type InferOut, type InspectedContract, type InspectedDirectional, type InspectedMessage, InspectorContract, type InspectorEvent, type MessageFlavor, type NodeStat, type NodeView, type Output, PROTOCOL, type PresenceStore, type PubFrame, type ReqFrame, type RequestDef, type Requests, type ResFrame, type RoleBlock, type RoleOf, type RoleRequests, type RoleTopics, type SErrFrame, type SReqFrame, type SResFrame, type Schema, type SchemaConverter, type Serializer, type ServerEntry, type ServerFrame, type ServerInput, type ServerMessageDef, type ServerMessages, type ServerRequestDef, type ServerRequests, type SharedEvents, type SharedRequests, type SharedServerRequests, type SharedTopics, type SubFrame, SuperLineError, type SuperLineErrorCode, type Topics, type UnsubFrame, classifyContract, defineContract, jsonSerializer, validate, validateSync };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
2
|
|
|
3
3
|
/** The built-in error codes super-line uses across the wire. */
|
|
4
|
-
type
|
|
4
|
+
type SuperLineErrorCode = 'BAD_REQUEST' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'NOT_FOUND' | 'TIMEOUT' | 'VALIDATION' | 'DISCONNECTED' | 'INTERNAL';
|
|
5
5
|
/** A built-in code or any custom string (autocomplete keeps the known set). */
|
|
6
|
-
type ErrorCode =
|
|
6
|
+
type ErrorCode = SuperLineErrorCode | (string & {});
|
|
7
7
|
/**
|
|
8
8
|
* The error type carried end-to-end. Throw one from a handler and the client's
|
|
9
9
|
* promise rejects with the same `code` (and optional `data`). Unknown throws
|
|
10
10
|
* become `INTERNAL` so server internals aren't leaked.
|
|
11
11
|
*/
|
|
12
|
-
declare class
|
|
12
|
+
declare class SuperLineError<Data = unknown> extends Error {
|
|
13
13
|
/** The typed error code (e.g. `'FORBIDDEN'`), available on the client. */
|
|
14
14
|
readonly code: ErrorCode;
|
|
15
15
|
/** Optional structured data attached to the error, delivered to the client. */
|
|
16
16
|
readonly data?: Data;
|
|
17
17
|
/**
|
|
18
|
-
* @param code - a {@link
|
|
18
|
+
* @param code - a {@link SuperLineErrorCode} or custom string.
|
|
19
19
|
* @param message - human-readable message (defaults to `code`).
|
|
20
20
|
* @param data - optional structured payload delivered to the client.
|
|
21
21
|
*/
|
|
@@ -36,8 +36,8 @@ interface Serializer {
|
|
|
36
36
|
declare const jsonSerializer: Serializer;
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* Cross-node fan-out seam. Rooms, topics, and
|
|
40
|
-
* channel pub/sub. A node subscribes to a channel only while it has a local member,
|
|
39
|
+
* Cross-node fan-out seam. Rooms, topics, and the cluster event bus all compile down
|
|
40
|
+
* to channel pub/sub. A node subscribes to a channel only while it has a local member,
|
|
41
41
|
* and publishes always go through the adapter (the in-memory adapter loops back),
|
|
42
42
|
* so a node delivers to its local members on receipt — one code path, no double-send.
|
|
43
43
|
*
|
|
@@ -55,6 +55,75 @@ interface Adapter {
|
|
|
55
55
|
onMessage(handler: (channel: string, payload: string | Uint8Array) => void): void;
|
|
56
56
|
/** Optional teardown (e.g. close Redis connections). */
|
|
57
57
|
close?(): void | Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Optional cluster-wide presence directory. Powers `srv.cluster.*` and
|
|
60
|
+
* `srv.isOnline`. The in-memory and Redis adapters implement it; cluster
|
|
61
|
+
* queries throw a clear error on an adapter that doesn't.
|
|
62
|
+
*/
|
|
63
|
+
presence?: PresenceStore;
|
|
64
|
+
}
|
|
65
|
+
/** A serializable snapshot of a connection, shared cluster-wide via the {@link PresenceStore}. */
|
|
66
|
+
interface ConnDescriptor {
|
|
67
|
+
/** The connection's server-assigned id. */
|
|
68
|
+
id: string;
|
|
69
|
+
/** The connection's role. */
|
|
70
|
+
role: string;
|
|
71
|
+
/** The node that holds this connection. */
|
|
72
|
+
nodeId: string;
|
|
73
|
+
/** The node's friendly name (defaults to a short slice of `nodeId`). */
|
|
74
|
+
nodeName: string;
|
|
75
|
+
/** When the connection was accepted (`Date.now()`). */
|
|
76
|
+
connectedAt: number;
|
|
77
|
+
/** The stable user key from the server's `identify` hook, if any. */
|
|
78
|
+
userId?: string;
|
|
79
|
+
/** Room memberships (topics and node-local `lastPongAt` are not included). */
|
|
80
|
+
rooms: string[];
|
|
81
|
+
/** Extra fields contributed by the server's `describeConn` hook. */
|
|
82
|
+
[extra: string]: unknown;
|
|
83
|
+
}
|
|
84
|
+
/** Per-node aggregate, returned by {@link PresenceStore.topology}. */
|
|
85
|
+
interface NodeStat {
|
|
86
|
+
/** The node's id. */
|
|
87
|
+
nodeId: string;
|
|
88
|
+
/** The node's friendly name (defaults to a short slice of `nodeId`). */
|
|
89
|
+
nodeName: string;
|
|
90
|
+
/** Number of connections on the node. */
|
|
91
|
+
connections: number;
|
|
92
|
+
/** Number of distinct rooms with members on the node. */
|
|
93
|
+
rooms: number;
|
|
94
|
+
/** Whether the node is currently live (heartbeat fresh). */
|
|
95
|
+
alive: boolean;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Cluster-wide presence directory: a query/addressbook layer kept in the shared
|
|
99
|
+
* substrate (in-memory bus or Redis). Live message delivery does NOT read this —
|
|
100
|
+
* it exists only to answer `srv.cluster.*` / `srv.isOnline`.
|
|
101
|
+
*/
|
|
102
|
+
interface PresenceStore {
|
|
103
|
+
/** Record (or replace) a connection's descriptor. */
|
|
104
|
+
set(descriptor: ConnDescriptor): void | Promise<void>;
|
|
105
|
+
/** Remove a connection's descriptor. */
|
|
106
|
+
del(connId: string): void | Promise<void>;
|
|
107
|
+
/** Refresh this node's liveness (heartbeat). */
|
|
108
|
+
beat(nodeId: string): void | Promise<void>;
|
|
109
|
+
/** Remove all of a node's connections + liveness (graceful shutdown cleanup). */
|
|
110
|
+
clearNode(nodeId: string): void | Promise<void>;
|
|
111
|
+
/** Add a room to a connection's membership. */
|
|
112
|
+
addRoom(connId: string, room: string): void | Promise<void>;
|
|
113
|
+
/** Remove a room from a connection's membership. */
|
|
114
|
+
removeRoom(connId: string, room: string): void | Promise<void>;
|
|
115
|
+
/** All live connection descriptors across the cluster. */
|
|
116
|
+
list(): ConnDescriptor[] | Promise<ConnDescriptor[]>;
|
|
117
|
+
/** One connection's descriptor, if present. */
|
|
118
|
+
get(connId: string): (ConnDescriptor | undefined) | Promise<ConnDescriptor | undefined>;
|
|
119
|
+
/** Descriptors for a given user key. */
|
|
120
|
+
byUser(userId: string): ConnDescriptor[] | Promise<ConnDescriptor[]>;
|
|
121
|
+
/** Descriptors that are members of `room`. */
|
|
122
|
+
roomMembers(room: string): ConnDescriptor[] | Promise<ConnDescriptor[]>;
|
|
123
|
+
/** Total live connection count across the cluster. */
|
|
124
|
+
count(): number | Promise<number>;
|
|
125
|
+
/** Per-node aggregates. */
|
|
126
|
+
topology(): NodeStat[] | Promise<NodeStat[]>;
|
|
58
127
|
}
|
|
59
128
|
|
|
60
129
|
/** Any [Standard Schema](https://standardschema.dev) validator (Zod, Valibot, ArkType…). */
|
|
@@ -80,25 +149,41 @@ interface ServerMessageDef {
|
|
|
80
149
|
/** When `true`, clients opt in via `client.subscribe(...)` (a topic). Omit for a push event. */
|
|
81
150
|
subscribe?: boolean;
|
|
82
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* A server→client request (request/response). The server sends `input`; the
|
|
154
|
+
* client's `implement` handler returns `output`. Lives in `serverToClient`
|
|
155
|
+
* alongside events and topics, distinguished by having `input`.
|
|
156
|
+
*/
|
|
157
|
+
interface ServerRequestDef {
|
|
158
|
+
/** Schema for the request payload the server sends. */
|
|
159
|
+
input: Schema;
|
|
160
|
+
/** Schema for the reply the client returns. */
|
|
161
|
+
output: Schema;
|
|
162
|
+
}
|
|
163
|
+
/** A `serverToClient` entry: a push event, a subscribable topic, or a server→client request. */
|
|
164
|
+
type ServerEntry = ServerMessageDef | ServerRequestDef;
|
|
83
165
|
/** The two directions within a `shared` or role block. */
|
|
84
166
|
interface Directional {
|
|
85
167
|
/** Requests this side may call (client→server). */
|
|
86
168
|
clientToServer?: Record<string, RequestDef>;
|
|
87
|
-
/** Events
|
|
88
|
-
serverToClient?: Record<string,
|
|
169
|
+
/** Events, topics, and server→client requests this side may receive. */
|
|
170
|
+
serverToClient?: Record<string, ServerEntry>;
|
|
171
|
+
}
|
|
172
|
+
/** A role block: its directions plus an optional `data` schema typing `conn.data`. */
|
|
173
|
+
interface RoleBlock extends Directional {
|
|
174
|
+
/** Schema for this role's mutable per-connection `conn.data` (server-side scratch state). */
|
|
175
|
+
data?: Schema;
|
|
89
176
|
}
|
|
90
177
|
/**
|
|
91
178
|
* The single source of truth, imported by both server and client. Split by
|
|
92
179
|
* **direction** and scoped by **role**: a `shared` base every role inherits,
|
|
93
|
-
* plus one block per role.
|
|
180
|
+
* plus one block per role.
|
|
94
181
|
*/
|
|
95
182
|
interface Contract {
|
|
96
183
|
/** Surface common to every role (merged into each role's effective surface). */
|
|
97
184
|
shared?: Directional;
|
|
98
185
|
/** Per-role surfaces. A connection's role selects which one (plus `shared`) it sees. */
|
|
99
|
-
roles: Record<string,
|
|
100
|
-
/** Typed node-to-node event payloads, for {@link "@super-line/server"!}'s `emitServer`/`onServer`. */
|
|
101
|
-
serverToServer?: Record<string, Schema>;
|
|
186
|
+
roles: Record<string, RoleBlock>;
|
|
102
187
|
}
|
|
103
188
|
/**
|
|
104
189
|
* Define a contract. An identity function — `const` preserves literal keys and
|
|
@@ -118,7 +203,6 @@ interface Contract {
|
|
|
118
203
|
* user: { clientToServer: { say: { input: z.object({ text: z.string() }), output: z.object({ id: z.string() }) } } },
|
|
119
204
|
* agent: { clientToServer: { announce: { input: z.object({ text: z.string() }), output: z.object({ id: z.string() }) } } },
|
|
120
205
|
* },
|
|
121
|
-
* serverToServer: { rebalance: z.object({ shard: z.number() }) },
|
|
122
206
|
* })
|
|
123
207
|
* ```
|
|
124
208
|
*/
|
|
@@ -129,10 +213,12 @@ type CtsOf<D> = D extends {
|
|
|
129
213
|
clientToServer: infer M extends Record<string, RequestDef>;
|
|
130
214
|
} ? M : {};
|
|
131
215
|
type StcOf<D> = D extends {
|
|
132
|
-
serverToClient: infer M extends Record<string,
|
|
216
|
+
serverToClient: infer M extends Record<string, ServerEntry>;
|
|
133
217
|
} ? M : {};
|
|
134
218
|
type EventsOf<M> = {
|
|
135
219
|
[K in keyof M as M[K] extends {
|
|
220
|
+
input: Schema;
|
|
221
|
+
} ? never : M[K] extends {
|
|
136
222
|
subscribe: true;
|
|
137
223
|
} ? never : K]: M[K];
|
|
138
224
|
};
|
|
@@ -141,6 +227,11 @@ type TopicsOf<M> = {
|
|
|
141
227
|
subscribe: true;
|
|
142
228
|
} ? K : never]: M[K];
|
|
143
229
|
};
|
|
230
|
+
type ServerReqOf<M> = {
|
|
231
|
+
[K in keyof M as M[K] extends {
|
|
232
|
+
input: Schema;
|
|
233
|
+
} ? K : never]: M[K];
|
|
234
|
+
};
|
|
144
235
|
/** A role's effective request map: `shared` ∪ `roles[R]` client→server requests. */
|
|
145
236
|
type Requests<C extends Contract, R extends RoleOf<C>> = CtsOf<C['shared']> & CtsOf<C['roles'][R]>;
|
|
146
237
|
/** A role's effective server→client map (events and topics combined). */
|
|
@@ -159,8 +250,16 @@ type SharedEvents<C extends Contract> = EventsOf<StcOf<C['shared']>>;
|
|
|
159
250
|
type SharedTopics<C extends Contract> = TopicsOf<StcOf<C['shared']>>;
|
|
160
251
|
/** Subscribable topics in one role's block (published via `srv.forRole(r).publish`). */
|
|
161
252
|
type RoleTopics<C extends Contract, R extends RoleOf<C>> = TopicsOf<StcOf<C['roles'][R]>>;
|
|
162
|
-
/**
|
|
163
|
-
type
|
|
253
|
+
/** A role's effective server→client requests (`shared` ∪ `roles[R]`), answered by `client.implement`. */
|
|
254
|
+
type ServerRequests<C extends Contract, R extends RoleOf<C>> = ServerReqOf<ServerMessages<C, R>>;
|
|
255
|
+
/** Server→client requests in the `shared` block (the surface `srv.toConn(id).request` can call). */
|
|
256
|
+
type SharedServerRequests<C extends Contract> = ServerReqOf<StcOf<C['shared']>>;
|
|
257
|
+
/** The typed shape of `conn.data` for role `R` (its `data` schema, or an empty object). */
|
|
258
|
+
type DataOf<C extends Contract, R extends RoleOf<C>> = C['roles'][R] extends {
|
|
259
|
+
data: infer S extends Schema;
|
|
260
|
+
} ? InferOut<S> : Record<string, never>;
|
|
261
|
+
/** Union of every role's `conn.data` shape (used where the role isn't narrowed, e.g. shared handlers). */
|
|
262
|
+
type AnyData<C extends Contract> = DataOf<C, RoleOf<C>>;
|
|
164
263
|
/** The input type a client passes for a request (pre-validation). */
|
|
165
264
|
type ClientInput<T> = T extends RequestDef ? InferIn<T['input']> : never;
|
|
166
265
|
/** The input type a server handler receives for a request (post-validation). */
|
|
@@ -171,10 +270,6 @@ type Output<T> = T extends RequestDef ? InferOut<T['output']> : never;
|
|
|
171
270
|
type EventData<T> = T extends ServerMessageDef ? InferOut<T['payload']> : never;
|
|
172
271
|
/** The data a server sends for an event/topic (pre-validation). */
|
|
173
272
|
type EmitData<T> = T extends ServerMessageDef ? InferIn<T['payload']> : never;
|
|
174
|
-
/** The data a server sends for a serverToServer event. */
|
|
175
|
-
type ServerEmit<T> = T extends Schema ? InferIn<T> : never;
|
|
176
|
-
/** The data a server receives for a serverToServer event. */
|
|
177
|
-
type ServerData<T> = T extends Schema ? InferOut<T> : never;
|
|
178
273
|
/** Infer a schema's **input** type (what you pass into the validator). */
|
|
179
274
|
type InferIn<S extends Schema> = StandardSchemaV1.InferInput<S>;
|
|
180
275
|
/** Infer a schema's **output** type (the validated result). */
|
|
@@ -185,7 +280,7 @@ type InferOut<S extends Schema> = StandardSchemaV1.InferOutput<S>;
|
|
|
185
280
|
* @param schema - the validator to run.
|
|
186
281
|
* @param value - the untrusted value to validate.
|
|
187
282
|
* @returns the parsed, typed value.
|
|
188
|
-
* @throws {@link
|
|
283
|
+
* @throws {@link SuperLineError} with code `VALIDATION` if the value doesn't match.
|
|
189
284
|
*/
|
|
190
285
|
declare function validate<S extends Schema>(schema: S, value: unknown): Promise<StandardSchemaV1.InferOutput<S>>;
|
|
191
286
|
/**
|
|
@@ -194,10 +289,182 @@ declare function validate<S extends Schema>(schema: S, value: unknown): Promise<
|
|
|
194
289
|
* @param schema - the validator to run.
|
|
195
290
|
* @param value - the untrusted value to validate.
|
|
196
291
|
* @returns the parsed, typed value.
|
|
197
|
-
* @throws {@link
|
|
292
|
+
* @throws {@link SuperLineError} with code `VALIDATION` on mismatch, or `INTERNAL` if the schema is async.
|
|
198
293
|
*/
|
|
199
294
|
declare function validateSync<S extends Schema>(schema: S, value: unknown): StandardSchemaV1.InferOutput<S>;
|
|
200
295
|
|
|
296
|
+
/** WS subprotocol the Control Center connects with; the server short-circuits auth for it. */
|
|
297
|
+
declare const INSPECTOR_SUBPROTOCOL = "superline.inspector.v1";
|
|
298
|
+
/** The reserved role minted for an inspector connection. */
|
|
299
|
+
declare const INSPECTOR_ROLE = "inspector";
|
|
300
|
+
/** How a contract message is used on the wire. */
|
|
301
|
+
type MessageFlavor = 'request' | 'event' | 'topic' | 'serverRequest';
|
|
302
|
+
/** One message in an {@link InspectedContract}. Schemas are best-effort JSON Schema, omitted when unavailable. */
|
|
303
|
+
interface InspectedMessage {
|
|
304
|
+
/** The message name (its key in the contract). */
|
|
305
|
+
name: string;
|
|
306
|
+
/** How the message is used. */
|
|
307
|
+
flavor: MessageFlavor;
|
|
308
|
+
/** Best-effort JSON Schema of the request/server-request input. */
|
|
309
|
+
input?: unknown;
|
|
310
|
+
/** Best-effort JSON Schema of the request/server-request output. */
|
|
311
|
+
output?: unknown;
|
|
312
|
+
/** Best-effort JSON Schema of an event/topic payload. */
|
|
313
|
+
payload?: unknown;
|
|
314
|
+
}
|
|
315
|
+
/** The two directions of a `shared` or role block, flattened for display. */
|
|
316
|
+
interface InspectedDirectional {
|
|
317
|
+
clientToServer: InspectedMessage[];
|
|
318
|
+
serverToClient: InspectedMessage[];
|
|
319
|
+
}
|
|
320
|
+
/** A serializable projection of a {@link Contract}'s structure — what `getContract` returns. */
|
|
321
|
+
interface InspectedContract {
|
|
322
|
+
shared: InspectedDirectional;
|
|
323
|
+
roles: Record<string, InspectedDirectional>;
|
|
324
|
+
}
|
|
325
|
+
/** The connected node's local view — what `getNode` returns. */
|
|
326
|
+
interface NodeView {
|
|
327
|
+
nodeId: string;
|
|
328
|
+
nodeName: string;
|
|
329
|
+
rooms: string[];
|
|
330
|
+
topics: string[];
|
|
331
|
+
}
|
|
332
|
+
/** A connection's detail — what `getConn` returns. ctx/data are node-local and best-effort safe-serialized. */
|
|
333
|
+
interface ConnView {
|
|
334
|
+
descriptor: ConnDescriptor;
|
|
335
|
+
/** Safe-serialized auth ctx; present only when the conn is on the queried node. */
|
|
336
|
+
ctx?: unknown;
|
|
337
|
+
/** Safe-serialized `conn.data`; present only when the conn is on the queried node. */
|
|
338
|
+
data?: unknown;
|
|
339
|
+
/** Whether ctx/data could be read (false for conns on another node). */
|
|
340
|
+
ctxAvailable: boolean;
|
|
341
|
+
}
|
|
342
|
+
/** A failed response/reply, carried on `msg.response` / `msg.serverReply`. */
|
|
343
|
+
interface MessageError {
|
|
344
|
+
code: string;
|
|
345
|
+
message: string;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* A live event pushed on the `events` topic, fanned out cluster-wide. Lifecycle events
|
|
349
|
+
* (connect/disconnect/room/topic) are always emitted when inspector is on; `msg.*` events
|
|
350
|
+
* carry actual message traffic and are only emitted when inspector is on. Message payloads are
|
|
351
|
+
* safe-serialized and field-redacted (via the `inspector.redact` list) before they cross the bus.
|
|
352
|
+
*/
|
|
353
|
+
type InspectorEvent = {
|
|
354
|
+
type: 'connect';
|
|
355
|
+
descriptor: ConnDescriptor;
|
|
356
|
+
} | {
|
|
357
|
+
type: 'disconnect';
|
|
358
|
+
connId: string;
|
|
359
|
+
nodeId: string;
|
|
360
|
+
userId?: string;
|
|
361
|
+
} | {
|
|
362
|
+
type: 'room.add';
|
|
363
|
+
connId: string;
|
|
364
|
+
room: string;
|
|
365
|
+
} | {
|
|
366
|
+
type: 'room.remove';
|
|
367
|
+
connId: string;
|
|
368
|
+
room: string;
|
|
369
|
+
} | {
|
|
370
|
+
type: 'topic.sub';
|
|
371
|
+
connId: string;
|
|
372
|
+
topic: string;
|
|
373
|
+
} | {
|
|
374
|
+
type: 'topic.unsub';
|
|
375
|
+
connId: string;
|
|
376
|
+
topic: string;
|
|
377
|
+
} | {
|
|
378
|
+
type: 'msg.request';
|
|
379
|
+
connId: string;
|
|
380
|
+
role: string;
|
|
381
|
+
name: string;
|
|
382
|
+
input: unknown;
|
|
383
|
+
} | {
|
|
384
|
+
type: 'msg.response';
|
|
385
|
+
connId: string;
|
|
386
|
+
name: string;
|
|
387
|
+
ok: boolean;
|
|
388
|
+
output?: unknown;
|
|
389
|
+
error?: MessageError;
|
|
390
|
+
} | {
|
|
391
|
+
type: 'msg.event';
|
|
392
|
+
target: string;
|
|
393
|
+
name: string;
|
|
394
|
+
data: unknown;
|
|
395
|
+
} | {
|
|
396
|
+
type: 'msg.broadcast';
|
|
397
|
+
room: string;
|
|
398
|
+
name: string;
|
|
399
|
+
data: unknown;
|
|
400
|
+
} | {
|
|
401
|
+
type: 'msg.publish';
|
|
402
|
+
topic: string;
|
|
403
|
+
data: unknown;
|
|
404
|
+
} | {
|
|
405
|
+
type: 'msg.serverRequest';
|
|
406
|
+
target: string;
|
|
407
|
+
name: string;
|
|
408
|
+
input: unknown;
|
|
409
|
+
} | {
|
|
410
|
+
type: 'msg.serverReply';
|
|
411
|
+
target: string;
|
|
412
|
+
name: string;
|
|
413
|
+
ok: boolean;
|
|
414
|
+
output?: unknown;
|
|
415
|
+
error?: MessageError;
|
|
416
|
+
};
|
|
417
|
+
/**
|
|
418
|
+
* The fixed, library-owned contract describing the inspector surface. Identical for every
|
|
419
|
+
* super-line app, so it is NOT merged into the user's contract — inbound dispatch routes an
|
|
420
|
+
* inspector connection against this instead, which keeps the user's `RoleOf<C>` clean.
|
|
421
|
+
*/
|
|
422
|
+
declare const InspectorContract: {
|
|
423
|
+
readonly roles: {
|
|
424
|
+
readonly inspector: {
|
|
425
|
+
readonly clientToServer: {
|
|
426
|
+
readonly getContract: {
|
|
427
|
+
readonly input: StandardSchemaV1<void, void>;
|
|
428
|
+
readonly output: StandardSchemaV1<InspectedContract, InspectedContract>;
|
|
429
|
+
};
|
|
430
|
+
readonly getTopology: {
|
|
431
|
+
readonly input: StandardSchemaV1<void, void>;
|
|
432
|
+
readonly output: StandardSchemaV1<NodeStat[], NodeStat[]>;
|
|
433
|
+
};
|
|
434
|
+
readonly listConnections: {
|
|
435
|
+
readonly input: StandardSchemaV1<void, void>;
|
|
436
|
+
readonly output: StandardSchemaV1<ConnDescriptor[], ConnDescriptor[]>;
|
|
437
|
+
};
|
|
438
|
+
readonly getNode: {
|
|
439
|
+
readonly input: StandardSchemaV1<void, void>;
|
|
440
|
+
readonly output: StandardSchemaV1<NodeView, NodeView>;
|
|
441
|
+
};
|
|
442
|
+
readonly getConn: {
|
|
443
|
+
readonly input: StandardSchemaV1<{
|
|
444
|
+
id: string;
|
|
445
|
+
}, {
|
|
446
|
+
id: string;
|
|
447
|
+
}>;
|
|
448
|
+
readonly output: StandardSchemaV1<ConnView, ConnView>;
|
|
449
|
+
};
|
|
450
|
+
};
|
|
451
|
+
readonly serverToClient: {
|
|
452
|
+
readonly events: {
|
|
453
|
+
readonly payload: StandardSchemaV1<InspectorEvent, InspectorEvent>;
|
|
454
|
+
readonly subscribe: true;
|
|
455
|
+
};
|
|
456
|
+
};
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
};
|
|
460
|
+
/** A schema → JSON Schema converter (best-effort). Supplied by the server in slice 3. */
|
|
461
|
+
type SchemaConverter = (schema: Schema) => unknown;
|
|
462
|
+
/**
|
|
463
|
+
* Walk a contract and project its structure: roles × directions × message names × flavors.
|
|
464
|
+
* Pass `convert` to attach best-effort JSON Schema to each message; omit it for structure only.
|
|
465
|
+
*/
|
|
466
|
+
declare function classifyContract(contract: Contract, convert?: SchemaConverter): InspectedContract;
|
|
467
|
+
|
|
201
468
|
/**
|
|
202
469
|
* The wire protocol below is an implementation detail — you rarely touch frames
|
|
203
470
|
* directly. It's exported for adapters, custom transports, and tooling.
|
|
@@ -219,7 +486,19 @@ interface UnsubFrame {
|
|
|
219
486
|
t: 'unsub';
|
|
220
487
|
c: string;
|
|
221
488
|
}
|
|
222
|
-
|
|
489
|
+
interface SResFrame {
|
|
490
|
+
t: 'sres';
|
|
491
|
+
i: number;
|
|
492
|
+
d: unknown;
|
|
493
|
+
}
|
|
494
|
+
interface SErrFrame {
|
|
495
|
+
t: 'serr';
|
|
496
|
+
i: number;
|
|
497
|
+
code: string;
|
|
498
|
+
m: string;
|
|
499
|
+
d?: unknown;
|
|
500
|
+
}
|
|
501
|
+
type ClientFrame = ReqFrame | SubFrame | UnsubFrame | SResFrame | SErrFrame;
|
|
223
502
|
interface ResFrame {
|
|
224
503
|
t: 'res';
|
|
225
504
|
i: number;
|
|
@@ -241,8 +520,15 @@ interface PubFrame {
|
|
|
241
520
|
t: 'pub';
|
|
242
521
|
c: string;
|
|
243
522
|
d: unknown;
|
|
523
|
+
i?: string;
|
|
524
|
+
}
|
|
525
|
+
interface SReqFrame {
|
|
526
|
+
t: 'sreq';
|
|
527
|
+
i: number;
|
|
528
|
+
m: string;
|
|
529
|
+
d: unknown;
|
|
244
530
|
}
|
|
245
|
-
type ServerFrame = ResFrame | ErrFrame | EvtFrame | PubFrame;
|
|
531
|
+
type ServerFrame = ResFrame | ErrFrame | EvtFrame | PubFrame | SReqFrame;
|
|
246
532
|
type Frame = ClientFrame | ServerFrame;
|
|
247
533
|
|
|
248
|
-
export { type Adapter, type ClientFrame, type ClientInput, type Contract, type Directional, type EmitData, type ErrFrame, type ErrorCode, type EventData, type Events, type EvtFrame, type Frame, type InferIn, type InferOut, type Output, PROTOCOL, type PubFrame, type ReqFrame, type RequestDef, type Requests, type ResFrame, type RoleOf, type RoleRequests, type RoleTopics, type
|
|
534
|
+
export { type Adapter, type AnyData, type ClientFrame, type ClientInput, type ConnDescriptor, type ConnView, type Contract, type DataOf, type Directional, type EmitData, type ErrFrame, type ErrorCode, type EventData, type Events, type EvtFrame, type Frame, INSPECTOR_ROLE, INSPECTOR_SUBPROTOCOL, type InferIn, type InferOut, type InspectedContract, type InspectedDirectional, type InspectedMessage, InspectorContract, type InspectorEvent, type MessageFlavor, type NodeStat, type NodeView, type Output, PROTOCOL, type PresenceStore, type PubFrame, type ReqFrame, type RequestDef, type Requests, type ResFrame, type RoleBlock, type RoleOf, type RoleRequests, type RoleTopics, type SErrFrame, type SReqFrame, type SResFrame, type Schema, type SchemaConverter, type Serializer, type ServerEntry, type ServerFrame, type ServerInput, type ServerMessageDef, type ServerMessages, type ServerRequestDef, type ServerRequests, type SharedEvents, type SharedRequests, type SharedServerRequests, type SharedTopics, type SubFrame, SuperLineError, type SuperLineErrorCode, type Topics, type UnsubFrame, classifyContract, defineContract, jsonSerializer, validate, validateSync };
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// src/errors.ts
|
|
2
|
-
var
|
|
2
|
+
var SuperLineError = class extends Error {
|
|
3
3
|
/** The typed error code (e.g. `'FORBIDDEN'`), available on the client. */
|
|
4
4
|
code;
|
|
5
5
|
/** Optional structured data attached to the error, delivered to the client. */
|
|
6
6
|
data;
|
|
7
7
|
/**
|
|
8
|
-
* @param code - a {@link
|
|
8
|
+
* @param code - a {@link SuperLineErrorCode} or custom string.
|
|
9
9
|
* @param message - human-readable message (defaults to `code`).
|
|
10
10
|
* @param data - optional structured payload delivered to the client.
|
|
11
11
|
*/
|
|
12
12
|
constructor(code, message, data) {
|
|
13
13
|
super(message ?? code);
|
|
14
|
-
this.name = "
|
|
14
|
+
this.name = "SuperLineError";
|
|
15
15
|
this.code = code;
|
|
16
16
|
this.data = data;
|
|
17
17
|
}
|
|
@@ -32,26 +32,99 @@ async function validate(schema, value) {
|
|
|
32
32
|
let result = schema["~standard"].validate(value);
|
|
33
33
|
if (result instanceof Promise) result = await result;
|
|
34
34
|
if (result.issues) {
|
|
35
|
-
throw new
|
|
35
|
+
throw new SuperLineError("VALIDATION", "Validation failed", result.issues);
|
|
36
36
|
}
|
|
37
37
|
return result.value;
|
|
38
38
|
}
|
|
39
39
|
function validateSync(schema, value) {
|
|
40
40
|
const result = schema["~standard"].validate(value);
|
|
41
41
|
if (result instanceof Promise) {
|
|
42
|
-
throw new
|
|
42
|
+
throw new SuperLineError("INTERNAL", "Async schema not supported for synchronous validation");
|
|
43
43
|
}
|
|
44
44
|
if (result.issues) {
|
|
45
|
-
throw new
|
|
45
|
+
throw new SuperLineError("VALIDATION", "Validation failed", result.issues);
|
|
46
46
|
}
|
|
47
47
|
return result.value;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// src/inspector.ts
|
|
51
|
+
var INSPECTOR_SUBPROTOCOL = "superline.inspector.v1";
|
|
52
|
+
var INSPECTOR_ROLE = "inspector";
|
|
53
|
+
function s() {
|
|
54
|
+
return {
|
|
55
|
+
"~standard": {
|
|
56
|
+
version: 1,
|
|
57
|
+
vendor: "super-line-inspector",
|
|
58
|
+
validate: (value) => ({ value })
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
var InspectorContract = defineContract({
|
|
63
|
+
roles: {
|
|
64
|
+
inspector: {
|
|
65
|
+
clientToServer: {
|
|
66
|
+
getContract: { input: s(), output: s() },
|
|
67
|
+
getTopology: { input: s(), output: s() },
|
|
68
|
+
listConnections: { input: s(), output: s() },
|
|
69
|
+
getNode: { input: s(), output: s() },
|
|
70
|
+
getConn: { input: s(), output: s() }
|
|
71
|
+
},
|
|
72
|
+
serverToClient: {
|
|
73
|
+
events: { payload: s(), subscribe: true }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
function withSchemas(msg, schemas, convert) {
|
|
79
|
+
if (!convert) return msg;
|
|
80
|
+
for (const [key, schema] of Object.entries(schemas)) {
|
|
81
|
+
const value = convert(schema);
|
|
82
|
+
if (value !== void 0) msg[key] = value;
|
|
83
|
+
}
|
|
84
|
+
return msg;
|
|
85
|
+
}
|
|
86
|
+
function classifyDirectional(d, convert) {
|
|
87
|
+
const clientToServer = [];
|
|
88
|
+
const serverToClient = [];
|
|
89
|
+
for (const [name, def] of Object.entries(d?.clientToServer ?? {})) {
|
|
90
|
+
clientToServer.push(
|
|
91
|
+
withSchemas({ name, flavor: "request" }, { input: def.input, output: def.output }, convert)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
for (const [name, def] of Object.entries(d?.serverToClient ?? {})) {
|
|
95
|
+
if ("input" in def) {
|
|
96
|
+
serverToClient.push(
|
|
97
|
+
withSchemas({ name, flavor: "serverRequest" }, { input: def.input, output: def.output }, convert)
|
|
98
|
+
);
|
|
99
|
+
} else {
|
|
100
|
+
serverToClient.push(
|
|
101
|
+
withSchemas(
|
|
102
|
+
{ name, flavor: def.subscribe === true ? "topic" : "event" },
|
|
103
|
+
{ payload: def.payload },
|
|
104
|
+
convert
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { clientToServer, serverToClient };
|
|
110
|
+
}
|
|
111
|
+
function classifyContract(contract, convert) {
|
|
112
|
+
const roles = {};
|
|
113
|
+
for (const [role, block] of Object.entries(contract.roles)) {
|
|
114
|
+
roles[role] = classifyDirectional(block, convert);
|
|
115
|
+
}
|
|
116
|
+
return { shared: classifyDirectional(contract.shared, convert), roles };
|
|
117
|
+
}
|
|
118
|
+
|
|
50
119
|
// src/wire.ts
|
|
51
120
|
var PROTOCOL = "superline.v1";
|
|
52
121
|
export {
|
|
122
|
+
INSPECTOR_ROLE,
|
|
123
|
+
INSPECTOR_SUBPROTOCOL,
|
|
124
|
+
InspectorContract,
|
|
53
125
|
PROTOCOL,
|
|
54
|
-
|
|
126
|
+
SuperLineError,
|
|
127
|
+
classifyContract,
|
|
55
128
|
defineContract,
|
|
56
129
|
jsonSerializer,
|
|
57
130
|
validate,
|