wire-mesh-core 4.0.3 → 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.
Files changed (67) hide show
  1. package/dist/adapters/byte-stream-connection.cjs +15 -4
  2. package/dist/adapters/byte-stream-connection.mjs +15 -4
  3. package/dist/adapters/memory-storage.d.cts +1 -1
  4. package/dist/adapters/memory-storage.d.mts +1 -1
  5. package/dist/adapters/node-fs-storage.cjs +4 -3
  6. package/dist/adapters/node-fs-storage.d.cts +1 -1
  7. package/dist/adapters/node-fs-storage.d.mts +1 -1
  8. package/dist/adapters/node-fs-storage.mjs +4 -3
  9. package/dist/adapters/system-clock.d.cts +1 -1
  10. package/dist/adapters/system-clock.d.mts +1 -1
  11. package/dist/clock-DhUPRfLh.d.cts +10 -0
  12. package/dist/clock-DhUPRfLh.d.mts +10 -0
  13. package/dist/domain/bulk.cjs +21 -12
  14. package/dist/domain/bulk.d.cts +1 -1
  15. package/dist/domain/bulk.d.mts +1 -1
  16. package/dist/domain/bulk.mjs +21 -12
  17. package/dist/domain/capability-grant.d.cts +1 -1
  18. package/dist/domain/capability-grant.d.mts +1 -1
  19. package/dist/domain/capability-request.d.cts +2 -2
  20. package/dist/domain/capability-request.d.mts +2 -2
  21. package/dist/domain/data-sync.cjs +22 -19
  22. package/dist/domain/data-sync.d.cts +1 -1
  23. package/dist/domain/data-sync.d.mts +1 -1
  24. package/dist/domain/data-sync.mjs +22 -19
  25. package/dist/domain/gossip-expansion.cjs +11 -5
  26. package/dist/domain/gossip-expansion.mjs +11 -5
  27. package/dist/domain/hub-mailbox.d.cts +1 -1
  28. package/dist/domain/hub-mailbox.d.mts +1 -1
  29. package/dist/domain/mesh-session.cjs +27 -14
  30. package/dist/domain/mesh-session.d.cts +1 -1
  31. package/dist/domain/mesh-session.d.mts +1 -1
  32. package/dist/domain/mesh-session.mjs +27 -14
  33. package/dist/domain/notice-board.cjs +4 -1
  34. package/dist/domain/notice-board.d.cts +2 -2
  35. package/dist/domain/notice-board.d.mts +2 -2
  36. package/dist/domain/notice-board.mjs +4 -1
  37. package/dist/domain/path-trace.d.cts +1 -1
  38. package/dist/domain/path-trace.d.mts +1 -1
  39. package/dist/domain/relay-hub.cjs +15 -10
  40. package/dist/domain/relay-hub.mjs +15 -10
  41. package/dist/domain/room-rekey.d.cts +1 -1
  42. package/dist/domain/room-rekey.d.mts +1 -1
  43. package/dist/domain/room.d.cts +1 -1
  44. package/dist/domain/room.d.mts +1 -1
  45. package/dist/domain/token-predicates.cjs +10 -10
  46. package/dist/domain/token-predicates.d.cts +1 -1
  47. package/dist/domain/token-predicates.d.mts +1 -1
  48. package/dist/domain/token-predicates.mjs +10 -10
  49. package/dist/domain/tokens.cjs +16 -4
  50. package/dist/domain/tokens.d.cts +2 -2
  51. package/dist/domain/tokens.d.mts +2 -2
  52. package/dist/domain/tokens.mjs +16 -4
  53. package/dist/ports/clock.d.cts +1 -1
  54. package/dist/ports/clock.d.mts +1 -1
  55. package/dist/ports/storage.d.cts +1 -1
  56. package/dist/ports/storage.d.mts +1 -1
  57. package/dist/sequential-CtQVlW8M.cjs +31 -0
  58. package/dist/sequential-D_W3THly.mjs +20 -0
  59. package/dist/storage-jbGFAYIV.d.cts +13 -0
  60. package/dist/storage-jbGFAYIV.d.mts +13 -0
  61. package/dist/{token-predicates-DqpG8VZZ.d.mts → token-predicates-CrieP1WI.d.mts} +1 -1
  62. package/dist/{token-predicates-D4NNPLnp.d.cts → token-predicates-lg4TtUxm.d.cts} +1 -1
  63. package/package.json +2 -2
  64. package/dist/clock-DiSx-WKM.d.cts +0 -7
  65. package/dist/clock-DiSx-WKM.d.mts +0 -7
  66. package/dist/storage-B403CRyu.d.cts +0 -10
  67. package/dist/storage-B403CRyu.d.mts +0 -10
@@ -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
- const chunk = await reader.read();
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
- const chunk = await reader.read();
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-B403CRyu.cjs";
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-B403CRyu.mjs";
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
- const files = [];
27
- for (const entry of entries) if ((await node_fs_promises.stat(node_path.join(baseDir, entry)).catch(() => null))?.isFile() === true) files.push(entry.split(node_path.sep).join("/"));
28
- return files;
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-B403CRyu.cjs";
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-B403CRyu.mjs";
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
- const files = [];
23
- for (const entry of entries) if ((await fs.stat(path.join(baseDir, entry)).catch(() => null))?.isFile() === true) files.push(entry.split(path.sep).join("/"));
24
- return files;
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-DiSx-WKM.cjs";
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-DiSx-WKM.mjs";
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 };
@@ -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
- while (bytesInFlight + chunk.length > window) {
113
- const ack = await waitForAck(id);
114
- ackedCount = ack["ack-seq"];
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
- for (let seq = 0; seq < ackedCount; seq += 1) {
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,
@@ -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-B403CRyu.cjs";
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. */
@@ -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-B403CRyu.mjs";
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. */
@@ -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
- while (bytesInFlight + chunk.length > window) {
112
- const ack = await waitForAck(id);
113
- ackedCount = ack["ack-seq"];
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
- for (let seq = 0; seq < ackedCount; seq += 1) {
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-DiSx-WKM.cjs";
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-DiSx-WKM.mjs";
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-DiSx-WKM.cjs";
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-D4NNPLnp.cjs";
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-DiSx-WKM.mjs";
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-DqpG8VZZ.mjs";
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
- let seq = frame["from-seq"];
83
- for (const entry of frame.entries) {
84
- seq += 1;
85
- await storage.set(entryKey(frame.peer, seq), entry);
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
- const entries = [];
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-B403CRyu.cjs";
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-B403CRyu.mjs";
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
- let seq = frame["from-seq"];
82
- for (const entry of frame.entries) {
83
- seq += 1;
84
- await storage.set(entryKey(frame.peer, seq), entry);
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
- const entries = [];
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
- const errors = [];
24
- for (const address of candidate.addresses) try {
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
- errors.push(toError(error));
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
- const errors = [];
23
- for (const address of candidate.addresses) try {
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
- errors.push(toError(error));
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-B403CRyu.cjs";
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-B403CRyu.mjs";
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. */