@loro-dev/flock-sqlite 0.7.0 → 0.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loro-dev/flock-sqlite",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "SQLite-backed Flock CRDT replica for Node, browsers, and Cloudflare Workers.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -37,10 +37,10 @@
37
37
  "access": "public"
38
38
  },
39
39
  "peerDependencies": {
40
+ "@loro-dev/unisqlite": "^0.4.0",
40
41
  "@sqlite.org/sqlite-wasm": "3.50.1-build1",
41
42
  "better-sqlite3": "^11.7.0",
42
- "broadcast-channel": "^7.0.0",
43
- "@loro-dev/unisqlite": "0.2.0"
43
+ "broadcast-channel": "^7.0.0"
44
44
  },
45
45
  "peerDependenciesMeta": {
46
46
  "@loro-dev/unisqlite": {
@@ -64,7 +64,7 @@
64
64
  "typescript": "^5.9.2",
65
65
  "vitest": "^3.2.4",
66
66
  "@loro-dev/flock": "4.4.0",
67
- "@loro-dev/unisqlite": "0.2.0"
67
+ "@loro-dev/unisqlite": "0.6.0"
68
68
  },
69
69
  "scripts": {
70
70
  "build": "tsdown",
@@ -0,0 +1,189 @@
1
+ import type { FlockRuntime, TimeoutHandle } from "./multi-tab-env";
2
+
3
+ export class EventBatcher<TEvent> {
4
+ private txnEventSink: TEvent[] | undefined;
5
+ private debounceState:
6
+ | {
7
+ timeout: number;
8
+ maxDebounceTime: number;
9
+ timerId: TimeoutHandle;
10
+ maxTimerId: TimeoutHandle;
11
+ pendingEvents: TEvent[];
12
+ }
13
+ | undefined;
14
+
15
+ private readonly runtime: FlockRuntime;
16
+ private readonly emit: (source: string, events: TEvent[]) => void;
17
+
18
+ constructor(options: {
19
+ runtime: FlockRuntime;
20
+ emit: (source: string, events: TEvent[]) => void;
21
+ }) {
22
+ this.runtime = options.runtime;
23
+ this.emit = options.emit;
24
+ this.txnEventSink = undefined;
25
+ this.debounceState = undefined;
26
+ }
27
+
28
+ handleCommitEvents(
29
+ source: string,
30
+ events: TEvent[],
31
+ bufferable: boolean,
32
+ ): void {
33
+ if (events.length === 0) {
34
+ return;
35
+ }
36
+
37
+ if (bufferable && this.txnEventSink) {
38
+ this.txnEventSink.push(...events);
39
+ return;
40
+ }
41
+ if (bufferable && this.debounceState) {
42
+ this.debounceState.pendingEvents.push(...events);
43
+ this.resetDebounceTimer();
44
+ return;
45
+ }
46
+ this.emit(source, events);
47
+ }
48
+
49
+ beforeImport(): void {
50
+ if (this.debounceState !== undefined) {
51
+ this.commit();
52
+ }
53
+ }
54
+
55
+ async txn<T>(callback: () => Promise<T>): Promise<T> {
56
+ if (this.txnEventSink !== undefined) {
57
+ throw new Error("Nested transactions are not supported");
58
+ }
59
+ if (this.debounceState !== undefined) {
60
+ throw new Error(
61
+ "Cannot start transaction while autoDebounceCommit is active",
62
+ );
63
+ }
64
+
65
+ const eventSink: TEvent[] = [];
66
+ this.txnEventSink = eventSink;
67
+
68
+ try {
69
+ const result = await callback();
70
+ if (eventSink.length > 0) {
71
+ this.emit("local", eventSink);
72
+ }
73
+ return result;
74
+ } finally {
75
+ this.txnEventSink = undefined;
76
+ }
77
+ }
78
+
79
+ isInTxn(): boolean {
80
+ return this.txnEventSink !== undefined;
81
+ }
82
+
83
+ autoDebounceCommit(
84
+ timeout: number,
85
+ options?: { maxDebounceTime?: number },
86
+ ): void {
87
+ if (this.txnEventSink !== undefined) {
88
+ throw new Error(
89
+ "Cannot enable autoDebounceCommit while transaction is active",
90
+ );
91
+ }
92
+ if (this.debounceState !== undefined) {
93
+ throw new Error("autoDebounceCommit is already active");
94
+ }
95
+
96
+ const maxDebounceTime = options?.maxDebounceTime ?? 10000;
97
+
98
+ this.debounceState = {
99
+ timeout,
100
+ maxDebounceTime,
101
+ timerId: undefined,
102
+ maxTimerId: undefined,
103
+ pendingEvents: [],
104
+ };
105
+ }
106
+
107
+ disableAutoDebounceCommit(): void {
108
+ if (this.debounceState === undefined) {
109
+ return;
110
+ }
111
+
112
+ const { timerId, maxTimerId, pendingEvents } = this.debounceState;
113
+ if (timerId !== undefined) {
114
+ this.runtime.clearTimeout(timerId);
115
+ }
116
+ if (maxTimerId !== undefined) {
117
+ this.runtime.clearTimeout(maxTimerId);
118
+ }
119
+ this.debounceState = undefined;
120
+
121
+ if (pendingEvents.length > 0) {
122
+ this.emit("local", pendingEvents);
123
+ }
124
+ }
125
+
126
+ commit(): void {
127
+ if (this.debounceState === undefined) {
128
+ return;
129
+ }
130
+
131
+ const { timerId, maxTimerId, pendingEvents } = this.debounceState;
132
+ if (timerId !== undefined) {
133
+ this.runtime.clearTimeout(timerId);
134
+ this.debounceState.timerId = undefined;
135
+ }
136
+ if (maxTimerId !== undefined) {
137
+ this.runtime.clearTimeout(maxTimerId);
138
+ this.debounceState.maxTimerId = undefined;
139
+ }
140
+
141
+ if (pendingEvents.length > 0) {
142
+ this.emit("local", pendingEvents);
143
+ this.debounceState.pendingEvents = [];
144
+ }
145
+ }
146
+
147
+ isAutoDebounceActive(): boolean {
148
+ return this.debounceState !== undefined;
149
+ }
150
+
151
+ close(): void {
152
+ // Commit any pending debounced events
153
+ if (this.debounceState !== undefined) {
154
+ this.disableAutoDebounceCommit();
155
+ }
156
+
157
+ // Commit any transaction events (edge case: close during txn)
158
+ if (this.txnEventSink !== undefined) {
159
+ const pending = this.txnEventSink;
160
+ this.txnEventSink = undefined;
161
+ if (pending.length > 0) {
162
+ this.emit("local", pending);
163
+ }
164
+ }
165
+ }
166
+
167
+ private resetDebounceTimer(): void {
168
+ if (this.debounceState === undefined) {
169
+ return;
170
+ }
171
+
172
+ if (this.debounceState.timerId !== undefined) {
173
+ this.runtime.clearTimeout(this.debounceState.timerId);
174
+ }
175
+
176
+ this.debounceState.timerId = this.runtime.setTimeout(() => {
177
+ this.commit();
178
+ }, this.debounceState.timeout);
179
+
180
+ if (
181
+ this.debounceState.maxTimerId === undefined &&
182
+ this.debounceState.pendingEvents.length === 1
183
+ ) {
184
+ this.debounceState.maxTimerId = this.runtime.setTimeout(() => {
185
+ this.commit();
186
+ }, this.debounceState.maxDebounceTime);
187
+ }
188
+ }
189
+ }