@yorkie-js/sdk 0.7.7 → 0.7.8
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 +1 -1
- package/dist/yorkie-js-sdk.d.ts +68 -12
- package/dist/yorkie-js-sdk.es.js +205 -37
- package/dist/yorkie-js-sdk.es.js.map +1 -1
- package/dist/yorkie-js-sdk.js +205 -37
- package/dist/yorkie-js-sdk.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ To get started using Yorkie JavaScript SDK, see: https://yorkie.dev/docs/js-sdk
|
|
|
10
10
|
|
|
11
11
|
## Contributing
|
|
12
12
|
|
|
13
|
-
See [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow.
|
|
13
|
+
See [CONTRIBUTING](../../CONTRIBUTING.md) for details on submitting patches and the contribution workflow.
|
|
14
14
|
|
|
15
15
|
## Contributors ✨
|
|
16
16
|
|
package/dist/yorkie-js-sdk.d.ts
CHANGED
|
@@ -93,20 +93,30 @@ declare interface Attachable {
|
|
|
93
93
|
/**
|
|
94
94
|
* `AttachChannelOptions` are user-settable options used when attaching channels.
|
|
95
95
|
*/
|
|
96
|
-
declare interface AttachChannelOptions {
|
|
96
|
+
export declare interface AttachChannelOptions {
|
|
97
97
|
/**
|
|
98
|
-
* `
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
98
|
+
* `syncMode` selects how the channel keeps presence in sync with the server.
|
|
99
|
+
* Default is `SyncMode.Realtime`.
|
|
100
|
+
* - `SyncMode.Realtime`: open a watch stream and run the heartbeat. Required
|
|
101
|
+
* to receive broadcast events.
|
|
102
|
+
* - `SyncMode.Polling`: heartbeat-only. No watch stream is opened. The
|
|
103
|
+
* heartbeat refreshes TTL and brings the latest sessionCount. Recommended
|
|
104
|
+
* for large channels where broadcast is not needed.
|
|
105
|
+
* - `SyncMode.Manual`: no automatic activity. Caller must invoke `sync()`.
|
|
102
106
|
*/
|
|
103
|
-
|
|
107
|
+
syncMode?: SyncMode;
|
|
108
|
+
/**
|
|
109
|
+
* `channelHeartbeatInterval` overrides the heartbeat interval (ms) for this
|
|
110
|
+
* attachment. If unset, mode-specific defaults apply: Polling=3000,
|
|
111
|
+
* Realtime=30000.
|
|
112
|
+
*/
|
|
113
|
+
channelHeartbeatInterval?: number;
|
|
104
114
|
}
|
|
105
115
|
|
|
106
116
|
/**
|
|
107
117
|
* `AttachOptions` are user-settable options used when attaching documents.
|
|
108
118
|
*/
|
|
109
|
-
declare interface AttachOptions<R, P> {
|
|
119
|
+
export declare interface AttachOptions<R, P> {
|
|
110
120
|
/**
|
|
111
121
|
* `initialRoot` is the initial root of the document. It is used to
|
|
112
122
|
* initialize the document. It is used when the fields are not set in the
|
|
@@ -121,6 +131,11 @@ declare interface AttachOptions<R, P> {
|
|
|
121
131
|
* `syncMode` defines the synchronization mode of the document.
|
|
122
132
|
*/
|
|
123
133
|
syncMode?: SyncMode;
|
|
134
|
+
/**
|
|
135
|
+
* `documentPollInterval` (ms) — only used when `syncMode` is `Polling`.
|
|
136
|
+
* Default: 3000.
|
|
137
|
+
*/
|
|
138
|
+
documentPollInterval?: number;
|
|
124
139
|
/**
|
|
125
140
|
* `schema` is the schema of the document. It is used to validate the
|
|
126
141
|
* document.
|
|
@@ -1039,6 +1054,17 @@ export declare class Client {
|
|
|
1039
1054
|
* `changeSyncMode` changes the synchronization mode of the given document.
|
|
1040
1055
|
*/
|
|
1041
1056
|
changeSyncMode<R, P extends Indexable>(doc: Document_2<R, P>, syncMode: SyncMode): Promise<Document_2<R, P>>;
|
|
1057
|
+
/**
|
|
1058
|
+
* `changeSyncMode` changes the synchronization mode of the given channel.
|
|
1059
|
+
*/
|
|
1060
|
+
changeSyncMode(channel: Channel, syncMode: SyncMode): Promise<Channel>;
|
|
1061
|
+
private changeDocumentSyncMode;
|
|
1062
|
+
/**
|
|
1063
|
+
* `assertValidChannelSyncMode` rejects sync modes that are not valid for
|
|
1064
|
+
* channels. `RealtimePushOnly` and `RealtimeSyncOff` are document-only.
|
|
1065
|
+
*/
|
|
1066
|
+
private assertValidChannelSyncMode;
|
|
1067
|
+
private changeChannelSyncMode;
|
|
1042
1068
|
/**
|
|
1043
1069
|
* `sync` pushes local changes of the attached documents to the server and
|
|
1044
1070
|
* receives changes of the remote replica from the server then apply them to
|
|
@@ -1997,12 +2023,28 @@ declare class CRDTTree extends CRDTElement implements GCParent {
|
|
|
1997
2023
|
* `edit` edits the tree with the given range and content.
|
|
1998
2024
|
* If the content is undefined, the range will be removed.
|
|
1999
2025
|
*/
|
|
2000
|
-
edit(range: [CRDTTreePos, CRDTTreePos], contents: Array<CRDTTreeNode> | undefined, splitLevel: number, editedAt: TimeTicket, issueTimeTicket: (() => TimeTicket) | undefined, versionVector?: VersionVector): [
|
|
2026
|
+
edit(range: [CRDTTreePos, CRDTTreePos], contents: Array<CRDTTreeNode> | undefined, splitLevel: number, editedAt: TimeTicket, issueTimeTicket: (() => TimeTicket) | undefined, versionVector?: VersionVector): [
|
|
2027
|
+
Array<TreeChange>,
|
|
2028
|
+
Array<GCPair>,
|
|
2029
|
+
DataSize,
|
|
2030
|
+
Array<CRDTTreeNode>,
|
|
2031
|
+
number,
|
|
2032
|
+
number,
|
|
2033
|
+
Set<string>
|
|
2034
|
+
];
|
|
2001
2035
|
/**
|
|
2002
2036
|
* `editT` edits the given range with the given value.
|
|
2003
2037
|
* This method uses indexes instead of a pair of TreePos for testing.
|
|
2004
2038
|
*/
|
|
2005
|
-
editT(range: [number, number], contents: Array<CRDTTreeNode> | undefined, splitLevel: number, editedAt: TimeTicket, issueTimeTicket: () => TimeTicket): [
|
|
2039
|
+
editT(range: [number, number], contents: Array<CRDTTreeNode> | undefined, splitLevel: number, editedAt: TimeTicket, issueTimeTicket: () => TimeTicket): [
|
|
2040
|
+
Array<TreeChange>,
|
|
2041
|
+
Array<GCPair>,
|
|
2042
|
+
DataSize,
|
|
2043
|
+
Array<CRDTTreeNode>,
|
|
2044
|
+
number,
|
|
2045
|
+
number,
|
|
2046
|
+
Set<string>
|
|
2047
|
+
];
|
|
2006
2048
|
/**
|
|
2007
2049
|
* `move` move the given source range to the given target range.
|
|
2008
2050
|
*/
|
|
@@ -3035,7 +3077,12 @@ declare class Document_2<R, P extends Indexable = Indexable> implements Attachab
|
|
|
3035
3077
|
* `applySnapshot` applies the given snapshot into this document.
|
|
3036
3078
|
*/
|
|
3037
3079
|
applySnapshot(serverSeq: bigint, snapshotVector: VersionVector, snapshot?: Uint8Array, clientSeq?: number): void;
|
|
3038
|
-
|
|
3080
|
+
/**
|
|
3081
|
+
* `clearHistory` flushes both undo and redo stacks. This is used
|
|
3082
|
+
* after applying a snapshot or initialRoot so that setup operations
|
|
3083
|
+
* are not reachable via undo.
|
|
3084
|
+
*/
|
|
3085
|
+
clearHistory(): void;
|
|
3039
3086
|
/**
|
|
3040
3087
|
* `applyChanges` applies the given changes into this document.
|
|
3041
3088
|
*/
|
|
@@ -6005,7 +6052,8 @@ declare interface SubscribeFn<T> {
|
|
|
6005
6052
|
}
|
|
6006
6053
|
|
|
6007
6054
|
/**
|
|
6008
|
-
* `SyncMode` defines synchronization modes for the PushPullChanges API
|
|
6055
|
+
* `SyncMode` defines synchronization modes for the PushPullChanges API
|
|
6056
|
+
* (documents) and the RefreshChannel heartbeat (channels).
|
|
6009
6057
|
*/
|
|
6010
6058
|
export declare enum SyncMode {
|
|
6011
6059
|
/**
|
|
@@ -6024,7 +6072,15 @@ export declare enum SyncMode {
|
|
|
6024
6072
|
* `RealtimeSyncOff` mode indicates that changes are not automatically pushed or pulled,
|
|
6025
6073
|
* but the watch stream is kept active.
|
|
6026
6074
|
*/
|
|
6027
|
-
RealtimeSyncOff = "realtime-syncoff"
|
|
6075
|
+
RealtimeSyncOff = "realtime-syncoff",
|
|
6076
|
+
/**
|
|
6077
|
+
* `Polling` mode runs the sync loop without opening a watch stream.
|
|
6078
|
+
* - For Channel: heartbeat refreshes TTL and brings sessionCount.
|
|
6079
|
+
* - For Document: PushPullChanges runs at the polling interval. Remote
|
|
6080
|
+
* changes arrive on the next tick (latency = interval). Not suitable
|
|
6081
|
+
* for collaborative editing — use Realtime for that.
|
|
6082
|
+
*/
|
|
6083
|
+
Polling = "polling"
|
|
6028
6084
|
}
|
|
6029
6085
|
|
|
6030
6086
|
/**
|
package/dist/yorkie-js-sdk.es.js
CHANGED
|
@@ -358,7 +358,7 @@ function isScalarZeroValue(type, value) {
|
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
360
|
const IMPLICIT$3 = 2;
|
|
361
|
-
const unsafeLocal = Symbol.for("reflect unsafe local");
|
|
361
|
+
const unsafeLocal = /* @__PURE__ */ Symbol.for("reflect unsafe local");
|
|
362
362
|
function unsafeOneofCase(target, oneof) {
|
|
363
363
|
const c = target[oneof.localName].case;
|
|
364
364
|
if (c === void 0) {
|
|
@@ -584,7 +584,7 @@ function convertObjectValues(obj, fn) {
|
|
|
584
584
|
}
|
|
585
585
|
return ret;
|
|
586
586
|
}
|
|
587
|
-
const tokenZeroMessageField = Symbol();
|
|
587
|
+
const tokenZeroMessageField = /* @__PURE__ */ Symbol();
|
|
588
588
|
const messagePrototypes = /* @__PURE__ */ new WeakMap();
|
|
589
589
|
function createZeroMessage(desc) {
|
|
590
590
|
let msg;
|
|
@@ -686,7 +686,7 @@ class FieldError extends Error {
|
|
|
686
686
|
function isFieldError(arg) {
|
|
687
687
|
return arg instanceof Error && errorNames.includes(arg.name) && "field" in arg && typeof arg.field == "function";
|
|
688
688
|
}
|
|
689
|
-
const symbol = Symbol.for("@bufbuild/protobuf/text-encoding");
|
|
689
|
+
const symbol = /* @__PURE__ */ Symbol.for("@bufbuild/protobuf/text-encoding");
|
|
690
690
|
function getTextEncoding() {
|
|
691
691
|
if (globalThis[symbol] == void 0) {
|
|
692
692
|
const te = new globalThis.TextEncoder();
|
|
@@ -4054,7 +4054,7 @@ function readScalarField(msg, field, json) {
|
|
|
4054
4054
|
msg.set(field, scalarValue);
|
|
4055
4055
|
}
|
|
4056
4056
|
}
|
|
4057
|
-
const tokenIgnoredUnknownEnum = Symbol();
|
|
4057
|
+
const tokenIgnoredUnknownEnum = /* @__PURE__ */ Symbol();
|
|
4058
4058
|
function readEnum(desc, json, ignoreUnknownFields, nullAsZeroValue) {
|
|
4059
4059
|
if (json === null) {
|
|
4060
4060
|
if (desc.typeName == "google.protobuf.NullValue") {
|
|
@@ -4080,7 +4080,7 @@ function readEnum(desc, json, ignoreUnknownFields, nullAsZeroValue) {
|
|
|
4080
4080
|
}
|
|
4081
4081
|
throw new Error(`cannot decode ${desc} from JSON: ${formatVal(json)}`);
|
|
4082
4082
|
}
|
|
4083
|
-
const tokenNull = Symbol();
|
|
4083
|
+
const tokenNull = /* @__PURE__ */ Symbol();
|
|
4084
4084
|
function scalarFromJson(field, json, nullAsZeroValue) {
|
|
4085
4085
|
if (json === null) {
|
|
4086
4086
|
if (nullAsZeroValue) {
|
|
@@ -5901,6 +5901,8 @@ class YorkieError extends Error {
|
|
|
5901
5901
|
this.message = message;
|
|
5902
5902
|
this.toString = () => `[code=${this.code}]: ${this.message}`;
|
|
5903
5903
|
}
|
|
5904
|
+
code;
|
|
5905
|
+
message;
|
|
5904
5906
|
name = "YorkieError";
|
|
5905
5907
|
stack;
|
|
5906
5908
|
}
|
|
@@ -12530,8 +12532,10 @@ class CRDTTree extends CRDTElement {
|
|
|
12530
12532
|
break;
|
|
12531
12533
|
}
|
|
12532
12534
|
if (next.parent && next.parent === toParent) {
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
+
if (toLeft !== toParent) {
|
|
12536
|
+
collectFromLeft = next;
|
|
12537
|
+
collectFromParent = toParent;
|
|
12538
|
+
}
|
|
12535
12539
|
break;
|
|
12536
12540
|
}
|
|
12537
12541
|
current = next;
|
|
@@ -12594,6 +12598,7 @@ class CRDTTree extends CRDTElement {
|
|
|
12594
12598
|
tokensToBeRemoved,
|
|
12595
12599
|
editedAt
|
|
12596
12600
|
);
|
|
12601
|
+
const mergeLevel = toBeMergedNodes.length;
|
|
12597
12602
|
const pairs = [];
|
|
12598
12603
|
for (const node of nodesToBeRemoved) {
|
|
12599
12604
|
if (node.remove(editedAt)) {
|
|
@@ -12720,7 +12725,15 @@ class CRDTTree extends CRDTElement {
|
|
|
12720
12725
|
}
|
|
12721
12726
|
}
|
|
12722
12727
|
}
|
|
12723
|
-
return [
|
|
12728
|
+
return [
|
|
12729
|
+
changes,
|
|
12730
|
+
pairs,
|
|
12731
|
+
diff,
|
|
12732
|
+
nodesToBeRemoved,
|
|
12733
|
+
fromIdx,
|
|
12734
|
+
mergeLevel,
|
|
12735
|
+
preTombstoned
|
|
12736
|
+
];
|
|
12724
12737
|
}
|
|
12725
12738
|
/**
|
|
12726
12739
|
* `editT` edits the given range with the given value.
|
|
@@ -13161,11 +13174,35 @@ class CRDTTree extends CRDTElement {
|
|
|
13161
13174
|
return [prev, prev.isText ? TokenType.Text : TokenType.End];
|
|
13162
13175
|
}
|
|
13163
13176
|
}
|
|
13164
|
-
function
|
|
13165
|
-
|
|
13177
|
+
function cloneAndDropPreTombstoned(node, preTombstoned) {
|
|
13178
|
+
const clone = node.deepcopy();
|
|
13179
|
+
filterChildren(clone, preTombstoned);
|
|
13180
|
+
traverseAll(clone, (n) => {
|
|
13166
13181
|
n.removedAt = void 0;
|
|
13167
|
-
n.
|
|
13182
|
+
if (n.isText) {
|
|
13183
|
+
n.visibleSize = n.value.length;
|
|
13184
|
+
n.totalSize = n.value.length;
|
|
13185
|
+
return;
|
|
13186
|
+
}
|
|
13187
|
+
let size = 0;
|
|
13188
|
+
for (const child of n._children) size += child.paddedSize();
|
|
13189
|
+
n.visibleSize = size;
|
|
13190
|
+
n.totalSize = size;
|
|
13168
13191
|
});
|
|
13192
|
+
return clone;
|
|
13193
|
+
}
|
|
13194
|
+
function filterChildren(node, preTombstoned) {
|
|
13195
|
+
const all = node._children;
|
|
13196
|
+
if (!all) return;
|
|
13197
|
+
const kept = [];
|
|
13198
|
+
for (const child of all) {
|
|
13199
|
+
if (preTombstoned.has(child.id.toIDString())) {
|
|
13200
|
+
continue;
|
|
13201
|
+
}
|
|
13202
|
+
filterChildren(child, preTombstoned);
|
|
13203
|
+
kept.push(child);
|
|
13204
|
+
}
|
|
13205
|
+
node._children = kept;
|
|
13169
13206
|
}
|
|
13170
13207
|
class TreeEditOperation extends Operation {
|
|
13171
13208
|
fromPos;
|
|
@@ -13237,7 +13274,15 @@ class TreeEditOperation extends Operation {
|
|
|
13237
13274
|
this.toPos = tree.findPos(this.toIdx);
|
|
13238
13275
|
}
|
|
13239
13276
|
}
|
|
13240
|
-
const [
|
|
13277
|
+
const [
|
|
13278
|
+
changes,
|
|
13279
|
+
pairs,
|
|
13280
|
+
diff,
|
|
13281
|
+
removedNodes,
|
|
13282
|
+
preEditFromIdx,
|
|
13283
|
+
mergeLevel,
|
|
13284
|
+
preTombstoned
|
|
13285
|
+
] = tree.edit(
|
|
13241
13286
|
[this.fromPos, this.toPos],
|
|
13242
13287
|
this.contents?.map((content) => content.deepcopy()),
|
|
13243
13288
|
this.splitLevel,
|
|
@@ -13272,7 +13317,13 @@ class TreeEditOperation extends Operation {
|
|
|
13272
13317
|
let reverseOp;
|
|
13273
13318
|
const isPureSplit = this.splitLevel > 0 && !this.contents?.length && removedNodes.length === 0;
|
|
13274
13319
|
if (this.splitLevel === 0) {
|
|
13275
|
-
reverseOp = this.toReverseOperation(
|
|
13320
|
+
reverseOp = this.toReverseOperation(
|
|
13321
|
+
tree,
|
|
13322
|
+
removedNodes,
|
|
13323
|
+
preEditFromIdx,
|
|
13324
|
+
preTombstoned,
|
|
13325
|
+
mergeLevel
|
|
13326
|
+
);
|
|
13276
13327
|
} else if (isPureSplit) {
|
|
13277
13328
|
reverseOp = this.toSplitReverseOperation(tree, preEditFromIdx);
|
|
13278
13329
|
}
|
|
@@ -13310,7 +13361,7 @@ class TreeEditOperation extends Operation {
|
|
|
13310
13361
|
* @param removedNodes - Nodes that were removed by this edit
|
|
13311
13362
|
* @param preEditFromIdx - The from index captured BEFORE the edit
|
|
13312
13363
|
*/
|
|
13313
|
-
toReverseOperation(tree, removedNodes, preEditFromIdx) {
|
|
13364
|
+
toReverseOperation(tree, removedNodes, preEditFromIdx, preTombstoned, mergeLevel) {
|
|
13314
13365
|
if (this.redoSplitLevel !== void 0 && this.redoSplitLevel > 0) {
|
|
13315
13366
|
const splitRedoFromPos = tree.findPos(preEditFromIdx);
|
|
13316
13367
|
const splitRedoOp = TreeEditOperation.create(
|
|
@@ -13329,19 +13380,36 @@ class TreeEditOperation extends Operation {
|
|
|
13329
13380
|
);
|
|
13330
13381
|
return splitRedoOp;
|
|
13331
13382
|
}
|
|
13383
|
+
if (mergeLevel && mergeLevel > 0) {
|
|
13384
|
+
const splitFromPos = tree.findPos(preEditFromIdx);
|
|
13385
|
+
const splitUndoOp = TreeEditOperation.create(
|
|
13386
|
+
this.getParentCreatedAt(),
|
|
13387
|
+
splitFromPos,
|
|
13388
|
+
splitFromPos,
|
|
13389
|
+
void 0,
|
|
13390
|
+
// no inserted content — split creates boundaries
|
|
13391
|
+
mergeLevel,
|
|
13392
|
+
// splitLevel = number of merged boundaries
|
|
13393
|
+
void 0,
|
|
13394
|
+
// executedAt assigned at undo time
|
|
13395
|
+
true,
|
|
13396
|
+
// isUndoOp
|
|
13397
|
+
preEditFromIdx,
|
|
13398
|
+
preEditFromIdx
|
|
13399
|
+
);
|
|
13400
|
+
return splitUndoOp;
|
|
13401
|
+
}
|
|
13332
13402
|
const insertedContentSize = this.contents ? this.contents.reduce((sum, node) => sum + node.paddedSize(), 0) : 0;
|
|
13333
13403
|
const maxNeededIdx = preEditFromIdx + insertedContentSize;
|
|
13334
13404
|
if (maxNeededIdx > tree.getSize()) {
|
|
13335
13405
|
return void 0;
|
|
13336
13406
|
}
|
|
13337
13407
|
const topLevelRemoved = removedNodes.filter(
|
|
13338
|
-
(node) => !node.parent || !removedNodes.includes(node.parent)
|
|
13408
|
+
(node) => !preTombstoned.has(node.id.toIDString()) && (!node.parent || !removedNodes.includes(node.parent))
|
|
13339
13409
|
);
|
|
13340
|
-
const reverseContents = topLevelRemoved.length > 0 ? topLevelRemoved.map(
|
|
13341
|
-
|
|
13342
|
-
|
|
13343
|
-
return clone;
|
|
13344
|
-
}) : void 0;
|
|
13410
|
+
const reverseContents = topLevelRemoved.length > 0 ? topLevelRemoved.map(
|
|
13411
|
+
(n) => cloneAndDropPreTombstoned(n, preTombstoned)
|
|
13412
|
+
) : void 0;
|
|
13345
13413
|
const reverseFromPos = tree.findPos(preEditFromIdx);
|
|
13346
13414
|
let reverseToPos;
|
|
13347
13415
|
if (insertedContentSize > 0) {
|
|
@@ -20160,6 +20228,11 @@ class Document {
|
|
|
20160
20228
|
}
|
|
20161
20229
|
]);
|
|
20162
20230
|
}
|
|
20231
|
+
/**
|
|
20232
|
+
* `clearHistory` flushes both undo and redo stacks. This is used
|
|
20233
|
+
* after applying a snapshot or initialRoot so that setup operations
|
|
20234
|
+
* are not reachable via undo.
|
|
20235
|
+
*/
|
|
20163
20236
|
clearHistory() {
|
|
20164
20237
|
this.internalHistory.clearRedo();
|
|
20165
20238
|
this.internalHistory.clearUndo();
|
|
@@ -20619,10 +20692,7 @@ class Document {
|
|
|
20619
20692
|
}
|
|
20620
20693
|
const ops = isUndo ? this.internalHistory.popUndo() : this.internalHistory.popRedo();
|
|
20621
20694
|
if (!ops) {
|
|
20622
|
-
|
|
20623
|
-
Code.ErrRefused,
|
|
20624
|
-
`There is no operation to be ${isUndo ? "undone" : "redone"}`
|
|
20625
|
-
);
|
|
20695
|
+
return;
|
|
20626
20696
|
}
|
|
20627
20697
|
this.ensureClone();
|
|
20628
20698
|
const ctx = ChangeContext.create(
|
|
@@ -20715,6 +20785,8 @@ class Attachment {
|
|
|
20715
20785
|
syncMode;
|
|
20716
20786
|
changeEventReceived;
|
|
20717
20787
|
lastHeartbeatTime;
|
|
20788
|
+
pollInterval;
|
|
20789
|
+
pollIntervalPinned;
|
|
20718
20790
|
reconnectStreamDelay;
|
|
20719
20791
|
cancelled;
|
|
20720
20792
|
watchStream;
|
|
@@ -20722,13 +20794,15 @@ class Attachment {
|
|
|
20722
20794
|
watchAbortController;
|
|
20723
20795
|
syncPromise;
|
|
20724
20796
|
_detaching = false;
|
|
20725
|
-
constructor(reconnectStreamDelay, resource, resourceID, syncMode) {
|
|
20797
|
+
constructor(reconnectStreamDelay, resource, resourceID, syncMode, pollInterval = 0, pollIntervalPinned = false) {
|
|
20726
20798
|
this.reconnectStreamDelay = reconnectStreamDelay;
|
|
20727
20799
|
this.resource = resource;
|
|
20728
20800
|
this.resourceID = resourceID;
|
|
20729
20801
|
this.syncMode = syncMode;
|
|
20730
20802
|
this.changeEventReceived = syncMode !== void 0 ? false : void 0;
|
|
20731
20803
|
this.lastHeartbeatTime = Date.now();
|
|
20804
|
+
this.pollInterval = pollInterval;
|
|
20805
|
+
this.pollIntervalPinned = pollIntervalPinned;
|
|
20732
20806
|
this.cancelled = false;
|
|
20733
20807
|
}
|
|
20734
20808
|
/**
|
|
@@ -20748,6 +20822,9 @@ class Attachment {
|
|
|
20748
20822
|
if (this.syncMode === SyncMode.RealtimePushOnly) {
|
|
20749
20823
|
return this.resource.hasLocalChanges();
|
|
20750
20824
|
}
|
|
20825
|
+
if (this.syncMode === SyncMode.Polling) {
|
|
20826
|
+
return Date.now() - this.lastHeartbeatTime >= this.pollInterval;
|
|
20827
|
+
}
|
|
20751
20828
|
return this.syncMode !== SyncMode.Manual && (this.resource.hasLocalChanges() || (this.changeEventReceived ?? false));
|
|
20752
20829
|
}
|
|
20753
20830
|
/**
|
|
@@ -20761,7 +20838,8 @@ class Attachment {
|
|
|
20761
20838
|
if (this.syncMode === SyncMode.Manual) {
|
|
20762
20839
|
return false;
|
|
20763
20840
|
}
|
|
20764
|
-
|
|
20841
|
+
const interval = this.pollInterval > 0 ? this.pollInterval : heartbeatInterval;
|
|
20842
|
+
return Date.now() - this.lastHeartbeatTime >= interval;
|
|
20765
20843
|
}
|
|
20766
20844
|
/**
|
|
20767
20845
|
* `updateHeartbeatTime` updates the last heartbeat time.
|
|
@@ -20840,6 +20918,16 @@ class Attachment {
|
|
|
20840
20918
|
}
|
|
20841
20919
|
}
|
|
20842
20920
|
}
|
|
20921
|
+
/**
|
|
20922
|
+
* `resetCancelled` clears the cancelled flag so the watch loop can run again
|
|
20923
|
+
* after a previous cancellation (e.g., after changeSyncMode back to Realtime).
|
|
20924
|
+
* Caller must invoke `runWatchLoop` immediately after to claim the stream slot;
|
|
20925
|
+
* `doLoop`'s `if (this.watchStream)` guard prevents double-stream creation if a
|
|
20926
|
+
* delayed `onDisconnect` callback from the previously-cancelled stream races.
|
|
20927
|
+
*/
|
|
20928
|
+
resetCancelled() {
|
|
20929
|
+
this.cancelled = false;
|
|
20930
|
+
}
|
|
20843
20931
|
/**
|
|
20844
20932
|
* `cancelWatchStream` cancels the watch stream.
|
|
20845
20933
|
*/
|
|
@@ -20874,7 +20962,7 @@ function createAuthInterceptor(apiKey, token) {
|
|
|
20874
20962
|
};
|
|
20875
20963
|
}
|
|
20876
20964
|
const name = "@yorkie-js/sdk";
|
|
20877
|
-
const version = "0.7.
|
|
20965
|
+
const version = "0.7.8";
|
|
20878
20966
|
const pkg = {
|
|
20879
20967
|
name,
|
|
20880
20968
|
version
|
|
@@ -21157,6 +21245,7 @@ var SyncMode = /* @__PURE__ */ ((SyncMode2) => {
|
|
|
21157
21245
|
SyncMode2["Realtime"] = "realtime";
|
|
21158
21246
|
SyncMode2["RealtimePushOnly"] = "realtime-pushonly";
|
|
21159
21247
|
SyncMode2["RealtimeSyncOff"] = "realtime-syncoff";
|
|
21248
|
+
SyncMode2["Polling"] = "polling";
|
|
21160
21249
|
return SyncMode2;
|
|
21161
21250
|
})(SyncMode || {});
|
|
21162
21251
|
var ClientStatus = /* @__PURE__ */ ((ClientStatus2) => {
|
|
@@ -21169,6 +21258,7 @@ var ClientCondition = /* @__PURE__ */ ((ClientCondition2) => {
|
|
|
21169
21258
|
ClientCondition2["WatchLoop"] = "WatchLoop";
|
|
21170
21259
|
return ClientCondition2;
|
|
21171
21260
|
})(ClientCondition || {});
|
|
21261
|
+
const DefaultPollingIntervalMs = 3e3;
|
|
21172
21262
|
const DefaultClientOptions = {
|
|
21173
21263
|
rpcAddr: "https://api.yorkie.dev",
|
|
21174
21264
|
syncLoopDuration: 50,
|
|
@@ -21364,6 +21454,14 @@ class Client {
|
|
|
21364
21454
|
doc.setActor(this.id);
|
|
21365
21455
|
doc.update((_, p) => p.set(opts.initialPresence || {}));
|
|
21366
21456
|
const syncMode = opts.syncMode ?? "realtime";
|
|
21457
|
+
if (opts.documentPollInterval !== void 0 && opts.documentPollInterval <= 0) {
|
|
21458
|
+
throw new YorkieError(
|
|
21459
|
+
Code.ErrInvalidArgument,
|
|
21460
|
+
"documentPollInterval must be greater than 0"
|
|
21461
|
+
);
|
|
21462
|
+
}
|
|
21463
|
+
const pollIntervalPinned = opts.documentPollInterval !== void 0;
|
|
21464
|
+
const pollInterval = pollIntervalPinned ? opts.documentPollInterval : syncMode === "polling" ? DefaultPollingIntervalMs : 0;
|
|
21367
21465
|
return this.enqueueTask(async () => {
|
|
21368
21466
|
try {
|
|
21369
21467
|
const res = await this.rpcClient.attachDocument(
|
|
@@ -21393,10 +21491,12 @@ class Client {
|
|
|
21393
21491
|
this.reconnectStreamDelay,
|
|
21394
21492
|
doc,
|
|
21395
21493
|
res.documentId,
|
|
21396
|
-
syncMode
|
|
21494
|
+
syncMode,
|
|
21495
|
+
pollInterval,
|
|
21496
|
+
pollIntervalPinned
|
|
21397
21497
|
)
|
|
21398
21498
|
);
|
|
21399
|
-
if (syncMode !== "manual") {
|
|
21499
|
+
if (syncMode !== "manual" && syncMode !== "polling") {
|
|
21400
21500
|
await this.runWatchLoop(doc.getKey());
|
|
21401
21501
|
}
|
|
21402
21502
|
logger.info(`[AD] c:"${this.getKey()}" attaches d:"${doc.getKey()}"`);
|
|
@@ -21412,6 +21512,7 @@ class Client {
|
|
|
21412
21512
|
}
|
|
21413
21513
|
});
|
|
21414
21514
|
}
|
|
21515
|
+
doc.clearHistory();
|
|
21415
21516
|
return doc;
|
|
21416
21517
|
} catch (err) {
|
|
21417
21518
|
logger.error(`[AD] c:"${this.getKey()}" err :`, err);
|
|
@@ -21522,12 +21623,23 @@ class Client {
|
|
|
21522
21623
|
channel.setSessionID(res.sessionId);
|
|
21523
21624
|
channel.updateSessionCount(Number(res.sessionCount), 0);
|
|
21524
21625
|
channel.applyStatus(ChannelStatus.Attached);
|
|
21525
|
-
const syncMode = opts.
|
|
21626
|
+
const syncMode = opts.syncMode ?? "realtime";
|
|
21627
|
+
this.assertValidChannelSyncMode(syncMode);
|
|
21628
|
+
if (opts.channelHeartbeatInterval !== void 0 && opts.channelHeartbeatInterval <= 0) {
|
|
21629
|
+
throw new YorkieError(
|
|
21630
|
+
Code.ErrInvalidArgument,
|
|
21631
|
+
"channelHeartbeatInterval must be greater than 0"
|
|
21632
|
+
);
|
|
21633
|
+
}
|
|
21634
|
+
const pollIntervalPinned = opts.channelHeartbeatInterval !== void 0;
|
|
21635
|
+
const pollInterval = pollIntervalPinned ? opts.channelHeartbeatInterval : syncMode === "polling" ? DefaultPollingIntervalMs : this.channelHeartbeatInterval;
|
|
21526
21636
|
const attachment = new Attachment(
|
|
21527
21637
|
this.reconnectStreamDelay,
|
|
21528
21638
|
channel,
|
|
21529
21639
|
res.sessionId,
|
|
21530
|
-
syncMode
|
|
21640
|
+
syncMode,
|
|
21641
|
+
pollInterval,
|
|
21642
|
+
pollIntervalPinned
|
|
21531
21643
|
);
|
|
21532
21644
|
channel.subscribe("local-broadcast", (event) => {
|
|
21533
21645
|
const { topic, payload, options } = event;
|
|
@@ -21603,9 +21715,17 @@ class Client {
|
|
|
21603
21715
|
return this.enqueueTask(task);
|
|
21604
21716
|
}
|
|
21605
21717
|
/**
|
|
21606
|
-
* `changeSyncMode` changes the synchronization mode of the given
|
|
21718
|
+
* `changeSyncMode` changes the synchronization mode of the given resource.
|
|
21607
21719
|
*/
|
|
21608
|
-
async changeSyncMode(
|
|
21720
|
+
async changeSyncMode(resource, syncMode) {
|
|
21721
|
+
return this.enqueueTask(async () => {
|
|
21722
|
+
if (resource instanceof Channel2) {
|
|
21723
|
+
return this.changeChannelSyncMode(resource, syncMode);
|
|
21724
|
+
}
|
|
21725
|
+
return this.changeDocumentSyncMode(resource, syncMode);
|
|
21726
|
+
});
|
|
21727
|
+
}
|
|
21728
|
+
async changeDocumentSyncMode(doc, syncMode) {
|
|
21609
21729
|
if (!this.isActive()) {
|
|
21610
21730
|
throw new YorkieError(
|
|
21611
21731
|
Code.ErrClientNotActivated,
|
|
@@ -21623,19 +21743,66 @@ class Client {
|
|
|
21623
21743
|
if (prevSyncMode === syncMode) {
|
|
21624
21744
|
return doc;
|
|
21625
21745
|
}
|
|
21626
|
-
|
|
21627
|
-
if (syncMode === "manual") {
|
|
21746
|
+
if (syncMode === "manual" || syncMode === "polling") {
|
|
21628
21747
|
attachment.cancelWatchStream();
|
|
21629
|
-
return doc;
|
|
21630
21748
|
}
|
|
21749
|
+
attachment.changeSyncMode(syncMode);
|
|
21631
21750
|
if (syncMode === "realtime") {
|
|
21632
21751
|
attachment.changeEventReceived = true;
|
|
21633
21752
|
}
|
|
21634
|
-
if (
|
|
21753
|
+
if (!attachment.pollIntervalPinned) {
|
|
21754
|
+
attachment.pollInterval = syncMode === "polling" ? DefaultPollingIntervalMs : 0;
|
|
21755
|
+
}
|
|
21756
|
+
if ((prevSyncMode === "manual" || prevSyncMode === "polling") && syncMode !== "manual" && syncMode !== "polling") {
|
|
21757
|
+
attachment.resetCancelled();
|
|
21635
21758
|
await this.runWatchLoop(doc.getKey());
|
|
21636
21759
|
}
|
|
21637
21760
|
return doc;
|
|
21638
21761
|
}
|
|
21762
|
+
/**
|
|
21763
|
+
* `assertValidChannelSyncMode` rejects sync modes that are not valid for
|
|
21764
|
+
* channels. `RealtimePushOnly` and `RealtimeSyncOff` are document-only.
|
|
21765
|
+
*/
|
|
21766
|
+
assertValidChannelSyncMode(syncMode) {
|
|
21767
|
+
if (syncMode !== "manual" && syncMode !== "realtime" && syncMode !== "polling") {
|
|
21768
|
+
throw new YorkieError(
|
|
21769
|
+
Code.ErrInvalidArgument,
|
|
21770
|
+
`invalid channel sync mode: ${syncMode}`
|
|
21771
|
+
);
|
|
21772
|
+
}
|
|
21773
|
+
}
|
|
21774
|
+
async changeChannelSyncMode(channel, syncMode) {
|
|
21775
|
+
if (!this.isActive()) {
|
|
21776
|
+
throw new YorkieError(
|
|
21777
|
+
Code.ErrClientNotActivated,
|
|
21778
|
+
`${this.key} is not active`
|
|
21779
|
+
);
|
|
21780
|
+
}
|
|
21781
|
+
const attachment = this.attachmentMap.get(channel.getKey());
|
|
21782
|
+
if (!attachment) {
|
|
21783
|
+
throw new YorkieError(
|
|
21784
|
+
Code.ErrNotAttached,
|
|
21785
|
+
`${channel.getKey()} is not attached`
|
|
21786
|
+
);
|
|
21787
|
+
}
|
|
21788
|
+
const prevSyncMode = attachment.syncMode;
|
|
21789
|
+
if (prevSyncMode === syncMode) {
|
|
21790
|
+
return channel;
|
|
21791
|
+
}
|
|
21792
|
+
this.assertValidChannelSyncMode(syncMode);
|
|
21793
|
+
if (prevSyncMode === "realtime") {
|
|
21794
|
+
attachment.cancelWatchStream();
|
|
21795
|
+
}
|
|
21796
|
+
attachment.changeSyncMode(syncMode);
|
|
21797
|
+
if (!attachment.pollIntervalPinned) {
|
|
21798
|
+
attachment.pollInterval = syncMode === "polling" ? DefaultPollingIntervalMs : syncMode === "realtime" ? this.channelHeartbeatInterval : 0;
|
|
21799
|
+
}
|
|
21800
|
+
if (syncMode === "realtime") {
|
|
21801
|
+
attachment.resetCancelled();
|
|
21802
|
+
await this.runWatchLoop(channel.getKey());
|
|
21803
|
+
}
|
|
21804
|
+
return channel;
|
|
21805
|
+
}
|
|
21639
21806
|
/**
|
|
21640
21807
|
* `sync` implementation that handles both Document and Channel.
|
|
21641
21808
|
*/
|
|
@@ -22480,6 +22647,7 @@ class Client {
|
|
|
22480
22647
|
return doc;
|
|
22481
22648
|
}
|
|
22482
22649
|
doc.applyChangePack(respPack);
|
|
22650
|
+
attachment.updateHeartbeatTime();
|
|
22483
22651
|
attachment.resource.publish([
|
|
22484
22652
|
{
|
|
22485
22653
|
type: DocEventType.SyncStatusChanged,
|