@loro-dev/streams-crdt 0.14.1 → 0.15.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/README.md +461 -147
- package/dist/flock.d.ts +2 -2
- package/dist/flock.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/loro.d.ts +2 -2
- package/dist/loro.js +2 -2
- package/dist/{stream-id-CEp4zEQJ.js → stream-id-BygPTNvQ.js} +284 -229
- package/dist/stream-id-BygPTNvQ.js.map +1 -0
- package/dist/{stream-id-BuXMoCvo.d.ts → stream-id-CVCOLgSp.d.ts} +123 -52
- package/package.json +1 -1
- package/dist/stream-id-CEp4zEQJ.js.map +0 -1
|
@@ -379,8 +379,7 @@ type TransportError =
|
|
|
379
379
|
} | {
|
|
380
380
|
readonly code: "payload_protection_error";
|
|
381
381
|
readonly retryable: false;
|
|
382
|
-
readonly reason:
|
|
383
|
-
readonly keyId?: string;
|
|
382
|
+
readonly reason: PayloadProtectionFailureReason;
|
|
384
383
|
readonly message: string;
|
|
385
384
|
};
|
|
386
385
|
interface WriteOnlyAppendResult<TVersion extends JsonObject> {
|
|
@@ -523,34 +522,74 @@ interface SnapshotCodec {
|
|
|
523
522
|
readonly compress: SnapshotTransformHook;
|
|
524
523
|
readonly decompress: SnapshotTransformHook;
|
|
525
524
|
}
|
|
525
|
+
type PayloadProtectionFailureReason = "plaintext_forbidden" | "missing_read_key" | "decrypt_failed" | "invalid_envelope" | "wrong_payload_kind" | "encrypt_failed";
|
|
526
526
|
type PayloadProtectionReadPolicy = "encrypted-only" | "allow-plaintext";
|
|
527
527
|
type PayloadProtectionWritePolicy = "encrypt" | "plaintext";
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
readonly
|
|
534
|
-
readonly
|
|
528
|
+
/** Durable CRDT payload class authenticated by the protection boundary. */
|
|
529
|
+
type PayloadProtectionKind = "update_batch" | "snapshot";
|
|
530
|
+
/** Stable envelope context shared by provider-backed seal/open calls. */
|
|
531
|
+
interface PayloadProtectionContext {
|
|
532
|
+
readonly protocol: "loro-streams-crdt-payload-protection";
|
|
533
|
+
readonly version: 2;
|
|
534
|
+
readonly kind: PayloadProtectionKind;
|
|
535
535
|
}
|
|
536
|
-
|
|
537
|
-
|
|
536
|
+
interface PayloadProtectionSealInput {
|
|
537
|
+
readonly plaintext: Uint8Array;
|
|
538
|
+
readonly context: PayloadProtectionContext;
|
|
538
539
|
/**
|
|
539
|
-
*
|
|
540
|
+
* Returns the exact AAD for the opaque authenticated header the provider
|
|
541
|
+
* will return.
|
|
540
542
|
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
543
|
+
* Providers MUST pass their final header to this function exactly once
|
|
544
|
+
* before sealing and MUST authenticate the returned bytes. A provider may
|
|
545
|
+
* frame and append caller-owned room/application AAD before passing the
|
|
546
|
+
* combined value to its AEAD. streams-crdt deliberately does not know or
|
|
547
|
+
* derive that application identity.
|
|
544
548
|
*/
|
|
545
|
-
readonly
|
|
546
|
-
|
|
547
|
-
|
|
549
|
+
readonly additionalData: (header: Uint8Array) => Uint8Array;
|
|
550
|
+
}
|
|
551
|
+
interface PayloadProtectionSealResult {
|
|
552
|
+
/** Opaque authenticated provider header; limited to 512 bytes. */
|
|
553
|
+
readonly header: Uint8Array;
|
|
548
554
|
/**
|
|
549
|
-
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
555
|
+
* Provider-defined sealed bytes. Nonces and authentication tags stay opaque
|
|
556
|
+
* to streams-crdt, so providers may use formats such as
|
|
557
|
+
* `nonce[24] || XChaCha20-Poly1305 ciphertext+tag`.
|
|
558
|
+
*/
|
|
559
|
+
readonly sealed: Uint8Array;
|
|
560
|
+
}
|
|
561
|
+
interface PayloadProtectionOpenInput {
|
|
562
|
+
readonly sealed: Uint8Array;
|
|
563
|
+
/** Opaque authenticated provider header returned by the writer. */
|
|
564
|
+
readonly header: Uint8Array;
|
|
565
|
+
readonly context: PayloadProtectionContext;
|
|
566
|
+
/** Exact envelope AAD that the provider must authenticate. */
|
|
567
|
+
readonly additionalData: Uint8Array;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Caller-owned authenticated payload codec.
|
|
571
|
+
*
|
|
572
|
+
* streams-crdt owns only the versioned envelope, its fixed envelope AAD,
|
|
573
|
+
* placement at the framing boundary, and fail-closed policies. The provider
|
|
574
|
+
* owns the audited AEAD primitive, nonce generation, application/room AAD,
|
|
575
|
+
* current write-key selection, and historical key lookup.
|
|
576
|
+
*/
|
|
577
|
+
interface PayloadProtectionProvider {
|
|
578
|
+
/**
|
|
579
|
+
* Maximum `header.byteLength + sealed.byteLength - plaintext.byteLength`
|
|
580
|
+
* this provider can return. This lets streams-crdt batch without performing
|
|
581
|
+
* a speculative seal or consuming a nonce. The runtime rejects a seal that
|
|
582
|
+
* exceeds the declared bound.
|
|
552
583
|
*/
|
|
553
|
-
readonly
|
|
584
|
+
readonly maxSealOverheadBytes: number;
|
|
585
|
+
seal(input: PayloadProtectionSealInput): MaybePromise<PayloadProtectionSealResult>;
|
|
586
|
+
open(input: PayloadProtectionOpenInput): MaybePromise<Uint8Array>;
|
|
587
|
+
}
|
|
588
|
+
/** Scope-free provider config suitable for direct repo/room forwarding. */
|
|
589
|
+
interface PayloadProtectionProviderConfig {
|
|
590
|
+
readonly provider: PayloadProtectionProvider;
|
|
591
|
+
readonly readPolicy?: PayloadProtectionReadPolicy;
|
|
592
|
+
readonly writePolicy?: PayloadProtectionWritePolicy;
|
|
554
593
|
}
|
|
555
594
|
interface PayloadProtectionOptions {
|
|
556
595
|
/**
|
|
@@ -562,14 +601,20 @@ interface PayloadProtectionOptions {
|
|
|
562
601
|
* Local write policy. Defaults to "encrypt" when E2EE is provided.
|
|
563
602
|
*/
|
|
564
603
|
readonly writePolicy?: PayloadProtectionWritePolicy;
|
|
565
|
-
|
|
604
|
+
/**
|
|
605
|
+
* Provider for protected writes and reads.
|
|
606
|
+
*
|
|
607
|
+
* The provider owns the opaque header used to resolve historical keys. A
|
|
608
|
+
* write-key transition is an application session boundary: drain pending
|
|
609
|
+
* appends, close this transport, then construct a replacement with the new
|
|
610
|
+
* finalized write epoch. streams-crdt does not invent that rekey barrier.
|
|
611
|
+
*/
|
|
612
|
+
readonly provider?: PayloadProtectionProvider;
|
|
566
613
|
}
|
|
567
614
|
type E2eeReadPolicy = PayloadProtectionReadPolicy;
|
|
568
615
|
type E2eeWritePolicy = PayloadProtectionWritePolicy;
|
|
569
|
-
type
|
|
570
|
-
type
|
|
571
|
-
type E2eeKeyProvider = PayloadProtectionKeyProvider;
|
|
572
|
-
type E2eeEncryptionOptions = PayloadProtectionEncryptionOptions;
|
|
616
|
+
type E2eeProvider = PayloadProtectionProvider;
|
|
617
|
+
type E2eeProviderConfig = PayloadProtectionProviderConfig;
|
|
573
618
|
type E2eeOptions = PayloadProtectionOptions;
|
|
574
619
|
/**
|
|
575
620
|
* Active live subscription returned by `join()`.
|
|
@@ -798,7 +843,11 @@ interface EphemeralStreamCrdtOptions {
|
|
|
798
843
|
* Each instance is bound to exactly one stream URL and one local CRDT adapter.
|
|
799
844
|
*/
|
|
800
845
|
interface StreamsCrdtOptions<TVersion extends JsonObject> {
|
|
801
|
-
/**
|
|
846
|
+
/**
|
|
847
|
+
* Full, opaque Durable Streams URL used for HTTP requests and cursor keys.
|
|
848
|
+
* Payload-protection providers own any separate application/room identity
|
|
849
|
+
* they authenticate; streams-crdt never parses this URL to derive AAD.
|
|
850
|
+
*/
|
|
802
851
|
readonly streamUrl: string;
|
|
803
852
|
/** CRDT adapter for the local document or replica. */
|
|
804
853
|
readonly adapter: CrdtAdapter<TVersion>;
|
|
@@ -861,11 +910,15 @@ interface StreamsCrdtOptions<TVersion extends JsonObject> {
|
|
|
861
910
|
*/
|
|
862
911
|
readonly e2ee?: E2eeOptions;
|
|
863
912
|
/**
|
|
864
|
-
*
|
|
913
|
+
* Fail construction when `e2ee` is absent.
|
|
914
|
+
* Repo integrations can set this after enabling protection globally so a
|
|
915
|
+
* missing per-room config never becomes an implicit plaintext room.
|
|
865
916
|
*
|
|
866
|
-
*
|
|
917
|
+
* This is a missing-config guard, not a policy override. An explicitly
|
|
918
|
+
* supplied config may still opt into `allow-plaintext` / `plaintext`, for
|
|
919
|
+
* example for the public write-only mode.
|
|
867
920
|
*/
|
|
868
|
-
readonly
|
|
921
|
+
readonly payloadProtectionRequired?: boolean;
|
|
869
922
|
readonly snapshotUpload?: SnapshotUploadOptions;
|
|
870
923
|
/** Optional origin pools for routing transport requests. */
|
|
871
924
|
readonly shardUrls?: StreamsCrdtShardUrlsOptions;
|
|
@@ -1046,6 +1099,35 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
|
|
|
1046
1099
|
private disposeJoinState;
|
|
1047
1100
|
private runInitialSyncWithDeadline;
|
|
1048
1101
|
private startJoin;
|
|
1102
|
+
/**
|
|
1103
|
+
* Join-time salvage for a stream whose head cannot be fully applied
|
|
1104
|
+
* (apply_incomplete at `up_to_date`, even after bootstrap): before the
|
|
1105
|
+
* join surfaces the error, durably append everything this replica holds
|
|
1106
|
+
* that the server does not.
|
|
1107
|
+
*
|
|
1108
|
+
* Why this exists: the dual-author wedge is usually two-sided — the stream
|
|
1109
|
+
* is missing some author's ops, and a replica that HOLDS those ops (e.g.
|
|
1110
|
+
* received out-of-band over a local data plane) may itself be unable to
|
|
1111
|
+
* complete the join because of a second gap. The regular join-time export
|
|
1112
|
+
* runs only AFTER a successful initial sync, so without this salvage such
|
|
1113
|
+
* a replica can never publish the healing ops, and its own backlog from a
|
|
1114
|
+
* previous process run stays local for as long as the stream stays
|
|
1115
|
+
* poisoned.
|
|
1116
|
+
*
|
|
1117
|
+
* `error.appliedVersion` advances only from success spans, so
|
|
1118
|
+
* `exportUpdates(appliedVersion)` is exactly "ops this replica holds that
|
|
1119
|
+
* the server does not" — the failed initial sync already imported
|
|
1120
|
+
* everything the server DOES have, so the delta cannot re-append
|
|
1121
|
+
* server-known data. Cursor safety is untouched: the append is write-only
|
|
1122
|
+
* style (producer-acked, no read catch-up, no durable-cursor save), and
|
|
1123
|
+
* the retried initial sync re-derives its cursor from the server.
|
|
1124
|
+
*
|
|
1125
|
+
* Returns `true` when anything was durably appended; the caller then
|
|
1126
|
+
* retries the initial sync once. Best-effort: a salvage failure logs and
|
|
1127
|
+
* returns `false` so the original apply-incomplete error is what callers
|
|
1128
|
+
* observe.
|
|
1129
|
+
*/
|
|
1130
|
+
private salvageLocalBacklogOnIncompleteInitialSync;
|
|
1049
1131
|
private syncOnce;
|
|
1050
1132
|
private performInitialJoinSync;
|
|
1051
1133
|
private createInitialCursor;
|
|
@@ -1160,6 +1242,12 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
|
|
|
1160
1242
|
* `"error"` write status (with `lastWriteError` set) and then retries via
|
|
1161
1243
|
* this flush's own backoff timer (`maybeScheduleWriteRetry`) — it does not
|
|
1162
1244
|
* depend on a future local op to re-trigger the flush.
|
|
1245
|
+
*
|
|
1246
|
+
* While the cursor is incomplete (unresolved remote spans), the flush
|
|
1247
|
+
* degrades to an append-only mode instead of pausing: batches still reach
|
|
1248
|
+
* the server, but read progress, durable-cursor saves, and snapshot
|
|
1249
|
+
* scheduling stay untouched. See the inline comment in the incomplete
|
|
1250
|
+
* branch and specs/loro-pending.md "Local Append Rules".
|
|
1163
1251
|
*/
|
|
1164
1252
|
private flushPendingLocal;
|
|
1165
1253
|
/**
|
|
@@ -1265,33 +1353,16 @@ declare class LocalAppendFailedError extends Error {
|
|
|
1265
1353
|
}
|
|
1266
1354
|
//#endregion
|
|
1267
1355
|
//#region src/payload-protection.d.ts
|
|
1268
|
-
type PayloadProtectionFailureReason = "plaintext_forbidden" | "missing_read_key" | "decrypt_failed" | "invalid_envelope" | "wrong_payload_kind" | "encrypt_failed";
|
|
1269
1356
|
declare class PayloadProtectionError extends Error {
|
|
1270
1357
|
readonly reason: PayloadProtectionFailureReason;
|
|
1271
|
-
|
|
1272
|
-
constructor(reason: PayloadProtectionFailureReason, message: string, options?: {
|
|
1273
|
-
readonly keyId?: string;
|
|
1274
|
-
readonly cause?: unknown;
|
|
1275
|
-
});
|
|
1358
|
+
constructor(reason: PayloadProtectionFailureReason, message?: string);
|
|
1276
1359
|
}
|
|
1277
1360
|
//#endregion
|
|
1278
1361
|
//#region src/stream-id.d.ts
|
|
1279
1362
|
/**
|
|
1280
|
-
* Validates a stream id
|
|
1363
|
+
* Validates a Durable Streams stream id.
|
|
1281
1364
|
*/
|
|
1282
1365
|
declare function isValidRillId(id: string): boolean;
|
|
1283
|
-
/**
|
|
1284
|
-
* Validates a bucket id accepted by `createStreamUrl()`.
|
|
1285
|
-
*/
|
|
1286
|
-
declare function isValidBucketId(id: string): boolean;
|
|
1287
|
-
/**
|
|
1288
|
-
* Builds a Durable Streams HTTP URL for one `(bucketId, streamId)` pair.
|
|
1289
|
-
*/
|
|
1290
|
-
declare function createStreamUrl(input: {
|
|
1291
|
-
bucketId: string;
|
|
1292
|
-
streamId: string;
|
|
1293
|
-
baseUrl?: string;
|
|
1294
|
-
}): string;
|
|
1295
1366
|
//#endregion
|
|
1296
|
-
export { WriteOnlyAppendResult as $,
|
|
1297
|
-
//# sourceMappingURL=stream-id-
|
|
1367
|
+
export { WriteOnlyAppendResult as $, PayloadProtectionProviderConfig as A, StreamsAuthProvider as B, LocalSyncStats as C, PayloadProtectionOpenInput as D, PayloadProtectionKind as E, Result as F, TransportCreateStreamSuccess as G, StreamsCrdtShardUrlsOptions as H, SnapshotCodec as I, TransportJoinParams as J, TransportDeleteStreamSuccess as K, SnapshotTransformHook as L, PayloadProtectionSealInput as M, PayloadProtectionSealResult as N, PayloadProtectionOptions as O, PayloadProtectionWritePolicy as P, TransportSyncSuccess as Q, SnapshotUploadOptions as R, JsonValue as S, PayloadProtectionFailureReason as T, TransportCatchupParams as U, StreamsCrdtOptions as V, TransportCatchupSuccess as W, TransportSnapshotUploadSuccess as X, TransportRoomStatus as Y, TransportSubscription as Z, EphemeralStreamCrdtOptions as _, StreamsCrdt as a, RemoteCursor as at, IsolatedCrdtAdapter as b, CrdtApplyReturn as c, createInitialRemoteCursor as ct, E2eeOptions as d, BeforeRemoteCursorSaveContext as et, E2eeProvider as f, EphemeralStreamAdaptor as g, E2eeWritePolicy as h, EphemeralStreamCrdt as i, IndexedDbRemoteCursorStoreOptions as it, PayloadProtectionReadPolicy as j, PayloadProtectionProvider as k, CrdtUnresolvedSpan as l, E2eeReadPolicy as m, PayloadProtectionError as n, InMemoryRemoteCursorStore as nt, CrdtAdapter as o, RemoteCursorSaveSource as ot, E2eeProviderConfig as p, TransportError as q, LocalAppendFailedError as r, IndexedDbRemoteCursorStore as rt, CrdtApplyOutcome as s, RemoteCursorStore as st, isValidRillId as t, BeforeRemoteCursorSaveHook as tt, CrdtUpdateBatch as u, EphemeralStreamJoinParams as v, PayloadProtectionContext as w, JsonObject as x, EphemeralStreamSubscription as y, StreamsAuthContext as z };
|
|
1368
|
+
//# sourceMappingURL=stream-id-CVCOLgSp.d.ts.map
|
package/package.json
CHANGED