@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,83 @@
1
+ /**
2
+ * `defineStore` — the reusable typed description.
3
+ *
4
+ * It holds validators, never authority handlers, so a client bundle
5
+ * that joins a store does not carry the owner's gameplay code.
6
+ *
7
+ * It is not a pass-through. It checks the two properties whose
8
+ * violation is invisible until much later:
9
+ *
10
+ * 1. `empty()` must itself be a valid state. `empty()` is what a
11
+ * replica installs when it loses visibility, so an `empty()` the
12
+ * schema would reject means audience clearing fails at the worst
13
+ * possible moment — mid-transition, with the old projection already
14
+ * fenced.
15
+ * 2. `id` and `version` must be usable as an incarnation identity.
16
+ * They travel in every envelope and a blank id or a non-integer
17
+ * version turns a version-mismatch refusal into a confusing parse
18
+ * error at the far end.
19
+ */
20
+ import { StoreError } from './errors.js';
21
+ import { compileVisibility } from './visibility.js';
22
+ export function defineStore(definition) {
23
+ if (definition.id.length === 0) {
24
+ throw new StoreError('invalid-data', 'a store definition needs a non-empty id');
25
+ }
26
+ if (!Number.isSafeInteger(definition.version) || definition.version < 1) {
27
+ throw new StoreError('invalid-data', `store '${definition.id}' needs a positive integer version, got ${String(definition.version)}`);
28
+ }
29
+ // `empty()` is state, so it must pass the state validator. Checking
30
+ // it here fails at definition time — at the developer's desk —
31
+ // instead of during an audience transition in front of a player.
32
+ let empty;
33
+ try {
34
+ empty = definition.empty();
35
+ }
36
+ catch (error) {
37
+ throw new StoreError('invalid-data', `store '${definition.id}' could not build its empty state: ${String(error)}`, { cause: error });
38
+ }
39
+ try {
40
+ definition.state(empty);
41
+ }
42
+ catch (error) {
43
+ throw new StoreError('invalid-data', `store '${definition.id}': empty() is not a valid state, so a replica could not clear a projection with it: ${String(error)}`, { cause: error });
44
+ }
45
+ // Visibility rules that could not be enforced as written fail here,
46
+ // at the developer's desk, rather than at the first projection.
47
+ if (definition.visibility !== undefined) {
48
+ try {
49
+ compileVisibility(definition.visibility);
50
+ }
51
+ catch (error) {
52
+ throw new StoreError('invalid-data', `store '${definition.id}': ${error instanceof Error ? error.message : String(error)}`, {
53
+ cause: error,
54
+ });
55
+ }
56
+ }
57
+ if (definition.interest !== undefined) {
58
+ for (const [collection, key] of Object.entries(definition.interest)) {
59
+ if (collection.length === 0 || collection.includes('.') || typeof key !== 'function') {
60
+ throw new StoreError('invalid-data', `store '${definition.id}': interest maps a top-level collection name to a key function, got '${collection}'`);
61
+ }
62
+ }
63
+ }
64
+ if (definition.entities !== undefined) {
65
+ for (const [collection, parse] of Object.entries(definition.entities)) {
66
+ if (collection.length === 0 || collection.includes('.') || typeof parse !== 'function') {
67
+ throw new StoreError('invalid-data', `store '${definition.id}': entities maps a top-level collection name to a per-entity parser, got '${collection}'`);
68
+ }
69
+ const inEmpty = empty[collection];
70
+ if (inEmpty !== undefined && !isEntityMap(inEmpty)) {
71
+ throw new StoreError('invalid-data', `store '${definition.id}': entities names '${collection}', but empty().${collection} is not a record of entities`);
72
+ }
73
+ }
74
+ }
75
+ return Object.freeze({ ...definition });
76
+ }
77
+ /** A plain object: what an entity map is. */
78
+ export function isEntityMap(value) {
79
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
80
+ return false;
81
+ const proto = Object.getPrototypeOf(value);
82
+ return proto === Object.prototype || proto === null;
83
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The store's error taxonomy.
3
+ *
4
+ * One code per refusal a caller can act on differently. The codes are
5
+ * the design document's list
6
+ * (`docs/internal/plans/BROWSER_GAME_STORE_API_DESIGN.md` §2), and the
7
+ * one that carries a contract subtlety is {@link StoreErrorCode}
8
+ * `result-expired` — see its note below, because getting it wrong
9
+ * fabricates a success receipt.
10
+ */
11
+ /**
12
+ * Why a store operation was refused.
13
+ *
14
+ * - `invalid-data` — a payload failed its validator.
15
+ * - `version-mismatch` — definition id/version disagreement.
16
+ * - `forbidden` — the owner's `authorize` refused.
17
+ * - `not-ready` — the handle has no live, synchronized view.
18
+ * - `capacity` — a declared bound was reached.
19
+ * - `timeout` / `aborted` — the caller's deadline or signal fired.
20
+ * - `indeterminate` — submitted, and no response established the outcome.
21
+ * - `owner-lost` — the store incarnation ended. Terminal: there is no
22
+ * handle to obtain, because the owner that held the document is gone.
23
+ * A closing host SAYS this, unsolicited, to every handle it holds
24
+ * (`owner.farewell`) — silence is not an answer, and a replica whose
25
+ * host closed used to learn nothing until its own deadline fired and
26
+ * reported `indeterminate`. It is deliberately NOT `closed`: that
27
+ * one is the expiry notice a replica rejoins on, and rejoining an
28
+ * owner that is gone either hangs or silently attaches the caller to
29
+ * a SUCCESSOR's different document under the handle it already had.
30
+ * - `closed` — **this handle is unusable**: unknown, expired, fenced by
31
+ * the owner, or bound to another peer. Deliberately one code for all
32
+ * four, so a refusal cannot disclose whether a handle exists. It is
33
+ * terminal for the handle and for any action in flight on it —
34
+ * nothing is replayed and no outcome is inferred, exactly as for
35
+ * `result-expired` below — while the *subscription* is recoverable by
36
+ * joining afresh, which yields a new handle rather than resuming the
37
+ * old one.
38
+ * - `action-rejected` — the owner refused this action, or a handler broke
39
+ * the synchronous-transaction contract.
40
+ * - `result-expired` — **this request cannot execute again, and its
41
+ * original result is unavailable.** It asserts nothing about whether the
42
+ * original attempt committed: a retired sequence may have been rejected,
43
+ * aborted before commit, or fenced without ever executing. A
44
+ * non-reexecution floor is not evidence of successful execution, so this
45
+ * code must never be read — or reported — as a success receipt.
46
+ */
47
+ /**
48
+ * The codes, as a runtime set.
49
+ *
50
+ * `no.code` on the wire is **exactly** this list (brief §2), so the
51
+ * parser needs it at runtime and not only in the type system: a code
52
+ * the caller cannot branch on is a refusal it cannot act on.
53
+ *
54
+ * {@link StoreErrorCode} is derived from it, so the two cannot drift —
55
+ * a code added to one and not the other would be a type the parser
56
+ * refuses or a wire value the caller cannot name.
57
+ */
58
+ export declare const STORE_ERROR_CODES: readonly ["invalid-data", "version-mismatch", "forbidden", "not-ready", "capacity", "timeout", "aborted", "indeterminate", "owner-lost", "closed", "action-rejected", "result-expired"];
59
+ export type StoreErrorCode = (typeof STORE_ERROR_CODES)[number];
60
+ /** A refused store operation, carrying the code a caller branches on. */
61
+ export declare class StoreError extends Error {
62
+ readonly code: StoreErrorCode;
63
+ constructor(code: StoreErrorCode, message: string, options?: {
64
+ cause?: unknown;
65
+ });
66
+ }
67
+ /**
68
+ * Whether a send failed because its stream belongs to a session that
69
+ * has been replaced — the ONE failure a reopen repairs.
70
+ *
71
+ * The leaf names it (`leaf/src/node.rs`): "stale stream handle:
72
+ * opened on incarnation N; reopen the stream". It is matched on the
73
+ * message because that is what crosses the wasm boundary; everything
74
+ * else — an oversized payload, a fenced id, a closed node — is
75
+ * permanent, and reopening for it burns a stream handle per frame.
76
+ */
77
+ export declare function isStaleStream(error: unknown): boolean;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The store's error taxonomy.
3
+ *
4
+ * One code per refusal a caller can act on differently. The codes are
5
+ * the design document's list
6
+ * (`docs/internal/plans/BROWSER_GAME_STORE_API_DESIGN.md` §2), and the
7
+ * one that carries a contract subtlety is {@link StoreErrorCode}
8
+ * `result-expired` — see its note below, because getting it wrong
9
+ * fabricates a success receipt.
10
+ */
11
+ /**
12
+ * Why a store operation was refused.
13
+ *
14
+ * - `invalid-data` — a payload failed its validator.
15
+ * - `version-mismatch` — definition id/version disagreement.
16
+ * - `forbidden` — the owner's `authorize` refused.
17
+ * - `not-ready` — the handle has no live, synchronized view.
18
+ * - `capacity` — a declared bound was reached.
19
+ * - `timeout` / `aborted` — the caller's deadline or signal fired.
20
+ * - `indeterminate` — submitted, and no response established the outcome.
21
+ * - `owner-lost` — the store incarnation ended. Terminal: there is no
22
+ * handle to obtain, because the owner that held the document is gone.
23
+ * A closing host SAYS this, unsolicited, to every handle it holds
24
+ * (`owner.farewell`) — silence is not an answer, and a replica whose
25
+ * host closed used to learn nothing until its own deadline fired and
26
+ * reported `indeterminate`. It is deliberately NOT `closed`: that
27
+ * one is the expiry notice a replica rejoins on, and rejoining an
28
+ * owner that is gone either hangs or silently attaches the caller to
29
+ * a SUCCESSOR's different document under the handle it already had.
30
+ * - `closed` — **this handle is unusable**: unknown, expired, fenced by
31
+ * the owner, or bound to another peer. Deliberately one code for all
32
+ * four, so a refusal cannot disclose whether a handle exists. It is
33
+ * terminal for the handle and for any action in flight on it —
34
+ * nothing is replayed and no outcome is inferred, exactly as for
35
+ * `result-expired` below — while the *subscription* is recoverable by
36
+ * joining afresh, which yields a new handle rather than resuming the
37
+ * old one.
38
+ * - `action-rejected` — the owner refused this action, or a handler broke
39
+ * the synchronous-transaction contract.
40
+ * - `result-expired` — **this request cannot execute again, and its
41
+ * original result is unavailable.** It asserts nothing about whether the
42
+ * original attempt committed: a retired sequence may have been rejected,
43
+ * aborted before commit, or fenced without ever executing. A
44
+ * non-reexecution floor is not evidence of successful execution, so this
45
+ * code must never be read — or reported — as a success receipt.
46
+ */
47
+ /**
48
+ * The codes, as a runtime set.
49
+ *
50
+ * `no.code` on the wire is **exactly** this list (brief §2), so the
51
+ * parser needs it at runtime and not only in the type system: a code
52
+ * the caller cannot branch on is a refusal it cannot act on.
53
+ *
54
+ * {@link StoreErrorCode} is derived from it, so the two cannot drift —
55
+ * a code added to one and not the other would be a type the parser
56
+ * refuses or a wire value the caller cannot name.
57
+ */
58
+ export const STORE_ERROR_CODES = [
59
+ 'invalid-data',
60
+ 'version-mismatch',
61
+ 'forbidden',
62
+ 'not-ready',
63
+ 'capacity',
64
+ 'timeout',
65
+ 'aborted',
66
+ 'indeterminate',
67
+ 'owner-lost',
68
+ 'closed',
69
+ 'action-rejected',
70
+ 'result-expired',
71
+ ];
72
+ /** A refused store operation, carrying the code a caller branches on. */
73
+ export class StoreError extends Error {
74
+ code;
75
+ constructor(code, message, options) {
76
+ super(message, options);
77
+ this.name = 'StoreError';
78
+ this.code = code;
79
+ }
80
+ }
81
+ /**
82
+ * Whether a send failed because its stream belongs to a session that
83
+ * has been replaced — the ONE failure a reopen repairs.
84
+ *
85
+ * The leaf names it (`leaf/src/node.rs`): "stale stream handle:
86
+ * opened on incarnation N; reopen the stream". It is matched on the
87
+ * message because that is what crosses the wasm boundary; everything
88
+ * else — an oversized payload, a fenced id, a closed node — is
89
+ * permanent, and reopening for it burns a stream handle per frame.
90
+ */
91
+ export function isStaleStream(error) {
92
+ const message = error instanceof Error ? error.message : String(error);
93
+ return message.includes('stale stream handle');
94
+ }
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Slice G — `hostStore` and `joinStore` over a real session.
3
+ *
4
+ * This is the layer where the authenticated-origin gate is either
5
+ * satisfied or not, and it is three lines of it: a frame arrives as a
6
+ * `stream_data` event, the event carries `peerNode` — the peer whose
7
+ * installed session opened the packet — and that string is what goes
8
+ * into `owner.receive(frame, peer)`. Nothing in a store frame is
9
+ * consulted for identity, because no store frame has an originator
10
+ * field to consult (§1.3).
11
+ *
12
+ * ## What this module is typed against
13
+ *
14
+ * Not a port, not an adapter: the structural subset of the real
15
+ * `BrowserNode`/`MeshSession` surface that the store actually uses.
16
+ * Both satisfy {@link StoreTransport} as they are, so there is no
17
+ * second implementation of anything, and a test can drive the same
18
+ * type without a mesh.
19
+ *
20
+ * ## The gate this slice does NOT clear
21
+ *
22
+ * §4 gates G on leader-proxy lifecycle: follower tabs, last-consumer
23
+ * cleanup, the peer/stream lifecycle on a proxied handle. A
24
+ * `ProxyStream` now carries its peer (6256f970b) which is the
25
+ * precondition, but the *store's* use of it on a follower is not
26
+ * established, and nothing here claims it is.
27
+ */
28
+ import { StoreOwner, type Dispatched, type OwnerDeps, type Viewer } from './owner.js';
29
+ import type { ActionSpec, Cancel, EntityCollection, EntityOf, InputSpec, StoreDefinition } from './types.js';
30
+ import { type Hex } from './wire.js';
31
+ /** How often a host expires handles whose lease has run out (§2). */
32
+ export declare const HOST_SWEEP_MS = 5000;
33
+ /**
34
+ * How long `close()` will wait for one goodbye to flush.
35
+ *
36
+ * A page tearing down cannot wait forever to be polite, and a
37
+ * transport whose `send` never settles would otherwise make `close()`
38
+ * never return — a review probe held one and measured exactly that.
39
+ * Short, because the alternative to a bounded wait is a hang, and the
40
+ * frame is not retried afterwards: a farewell that missed its window
41
+ * is a counted loss.
42
+ *
43
+ * HALF A SECOND, not two: the first version chose two and a review
44
+ * probe — which allowed 750 ms for `close()` to return — read that as
45
+ * an unbounded close, and it was right to. A page tearing down has an
46
+ * unload budget under a second, and a frame that has not flushed on a
47
+ * live DataChannel in 500 ms is not going to matter to the replica
48
+ * that was going to receive it.
49
+ */
50
+ export declare const FAREWELL_DEADLINE_MS = 500;
51
+ /** One frame, as the transport carries it. */
52
+ export type Frame = Uint8Array;
53
+ /** The subset of a stream the store writes to and reads from. */
54
+ export interface TransportStream {
55
+ send(payload: Frame): Promise<void> | void;
56
+ close(): void;
57
+ }
58
+ /** A `stream_data` arrival, narrowed to what the store reads. */
59
+ export interface TransportFrame {
60
+ readonly type: string;
61
+ readonly streamId?: string;
62
+ /** The authenticated peer. The store's only source of identity. */
63
+ readonly peerNode?: string | null;
64
+ readonly payload?: Frame;
65
+ }
66
+ /**
67
+ * The structural subset of `BrowserNode` / `MeshSession` the store uses.
68
+ *
69
+ * `openStream` is synchronous on the direct node and a promise on a
70
+ * session, so this accepts either and the store awaits both.
71
+ */
72
+ export interface StoreTransport {
73
+ /** This node's id — the authority a replica is talking to. */
74
+ nodeIdHex(): string | null;
75
+ /**
76
+ * Open a stream to a peer.
77
+ *
78
+ * **`label`, never `streamId`.** A stream id is a `u64` the leaf
79
+ * DERIVES from the label, and it sets a discriminator bit that makes
80
+ * an unsolicited arrival classify as stream data at the far end
81
+ * rather than as a channel message. Passing a textual id is refused
82
+ * by the wasm option reader outright, and passing an arbitrary
83
+ * number loses the discriminator — so the label is the only correct
84
+ * input and both ends derive the same id from it without exchanging
85
+ * one.
86
+ */
87
+ openStream(options: {
88
+ reliability: 'reliable' | 'fireAndForget';
89
+ peer?: string;
90
+ label?: string;
91
+ /**
92
+ * Ride the lossy carrier (fire-and-forget only): unordered, no
93
+ * retransmits, never delaying anything else. A transport without one
94
+ * may ignore it and send the packets like any others.
95
+ */
96
+ lossy?: boolean;
97
+ }): TransportStream | Promise<TransportStream>;
98
+ onEvent(handler: (event: TransportFrame) => void): Cancel;
99
+ /**
100
+ * Make sure there IS a session with this peer, if the transport can.
101
+ *
102
+ * `openStream({peer})` refuses a peer the node has no session with,
103
+ * and a session — even a relayed one — is installed by a peer
104
+ * ATTEMPT (§9 step 2, `ensure_relayed_session`), not by discovery.
105
+ * So a store that only discovered its host still could not send it
106
+ * anything: `session: no session with 0x…`, which is what the real
107
+ * browser harness reports.
108
+ *
109
+ * Optional, because a transport that needs no such step (an
110
+ * in-process one, or a page that already connected the peer itself)
111
+ * should not have to pretend to offer it. When it is absent, or it
112
+ * fails, the store still tries to open — and the typed refusal from
113
+ * `openStream` is the honest answer rather than a second guess.
114
+ */
115
+ connectPeer?(peerHex: string): Promise<unknown>;
116
+ }
117
+ /**
118
+ * A peer id in the spelling `openStream` takes: 16 lowercase hex.
119
+ *
120
+ * Events carry the authenticated peer as an EXACT DECIMAL string, and
121
+ * `openStream({ peer })` requires 16 hex digits. Handing the decimal
122
+ * straight back is rejected for a short id and — worse — names a
123
+ * DIFFERENT peer for a 16-digit decimal one, which is a cross-peer
124
+ * defect wearing the shape of a formatting bug. `LeafStream` already
125
+ * reconciles the two spellings through `BigInt`; this is the same
126
+ * reconciliation for the store's own seam.
127
+ */
128
+ export declare function peerHexOf(peer: string): string | null;
129
+ /** Whether two peer spellings name the same node. */
130
+ export declare function samePeer(left: string, right: string): boolean;
131
+ /**
132
+ * How a host decides what each replica sees: exactly one of these.
133
+ *
134
+ * - `project(state, audience)` — one view per audience, shared by
135
+ * every player reading it. The default choice.
136
+ * - `projectFor(state, { peer, audience })` — one view per player, for
137
+ * what differs by who is looking (your own hand). Costs one
138
+ * projection per player per change.
139
+ */
140
+ export type HostProjection<S extends object> = {
141
+ project(state: S, audience: readonly string[]): S;
142
+ readonly projectFor?: never;
143
+ } | {
144
+ projectFor(state: S, viewer: Viewer): S;
145
+ readonly project?: never;
146
+ } | {
147
+ /** Neither: the definition's declared `visibility` alone decides. */
148
+ readonly project?: never;
149
+ readonly projectFor?: never;
150
+ };
151
+ /** What a host needs beyond the owner's own dependencies. */
152
+ export type HostStoreOptions<S extends object, A extends ActionSpec, I extends InputSpec> = HostStoreBaseOptions<S, A, I> & HostProjection<S>;
153
+ /** {@link HostStoreOptions} without the projection. */
154
+ export interface HostStoreBaseOptions<S extends object, A extends ActionSpec, I extends InputSpec> extends Omit<OwnerDeps<S, A, I>, 'maxEventBytes' | 'newHandle' | 'newIncarnation' | 'now' | 'canProject' | 'store' | 'project' | 'projectFor'> {
155
+ readonly transport: StoreTransport;
156
+ /**
157
+ * WHICH store of this definition this is — the address a joiner
158
+ * names. Defaults to the definition id, which is what a node
159
+ * serving a single store of it means.
160
+ *
161
+ * A node hosting SEVERAL stores of one definition MUST name each,
162
+ * and their joiners must ask for them by name: every host store on
163
+ * a node sees every frame, so this is the only thing that decides
164
+ * which one answers.
165
+ */
166
+ readonly store?: string;
167
+ /** The stream id both sides address. One label per store. */
168
+ readonly streamId?: string;
169
+ readonly maxEventBytes: number;
170
+ readonly initialState: S;
171
+ /**
172
+ * Development checks: `true` warns through `console.warn`, a function
173
+ * receives the messages. It says so when every player would receive
174
+ * the whole state without the definition declaring `visibility:
175
+ * 'open'`, and when a projection fails the state validator (which
176
+ * otherwise sends the player `empty()` silently). Leave it off in
177
+ * production.
178
+ */
179
+ readonly dev?: boolean | ((message: string) => void);
180
+ now?: () => number;
181
+ newHandle?: () => Hex;
182
+ newIncarnation?: () => Hex;
183
+ canProject?: () => boolean;
184
+ /** Elapsed-time driver, so a test does not need real timers. */
185
+ schedule?: (run: () => void, ms: number) => Cancel;
186
+ }
187
+ /** The authoritative store, served to whoever the transport authenticates. */
188
+ export interface HostedStoreHandle<S extends object, A extends ActionSpec = ActionSpec, I extends InputSpec = InputSpec> {
189
+ /** This node's id: the authority every replica is talking to. */
190
+ readonly authority: string;
191
+ /** The definition this store serves. */
192
+ readonly definition: StoreDefinition<S, A, I>;
193
+ getState(): S;
194
+ subscribe(listener: (state: S, previous: S) => void): Cancel;
195
+ setState(next: S): void;
196
+ /**
197
+ * Write entities of a collection declared in the definition's
198
+ * `entities`: `id → value`, or `id → undefined` to remove one. Only the
199
+ * written entities are validated (by their per-entity parser); the
200
+ * whole-document `state` validator does not run, so a tick that moves a
201
+ * few ships out of thousands costs what it touches. One revision, one
202
+ * delta, however many ids.
203
+ */
204
+ setEntities<C extends EntityCollection<S>>(collection: C, changes: Readonly<Record<string, EntityOf<S, C> | undefined>>): void;
205
+ /** {@link setEntities} for one id; `undefined` removes it. */
206
+ setEntity<C extends EntityCollection<S>>(collection: C, id: string, value: EntityOf<S, C> | undefined): void;
207
+ /**
208
+ * Handles, ledgers and pending projections, for a bounds report; and
209
+ * `sparseViews`, how many per-view deltas were projected from the changed
210
+ * entities alone (an entity write on a collection declared in both
211
+ * `interest` and `entities`, with only declared visibility rules).
212
+ */
213
+ counts(): {
214
+ readonly handles: number;
215
+ readonly ledgers: number;
216
+ readonly deferred: number;
217
+ readonly sparseViews: number;
218
+ };
219
+ counters(): Readonly<Record<string, number>>;
220
+ close(): Promise<void>;
221
+ }
222
+ /**
223
+ * What {@link hostPlayer} needs from a hosted store and nothing else
224
+ * does: its owner, the path that sends what a local call dispatched,
225
+ * and whether the store is still serving. Package-internal — keyed on
226
+ * the handle object, so only a handle `hostStore` returned has one.
227
+ */
228
+ export interface HostInternals<S extends object, A extends ActionSpec, I extends InputSpec> {
229
+ readonly owner: StoreOwner<S, A, I>;
230
+ readonly peer: string;
231
+ readonly maxEventBytes: number;
232
+ dispatched(result: Dispatched): void;
233
+ isClosed(): boolean;
234
+ onClose(listener: () => void): void;
235
+ }
236
+ /** The internals of a handle `hostStore` returned, or `undefined`. */
237
+ export declare function hostInternals<S extends object, A extends ActionSpec, I extends InputSpec>(handle: HostedStoreHandle<S, A, I>): HostInternals<S, A, I> | undefined;
238
+ /**
239
+ * Serve one store to the mesh.
240
+ *
241
+ * Every inbound frame is dispatched with the peer the TRANSPORT
242
+ * authenticated. A frame whose event carries no peer is dropped and
243
+ * counted rather than dispatched with a guess: on a follower's proxied
244
+ * handle that field was once absent, and admitting those frames is the
245
+ * cross-peer admixture the identity work removed.
246
+ */
247
+ export declare function hostStore<S extends object, A extends ActionSpec, I extends InputSpec>(options: HostStoreOptions<S, A, I>): HostedStoreHandle<S, A, I>;