@syncular/client 0.1.2 → 0.2.0
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 +227 -709
- package/dist/apply.d.ts +80 -0
- package/dist/apply.js +301 -0
- package/dist/blob.d.ts +127 -0
- package/dist/blob.js +201 -0
- package/dist/bun-database.d.ts +22 -0
- package/dist/bun-database.js +61 -0
- package/dist/client.d.ts +320 -69
- package/dist/client.js +1627 -163
- package/dist/content-type.d.ts +2 -0
- package/dist/content-type.js +2 -0
- package/dist/database.d.ts +35 -80
- package/dist/database.js +32 -947
- package/dist/encryption.d.ts +47 -0
- package/dist/encryption.js +75 -0
- package/dist/errors.d.ts +8 -22
- package/dist/errors.js +10 -207
- package/dist/http.d.ts +32 -0
- package/dist/http.js +196 -0
- package/dist/index.d.ts +28 -16
- package/dist/index.js +25 -12
- package/dist/invalidation.d.ts +69 -0
- package/dist/invalidation.js +84 -0
- package/dist/leader-lock.d.ts +28 -0
- package/dist/leader-lock.js +38 -0
- package/dist/multi-tab.d.ts +134 -0
- package/dist/multi-tab.js +399 -0
- package/dist/node-database.d.ts +41 -0
- package/dist/node-database.js +133 -0
- package/dist/outbox.d.ts +56 -0
- package/dist/outbox.js +140 -0
- package/dist/schema.d.ts +107 -0
- package/dist/schema.js +267 -0
- package/dist/state.d.ts +40 -0
- package/dist/state.js +77 -0
- package/dist/transport.d.ts +44 -0
- package/dist/transport.js +7 -0
- package/dist/wasm-database.d.ts +30 -0
- package/dist/wasm-database.js +170 -0
- package/dist/window.d.ts +64 -0
- package/dist/window.js +0 -0
- package/dist/worker-entry.d.ts +16 -2
- package/dist/worker-entry.js +300 -456
- package/dist/worker-host.d.ts +174 -0
- package/dist/worker-host.js +536 -0
- package/dist/worker-protocol.d.ts +173 -61
- package/dist/worker-protocol.js +7 -16
- package/package.json +45 -112
- package/src/apply.ts +435 -0
- package/src/blob.ts +333 -0
- package/src/bun-database.ts +75 -0
- package/src/client.ts +2247 -277
- package/src/content-type.ts +2 -0
- package/src/database.ts +63 -1389
- package/src/encryption.ts +123 -0
- package/src/errors.ts +11 -265
- package/src/http.ts +254 -0
- package/src/index.ts +25 -20
- package/src/invalidation.ts +128 -0
- package/src/leader-lock.ts +68 -0
- package/src/multi-tab.ts +550 -0
- package/src/node-database.ts +180 -0
- package/src/outbox.ts +206 -0
- package/src/schema.ts +397 -0
- package/src/state.ts +126 -0
- package/src/transport.ts +52 -0
- package/src/wasm-database.ts +279 -0
- package/src/window.ts +0 -0
- package/src/worker-entry.ts +391 -545
- package/src/worker-host.ts +800 -0
- package/src/worker-protocol.ts +204 -99
- package/dist/auth-leases.d.ts +0 -11
- package/dist/auth-leases.d.ts.map +0 -1
- package/dist/auth-leases.js +0 -140
- package/dist/auth-leases.js.map +0 -1
- package/dist/blob-limits.d.ts +0 -15
- package/dist/blob-limits.d.ts.map +0 -1
- package/dist/blob-limits.js +0 -66
- package/dist/blob-limits.js.map +0 -1
- package/dist/bridge-client.d.ts +0 -69
- package/dist/bridge-client.d.ts.map +0 -1
- package/dist/bridge-client.js +0 -282
- package/dist/bridge-client.js.map +0 -1
- package/dist/client-config.d.ts +0 -6
- package/dist/client-config.d.ts.map +0 -1
- package/dist/client-config.js +0 -18
- package/dist/client-config.js.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/command-history.d.ts +0 -35
- package/dist/command-history.d.ts.map +0 -1
- package/dist/command-history.js +0 -378
- package/dist/command-history.js.map +0 -1
- package/dist/console-diagnostics.d.ts +0 -33
- package/dist/console-diagnostics.d.ts.map +0 -1
- package/dist/console-diagnostics.js +0 -496
- package/dist/console-diagnostics.js.map +0 -1
- package/dist/crdt-yjs/index.d.ts +0 -4
- package/dist/crdt-yjs/index.d.ts.map +0 -1
- package/dist/crdt-yjs/index.js +0 -4
- package/dist/crdt-yjs/index.js.map +0 -1
- package/dist/crdt-yjs/webview-host-facade.d.ts +0 -126
- package/dist/crdt-yjs/webview-host-facade.d.ts.map +0 -1
- package/dist/crdt-yjs/webview-host-facade.js +0 -284
- package/dist/crdt-yjs/webview-host-facade.js.map +0 -1
- package/dist/crdt-yjs/yjs-document-field-adapter.d.ts +0 -153
- package/dist/crdt-yjs/yjs-document-field-adapter.d.ts.map +0 -1
- package/dist/crdt-yjs/yjs-document-field-adapter.js +0 -406
- package/dist/crdt-yjs/yjs-document-field-adapter.js.map +0 -1
- package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts +0 -73
- package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts.map +0 -1
- package/dist/crdt-yjs/yjs-prosemirror-bridge.js +0 -169
- package/dist/crdt-yjs/yjs-prosemirror-bridge.js.map +0 -1
- package/dist/database.d.ts.map +0 -1
- package/dist/database.js.map +0 -1
- package/dist/diagnostics.d.ts +0 -10
- package/dist/diagnostics.d.ts.map +0 -1
- package/dist/diagnostics.js +0 -83
- package/dist/diagnostics.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/generated-bridge.d.ts +0 -365
- package/dist/generated-bridge.d.ts.map +0 -1
- package/dist/generated-bridge.js +0 -250
- package/dist/generated-bridge.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mutations.d.ts +0 -72
- package/dist/mutations.d.ts.map +0 -1
- package/dist/mutations.js +0 -63
- package/dist/mutations.js.map +0 -1
- package/dist/network.d.ts +0 -3
- package/dist/network.d.ts.map +0 -1
- package/dist/network.js +0 -17
- package/dist/network.js.map +0 -1
- package/dist/react/index.d.ts +0 -169
- package/dist/react/index.d.ts.map +0 -1
- package/dist/react/index.js +0 -628
- package/dist/react/index.js.map +0 -1
- package/dist/react-native/index.d.ts +0 -35
- package/dist/react-native/index.d.ts.map +0 -1
- package/dist/react-native/index.js +0 -49
- package/dist/react-native/index.js.map +0 -1
- package/dist/runtime-contract.d.ts +0 -13
- package/dist/runtime-contract.d.ts.map +0 -1
- package/dist/runtime-contract.js +0 -24
- package/dist/runtime-contract.js.map +0 -1
- package/dist/rust-client.d.ts +0 -106
- package/dist/rust-client.d.ts.map +0 -1
- package/dist/rust-client.js +0 -938
- package/dist/rust-client.js.map +0 -1
- package/dist/sentry.d.ts +0 -35
- package/dist/sentry.d.ts.map +0 -1
- package/dist/sentry.js +0 -155
- package/dist/sentry.js.map +0 -1
- package/dist/sql-safety.d.ts +0 -3
- package/dist/sql-safety.d.ts.map +0 -1
- package/dist/sql-safety.js +0 -62
- package/dist/sql-safety.js.map +0 -1
- package/dist/syncular-runtime-artifacts.json +0 -61
- package/dist/tauri/index.d.ts +0 -35
- package/dist/tauri/index.d.ts.map +0 -1
- package/dist/tauri/index.js +0 -114
- package/dist/tauri/index.js.map +0 -1
- package/dist/types.d.ts +0 -1096
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/wasm/.syncular-wasm-profile +0 -1
- package/dist/wasm/syncular-runtime-artifact.json +0 -21
- package/dist/wasm/syncular.d.ts +0 -207
- package/dist/wasm/syncular.js +0 -2341
- package/dist/wasm/syncular_bg.wasm +0 -0
- package/dist/wasm/syncular_bg.wasm.d.ts +0 -97
- package/dist/wasm-bindings/runtime-contract.d.ts +0 -22
- package/dist/wasm-bindings/runtime-contract.d.ts.map +0 -1
- package/dist/wasm-bindings/runtime-contract.js +0 -112
- package/dist/wasm-bindings/runtime-contract.js.map +0 -1
- package/dist/wasm-core/.syncular-wasm-profile +0 -1
- package/dist/wasm-core/syncular-runtime-artifact.json +0 -17
- package/dist/wasm-core/syncular.d.ts +0 -162
- package/dist/wasm-core/syncular.js +0 -1847
- package/dist/wasm-core/syncular_bg.wasm +0 -0
- package/dist/wasm-core/syncular_bg.wasm.d.ts +0 -77
- package/dist/wasm-perf/.syncular-wasm-profile +0 -1
- package/dist/wasm-perf/syncular-runtime-artifact.json +0 -21
- package/dist/wasm-perf/syncular.d.ts +0 -207
- package/dist/wasm-perf/syncular.js +0 -2341
- package/dist/wasm-perf/syncular_bg.wasm +0 -0
- package/dist/wasm-perf/syncular_bg.wasm.d.ts +0 -97
- package/dist/wasm-runtime.d.ts +0 -23
- package/dist/wasm-runtime.d.ts.map +0 -1
- package/dist/wasm-runtime.js +0 -69
- package/dist/wasm-runtime.js.map +0 -1
- package/dist/worker-client.d.ts +0 -123
- package/dist/worker-client.d.ts.map +0 -1
- package/dist/worker-client.js +0 -1735
- package/dist/worker-client.js.map +0 -1
- package/dist/worker-entry.d.ts.map +0 -1
- package/dist/worker-entry.js.map +0 -1
- package/dist/worker-protocol.d.ts.map +0 -1
- package/dist/worker-protocol.js.map +0 -1
- package/dist/worker-realtime.d.ts +0 -39
- package/dist/worker-realtime.d.ts.map +0 -1
- package/dist/worker-realtime.js +0 -677
- package/dist/worker-realtime.js.map +0 -1
- package/src/auth-leases.ts +0 -251
- package/src/blob-limits.ts +0 -98
- package/src/bridge-client.ts +0 -512
- package/src/client-config.ts +0 -29
- package/src/command-history.ts +0 -623
- package/src/console-diagnostics.ts +0 -617
- package/src/crdt-yjs/index.ts +0 -3
- package/src/crdt-yjs/webview-host-facade.ts +0 -477
- package/src/crdt-yjs/yjs-document-field-adapter.ts +0 -733
- package/src/crdt-yjs/yjs-prosemirror-bridge.ts +0 -272
- package/src/diagnostics.ts +0 -116
- package/src/generated-bridge.ts +0 -741
- package/src/mutations.ts +0 -168
- package/src/network.ts +0 -32
- package/src/react/index.ts +0 -1036
- package/src/react-native/index.ts +0 -152
- package/src/runtime-contract.ts +0 -48
- package/src/rust-client.ts +0 -1491
- package/src/sentry.ts +0 -215
- package/src/sql-safety.ts +0 -61
- package/src/tauri/index.ts +0 -211
- package/src/types.ts +0 -1397
- package/src/wasm-bindings/generated-wasm-bindings.d.ts +0 -70
- package/src/wasm-bindings/runtime-contract.ts +0 -158
- package/src/wasm-runtime.ts +0 -145
- package/src/worker-client.ts +0 -2289
- package/src/worker-realtime.ts +0 -843
|
@@ -1,733 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
SyncularChangedRow,
|
|
3
|
-
SyncularCrdtDocumentSnapshot,
|
|
4
|
-
SyncularCrdtFieldCompactionReceipt,
|
|
5
|
-
SyncularCrdtFieldDescriptor,
|
|
6
|
-
SyncularCrdtFieldMaterialization,
|
|
7
|
-
SyncularCrdtFieldRequest,
|
|
8
|
-
SyncularCrdtFieldWriteReceipt,
|
|
9
|
-
SyncularCrdtFieldYjsUpdateRequest,
|
|
10
|
-
SyncularCrdtUpdateLogEntry,
|
|
11
|
-
SyncularRowsChangedEvent,
|
|
12
|
-
SyncularYjsUpdateEnvelope,
|
|
13
|
-
} from '@syncular/client';
|
|
14
|
-
|
|
15
|
-
export interface SyncularCrdtFieldHost {
|
|
16
|
-
openCrdtField(
|
|
17
|
-
request: SyncularCrdtFieldRequest
|
|
18
|
-
): Promise<SyncularCrdtFieldDescriptor>;
|
|
19
|
-
applyCrdtFieldYjsUpdate(
|
|
20
|
-
request: SyncularCrdtFieldYjsUpdateRequest
|
|
21
|
-
): Promise<SyncularCrdtFieldWriteReceipt>;
|
|
22
|
-
enqueueCrdtFieldYjsUpdate?(
|
|
23
|
-
request: SyncularCrdtFieldYjsUpdateRequest
|
|
24
|
-
): Promise<string>;
|
|
25
|
-
materializeCrdtField(
|
|
26
|
-
request: SyncularCrdtFieldRequest
|
|
27
|
-
): Promise<SyncularCrdtFieldMaterialization>;
|
|
28
|
-
crdtDocumentSnapshot?(
|
|
29
|
-
request: SyncularCrdtFieldRequest
|
|
30
|
-
): Promise<SyncularCrdtDocumentSnapshot>;
|
|
31
|
-
crdtUpdateLog?(
|
|
32
|
-
request: SyncularCrdtFieldRequest & { limit?: number }
|
|
33
|
-
): Promise<SyncularCrdtUpdateLogEntry[]>;
|
|
34
|
-
snapshotCrdtFieldStateVector(
|
|
35
|
-
request: SyncularCrdtFieldRequest
|
|
36
|
-
): Promise<{ stateVectorBase64: string }>;
|
|
37
|
-
compactCrdtField(request: {
|
|
38
|
-
table: string;
|
|
39
|
-
rowId: string;
|
|
40
|
-
field: string;
|
|
41
|
-
minUncheckpointedUpdates?: number;
|
|
42
|
-
}): Promise<SyncularCrdtFieldCompactionReceipt>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface SyncularCrdtProjectionHost extends SyncularCrdtFieldHost {
|
|
46
|
-
addRowsChangedListener(
|
|
47
|
-
listener: (event: SyncularRowsChangedEvent) => void
|
|
48
|
-
): () => void;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface YjsDocumentBinding {
|
|
52
|
-
subscribeLocalUpdates(listener: (update: Uint8Array) => void): () => void;
|
|
53
|
-
applyRemoteUpdate?: (update: Uint8Array) => void;
|
|
54
|
-
replaceDocumentState?: (
|
|
55
|
-
state: Uint8Array,
|
|
56
|
-
receipt: YjsDocumentRestoreReceipt
|
|
57
|
-
) => void;
|
|
58
|
-
replaceMaterializedValue?: (value: unknown) => void;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface YjsDocumentFieldAdapterOptions {
|
|
62
|
-
flushDelayMs?: number;
|
|
63
|
-
maxPendingUpdates?: number;
|
|
64
|
-
restoreOnStart?: boolean;
|
|
65
|
-
updateId?: () => string;
|
|
66
|
-
onBackpressure?: (event: YjsDocumentFieldBackpressureEvent) => void;
|
|
67
|
-
onFlushStart?: (event: YjsDocumentFieldFlushEvent) => void;
|
|
68
|
-
onFlushSuccess?: (event: YjsDocumentFieldFlushEvent) => void;
|
|
69
|
-
onFlushError?: (error: unknown) => void;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface YjsDocumentFieldBackpressureEvent {
|
|
73
|
-
pendingUpdates: number;
|
|
74
|
-
maxPendingUpdates: number;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface YjsDocumentFieldFlushEvent {
|
|
78
|
-
pendingUpdates: number;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface YjsDocumentFieldAdapter {
|
|
82
|
-
open(): Promise<SyncularCrdtFieldDescriptor>;
|
|
83
|
-
start(): Promise<() => Promise<void>>;
|
|
84
|
-
flush(): Promise<void>;
|
|
85
|
-
pendingUpdateCount(): number;
|
|
86
|
-
restoreFromPersistedState(): Promise<YjsDocumentRestoreReceipt>;
|
|
87
|
-
refreshMaterializedValue(): Promise<SyncularCrdtFieldMaterialization>;
|
|
88
|
-
snapshotStateVector(): Promise<string>;
|
|
89
|
-
applyRemoteUpdate(update: SyncularYjsUpdateEnvelope): void;
|
|
90
|
-
compact(
|
|
91
|
-
minUncheckpointedUpdates?: number
|
|
92
|
-
): Promise<SyncularCrdtFieldCompactionReceipt>;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export interface YjsDocumentRestoreReceipt {
|
|
96
|
-
field: SyncularCrdtFieldRequest;
|
|
97
|
-
descriptor: SyncularCrdtFieldDescriptor;
|
|
98
|
-
materialization: SyncularCrdtFieldMaterialization;
|
|
99
|
-
documentSnapshot?: SyncularCrdtDocumentSnapshot;
|
|
100
|
-
stateBase64?: string | null;
|
|
101
|
-
stateVectorBase64: string;
|
|
102
|
-
restoredState: boolean;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export type SyncularCrdtProjectionReason =
|
|
106
|
-
| 'local-write'
|
|
107
|
-
| 'remote-apply'
|
|
108
|
-
| 'compaction'
|
|
109
|
-
| 'manual'
|
|
110
|
-
| 'startup';
|
|
111
|
-
|
|
112
|
-
export interface SyncularCrdtProjectionCause {
|
|
113
|
-
reason: SyncularCrdtProjectionReason;
|
|
114
|
-
source: SyncularRowsChangedEvent['source'] | 'manual' | 'startup';
|
|
115
|
-
changedRow?: SyncularChangedRow;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export interface SyncularCrdtProjectionEvent {
|
|
119
|
-
field: SyncularCrdtFieldRequest;
|
|
120
|
-
descriptor: SyncularCrdtFieldDescriptor;
|
|
121
|
-
materialization: SyncularCrdtFieldMaterialization;
|
|
122
|
-
documentSnapshot?: SyncularCrdtDocumentSnapshot;
|
|
123
|
-
updateLog?: SyncularCrdtUpdateLogEntry[];
|
|
124
|
-
latestUpdate?: SyncularCrdtUpdateLogEntry;
|
|
125
|
-
reason: SyncularCrdtProjectionReason;
|
|
126
|
-
source: SyncularCrdtProjectionCause['source'];
|
|
127
|
-
operation?: SyncularChangedRow['operation'];
|
|
128
|
-
commitId?: string | null;
|
|
129
|
-
commitSeq?: number | null;
|
|
130
|
-
serverVersion?: number | null;
|
|
131
|
-
stateVectorBase64: string;
|
|
132
|
-
changedRow?: SyncularChangedRow;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface SyncularCrdtProjectionDefinition<TProjection> {
|
|
136
|
-
derive(
|
|
137
|
-
materialization: SyncularCrdtFieldMaterialization,
|
|
138
|
-
event: SyncularCrdtProjectionEvent
|
|
139
|
-
): TProjection | Promise<TProjection>;
|
|
140
|
-
apply(
|
|
141
|
-
projection: TProjection,
|
|
142
|
-
event: SyncularCrdtProjectionEvent
|
|
143
|
-
): void | Promise<void>;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export interface SyncularCrdtProjectionMaterializerOptions<TProjection> {
|
|
147
|
-
materializeOnStart?: boolean;
|
|
148
|
-
updateLogLimit?: number;
|
|
149
|
-
onMaterialized?: (
|
|
150
|
-
projection: TProjection,
|
|
151
|
-
event: SyncularCrdtProjectionEvent
|
|
152
|
-
) => void;
|
|
153
|
-
onError?: (error: unknown, event?: SyncularCrdtProjectionEvent) => void;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export interface SyncularCrdtProjectionMaterializer<_TProjection> {
|
|
157
|
-
start(): Promise<() => Promise<void>>;
|
|
158
|
-
stop(): Promise<void>;
|
|
159
|
-
flush(): Promise<void>;
|
|
160
|
-
materialize(
|
|
161
|
-
cause?: Partial<SyncularCrdtProjectionCause>
|
|
162
|
-
): Promise<SyncularCrdtProjectionEvent>;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export interface RichEditorCrdtAdapterOptions<TProjection> {
|
|
166
|
-
field?: YjsDocumentFieldAdapterOptions;
|
|
167
|
-
projections?: SyncularCrdtProjectionMaterializerOptions<TProjection>;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export interface RichEditorCrdtAdapter<_TProjection> {
|
|
171
|
-
document: YjsDocumentFieldAdapter;
|
|
172
|
-
projections: SyncularCrdtProjectionMaterializer<_TProjection>;
|
|
173
|
-
start(): Promise<() => Promise<void>>;
|
|
174
|
-
stop(): Promise<void>;
|
|
175
|
-
flush(): Promise<void>;
|
|
176
|
-
restoreFromPersistedState(): Promise<YjsDocumentRestoreReceipt>;
|
|
177
|
-
compact(
|
|
178
|
-
minUncheckpointedUpdates?: number
|
|
179
|
-
): Promise<SyncularCrdtFieldCompactionReceipt>;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export type YjsEditorBackpressureState = 'open' | 'blocked' | 'recovering';
|
|
183
|
-
|
|
184
|
-
export interface YjsEditorBackpressureEvent {
|
|
185
|
-
state: YjsEditorBackpressureState;
|
|
186
|
-
pendingUpdates: number;
|
|
187
|
-
maxPendingUpdates?: number;
|
|
188
|
-
retryAttempt: number;
|
|
189
|
-
nextRetryDelayMs?: number;
|
|
190
|
-
error?: unknown;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export interface YjsEditorBackpressureControllerOptions {
|
|
194
|
-
retryDelayMs?: number;
|
|
195
|
-
maxRetryDelayMs?: number;
|
|
196
|
-
autoRetry?: boolean;
|
|
197
|
-
setEditorReadOnly?: (
|
|
198
|
-
readOnly: boolean,
|
|
199
|
-
event: YjsEditorBackpressureEvent
|
|
200
|
-
) => void;
|
|
201
|
-
showSavingBlocked?: (event: YjsEditorBackpressureEvent) => void;
|
|
202
|
-
clearSavingBlocked?: (event: YjsEditorBackpressureEvent) => void;
|
|
203
|
-
onStateChange?: (event: YjsEditorBackpressureEvent) => void;
|
|
204
|
-
onRetryError?: (error: unknown, event: YjsEditorBackpressureEvent) => void;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export interface YjsEditorBackpressureController {
|
|
208
|
-
readonly fieldOptions: Pick<
|
|
209
|
-
YjsDocumentFieldAdapterOptions,
|
|
210
|
-
'onBackpressure' | 'onFlushError' | 'onFlushStart' | 'onFlushSuccess'
|
|
211
|
-
>;
|
|
212
|
-
attach(
|
|
213
|
-
adapter: Pick<YjsDocumentFieldAdapter, 'flush' | 'pendingUpdateCount'>
|
|
214
|
-
): void;
|
|
215
|
-
detach(): void;
|
|
216
|
-
state(): YjsEditorBackpressureState;
|
|
217
|
-
retryNow(): Promise<void>;
|
|
218
|
-
stop(): void;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export function createYjsDocumentFieldAdapter(
|
|
222
|
-
host: SyncularCrdtFieldHost,
|
|
223
|
-
field: SyncularCrdtFieldRequest,
|
|
224
|
-
binding: YjsDocumentBinding,
|
|
225
|
-
options: YjsDocumentFieldAdapterOptions = {}
|
|
226
|
-
): YjsDocumentFieldAdapter {
|
|
227
|
-
const flushDelayMs = options.flushDelayMs ?? 16;
|
|
228
|
-
const maxPendingUpdates = options.maxPendingUpdates ?? 1024;
|
|
229
|
-
const updateId = options.updateId ?? randomUpdateId;
|
|
230
|
-
const pending: SyncularYjsUpdateEnvelope[] = [];
|
|
231
|
-
let flushTimer: ReturnType<typeof setTimeout> | undefined;
|
|
232
|
-
let activeFlush: Promise<void> | undefined;
|
|
233
|
-
let descriptor: SyncularCrdtFieldDescriptor | undefined;
|
|
234
|
-
|
|
235
|
-
const scheduleFlush = () => {
|
|
236
|
-
if (flushTimer != null) return;
|
|
237
|
-
flushTimer = setTimeout(() => {
|
|
238
|
-
flushTimer = undefined;
|
|
239
|
-
void adapter.flush().catch((error) => {
|
|
240
|
-
options.onFlushError?.(error);
|
|
241
|
-
});
|
|
242
|
-
}, flushDelayMs);
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const adapter: YjsDocumentFieldAdapter = {
|
|
246
|
-
open() {
|
|
247
|
-
return host.openCrdtField(field).then((nextDescriptor) => {
|
|
248
|
-
descriptor = nextDescriptor;
|
|
249
|
-
return nextDescriptor;
|
|
250
|
-
});
|
|
251
|
-
},
|
|
252
|
-
|
|
253
|
-
async start() {
|
|
254
|
-
await adapter.open();
|
|
255
|
-
if (options.restoreOnStart) {
|
|
256
|
-
await adapter.restoreFromPersistedState();
|
|
257
|
-
}
|
|
258
|
-
const unsubscribe = binding.subscribeLocalUpdates((update) => {
|
|
259
|
-
if (pending.length >= maxPendingUpdates) {
|
|
260
|
-
options.onBackpressure?.({
|
|
261
|
-
pendingUpdates: pending.length,
|
|
262
|
-
maxPendingUpdates,
|
|
263
|
-
});
|
|
264
|
-
throw new Error(
|
|
265
|
-
`Syncular CRDT adapter pending update limit exceeded (${maxPendingUpdates})`
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
pending.push({
|
|
269
|
-
updateId: updateId(),
|
|
270
|
-
updateBase64: bytesToBase64(update),
|
|
271
|
-
});
|
|
272
|
-
scheduleFlush();
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
return async () => {
|
|
276
|
-
if (flushTimer != null) {
|
|
277
|
-
clearTimeout(flushTimer);
|
|
278
|
-
flushTimer = undefined;
|
|
279
|
-
}
|
|
280
|
-
unsubscribe();
|
|
281
|
-
await adapter.flush();
|
|
282
|
-
};
|
|
283
|
-
},
|
|
284
|
-
|
|
285
|
-
flush() {
|
|
286
|
-
activeFlush ??= (async () => {
|
|
287
|
-
options.onFlushStart?.({ pendingUpdates: pending.length });
|
|
288
|
-
while (pending.length > 0) {
|
|
289
|
-
const update = pending[0];
|
|
290
|
-
if (update == null) continue;
|
|
291
|
-
if (host.enqueueCrdtFieldYjsUpdate) {
|
|
292
|
-
await host.enqueueCrdtFieldYjsUpdate({ ...field, update });
|
|
293
|
-
} else {
|
|
294
|
-
await host.applyCrdtFieldYjsUpdate({ ...field, update });
|
|
295
|
-
}
|
|
296
|
-
pending.shift();
|
|
297
|
-
}
|
|
298
|
-
options.onFlushSuccess?.({ pendingUpdates: pending.length });
|
|
299
|
-
})().finally(() => {
|
|
300
|
-
activeFlush = undefined;
|
|
301
|
-
});
|
|
302
|
-
return activeFlush;
|
|
303
|
-
},
|
|
304
|
-
|
|
305
|
-
pendingUpdateCount() {
|
|
306
|
-
return pending.length;
|
|
307
|
-
},
|
|
308
|
-
|
|
309
|
-
async restoreFromPersistedState() {
|
|
310
|
-
const currentDescriptor = descriptor ?? (await adapter.open());
|
|
311
|
-
const [materialized, documentSnapshot] = await Promise.all([
|
|
312
|
-
host.materializeCrdtField(field),
|
|
313
|
-
host.crdtDocumentSnapshot?.(field),
|
|
314
|
-
]);
|
|
315
|
-
const stateBase64 =
|
|
316
|
-
documentSnapshot?.stateBase64 ?? materialized.stateBase64 ?? null;
|
|
317
|
-
const receipt: YjsDocumentRestoreReceipt = {
|
|
318
|
-
field,
|
|
319
|
-
descriptor: currentDescriptor,
|
|
320
|
-
materialization: materialized,
|
|
321
|
-
documentSnapshot,
|
|
322
|
-
stateBase64,
|
|
323
|
-
stateVectorBase64:
|
|
324
|
-
documentSnapshot?.stateVectorBase64 ?? materialized.stateVectorBase64,
|
|
325
|
-
restoredState: stateBase64 != null && stateBase64 !== '',
|
|
326
|
-
};
|
|
327
|
-
if (stateBase64 != null && stateBase64 !== '') {
|
|
328
|
-
binding.replaceDocumentState?.(base64ToBytes(stateBase64), receipt);
|
|
329
|
-
}
|
|
330
|
-
binding.replaceMaterializedValue?.(materialized.value);
|
|
331
|
-
return receipt;
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
async refreshMaterializedValue() {
|
|
335
|
-
const materialized = await host.materializeCrdtField(field);
|
|
336
|
-
binding.replaceMaterializedValue?.(materialized.value);
|
|
337
|
-
return materialized;
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
async snapshotStateVector() {
|
|
341
|
-
const snapshot = await host.snapshotCrdtFieldStateVector(field);
|
|
342
|
-
return snapshot.stateVectorBase64;
|
|
343
|
-
},
|
|
344
|
-
|
|
345
|
-
applyRemoteUpdate(update) {
|
|
346
|
-
binding.applyRemoteUpdate?.(base64ToBytes(update.updateBase64));
|
|
347
|
-
},
|
|
348
|
-
|
|
349
|
-
compact(minUncheckpointedUpdates = 100) {
|
|
350
|
-
return host.compactCrdtField({ ...field, minUncheckpointedUpdates });
|
|
351
|
-
},
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
return adapter;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
export function createYjsEditorBackpressureController(
|
|
358
|
-
options: YjsEditorBackpressureControllerOptions = {}
|
|
359
|
-
): YjsEditorBackpressureController {
|
|
360
|
-
const retryDelayMs = options.retryDelayMs ?? 750;
|
|
361
|
-
const maxRetryDelayMs = options.maxRetryDelayMs ?? 8_000;
|
|
362
|
-
const autoRetry = options.autoRetry ?? true;
|
|
363
|
-
let adapter:
|
|
364
|
-
| Pick<YjsDocumentFieldAdapter, 'flush' | 'pendingUpdateCount'>
|
|
365
|
-
| undefined;
|
|
366
|
-
let currentState: YjsEditorBackpressureState = 'open';
|
|
367
|
-
let retryAttempt = 0;
|
|
368
|
-
let retryTimer: ReturnType<typeof setTimeout> | undefined;
|
|
369
|
-
let lastEvent: YjsEditorBackpressureEvent = {
|
|
370
|
-
state: 'open',
|
|
371
|
-
pendingUpdates: 0,
|
|
372
|
-
retryAttempt: 0,
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
const clearRetryTimer = () => {
|
|
376
|
-
if (retryTimer != null) {
|
|
377
|
-
clearTimeout(retryTimer);
|
|
378
|
-
retryTimer = undefined;
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
const publish = (
|
|
383
|
-
state: YjsEditorBackpressureState,
|
|
384
|
-
event: Partial<YjsEditorBackpressureEvent> = {}
|
|
385
|
-
): YjsEditorBackpressureEvent => {
|
|
386
|
-
currentState = state;
|
|
387
|
-
lastEvent = {
|
|
388
|
-
...lastEvent,
|
|
389
|
-
...event,
|
|
390
|
-
state,
|
|
391
|
-
retryAttempt,
|
|
392
|
-
pendingUpdates:
|
|
393
|
-
event.pendingUpdates ?? adapter?.pendingUpdateCount() ?? 0,
|
|
394
|
-
};
|
|
395
|
-
options.onStateChange?.(lastEvent);
|
|
396
|
-
return lastEvent;
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
const block = (event: Partial<YjsEditorBackpressureEvent>) => {
|
|
400
|
-
const nextEvent = publish('blocked', event);
|
|
401
|
-
options.setEditorReadOnly?.(true, nextEvent);
|
|
402
|
-
options.showSavingBlocked?.(nextEvent);
|
|
403
|
-
scheduleRetry(nextEvent);
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
const unblock = (event: Partial<YjsEditorBackpressureEvent> = {}) => {
|
|
407
|
-
clearRetryTimer();
|
|
408
|
-
retryAttempt = 0;
|
|
409
|
-
const nextEvent = publish('open', event);
|
|
410
|
-
options.clearSavingBlocked?.(nextEvent);
|
|
411
|
-
options.setEditorReadOnly?.(false, nextEvent);
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
const scheduleRetry = (event: YjsEditorBackpressureEvent) => {
|
|
415
|
-
if (!autoRetry || adapter == null || retryTimer != null) return;
|
|
416
|
-
const nextRetryDelayMs = Math.min(
|
|
417
|
-
maxRetryDelayMs,
|
|
418
|
-
retryDelayMs * 2 ** retryAttempt
|
|
419
|
-
);
|
|
420
|
-
lastEvent = { ...event, nextRetryDelayMs };
|
|
421
|
-
retryTimer = setTimeout(() => {
|
|
422
|
-
retryTimer = undefined;
|
|
423
|
-
void controller.retryNow();
|
|
424
|
-
}, nextRetryDelayMs);
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
const controller: YjsEditorBackpressureController = {
|
|
428
|
-
fieldOptions: {
|
|
429
|
-
onBackpressure(event) {
|
|
430
|
-
retryAttempt = 0;
|
|
431
|
-
block({
|
|
432
|
-
pendingUpdates: event.pendingUpdates,
|
|
433
|
-
maxPendingUpdates: event.maxPendingUpdates,
|
|
434
|
-
});
|
|
435
|
-
},
|
|
436
|
-
|
|
437
|
-
onFlushStart(event) {
|
|
438
|
-
if (currentState === 'blocked') {
|
|
439
|
-
publish('recovering', { pendingUpdates: event.pendingUpdates });
|
|
440
|
-
}
|
|
441
|
-
},
|
|
442
|
-
|
|
443
|
-
onFlushSuccess(event) {
|
|
444
|
-
if (event.pendingUpdates === 0 && currentState !== 'open') {
|
|
445
|
-
unblock({ pendingUpdates: 0 });
|
|
446
|
-
}
|
|
447
|
-
},
|
|
448
|
-
|
|
449
|
-
onFlushError(error) {
|
|
450
|
-
retryAttempt += 1;
|
|
451
|
-
const event = publish(
|
|
452
|
-
currentState === 'open' ? 'blocked' : currentState,
|
|
453
|
-
{
|
|
454
|
-
error,
|
|
455
|
-
}
|
|
456
|
-
);
|
|
457
|
-
options.onRetryError?.(error, event);
|
|
458
|
-
scheduleRetry(event);
|
|
459
|
-
},
|
|
460
|
-
},
|
|
461
|
-
|
|
462
|
-
attach(nextAdapter) {
|
|
463
|
-
adapter = nextAdapter;
|
|
464
|
-
},
|
|
465
|
-
|
|
466
|
-
detach() {
|
|
467
|
-
adapter = undefined;
|
|
468
|
-
clearRetryTimer();
|
|
469
|
-
},
|
|
470
|
-
|
|
471
|
-
state() {
|
|
472
|
-
return currentState;
|
|
473
|
-
},
|
|
474
|
-
|
|
475
|
-
async retryNow() {
|
|
476
|
-
clearRetryTimer();
|
|
477
|
-
if (adapter == null) return;
|
|
478
|
-
publish('recovering');
|
|
479
|
-
try {
|
|
480
|
-
await adapter.flush();
|
|
481
|
-
if (adapter.pendingUpdateCount() === 0) {
|
|
482
|
-
unblock({ pendingUpdates: 0 });
|
|
483
|
-
} else {
|
|
484
|
-
retryAttempt += 1;
|
|
485
|
-
block({ pendingUpdates: adapter.pendingUpdateCount() });
|
|
486
|
-
}
|
|
487
|
-
} catch (error) {
|
|
488
|
-
retryAttempt += 1;
|
|
489
|
-
const event = publish('blocked', { error });
|
|
490
|
-
options.onRetryError?.(error, event);
|
|
491
|
-
scheduleRetry(event);
|
|
492
|
-
}
|
|
493
|
-
},
|
|
494
|
-
|
|
495
|
-
stop() {
|
|
496
|
-
clearRetryTimer();
|
|
497
|
-
retryAttempt = 0;
|
|
498
|
-
if (currentState !== 'open') {
|
|
499
|
-
unblock({ pendingUpdates: adapter?.pendingUpdateCount() ?? 0 });
|
|
500
|
-
}
|
|
501
|
-
},
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
return controller;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
export function createRichEditorCrdtAdapter<TProjection>(
|
|
508
|
-
host: SyncularCrdtProjectionHost,
|
|
509
|
-
field: SyncularCrdtFieldRequest,
|
|
510
|
-
binding: YjsDocumentBinding,
|
|
511
|
-
projection: SyncularCrdtProjectionDefinition<TProjection>,
|
|
512
|
-
options: RichEditorCrdtAdapterOptions<TProjection> = {}
|
|
513
|
-
): RichEditorCrdtAdapter<TProjection> {
|
|
514
|
-
const document = createYjsDocumentFieldAdapter(host, field, binding, {
|
|
515
|
-
restoreOnStart: true,
|
|
516
|
-
...options.field,
|
|
517
|
-
});
|
|
518
|
-
const projections = createCrdtFieldProjectionMaterializer(
|
|
519
|
-
host,
|
|
520
|
-
field,
|
|
521
|
-
projection,
|
|
522
|
-
{
|
|
523
|
-
materializeOnStart: true,
|
|
524
|
-
...options.projections,
|
|
525
|
-
}
|
|
526
|
-
);
|
|
527
|
-
let stopDocument: (() => Promise<void>) | undefined;
|
|
528
|
-
let stopProjections: (() => Promise<void>) | undefined;
|
|
529
|
-
|
|
530
|
-
const adapter: RichEditorCrdtAdapter<TProjection> = {
|
|
531
|
-
document,
|
|
532
|
-
projections,
|
|
533
|
-
|
|
534
|
-
async start() {
|
|
535
|
-
stopDocument = await document.start();
|
|
536
|
-
stopProjections = await projections.start();
|
|
537
|
-
return () => adapter.stop();
|
|
538
|
-
},
|
|
539
|
-
|
|
540
|
-
async stop() {
|
|
541
|
-
const currentStopDocument = stopDocument;
|
|
542
|
-
const currentStopProjections = stopProjections;
|
|
543
|
-
stopProjections = undefined;
|
|
544
|
-
stopDocument = undefined;
|
|
545
|
-
await currentStopDocument?.();
|
|
546
|
-
await currentStopProjections?.();
|
|
547
|
-
},
|
|
548
|
-
|
|
549
|
-
async flush() {
|
|
550
|
-
await document.flush();
|
|
551
|
-
await projections.flush();
|
|
552
|
-
},
|
|
553
|
-
|
|
554
|
-
restoreFromPersistedState() {
|
|
555
|
-
return document.restoreFromPersistedState();
|
|
556
|
-
},
|
|
557
|
-
|
|
558
|
-
compact(minUncheckpointedUpdates) {
|
|
559
|
-
return document.compact(minUncheckpointedUpdates);
|
|
560
|
-
},
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
return adapter;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
export function createCrdtFieldProjectionMaterializer<TProjection>(
|
|
567
|
-
host: SyncularCrdtProjectionHost,
|
|
568
|
-
field: SyncularCrdtFieldRequest,
|
|
569
|
-
projection: SyncularCrdtProjectionDefinition<TProjection>,
|
|
570
|
-
options: SyncularCrdtProjectionMaterializerOptions<TProjection> = {}
|
|
571
|
-
): SyncularCrdtProjectionMaterializer<TProjection> {
|
|
572
|
-
let descriptor: SyncularCrdtFieldDescriptor | undefined;
|
|
573
|
-
let unsubscribeRowsChanged: (() => void) | undefined;
|
|
574
|
-
let queue = Promise.resolve();
|
|
575
|
-
|
|
576
|
-
const open = async () => {
|
|
577
|
-
descriptor ??= await host.openCrdtField(field);
|
|
578
|
-
return descriptor;
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
const runMaterialization = async (
|
|
582
|
-
cause: SyncularCrdtProjectionCause
|
|
583
|
-
): Promise<SyncularCrdtProjectionEvent> => {
|
|
584
|
-
const currentDescriptor = await open();
|
|
585
|
-
const updateLogLimit = Math.max(0, options.updateLogLimit ?? 0);
|
|
586
|
-
const [materialization, documentSnapshot, updateLog] = await Promise.all([
|
|
587
|
-
host.materializeCrdtField(field),
|
|
588
|
-
host.crdtDocumentSnapshot?.(field),
|
|
589
|
-
updateLogLimit > 0
|
|
590
|
-
? host.crdtUpdateLog?.({ ...field, limit: updateLogLimit })
|
|
591
|
-
: undefined,
|
|
592
|
-
]);
|
|
593
|
-
const stateVectorBase64 =
|
|
594
|
-
documentSnapshot?.stateVectorBase64 ?? materialization.stateVectorBase64;
|
|
595
|
-
const event: SyncularCrdtProjectionEvent = {
|
|
596
|
-
field,
|
|
597
|
-
descriptor: currentDescriptor,
|
|
598
|
-
materialization,
|
|
599
|
-
documentSnapshot,
|
|
600
|
-
updateLog,
|
|
601
|
-
latestUpdate: updateLog?.[0],
|
|
602
|
-
reason: cause.reason,
|
|
603
|
-
source: cause.source,
|
|
604
|
-
operation: cause.changedRow?.operation,
|
|
605
|
-
commitId: cause.changedRow?.commitId,
|
|
606
|
-
commitSeq: cause.changedRow?.commitSeq,
|
|
607
|
-
serverVersion: cause.changedRow?.serverVersion,
|
|
608
|
-
stateVectorBase64,
|
|
609
|
-
changedRow: cause.changedRow,
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
try {
|
|
613
|
-
const nextProjection = await projection.derive(materialization, event);
|
|
614
|
-
await projection.apply(nextProjection, event);
|
|
615
|
-
options.onMaterialized?.(nextProjection, event);
|
|
616
|
-
return event;
|
|
617
|
-
} catch (error) {
|
|
618
|
-
options.onError?.(error, event);
|
|
619
|
-
throw error;
|
|
620
|
-
}
|
|
621
|
-
};
|
|
622
|
-
|
|
623
|
-
const enqueueMaterialization = (cause: SyncularCrdtProjectionCause) => {
|
|
624
|
-
queue = queue
|
|
625
|
-
.then(() => runMaterialization(cause))
|
|
626
|
-
.then(
|
|
627
|
-
() => undefined,
|
|
628
|
-
() => undefined
|
|
629
|
-
);
|
|
630
|
-
};
|
|
631
|
-
|
|
632
|
-
const materializer: SyncularCrdtProjectionMaterializer<TProjection> = {
|
|
633
|
-
async start() {
|
|
634
|
-
const currentDescriptor = await open();
|
|
635
|
-
unsubscribeRowsChanged = host.addRowsChangedListener((event) => {
|
|
636
|
-
const changedRow = event.changedRows.find((row) =>
|
|
637
|
-
crdtChangedRowMatchesField(row, field, currentDescriptor)
|
|
638
|
-
);
|
|
639
|
-
if (changedRow == null) return;
|
|
640
|
-
enqueueMaterialization({
|
|
641
|
-
reason: projectionReasonForRowsChanged(event, changedRow),
|
|
642
|
-
source: event.source,
|
|
643
|
-
changedRow,
|
|
644
|
-
});
|
|
645
|
-
});
|
|
646
|
-
if (options.materializeOnStart) {
|
|
647
|
-
enqueueMaterialization({ reason: 'startup', source: 'startup' });
|
|
648
|
-
}
|
|
649
|
-
return () => materializer.stop();
|
|
650
|
-
},
|
|
651
|
-
|
|
652
|
-
async stop() {
|
|
653
|
-
unsubscribeRowsChanged?.();
|
|
654
|
-
unsubscribeRowsChanged = undefined;
|
|
655
|
-
await materializer.flush();
|
|
656
|
-
},
|
|
657
|
-
|
|
658
|
-
flush() {
|
|
659
|
-
return queue;
|
|
660
|
-
},
|
|
661
|
-
|
|
662
|
-
materialize(cause = {}) {
|
|
663
|
-
return runMaterialization({
|
|
664
|
-
reason: cause.reason ?? 'manual',
|
|
665
|
-
source: cause.source ?? 'manual',
|
|
666
|
-
changedRow: cause.changedRow,
|
|
667
|
-
});
|
|
668
|
-
},
|
|
669
|
-
};
|
|
670
|
-
|
|
671
|
-
return materializer;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
export function crdtChangedRowMatchesField(
|
|
675
|
-
row: SyncularChangedRow,
|
|
676
|
-
field: SyncularCrdtFieldRequest,
|
|
677
|
-
descriptor: SyncularCrdtFieldDescriptor
|
|
678
|
-
): boolean {
|
|
679
|
-
if (row.table !== field.table || row.rowId !== field.rowId) return false;
|
|
680
|
-
return (
|
|
681
|
-
row.crdtFields.includes(descriptor.stateColumn) ||
|
|
682
|
-
row.changedFields.includes(descriptor.stateColumn) ||
|
|
683
|
-
row.changedFields.includes(field.field)
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
export function projectionReasonForRowsChanged(
|
|
688
|
-
event: SyncularRowsChangedEvent,
|
|
689
|
-
row: SyncularChangedRow
|
|
690
|
-
): SyncularCrdtProjectionReason {
|
|
691
|
-
if (row.operation === 'compact') return 'compaction';
|
|
692
|
-
if (event.source === 'remotePull') return 'remote-apply';
|
|
693
|
-
return 'local-write';
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
export function bytesToBase64(bytes: Uint8Array): string {
|
|
697
|
-
const maybeBuffer = (
|
|
698
|
-
globalThis as {
|
|
699
|
-
Buffer?: {
|
|
700
|
-
from(input: Uint8Array): { toString(encoding: 'base64'): string };
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
).Buffer;
|
|
704
|
-
if (maybeBuffer != null) return maybeBuffer.from(bytes).toString('base64');
|
|
705
|
-
|
|
706
|
-
let binary = '';
|
|
707
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
708
|
-
return btoa(binary);
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
export function base64ToBytes(value: string): Uint8Array {
|
|
712
|
-
const maybeBuffer = (
|
|
713
|
-
globalThis as {
|
|
714
|
-
Buffer?: {
|
|
715
|
-
from(input: string, encoding: 'base64'): Uint8Array;
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
).Buffer;
|
|
719
|
-
if (maybeBuffer != null) return maybeBuffer.from(value, 'base64');
|
|
720
|
-
|
|
721
|
-
const binary = atob(value);
|
|
722
|
-
const bytes = new Uint8Array(binary.length);
|
|
723
|
-
for (let index = 0; index < binary.length; index += 1) {
|
|
724
|
-
bytes[index] = binary.charCodeAt(index);
|
|
725
|
-
}
|
|
726
|
-
return bytes;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
function randomUpdateId(): string {
|
|
730
|
-
return (
|
|
731
|
-
globalThis.crypto?.randomUUID?.() ?? `yjs-${Date.now()}-${Math.random()}`
|
|
732
|
-
);
|
|
733
|
-
}
|