@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.
Files changed (42) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +26 -0
  3. package/base64/event-batcher.d.ts +37 -0
  4. package/base64/event-batcher.js +204 -0
  5. package/base64/flock_wasm.js +1158 -0
  6. package/base64/index.d.ts +268 -0
  7. package/base64/index.js +1012 -0
  8. package/base64/wasm.d.ts +37 -0
  9. package/base64/wasm.js +15 -0
  10. package/bundler/event-batcher.d.ts +37 -0
  11. package/bundler/event-batcher.js +204 -0
  12. package/bundler/flock_wasm.d.ts +313 -0
  13. package/bundler/flock_wasm.js +5 -0
  14. package/bundler/flock_wasm_bg.js +1119 -0
  15. package/bundler/flock_wasm_bg.wasm +0 -0
  16. package/bundler/flock_wasm_bg.wasm.d.ts +40 -0
  17. package/bundler/index.d.ts +268 -0
  18. package/bundler/index.js +1012 -0
  19. package/bundler/wasm.d.ts +37 -0
  20. package/bundler/wasm.js +4 -0
  21. package/nodejs/event-batcher.d.ts +37 -0
  22. package/nodejs/event-batcher.js +208 -0
  23. package/nodejs/flock_wasm.d.ts +313 -0
  24. package/nodejs/flock_wasm.js +1126 -0
  25. package/nodejs/flock_wasm_bg.wasm +0 -0
  26. package/nodejs/flock_wasm_bg.wasm.d.ts +40 -0
  27. package/nodejs/index.d.ts +268 -0
  28. package/nodejs/index.js +1018 -0
  29. package/nodejs/package.json +1 -0
  30. package/nodejs/wasm.d.ts +37 -0
  31. package/nodejs/wasm.js +2 -0
  32. package/package.json +50 -0
  33. package/web/event-batcher.d.ts +37 -0
  34. package/web/event-batcher.js +204 -0
  35. package/web/flock_wasm.d.ts +377 -0
  36. package/web/flock_wasm.js +1158 -0
  37. package/web/flock_wasm_bg.wasm +0 -0
  38. package/web/flock_wasm_bg.wasm.d.ts +40 -0
  39. package/web/index.d.ts +268 -0
  40. package/web/index.js +1012 -0
  41. package/web/wasm.d.ts +37 -0
  42. package/web/wasm.js +4 -0
@@ -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
+ }
@@ -0,0 +1,313 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /**
4
+ * Initializes the WASM module.
5
+ *
6
+ * This function is automatically called when the WASM module is loaded.
7
+ * It sets up the panic hook to route Rust panics to the JavaScript console.
8
+ */
9
+ export function start(): void;
10
+ /**
11
+ * Drains the pending callback queue, invoking all enqueued callbacks.
12
+ *
13
+ * This function is exported to JavaScript and can be called manually,
14
+ * but is typically invoked automatically via microtasks.
15
+ *
16
+ * Callbacks are invoked sequentially. If any callback throws an error,
17
+ * the error is propagated to JavaScript after the current batch completes.
18
+ */
19
+ export function callPendingEvents(): void;
20
+ /**
21
+ * The main WASM-exposed flock instance.
22
+ *
23
+ * Wraps the underlying [`flock::Flock`] and provides JavaScript-compatible
24
+ * methods for all CRDT operations including put, get, delete, scan, merge,
25
+ * import/export, and event subscription.
26
+ */
27
+ export class RawFlock {
28
+ free(): void;
29
+ /**
30
+ * Commits the current transaction.
31
+ *
32
+ * Flushes all buffered operations and delivers batched events.
33
+ *
34
+ * # Errors
35
+ *
36
+ * Returns an error if no transaction is active or if commit fails.
37
+ */
38
+ txnCommit(): void;
39
+ exportFile(): Uint8Array;
40
+ /**
41
+ * Exports the current state as a JSON bundle.
42
+ *
43
+ * # Arguments
44
+ *
45
+ * * `from` - Optional version vector for incremental export
46
+ * * `prune_tombstones_before` - Optional timestamp to prune tombstones older than this
47
+ * * `peer_id` - Optional peer ID filter to only export entries from this peer
48
+ *
49
+ * # Returns
50
+ *
51
+ * An export bundle containing entries and version information.
52
+ *
53
+ * # Errors
54
+ *
55
+ * Returns an error if arguments are invalid or serialization fails.
56
+ */
57
+ exportJson(from: any, prune_tombstones_before: any, peer_id: any): any;
58
+ /**
59
+ * Imports a JSON bundle into this flock instance.
60
+ *
61
+ * # Arguments
62
+ *
63
+ * * `bundle` - The export bundle to import
64
+ *
65
+ * # Returns
66
+ *
67
+ * An import report containing counts of accepted and skipped entries.
68
+ *
69
+ * # Errors
70
+ *
71
+ * Returns an error if the bundle is invalid or import fails.
72
+ */
73
+ importJson(bundle: any): any;
74
+ /**
75
+ * Sets a new peer ID for this flock instance.
76
+ *
77
+ * # Arguments
78
+ *
79
+ * * `peer_id` - The new peer ID to set
80
+ *
81
+ * # Errors
82
+ *
83
+ * Returns an error if the peer ID is invalid.
84
+ */
85
+ setPeerId(peer_id: string): void;
86
+ /**
87
+ * Unsubscribes from change events.
88
+ *
89
+ * # Arguments
90
+ *
91
+ * * `id` - The subscription ID returned from `subscribe`
92
+ *
93
+ * # Returns
94
+ *
95
+ * `true` if a subscription was removed, `false` if the ID was not found.
96
+ */
97
+ unsubscribe(id: number): boolean;
98
+ /**
99
+ * Rolls back the current transaction.
100
+ *
101
+ * Discards all buffered operations since `txnBegin` was called.
102
+ * Note: Data changes are not rolled back, only event emission is affected.
103
+ */
104
+ txnRollback(): void;
105
+ /**
106
+ * Stores a value with associated metadata at the specified key.
107
+ *
108
+ * # Arguments
109
+ *
110
+ * * `key` - The key as a JSON array
111
+ * * `value` - The value to store
112
+ * * `metadata` - Optional metadata object to associate with this entry
113
+ * * `now` - Optional timestamp in milliseconds
114
+ *
115
+ * # Errors
116
+ *
117
+ * Returns an error if any argument is invalid or the operation fails.
118
+ */
119
+ putWithMeta(key: any, value: any, metadata: any, now: any): void;
120
+ /**
121
+ * Returns the inclusive version vector for this flock.
122
+ *
123
+ * The inclusive version includes all peers ever seen, even if their
124
+ * entries have been overridden by other peers.
125
+ *
126
+ * # Errors
127
+ *
128
+ * Returns an error if serialization fails.
129
+ */
130
+ inclusiveVersion(): any;
131
+ /**
132
+ * Returns the maximum physical time across all entries in this flock.
133
+ *
134
+ * This is useful for determining a safe timestamp for tombstone pruning.
135
+ */
136
+ getMaxPhysicalTime(): number;
137
+ /**
138
+ * Retrieves the value at the specified key.
139
+ *
140
+ * Returns `undefined` if the key doesn't exist or has been deleted.
141
+ *
142
+ * # Arguments
143
+ *
144
+ * * `key` - The key as a JSON array
145
+ *
146
+ * # Errors
147
+ *
148
+ * Returns an error if serialization of the result fails.
149
+ */
150
+ get(key: any): any;
151
+ /**
152
+ * Creates a new WASM flock instance with the given peer ID.
153
+ *
154
+ * The instance is created in buffered mode with memtable enabled.
155
+ * Use transactions (`txnBegin` + `txnCommit`) to flush buffered writes explicitly.
156
+ *
157
+ * # Arguments
158
+ *
159
+ * * `peer_id` - The unique identifier for this peer
160
+ *
161
+ * # Errors
162
+ *
163
+ * Returns an error if the peer ID is invalid or if instance creation fails.
164
+ */
165
+ constructor(peer_id: string);
166
+ /**
167
+ * Stores a value at the specified key.
168
+ *
169
+ * # Arguments
170
+ *
171
+ * * `key` - The key as a JSON array
172
+ * * `value` - The value to store (any JSON-serializable value)
173
+ * * `now` - Optional timestamp in milliseconds (uses current time if undefined)
174
+ *
175
+ * # Errors
176
+ *
177
+ * Returns an error if the key or value is invalid, or if the operation fails.
178
+ */
179
+ put(key: any, value: any, now: any): void;
180
+ /**
181
+ * Scans entries within the specified bounds and prefix.
182
+ *
183
+ * # Arguments
184
+ *
185
+ * * `start` - Optional start bound (inclusive/exclusive/unbounded)
186
+ * * `end` - Optional end bound
187
+ * * `prefix` - Optional key prefix to filter by
188
+ *
189
+ * # Returns
190
+ *
191
+ * An array of scan rows containing keys, values, and raw records.
192
+ *
193
+ * # Errors
194
+ *
195
+ * Returns an error if scan bounds are invalid or serialization fails.
196
+ */
197
+ scan(start: any, end: any, prefix: any): any;
198
+ /**
199
+ * Merges state from another flock instance into this one.
200
+ *
201
+ * # Arguments
202
+ *
203
+ * * `other` - The other flock instance to merge from
204
+ *
205
+ * # Returns
206
+ *
207
+ * An import report containing counts of accepted and skipped entries.
208
+ *
209
+ * # Errors
210
+ *
211
+ * Returns an error if the merge operation fails.
212
+ */
213
+ merge(other: RawFlock): any;
214
+ /**
215
+ * Deletes the value at the specified key (creates a tombstone).
216
+ *
217
+ * # Arguments
218
+ *
219
+ * * `key` - The key as a JSON array
220
+ * * `now` - Optional timestamp in milliseconds
221
+ *
222
+ * # Errors
223
+ *
224
+ * Returns an error if the key is invalid or the operation fails.
225
+ */
226
+ delete(key: any, now: any): void;
227
+ /**
228
+ * Returns the current peer ID of this flock instance.
229
+ */
230
+ peerId(): string;
231
+ /**
232
+ * Returns the exclusive version vector for this flock.
233
+ *
234
+ * The exclusive version only includes peers that have at least one entry
235
+ * in the current state (excluding tombstones and overridden entries).
236
+ *
237
+ * # Errors
238
+ *
239
+ * Returns an error if serialization fails.
240
+ */
241
+ version(): any;
242
+ /**
243
+ * Creates a new flock instance from a file bytes array.
244
+ *
245
+ * # Arguments
246
+ *
247
+ * * `peer_id` - The peer ID for the new instance
248
+ * * `bytes` - The file contents as a Uint8Array
249
+ *
250
+ * # Errors
251
+ *
252
+ * Returns an error if the file format is invalid or loading fails.
253
+ */
254
+ static fromFile(peer_id: string, bytes: Uint8Array): RawFlock;
255
+ /**
256
+ * Creates a new flock instance from a JSON export bundle.
257
+ *
258
+ * # Arguments
259
+ *
260
+ * * `bundle` - The export bundle as a JavaScript object
261
+ * * `peer_id` - The peer ID for the new instance
262
+ *
263
+ * # Errors
264
+ *
265
+ * Returns an error if the bundle is invalid or import fails.
266
+ */
267
+ static fromJson(bundle: any, peer_id: string): RawFlock;
268
+ /**
269
+ * Retrieves complete entry information including data, metadata, and clock.
270
+ *
271
+ * Returns `undefined` if the key doesn't exist.
272
+ *
273
+ * # Arguments
274
+ *
275
+ * * `key` - The key as a JSON array
276
+ *
277
+ * # Errors
278
+ *
279
+ * Returns an error if serialization of the result fails.
280
+ */
281
+ getEntry(key: any): any;
282
+ /**
283
+ * Returns whether a transaction is currently active.
284
+ */
285
+ isInTxn(): boolean;
286
+ /**
287
+ * Subscribes to change events from this flock instance.
288
+ *
289
+ * # Arguments
290
+ *
291
+ * * `listener` - A JavaScript function that will be called with event batches
292
+ *
293
+ * # Returns
294
+ *
295
+ * A subscription ID that can be used to unsubscribe later.
296
+ *
297
+ * # Errors
298
+ *
299
+ * Returns an error if the subscription fails or the ID overflows.
300
+ */
301
+ subscribe(listener: Function): number;
302
+ /**
303
+ * Begins a transaction.
304
+ *
305
+ * All subsequent put/delete operations will be buffered until `txnCommit`
306
+ * is called. Events will be batched and delivered as a single batch on commit.
307
+ *
308
+ * # Errors
309
+ *
310
+ * Returns an error if a transaction is already active or if begin fails.
311
+ */
312
+ txnBegin(): void;
313
+ }
@@ -0,0 +1,5 @@
1
+ import * as wasm from "./flock_wasm_bg.wasm";
2
+ export * from "./flock_wasm_bg.js";
3
+ import { __wbg_set_wasm } from "./flock_wasm_bg.js";
4
+ __wbg_set_wasm(wasm);
5
+ wasm.__wbindgen_start();