@loro-dev/streams-crdt 0.8.2 → 0.8.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 +102 -5
- package/dist/flock.d.ts +1 -1
- package/dist/flock.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/loro.d.ts +1 -1
- package/dist/loro.js +1 -1
- package/dist/{stream-id-BjmGvObf.js → stream-id-BBE3LRAQ.js} +133 -19
- package/dist/stream-id-BBE3LRAQ.js.map +1 -0
- package/dist/{stream-id-Bi5rAZfP.d.ts → stream-id-JuJ1JqCX.d.ts} +122 -18
- package/package.json +4 -4
- package/dist/stream-id-BjmGvObf.js.map +0 -1
package/README.md
CHANGED
|
@@ -37,19 +37,28 @@ if (!result.ok) {
|
|
|
37
37
|
// result.error is a TransportError with a discriminated `code` field.
|
|
38
38
|
switch (result.error.code) {
|
|
39
39
|
case "stream_not_found":
|
|
40
|
-
//
|
|
40
|
+
// 404 — the stream doesn't exist yet. Create it first.
|
|
41
41
|
break;
|
|
42
42
|
case "auth_failed":
|
|
43
|
-
//
|
|
43
|
+
// 401/403 from the server — refresh credentials and retry.
|
|
44
44
|
break;
|
|
45
|
-
case "
|
|
46
|
-
|
|
47
|
-
//
|
|
45
|
+
case "auth_provider_error":
|
|
46
|
+
// Your auth callback threw. The live loop will retry automatically.
|
|
47
|
+
// For permanent logout, return undefined from the callback instead.
|
|
48
|
+
break;
|
|
49
|
+
case "server_error":
|
|
50
|
+
// 5xx — transient backend failure. The live loop retries automatically;
|
|
51
|
+
// for one-shot calls (sync/createStream/etc.) you decide whether to
|
|
52
|
+
// retry. `retryAfterMs` honors the server's Retry-After hint.
|
|
48
53
|
break;
|
|
49
54
|
case "network_error":
|
|
50
55
|
case "timeout":
|
|
51
56
|
// Transient — safe to retry.
|
|
52
57
|
break;
|
|
58
|
+
case "protocol_error":
|
|
59
|
+
case "internal_error":
|
|
60
|
+
// Non-retryable: bad server response or local runtime/adapter failure.
|
|
61
|
+
break;
|
|
53
62
|
default:
|
|
54
63
|
console.error("transport error:", result.error);
|
|
55
64
|
}
|
|
@@ -68,11 +77,73 @@ if (!result.ok && result.error.retryable) {
|
|
|
68
77
|
}
|
|
69
78
|
```
|
|
70
79
|
|
|
80
|
+
#### Surfacing errors to end users
|
|
81
|
+
|
|
82
|
+
HTTP-derived variants (`server_error`, `auth_failed`, `protocol_error`,
|
|
83
|
+
`stream_not_found`, `gone`) carry the response details so apps can show
|
|
84
|
+
actionable messages without parsing the `message` string:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const err = result.error;
|
|
88
|
+
if (err.code === "server_error") {
|
|
89
|
+
toast.error(`Server error ${err.status}${err.requestId ? ` (req: ${err.requestId})` : ""}`);
|
|
90
|
+
console.error("backend body:", err.body);
|
|
91
|
+
// err.retryAfterMs may suggest a backoff if the server set Retry-After.
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Code | `status` | `message` | `body` | Other |
|
|
96
|
+
| ---------------------- | -------- | --------- | ------ | ------------------------------ |
|
|
97
|
+
| `stream_not_found` | 404 | yes | yes | — |
|
|
98
|
+
| `auth_failed` | 401/403 | yes | yes | — |
|
|
99
|
+
| `protocol_error` | yes | yes | yes | — |
|
|
100
|
+
| `gone` | 410 | yes | yes | — |
|
|
101
|
+
| `server_error` | 5xx | yes | yes | `requestId?`, `retryAfterMs?` |
|
|
102
|
+
| `timeout` | — | yes | — | `phase: "connect" \| "poll"` |
|
|
103
|
+
| `network_error` | — | yes | — | — |
|
|
104
|
+
| `auth_provider_error` | — | yes | — | `cause?` (original thrown value) |
|
|
105
|
+
| `internal_error` | — | yes | — | — |
|
|
106
|
+
| `apply_incomplete` | — | yes | — | `reason: "loro_pending_updates"` |
|
|
107
|
+
|
|
108
|
+
#### Operation-level retries vs. live-mode retries
|
|
109
|
+
|
|
110
|
+
- `sync()`, `createStream()`, `deleteStream()`, `appendWriteOnly()` return
|
|
111
|
+
after a **single** attempt. The application is responsible for retrying
|
|
112
|
+
`retryable` errors.
|
|
113
|
+
- After `join()` succeeds, the live read/write loops automatically reconnect
|
|
114
|
+
on every `retryable: true` error using exponential backoff
|
|
115
|
+
(`[0, 0.5s, 1s, 2s, 4s, 8s, 15s, 30s]` with ±20% jitter; cap 30s). When a
|
|
116
|
+
5xx response includes `Retry-After`, the runtime waits at least that long.
|
|
117
|
+
Non-retryable errors transition the subscription to `status: "error"`;
|
|
118
|
+
call `transport.rejoin()` after fixing the cause (e.g., refreshing the auth
|
|
119
|
+
token) to reset retry state and reconnect.
|
|
120
|
+
|
|
71
121
|
> **Why `Result` instead of `throw`?** Transport failures (network errors,
|
|
72
122
|
> auth expiry, missing streams) are expected at runtime, not exceptional.
|
|
73
123
|
> Wrapping them in `Result` makes the error path explicit in the type system
|
|
74
124
|
> so callers cannot accidentally ignore failures with a missing `try/catch`.
|
|
75
125
|
|
|
126
|
+
#### Migration notes
|
|
127
|
+
|
|
128
|
+
For most callers nothing changes — `if (!result.ok) { handle(result.error); }`
|
|
129
|
+
keeps working, and `result.error.retryable` is still the right gate for
|
|
130
|
+
"safe to retry?".
|
|
131
|
+
|
|
132
|
+
Only adjust if any of these apply:
|
|
133
|
+
|
|
134
|
+
- **Your auth callback throws on permanent failure** (e.g., user is logged
|
|
135
|
+
out). Throwing is now treated as a transient error and retried in live
|
|
136
|
+
mode. **Return `undefined` instead** so the server returns 401 and you get
|
|
137
|
+
the terminal `auth_failed`. See "What if the auth callback throws?" below.
|
|
138
|
+
- **You construct `TransportError` literals** (e.g., in tests or fixtures).
|
|
139
|
+
`stream_not_found` now requires `status: 404` + `message`; `gone` now
|
|
140
|
+
requires `status: 410`. TypeScript will flag these.
|
|
141
|
+
- **You exhaustively switch on `code` with a `: never` default.** Add a case
|
|
142
|
+
for `auth_provider_error` (and optionally `server_error` if you handled
|
|
143
|
+
5xx via `unknown`/`internal_error` before).
|
|
144
|
+
- **You used `"message" in error` to defensively narrow.** All variants now
|
|
145
|
+
carry `message`; the guard is no longer needed.
|
|
146
|
+
|
|
76
147
|
## Quick Start (Loro)
|
|
77
148
|
|
|
78
149
|
```ts
|
|
@@ -324,6 +395,32 @@ const transport = new StreamsCrdt({
|
|
|
324
395
|
If your callback already returns a fresh token every time, you can ignore the
|
|
325
396
|
`context` argument.
|
|
326
397
|
|
|
398
|
+
### What if the auth callback throws?
|
|
399
|
+
|
|
400
|
+
Throwing from the callback is treated as a **transient** failure. The runtime
|
|
401
|
+
wraps the error as `auth_provider_error` (`retryable: true`) so a hiccupping
|
|
402
|
+
auth backend doesn't terminate the live read/write loops — they retry under
|
|
403
|
+
the same exponential backoff used for `server_error` and `network_error`.
|
|
404
|
+
|
|
405
|
+
For the "user is permanently logged out" case, **return `undefined`** (or
|
|
406
|
+
`null`/empty string) instead of throwing. The request goes out without an
|
|
407
|
+
`Authorization` header, the server returns 401, and you receive the
|
|
408
|
+
non-retryable `auth_failed` instead — which the application typically routes
|
|
409
|
+
to a sign-in page.
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
auth: async () => {
|
|
413
|
+
try {
|
|
414
|
+
return await fetchTokenFromAuthBackend(); // network → throw → retry
|
|
415
|
+
} catch (e) {
|
|
416
|
+
if (isPermanentLogoutError(e)) {
|
|
417
|
+
return undefined; // → server 401 → auth_failed (terminal)
|
|
418
|
+
}
|
|
419
|
+
throw e; // → auth_provider_error (retryable)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
327
424
|
## End-to-End Encryption (E2EE)
|
|
328
425
|
|
|
329
426
|
`e2ee` enables end-to-end encryption for CRDT update batches and snapshots
|
package/dist/flock.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as IndexedDbRemoteCursorStoreOptions, A as PayloadProtectionReadPolicy, B as TransportCreateStreamSuccess, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportSnapshotUploadSuccess, H as TransportError, I as SnapshotUploadOptions, J as WriteOnlyAppendResult, K as TransportSubscription, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as IndexedDbRemoteCursorStore, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportJoinParams, V as TransportDeleteStreamSuccess, W as TransportRoomStatus, X as BeforeRemoteCursorSaveHook, Y as BeforeRemoteCursorSaveContext, Z as InMemoryRemoteCursorStore, _ as E2eeScope, a as EphemeralStreamCrdt, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as RemoteCursor, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as RemoteCursorStore, o as StreamsCrdt, p as E2eeKey, q as TransportSyncSuccess, r as isValidRillId, rt as createInitialRemoteCursor, s as CrdtAdapter, t as createStreamUrl, tt as RemoteCursorSaveSource, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-
|
|
1
|
+
import { $ as IndexedDbRemoteCursorStoreOptions, A as PayloadProtectionReadPolicy, B as TransportCreateStreamSuccess, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportSnapshotUploadSuccess, H as TransportError, I as SnapshotUploadOptions, J as WriteOnlyAppendResult, K as TransportSubscription, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as IndexedDbRemoteCursorStore, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportJoinParams, V as TransportDeleteStreamSuccess, W as TransportRoomStatus, X as BeforeRemoteCursorSaveHook, Y as BeforeRemoteCursorSaveContext, Z as InMemoryRemoteCursorStore, _ as E2eeScope, a as EphemeralStreamCrdt, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as RemoteCursor, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as RemoteCursorStore, o as StreamsCrdt, p as E2eeKey, q as TransportSyncSuccess, r as isValidRillId, rt as createInitialRemoteCursor, s as CrdtAdapter, t as createStreamUrl, tt as RemoteCursorSaveSource, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-JuJ1JqCX.js";
|
|
2
2
|
import { ExportBundle, Flock, VersionVector, VersionVector as VersionVector$1 } from "@loro-dev/flock-wasm";
|
|
3
3
|
|
|
4
4
|
//#region src/flock.d.ts
|
package/dist/flock.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as InMemoryRemoteCursorStore, c as EphemeralStreamCrdt, i as StreamsCrdt, l as PayloadProtectionError, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-
|
|
1
|
+
import { a as InMemoryRemoteCursorStore, c as EphemeralStreamCrdt, i as StreamsCrdt, l as PayloadProtectionError, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-BBE3LRAQ.js";
|
|
2
2
|
import { Flock } from "@loro-dev/flock-wasm";
|
|
3
3
|
//#region src/flock.ts
|
|
4
4
|
function isAfter(entry, current) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $ as IndexedDbRemoteCursorStoreOptions, A as PayloadProtectionReadPolicy, B as TransportCreateStreamSuccess, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportSnapshotUploadSuccess, H as TransportError, I as SnapshotUploadOptions, J as WriteOnlyAppendResult, K as TransportSubscription, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as IndexedDbRemoteCursorStore, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportJoinParams, V as TransportDeleteStreamSuccess, W as TransportRoomStatus, X as BeforeRemoteCursorSaveHook, Y as BeforeRemoteCursorSaveContext, Z as InMemoryRemoteCursorStore, _ as E2eeScope, a as EphemeralStreamCrdt, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as RemoteCursor, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as RemoteCursorStore, o as StreamsCrdt, p as E2eeKey, q as TransportSyncSuccess, r as isValidRillId, rt as createInitialRemoteCursor, s as CrdtAdapter, t as createStreamUrl, tt as RemoteCursorSaveSource, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-
|
|
1
|
+
import { $ as IndexedDbRemoteCursorStoreOptions, A as PayloadProtectionReadPolicy, B as TransportCreateStreamSuccess, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportSnapshotUploadSuccess, H as TransportError, I as SnapshotUploadOptions, J as WriteOnlyAppendResult, K as TransportSubscription, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as IndexedDbRemoteCursorStore, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportJoinParams, V as TransportDeleteStreamSuccess, W as TransportRoomStatus, X as BeforeRemoteCursorSaveHook, Y as BeforeRemoteCursorSaveContext, Z as InMemoryRemoteCursorStore, _ as E2eeScope, a as EphemeralStreamCrdt, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as RemoteCursor, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as RemoteCursorStore, o as StreamsCrdt, p as E2eeKey, q as TransportSyncSuccess, r as isValidRillId, rt as createInitialRemoteCursor, s as CrdtAdapter, t as createStreamUrl, tt as RemoteCursorSaveSource, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-JuJ1JqCX.js";
|
|
2
2
|
export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtApplyOutcome, CrdtApplyReturn, CrdtUnresolvedSpan, CrdtUpdateBatch, E2eeEncryptionOptions, PayloadProtectionError as E2eeError, PayloadProtectionError, E2eeKey, E2eeKeyProvider, E2eeOptions, E2eeReadPolicy, E2eeScope, E2eeWritePolicy, EphemeralStreamAdaptor, EphemeralStreamCrdt, EphemeralStreamCrdtOptions, EphemeralStreamJoinParams, EphemeralStreamSubscription, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, IsolatedCrdtAdapter, JsonObject, JsonValue, PayloadProtectionEncryptionOptions, PayloadProtectionKey, PayloadProtectionKeyProvider, PayloadProtectionOptions, PayloadProtectionReadPolicy, PayloadProtectionScope, PayloadProtectionWritePolicy, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotCodec, SnapshotTransformHook, SnapshotUploadOptions, StreamsAuthContext, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSnapshotUploadSuccess, TransportSubscription, TransportSyncSuccess, WriteOnlyAppendResult, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as InMemoryRemoteCursorStore, c as EphemeralStreamCrdt, i as StreamsCrdt, l as PayloadProtectionError, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-
|
|
1
|
+
import { a as InMemoryRemoteCursorStore, c as EphemeralStreamCrdt, i as StreamsCrdt, l as PayloadProtectionError, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-BBE3LRAQ.js";
|
|
2
2
|
export { PayloadProtectionError as E2eeError, PayloadProtectionError, EphemeralStreamCrdt, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, StreamsCrdt, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
package/dist/loro.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as IndexedDbRemoteCursorStoreOptions, A as PayloadProtectionReadPolicy, B as TransportCreateStreamSuccess, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportSnapshotUploadSuccess, H as TransportError, I as SnapshotUploadOptions, J as WriteOnlyAppendResult, K as TransportSubscription, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as IndexedDbRemoteCursorStore, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportJoinParams, V as TransportDeleteStreamSuccess, W as TransportRoomStatus, X as BeforeRemoteCursorSaveHook, Y as BeforeRemoteCursorSaveContext, Z as InMemoryRemoteCursorStore, _ as E2eeScope, a as EphemeralStreamCrdt, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as RemoteCursor, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as RemoteCursorStore, o as StreamsCrdt, p as E2eeKey, q as TransportSyncSuccess, r as isValidRillId, rt as createInitialRemoteCursor, s as CrdtAdapter, t as createStreamUrl, tt as RemoteCursorSaveSource, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-
|
|
1
|
+
import { $ as IndexedDbRemoteCursorStoreOptions, A as PayloadProtectionReadPolicy, B as TransportCreateStreamSuccess, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportSnapshotUploadSuccess, H as TransportError, I as SnapshotUploadOptions, J as WriteOnlyAppendResult, K as TransportSubscription, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as IndexedDbRemoteCursorStore, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportJoinParams, V as TransportDeleteStreamSuccess, W as TransportRoomStatus, X as BeforeRemoteCursorSaveHook, Y as BeforeRemoteCursorSaveContext, Z as InMemoryRemoteCursorStore, _ as E2eeScope, a as EphemeralStreamCrdt, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as RemoteCursor, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as RemoteCursorStore, o as StreamsCrdt, p as E2eeKey, q as TransportSyncSuccess, r as isValidRillId, rt as createInitialRemoteCursor, s as CrdtAdapter, t as createStreamUrl, tt as RemoteCursorSaveSource, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-JuJ1JqCX.js";
|
|
2
2
|
import { LoroDoc } from "loro-crdt";
|
|
3
3
|
|
|
4
4
|
//#region src/loro.d.ts
|
package/dist/loro.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as InMemoryRemoteCursorStore, c as EphemeralStreamCrdt, i as StreamsCrdt, l as PayloadProtectionError, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-
|
|
1
|
+
import { a as InMemoryRemoteCursorStore, c as EphemeralStreamCrdt, i as StreamsCrdt, l as PayloadProtectionError, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-BBE3LRAQ.js";
|
|
2
2
|
import { LoroDoc, VersionVector, decodeImportBlobMeta } from "loro-crdt";
|
|
3
3
|
//#region src/loro.ts
|
|
4
4
|
function batchImportUpdates(doc, updates) {
|
|
@@ -129,7 +129,8 @@ const DEFAULT_RECONNECT_CONFIG = {
|
|
|
129
129
|
2e3,
|
|
130
130
|
4e3,
|
|
131
131
|
8e3,
|
|
132
|
-
15e3
|
|
132
|
+
15e3,
|
|
133
|
+
3e4
|
|
133
134
|
],
|
|
134
135
|
jitterFraction: .2,
|
|
135
136
|
maxAttempts: Infinity,
|
|
@@ -149,11 +150,15 @@ function applyJitter(baseMs, fraction) {
|
|
|
149
150
|
/**
|
|
150
151
|
* Compute the retry delay for a given attempt number.
|
|
151
152
|
* Uses the delays table from config, clamping at the last entry.
|
|
153
|
+
*
|
|
154
|
+
* If `minDelayMs` is provided (e.g. from a Retry-After hint), the result is
|
|
155
|
+
* clamped to be at least that value before jitter, so honoring the server's
|
|
156
|
+
* backoff request never falls below what we'd already wait.
|
|
152
157
|
*/
|
|
153
|
-
function computeRetryDelay(attempt, config = DEFAULT_RECONNECT_CONFIG) {
|
|
158
|
+
function computeRetryDelay(attempt, config = DEFAULT_RECONNECT_CONFIG, minDelayMs) {
|
|
154
159
|
const { delays, jitterFraction } = config;
|
|
155
|
-
const
|
|
156
|
-
return applyJitter(
|
|
160
|
+
const scheduled = delays[Math.min(attempt, delays.length - 1)];
|
|
161
|
+
return applyJitter(typeof minDelayMs === "number" && minDelayMs > scheduled ? minDelayMs : scheduled, jitterFraction);
|
|
157
162
|
}
|
|
158
163
|
//#endregion
|
|
159
164
|
//#region src/items.ts
|
|
@@ -477,9 +482,16 @@ function createProducerId() {
|
|
|
477
482
|
}
|
|
478
483
|
var HttpError = class extends Error {
|
|
479
484
|
status;
|
|
480
|
-
|
|
485
|
+
requestId;
|
|
486
|
+
retryAfterMs;
|
|
487
|
+
/** Raw response body text, when readable. Empty bodies are stored as undefined. */
|
|
488
|
+
body;
|
|
489
|
+
constructor(message, status, options) {
|
|
481
490
|
super(message);
|
|
482
491
|
this.status = status;
|
|
492
|
+
if (options?.requestId !== void 0) this.requestId = options.requestId;
|
|
493
|
+
if (options?.retryAfterMs !== void 0) this.retryAfterMs = options.retryAfterMs;
|
|
494
|
+
if (options?.body !== void 0 && options.body.length > 0) this.body = options.body;
|
|
483
495
|
}
|
|
484
496
|
};
|
|
485
497
|
var TimeoutError = class extends Error {
|
|
@@ -498,6 +510,22 @@ var ProtocolError = class extends Error {
|
|
|
498
510
|
this.status = status;
|
|
499
511
|
}
|
|
500
512
|
};
|
|
513
|
+
/**
|
|
514
|
+
* Wraps any error thrown by a user-supplied auth provider callback.
|
|
515
|
+
*
|
|
516
|
+
* The runtime treats these as retryable so transient credential-fetch failures
|
|
517
|
+
* (e.g. a hiccupping auth backend) do not terminate the live read/write loops.
|
|
518
|
+
* For permanent "user is logged out" cases, the auth provider should return
|
|
519
|
+
* `undefined` instead of throwing — that lets the request go out without an
|
|
520
|
+
* `Authorization` header so the server returns a real 401 (`auth_failed`).
|
|
521
|
+
*/
|
|
522
|
+
var AuthProviderError = class extends Error {
|
|
523
|
+
cause;
|
|
524
|
+
constructor(message, options) {
|
|
525
|
+
super(message);
|
|
526
|
+
if (options?.cause !== void 0) this.cause = options.cause;
|
|
527
|
+
}
|
|
528
|
+
};
|
|
501
529
|
function isObject(value) {
|
|
502
530
|
return typeof value === "object" && value != null;
|
|
503
531
|
}
|
|
@@ -603,6 +631,12 @@ function classifyFetchError(err) {
|
|
|
603
631
|
retriable: true,
|
|
604
632
|
atMs: Date.now()
|
|
605
633
|
};
|
|
634
|
+
if (err instanceof AuthProviderError) return {
|
|
635
|
+
code: "auth_provider",
|
|
636
|
+
message,
|
|
637
|
+
retriable: true,
|
|
638
|
+
atMs: Date.now()
|
|
639
|
+
};
|
|
606
640
|
if (status === 401 || status === 403) return {
|
|
607
641
|
code: "auth_failed",
|
|
608
642
|
message,
|
|
@@ -621,6 +655,12 @@ function classifyFetchError(err) {
|
|
|
621
655
|
retriable: false,
|
|
622
656
|
atMs: Date.now()
|
|
623
657
|
};
|
|
658
|
+
if (typeof status === "number" && status >= 500 && status < 600) return {
|
|
659
|
+
code: "server",
|
|
660
|
+
message,
|
|
661
|
+
retriable: true,
|
|
662
|
+
atMs: Date.now()
|
|
663
|
+
};
|
|
624
664
|
if (err instanceof ProtocolError) return {
|
|
625
665
|
code: "protocol",
|
|
626
666
|
message,
|
|
@@ -662,15 +702,28 @@ function toTransportError(error) {
|
|
|
662
702
|
reason: "loro_pending_updates",
|
|
663
703
|
message: error instanceof Error ? error.message : String(error)
|
|
664
704
|
};
|
|
665
|
-
if (
|
|
666
|
-
code: "
|
|
667
|
-
retryable:
|
|
705
|
+
if (error instanceof AuthProviderError) return {
|
|
706
|
+
code: "auth_provider_error",
|
|
707
|
+
retryable: true,
|
|
708
|
+
message: error.message,
|
|
709
|
+
...error.cause !== void 0 && { cause: error.cause }
|
|
668
710
|
};
|
|
711
|
+
if (isStreamNotFoundError(error)) {
|
|
712
|
+
const httpError = error;
|
|
713
|
+
return {
|
|
714
|
+
code: "stream_not_found",
|
|
715
|
+
retryable: false,
|
|
716
|
+
status: 404,
|
|
717
|
+
message: httpError.message,
|
|
718
|
+
...httpError.body !== void 0 && { body: httpError.body }
|
|
719
|
+
};
|
|
720
|
+
}
|
|
669
721
|
if (error instanceof HttpError && (error.status === 401 || error.status === 403)) return {
|
|
670
722
|
code: "auth_failed",
|
|
671
723
|
retryable: false,
|
|
672
724
|
status: error.status,
|
|
673
|
-
message: error.message
|
|
725
|
+
message: error.message,
|
|
726
|
+
...error.body !== void 0 && { body: error.body }
|
|
674
727
|
};
|
|
675
728
|
if (error instanceof HttpError && [
|
|
676
729
|
400,
|
|
@@ -680,7 +733,8 @@ function toTransportError(error) {
|
|
|
680
733
|
code: "protocol_error",
|
|
681
734
|
retryable: false,
|
|
682
735
|
status: error.status,
|
|
683
|
-
message: error.message
|
|
736
|
+
message: error.message,
|
|
737
|
+
...error.body !== void 0 && { body: error.body }
|
|
684
738
|
};
|
|
685
739
|
if (error instanceof ProtocolError) return {
|
|
686
740
|
code: "protocol_error",
|
|
@@ -697,7 +751,18 @@ function toTransportError(error) {
|
|
|
697
751
|
if (error instanceof HttpError && error.status === 410) return {
|
|
698
752
|
code: "gone",
|
|
699
753
|
retryable: true,
|
|
700
|
-
|
|
754
|
+
status: 410,
|
|
755
|
+
message: error.message,
|
|
756
|
+
...error.body !== void 0 && { body: error.body }
|
|
757
|
+
};
|
|
758
|
+
if (error instanceof HttpError && error.status >= 500 && error.status < 600) return {
|
|
759
|
+
code: "server_error",
|
|
760
|
+
retryable: true,
|
|
761
|
+
status: error.status,
|
|
762
|
+
message: error.message,
|
|
763
|
+
...error.body !== void 0 && { body: error.body },
|
|
764
|
+
...error.requestId !== void 0 && { requestId: error.requestId },
|
|
765
|
+
...error.retryAfterMs !== void 0 && { retryAfterMs: error.retryAfterMs }
|
|
701
766
|
};
|
|
702
767
|
if (isPayloadProtectionError(error)) return {
|
|
703
768
|
code: "payload_protection_error",
|
|
@@ -744,7 +809,13 @@ function normalizeAuthProvider(auth) {
|
|
|
744
809
|
return async () => token.length === 0 ? void 0 : token;
|
|
745
810
|
}
|
|
746
811
|
return async (context) => {
|
|
747
|
-
|
|
812
|
+
let value;
|
|
813
|
+
try {
|
|
814
|
+
value = await auth(context);
|
|
815
|
+
} catch (cause) {
|
|
816
|
+
throw new AuthProviderError(cause instanceof Error ? cause.message : `auth provider threw: ${String(cause)}`, { cause });
|
|
817
|
+
}
|
|
818
|
+
const token = value?.trim() ?? "";
|
|
748
819
|
return token.length === 0 ? void 0 : token;
|
|
749
820
|
};
|
|
750
821
|
}
|
|
@@ -1073,8 +1144,12 @@ var StreamsTransportClient = class {
|
|
|
1073
1144
|
return headers;
|
|
1074
1145
|
}
|
|
1075
1146
|
async responseToEphemeralFailure(response, context) {
|
|
1076
|
-
const
|
|
1077
|
-
return new HttpError(
|
|
1147
|
+
const bodyText = await response.text().catch(() => "");
|
|
1148
|
+
return new HttpError(bodyText.length > 0 ? `${context} failed: ${bodyText}` : `${context} failed with status ${response.status}`, response.status, {
|
|
1149
|
+
requestId: extractRequestIdFromResponseHeaders(response.headers),
|
|
1150
|
+
retryAfterMs: parseRetryAfterMs(response.headers.get("retry-after") ?? void 0),
|
|
1151
|
+
body: bodyText.length > 0 ? bodyText : void 0
|
|
1152
|
+
});
|
|
1078
1153
|
}
|
|
1079
1154
|
ephemeralReadEndpoint() {
|
|
1080
1155
|
const target = new URL(this.streamUrl);
|
|
@@ -1089,12 +1164,16 @@ var StreamsTransportClient = class {
|
|
|
1089
1164
|
case "not_found":
|
|
1090
1165
|
case "conflict":
|
|
1091
1166
|
case "gone":
|
|
1092
|
-
case "rate_limited": return new HttpError(error.message, error.status);
|
|
1167
|
+
case "rate_limited": return new HttpError(error.message, error.status, { body: error.bodyText });
|
|
1093
1168
|
case "timeout": return new TimeoutError(error.phase, error.phase === "poll" ? this.reconnectConfig.pollTimeoutMs : this.reconnectConfig.connectTimeoutMs);
|
|
1094
1169
|
case "network_error": return error.cause instanceof TypeError ? error.cause : new TypeError(error.message);
|
|
1095
1170
|
case "protocol_error": return new ProtocolError(error.message, error.status);
|
|
1096
1171
|
case "unknown":
|
|
1097
|
-
if (typeof error.status === "number") return new HttpError(error.message, error.status
|
|
1172
|
+
if (typeof error.status === "number") return new HttpError(error.message, error.status, {
|
|
1173
|
+
requestId: extractRequestIdFromHeaders(error.headers),
|
|
1174
|
+
retryAfterMs: parseRetryAfterMs(error.headers?.["retry-after"]),
|
|
1175
|
+
body: error.bodyText
|
|
1176
|
+
});
|
|
1098
1177
|
if (error.cause instanceof TypeError) return error.cause;
|
|
1099
1178
|
if (error.cause instanceof Error) return error.cause;
|
|
1100
1179
|
return new Error(error.message);
|
|
@@ -1207,6 +1286,41 @@ var StreamsTransportClient = class {
|
|
|
1207
1286
|
};
|
|
1208
1287
|
}
|
|
1209
1288
|
};
|
|
1289
|
+
const REQUEST_ID_HEADER_NAMES = [
|
|
1290
|
+
"x-request-id",
|
|
1291
|
+
"x-amzn-trace-id",
|
|
1292
|
+
"cf-ray",
|
|
1293
|
+
"x-trace-id"
|
|
1294
|
+
];
|
|
1295
|
+
function extractRequestIdFromHeaders(headers) {
|
|
1296
|
+
if (headers == null) return;
|
|
1297
|
+
const lowerCased = {};
|
|
1298
|
+
for (const [key, value] of Object.entries(headers)) lowerCased[key.toLowerCase()] = value;
|
|
1299
|
+
for (const name of REQUEST_ID_HEADER_NAMES) {
|
|
1300
|
+
const value = lowerCased[name];
|
|
1301
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
function extractRequestIdFromResponseHeaders(headers) {
|
|
1305
|
+
for (const name of REQUEST_ID_HEADER_NAMES) {
|
|
1306
|
+
const value = headers.get(name);
|
|
1307
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
function parseRetryAfterMs(value) {
|
|
1311
|
+
if (value == null) return;
|
|
1312
|
+
const trimmed = value.trim();
|
|
1313
|
+
if (trimmed.length === 0) return;
|
|
1314
|
+
const numeric = Number(trimmed);
|
|
1315
|
+
if (Number.isFinite(numeric)) {
|
|
1316
|
+
if (numeric < 0) return;
|
|
1317
|
+
return Math.round(numeric * 1e3);
|
|
1318
|
+
}
|
|
1319
|
+
const parsed = Date.parse(trimmed);
|
|
1320
|
+
if (Number.isNaN(parsed)) return;
|
|
1321
|
+
const delta = parsed - Date.now();
|
|
1322
|
+
return delta > 0 ? delta : void 0;
|
|
1323
|
+
}
|
|
1210
1324
|
//#endregion
|
|
1211
1325
|
//#region src/ephemeral-runtime.ts
|
|
1212
1326
|
function createNoopAdapter() {
|
|
@@ -3060,7 +3174,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3060
3174
|
this.setReadSubStatus(state, "disconnected");
|
|
3061
3175
|
return;
|
|
3062
3176
|
}
|
|
3063
|
-
const delay = computeRetryDelay(state.retryAttempt, this.reconnectConfig);
|
|
3177
|
+
const delay = computeRetryDelay(state.retryAttempt, this.reconnectConfig, error instanceof HttpError ? error.retryAfterMs : void 0);
|
|
3064
3178
|
state.retryAttempt += 1;
|
|
3065
3179
|
await sleep(delay, state.retrySleepController.signal);
|
|
3066
3180
|
}
|
|
@@ -3146,7 +3260,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3146
3260
|
return;
|
|
3147
3261
|
}
|
|
3148
3262
|
this.setWriteSubStatus(state, "reconnecting");
|
|
3149
|
-
const delay = computeRetryDelay(attempts, this.reconnectConfig);
|
|
3263
|
+
const delay = computeRetryDelay(attempts, this.reconnectConfig, error instanceof HttpError ? error.retryAfterMs : void 0);
|
|
3150
3264
|
attempts += 1;
|
|
3151
3265
|
await sleep(delay, state.retrySleepController.signal);
|
|
3152
3266
|
}
|
|
@@ -3278,4 +3392,4 @@ function createStreamUrl(input) {
|
|
|
3278
3392
|
//#endregion
|
|
3279
3393
|
export { InMemoryRemoteCursorStore as a, EphemeralStreamCrdt as c, StreamsCrdt as i, PayloadProtectionError as l, isValidBucketId as n, IndexedDbRemoteCursorStore as o, isValidRillId as r, createInitialRemoteCursor as s, createStreamUrl as t };
|
|
3280
3394
|
|
|
3281
|
-
//# sourceMappingURL=stream-id-
|
|
3395
|
+
//# sourceMappingURL=stream-id-BBE3LRAQ.js.map
|