@loro-dev/flock-wasm 0.1.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/LICENSE +661 -0
- package/README.md +26 -0
- package/base64/event-batcher.d.ts +37 -0
- package/base64/event-batcher.js +204 -0
- package/base64/flock_wasm.js +1158 -0
- package/base64/index.d.ts +268 -0
- package/base64/index.js +1012 -0
- package/base64/wasm.d.ts +37 -0
- package/base64/wasm.js +15 -0
- package/bundler/event-batcher.d.ts +37 -0
- package/bundler/event-batcher.js +204 -0
- package/bundler/flock_wasm.d.ts +313 -0
- package/bundler/flock_wasm.js +5 -0
- package/bundler/flock_wasm_bg.js +1119 -0
- package/bundler/flock_wasm_bg.wasm +0 -0
- package/bundler/flock_wasm_bg.wasm.d.ts +40 -0
- package/bundler/index.d.ts +268 -0
- package/bundler/index.js +1012 -0
- package/bundler/wasm.d.ts +37 -0
- package/bundler/wasm.js +4 -0
- package/nodejs/event-batcher.d.ts +37 -0
- package/nodejs/event-batcher.js +208 -0
- package/nodejs/flock_wasm.d.ts +313 -0
- package/nodejs/flock_wasm.js +1126 -0
- package/nodejs/flock_wasm_bg.wasm +0 -0
- package/nodejs/flock_wasm_bg.wasm.d.ts +40 -0
- package/nodejs/index.d.ts +268 -0
- package/nodejs/index.js +1018 -0
- package/nodejs/package.json +1 -0
- package/nodejs/wasm.d.ts +37 -0
- package/nodejs/wasm.js +2 -0
- package/package.json +50 -0
- package/web/event-batcher.d.ts +37 -0
- package/web/event-batcher.js +204 -0
- package/web/flock_wasm.d.ts +377 -0
- package/web/flock_wasm.js +1158 -0
- package/web/flock_wasm_bg.wasm +0 -0
- package/web/flock_wasm_bg.wasm.d.ts +40 -0
- package/web/index.d.ts +268 -0
- package/web/index.js +1012 -0
- package/web/wasm.d.ts +37 -0
- package/web/wasm.js +4 -0
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# @loro-dev/flock-wasm
|
|
2
|
+
|
|
3
|
+
WASM bindings for `flock-rs`.
|
|
4
|
+
|
|
5
|
+
## Runtime mode
|
|
6
|
+
|
|
7
|
+
- `RawFlock` constructors (`new`, `fromJson`, `fromFile`) use buffered mode by default.
|
|
8
|
+
- Buffered mode means memtable is enabled by default.
|
|
9
|
+
- Buffered writes are flushed on `txnCommit` (and storage transaction commit), or when exporting file bytes.
|
|
10
|
+
- If you need strongest durability semantics, explicitly commit transactions more frequently.
|
|
11
|
+
|
|
12
|
+
## Build
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm -C packages/flock-wasm build
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Test
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm -C packages/flock-wasm test
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## License
|
|
25
|
+
|
|
26
|
+
`@loro-dev/flock-wasm` is licensed under `AGPL-3.0-only`.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type TimeoutHandle = unknown;
|
|
2
|
+
export type EventBatcherRuntime = {
|
|
3
|
+
now(): number;
|
|
4
|
+
setTimeout(fn: () => void, ms: number): TimeoutHandle;
|
|
5
|
+
clearTimeout(handle: TimeoutHandle): void;
|
|
6
|
+
};
|
|
7
|
+
export declare class EventBatcher<TEvent> {
|
|
8
|
+
private outbox;
|
|
9
|
+
private outboxCursor;
|
|
10
|
+
private draining;
|
|
11
|
+
private txnEventSink;
|
|
12
|
+
private debounceState;
|
|
13
|
+
private readonly runtime;
|
|
14
|
+
private readonly emit;
|
|
15
|
+
constructor(options: {
|
|
16
|
+
runtime: EventBatcherRuntime;
|
|
17
|
+
emit: (source: string, events: TEvent[]) => void;
|
|
18
|
+
});
|
|
19
|
+
handleCommitEvents(source: string, events: TEvent[], bufferable: boolean): void;
|
|
20
|
+
beforeImport(): void;
|
|
21
|
+
txn<T>(callback: () => Promise<T>): Promise<T>;
|
|
22
|
+
isInTxn(): boolean;
|
|
23
|
+
autoDebounceCommit(timeout: number, options?: {
|
|
24
|
+
maxDebounceTime?: number;
|
|
25
|
+
}): void;
|
|
26
|
+
disableAutoDebounceCommit(): void;
|
|
27
|
+
commit(): void;
|
|
28
|
+
isAutoDebounceActive(): boolean;
|
|
29
|
+
close(): void;
|
|
30
|
+
private enqueueBatch;
|
|
31
|
+
private drainOutbox;
|
|
32
|
+
private deliverBatch;
|
|
33
|
+
private bufferLocalEvents;
|
|
34
|
+
private scheduleDebounceFlush;
|
|
35
|
+
private takeDebouncedEvents;
|
|
36
|
+
private flushDebouncedEvents;
|
|
37
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
export class EventBatcher {
|
|
2
|
+
outbox = [];
|
|
3
|
+
outboxCursor = 0;
|
|
4
|
+
draining = false;
|
|
5
|
+
txnEventSink;
|
|
6
|
+
debounceState;
|
|
7
|
+
runtime;
|
|
8
|
+
emit;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.runtime = options.runtime;
|
|
11
|
+
this.emit = options.emit;
|
|
12
|
+
this.txnEventSink = undefined;
|
|
13
|
+
this.debounceState = undefined;
|
|
14
|
+
}
|
|
15
|
+
handleCommitEvents(source, events, bufferable) {
|
|
16
|
+
if (events.length === 0) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (bufferable && this.txnEventSink) {
|
|
20
|
+
this.txnEventSink.push(...events);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (bufferable && this.debounceState) {
|
|
24
|
+
this.bufferLocalEvents(events);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
this.enqueueBatch({ source, events });
|
|
28
|
+
}
|
|
29
|
+
beforeImport() {
|
|
30
|
+
if (this.debounceState !== undefined) {
|
|
31
|
+
this.commit();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async txn(callback) {
|
|
35
|
+
if (this.txnEventSink !== undefined) {
|
|
36
|
+
throw new Error("Nested transactions are not supported");
|
|
37
|
+
}
|
|
38
|
+
if (this.debounceState !== undefined) {
|
|
39
|
+
throw new Error("Cannot start transaction while autoDebounceCommit is active");
|
|
40
|
+
}
|
|
41
|
+
const eventSink = [];
|
|
42
|
+
this.txnEventSink = eventSink;
|
|
43
|
+
try {
|
|
44
|
+
const result = await callback();
|
|
45
|
+
if (eventSink.length > 0) {
|
|
46
|
+
this.txnEventSink = undefined;
|
|
47
|
+
this.enqueueBatch({ source: "local", events: eventSink });
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
this.txnEventSink = undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
isInTxn() {
|
|
56
|
+
return this.txnEventSink !== undefined;
|
|
57
|
+
}
|
|
58
|
+
autoDebounceCommit(timeout, options) {
|
|
59
|
+
if (this.txnEventSink !== undefined) {
|
|
60
|
+
throw new Error("Cannot enable autoDebounceCommit while transaction is active");
|
|
61
|
+
}
|
|
62
|
+
if (this.debounceState !== undefined) {
|
|
63
|
+
throw new Error("autoDebounceCommit is already active");
|
|
64
|
+
}
|
|
65
|
+
const maxDebounceTime = options?.maxDebounceTime ?? 10000;
|
|
66
|
+
this.debounceState = {
|
|
67
|
+
timeout,
|
|
68
|
+
maxDebounceTime,
|
|
69
|
+
timerId: undefined,
|
|
70
|
+
deadlineAt: undefined,
|
|
71
|
+
pendingEvents: [],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
disableAutoDebounceCommit() {
|
|
75
|
+
if (this.debounceState === undefined) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const eventsToEmit = this.takeDebouncedEvents();
|
|
79
|
+
this.debounceState = undefined;
|
|
80
|
+
if (eventsToEmit.length > 0) {
|
|
81
|
+
this.enqueueBatch({ source: "local", events: eventsToEmit });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
commit() {
|
|
85
|
+
if (this.debounceState === undefined) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.flushDebouncedEvents();
|
|
89
|
+
}
|
|
90
|
+
isAutoDebounceActive() {
|
|
91
|
+
return this.debounceState !== undefined;
|
|
92
|
+
}
|
|
93
|
+
close() {
|
|
94
|
+
// Commit any pending debounced events
|
|
95
|
+
if (this.debounceState !== undefined) {
|
|
96
|
+
this.disableAutoDebounceCommit();
|
|
97
|
+
}
|
|
98
|
+
// Commit any transaction events (edge case: close during txn)
|
|
99
|
+
if (this.txnEventSink !== undefined) {
|
|
100
|
+
const pending = this.txnEventSink;
|
|
101
|
+
this.txnEventSink = undefined;
|
|
102
|
+
if (pending.length > 0) {
|
|
103
|
+
const eventsToEmit = pending.slice();
|
|
104
|
+
pending.length = 0;
|
|
105
|
+
this.enqueueBatch({ source: "local", events: eventsToEmit });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
enqueueBatch(batch) {
|
|
110
|
+
this.outbox.push(batch);
|
|
111
|
+
this.drainOutbox();
|
|
112
|
+
}
|
|
113
|
+
drainOutbox() {
|
|
114
|
+
if (this.draining) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.draining = true;
|
|
118
|
+
try {
|
|
119
|
+
while (this.outboxCursor < this.outbox.length) {
|
|
120
|
+
const batch = this.outbox[this.outboxCursor];
|
|
121
|
+
this.outboxCursor += 1;
|
|
122
|
+
this.deliverBatch(batch);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
this.outbox = [];
|
|
127
|
+
this.outboxCursor = 0;
|
|
128
|
+
this.draining = false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
deliverBatch(batch) {
|
|
132
|
+
try {
|
|
133
|
+
this.emit(batch.source, batch.events);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
void error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
bufferLocalEvents(events) {
|
|
140
|
+
if (this.debounceState === undefined) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (events.length === 0) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const state = this.debounceState;
|
|
147
|
+
const now = this.runtime.now();
|
|
148
|
+
if (state.pendingEvents.length === 0) {
|
|
149
|
+
state.deadlineAt = now + state.maxDebounceTime;
|
|
150
|
+
}
|
|
151
|
+
state.pendingEvents.push(...events);
|
|
152
|
+
this.scheduleDebounceFlush(now);
|
|
153
|
+
}
|
|
154
|
+
scheduleDebounceFlush(now) {
|
|
155
|
+
if (this.debounceState === undefined) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const state = this.debounceState;
|
|
159
|
+
if (state.pendingEvents.length === 0) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (state.timeout <= 0) {
|
|
163
|
+
this.flushDebouncedEvents();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const deadlineAt = state.deadlineAt ?? now + state.maxDebounceTime;
|
|
167
|
+
state.deadlineAt = deadlineAt;
|
|
168
|
+
const dueAt = Math.min(now + state.timeout, deadlineAt);
|
|
169
|
+
if (dueAt <= now) {
|
|
170
|
+
this.flushDebouncedEvents();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (state.timerId !== undefined) {
|
|
174
|
+
this.runtime.clearTimeout(state.timerId);
|
|
175
|
+
}
|
|
176
|
+
state.timerId = this.runtime.setTimeout(() => {
|
|
177
|
+
this.flushDebouncedEvents();
|
|
178
|
+
}, Math.max(0, dueAt - now));
|
|
179
|
+
}
|
|
180
|
+
takeDebouncedEvents() {
|
|
181
|
+
if (this.debounceState === undefined) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
const state = this.debounceState;
|
|
185
|
+
if (state.timerId !== undefined) {
|
|
186
|
+
this.runtime.clearTimeout(state.timerId);
|
|
187
|
+
state.timerId = undefined;
|
|
188
|
+
}
|
|
189
|
+
state.deadlineAt = undefined;
|
|
190
|
+
if (state.pendingEvents.length === 0) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
const eventsToEmit = state.pendingEvents;
|
|
194
|
+
state.pendingEvents = [];
|
|
195
|
+
return eventsToEmit;
|
|
196
|
+
}
|
|
197
|
+
flushDebouncedEvents() {
|
|
198
|
+
const eventsToEmit = this.takeDebouncedEvents();
|
|
199
|
+
if (eventsToEmit.length === 0) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
this.enqueueBatch({ source: "local", events: eventsToEmit });
|
|
203
|
+
}
|
|
204
|
+
}
|