@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.
Files changed (110) hide show
  1. package/CHANGELOG.md +517 -0
  2. package/LICENSE-APACHE +190 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +666 -0
  5. package/dist/async-queue.d.ts +26 -0
  6. package/dist/async-queue.js +62 -0
  7. package/dist/credential.d.ts +70 -0
  8. package/dist/credential.js +95 -0
  9. package/dist/errors.d.ts +400 -0
  10. package/dist/errors.js +726 -0
  11. package/dist/events.d.ts +232 -0
  12. package/dist/events.js +298 -0
  13. package/dist/identity.d.ts +40 -0
  14. package/dist/identity.js +78 -0
  15. package/dist/index.bundle.js +2 -0
  16. package/dist/index.d.ts +38 -0
  17. package/dist/index.js +46 -0
  18. package/dist/leader/events.d.ts +111 -0
  19. package/dist/leader/events.js +116 -0
  20. package/dist/leader/index.d.ts +14 -0
  21. package/dist/leader/index.js +11 -0
  22. package/dist/leader/session.d.ts +539 -0
  23. package/dist/leader/session.js +770 -0
  24. package/dist/leader/wasm.d.ts +123 -0
  25. package/dist/leader/wasm.js +39 -0
  26. package/dist/lobby.d.ts +183 -0
  27. package/dist/lobby.js +504 -0
  28. package/dist/local.d.ts +80 -0
  29. package/dist/local.js +160 -0
  30. package/dist/net_leaf.d.ts +1494 -0
  31. package/dist/net_leaf.js +3753 -0
  32. package/dist/net_leaf_bg.wasm +0 -0
  33. package/dist/net_leaf_bg.wasm.d.ts +137 -0
  34. package/dist/netcode/client.d.ts +102 -0
  35. package/dist/netcode/client.js +296 -0
  36. package/dist/netcode/clock.d.ts +45 -0
  37. package/dist/netcode/clock.js +54 -0
  38. package/dist/netcode/host.d.ts +118 -0
  39. package/dist/netcode/host.js +276 -0
  40. package/dist/netcode/index.d.ts +19 -0
  41. package/dist/netcode/index.js +14 -0
  42. package/dist/netcode/interpolate.d.ts +50 -0
  43. package/dist/netcode/interpolate.js +100 -0
  44. package/dist/netcode/wire.d.ts +125 -0
  45. package/dist/netcode/wire.js +117 -0
  46. package/dist/node.d.ts +908 -0
  47. package/dist/node.js +1085 -0
  48. package/dist/org.d.ts +412 -0
  49. package/dist/org.js +567 -0
  50. package/dist/peer-driver.d.ts +117 -0
  51. package/dist/peer-driver.js +224 -0
  52. package/dist/store/assembly.d.ts +176 -0
  53. package/dist/store/assembly.js +325 -0
  54. package/dist/store/chunker.d.ts +83 -0
  55. package/dist/store/chunker.js +190 -0
  56. package/dist/store/core.d.ts +136 -0
  57. package/dist/store/core.js +477 -0
  58. package/dist/store/definition.d.ts +23 -0
  59. package/dist/store/definition.js +83 -0
  60. package/dist/store/errors.d.ts +77 -0
  61. package/dist/store/errors.js +94 -0
  62. package/dist/store/host.d.ts +247 -0
  63. package/dist/store/host.js +675 -0
  64. package/dist/store/index.d.ts +50 -0
  65. package/dist/store/index.js +40 -0
  66. package/dist/store/interest.d.ts +40 -0
  67. package/dist/store/interest.js +77 -0
  68. package/dist/store/inventory.d.ts +81 -0
  69. package/dist/store/inventory.js +140 -0
  70. package/dist/store/join.d.ts +118 -0
  71. package/dist/store/join.js +724 -0
  72. package/dist/store/json.d.ts +50 -0
  73. package/dist/store/json.js +283 -0
  74. package/dist/store/ledger.d.ts +187 -0
  75. package/dist/store/ledger.js +292 -0
  76. package/dist/store/owner.d.ts +616 -0
  77. package/dist/store/owner.js +1711 -0
  78. package/dist/store/patch.d.ts +70 -0
  79. package/dist/store/patch.js +166 -0
  80. package/dist/store/player.d.ts +65 -0
  81. package/dist/store/player.js +261 -0
  82. package/dist/store/replica.d.ts +187 -0
  83. package/dist/store/replica.js +740 -0
  84. package/dist/store/state.d.ts +42 -0
  85. package/dist/store/state.js +165 -0
  86. package/dist/store/types.d.ts +200 -0
  87. package/dist/store/types.js +25 -0
  88. package/dist/store/visibility.d.ts +118 -0
  89. package/dist/store/visibility.js +282 -0
  90. package/dist/store/wire.d.ts +309 -0
  91. package/dist/store/wire.js +834 -0
  92. package/dist/stream.d.ts +174 -0
  93. package/dist/stream.js +238 -0
  94. package/dist/three/index.d.ts +109 -0
  95. package/dist/three/index.js +130 -0
  96. package/dist/udp-probe.d.ts +159 -0
  97. package/dist/udp-probe.js +245 -0
  98. package/dist/wasm.d.ts +642 -0
  99. package/dist/wasm.js +98 -0
  100. package/dist/world/border.d.ts +84 -0
  101. package/dist/world/border.js +99 -0
  102. package/dist/world/handoff.d.ts +187 -0
  103. package/dist/world/handoff.js +222 -0
  104. package/dist/world/index.d.ts +13 -0
  105. package/dist/world/index.js +9 -0
  106. package/dist/world/link.d.ts +179 -0
  107. package/dist/world/link.js +476 -0
  108. package/dist/world/regions.d.ts +154 -0
  109. package/dist/world/regions.js +314 -0
  110. package/package.json +72 -0
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Slice E — the action ledger (§1.10).
3
+ *
4
+ * The rule the whole module exists for: **a sequence number identifies
5
+ * one request, not a slot.** A retained `s` replayed with the same
6
+ * request returns the same outcome; replayed with a *different* request
7
+ * it is refused and the retained entry is left alone. Without that, a
8
+ * caller could overwrite the record of what it asked for.
9
+ *
10
+ * ## Why a rejection is retained
11
+ *
12
+ * Authorization denial and a handler throw both retain an entry. If a
13
+ * refusal were simply absent, a later policy change would turn an
14
+ * earlier refusal into an execution of the same sequence — the same
15
+ * request succeeding the second time because the record of its refusal
16
+ * was not kept.
17
+ *
18
+ * ## Why the floor exists, and what it cannot say
19
+ *
20
+ * The window is bounded three ways (count, bytes, age), and eviction
21
+ * happens in `s` order so an entry is never dropped while a lower one
22
+ * is retained. The floor is the highest evicted `s`: at or below it the
23
+ * owner knows a sequence has been used but no longer knows the outcome,
24
+ * which is `result-expired` — explicitly NOT a claim that it did or did
25
+ * not commit. A handle that is gone is a different answer again
26
+ * (`closed`), because then the owner does not even know the sequence
27
+ * existed.
28
+ *
29
+ * ## The binding, and the one `await`
30
+ *
31
+ * The request binding is a canonical `name \x1f json` string, retained
32
+ * verbatim while it is small and as a SHA-256 when it is not. The small
33
+ * form is the common case and has no `await` at all, which is the point:
34
+ * the asynchronous path is the only one where the handle, the policy and
35
+ * the ledger can change under the request, and it is the only one that
36
+ * has to revalidate.
37
+ */
38
+ import { StoreError } from './errors.js';
39
+ import { utf8Length } from './wire.js';
40
+ /** Outcomes retained per handle (§2). */
41
+ export const LEDGER_MAX_ENTRIES = 32;
42
+ /** Bytes retained per handle (§2). */
43
+ export const LEDGER_MAX_BYTES = 64 * 1024;
44
+ /** How long an outcome is retained (§2). */
45
+ export const LEDGER_MAX_AGE_MS = 60_000;
46
+ /** Above this, a binding is retained as its digest rather than verbatim. */
47
+ export const BINDING_INLINE_MAX_BYTES = 2048;
48
+ /** The highest sequence a handle can reach. It does not wrap. */
49
+ export const MAX_SEQUENCE = (1n << 64n) - 1n;
50
+ /** The separator between an action's name and its input. */
51
+ const UNIT_SEPARATOR = '\u001f';
52
+ /**
53
+ * The canonical form of one request.
54
+ *
55
+ * Object keys sort ascending by UTF-16 code unit, there is no
56
+ * insignificant whitespace, and numbers are rendered by
57
+ * `Number.prototype.toString` — exact for every value the store admits,
58
+ * because §1.12 already refuses `NaN`, `Infinity` and duplicate keys,
59
+ * and identifiers needing full integer precision are strings by §1.1.
60
+ *
61
+ * Only the owner computes this and only the owner compares it: it never
62
+ * crosses the wire, so self-consistency is what matters.
63
+ */
64
+ export function canonicalRequest(name, input) {
65
+ return `${name}${UNIT_SEPARATOR}${canonicalJson(input)}`;
66
+ }
67
+ function canonicalJson(value) {
68
+ if (value === null || typeof value === 'boolean')
69
+ return String(value);
70
+ if (typeof value === 'number') {
71
+ if (!Number.isFinite(value)) {
72
+ // Unreachable through the parser, which refuses both — but this
73
+ // function is also called on a locally-built input, and a
74
+ // canonical form that silently rendered `null` here would make
75
+ // two different requests compare equal.
76
+ throw new StoreError('invalid-data', 'a canonical request cannot contain a non-finite number');
77
+ }
78
+ return String(value);
79
+ }
80
+ if (typeof value === 'string')
81
+ return JSON.stringify(value);
82
+ if (Array.isArray(value))
83
+ return `[${value.map(element => canonicalJson(element)).join(',')}]`;
84
+ const record = value;
85
+ const keys = Object.keys(record).sort();
86
+ const fields = keys.map(key => `${JSON.stringify(key)}:${canonicalJson(record[key])}`);
87
+ return `{${fields.join(',')}}`;
88
+ }
89
+ /** How many bytes a canonical form occupies. */
90
+ export function canonicalBytes(canonical) {
91
+ return new TextEncoder().encode(canonical).byteLength;
92
+ }
93
+ /** Whether this binding needs the asynchronous digest path. */
94
+ export function needsDigest(canonical) {
95
+ return canonicalBytes(canonical) > BINDING_INLINE_MAX_BYTES;
96
+ }
97
+ /** The binding for a small canonical form. Synchronous by construction. */
98
+ export function inlineBinding(canonical) {
99
+ if (needsDigest(canonical)) {
100
+ throw new StoreError('invalid-data', 'this canonical form needs the digest path');
101
+ }
102
+ return { form: 'inline', value: canonical };
103
+ }
104
+ /**
105
+ * The binding for a large canonical form.
106
+ *
107
+ * The caller must treat the `await` as a boundary: no authorization
108
+ * decision and no transaction context may cross it (§1.10).
109
+ */
110
+ export async function digestBinding(canonical) {
111
+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(canonical));
112
+ const bytes = new Uint8Array(digest);
113
+ let hex = '';
114
+ for (const byte of bytes)
115
+ hex += byte.toString(16).padStart(2, '0');
116
+ return { form: 'digest', value: hex };
117
+ }
118
+ function sameBinding(a, b) {
119
+ return a.form === b.form && a.value === b.value;
120
+ }
121
+ /**
122
+ * One handle's outcomes.
123
+ *
124
+ * Keyed by sequence, bounded by count, bytes and age, evicted in `s`
125
+ * order so the floor can only ever describe a contiguous prefix.
126
+ */
127
+ export class HandleLedger {
128
+ maxEntries;
129
+ maxBytes;
130
+ maxAgeMs;
131
+ /** The highest retired sequence: used, outcome no longer known. */
132
+ #floor = 0n;
133
+ #entries = new Map();
134
+ #bytes = 0;
135
+ constructor(maxEntries = LEDGER_MAX_ENTRIES, maxBytes = LEDGER_MAX_BYTES, maxAgeMs = LEDGER_MAX_AGE_MS) {
136
+ this.maxEntries = maxEntries;
137
+ this.maxBytes = maxBytes;
138
+ this.maxAgeMs = maxAgeMs;
139
+ }
140
+ get floor() {
141
+ return this.#floor.toString();
142
+ }
143
+ get size() {
144
+ return this.#entries.size;
145
+ }
146
+ get bytesHeld() {
147
+ return this.#bytes;
148
+ }
149
+ /**
150
+ * What to do with `(s, binding)`.
151
+ *
152
+ * Pure: it moves nothing, so the owner can call it before the
153
+ * transaction and — on the digest path — again after the `await`.
154
+ */
155
+ disposition(s, binding, now) {
156
+ this.expire(now);
157
+ if (s >= MAX_SEQUENCE)
158
+ return { kind: 'exhausted' };
159
+ const retained = this.#entries.get(s.toString());
160
+ if (retained !== undefined) {
161
+ // A sequence identifies one request. The same request replays;
162
+ // a different one under the same sequence is refused, and the
163
+ // retained entry is NOT overwritten.
164
+ if (!sameBinding(retained.binding, binding))
165
+ return { kind: 'conflict' };
166
+ return { kind: 'replay', outcome: retained.outcome };
167
+ }
168
+ // A gap is permitted and does not block: an unseen sequence above
169
+ // the floor is simply new. At or below it, the sequence has been
170
+ // used and the outcome is gone.
171
+ if (s <= this.#floor)
172
+ return { kind: 'expired' };
173
+ return { kind: 'execute' };
174
+ }
175
+ /**
176
+ * Retain an outcome.
177
+ *
178
+ * A refusal is retained exactly like a result: that is what stops a
179
+ * policy change from turning an earlier denial into a later
180
+ * execution of the same sequence.
181
+ */
182
+ retain(s, binding, outcome, now) {
183
+ const bytes = entryBytes(binding, outcome);
184
+ this.#entries.set(s.toString(), { s, binding, outcome, bytes, at: now });
185
+ this.#bytes += bytes;
186
+ this.evict(now);
187
+ }
188
+ /** Drop everything past its age, advancing the floor. */
189
+ expire(now) {
190
+ for (const entry of this.ordered()) {
191
+ if (now - entry.at < this.maxAgeMs)
192
+ break;
193
+ this.drop(entry);
194
+ }
195
+ }
196
+ evict(now) {
197
+ this.expire(now);
198
+ // Count and bytes, always in `s` order, so an entry is never
199
+ // dropped while a lower one is retained — which is what makes the
200
+ // floor a boundary rather than a set of holes.
201
+ while (this.#entries.size > this.maxEntries || this.#bytes > this.maxBytes) {
202
+ const lowest = this.ordered()[0];
203
+ if (lowest === undefined)
204
+ return;
205
+ this.drop(lowest);
206
+ }
207
+ }
208
+ drop(entry) {
209
+ this.#entries.delete(entry.s.toString());
210
+ this.#bytes -= entry.bytes;
211
+ if (entry.s > this.#floor)
212
+ this.#floor = entry.s;
213
+ }
214
+ ordered() {
215
+ return [...this.#entries.values()].sort((a, b) => (a.s < b.s ? -1 : a.s > b.s ? 1 : 0));
216
+ }
217
+ }
218
+ function entryBytes(binding, outcome) {
219
+ // UTF-8 BYTES, the unit `LEDGER_MAX_BYTES` bounds (the idiom
220
+ // `canonicalBytes` shows): `.length` counts UTF-16 code units and
221
+ // under-enforced the cap exactly for unicode-heavy inputs.
222
+ const payload = outcome.kind === 'result' ? JSON.stringify(outcome.out) : outcome.code;
223
+ return utf8Length(binding.value) + utf8Length(payload);
224
+ }
225
+ /**
226
+ * Latest-value inputs (§1.11).
227
+ *
228
+ * Monotone per `(handle, name)`: a sequence at or below the last seen
229
+ * one is dropped and counted. There is no retention, no replay and no
230
+ * gap recovery, and **loss does not imply a successor** — a dropped
231
+ * input may be the last one, so the game must tolerate a missing final
232
+ * input. The coalescing design invites the opposite assumption, which
233
+ * is why it is stated here beside the code.
234
+ */
235
+ export class InputSequences {
236
+ #latest = new Map();
237
+ #dropped = 0;
238
+ /** How many inputs this handle has dropped as stale. */
239
+ get dropped() {
240
+ return this.#dropped;
241
+ }
242
+ /** Whether this input is newer than the last for its name. */
243
+ admit(name, s) {
244
+ const last = this.#latest.get(name);
245
+ if (last !== undefined && s <= last) {
246
+ this.#dropped += 1;
247
+ return false;
248
+ }
249
+ this.#latest.set(name, s);
250
+ return true;
251
+ }
252
+ /** The last admitted sequence for a name, or null. */
253
+ latest(name) {
254
+ const value = this.#latest.get(name);
255
+ return value === undefined ? null : value.toString();
256
+ }
257
+ }
258
+ /** Per-handle ledger state, created on demand. */
259
+ export class LedgerTable {
260
+ #ledgers = new Map();
261
+ #inputs = new Map();
262
+ ledger(h) {
263
+ let ledger = this.#ledgers.get(h);
264
+ if (ledger === undefined) {
265
+ ledger = new HandleLedger();
266
+ this.#ledgers.set(h, ledger);
267
+ }
268
+ return ledger;
269
+ }
270
+ inputs(h) {
271
+ let sequences = this.#inputs.get(h);
272
+ if (sequences === undefined) {
273
+ sequences = new InputSequences();
274
+ this.#inputs.set(h, sequences);
275
+ }
276
+ return sequences;
277
+ }
278
+ /**
279
+ * Forget a handle's ledger.
280
+ *
281
+ * §2: the ledger goes with the handle, which is why a replay after
282
+ * eviction is `closed` and not `result-expired` — the owner no
283
+ * longer knows the sequence existed.
284
+ */
285
+ forget(h) {
286
+ this.#ledgers.delete(h);
287
+ this.#inputs.delete(h);
288
+ }
289
+ get size() {
290
+ return this.#ledgers.size;
291
+ }
292
+ }