@openlfcp/client 0.1.0-rc.1
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/LICENSE +201 -0
- package/README.md +27 -0
- package/dist/apply.d.ts +299 -0
- package/dist/apply.js +741 -0
- package/dist/checkpoint.d.ts +31 -0
- package/dist/checkpoint.js +50 -0
- package/dist/connection.d.ts +87 -0
- package/dist/connection.js +210 -0
- package/dist/data-unit.d.ts +50 -0
- package/dist/data-unit.js +39 -0
- package/dist/engine-guard.d.ts +58 -0
- package/dist/engine-guard.js +148 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/invite.d.ts +154 -0
- package/dist/invite.js +391 -0
- package/dist/outbound.d.ts +227 -0
- package/dist/outbound.js +508 -0
- package/dist/queue.d.ts +38 -0
- package/dist/queue.js +116 -0
- package/dist/resource-state.d.ts +34 -0
- package/dist/resource-state.js +24 -0
- package/dist/snapshot.d.ts +42 -0
- package/dist/snapshot.js +40 -0
- package/dist/storage.d.ts +96 -0
- package/dist/storage.js +289 -0
- package/dist/sync-client.d.ts +203 -0
- package/dist/sync-client.js +1032 -0
- package/package.json +50 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { type ControlRecordId, type DataEpoch, type DataUnitId, type Hash32, type ResourceId } from "@openlfcp/core";
|
|
2
|
+
import { type AgreementKeyPair } from "@openlfcp/crypto";
|
|
3
|
+
import { type LfcpStorage, type SecretStore } from "@openlfcp/storage";
|
|
4
|
+
import { type ClientConnectionState, type DataProfileCodec, type HaveVector, type Signer } from "@openlfcp/wire";
|
|
5
|
+
import type { ApplyOutcome, DataUnitApplier, EpochReconciliation } from "./apply.js";
|
|
6
|
+
import type { ProfileCheckpointer } from "./checkpoint.js";
|
|
7
|
+
import { type WebSocketFactory } from "./connection.js";
|
|
8
|
+
import type { AckOutcome, NackOutcome, OutboundQueue, StaleOutboundUnit } from "./outbound.js";
|
|
9
|
+
import { type ResourcePhase } from "./resource-state.js";
|
|
10
|
+
/**
|
|
11
|
+
* The client sync session (LFCP-039a): one LFCP connection to a server and
|
|
12
|
+
* the §65 machine of every Resource opened on it, orchestrating the parts
|
|
13
|
+
* built before it. It adds no protocol rule of its own:
|
|
14
|
+
*
|
|
15
|
+
* - connection, handshake, heartbeat, size limits: LfcpConnection;
|
|
16
|
+
* - Control catch-up: planControlSync, then validateControlChain over the
|
|
17
|
+
* stored records and the fetched ones; a fork is stored and surfaced
|
|
18
|
+
* (CONTROL_CONFLICT), never resolved;
|
|
19
|
+
* - keys: KEY_PACKAGE_GET for epochs without a DEK, receiveKeyPackage, the
|
|
20
|
+
* DEK into the SecretStore before its reference into storage;
|
|
21
|
+
* - data: Have Vectors (missingFrom, ≤256 ranges per DATA_GET), every unit
|
|
22
|
+
* through the DataUnitApplier; periodic DATA_HAVE (§69: anti-entropy);
|
|
23
|
+
* - outbound: OutboundQueue, flushed once keys are in place (§88 step 6),
|
|
24
|
+
* ACK and NACK routed back to it;
|
|
25
|
+
* - on every newly validated Control state: DataUnitApplier.reconcileEpochs
|
|
26
|
+
* AND OutboundQueue.reconcileEpochs (G-EP7, §88 step 7).
|
|
27
|
+
*
|
|
28
|
+
* Time and timers belong to the caller: tick(now) does the periodic work
|
|
29
|
+
* (heartbeat, anti-entropy, retries, checkpoints, reconnect), and a thin
|
|
30
|
+
* driver may call it from a timer (startSyncDriver). Events tell the
|
|
31
|
+
* application what happened; nothing is logged, plaintext never.
|
|
32
|
+
*/
|
|
33
|
+
/** A Resource to synchronize: its Data Profile applier (and optional checkpoints). */
|
|
34
|
+
/**
|
|
35
|
+
* How a Data Profile reads and writes Snapshots (§29, §66 step 3): its §13
|
|
36
|
+
* codec, loading a received Snapshot's state, and the state to publish
|
|
37
|
+
* (for Shared Objects: snapshotCodec(), loadSnapshot(), snapshotState()).
|
|
38
|
+
*/
|
|
39
|
+
export interface SnapshotBinding<T> {
|
|
40
|
+
readonly codec: DataProfileCodec<T>;
|
|
41
|
+
load(value: T): void;
|
|
42
|
+
current(): T;
|
|
43
|
+
}
|
|
44
|
+
/** A Resource to synchronize: its Data Profile applier (and optional checkpoints and Snapshots). */
|
|
45
|
+
export interface ResourceBinding {
|
|
46
|
+
readonly resourceId: ResourceId;
|
|
47
|
+
readonly applier: DataUnitApplier;
|
|
48
|
+
readonly checkpointer?: ProfileCheckpointer;
|
|
49
|
+
/** Load an offered Snapshot instead of replaying every unit, and allow publishing. */
|
|
50
|
+
readonly snapshot?: SnapshotBinding<unknown>;
|
|
51
|
+
}
|
|
52
|
+
/** When to try connecting again after the `attempt`-th failure (1, 2, …): a delay in ms, or null to stop. */
|
|
53
|
+
export type ReconnectPolicy = (attempt: number) => number | null;
|
|
54
|
+
/** 1 s, doubling, at most 60 s, forever. */
|
|
55
|
+
export declare const defaultReconnect: ReconnectPolicy;
|
|
56
|
+
export interface SyncClientOptions {
|
|
57
|
+
readonly url: string;
|
|
58
|
+
/** The session and writing Principal. */
|
|
59
|
+
readonly signer: Signer;
|
|
60
|
+
/** Its X25519 key pair, to open Key Packages addressed to it. */
|
|
61
|
+
readonly agreement: AgreementKeyPair;
|
|
62
|
+
readonly storage: LfcpStorage;
|
|
63
|
+
readonly secrets: SecretStore;
|
|
64
|
+
readonly outbound: OutboundQueue;
|
|
65
|
+
/** The caller's clock, in milliseconds. */
|
|
66
|
+
readonly now: () => number;
|
|
67
|
+
readonly webSocket?: WebSocketFactory;
|
|
68
|
+
readonly reconnect?: ReconnectPolicy;
|
|
69
|
+
/** How often a LIVE Resource exchanges DATA_HAVE (§69); default 30 s. */
|
|
70
|
+
readonly antiEntropyMs?: number;
|
|
71
|
+
/**
|
|
72
|
+
* How long a request waits for its answer on a live connection before
|
|
73
|
+
* the step it belongs to is issued again (a lost request or reply, §70
|
|
74
|
+
* at-least-once): RESOURCE_OPEN, the Control round, KEY_PACKAGE_GET, the
|
|
75
|
+
* data round. A lost SNAPSHOT_GET falls back to the data round. Default 15 s.
|
|
76
|
+
*/
|
|
77
|
+
readonly requestTimeoutMs?: number;
|
|
78
|
+
/** An opaque hosting credential for AUTH (§36): server policy only. */
|
|
79
|
+
readonly credential?: Uint8Array;
|
|
80
|
+
readonly dataProfiles?: readonly string[];
|
|
81
|
+
/**
|
|
82
|
+
* Whether to publish a Snapshot of a LIVE Resource now, asked on every
|
|
83
|
+
* tick with the number of units merged since the last one; no automatic
|
|
84
|
+
* schedule otherwise (publishSnapshot can also be called directly).
|
|
85
|
+
*/
|
|
86
|
+
readonly snapshotPolicy?: (resourceId: ResourceId, unitsSinceLast: number) => boolean;
|
|
87
|
+
}
|
|
88
|
+
/** What the session reports to the application. */
|
|
89
|
+
export type SyncEvent = {
|
|
90
|
+
readonly type: "connection";
|
|
91
|
+
readonly state: ClientConnectionState;
|
|
92
|
+
readonly reason?: string;
|
|
93
|
+
} | {
|
|
94
|
+
readonly type: "resource-state";
|
|
95
|
+
readonly resourceId: ResourceId;
|
|
96
|
+
readonly state: ResourcePhase;
|
|
97
|
+
}
|
|
98
|
+
/** A received unit's outcome (merged, held, quarantined, equivocation, …). */
|
|
99
|
+
| {
|
|
100
|
+
readonly type: "unit";
|
|
101
|
+
readonly resourceId: ResourceId;
|
|
102
|
+
readonly outcome: ApplyOutcome;
|
|
103
|
+
}
|
|
104
|
+
/** Merged units a new Key Epoch excluded, and the objects that changed (G-EP7). */
|
|
105
|
+
| {
|
|
106
|
+
readonly type: "epoch-reconciled";
|
|
107
|
+
readonly resourceId: ResourceId;
|
|
108
|
+
readonly applied: EpochReconciliation;
|
|
109
|
+
readonly outbound: readonly StaleOutboundUnit[];
|
|
110
|
+
} | {
|
|
111
|
+
readonly type: "control-conflict";
|
|
112
|
+
readonly resourceId: ResourceId;
|
|
113
|
+
readonly heads: readonly ControlRecordId[];
|
|
114
|
+
} | {
|
|
115
|
+
readonly type: "key-blocked";
|
|
116
|
+
readonly resourceId: ResourceId;
|
|
117
|
+
readonly epochs: readonly DataEpoch[];
|
|
118
|
+
}
|
|
119
|
+
/** A Snapshot was loaded (§66 step 3); only units beyond its frontier are fetched. */
|
|
120
|
+
| {
|
|
121
|
+
readonly type: "snapshot-loaded";
|
|
122
|
+
readonly resourceId: ResourceId;
|
|
123
|
+
readonly snapshotId: Hash32;
|
|
124
|
+
readonly frontier: HaveVector;
|
|
125
|
+
} | {
|
|
126
|
+
readonly type: "snapshot-published";
|
|
127
|
+
readonly resourceId: ResourceId;
|
|
128
|
+
readonly snapshotId: Hash32;
|
|
129
|
+
}
|
|
130
|
+
/** Stored accepted units applied again to a profile state that lacked them (restart). */
|
|
131
|
+
| {
|
|
132
|
+
readonly type: "replayed";
|
|
133
|
+
readonly resourceId: ResourceId;
|
|
134
|
+
readonly replayed: readonly DataUnitId[];
|
|
135
|
+
readonly skipped: readonly {
|
|
136
|
+
readonly unitId: DataUnitId;
|
|
137
|
+
readonly reason: string;
|
|
138
|
+
}[];
|
|
139
|
+
/** Units quarantined because they crashed the profile engine twice by themselves. */
|
|
140
|
+
readonly crashed: readonly DataUnitId[];
|
|
141
|
+
} | {
|
|
142
|
+
readonly type: "ack";
|
|
143
|
+
readonly outcome: AckOutcome;
|
|
144
|
+
}
|
|
145
|
+
/** A NACK of an outbound object: stale, equivocation alarm, rejected, repropose, … */
|
|
146
|
+
| {
|
|
147
|
+
readonly type: "nack";
|
|
148
|
+
readonly outcome: NackOutcome;
|
|
149
|
+
}
|
|
150
|
+
/** A refusal or failure that concerns a Resource or the session (codes and reasons, never payloads). */
|
|
151
|
+
| {
|
|
152
|
+
readonly type: "error";
|
|
153
|
+
readonly resourceId?: ResourceId;
|
|
154
|
+
readonly code: string;
|
|
155
|
+
readonly message: string;
|
|
156
|
+
};
|
|
157
|
+
export declare class SyncClient {
|
|
158
|
+
#private;
|
|
159
|
+
constructor(options: SyncClientOptions);
|
|
160
|
+
/** Subscribes to session events; returns the unsubscribe function. */
|
|
161
|
+
on(listener: (event: SyncEvent) => void): () => void;
|
|
162
|
+
/** Resolves once every message received so far has been handled (tests and shutdown). */
|
|
163
|
+
idle(): Promise<void>;
|
|
164
|
+
get connectionState(): ClientConnectionState;
|
|
165
|
+
resourceState(resource: ResourceId): ResourcePhase;
|
|
166
|
+
/** Connects, and reconnects after losses (per the ReconnectPolicy) until stop(). */
|
|
167
|
+
start(): void;
|
|
168
|
+
/** Closes the connection and stops reconnecting. Local state stays usable. */
|
|
169
|
+
stop(): Promise<void>;
|
|
170
|
+
/** Registers a Resource and opens it now (or when the session is READY). */
|
|
171
|
+
open(binding: ResourceBinding): void;
|
|
172
|
+
/** RESOURCE_CLOSE (§43): no more pushes; local state stays usable. */
|
|
173
|
+
close(resource: ResourceId): void;
|
|
174
|
+
/** RESOURCE_HOST (§39): asks the server to host a Resource from its exact Genesis bytes; resolves with the durability applied. */
|
|
175
|
+
host(genesis: Uint8Array, credential?: Uint8Array): Promise<bigint>;
|
|
176
|
+
/** Sends what the outbound queue has due for every Resource past KEY_SYNC (e.g. after a local write). */
|
|
177
|
+
flush(): void;
|
|
178
|
+
/**
|
|
179
|
+
* The periodic work, on the caller's clock: heartbeat (§38), reconnect,
|
|
180
|
+
* anti-entropy DATA_HAVE for LIVE Resources (§69), Key Package retries,
|
|
181
|
+
* due outbound retries and debounced checkpoints.
|
|
182
|
+
*/
|
|
183
|
+
tick(now: number): void;
|
|
184
|
+
/**
|
|
185
|
+
* Publishes a Snapshot of the Resource's current state (§29): its frontier
|
|
186
|
+
* is every merged unit and every stored Snapshot's frontier, so a client
|
|
187
|
+
* that loads it never skips content the state does not hold. Queued and
|
|
188
|
+
* sent like any object; returns its ID.
|
|
189
|
+
*/
|
|
190
|
+
publishSnapshot(resource: ResourceId): Promise<Hash32>;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* A thin timer driver for SyncClient: calls tick(now) every `intervalMs`
|
|
194
|
+
* with the given clock and timer functions (setInterval/clearInterval in
|
|
195
|
+
* production). Returns the stop function. The core never sets timers
|
|
196
|
+
* itself; this is the one explicit place that does.
|
|
197
|
+
*/
|
|
198
|
+
export declare function startSyncDriver(client: SyncClient, timers: {
|
|
199
|
+
readonly setInterval: (fn: () => void, ms: number) => unknown;
|
|
200
|
+
readonly clearInterval: (handle: unknown) => void;
|
|
201
|
+
readonly now: () => number;
|
|
202
|
+
}, intervalMs?: number): () => void;
|
|
203
|
+
//# sourceMappingURL=sync-client.d.ts.map
|