@net-mesh/browser 0.37.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/CHANGELOG.md +517 -0
- package/LICENSE-APACHE +190 -0
- package/LICENSE-MIT +21 -0
- package/README.md +666 -0
- package/dist/async-queue.d.ts +26 -0
- package/dist/async-queue.js +62 -0
- package/dist/credential.d.ts +70 -0
- package/dist/credential.js +95 -0
- package/dist/errors.d.ts +400 -0
- package/dist/errors.js +726 -0
- package/dist/events.d.ts +232 -0
- package/dist/events.js +298 -0
- package/dist/identity.d.ts +40 -0
- package/dist/identity.js +78 -0
- package/dist/index.bundle.js +2 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +46 -0
- package/dist/leader/events.d.ts +111 -0
- package/dist/leader/events.js +116 -0
- package/dist/leader/index.d.ts +14 -0
- package/dist/leader/index.js +11 -0
- package/dist/leader/session.d.ts +539 -0
- package/dist/leader/session.js +770 -0
- package/dist/leader/wasm.d.ts +123 -0
- package/dist/leader/wasm.js +39 -0
- package/dist/lobby.d.ts +183 -0
- package/dist/lobby.js +504 -0
- package/dist/local.d.ts +80 -0
- package/dist/local.js +160 -0
- package/dist/net_leaf.d.ts +1494 -0
- package/dist/net_leaf.js +3753 -0
- package/dist/net_leaf_bg.wasm +0 -0
- package/dist/net_leaf_bg.wasm.d.ts +137 -0
- package/dist/netcode/client.d.ts +102 -0
- package/dist/netcode/client.js +296 -0
- package/dist/netcode/clock.d.ts +45 -0
- package/dist/netcode/clock.js +54 -0
- package/dist/netcode/host.d.ts +118 -0
- package/dist/netcode/host.js +276 -0
- package/dist/netcode/index.d.ts +19 -0
- package/dist/netcode/index.js +14 -0
- package/dist/netcode/interpolate.d.ts +50 -0
- package/dist/netcode/interpolate.js +100 -0
- package/dist/netcode/wire.d.ts +125 -0
- package/dist/netcode/wire.js +117 -0
- package/dist/node.d.ts +908 -0
- package/dist/node.js +1085 -0
- package/dist/org.d.ts +412 -0
- package/dist/org.js +567 -0
- package/dist/peer-driver.d.ts +117 -0
- package/dist/peer-driver.js +224 -0
- package/dist/store/assembly.d.ts +176 -0
- package/dist/store/assembly.js +325 -0
- package/dist/store/chunker.d.ts +83 -0
- package/dist/store/chunker.js +190 -0
- package/dist/store/core.d.ts +136 -0
- package/dist/store/core.js +477 -0
- package/dist/store/definition.d.ts +23 -0
- package/dist/store/definition.js +83 -0
- package/dist/store/errors.d.ts +77 -0
- package/dist/store/errors.js +94 -0
- package/dist/store/host.d.ts +247 -0
- package/dist/store/host.js +675 -0
- package/dist/store/index.d.ts +50 -0
- package/dist/store/index.js +40 -0
- package/dist/store/interest.d.ts +40 -0
- package/dist/store/interest.js +77 -0
- package/dist/store/inventory.d.ts +81 -0
- package/dist/store/inventory.js +140 -0
- package/dist/store/join.d.ts +118 -0
- package/dist/store/join.js +724 -0
- package/dist/store/json.d.ts +50 -0
- package/dist/store/json.js +283 -0
- package/dist/store/ledger.d.ts +187 -0
- package/dist/store/ledger.js +292 -0
- package/dist/store/owner.d.ts +616 -0
- package/dist/store/owner.js +1711 -0
- package/dist/store/patch.d.ts +70 -0
- package/dist/store/patch.js +166 -0
- package/dist/store/player.d.ts +65 -0
- package/dist/store/player.js +261 -0
- package/dist/store/replica.d.ts +187 -0
- package/dist/store/replica.js +740 -0
- package/dist/store/state.d.ts +42 -0
- package/dist/store/state.js +165 -0
- package/dist/store/types.d.ts +200 -0
- package/dist/store/types.js +25 -0
- package/dist/store/visibility.d.ts +118 -0
- package/dist/store/visibility.js +282 -0
- package/dist/store/wire.d.ts +309 -0
- package/dist/store/wire.js +834 -0
- package/dist/stream.d.ts +174 -0
- package/dist/stream.js +238 -0
- package/dist/three/index.d.ts +109 -0
- package/dist/three/index.js +130 -0
- package/dist/udp-probe.d.ts +159 -0
- package/dist/udp-probe.js +245 -0
- package/dist/wasm.d.ts +642 -0
- package/dist/wasm.js +98 -0
- package/dist/world/border.d.ts +84 -0
- package/dist/world/border.js +99 -0
- package/dist/world/handoff.d.ts +187 -0
- package/dist/world/handoff.js +222 -0
- package/dist/world/index.d.ts +13 -0
- package/dist/world/index.js +9 -0
- package/dist/world/link.d.ts +179 -0
- package/dist/world/link.js +476 -0
- package/dist/world/regions.d.ts +154 -0
- package/dist/world/regions.js +314 -0
- package/package.json +72 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Large worlds: regions, their directory, and a player's merged view
|
|
3
|
+
* (browser plan §9 items 1–2).
|
|
4
|
+
*
|
|
5
|
+
* The map is cut into square regions of `size` units; region `r:<rx>:<rz>`
|
|
6
|
+
* covers `rx*size <= x < (rx+1)*size`, the same for z. Each region is its
|
|
7
|
+
* own store, hosted by whichever node announces it:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* // A region host (usually a dedicated Node host):
|
|
11
|
+
* hostStore({ definition: world, store: 'r:4:7', transport, … });
|
|
12
|
+
* announceRegions(node, 'my-world', ['r:4:7', 'r:4:8']);
|
|
13
|
+
*
|
|
14
|
+
* // A player:
|
|
15
|
+
* const view = joinWorld({ node, world: 'my-world', definition: world, collection: 'ships',
|
|
16
|
+
* position: { x, z }, size: 256, key: 'player', maxEventBytes: 8104 });
|
|
17
|
+
* bindEntities({ store: view, select: entities => entities, … });
|
|
18
|
+
* view.setPosition(x, z); // as the player moves
|
|
19
|
+
* await view.act('fire', { … }); // to the region the player is in
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* **The directory is discovery, not a root of trust.** Any node can
|
|
23
|
+
* announce any tag. {@link regionDirectory} therefore takes `trustedHosts`,
|
|
24
|
+
* the node ids that may host this world's regions. Without that list, a
|
|
25
|
+
* region two nodes claim is `ambiguous` and is NOT joined, rather than
|
|
26
|
+
* joining whichever answered first. A handoff link that authenticates by this
|
|
27
|
+
* directory (`handoffLink({ peerOf: directory.peerOf })`) needs the list:
|
|
28
|
+
* otherwise a node that announces a region could also send offers as it.
|
|
29
|
+
*/
|
|
30
|
+
import { peerHexOf } from '../store/host.js';
|
|
31
|
+
import { joinStore } from '../store/join.js';
|
|
32
|
+
/** Region `r:<rx>:<rz>` for a position, with regions `size` units square. */
|
|
33
|
+
export function regionOf(x, z, size) {
|
|
34
|
+
if (!(size > 0))
|
|
35
|
+
throw new RangeError('region size must be positive');
|
|
36
|
+
return `r:${Math.floor(x / size)}:${Math.floor(z / size)}`;
|
|
37
|
+
}
|
|
38
|
+
/** The regions within `radius` regions of a position (a square, `(2r+1)²`). */
|
|
39
|
+
export function regionsAround(x, z, options) {
|
|
40
|
+
const radius = Math.max(0, Math.floor(options.radius ?? 1));
|
|
41
|
+
const cx = Math.floor(x / options.size);
|
|
42
|
+
const cz = Math.floor(z / options.size);
|
|
43
|
+
const out = [];
|
|
44
|
+
for (let dx = -radius; dx <= radius; dx += 1) {
|
|
45
|
+
for (let dz = -radius; dz <= radius; dz += 1)
|
|
46
|
+
out.push(`r:${cx + dx}:${cz + dz}`);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** The directory tag a region host announces. */
|
|
51
|
+
export function regionTag(world, region) {
|
|
52
|
+
return `net-world:${world}:region:${region}`;
|
|
53
|
+
}
|
|
54
|
+
/** How often region hosts re-announce: an announcement is a lease. */
|
|
55
|
+
export const REGION_ANNOUNCE_MS = 2_000;
|
|
56
|
+
/**
|
|
57
|
+
* Announce that this node hosts `regions` of `world`, and keep announcing
|
|
58
|
+
* (an announcement is a lease). `announce` replaces a node's tags, so pass
|
|
59
|
+
* any other tags this node carries in `tags`. Returns a stop function.
|
|
60
|
+
*/
|
|
61
|
+
export function announceRegions(node, world, regions, options = {}) {
|
|
62
|
+
const tags = [...(options.tags ?? []), ...regions.map(region => regionTag(world, region))];
|
|
63
|
+
let stopped = false;
|
|
64
|
+
const once = () => {
|
|
65
|
+
if (!stopped)
|
|
66
|
+
void node.announce(tags).catch(() => undefined);
|
|
67
|
+
};
|
|
68
|
+
once();
|
|
69
|
+
const timer = setInterval(once, options.everyMs ?? REGION_ANNOUNCE_MS);
|
|
70
|
+
timer.unref?.();
|
|
71
|
+
return () => {
|
|
72
|
+
stopped = true;
|
|
73
|
+
clearInterval(timer);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** A region directory over the mesh's signed announcements. */
|
|
77
|
+
export function regionDirectory(options) {
|
|
78
|
+
const trusted = options.trustedHosts === undefined
|
|
79
|
+
? null
|
|
80
|
+
: new Set(options.trustedHosts.map(id => peerHexOf(id)).filter((id) => id !== null));
|
|
81
|
+
const cache = new Map();
|
|
82
|
+
return {
|
|
83
|
+
async lookup(region) {
|
|
84
|
+
let found;
|
|
85
|
+
try {
|
|
86
|
+
found = await options.node.query(regionTag(options.world, region));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
found = [];
|
|
90
|
+
}
|
|
91
|
+
const hosts = [
|
|
92
|
+
...new Set(found
|
|
93
|
+
.map(descriptor => peerHexOf(descriptor.peerIdHex))
|
|
94
|
+
.filter((id) => id !== null && (trusted === null || trusted.has(id)))),
|
|
95
|
+
].sort();
|
|
96
|
+
const answer = hosts.length === 0
|
|
97
|
+
? { status: 'unhosted' }
|
|
98
|
+
: hosts.length === 1
|
|
99
|
+
? { status: 'hosted', host: hosts[0] }
|
|
100
|
+
: { status: 'ambiguous', hosts };
|
|
101
|
+
cache.set(region, answer);
|
|
102
|
+
return answer;
|
|
103
|
+
},
|
|
104
|
+
peerOf(region) {
|
|
105
|
+
const known = cache.get(region);
|
|
106
|
+
return known?.status === 'hosted' ? known.host : null;
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Join a world: the player's region and its neighbours, merged into one view. */
|
|
111
|
+
export function joinWorld(options) {
|
|
112
|
+
const radius = Math.max(0, Math.floor(options.radius ?? 1));
|
|
113
|
+
const retryMs = options.retryMs ?? 2_000;
|
|
114
|
+
const directory = options.directory ??
|
|
115
|
+
regionDirectory({
|
|
116
|
+
node: options.node,
|
|
117
|
+
world: options.world,
|
|
118
|
+
...(options.trustedHosts === undefined ? {} : { trustedHosts: options.trustedHosts }),
|
|
119
|
+
});
|
|
120
|
+
const held = new Map();
|
|
121
|
+
const listeners = new Set();
|
|
122
|
+
let position = { x: options.position.x, z: options.position.z };
|
|
123
|
+
let merged = {};
|
|
124
|
+
let closed = false;
|
|
125
|
+
const lingerMs = Math.max(0, options.lingerMs ?? 500);
|
|
126
|
+
// Where each entity in `merged` came from, and entities lingering after
|
|
127
|
+
// vanishing from a held region: their last state and when they vanished.
|
|
128
|
+
let origin = new Map();
|
|
129
|
+
const lingering = new Map();
|
|
130
|
+
let lingerTimer = null;
|
|
131
|
+
const remerge = () => {
|
|
132
|
+
const next = {};
|
|
133
|
+
const from = new Map();
|
|
134
|
+
for (const [region, entry] of held) {
|
|
135
|
+
if (entry.phase !== 'ready' || entry.replica === null)
|
|
136
|
+
continue;
|
|
137
|
+
const doc = entry.replica.getState();
|
|
138
|
+
const entities = doc[options.collection];
|
|
139
|
+
if (typeof entities !== 'object' || entities === null)
|
|
140
|
+
continue;
|
|
141
|
+
for (const [id, entity] of Object.entries(entities)) {
|
|
142
|
+
const already = from.get(id);
|
|
143
|
+
if (already !== undefined) {
|
|
144
|
+
// In two regions at once (mid-handoff): keep the copy from the
|
|
145
|
+
// region that contains it, when we can tell.
|
|
146
|
+
const at = options.positionOf?.(entity) ?? null;
|
|
147
|
+
if (at === null || regionOf(at.x, at.z, options.size) !== region)
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
Object.defineProperty(next, id, { value: entity, enumerable: true, writable: true, configurable: true });
|
|
151
|
+
from.set(id, region);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Vanished from a region still held: linger. Reappeared anywhere, or its
|
|
155
|
+
// region released, or its time up: gone from the lingering set.
|
|
156
|
+
const at = Date.now();
|
|
157
|
+
for (const [id, region] of origin) {
|
|
158
|
+
if (from.has(id) || lingering.has(id) || lingerMs === 0)
|
|
159
|
+
continue;
|
|
160
|
+
if (held.get(region)?.phase === 'ready') {
|
|
161
|
+
lingering.set(id, { entity: merged[id], region, since: at });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
let soonest = Infinity;
|
|
165
|
+
for (const [id, linger] of lingering) {
|
|
166
|
+
if (from.has(id) || held.get(linger.region)?.phase !== 'ready' || at - linger.since >= lingerMs) {
|
|
167
|
+
lingering.delete(id);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
Object.defineProperty(next, id, { value: linger.entity, enumerable: true, writable: true, configurable: true });
|
|
171
|
+
soonest = Math.min(soonest, linger.since + lingerMs);
|
|
172
|
+
}
|
|
173
|
+
if (lingerTimer !== null)
|
|
174
|
+
clearTimeout(lingerTimer);
|
|
175
|
+
lingerTimer = null;
|
|
176
|
+
if (soonest !== Infinity && !closed) {
|
|
177
|
+
lingerTimer = setTimeout(remerge, Math.max(0, soonest - at) + 1);
|
|
178
|
+
lingerTimer.unref?.();
|
|
179
|
+
}
|
|
180
|
+
origin = from;
|
|
181
|
+
const previous = merged;
|
|
182
|
+
merged = next;
|
|
183
|
+
for (const listener of [...listeners]) {
|
|
184
|
+
try {
|
|
185
|
+
listener(merged, previous);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// One listener's bug is not the view's.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const release = (region) => {
|
|
193
|
+
const entry = held.get(region);
|
|
194
|
+
if (entry === undefined)
|
|
195
|
+
return;
|
|
196
|
+
held.delete(region);
|
|
197
|
+
entry.stop?.();
|
|
198
|
+
if (entry.retry !== null)
|
|
199
|
+
clearTimeout(entry.retry);
|
|
200
|
+
if (entry.replica !== null)
|
|
201
|
+
void entry.replica.close().catch(() => undefined);
|
|
202
|
+
remerge();
|
|
203
|
+
};
|
|
204
|
+
const acquire = (region) => {
|
|
205
|
+
if (held.has(region) || closed)
|
|
206
|
+
return;
|
|
207
|
+
const entry = { phase: 'looking', host: null, replica: null, stop: null, retry: null };
|
|
208
|
+
held.set(region, entry);
|
|
209
|
+
const later = () => {
|
|
210
|
+
if (held.get(region) !== entry || closed)
|
|
211
|
+
return;
|
|
212
|
+
entry.retry = setTimeout(() => {
|
|
213
|
+
entry.retry = null;
|
|
214
|
+
if (held.get(region) !== entry || closed)
|
|
215
|
+
return;
|
|
216
|
+
entry.phase = 'looking';
|
|
217
|
+
void attempt();
|
|
218
|
+
}, retryMs);
|
|
219
|
+
entry.retry.unref?.();
|
|
220
|
+
};
|
|
221
|
+
const attempt = async () => {
|
|
222
|
+
const found = await directory.lookup(region);
|
|
223
|
+
if (held.get(region) !== entry || closed)
|
|
224
|
+
return;
|
|
225
|
+
if (found.status !== 'hosted') {
|
|
226
|
+
entry.phase = found.status;
|
|
227
|
+
later();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
entry.host = found.host;
|
|
231
|
+
entry.phase = 'joining';
|
|
232
|
+
const replica = joinStore({
|
|
233
|
+
definition: options.definition,
|
|
234
|
+
transport: options.node,
|
|
235
|
+
host: found.host,
|
|
236
|
+
store: region,
|
|
237
|
+
audience: options.audience ?? [],
|
|
238
|
+
key: options.key,
|
|
239
|
+
maxEventBytes: options.maxEventBytes,
|
|
240
|
+
});
|
|
241
|
+
entry.replica = replica;
|
|
242
|
+
entry.stop = replica.subscribe(() => {
|
|
243
|
+
if (held.get(region) === entry && entry.phase === 'ready')
|
|
244
|
+
remerge();
|
|
245
|
+
});
|
|
246
|
+
try {
|
|
247
|
+
await replica.ready();
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
if (held.get(region) !== entry)
|
|
251
|
+
return;
|
|
252
|
+
entry.stop?.();
|
|
253
|
+
entry.stop = null;
|
|
254
|
+
entry.replica = null;
|
|
255
|
+
entry.phase = 'failed';
|
|
256
|
+
void replica.close().catch(() => undefined);
|
|
257
|
+
later();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (held.get(region) !== entry || closed) {
|
|
261
|
+
void replica.close().catch(() => undefined);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
entry.phase = 'ready';
|
|
265
|
+
remerge();
|
|
266
|
+
};
|
|
267
|
+
void attempt();
|
|
268
|
+
};
|
|
269
|
+
const reconcile = () => {
|
|
270
|
+
const wanted = new Set(regionsAround(position.x, position.z, { size: options.size, radius }));
|
|
271
|
+
const kept = new Set(regionsAround(position.x, position.z, { size: options.size, radius: radius + 1 }));
|
|
272
|
+
for (const region of [...held.keys()])
|
|
273
|
+
if (!kept.has(region))
|
|
274
|
+
release(region);
|
|
275
|
+
for (const region of wanted)
|
|
276
|
+
acquire(region);
|
|
277
|
+
};
|
|
278
|
+
reconcile();
|
|
279
|
+
return {
|
|
280
|
+
getState: () => merged,
|
|
281
|
+
subscribe(listener) {
|
|
282
|
+
listeners.add(listener);
|
|
283
|
+
return () => {
|
|
284
|
+
listeners.delete(listener);
|
|
285
|
+
};
|
|
286
|
+
},
|
|
287
|
+
setPosition(x, z) {
|
|
288
|
+
if (closed)
|
|
289
|
+
return;
|
|
290
|
+
position = { x, z };
|
|
291
|
+
reconcile();
|
|
292
|
+
},
|
|
293
|
+
currentRegion: () => regionOf(position.x, position.z, options.size),
|
|
294
|
+
regions: () => [...held].map(([region, entry]) => ({ region, phase: entry.phase, host: entry.host })),
|
|
295
|
+
act(name, input) {
|
|
296
|
+
const region = regionOf(position.x, position.z, options.size);
|
|
297
|
+
const entry = held.get(region);
|
|
298
|
+
if (entry === undefined || entry.phase !== 'ready' || entry.replica === null) {
|
|
299
|
+
return Promise.reject(new Error(`region ${region} is not ready (${entry?.phase ?? 'not held'})`));
|
|
300
|
+
}
|
|
301
|
+
return entry.replica.act(name, input);
|
|
302
|
+
},
|
|
303
|
+
async close() {
|
|
304
|
+
if (closed)
|
|
305
|
+
return;
|
|
306
|
+
closed = true;
|
|
307
|
+
if (lingerTimer !== null)
|
|
308
|
+
clearTimeout(lingerTimer);
|
|
309
|
+
for (const region of [...held.keys()])
|
|
310
|
+
release(region);
|
|
311
|
+
listeners.clear();
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@net-mesh/browser",
|
|
3
|
+
"version": "0.37.0",
|
|
4
|
+
"description": "Browser-native Net mesh leaf \u2014 idiomatic TypeScript over the net-mesh-leaf wasm node",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./bundle": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.bundle.js"
|
|
16
|
+
},
|
|
17
|
+
"./three": {
|
|
18
|
+
"types": "./dist/three/index.d.ts",
|
|
19
|
+
"default": "./dist/three/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./local": {
|
|
22
|
+
"types": "./dist/local.d.ts",
|
|
23
|
+
"default": "./dist/local.js"
|
|
24
|
+
},
|
|
25
|
+
"./netcode": {
|
|
26
|
+
"types": "./dist/netcode/index.d.ts",
|
|
27
|
+
"default": "./dist/netcode/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./world": {
|
|
30
|
+
"types": "./dist/world/index.d.ts",
|
|
31
|
+
"default": "./dist/world/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"license": "MIT OR Apache-2.0",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/ai-2070/net.git"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"net",
|
|
42
|
+
"mesh",
|
|
43
|
+
"browser",
|
|
44
|
+
"webrtc",
|
|
45
|
+
"datachannel",
|
|
46
|
+
"wasm",
|
|
47
|
+
"nrpc",
|
|
48
|
+
"rust"
|
|
49
|
+
],
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"CHANGELOG.md",
|
|
53
|
+
"LICENSE-APACHE",
|
|
54
|
+
"LICENSE-MIT"
|
|
55
|
+
],
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsc && node scripts/bundle.mjs",
|
|
58
|
+
"size": "node scripts/size.mjs",
|
|
59
|
+
"check:package": "node scripts/check-package.mjs",
|
|
60
|
+
"test": "tsc -p tsconfig.test.json && vitest run",
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"bench:world": "node scripts/bench-world.mjs"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"esbuild": "^0.28.0",
|
|
66
|
+
"typescript": "^6.0.0",
|
|
67
|
+
"vitest": "^5.0.0"
|
|
68
|
+
},
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": ">=20"
|
|
71
|
+
}
|
|
72
|
+
}
|