@openlfcp/client 0.2.0-beta.6 → 0.2.0-beta.7

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.
@@ -1,3 +1,4 @@
1
+ import { LfcpError } from "@openlfcp/core";
1
2
  import { type AnyMessage, type ClientConnectionState, type ClientHandshakeConfig, type ReadySession } from "@openlfcp/wire";
2
3
  /**
3
4
  * One LFCP connection over a WebSocket (LFCP-WIRE-01 §30, §31, §34-§38,
@@ -36,6 +37,14 @@ export interface WebSocketLike {
36
37
  export type WebSocketFactory = (url: string, protocols: readonly string[]) => WebSocketLike;
37
38
  /** The platform WebSocket (globalThis.WebSocket); throws if the runtime has none. */
38
39
  export declare function platformWebSocket(): WebSocketFactory;
40
+ /**
41
+ * A send on a connection that is not READY: it closed while the caller was
42
+ * working (an ordinary network loss, not a fault of the caller's). Nothing
43
+ * was sent. Still an LfcpError UNSUPPORTED_VALUE, as before.
44
+ */
45
+ export declare class ConnectionNotReady extends LfcpError {
46
+ constructor();
47
+ }
39
48
  /** §30: the only LFCP subprotocol. */
40
49
  export declare const LFCP_SUBPROTOCOL = "lfcp-1";
41
50
  export interface ConnectionEvents {
@@ -7,6 +7,17 @@ export function platformWebSocket() {
7
7
  throw new LfcpError("UNSUPPORTED_VALUE", "this runtime has no global WebSocket; inject a factory");
8
8
  return (url, protocols) => new ctor(url, [...protocols]);
9
9
  }
10
+ /**
11
+ * A send on a connection that is not READY: it closed while the caller was
12
+ * working (an ordinary network loss, not a fault of the caller's). Nothing
13
+ * was sent. Still an LfcpError UNSUPPORTED_VALUE, as before.
14
+ */
15
+ export class ConnectionNotReady extends LfcpError {
16
+ constructor() {
17
+ super("UNSUPPORTED_VALUE", "the connection is not READY");
18
+ this.name = "ConnectionNotReady";
19
+ }
20
+ }
10
21
  /** §30: the only LFCP subprotocol. */
11
22
  export const LFCP_SUBPROTOCOL = "lfcp-1";
12
23
  const BYTES = (data) => {
@@ -147,7 +158,7 @@ export class LfcpConnection {
147
158
  /** Sends already encoded message bytes (e.g. an OutboundMessage) on a READY session. */
148
159
  sendEncoded(bytes) {
149
160
  if (this.#state !== "READY" || this.#socket === null)
150
- throw new LfcpError("UNSUPPORTED_VALUE", "the connection is not READY");
161
+ throw new ConnectionNotReady();
151
162
  if (bytes.length > this.#limit())
152
163
  throw new LfcpError("MESSAGE_TOO_LARGE", `${bytes.length} bytes exceed the session limit ${this.#limit()}`);
153
164
  this.#socket.send(bytes);
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export { type ApplyOutcome, type DataProfileHandler, DataUnitApplier, type DataU
4
4
  export { ACCEPTANCE_DURABILITY, type BatchStatus, type BatchStatusName, batchStatus, batchStatuses, } from "./batch-status.js";
5
5
  export { type CheckpointSource, ProfileCheckpointer } from "./checkpoint.js";
6
6
  export { type ClaimJournal, pendingInvitationClaims } from "./claim-journal.js";
7
- export { type ConnectionEvents, type ConnectionOptions, LFCP_SUBPROTOCOL, LfcpConnection, platformWebSocket, type WebSocketFactory, type WebSocketLike, } from "./connection.js";
7
+ export { type ConnectionEvents, ConnectionNotReady, type ConnectionOptions, LFCP_SUBPROTOCOL, LfcpConnection, platformWebSocket, type WebSocketFactory, type WebSocketLike, } from "./connection.js";
8
8
  export { type CreateDataUnitOptions, type CreatedDataUnit, createDataUnit } from "./data-unit.js";
9
9
  export { EngineGuard, type EngineItem, isEngineTrap, type Suspicion, snapshotItem, unitItem, } from "./engine-guard.js";
10
10
  export { type AcceptedInvitation, type AcceptInvitationOptions, type AcceptInvitationProgress, type AcceptInvitationStage, abandonInvitationClaim, acceptInvitation, type CreatedInvitation, type CreateInvitationOptions, createInvitation, DEFAULT_INVITATION_ABILITIES, InvitationLink, type ResumeInvitationClaimOptions, resumeInvitationClaim, } from "./invite.js";
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ export { DataUnitApplier, } from "./apply.js";
4
4
  export { ACCEPTANCE_DURABILITY, batchStatus, batchStatuses, } from "./batch-status.js";
5
5
  export { ProfileCheckpointer } from "./checkpoint.js";
6
6
  export { pendingInvitationClaims } from "./claim-journal.js";
7
- export { LFCP_SUBPROTOCOL, LfcpConnection, platformWebSocket, } from "./connection.js";
7
+ export { ConnectionNotReady, LFCP_SUBPROTOCOL, LfcpConnection, platformWebSocket, } from "./connection.js";
8
8
  export { createDataUnit } from "./data-unit.js";
9
9
  export { EngineGuard, isEngineTrap, snapshotItem, unitItem, } from "./engine-guard.js";
10
10
  export { abandonInvitationClaim, acceptInvitation, createInvitation, DEFAULT_INVITATION_ABILITIES, InvitationLink, resumeInvitationClaim, } from "./invite.js";
@@ -6,7 +6,7 @@ import { encode } from "@openlfcp/wire/cbor";
6
6
  import { afterAccepted, afterMismatch, RECOVERY_TRANSIENT_ATTEMPTS, RECOVERY_TRANSIENT_CODES, startRecovery, } from "./access-recovery.js";
7
7
  import { ACCEPTANCE_DURABILITY, batchStatus, batchStatuses, recordEvidence, } from "./batch-status.js";
8
8
  import { readClaimJournal } from "./claim-journal.js";
9
- import { LfcpConnection } from "./connection.js";
9
+ import { ConnectionNotReady, LfcpConnection } from "./connection.js";
10
10
  import { EngineGuard, isEngineTrap, snapshotItem } from "./engine-guard.js";
11
11
  import { resourceSyncState, snapshotFrontier } from "./outbound.js";
12
12
  import { controlRecordWrites, createQueuedSnapshot, keyPackageWrites } from "./queue.js";
@@ -349,6 +349,11 @@ export class SyncClient {
349
349
  this.#queue = this.#queue
350
350
  .then(() => (this.#trapped ? undefined : fn()))
351
351
  .catch((e) => {
352
+ // The connection closed while a handler worked: nothing was sent, and
353
+ // its closing (queued here) starts over at the next READY. An
354
+ // ordinary outcome, not an INTERNAL error.
355
+ if (e instanceof ConnectionNotReady)
356
+ return;
352
357
  if (!isEngineTrap(e)) {
353
358
  this.#emit({
354
359
  type: "error",
@@ -534,10 +539,19 @@ export class SyncClient {
534
539
  // -------------------------------------------------------------------------
535
540
  // plumbing
536
541
  #request(request, message) {
542
+ try {
543
+ this.#connection.send(message);
544
+ }
545
+ catch (e) {
546
+ // Never waiting for an answer to what was not sent (the connection's
547
+ // closing already failed the requests it saw).
548
+ if (e instanceof ConnectionNotReady && request.kind === "host")
549
+ request.reject(new Error("the connection closed before RESOURCE_HOSTED: not READY"));
550
+ throw e;
551
+ }
537
552
  const key = toHex(message.messageId);
538
553
  this.#requests.set(key, request);
539
554
  this.#sentAt.set(key, this.#o.now());
540
- this.#connection.send(message);
541
555
  }
542
556
  /**
543
557
  * Requests unanswered for requestTimeoutMs on a live connection (§70: a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openlfcp/client",
3
- "version": "0.2.0-beta.6",
3
+ "version": "0.2.0-beta.7",
4
4
  "description": "LFCP client: session, Control Plane and Data Plane synchronization.",
5
5
  "keywords": [
6
6
  "openlfcp",
@@ -42,9 +42,9 @@
42
42
  "tag": "beta"
43
43
  },
44
44
  "dependencies": {
45
- "@openlfcp/core": "^0.2.0-beta.6",
46
- "@openlfcp/crypto": "^0.2.0-beta.6",
47
- "@openlfcp/storage": "^0.2.0-beta.6",
48
- "@openlfcp/wire": "^0.2.0-beta.6"
45
+ "@openlfcp/core": "^0.2.0-beta.7",
46
+ "@openlfcp/crypto": "^0.2.0-beta.7",
47
+ "@openlfcp/storage": "^0.2.0-beta.7",
48
+ "@openlfcp/wire": "^0.2.0-beta.7"
49
49
  }
50
50
  }