@irtio/protocol 0.1.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/LICENSE +21 -0
- package/dist/index.d.ts +336 -0
- package/dist/index.js +432 -0
- package/package.json +29 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 irtio contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import * as _irtio_schema from '@irtio/schema';
|
|
2
|
+
import { ByteReader, RpcMap, ServerRpcs, AnySchema, RpcDesc, InstanceOf, Schema, SchemaDefs, SchemaRpc, SchemaRoles } from '@irtio/schema';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Frame envelope: one type byte, then an opaque payload. `encodeFrame`/`decodeFrame` only
|
|
6
|
+
* handle the envelope — payload codecs live in `session.ts`, `rpc.ts`, `msg.ts`.
|
|
7
|
+
*/
|
|
8
|
+
/** Wire protocol version (u8), bumped on any breaking change to framing or payload shapes. */
|
|
9
|
+
declare const PROTOCOL_VERSION = 1;
|
|
10
|
+
declare const FrameType: {
|
|
11
|
+
readonly HELLO: 1;
|
|
12
|
+
readonly WELCOME: 2;
|
|
13
|
+
readonly ERROR: 3;
|
|
14
|
+
readonly PING: 4;
|
|
15
|
+
readonly PONG: 5;
|
|
16
|
+
readonly DELTA: 6;
|
|
17
|
+
readonly WRITE: 7;
|
|
18
|
+
readonly CORRECT: 8;
|
|
19
|
+
readonly CALL: 9;
|
|
20
|
+
readonly REPLY: 10;
|
|
21
|
+
readonly MSG: 11;
|
|
22
|
+
/**
|
|
23
|
+
* Client → server, no payload. Sent on `room.leave()`/graceful close, right
|
|
24
|
+
* before the socket closes: tells the supervisor this departure is deliberate, so it skips the
|
|
25
|
+
* reconnect grace window and the room's `onLeave` sees `reason: 'left'` instead of waiting for
|
|
26
|
+
* the close to time out into `'timeout'`. Additive — protocol version stays 1.
|
|
27
|
+
*/
|
|
28
|
+
readonly LEAVE: 12;
|
|
29
|
+
};
|
|
30
|
+
type FrameType = (typeof FrameType)[keyof typeof FrameType];
|
|
31
|
+
interface Frame {
|
|
32
|
+
readonly type: FrameType;
|
|
33
|
+
readonly payload: Uint8Array;
|
|
34
|
+
}
|
|
35
|
+
declare function isFrameType(v: number): v is FrameType;
|
|
36
|
+
/** `[type byte][payload]`. */
|
|
37
|
+
declare function encodeFrame(type: FrameType, payload: Uint8Array): Uint8Array;
|
|
38
|
+
/** `payload` is a subarray view into `bytes` (no copy). Throws on an empty buffer or unknown type. */
|
|
39
|
+
declare function decodeFrame(bytes: Uint8Array): Frame;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Protocol error catalogue: stable numeric codes (1..n) with a `{placeholder}` message
|
|
43
|
+
* template per code. `ErrorPayload` (the wire shape) lives in `session.ts`.
|
|
44
|
+
*/
|
|
45
|
+
interface ErrorCodeDef {
|
|
46
|
+
readonly code: number;
|
|
47
|
+
readonly message: string;
|
|
48
|
+
}
|
|
49
|
+
/** Name -> `{ code, message template }`. Numeric codes are stable; never renumber. */
|
|
50
|
+
declare const ErrorCode: {
|
|
51
|
+
readonly E_PROTOCOL_VERSION: {
|
|
52
|
+
readonly code: 1;
|
|
53
|
+
readonly message: "unsupported protocol version {version}";
|
|
54
|
+
};
|
|
55
|
+
readonly E_ORIGIN: {
|
|
56
|
+
readonly code: 2;
|
|
57
|
+
readonly message: "origin {origin} is not allowed";
|
|
58
|
+
};
|
|
59
|
+
readonly E_AUTH: {
|
|
60
|
+
readonly code: 3;
|
|
61
|
+
readonly message: "authentication failed";
|
|
62
|
+
};
|
|
63
|
+
readonly E_SCHEMA_MISMATCH: {
|
|
64
|
+
readonly code: 4;
|
|
65
|
+
readonly message: "schema hash mismatch";
|
|
66
|
+
};
|
|
67
|
+
readonly E_ROOM_NOT_FOUND: {
|
|
68
|
+
readonly code: 5;
|
|
69
|
+
readonly message: "room {roomId} not found — room ids are 1-32 of A-Z a-z 0-9 - _";
|
|
70
|
+
};
|
|
71
|
+
readonly E_ROOM_FULL: {
|
|
72
|
+
readonly code: 6;
|
|
73
|
+
readonly message: "room {roomId} is full";
|
|
74
|
+
};
|
|
75
|
+
readonly E_ROOM_CLOSED: {
|
|
76
|
+
readonly code: 7;
|
|
77
|
+
readonly message: "room {roomId} is closed";
|
|
78
|
+
};
|
|
79
|
+
readonly E_RESUME_EXPIRED: {
|
|
80
|
+
readonly code: 8;
|
|
81
|
+
readonly message: "resume token expired";
|
|
82
|
+
};
|
|
83
|
+
readonly E_RATE_LIMITED: {
|
|
84
|
+
readonly code: 9;
|
|
85
|
+
readonly message: "rate limited";
|
|
86
|
+
};
|
|
87
|
+
readonly E_NOT_OWNER: {
|
|
88
|
+
readonly code: 10;
|
|
89
|
+
readonly message: "not the owner of {entity} {id}";
|
|
90
|
+
};
|
|
91
|
+
readonly E_RPC_UNKNOWN: {
|
|
92
|
+
readonly code: 11;
|
|
93
|
+
readonly message: "unknown rpc {name}";
|
|
94
|
+
};
|
|
95
|
+
readonly E_RPC_REJECTED: {
|
|
96
|
+
readonly code: 12;
|
|
97
|
+
readonly message: "rpc {name} rejected: {reason}";
|
|
98
|
+
};
|
|
99
|
+
readonly E_RPC_TIMEOUT: {
|
|
100
|
+
readonly code: 13;
|
|
101
|
+
readonly message: "rpc {name} timed out";
|
|
102
|
+
};
|
|
103
|
+
readonly E_RPC_BAD_PARAMS: {
|
|
104
|
+
readonly code: 14;
|
|
105
|
+
readonly message: "bad params for rpc {name}: {reason}";
|
|
106
|
+
};
|
|
107
|
+
readonly E_WRITE_REJECTED: {
|
|
108
|
+
readonly code: 15;
|
|
109
|
+
readonly message: "write rejected: {reason}";
|
|
110
|
+
};
|
|
111
|
+
readonly E_KICKED: {
|
|
112
|
+
readonly code: 16;
|
|
113
|
+
readonly message: "kicked: {reason}";
|
|
114
|
+
};
|
|
115
|
+
readonly E_INTERNAL: {
|
|
116
|
+
readonly code: 17;
|
|
117
|
+
readonly message: "internal error";
|
|
118
|
+
};
|
|
119
|
+
readonly E_BAD_FRAME: {
|
|
120
|
+
readonly code: 18;
|
|
121
|
+
readonly message: "malformed or unexpected frame {frame}";
|
|
122
|
+
};
|
|
123
|
+
readonly E_STARTING: {
|
|
124
|
+
readonly code: 19;
|
|
125
|
+
readonly message: "your game server is starting";
|
|
126
|
+
};
|
|
127
|
+
readonly E_SLOW_CONSUMER: {
|
|
128
|
+
readonly code: 20;
|
|
129
|
+
readonly message: "client cannot keep up with the stream; reconnect for a fresh snapshot";
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
type ErrorCodeName = keyof typeof ErrorCode;
|
|
133
|
+
interface ErrorCatalogueEntry {
|
|
134
|
+
readonly name: ErrorCodeName;
|
|
135
|
+
readonly code: number;
|
|
136
|
+
readonly message: string;
|
|
137
|
+
}
|
|
138
|
+
/** Flat catalogue (stable order = declaration order) for the CLI / docs generator. */
|
|
139
|
+
declare const ERROR_CATALOGUE: readonly ErrorCatalogueEntry[];
|
|
140
|
+
/** Fills `{placeholders}` in the named error's message template from `vars`. */
|
|
141
|
+
declare function formatError(name: ErrorCodeName, vars?: Readonly<Record<string, string | number>>): string;
|
|
142
|
+
/** Looks up a catalogue entry by numeric code. Throws if unknown. */
|
|
143
|
+
declare function errorByCode(code: number): ErrorCatalogueEntry;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Session-lifecycle payload codecs: HELLO, WELCOME, ERROR, PING, PONG, and the framing
|
|
147
|
+
* helpers for DELTA/WRITE/CORRECT (whose payload is opaque schema-codec bytes at this layer).
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
type Credential = {
|
|
151
|
+
readonly kind: 'key';
|
|
152
|
+
readonly key: string;
|
|
153
|
+
} | {
|
|
154
|
+
readonly kind: 'token';
|
|
155
|
+
readonly token: string;
|
|
156
|
+
};
|
|
157
|
+
interface Hello {
|
|
158
|
+
readonly protocolVersion: number;
|
|
159
|
+
readonly credential: Credential;
|
|
160
|
+
readonly roomId: string;
|
|
161
|
+
/** First 8 bytes of the schema hash (see `@irtio/schema` `Schema.hash8`). */
|
|
162
|
+
readonly schemaHash8: Uint8Array;
|
|
163
|
+
/** Present when resuming a prior session. */
|
|
164
|
+
readonly resumeToken?: string | undefined;
|
|
165
|
+
/** Role requested at join (server may override/reject). */
|
|
166
|
+
readonly role?: string | undefined;
|
|
167
|
+
/** Display name requested at join. */
|
|
168
|
+
readonly name?: string | undefined;
|
|
169
|
+
}
|
|
170
|
+
declare function encodeHello(h: Hello): Uint8Array;
|
|
171
|
+
declare function decodeHello(bytes: Uint8Array): Hello;
|
|
172
|
+
interface Welcome {
|
|
173
|
+
readonly clientId: string;
|
|
174
|
+
readonly role: string;
|
|
175
|
+
readonly tick: number;
|
|
176
|
+
/** Full-state snapshot, opaque schema-codec bytes. */
|
|
177
|
+
readonly snapshot: Uint8Array;
|
|
178
|
+
readonly resumeToken: string;
|
|
179
|
+
/**
|
|
180
|
+
* The room this session joined. A client that sent an empty `HELLO.roomId` (create-by-join)
|
|
181
|
+
* learns its room code here — it is what `room.id` / `room.link` / `?room=` are built
|
|
182
|
+
* from. Appended after `resumeToken`, so every byte before it is unchanged from the
|
|
183
|
+
* pre-`roomId` layout.
|
|
184
|
+
*/
|
|
185
|
+
readonly roomId: string;
|
|
186
|
+
/**
|
|
187
|
+
* The room's tick interval in milliseconds (`round(1000 / tickRate)`), or `0` when the sender
|
|
188
|
+
* does not know it (a relay room, or a pre-week-8 server). The client's interpolation delay
|
|
189
|
+
* defaults to `max(50, 2 × tickIntervalMs)` (D20). Appended after `roomId` in week 8, same
|
|
190
|
+
* additive style as `roomId` itself.
|
|
191
|
+
*/
|
|
192
|
+
readonly tickIntervalMs: number;
|
|
193
|
+
}
|
|
194
|
+
declare function encodeWelcome(w0: Welcome): Uint8Array;
|
|
195
|
+
declare function decodeWelcome(bytes: Uint8Array): Welcome;
|
|
196
|
+
interface ErrorPayload {
|
|
197
|
+
/** Numeric `ErrorCode.<NAME>.code` (see `errors.ts`). */
|
|
198
|
+
readonly code: number;
|
|
199
|
+
readonly message: string;
|
|
200
|
+
readonly fatal: boolean;
|
|
201
|
+
}
|
|
202
|
+
declare function encodeErrorPayload(e: ErrorPayload): Uint8Array;
|
|
203
|
+
declare function decodeErrorPayload(bytes: Uint8Array): ErrorPayload;
|
|
204
|
+
interface Ping {
|
|
205
|
+
readonly t: number;
|
|
206
|
+
}
|
|
207
|
+
declare function encodePing(p: Ping): Uint8Array;
|
|
208
|
+
declare function decodePing(bytes: Uint8Array): Ping;
|
|
209
|
+
interface Pong {
|
|
210
|
+
readonly t: number;
|
|
211
|
+
readonly serverTick: number;
|
|
212
|
+
}
|
|
213
|
+
declare function encodePong(p: Pong): Uint8Array;
|
|
214
|
+
declare function decodePong(bytes: Uint8Array): Pong;
|
|
215
|
+
/** Identity: the DELTA frame payload is schema-codec bytes, opaque at this layer. */
|
|
216
|
+
declare function deltaPayload(codecBytes: Uint8Array): Uint8Array;
|
|
217
|
+
/** Identity: the WRITE frame payload is schema-codec bytes, opaque at this layer. */
|
|
218
|
+
declare function writePayload(codecBytes: Uint8Array): Uint8Array;
|
|
219
|
+
/**
|
|
220
|
+
* The CORRECT frame payload: the schema-codec delta bytes with the judged client write tick
|
|
221
|
+
* (u32) appended (week 8, D19). Additive: the codec's delta decoder never reads past the delta
|
|
222
|
+
* body, so a decoder that does not know about the suffix ignores it; a reader that does
|
|
223
|
+
* (`decodeDeltaFrom`) is left positioned exactly at it.
|
|
224
|
+
*/
|
|
225
|
+
declare function correctPayload(codecBytes: Uint8Array, clientTick: number): Uint8Array;
|
|
226
|
+
/**
|
|
227
|
+
* Reads the `clientTick` suffix a reader is positioned at after `decodeDeltaFrom`, or
|
|
228
|
+
* `undefined` for a pre-week-8 CORRECT payload with no suffix.
|
|
229
|
+
*/
|
|
230
|
+
declare function readCorrectClientTick(r: ByteReader): number | undefined;
|
|
231
|
+
declare function encodeDeltaFrame(codecBytes: Uint8Array): Uint8Array;
|
|
232
|
+
declare function encodeWriteFrame(codecBytes: Uint8Array): Uint8Array;
|
|
233
|
+
declare function encodeCorrectFrame(codecBytes: Uint8Array, clientTick: number): Uint8Array;
|
|
234
|
+
|
|
235
|
+
interface Call {
|
|
236
|
+
readonly reqId: number;
|
|
237
|
+
readonly rpcId: number;
|
|
238
|
+
/** Rest-of-buffer: opaque schema-codec-encoded params (not length-prefixed). */
|
|
239
|
+
readonly params: Uint8Array;
|
|
240
|
+
}
|
|
241
|
+
declare function encodeCall(c: Call): Uint8Array;
|
|
242
|
+
declare function decodeCall(bytes: Uint8Array): Call;
|
|
243
|
+
type Reply = {
|
|
244
|
+
readonly reqId: number;
|
|
245
|
+
readonly ok: true;
|
|
246
|
+
readonly result: Uint8Array;
|
|
247
|
+
} | {
|
|
248
|
+
readonly reqId: number;
|
|
249
|
+
readonly ok: false;
|
|
250
|
+
readonly error: string;
|
|
251
|
+
};
|
|
252
|
+
declare function encodeReply(r: Reply): Uint8Array;
|
|
253
|
+
declare function decodeReply(bytes: Uint8Array): Reply;
|
|
254
|
+
/** `requestOwnership(entity, id) -> { granted }`: the only built-in RPC. */
|
|
255
|
+
declare const requestOwnership: _irtio_schema.RpcDef<"server", {
|
|
256
|
+
entity: _irtio_schema.Type<string, false>;
|
|
257
|
+
id: _irtio_schema.Type<string, false>;
|
|
258
|
+
}, {
|
|
259
|
+
granted: _irtio_schema.Type<boolean, false>;
|
|
260
|
+
}>;
|
|
261
|
+
/** Built-in RPCs every room gets for free, appended to the schema's RPC table. */
|
|
262
|
+
declare const builtinRpcs: {
|
|
263
|
+
requestOwnership: _irtio_schema.RpcDef<"server", {
|
|
264
|
+
entity: _irtio_schema.Type<string, false>;
|
|
265
|
+
id: _irtio_schema.Type<string, false>;
|
|
266
|
+
}, {
|
|
267
|
+
granted: _irtio_schema.Type<boolean, false>;
|
|
268
|
+
}>;
|
|
269
|
+
};
|
|
270
|
+
declare function rpcTable(schema: AnySchema): RpcDesc[];
|
|
271
|
+
declare function rpcIdOf(schema: AnySchema, name: string): number;
|
|
272
|
+
declare function rpcByIdOf(schema: AnySchema, id: number): RpcDesc;
|
|
273
|
+
/**
|
|
274
|
+
* The client's callable RPC set: every builder-declared server RPC plus irtio's built-ins
|
|
275
|
+
* (`requestOwnership`), which the schema builder never declares (`Implementations<ServerRpcs<R>>`
|
|
276
|
+
* stays builder-only — only the client call proxy needs the built-ins mixed in).
|
|
277
|
+
*/
|
|
278
|
+
type ClientCallable<R extends RpcMap> = ServerRpcs<R> & typeof builtinRpcs;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* MSG frame payload: an out-of-band message with an addressing discriminator. The same
|
|
282
|
+
* shape is reused in both directions — client→server as the send *target*, server→client as
|
|
283
|
+
* the *from* (sender) of a relayed message.
|
|
284
|
+
*/
|
|
285
|
+
type MsgTarget = {
|
|
286
|
+
readonly kind: 'all';
|
|
287
|
+
} | {
|
|
288
|
+
readonly kind: 'client';
|
|
289
|
+
readonly clientId: string;
|
|
290
|
+
} | {
|
|
291
|
+
readonly kind: 'role';
|
|
292
|
+
readonly role: string;
|
|
293
|
+
} | {
|
|
294
|
+
readonly kind: 'server';
|
|
295
|
+
};
|
|
296
|
+
interface Msg {
|
|
297
|
+
readonly target: MsgTarget;
|
|
298
|
+
/** Rest-of-buffer: opaque application payload. */
|
|
299
|
+
readonly payload: Uint8Array;
|
|
300
|
+
}
|
|
301
|
+
declare function encodeMsg(m: Msg): Uint8Array;
|
|
302
|
+
declare function decodeMsg(bytes: Uint8Array): Msg;
|
|
303
|
+
|
|
304
|
+
declare const presenceEntity: _irtio_schema.EntityDef<{
|
|
305
|
+
clientId: _irtio_schema.Type<string, false>;
|
|
306
|
+
role: _irtio_schema.Type<string, false>;
|
|
307
|
+
name: _irtio_schema.Type<string, false>;
|
|
308
|
+
connected: _irtio_schema.Type<boolean, false>;
|
|
309
|
+
}, {
|
|
310
|
+
readonly serverOwned: true;
|
|
311
|
+
}>;
|
|
312
|
+
/** Collection name the runtime merges `presenceEntity` in under. */
|
|
313
|
+
declare const PRESENCE_COLLECTION = "clients";
|
|
314
|
+
type PresenceRecord = InstanceOf<typeof presenceEntity>;
|
|
315
|
+
/**
|
|
316
|
+
* The runtime-extended schema: the builder's schema plus the built-in `clients` presence
|
|
317
|
+
* collection. Both the runtime and the client SDK derive it, so snapshot/delta headers carry its
|
|
318
|
+
* `hash8` while `HELLO` carries the builder's. Pure and memoised per schema.
|
|
319
|
+
*/
|
|
320
|
+
declare function withBuiltins<S extends AnySchema>(schema: S): Schema<SchemaDefs<S> & {
|
|
321
|
+
clients: typeof presenceEntity;
|
|
322
|
+
}, SchemaRpc<S>, SchemaRoles<S>>;
|
|
323
|
+
/**
|
|
324
|
+
* The schema of a room with nothing deployed — no builder collections, just the
|
|
325
|
+
* built-in presence merged by `withBuiltins`. The supervisor's relay forwarder encodes presence
|
|
326
|
+
* snapshots/deltas with it so the client SDK has one `room.clients` path with or without a
|
|
327
|
+
* deployed schema.
|
|
328
|
+
*/
|
|
329
|
+
declare const relaySchema: Schema<{
|
|
330
|
+
clients: typeof presenceEntity;
|
|
331
|
+
}, {}, readonly string[]>;
|
|
332
|
+
/** The `schemaHash8` a relay HELLO carries: 8 zero bytes ("no schema"). */
|
|
333
|
+
declare const RELAY_HASH8: Uint8Array;
|
|
334
|
+
declare function isRelayHash8(hash8: Uint8Array): boolean;
|
|
335
|
+
|
|
336
|
+
export { type Call, type ClientCallable, type Credential, ERROR_CATALOGUE, type ErrorCatalogueEntry, ErrorCode, type ErrorCodeDef, type ErrorCodeName, type ErrorPayload, type Frame, FrameType, type Hello, type Msg, type MsgTarget, PRESENCE_COLLECTION, PROTOCOL_VERSION, type Ping, type Pong, type PresenceRecord, RELAY_HASH8, type Reply, type Welcome, builtinRpcs, correctPayload, decodeCall, decodeErrorPayload, decodeFrame, decodeHello, decodeMsg, decodePing, decodePong, decodeReply, decodeWelcome, deltaPayload, encodeCall, encodeCorrectFrame, encodeDeltaFrame, encodeErrorPayload, encodeFrame, encodeHello, encodeMsg, encodePing, encodePong, encodeReply, encodeWelcome, encodeWriteFrame, errorByCode, formatError, isFrameType, isRelayHash8, presenceEntity, readCorrectClientTick, relaySchema, requestOwnership, rpcByIdOf, rpcIdOf, rpcTable, withBuiltins, writePayload };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
// src/frame.ts
|
|
2
|
+
var PROTOCOL_VERSION = 1;
|
|
3
|
+
var FrameType = {
|
|
4
|
+
HELLO: 1,
|
|
5
|
+
WELCOME: 2,
|
|
6
|
+
ERROR: 3,
|
|
7
|
+
PING: 4,
|
|
8
|
+
PONG: 5,
|
|
9
|
+
DELTA: 6,
|
|
10
|
+
WRITE: 7,
|
|
11
|
+
CORRECT: 8,
|
|
12
|
+
CALL: 9,
|
|
13
|
+
REPLY: 10,
|
|
14
|
+
MSG: 11,
|
|
15
|
+
/**
|
|
16
|
+
* Client → server, no payload. Sent on `room.leave()`/graceful close, right
|
|
17
|
+
* before the socket closes: tells the supervisor this departure is deliberate, so it skips the
|
|
18
|
+
* reconnect grace window and the room's `onLeave` sees `reason: 'left'` instead of waiting for
|
|
19
|
+
* the close to time out into `'timeout'`. Additive — protocol version stays 1.
|
|
20
|
+
*/
|
|
21
|
+
LEAVE: 12
|
|
22
|
+
};
|
|
23
|
+
var FRAME_TYPE_VALUES = new Set(Object.values(FrameType));
|
|
24
|
+
function isFrameType(v) {
|
|
25
|
+
return FRAME_TYPE_VALUES.has(v);
|
|
26
|
+
}
|
|
27
|
+
function encodeFrame(type, payload) {
|
|
28
|
+
const out = new Uint8Array(1 + payload.length);
|
|
29
|
+
out[0] = type;
|
|
30
|
+
out.set(payload, 1);
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function decodeFrame(bytes) {
|
|
34
|
+
if (bytes.length === 0) throw new Error("decodeFrame: empty buffer");
|
|
35
|
+
const type = bytes[0];
|
|
36
|
+
if (!isFrameType(type)) throw new Error(`decodeFrame: unknown frame type ${type}`);
|
|
37
|
+
return { type, payload: bytes.subarray(1) };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/errors.ts
|
|
41
|
+
var ErrorCode = {
|
|
42
|
+
E_PROTOCOL_VERSION: { code: 1, message: "unsupported protocol version {version}" },
|
|
43
|
+
E_ORIGIN: { code: 2, message: "origin {origin} is not allowed" },
|
|
44
|
+
E_AUTH: { code: 3, message: "authentication failed" },
|
|
45
|
+
E_SCHEMA_MISMATCH: { code: 4, message: "schema hash mismatch" },
|
|
46
|
+
E_ROOM_NOT_FOUND: {
|
|
47
|
+
code: 5,
|
|
48
|
+
message: "room {roomId} not found \u2014 room ids are 1-32 of A-Z a-z 0-9 - _"
|
|
49
|
+
},
|
|
50
|
+
E_ROOM_FULL: { code: 6, message: "room {roomId} is full" },
|
|
51
|
+
E_ROOM_CLOSED: { code: 7, message: "room {roomId} is closed" },
|
|
52
|
+
E_RESUME_EXPIRED: { code: 8, message: "resume token expired" },
|
|
53
|
+
E_RATE_LIMITED: { code: 9, message: "rate limited" },
|
|
54
|
+
E_NOT_OWNER: { code: 10, message: "not the owner of {entity} {id}" },
|
|
55
|
+
E_RPC_UNKNOWN: { code: 11, message: "unknown rpc {name}" },
|
|
56
|
+
E_RPC_REJECTED: { code: 12, message: "rpc {name} rejected: {reason}" },
|
|
57
|
+
E_RPC_TIMEOUT: { code: 13, message: "rpc {name} timed out" },
|
|
58
|
+
E_RPC_BAD_PARAMS: { code: 14, message: "bad params for rpc {name}: {reason}" },
|
|
59
|
+
E_WRITE_REJECTED: { code: 15, message: "write rejected: {reason}" },
|
|
60
|
+
E_KICKED: { code: 16, message: "kicked: {reason}" },
|
|
61
|
+
E_INTERNAL: { code: 17, message: "internal error" },
|
|
62
|
+
E_BAD_FRAME: { code: 18, message: "malformed or unexpected frame {frame}" },
|
|
63
|
+
E_STARTING: { code: 19, message: "your game server is starting" },
|
|
64
|
+
E_SLOW_CONSUMER: {
|
|
65
|
+
code: 20,
|
|
66
|
+
message: "client cannot keep up with the stream; reconnect for a fresh snapshot"
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var ERROR_CATALOGUE = Object.keys(ErrorCode).map((name) => ({ name, code: ErrorCode[name].code, message: ErrorCode[name].message }));
|
|
70
|
+
var BY_CODE = new Map(
|
|
71
|
+
ERROR_CATALOGUE.map((e) => [e.code, e])
|
|
72
|
+
);
|
|
73
|
+
function formatError(name, vars = {}) {
|
|
74
|
+
const def = ErrorCode[name];
|
|
75
|
+
return def.message.replace(
|
|
76
|
+
/\{(\w+)\}/g,
|
|
77
|
+
(m, key) => Object.prototype.hasOwnProperty.call(vars, key) ? String(vars[key]) : m
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
function errorByCode(code) {
|
|
81
|
+
const found = BY_CODE.get(code);
|
|
82
|
+
if (!found) throw new Error(`errorByCode: unknown error code ${code}`);
|
|
83
|
+
return found;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/session.ts
|
|
87
|
+
import { ByteReader, ByteWriter } from "@irtio/schema";
|
|
88
|
+
function assertEof(r, what) {
|
|
89
|
+
if (!r.eof) throw new Error(`${what}: trailing bytes`);
|
|
90
|
+
}
|
|
91
|
+
var HELLO_RESUME_BIT = 1 << 0;
|
|
92
|
+
var HELLO_ROLE_BIT = 1 << 1;
|
|
93
|
+
var HELLO_NAME_BIT = 1 << 2;
|
|
94
|
+
function encodeHello(h) {
|
|
95
|
+
if (h.schemaHash8.length !== 8) {
|
|
96
|
+
throw new Error(`encodeHello: schemaHash8 must be 8 bytes, got ${h.schemaHash8.length}`);
|
|
97
|
+
}
|
|
98
|
+
const w = new ByteWriter();
|
|
99
|
+
w.u8(h.protocolVersion);
|
|
100
|
+
if (h.credential.kind === "key") {
|
|
101
|
+
w.u8(0);
|
|
102
|
+
w.str(h.credential.key);
|
|
103
|
+
} else {
|
|
104
|
+
w.u8(1);
|
|
105
|
+
w.str(h.credential.token);
|
|
106
|
+
}
|
|
107
|
+
w.str(h.roomId);
|
|
108
|
+
w.bytes(h.schemaHash8);
|
|
109
|
+
let mask = 0;
|
|
110
|
+
if (h.resumeToken !== void 0) mask |= HELLO_RESUME_BIT;
|
|
111
|
+
if (h.role !== void 0) mask |= HELLO_ROLE_BIT;
|
|
112
|
+
if (h.name !== void 0) mask |= HELLO_NAME_BIT;
|
|
113
|
+
w.u8(mask);
|
|
114
|
+
if (h.resumeToken !== void 0) w.str(h.resumeToken);
|
|
115
|
+
if (h.role !== void 0) w.str(h.role);
|
|
116
|
+
if (h.name !== void 0) w.str(h.name);
|
|
117
|
+
return w.finish();
|
|
118
|
+
}
|
|
119
|
+
function decodeHello(bytes) {
|
|
120
|
+
const r = new ByteReader(bytes);
|
|
121
|
+
const protocolVersion = r.u8();
|
|
122
|
+
const credKind = r.u8();
|
|
123
|
+
let credential;
|
|
124
|
+
if (credKind === 0) credential = { kind: "key", key: r.str() };
|
|
125
|
+
else if (credKind === 1) credential = { kind: "token", token: r.str() };
|
|
126
|
+
else throw new Error(`decodeHello: unknown credential kind ${credKind}`);
|
|
127
|
+
const roomId = r.str();
|
|
128
|
+
const schemaHash8 = r.bytes(8);
|
|
129
|
+
const mask = r.u8();
|
|
130
|
+
const resumeToken = (mask & HELLO_RESUME_BIT) !== 0 ? r.str() : void 0;
|
|
131
|
+
const role = (mask & HELLO_ROLE_BIT) !== 0 ? r.str() : void 0;
|
|
132
|
+
const name = (mask & HELLO_NAME_BIT) !== 0 ? r.str() : void 0;
|
|
133
|
+
assertEof(r, "decodeHello");
|
|
134
|
+
const hello = { protocolVersion, credential, roomId, schemaHash8 };
|
|
135
|
+
return {
|
|
136
|
+
...hello,
|
|
137
|
+
...resumeToken !== void 0 ? { resumeToken } : {},
|
|
138
|
+
...role !== void 0 ? { role } : {},
|
|
139
|
+
...name !== void 0 ? { name } : {}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function encodeWelcome(w0) {
|
|
143
|
+
const w = new ByteWriter();
|
|
144
|
+
w.str(w0.clientId);
|
|
145
|
+
w.str(w0.role);
|
|
146
|
+
w.u32(w0.tick);
|
|
147
|
+
w.blob(w0.snapshot);
|
|
148
|
+
w.str(w0.resumeToken);
|
|
149
|
+
w.str(w0.roomId);
|
|
150
|
+
w.u16(w0.tickIntervalMs);
|
|
151
|
+
return w.finish();
|
|
152
|
+
}
|
|
153
|
+
function decodeWelcome(bytes) {
|
|
154
|
+
const r = new ByteReader(bytes);
|
|
155
|
+
const clientId = r.str();
|
|
156
|
+
const role = r.str();
|
|
157
|
+
const tick = r.u32();
|
|
158
|
+
const snapshot = r.blob();
|
|
159
|
+
const resumeToken = r.str();
|
|
160
|
+
const roomId = r.str();
|
|
161
|
+
const tickIntervalMs = r.eof ? 0 : r.u16();
|
|
162
|
+
assertEof(r, "decodeWelcome");
|
|
163
|
+
return { clientId, role, tick, snapshot, resumeToken, roomId, tickIntervalMs };
|
|
164
|
+
}
|
|
165
|
+
function encodeErrorPayload(e) {
|
|
166
|
+
const w = new ByteWriter();
|
|
167
|
+
w.u16(e.code);
|
|
168
|
+
w.str(e.message);
|
|
169
|
+
w.bool(e.fatal);
|
|
170
|
+
return w.finish();
|
|
171
|
+
}
|
|
172
|
+
function decodeErrorPayload(bytes) {
|
|
173
|
+
const r = new ByteReader(bytes);
|
|
174
|
+
const code = r.u16();
|
|
175
|
+
const message = r.str();
|
|
176
|
+
const fatal = r.bool();
|
|
177
|
+
assertEof(r, "decodeErrorPayload");
|
|
178
|
+
return { code, message, fatal };
|
|
179
|
+
}
|
|
180
|
+
function encodePing(p) {
|
|
181
|
+
const w = new ByteWriter();
|
|
182
|
+
w.u32(p.t);
|
|
183
|
+
return w.finish();
|
|
184
|
+
}
|
|
185
|
+
function decodePing(bytes) {
|
|
186
|
+
const r = new ByteReader(bytes);
|
|
187
|
+
const t = r.u32();
|
|
188
|
+
assertEof(r, "decodePing");
|
|
189
|
+
return { t };
|
|
190
|
+
}
|
|
191
|
+
function encodePong(p) {
|
|
192
|
+
const w = new ByteWriter();
|
|
193
|
+
w.u32(p.t);
|
|
194
|
+
w.u32(p.serverTick);
|
|
195
|
+
return w.finish();
|
|
196
|
+
}
|
|
197
|
+
function decodePong(bytes) {
|
|
198
|
+
const r = new ByteReader(bytes);
|
|
199
|
+
const t = r.u32();
|
|
200
|
+
const serverTick = r.u32();
|
|
201
|
+
assertEof(r, "decodePong");
|
|
202
|
+
return { t, serverTick };
|
|
203
|
+
}
|
|
204
|
+
function deltaPayload(codecBytes) {
|
|
205
|
+
return codecBytes;
|
|
206
|
+
}
|
|
207
|
+
function writePayload(codecBytes) {
|
|
208
|
+
return codecBytes;
|
|
209
|
+
}
|
|
210
|
+
function correctPayload(codecBytes, clientTick) {
|
|
211
|
+
const w = new ByteWriter(codecBytes.length + 4);
|
|
212
|
+
w.bytes(codecBytes);
|
|
213
|
+
w.u32(clientTick);
|
|
214
|
+
return w.finish();
|
|
215
|
+
}
|
|
216
|
+
function readCorrectClientTick(r) {
|
|
217
|
+
return r.remaining >= 4 ? r.u32() : void 0;
|
|
218
|
+
}
|
|
219
|
+
function encodeDeltaFrame(codecBytes) {
|
|
220
|
+
return encodeFrame(FrameType.DELTA, deltaPayload(codecBytes));
|
|
221
|
+
}
|
|
222
|
+
function encodeWriteFrame(codecBytes) {
|
|
223
|
+
return encodeFrame(FrameType.WRITE, writePayload(codecBytes));
|
|
224
|
+
}
|
|
225
|
+
function encodeCorrectFrame(codecBytes, clientTick) {
|
|
226
|
+
return encodeFrame(FrameType.CORRECT, correctPayload(codecBytes, clientTick));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/rpc.ts
|
|
230
|
+
import { ByteReader as ByteReader2, ByteWriter as ByteWriter2, bool, server, str } from "@irtio/schema";
|
|
231
|
+
function assertEof2(r, what) {
|
|
232
|
+
if (!r.eof) throw new Error(`${what}: trailing bytes`);
|
|
233
|
+
}
|
|
234
|
+
function encodeCall(c) {
|
|
235
|
+
const w = new ByteWriter2();
|
|
236
|
+
w.u32(c.reqId);
|
|
237
|
+
w.u16(c.rpcId);
|
|
238
|
+
w.bytes(c.params);
|
|
239
|
+
return w.finish();
|
|
240
|
+
}
|
|
241
|
+
function decodeCall(bytes) {
|
|
242
|
+
const r = new ByteReader2(bytes);
|
|
243
|
+
const reqId = r.u32();
|
|
244
|
+
const rpcId = r.u16();
|
|
245
|
+
const params = r.rest();
|
|
246
|
+
return { reqId, rpcId, params };
|
|
247
|
+
}
|
|
248
|
+
function encodeReply(r) {
|
|
249
|
+
const w = new ByteWriter2();
|
|
250
|
+
w.u32(r.reqId);
|
|
251
|
+
w.bool(r.ok);
|
|
252
|
+
if (r.ok) w.bytes(r.result);
|
|
253
|
+
else w.str(r.error);
|
|
254
|
+
return w.finish();
|
|
255
|
+
}
|
|
256
|
+
function decodeReply(bytes) {
|
|
257
|
+
const r = new ByteReader2(bytes);
|
|
258
|
+
const reqId = r.u32();
|
|
259
|
+
const ok = r.bool();
|
|
260
|
+
if (ok) {
|
|
261
|
+
const result = r.rest();
|
|
262
|
+
return { reqId, ok: true, result };
|
|
263
|
+
}
|
|
264
|
+
const error = r.str();
|
|
265
|
+
assertEof2(r, "decodeReply");
|
|
266
|
+
return { reqId, ok: false, error };
|
|
267
|
+
}
|
|
268
|
+
var requestOwnership = server({
|
|
269
|
+
params: { entity: str(64), id: str(32) },
|
|
270
|
+
returns: { granted: bool }
|
|
271
|
+
});
|
|
272
|
+
var builtinRpcs = { requestOwnership };
|
|
273
|
+
function fieldDescsOf(fields) {
|
|
274
|
+
return Object.entries(fields).map(([name, t], index) => ({ name, index, type: t.desc }));
|
|
275
|
+
}
|
|
276
|
+
var builtinRpcDescs = Object.keys(builtinRpcs).sort().map((name, index) => {
|
|
277
|
+
const r = builtinRpcs[name];
|
|
278
|
+
return {
|
|
279
|
+
name,
|
|
280
|
+
index,
|
|
281
|
+
direction: r.direction,
|
|
282
|
+
params: fieldDescsOf(r.params),
|
|
283
|
+
returns: r.returns ? fieldDescsOf(r.returns) : void 0
|
|
284
|
+
};
|
|
285
|
+
});
|
|
286
|
+
var rpcTables = /* @__PURE__ */ new WeakMap();
|
|
287
|
+
function rpcTable(schema) {
|
|
288
|
+
let t = rpcTables.get(schema);
|
|
289
|
+
if (!t) {
|
|
290
|
+
t = [...schema.rpcs, ...builtinRpcDescs].map((r, index) => ({ ...r, index }));
|
|
291
|
+
rpcTables.set(schema, t);
|
|
292
|
+
}
|
|
293
|
+
return t;
|
|
294
|
+
}
|
|
295
|
+
function rpcIdOf(schema, name) {
|
|
296
|
+
const found = rpcTable(schema).find((r) => r.name === name);
|
|
297
|
+
if (!found) throw new Error(`rpcIdOf: unknown rpc ${JSON.stringify(name)}`);
|
|
298
|
+
return found.index;
|
|
299
|
+
}
|
|
300
|
+
function rpcByIdOf(schema, id) {
|
|
301
|
+
const found = rpcTable(schema).find((r) => r.index === id);
|
|
302
|
+
if (!found) throw new Error(`rpcByIdOf: unknown rpc id ${id}`);
|
|
303
|
+
return found;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/msg.ts
|
|
307
|
+
import { ByteReader as ByteReader3, ByteWriter as ByteWriter3 } from "@irtio/schema";
|
|
308
|
+
var MSG_KIND_ALL = 0;
|
|
309
|
+
var MSG_KIND_CLIENT = 1;
|
|
310
|
+
var MSG_KIND_ROLE = 2;
|
|
311
|
+
var MSG_KIND_SERVER = 3;
|
|
312
|
+
function encodeMsg(m) {
|
|
313
|
+
const w = new ByteWriter3();
|
|
314
|
+
switch (m.target.kind) {
|
|
315
|
+
case "all":
|
|
316
|
+
w.u8(MSG_KIND_ALL);
|
|
317
|
+
break;
|
|
318
|
+
case "client":
|
|
319
|
+
w.u8(MSG_KIND_CLIENT);
|
|
320
|
+
w.str(m.target.clientId);
|
|
321
|
+
break;
|
|
322
|
+
case "role":
|
|
323
|
+
w.u8(MSG_KIND_ROLE);
|
|
324
|
+
w.str(m.target.role);
|
|
325
|
+
break;
|
|
326
|
+
case "server":
|
|
327
|
+
w.u8(MSG_KIND_SERVER);
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
w.bytes(m.payload);
|
|
331
|
+
return w.finish();
|
|
332
|
+
}
|
|
333
|
+
function decodeMsg(bytes) {
|
|
334
|
+
const r = new ByteReader3(bytes);
|
|
335
|
+
const kind = r.u8();
|
|
336
|
+
let target;
|
|
337
|
+
switch (kind) {
|
|
338
|
+
case MSG_KIND_ALL:
|
|
339
|
+
target = { kind: "all" };
|
|
340
|
+
break;
|
|
341
|
+
case MSG_KIND_CLIENT:
|
|
342
|
+
target = { kind: "client", clientId: r.str() };
|
|
343
|
+
break;
|
|
344
|
+
case MSG_KIND_ROLE:
|
|
345
|
+
target = { kind: "role", role: r.str() };
|
|
346
|
+
break;
|
|
347
|
+
case MSG_KIND_SERVER:
|
|
348
|
+
target = { kind: "server" };
|
|
349
|
+
break;
|
|
350
|
+
default:
|
|
351
|
+
throw new Error(`decodeMsg: unknown target discriminator ${kind}`);
|
|
352
|
+
}
|
|
353
|
+
const payload = r.rest();
|
|
354
|
+
return { target, payload };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/presence.ts
|
|
358
|
+
import { bool as bool2, defineSchema, entity, str as str2 } from "@irtio/schema";
|
|
359
|
+
var presenceEntity = entity(
|
|
360
|
+
{ clientId: str2(32), role: str2(32), name: str2(32), connected: bool2 },
|
|
361
|
+
{ serverOwned: true }
|
|
362
|
+
);
|
|
363
|
+
var PRESENCE_COLLECTION = "clients";
|
|
364
|
+
var extended = /* @__PURE__ */ new WeakMap();
|
|
365
|
+
function withBuiltins(schema) {
|
|
366
|
+
let ext = extended.get(schema);
|
|
367
|
+
if (!ext) {
|
|
368
|
+
ext = defineSchema(
|
|
369
|
+
{ ...schema.defs, [PRESENCE_COLLECTION]: presenceEntity },
|
|
370
|
+
{
|
|
371
|
+
rpc: schema.rpc,
|
|
372
|
+
roles: schema.roles,
|
|
373
|
+
...schema.project !== void 0 ? { project: schema.project } : {},
|
|
374
|
+
allowReservedNames: true
|
|
375
|
+
}
|
|
376
|
+
);
|
|
377
|
+
extended.set(schema, ext);
|
|
378
|
+
}
|
|
379
|
+
return ext;
|
|
380
|
+
}
|
|
381
|
+
var relaySchema = withBuiltins(defineSchema({}));
|
|
382
|
+
var RELAY_HASH8 = new Uint8Array(8);
|
|
383
|
+
function isRelayHash8(hash8) {
|
|
384
|
+
if (hash8.length !== 8) return false;
|
|
385
|
+
for (const b of hash8) if (b !== 0) return false;
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
export {
|
|
389
|
+
ERROR_CATALOGUE,
|
|
390
|
+
ErrorCode,
|
|
391
|
+
FrameType,
|
|
392
|
+
PRESENCE_COLLECTION,
|
|
393
|
+
PROTOCOL_VERSION,
|
|
394
|
+
RELAY_HASH8,
|
|
395
|
+
builtinRpcs,
|
|
396
|
+
correctPayload,
|
|
397
|
+
decodeCall,
|
|
398
|
+
decodeErrorPayload,
|
|
399
|
+
decodeFrame,
|
|
400
|
+
decodeHello,
|
|
401
|
+
decodeMsg,
|
|
402
|
+
decodePing,
|
|
403
|
+
decodePong,
|
|
404
|
+
decodeReply,
|
|
405
|
+
decodeWelcome,
|
|
406
|
+
deltaPayload,
|
|
407
|
+
encodeCall,
|
|
408
|
+
encodeCorrectFrame,
|
|
409
|
+
encodeDeltaFrame,
|
|
410
|
+
encodeErrorPayload,
|
|
411
|
+
encodeFrame,
|
|
412
|
+
encodeHello,
|
|
413
|
+
encodeMsg,
|
|
414
|
+
encodePing,
|
|
415
|
+
encodePong,
|
|
416
|
+
encodeReply,
|
|
417
|
+
encodeWelcome,
|
|
418
|
+
encodeWriteFrame,
|
|
419
|
+
errorByCode,
|
|
420
|
+
formatError,
|
|
421
|
+
isFrameType,
|
|
422
|
+
isRelayHash8,
|
|
423
|
+
presenceEntity,
|
|
424
|
+
readCorrectClientTick,
|
|
425
|
+
relaySchema,
|
|
426
|
+
requestOwnership,
|
|
427
|
+
rpcByIdOf,
|
|
428
|
+
rpcIdOf,
|
|
429
|
+
rpcTable,
|
|
430
|
+
withBuiltins,
|
|
431
|
+
writePayload
|
|
432
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@irtio/protocol",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "irtio wire protocol: frames, framing, session payloads, error codes, built-in presence",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@irtio/schema": "0.1.0"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"test": "vitest run"
|
|
28
|
+
}
|
|
29
|
+
}
|