wire-mesh-core 4.0.2 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -4
- package/dist/adapters/byte-stream-connection.cjs +15 -4
- package/dist/adapters/byte-stream-connection.mjs +15 -4
- package/dist/adapters/memory-storage.d.cts +1 -1
- package/dist/adapters/memory-storage.d.mts +1 -1
- package/dist/adapters/node-fs-storage.cjs +4 -3
- package/dist/adapters/node-fs-storage.d.cts +1 -1
- package/dist/adapters/node-fs-storage.d.mts +1 -1
- package/dist/adapters/node-fs-storage.mjs +4 -3
- package/dist/adapters/system-clock.d.cts +1 -1
- package/dist/adapters/system-clock.d.mts +1 -1
- package/dist/clock-DhUPRfLh.d.cts +10 -0
- package/dist/clock-DhUPRfLh.d.mts +10 -0
- package/dist/domain/bulk.cjs +21 -12
- package/dist/domain/bulk.d.cts +1 -1
- package/dist/domain/bulk.d.mts +1 -1
- package/dist/domain/bulk.mjs +21 -12
- package/dist/domain/capability-grant.d.cts +1 -1
- package/dist/domain/capability-grant.d.mts +1 -1
- package/dist/domain/capability-request.d.cts +2 -2
- package/dist/domain/capability-request.d.mts +2 -2
- package/dist/domain/data-sync.cjs +22 -19
- package/dist/domain/data-sync.d.cts +1 -1
- package/dist/domain/data-sync.d.mts +1 -1
- package/dist/domain/data-sync.mjs +22 -19
- package/dist/domain/gossip-expansion.cjs +11 -5
- package/dist/domain/gossip-expansion.mjs +11 -5
- package/dist/domain/hub-mailbox.d.cts +1 -1
- package/dist/domain/hub-mailbox.d.mts +1 -1
- package/dist/domain/mesh-session.cjs +27 -14
- package/dist/domain/mesh-session.d.cts +1 -1
- package/dist/domain/mesh-session.d.mts +1 -1
- package/dist/domain/mesh-session.mjs +27 -14
- package/dist/domain/notice-board.cjs +4 -1
- package/dist/domain/notice-board.d.cts +2 -2
- package/dist/domain/notice-board.d.mts +2 -2
- package/dist/domain/notice-board.mjs +4 -1
- package/dist/domain/path-trace.d.cts +1 -1
- package/dist/domain/path-trace.d.mts +1 -1
- package/dist/domain/relay-hub.cjs +15 -10
- package/dist/domain/relay-hub.mjs +15 -10
- package/dist/domain/room-rekey.d.cts +1 -1
- package/dist/domain/room-rekey.d.mts +1 -1
- package/dist/domain/room.d.cts +1 -1
- package/dist/domain/room.d.mts +1 -1
- package/dist/domain/token-predicates.cjs +10 -10
- package/dist/domain/token-predicates.d.cts +1 -1
- package/dist/domain/token-predicates.d.mts +1 -1
- package/dist/domain/token-predicates.mjs +10 -10
- package/dist/domain/tokens.cjs +16 -4
- package/dist/domain/tokens.d.cts +2 -2
- package/dist/domain/tokens.d.mts +2 -2
- package/dist/domain/tokens.mjs +16 -4
- package/dist/ports/clock.d.cts +1 -1
- package/dist/ports/clock.d.mts +1 -1
- package/dist/ports/storage.d.cts +1 -1
- package/dist/ports/storage.d.mts +1 -1
- package/dist/sequential-CtQVlW8M.cjs +31 -0
- package/dist/sequential-D_W3THly.mjs +20 -0
- package/dist/storage-jbGFAYIV.d.cts +13 -0
- package/dist/storage-jbGFAYIV.d.mts +13 -0
- package/dist/{token-predicates-DqpG8VZZ.d.mts → token-predicates-CrieP1WI.d.mts} +1 -1
- package/dist/{token-predicates-D4NNPLnp.d.cts → token-predicates-lg4TtUxm.d.cts} +1 -1
- package/package.json +2 -2
- package/dist/clock-DiSx-WKM.d.cts +0 -7
- package/dist/clock-DiSx-WKM.d.mts +0 -7
- package/dist/storage-B403CRyu.d.cts +0 -10
- package/dist/storage-B403CRyu.d.mts +0 -10
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@ The TypeScript implementation of wire-mesh's protocol, built ports/adapters: dom
|
|
|
4
4
|
|
|
5
5
|
## Ports
|
|
6
6
|
|
|
7
|
-
- **Transport** (`src/ports/transport.ts`)
|
|
8
|
-
- **Storage** (`src/ports/storage.ts`)
|
|
7
|
+
- **Transport** (`src/ports/transport.ts`): an async contract for sending/receiving `Frame` values over a connection. Adapters: `src/adapters/tcp-transport.ts` (length-prefixed CBOR frames over plain `node:net`, matching Cascade's own transport shape) and `src/adapters/tls-transport.ts` (the same framing over `node:tls`).
|
|
8
|
+
- **Storage** (`src/ports/storage.ts`): an async key/value contract. Adapters: `src/adapters/memory-storage.ts` (in-process, for tests and single-process nodes) and `src/adapters/node-fs-storage.ts` (filesystem).
|
|
9
9
|
- **Identity** (`src/ports/identity.ts`) -- device-id derivation and COSE signing/verification. Adapter: `src/adapters/node-identity.ts` (Web Crypto, ECDSA P-256 and Ed25519 -- the two algorithms the spec's own conformance vectors use).
|
|
10
10
|
- **Clock** (`src/ports/clock.ts`) -- injected time, never `Date.now()` pulled directly into domain logic. Adapter: `src/adapters/system-clock.ts`.
|
|
11
11
|
|
|
@@ -15,10 +15,17 @@ The TypeScript implementation of wire-mesh's protocol, built ports/adapters: dom
|
|
|
15
15
|
- **Capability-token verification** (`src/domain/tokens.ts`) -- the full chain tokens.cddl documents: COSE_Sign1 signature verification, the self-certifying issuer-key check (`sha256(issuer-key.public-key) == issuer`), expiry/not-before, issuer-matched revocation (only a token's own issuer's signed revocation-entry counts, checked across every ancestor in the delegation chain, not just the leaf), and recursive delegation-chain narrowing across all three axes of authority: a delegated token's issuer must be its parent's bearer (the chain is unbroken), its expiry must not exceed its parent's, and its scope must narrow its parent's (identical kind, equal-or-descendant path when the parent carries one) with an identical capability verb (the verb grammar has no sub-verb relation, so a different verb is different authority, not narrower). Also exports `verifyRevocationEntry` for ingesting gossiped revocation-announce frames: each entry is itself a signed, self-certifying COSE_Sign1 over revocation-claims, verified before it may enter the revocation view.
|
|
16
16
|
- **Peer-advert verification** (`src/domain/peer-advert.ts`): the two checks transport.cddl states for a gossiped advert, applied by the relay hub before it registers or forwards one and by every session before one reaches its directory: the embedded identity-key self-certifies (`sha256(identity-key.public-key) == device`), and the signature verifies under that same key over the domain-separated canonical encoding of the advert with its signature removed, which covers the open extension tail as well. Deliberately not tokens.cddl's COSE Sig_structure: an advert's open tail lets an attacker craft one whose encoding also satisfies token-claims, so the prefixed construction keeps the two byte strings disjoint. Reaches a verdict for every input rather than throwing, since an advert's algorithm and key bytes are attacker-chosen.
|
|
17
17
|
- **Secure channel** (`src/domain/secure-channel.ts`, `src/domain/relay-channels.ts`): the end-to-end channel spec/secure-channel.cddl defines between two devices that reach each other through a relay. A session opens one with each peer it addresses through a relay pairing, seals every manage-request, manage-response and core/data frame in it, drops any that arrives as plain relay-data, and attributes a request to the identity the peer's handshake proved rather than to the `from-device` a hub stamps. There is no plaintext fallback, so a peer that does not speak it cannot be reached through a relay.
|
|
18
|
+
- **Relay hub** (`src/domain/relay-hub.ts`, `src/domain/relay-pairing.ts`, `src/domain/hub-mailbox.ts`): the hub's relay role against the Transport port alone. It verifies every gossiped advert before registering, forwarding or replaying it, pairs devices for relayed traffic, and, as a mailbox, replicates the `core/data` logs of devices that are offline.
|
|
19
|
+
- **Bulk transfer** (`src/domain/bulk.ts`): resumable, flow-controlled `core/bulk` transfer, with a byte-credit window that the receiver's acknowledgements move. `src/domain/sharded-delivery.ts` composes it with erasure coding (`src/domain/erasure-coding.ts`) and shard manifests (`src/domain/shard-manifest.ts`) to spread a payload across holder devices.
|
|
20
|
+
- **Data sync** (`src/domain/data-sync.ts`): the have, request and entries primitive over a storage-backed oplog, which never accepts a gap. What to broadcast, whose logs to track and which holder to ask are left to the application.
|
|
21
|
+
- **Coordinator election** (`src/domain/coordinator-election.ts`): the gossiped, term-based election over `coordinator-frame`, where a higher term supersedes a lower one and equal terms are broken by lowest device-id.
|
|
22
|
+
- **Rooms** (`src/domain/room.ts`, `src/domain/room-path.ts`, `src/domain/room-token-verification.ts`, `src/domain/room-rekey.ts`, `src/domain/group-key.ts`, `src/domain/notice-board.ts`): self-certifying room-notice verification, owner-named and direct-message room paths, the room verifier obligations on top of token verification, content-key distribution and rotation, and the encrypted noticeboard that composes them with data sync.
|
|
23
|
+
- **Threshold signing** (`src/domain/threshold-network.ts`, `src/adapters/threshold-*.ts`): the `exadev.io/threshold` manage-commands for FROST(Ed25519) key generation, resharing and two-round signing, with the cryptography supplied by the WebAssembly build of the Rust threshold crate.
|
|
24
|
+
- **Mesh session** (`src/domain/mesh-session.ts`): the connection session that owns the handshake exchange, the gossip-built peer directory, manage-request dispatch and reconnection, and that the hub, LAN node and web console build on.
|
|
18
25
|
|
|
19
|
-
##
|
|
26
|
+
## Not implemented
|
|
20
27
|
|
|
21
|
-
|
|
28
|
+
Exec and streaming are schema only: `conformance-check` proves the generated schemas decode and re-encode their golden vectors byte-exactly, but no code spawns a process or PTY (`proc.*`, `pty.*`) or sends, receives or enforces the credit window of a stream (`stream-data` and its acknowledgements). Tracked in [#341](https://github.com/ExaDev/wire-mesh/issues/341). Direct-path NAT traversal is not implemented either, so a connection across a NAT goes through the relay; tracked in [#343](https://github.com/ExaDev/wire-mesh/issues/343). Nothing resolves DNS-anchored handles. The federation link protocol no longer exists in the spec: cross-scope sharing is ordinary capability-token delegation, and the verification chain in `src/domain/tokens.ts` is the mechanism that governs it.
|
|
22
29
|
|
|
23
30
|
## Building from a fresh checkout
|
|
24
31
|
|
|
@@ -22,16 +22,27 @@ function concat(first, second) {
|
|
|
22
22
|
joined.set(second, first.length);
|
|
23
23
|
return joined;
|
|
24
24
|
}
|
|
25
|
+
/** A reader's chunks as an async iterable, which ends with the stream and leaves releasing or cancelling the reader to its owner. */
|
|
26
|
+
function chunksOf(reader) {
|
|
27
|
+
return { [Symbol.asyncIterator]: () => ({ next: async () => {
|
|
28
|
+
const chunk = await reader.read();
|
|
29
|
+
return chunk.done ? {
|
|
30
|
+
done: true,
|
|
31
|
+
value: void 0
|
|
32
|
+
} : {
|
|
33
|
+
done: false,
|
|
34
|
+
value: chunk.value
|
|
35
|
+
};
|
|
36
|
+
} }) };
|
|
37
|
+
}
|
|
25
38
|
/**
|
|
26
39
|
* Frames arriving on a byte stream, in order, until it ends. An empty body (STREAM_OPEN_MARKER) is skipped. A body that does not decode as CBOR, or a length prefix above MAX_FRAME_BYTES, rejects the iteration (a connection-level failure); a body that decodes but fails frame validation is dropped and the stream continues, since an unrecognised frame from a newer peer is what version negotiation exists to tolerate.
|
|
27
40
|
*/
|
|
28
41
|
async function* readFrames(reader) {
|
|
29
42
|
let buffered = /* @__PURE__ */ new Uint8Array(0);
|
|
30
43
|
try {
|
|
31
|
-
for (
|
|
32
|
-
|
|
33
|
-
if (chunk.done) return;
|
|
34
|
-
buffered = concat(buffered, chunk.value);
|
|
44
|
+
for await (const chunk of chunksOf(reader)) {
|
|
45
|
+
buffered = concat(buffered, chunk);
|
|
35
46
|
while (buffered.length >= LENGTH_PREFIX_BYTES) {
|
|
36
47
|
const bodyLength = new DataView(buffered.buffer, buffered.byteOffset).getUint32(0);
|
|
37
48
|
if (bodyLength > 104857600) throw new Error(`frame of ${String(bodyLength)} bytes exceeds the ${String(MAX_FRAME_BYTES)}-byte limit`);
|
|
@@ -21,16 +21,27 @@ function concat(first, second) {
|
|
|
21
21
|
joined.set(second, first.length);
|
|
22
22
|
return joined;
|
|
23
23
|
}
|
|
24
|
+
/** A reader's chunks as an async iterable, which ends with the stream and leaves releasing or cancelling the reader to its owner. */
|
|
25
|
+
function chunksOf(reader) {
|
|
26
|
+
return { [Symbol.asyncIterator]: () => ({ next: async () => {
|
|
27
|
+
const chunk = await reader.read();
|
|
28
|
+
return chunk.done ? {
|
|
29
|
+
done: true,
|
|
30
|
+
value: void 0
|
|
31
|
+
} : {
|
|
32
|
+
done: false,
|
|
33
|
+
value: chunk.value
|
|
34
|
+
};
|
|
35
|
+
} }) };
|
|
36
|
+
}
|
|
24
37
|
/**
|
|
25
38
|
* Frames arriving on a byte stream, in order, until it ends. An empty body (STREAM_OPEN_MARKER) is skipped. A body that does not decode as CBOR, or a length prefix above MAX_FRAME_BYTES, rejects the iteration (a connection-level failure); a body that decodes but fails frame validation is dropped and the stream continues, since an unrecognised frame from a newer peer is what version negotiation exists to tolerate.
|
|
26
39
|
*/
|
|
27
40
|
async function* readFrames(reader) {
|
|
28
41
|
let buffered = /* @__PURE__ */ new Uint8Array(0);
|
|
29
42
|
try {
|
|
30
|
-
for (
|
|
31
|
-
|
|
32
|
-
if (chunk.done) return;
|
|
33
|
-
buffered = concat(buffered, chunk.value);
|
|
43
|
+
for await (const chunk of chunksOf(reader)) {
|
|
44
|
+
buffered = concat(buffered, chunk);
|
|
34
45
|
while (buffered.length >= LENGTH_PREFIX_BYTES) {
|
|
35
46
|
const bodyLength = new DataView(buffered.buffer, buffered.byteOffset).getUint32(0);
|
|
36
47
|
if (bodyLength > 104857600) throw new Error(`frame of ${String(bodyLength)} bytes exceeds the ${String(MAX_FRAME_BYTES)}-byte limit`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as KeyValueStorage } from "../storage-
|
|
1
|
+
import { t as KeyValueStorage } from "../storage-jbGFAYIV.cjs";
|
|
2
2
|
//#region src/adapters/memory-storage.d.ts
|
|
3
3
|
/** An in-process KeyValueStorage backed by a Map -- for tests and single-process nodes; a real deployment substitutes a persistent adapter behind the same contract without touching anything that depends on the port. Not declared `async`: every operation is genuinely synchronous under the hood, so the contract's Promise return is satisfied directly via Promise.resolve() rather than an async function with no await in its body. */
|
|
4
4
|
export declare function createMemoryStorage(): KeyValueStorage;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as KeyValueStorage } from "../storage-
|
|
1
|
+
import { t as KeyValueStorage } from "../storage-jbGFAYIV.mjs";
|
|
2
2
|
//#region src/adapters/memory-storage.d.ts
|
|
3
3
|
/** An in-process KeyValueStorage backed by a Map -- for tests and single-process nodes; a real deployment substitutes a persistent adapter behind the same contract without touching anything that depends on the port. Not declared `async`: every operation is genuinely synchronous under the hood, so the contract's Promise return is satisfied directly via Promise.resolve() rather than an async function with no await in its body. */
|
|
4
4
|
export declare function createMemoryStorage(): KeyValueStorage;
|
|
@@ -23,9 +23,10 @@ async function listAllRelativePaths(baseDir) {
|
|
|
23
23
|
if (isEnoent(error)) return [];
|
|
24
24
|
throw error;
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
return (await Promise.all(entries.map(async (entry) => ({
|
|
27
|
+
entry,
|
|
28
|
+
stat: await node_fs_promises.stat(node_path.join(baseDir, entry)).catch(() => null)
|
|
29
|
+
})))).filter(({ stat }) => stat?.isFile() === true).map(({ entry }) => entry.split(node_path.sep).join("/"));
|
|
29
30
|
}
|
|
30
31
|
function createNodeFsStorage(options) {
|
|
31
32
|
const baseDir = options.dir;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as KeyValueStorage } from "../storage-
|
|
1
|
+
import { t as KeyValueStorage } from "../storage-jbGFAYIV.cjs";
|
|
2
2
|
//#region src/adapters/node-fs-storage.d.ts
|
|
3
3
|
export interface NodeFsStorageOptions {
|
|
4
4
|
/** Directory to store entries under. Created on first write if it doesn't already exist. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as KeyValueStorage } from "../storage-
|
|
1
|
+
import { t as KeyValueStorage } from "../storage-jbGFAYIV.mjs";
|
|
2
2
|
//#region src/adapters/node-fs-storage.d.ts
|
|
3
3
|
export interface NodeFsStorageOptions {
|
|
4
4
|
/** Directory to store entries under. Created on first write if it doesn't already exist. */
|
|
@@ -19,9 +19,10 @@ async function listAllRelativePaths(baseDir) {
|
|
|
19
19
|
if (isEnoent(error)) return [];
|
|
20
20
|
throw error;
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
return (await Promise.all(entries.map(async (entry) => ({
|
|
23
|
+
entry,
|
|
24
|
+
stat: await fs.stat(path.join(baseDir, entry)).catch(() => null)
|
|
25
|
+
})))).filter(({ stat }) => stat?.isFile() === true).map(({ entry }) => entry.split(path.sep).join("/"));
|
|
25
26
|
}
|
|
26
27
|
function createNodeFsStorage(options) {
|
|
27
28
|
const baseDir = options.dir;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as Clock } from "../clock-
|
|
1
|
+
import { t as Clock } from "../clock-DhUPRfLh.cjs";
|
|
2
2
|
//#region src/adapters/system-clock.d.ts
|
|
3
3
|
/** Wraps the system wall clock -- the only place Date.now() appears in this package. */
|
|
4
4
|
export declare function createSystemClock(): Clock;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as Clock } from "../clock-
|
|
1
|
+
import { t as Clock } from "../clock-DhUPRfLh.mjs";
|
|
2
2
|
//#region src/adapters/system-clock.d.ts
|
|
3
3
|
/** Wraps the system wall clock -- the only place Date.now() appears in this package. */
|
|
4
4
|
export declare function createSystemClock(): Clock;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/ports/clock.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Injected time source, never pulled from global Date.now() directly in domain logic -- lets token-expiry and revocation-timestamp checks be tested deterministically and lets a consumer substitute a synchronised network clock later without touching domain code.
|
|
4
|
+
*/
|
|
5
|
+
interface Clock {
|
|
6
|
+
/** Current time as Unix milliseconds, matching every uint timestamp field in the protocol (token-claims.expires, revocation-entry.revoked-at, handshake params, etc.). */
|
|
7
|
+
now: () => number;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { Clock as t };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/ports/clock.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Injected time source, never pulled from global Date.now() directly in domain logic -- lets token-expiry and revocation-timestamp checks be tested deterministically and lets a consumer substitute a synchronised network clock later without touching domain code.
|
|
4
|
+
*/
|
|
5
|
+
interface Clock {
|
|
6
|
+
/** Current time as Unix milliseconds, matching every uint timestamp field in the protocol (token-claims.expires, revocation-entry.revoked-at, handshake params, etc.). */
|
|
7
|
+
now: () => number;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { Clock as t };
|
package/dist/domain/bulk.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_generated_protocol = require("../generated/protocol.cjs");
|
|
3
3
|
const require_domain_device_id = require("./device-id.cjs");
|
|
4
|
+
const require_sequential = require("../sequential-CtQVlW8M.cjs");
|
|
4
5
|
//#region src/domain/bulk.ts
|
|
5
6
|
/**
|
|
6
7
|
* core/bulk (wire-mesh#34/#123): resumable, flow-controlled bulk transfer. `bulk.open`/`bulk.resume`/`bulk.cancel` ride manage-request-frame as ordinary manage-command params; `bulk-data`/`bulk-ack`/`bulk-end` are their own frame kind, riding the connection directly rather than a manage-response -- see spec/bulk.cddl's own header comment for the full design, including the three previously-open questions this module resolves in code (chunk-size negotiation is not a wire concept at all; transfer-id/issuer pairing is a verifier obligation, not a wire field; token expiry is checked once per manage-command, never per data frame).
|
|
@@ -103,22 +104,30 @@ function createBulkSender(options) {
|
|
|
103
104
|
let window = initialWindow;
|
|
104
105
|
let bytesInFlight = 0;
|
|
105
106
|
const inFlight = [];
|
|
107
|
+
/** Takes one ack's credit in: it replaces the window, and every chunk it covers is retired. */
|
|
108
|
+
const applyAck = (ack) => {
|
|
109
|
+
ackedCount = ack["ack-seq"];
|
|
110
|
+
window = ack.window;
|
|
111
|
+
for (let head = inFlight[0]; head !== void 0 && head.seq < ackedCount; head = inFlight[0]) {
|
|
112
|
+
inFlight.shift();
|
|
113
|
+
bytesInFlight -= head.length;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const exceedsWindow = (length) => bytesInFlight + length > window;
|
|
117
|
+
/** Takes acks until `length` more bytes fit in the window. */
|
|
118
|
+
const awaitCredit = async (length) => {
|
|
119
|
+
applyAck(await waitForAck(id));
|
|
120
|
+
if (exceedsWindow(length)) await awaitCredit(length);
|
|
121
|
+
};
|
|
106
122
|
try {
|
|
107
123
|
for await (const chunk of options.source(fromSeq)) {
|
|
108
124
|
if (cancelled.has(id)) return {
|
|
109
125
|
status: "incomplete",
|
|
110
126
|
ackedCount
|
|
111
127
|
};
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
window = ack.window;
|
|
116
|
-
for (;;) {
|
|
117
|
-
const head = inFlight[0];
|
|
118
|
-
if (head === void 0 || head.seq >= ackedCount) break;
|
|
119
|
-
inFlight.shift();
|
|
120
|
-
bytesInFlight -= head.length;
|
|
121
|
-
}
|
|
128
|
+
if (exceedsWindow(chunk.length)) {
|
|
129
|
+
applyAck(await waitForAck(id));
|
|
130
|
+
if (exceedsWindow(chunk.length)) await awaitCredit(chunk.length);
|
|
122
131
|
}
|
|
123
132
|
const dataFrame = {
|
|
124
133
|
type: "bulk-data",
|
|
@@ -306,10 +315,10 @@ function createBulkReceiver(options) {
|
|
|
306
315
|
}
|
|
307
316
|
const ackedCount = await readAckSeq(idHex);
|
|
308
317
|
const chunks = [];
|
|
309
|
-
|
|
318
|
+
await require_sequential.eachInOrder(Array.from({ length: ackedCount }, (_, seq) => seq), async (seq) => {
|
|
310
319
|
const chunk = await storage.get(chunkKey(idHex, seq));
|
|
311
320
|
if (chunk !== void 0) chunks.push(chunk);
|
|
312
|
-
}
|
|
321
|
+
});
|
|
313
322
|
const ok = bytesEqual(await digestOf(concatChunks(chunks)), frame.digest);
|
|
314
323
|
reg.onComplete?.({
|
|
315
324
|
ok,
|
package/dist/domain/bulk.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { D as DeviceId, N as Frame, V as ManageCommand, f as CapabilityScope, p as CapabilityToken } from "../protocol-DzmpxxUr.cjs";
|
|
2
2
|
import { t as Connection } from "../transport-gDbSHO-r.cjs";
|
|
3
|
-
import { t as KeyValueStorage } from "../storage-
|
|
3
|
+
import { t as KeyValueStorage } from "../storage-jbGFAYIV.cjs";
|
|
4
4
|
import { IncomingManageRequest, MeshSession } from "./mesh-session.cjs";
|
|
5
5
|
//#region src/domain/bulk.d.ts
|
|
6
6
|
/** The two capabilities matching the direction each manage-command actually flows (spec/bulk.cddl): the sender proves write authority to open a transfer, the receiver proves read authority to ask for one to continue. */
|
package/dist/domain/bulk.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { D as DeviceId, N as Frame, V as ManageCommand, f as CapabilityScope, p as CapabilityToken } from "../protocol-DzmpxxUr.mjs";
|
|
2
2
|
import { t as Connection } from "../transport-bD3p_TO_.mjs";
|
|
3
|
-
import { t as KeyValueStorage } from "../storage-
|
|
3
|
+
import { t as KeyValueStorage } from "../storage-jbGFAYIV.mjs";
|
|
4
4
|
import { IncomingManageRequest, MeshSession } from "./mesh-session.mjs";
|
|
5
5
|
//#region src/domain/bulk.d.ts
|
|
6
6
|
/** The two capabilities matching the direction each manage-command actually flows (spec/bulk.cddl): the sender proves write authority to open a transfer, the receiver proves read authority to ask for one to continue. */
|
package/dist/domain/bulk.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { bulkCancelSchema, bulkOpenSchema, bulkResumeSchema } from "../generated/protocol.mjs";
|
|
2
2
|
import { bytesToHex } from "./device-id.mjs";
|
|
3
|
+
import { t as eachInOrder } from "../sequential-D_W3THly.mjs";
|
|
3
4
|
//#region src/domain/bulk.ts
|
|
4
5
|
/**
|
|
5
6
|
* core/bulk (wire-mesh#34/#123): resumable, flow-controlled bulk transfer. `bulk.open`/`bulk.resume`/`bulk.cancel` ride manage-request-frame as ordinary manage-command params; `bulk-data`/`bulk-ack`/`bulk-end` are their own frame kind, riding the connection directly rather than a manage-response -- see spec/bulk.cddl's own header comment for the full design, including the three previously-open questions this module resolves in code (chunk-size negotiation is not a wire concept at all; transfer-id/issuer pairing is a verifier obligation, not a wire field; token expiry is checked once per manage-command, never per data frame).
|
|
@@ -102,22 +103,30 @@ function createBulkSender(options) {
|
|
|
102
103
|
let window = initialWindow;
|
|
103
104
|
let bytesInFlight = 0;
|
|
104
105
|
const inFlight = [];
|
|
106
|
+
/** Takes one ack's credit in: it replaces the window, and every chunk it covers is retired. */
|
|
107
|
+
const applyAck = (ack) => {
|
|
108
|
+
ackedCount = ack["ack-seq"];
|
|
109
|
+
window = ack.window;
|
|
110
|
+
for (let head = inFlight[0]; head !== void 0 && head.seq < ackedCount; head = inFlight[0]) {
|
|
111
|
+
inFlight.shift();
|
|
112
|
+
bytesInFlight -= head.length;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const exceedsWindow = (length) => bytesInFlight + length > window;
|
|
116
|
+
/** Takes acks until `length` more bytes fit in the window. */
|
|
117
|
+
const awaitCredit = async (length) => {
|
|
118
|
+
applyAck(await waitForAck(id));
|
|
119
|
+
if (exceedsWindow(length)) await awaitCredit(length);
|
|
120
|
+
};
|
|
105
121
|
try {
|
|
106
122
|
for await (const chunk of options.source(fromSeq)) {
|
|
107
123
|
if (cancelled.has(id)) return {
|
|
108
124
|
status: "incomplete",
|
|
109
125
|
ackedCount
|
|
110
126
|
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
window = ack.window;
|
|
115
|
-
for (;;) {
|
|
116
|
-
const head = inFlight[0];
|
|
117
|
-
if (head === void 0 || head.seq >= ackedCount) break;
|
|
118
|
-
inFlight.shift();
|
|
119
|
-
bytesInFlight -= head.length;
|
|
120
|
-
}
|
|
127
|
+
if (exceedsWindow(chunk.length)) {
|
|
128
|
+
applyAck(await waitForAck(id));
|
|
129
|
+
if (exceedsWindow(chunk.length)) await awaitCredit(chunk.length);
|
|
121
130
|
}
|
|
122
131
|
const dataFrame = {
|
|
123
132
|
type: "bulk-data",
|
|
@@ -305,10 +314,10 @@ function createBulkReceiver(options) {
|
|
|
305
314
|
}
|
|
306
315
|
const ackedCount = await readAckSeq(idHex);
|
|
307
316
|
const chunks = [];
|
|
308
|
-
|
|
317
|
+
await eachInOrder(Array.from({ length: ackedCount }, (_, seq) => seq), async (seq) => {
|
|
309
318
|
const chunk = await storage.get(chunkKey(idHex, seq));
|
|
310
319
|
if (chunk !== void 0) chunks.push(chunk);
|
|
311
|
-
}
|
|
320
|
+
});
|
|
312
321
|
const ok = bytesEqual(await digestOf(concatChunks(chunks)), frame.digest);
|
|
313
322
|
reg.onComplete?.({
|
|
314
323
|
ok,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { D as DeviceId, V as ManageCommand, f as CapabilityScope, p as CapabilityToken } from "../protocol-DzmpxxUr.cjs";
|
|
2
2
|
import { t as IdentityPort } from "../identity-B6nhlSOT.cjs";
|
|
3
|
-
import { t as Clock } from "../clock-
|
|
3
|
+
import { t as Clock } from "../clock-DhUPRfLh.cjs";
|
|
4
4
|
import { IncomingManageRequest, ManageOutcome, MeshSession } from "./mesh-session.cjs";
|
|
5
5
|
import { RevocationCheck } from "./tokens.cjs";
|
|
6
6
|
//#region src/domain/capability-grant.d.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { D as DeviceId, V as ManageCommand, f as CapabilityScope, p as CapabilityToken } from "../protocol-DzmpxxUr.mjs";
|
|
2
2
|
import { t as IdentityPort } from "../identity-EV-HHv_D.mjs";
|
|
3
|
-
import { t as Clock } from "../clock-
|
|
3
|
+
import { t as Clock } from "../clock-DhUPRfLh.mjs";
|
|
4
4
|
import { IncomingManageRequest, ManageOutcome, MeshSession } from "./mesh-session.mjs";
|
|
5
5
|
import { RevocationCheck } from "./tokens.mjs";
|
|
6
6
|
//#region src/domain/capability-grant.d.ts
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { D as DeviceId, V as ManageCommand, f as CapabilityScope, p as CapabilityToken, u as CapabilityGrantOk } from "../protocol-DzmpxxUr.cjs";
|
|
2
2
|
import { t as IdentityPort } from "../identity-B6nhlSOT.cjs";
|
|
3
|
-
import { t as Clock } from "../clock-
|
|
3
|
+
import { t as Clock } from "../clock-DhUPRfLh.cjs";
|
|
4
4
|
import { IncomingManageRequest, MeshSession } from "./mesh-session.cjs";
|
|
5
|
-
import { m as TokenDelegateHandler, r as ConditionsContext } from "../token-predicates-
|
|
5
|
+
import { m as TokenDelegateHandler, r as ConditionsContext } from "../token-predicates-lg4TtUxm.cjs";
|
|
6
6
|
import { RevocationCheck } from "./tokens.cjs";
|
|
7
7
|
//#region src/domain/capability-request.d.ts
|
|
8
8
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { D as DeviceId, V as ManageCommand, f as CapabilityScope, p as CapabilityToken, u as CapabilityGrantOk } from "../protocol-DzmpxxUr.mjs";
|
|
2
2
|
import { t as IdentityPort } from "../identity-EV-HHv_D.mjs";
|
|
3
|
-
import { t as Clock } from "../clock-
|
|
3
|
+
import { t as Clock } from "../clock-DhUPRfLh.mjs";
|
|
4
4
|
import { IncomingManageRequest, MeshSession } from "./mesh-session.mjs";
|
|
5
|
-
import { m as TokenDelegateHandler, r as ConditionsContext } from "../token-predicates-
|
|
5
|
+
import { m as TokenDelegateHandler, r as ConditionsContext } from "../token-predicates-CrieP1WI.mjs";
|
|
6
6
|
import { RevocationCheck } from "./tokens.mjs";
|
|
7
7
|
//#region src/domain/capability-request.d.ts
|
|
8
8
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_domain_device_id = require("./device-id.cjs");
|
|
3
|
+
const require_sequential = require("../sequential-CtQVlW8M.cjs");
|
|
3
4
|
//#region src/domain/data-sync.ts
|
|
4
5
|
function headKey(peer) {
|
|
5
6
|
return `data/${require_domain_device_id.deviceIdToHex(peer)}/head`;
|
|
@@ -50,18 +51,28 @@ async function handleDataHave(storage, frame) {
|
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
54
|
+
* The entries stored for `peer` from sequence `first` to `last` inclusive, read one after another and cut at the first hole: a hole is a record missing from a range the head claims is held, which should not happen while entryKey and headKey stay consistent, and stopping there is better than returning a run with a silent gap that a receiver's own check would only catch after the fact. Keys past a hole are never read.
|
|
55
|
+
*/
|
|
56
|
+
async function readRun(storage, peer, first, last) {
|
|
57
|
+
const run = [];
|
|
58
|
+
const readFrom = async (seq) => {
|
|
59
|
+
if (seq > last) return;
|
|
60
|
+
const entry = await storage.get(entryKey(peer, seq));
|
|
61
|
+
if (entry === void 0) return;
|
|
62
|
+
run.push(entry);
|
|
63
|
+
await readFrom(seq + 1);
|
|
64
|
+
};
|
|
65
|
+
await readFrom(first);
|
|
66
|
+
return run;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
53
69
|
* Handles an incoming data-request: the entries this store actually holds for `frame.peer` after `frame["from-seq"]`, capped at `limit`, or null if it holds none past that point. The responder need not be `frame.peer` itself -- any peer holding a replica may answer on the original author's behalf, per data-domain.cddl's own fan-out rule; this function makes no distinction.
|
|
54
70
|
*/
|
|
55
71
|
async function handleDataRequest(storage, frame, limit) {
|
|
56
72
|
const ourHead = await readHeadSeq(storage, frame.peer);
|
|
57
73
|
if (ourHead <= frame["from-seq"]) return null;
|
|
58
74
|
const lastSeq = Math.min(ourHead, frame["from-seq"] + limit);
|
|
59
|
-
const entries = [];
|
|
60
|
-
for (let seq = frame["from-seq"] + 1; seq <= lastSeq; seq += 1) {
|
|
61
|
-
const entry = await storage.get(entryKey(frame.peer, seq));
|
|
62
|
-
if (entry === void 0) break;
|
|
63
|
-
entries.push(entry);
|
|
64
|
-
}
|
|
75
|
+
const entries = await readRun(storage, frame.peer, frame["from-seq"] + 1, lastSeq);
|
|
65
76
|
if (entries.length === 0) return null;
|
|
66
77
|
return {
|
|
67
78
|
type: "data-entries",
|
|
@@ -79,12 +90,10 @@ async function handleDataEntries(storage, frame) {
|
|
|
79
90
|
ok: false,
|
|
80
91
|
reason: "gap"
|
|
81
92
|
};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
await writeHeadSeq(storage, frame.peer, seq);
|
|
93
|
+
await require_sequential.eachInOrder(frame.entries, async (entry, index) => {
|
|
94
|
+
await storage.set(entryKey(frame.peer, frame["from-seq"] + 1 + index), entry);
|
|
95
|
+
});
|
|
96
|
+
await writeHeadSeq(storage, frame.peer, frame["from-seq"] + frame.entries.length);
|
|
88
97
|
return { ok: true };
|
|
89
98
|
}
|
|
90
99
|
/**
|
|
@@ -92,13 +101,7 @@ async function handleDataEntries(storage, frame) {
|
|
|
92
101
|
*/
|
|
93
102
|
async function readEntries(storage, peer, fromSeqExclusive) {
|
|
94
103
|
const head = await readHeadSeq(storage, peer);
|
|
95
|
-
|
|
96
|
-
for (let seq = fromSeqExclusive + 1; seq <= head; seq += 1) {
|
|
97
|
-
const entry = await storage.get(entryKey(peer, seq));
|
|
98
|
-
if (entry === void 0) break;
|
|
99
|
-
entries.push(entry);
|
|
100
|
-
}
|
|
101
|
-
return entries;
|
|
104
|
+
return readRun(storage, peer, fromSeqExclusive + 1, head);
|
|
102
105
|
}
|
|
103
106
|
//#endregion
|
|
104
107
|
exports.appendOwnEntry = appendOwnEntry;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { D as DeviceId, E as DataRequestFrame, T as DataHaveFrame, w as DataEntriesFrame } from "../protocol-DzmpxxUr.cjs";
|
|
2
|
-
import { t as KeyValueStorage } from "../storage-
|
|
2
|
+
import { t as KeyValueStorage } from "../storage-jbGFAYIV.cjs";
|
|
3
3
|
import { t as IdentityPort } from "../identity-B6nhlSOT.cjs";
|
|
4
4
|
//#region src/domain/data-sync.d.ts
|
|
5
5
|
export interface DataSyncOptions {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { D as DeviceId, E as DataRequestFrame, T as DataHaveFrame, w as DataEntriesFrame } from "../protocol-DzmpxxUr.mjs";
|
|
2
|
-
import { t as KeyValueStorage } from "../storage-
|
|
2
|
+
import { t as KeyValueStorage } from "../storage-jbGFAYIV.mjs";
|
|
3
3
|
import { t as IdentityPort } from "../identity-EV-HHv_D.mjs";
|
|
4
4
|
//#region src/domain/data-sync.d.ts
|
|
5
5
|
export interface DataSyncOptions {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { deviceIdToHex } from "./device-id.mjs";
|
|
2
|
+
import { t as eachInOrder } from "../sequential-D_W3THly.mjs";
|
|
2
3
|
//#region src/domain/data-sync.ts
|
|
3
4
|
function headKey(peer) {
|
|
4
5
|
return `data/${deviceIdToHex(peer)}/head`;
|
|
@@ -49,18 +50,28 @@ async function handleDataHave(storage, frame) {
|
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
/**
|
|
53
|
+
* The entries stored for `peer` from sequence `first` to `last` inclusive, read one after another and cut at the first hole: a hole is a record missing from a range the head claims is held, which should not happen while entryKey and headKey stay consistent, and stopping there is better than returning a run with a silent gap that a receiver's own check would only catch after the fact. Keys past a hole are never read.
|
|
54
|
+
*/
|
|
55
|
+
async function readRun(storage, peer, first, last) {
|
|
56
|
+
const run = [];
|
|
57
|
+
const readFrom = async (seq) => {
|
|
58
|
+
if (seq > last) return;
|
|
59
|
+
const entry = await storage.get(entryKey(peer, seq));
|
|
60
|
+
if (entry === void 0) return;
|
|
61
|
+
run.push(entry);
|
|
62
|
+
await readFrom(seq + 1);
|
|
63
|
+
};
|
|
64
|
+
await readFrom(first);
|
|
65
|
+
return run;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
52
68
|
* Handles an incoming data-request: the entries this store actually holds for `frame.peer` after `frame["from-seq"]`, capped at `limit`, or null if it holds none past that point. The responder need not be `frame.peer` itself -- any peer holding a replica may answer on the original author's behalf, per data-domain.cddl's own fan-out rule; this function makes no distinction.
|
|
53
69
|
*/
|
|
54
70
|
async function handleDataRequest(storage, frame, limit) {
|
|
55
71
|
const ourHead = await readHeadSeq(storage, frame.peer);
|
|
56
72
|
if (ourHead <= frame["from-seq"]) return null;
|
|
57
73
|
const lastSeq = Math.min(ourHead, frame["from-seq"] + limit);
|
|
58
|
-
const entries = [];
|
|
59
|
-
for (let seq = frame["from-seq"] + 1; seq <= lastSeq; seq += 1) {
|
|
60
|
-
const entry = await storage.get(entryKey(frame.peer, seq));
|
|
61
|
-
if (entry === void 0) break;
|
|
62
|
-
entries.push(entry);
|
|
63
|
-
}
|
|
74
|
+
const entries = await readRun(storage, frame.peer, frame["from-seq"] + 1, lastSeq);
|
|
64
75
|
if (entries.length === 0) return null;
|
|
65
76
|
return {
|
|
66
77
|
type: "data-entries",
|
|
@@ -78,12 +89,10 @@ async function handleDataEntries(storage, frame) {
|
|
|
78
89
|
ok: false,
|
|
79
90
|
reason: "gap"
|
|
80
91
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
await writeHeadSeq(storage, frame.peer, seq);
|
|
92
|
+
await eachInOrder(frame.entries, async (entry, index) => {
|
|
93
|
+
await storage.set(entryKey(frame.peer, frame["from-seq"] + 1 + index), entry);
|
|
94
|
+
});
|
|
95
|
+
await writeHeadSeq(storage, frame.peer, frame["from-seq"] + frame.entries.length);
|
|
87
96
|
return { ok: true };
|
|
88
97
|
}
|
|
89
98
|
/**
|
|
@@ -91,13 +100,7 @@ async function handleDataEntries(storage, frame) {
|
|
|
91
100
|
*/
|
|
92
101
|
async function readEntries(storage, peer, fromSeqExclusive) {
|
|
93
102
|
const head = await readHeadSeq(storage, peer);
|
|
94
|
-
|
|
95
|
-
for (let seq = fromSeqExclusive + 1; seq <= head; seq += 1) {
|
|
96
|
-
const entry = await storage.get(entryKey(peer, seq));
|
|
97
|
-
if (entry === void 0) break;
|
|
98
|
-
entries.push(entry);
|
|
99
|
-
}
|
|
100
|
-
return entries;
|
|
103
|
+
return readRun(storage, peer, fromSeqExclusive + 1, head);
|
|
101
104
|
}
|
|
102
105
|
//#endregion
|
|
103
106
|
export { appendOwnEntry, handleDataEntries, handleDataHave, handleDataRequest, headSeqFor, readEntries };
|
|
@@ -20,15 +20,21 @@ function createGossipExpansion(options) {
|
|
|
20
20
|
options.onExpansionDeclined?.(candidate);
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
await dialInOrder(candidate, candidate.addresses, []);
|
|
24
|
+
}
|
|
25
|
+
/** Dials the addresses one at a time, in order, stopping at the first that connects: a later address is only tried once every earlier one has failed, and when none connects the failures, in order, are reported through `onExpansionFailed`. */
|
|
26
|
+
async function dialInOrder(candidate, addresses, failures) {
|
|
27
|
+
const [address, ...remaining] = addresses;
|
|
28
|
+
if (address === void 0) {
|
|
29
|
+
options.onExpansionFailed?.(candidate, failures);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
25
33
|
const session = await options.dial(address);
|
|
26
34
|
options.onExpanded(candidate, address, session);
|
|
27
|
-
return;
|
|
28
35
|
} catch (error) {
|
|
29
|
-
|
|
36
|
+
await dialInOrder(candidate, remaining, [...failures, toError(error)]);
|
|
30
37
|
}
|
|
31
|
-
options.onExpansionFailed?.(candidate, errors);
|
|
32
38
|
}
|
|
33
39
|
return { considerAdvert(advert) {
|
|
34
40
|
const hex = require_domain_device_id.deviceIdToHex(advert.device);
|
|
@@ -19,15 +19,21 @@ function createGossipExpansion(options) {
|
|
|
19
19
|
options.onExpansionDeclined?.(candidate);
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
await dialInOrder(candidate, candidate.addresses, []);
|
|
23
|
+
}
|
|
24
|
+
/** Dials the addresses one at a time, in order, stopping at the first that connects: a later address is only tried once every earlier one has failed, and when none connects the failures, in order, are reported through `onExpansionFailed`. */
|
|
25
|
+
async function dialInOrder(candidate, addresses, failures) {
|
|
26
|
+
const [address, ...remaining] = addresses;
|
|
27
|
+
if (address === void 0) {
|
|
28
|
+
options.onExpansionFailed?.(candidate, failures);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
24
32
|
const session = await options.dial(address);
|
|
25
33
|
options.onExpanded(candidate, address, session);
|
|
26
|
-
return;
|
|
27
34
|
} catch (error) {
|
|
28
|
-
|
|
35
|
+
await dialInOrder(candidate, remaining, [...failures, toError(error)]);
|
|
29
36
|
}
|
|
30
|
-
options.onExpansionFailed?.(candidate, errors);
|
|
31
37
|
}
|
|
32
38
|
return { considerAdvert(advert) {
|
|
33
39
|
const hex = deviceIdToHex(advert.device);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, w as DataEntriesFrame } from "../protocol-DzmpxxUr.cjs";
|
|
2
|
-
import { t as KeyValueStorage } from "../storage-
|
|
2
|
+
import { t as KeyValueStorage } from "../storage-jbGFAYIV.cjs";
|
|
3
3
|
//#region src/domain/hub-mailbox.d.ts
|
|
4
4
|
export interface MailboxLimits {
|
|
5
5
|
/** How many devices' logs the mailbox will hold at once. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, w as DataEntriesFrame } from "../protocol-DzmpxxUr.mjs";
|
|
2
|
-
import { t as KeyValueStorage } from "../storage-
|
|
2
|
+
import { t as KeyValueStorage } from "../storage-jbGFAYIV.mjs";
|
|
3
3
|
//#region src/domain/hub-mailbox.d.ts
|
|
4
4
|
export interface MailboxLimits {
|
|
5
5
|
/** How many devices' logs the mailbox will hold at once. */
|