@rivium/sync-node 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/dist/index.js ADDED
@@ -0,0 +1,888 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ RiviumSyncAdmin: () => RiviumSyncAdmin,
34
+ RiviumSyncError: () => RiviumSyncError,
35
+ RiviumSyncErrorCode: () => RiviumSyncErrorCode,
36
+ RiviumSyncLogLevel: () => RiviumSyncLogLevel,
37
+ SyncCollection: () => SyncCollection,
38
+ SyncDatabase: () => SyncDatabase,
39
+ SyncDocumentRef: () => SyncDocumentRef,
40
+ SyncQuery: () => SyncQuery,
41
+ WriteBatch: () => WriteBatch,
42
+ default: () => index_default
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+ var import_mqtt = __toESM(require("mqtt"));
46
+ var RiviumSyncErrorCode = /* @__PURE__ */ ((RiviumSyncErrorCode2) => {
47
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["CONNECTION_FAILED"] = 1e3] = "CONNECTION_FAILED";
48
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["CONNECTION_TIMEOUT"] = 1001] = "CONNECTION_TIMEOUT";
49
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["CONNECTION_LOST"] = 1002] = "CONNECTION_LOST";
50
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["AUTHENTICATION_FAILED"] = 1004] = "AUTHENTICATION_FAILED";
51
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["SUBSCRIPTION_FAILED"] = 1100] = "SUBSCRIPTION_FAILED";
52
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["DATA_FETCH_FAILED"] = 1200] = "DATA_FETCH_FAILED";
53
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["DATA_PARSE_ERROR"] = 1201] = "DATA_PARSE_ERROR";
54
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["DATA_WRITE_FAILED"] = 1202] = "DATA_WRITE_FAILED";
55
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["DATA_DELETE_FAILED"] = 1203] = "DATA_DELETE_FAILED";
56
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["DOCUMENT_NOT_FOUND"] = 1204] = "DOCUMENT_NOT_FOUND";
57
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["INVALID_CONFIG"] = 1300] = "INVALID_CONFIG";
58
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["MISSING_API_KEY"] = 1301] = "MISSING_API_KEY";
59
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["MISSING_SERVER_URL"] = 1302] = "MISSING_SERVER_URL";
60
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["MISSING_SERVER_SECRET"] = 1303] = "MISSING_SERVER_SECRET";
61
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["NOT_INITIALIZED"] = 1500] = "NOT_INITIALIZED";
62
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["NOT_CONNECTED"] = 1501] = "NOT_CONNECTED";
63
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["INVALID_QUERY"] = 1700] = "INVALID_QUERY";
64
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["QUERY_EXECUTION_FAILED"] = 1701] = "QUERY_EXECUTION_FAILED";
65
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["BATCH_WRITE_FAILED"] = 1800] = "BATCH_WRITE_FAILED";
66
+ RiviumSyncErrorCode2[RiviumSyncErrorCode2["UNKNOWN_ERROR"] = 9999] = "UNKNOWN_ERROR";
67
+ return RiviumSyncErrorCode2;
68
+ })(RiviumSyncErrorCode || {});
69
+ var ERROR_MESSAGES = {
70
+ [1e3 /* CONNECTION_FAILED */]: "Failed to connect to server",
71
+ [1001 /* CONNECTION_TIMEOUT */]: "Connection timed out",
72
+ [1002 /* CONNECTION_LOST */]: "Connection to server was lost",
73
+ [1004 /* AUTHENTICATION_FAILED */]: "Authentication failed - invalid API key",
74
+ [1100 /* SUBSCRIPTION_FAILED */]: "Failed to subscribe to path",
75
+ [1200 /* DATA_FETCH_FAILED */]: "Failed to fetch data",
76
+ [1201 /* DATA_PARSE_ERROR */]: "Failed to parse data",
77
+ [1202 /* DATA_WRITE_FAILED */]: "Failed to write data",
78
+ [1203 /* DATA_DELETE_FAILED */]: "Failed to delete data",
79
+ [1204 /* DOCUMENT_NOT_FOUND */]: "Document not found",
80
+ [1300 /* INVALID_CONFIG */]: "Invalid configuration",
81
+ [1301 /* MISSING_API_KEY */]: "API key is missing",
82
+ [1302 /* MISSING_SERVER_URL */]: "Server URL is missing",
83
+ [1303 /* MISSING_SERVER_SECRET */]: "Server secret is missing",
84
+ [1500 /* NOT_INITIALIZED */]: "SDK is not initialized",
85
+ [1501 /* NOT_CONNECTED */]: "Not connected to server",
86
+ [1700 /* INVALID_QUERY */]: "Invalid query parameters",
87
+ [1701 /* QUERY_EXECUTION_FAILED */]: "Query execution failed",
88
+ [1800 /* BATCH_WRITE_FAILED */]: "Batch write operation failed",
89
+ [9999 /* UNKNOWN_ERROR */]: "An unknown error occurred"
90
+ };
91
+ var RiviumSyncError = class extends Error {
92
+ constructor(code, details) {
93
+ super(ERROR_MESSAGES[code] || "Unknown error");
94
+ this.name = "RiviumSyncError";
95
+ this.code = code;
96
+ this.details = details;
97
+ }
98
+ toJSON() {
99
+ return {
100
+ code: this.code,
101
+ message: this.message,
102
+ details: this.details
103
+ };
104
+ }
105
+ };
106
+ var RiviumSyncLogLevel = /* @__PURE__ */ ((RiviumSyncLogLevel2) => {
107
+ RiviumSyncLogLevel2[RiviumSyncLogLevel2["NONE"] = 0] = "NONE";
108
+ RiviumSyncLogLevel2[RiviumSyncLogLevel2["ERROR"] = 1] = "ERROR";
109
+ RiviumSyncLogLevel2[RiviumSyncLogLevel2["WARNING"] = 2] = "WARNING";
110
+ RiviumSyncLogLevel2[RiviumSyncLogLevel2["INFO"] = 3] = "INFO";
111
+ RiviumSyncLogLevel2[RiviumSyncLogLevel2["DEBUG"] = 4] = "DEBUG";
112
+ RiviumSyncLogLevel2[RiviumSyncLogLevel2["VERBOSE"] = 5] = "VERBOSE";
113
+ return RiviumSyncLogLevel2;
114
+ })(RiviumSyncLogLevel || {});
115
+ var SyncCollection = class {
116
+ constructor(admin, databaseId, collectionId) {
117
+ this.admin = admin;
118
+ this.databaseId = databaseId;
119
+ this.collectionId = collectionId;
120
+ }
121
+ /**
122
+ * Get a document reference
123
+ */
124
+ doc(documentId) {
125
+ return new SyncDocumentRef(this.admin, this.databaseId, this.collectionId, documentId);
126
+ }
127
+ /**
128
+ * Create a new document with auto-generated ID
129
+ */
130
+ async add(data) {
131
+ return this.admin.addDocument(this.databaseId, this.collectionId, data);
132
+ }
133
+ /**
134
+ * Get a single document by ID
135
+ */
136
+ async get(documentId) {
137
+ return this.admin.getDocument(this.databaseId, this.collectionId, documentId);
138
+ }
139
+ /**
140
+ * Get all documents in collection
141
+ */
142
+ async getAll(options) {
143
+ return this.admin.getDocuments(this.databaseId, this.collectionId, options);
144
+ }
145
+ /**
146
+ * Listen to collection changes (requires enableRealtime: true)
147
+ */
148
+ onSnapshot(callback, options) {
149
+ return this.admin.listenCollection(this.databaseId, this.collectionId, callback, options);
150
+ }
151
+ /**
152
+ * Start a query builder
153
+ */
154
+ query() {
155
+ return new SyncQuery(this.admin, this.databaseId, this.collectionId);
156
+ }
157
+ /**
158
+ * Add a filter condition
159
+ */
160
+ where(field, operator, value) {
161
+ return new SyncQuery(this.admin, this.databaseId, this.collectionId).where(field, operator, value);
162
+ }
163
+ /**
164
+ * Order results
165
+ */
166
+ orderBy(field, direction = "asc") {
167
+ return new SyncQuery(this.admin, this.databaseId, this.collectionId).orderBy(field, direction);
168
+ }
169
+ /**
170
+ * Limit results
171
+ */
172
+ limit(count) {
173
+ return new SyncQuery(this.admin, this.databaseId, this.collectionId).limit(count);
174
+ }
175
+ };
176
+ var SyncDocumentRef = class {
177
+ constructor(admin, databaseId, collectionId, documentId) {
178
+ this.admin = admin;
179
+ this.databaseId = databaseId;
180
+ this.collectionId = collectionId;
181
+ this.documentId = documentId;
182
+ }
183
+ get id() {
184
+ return this.documentId;
185
+ }
186
+ get path() {
187
+ return `/${this.databaseId}/${this.collectionId}/${this.documentId}`;
188
+ }
189
+ /**
190
+ * Get document data
191
+ */
192
+ async get() {
193
+ return this.admin.getDocument(this.databaseId, this.collectionId, this.documentId);
194
+ }
195
+ /**
196
+ * Check if document exists
197
+ */
198
+ async exists() {
199
+ const doc = await this.get();
200
+ return doc !== null;
201
+ }
202
+ /**
203
+ * Set document data (overwrite)
204
+ */
205
+ async set(data) {
206
+ await this.admin.setDocument(this.databaseId, this.collectionId, this.documentId, data);
207
+ }
208
+ /**
209
+ * Update document data (merge)
210
+ */
211
+ async update(data) {
212
+ await this.admin.updateDocument(this.databaseId, this.collectionId, this.documentId, data);
213
+ }
214
+ /**
215
+ * Delete document
216
+ */
217
+ async delete() {
218
+ await this.admin.deleteDocument(this.databaseId, this.collectionId, this.documentId);
219
+ }
220
+ /**
221
+ * Listen to document changes (requires enableRealtime: true)
222
+ */
223
+ onSnapshot(callback) {
224
+ return this.admin.listenDocument(this.databaseId, this.collectionId, this.documentId, callback);
225
+ }
226
+ };
227
+ var SyncQuery = class {
228
+ constructor(admin, databaseId, collectionId) {
229
+ this.options = {};
230
+ this.admin = admin;
231
+ this.databaseId = databaseId;
232
+ this.collectionId = collectionId;
233
+ }
234
+ /**
235
+ * Add a filter condition
236
+ */
237
+ where(field, operator, value) {
238
+ if (!this.options.filters) {
239
+ this.options.filters = [];
240
+ }
241
+ this.options.filters.push({ field, operator, value });
242
+ return this;
243
+ }
244
+ /**
245
+ * Order results
246
+ */
247
+ orderBy(field, direction = "asc") {
248
+ this.options.orderBy = field;
249
+ this.options.orderDirection = direction;
250
+ return this;
251
+ }
252
+ /**
253
+ * Limit results
254
+ */
255
+ limit(count) {
256
+ this.options.limit = count;
257
+ return this;
258
+ }
259
+ /**
260
+ * Skip results (for pagination)
261
+ */
262
+ offset(count) {
263
+ this.options.offset = count;
264
+ return this;
265
+ }
266
+ /**
267
+ * Alias for offset - skip first N results
268
+ */
269
+ startAfter(count) {
270
+ return this.offset(count);
271
+ }
272
+ /**
273
+ * Execute query and get results
274
+ */
275
+ async get() {
276
+ return this.admin.getDocuments(this.databaseId, this.collectionId, this.options);
277
+ }
278
+ /**
279
+ * Get first result only
280
+ */
281
+ async getFirst() {
282
+ const originalLimit = this.options.limit;
283
+ this.options.limit = 1;
284
+ const results = await this.get();
285
+ this.options.limit = originalLimit;
286
+ return results.length > 0 ? results[0] : null;
287
+ }
288
+ /**
289
+ * Count matching documents
290
+ */
291
+ async count() {
292
+ const results = await this.get();
293
+ return results.length;
294
+ }
295
+ /**
296
+ * Listen to query results (requires enableRealtime: true)
297
+ */
298
+ onSnapshot(callback) {
299
+ return this.admin.listenCollection(this.databaseId, this.collectionId, callback, this.options);
300
+ }
301
+ };
302
+ var SyncDatabase = class {
303
+ constructor(admin, databaseId) {
304
+ this.admin = admin;
305
+ this.databaseId = databaseId;
306
+ }
307
+ get id() {
308
+ return this.databaseId;
309
+ }
310
+ /**
311
+ * Get a collection reference
312
+ */
313
+ collection(collectionId) {
314
+ return new SyncCollection(this.admin, this.databaseId, collectionId);
315
+ }
316
+ };
317
+ var WriteBatch = class {
318
+ constructor(admin) {
319
+ this.operations = [];
320
+ this.admin = admin;
321
+ }
322
+ /**
323
+ * Add a set operation to the batch
324
+ */
325
+ set(docRef, data) {
326
+ this.operations.push({
327
+ type: "set",
328
+ databaseId: docRef.databaseId,
329
+ collectionId: docRef.collectionId,
330
+ documentId: docRef.id,
331
+ data
332
+ });
333
+ return this;
334
+ }
335
+ /**
336
+ * Add an update operation to the batch
337
+ */
338
+ update(docRef, data) {
339
+ this.operations.push({
340
+ type: "update",
341
+ databaseId: docRef.databaseId,
342
+ collectionId: docRef.collectionId,
343
+ documentId: docRef.id,
344
+ data
345
+ });
346
+ return this;
347
+ }
348
+ /**
349
+ * Add a delete operation to the batch
350
+ */
351
+ delete(docRef) {
352
+ this.operations.push({
353
+ type: "delete",
354
+ databaseId: docRef.databaseId,
355
+ collectionId: docRef.collectionId,
356
+ documentId: docRef.id
357
+ });
358
+ return this;
359
+ }
360
+ /**
361
+ * Commit all operations in the batch
362
+ */
363
+ async commit() {
364
+ await this.admin.executeBatch(this.operations);
365
+ this.operations = [];
366
+ }
367
+ /**
368
+ * Get the number of pending operations
369
+ */
370
+ get size() {
371
+ return this.operations.length;
372
+ }
373
+ };
374
+ var _RiviumSyncAdmin = class _RiviumSyncAdmin {
375
+ constructor(config) {
376
+ this.mqttClient = null;
377
+ this.mqttConfig = null;
378
+ // Realtime listeners
379
+ this.documentListeners = /* @__PURE__ */ new Map();
380
+ this.collectionListeners = /* @__PURE__ */ new Map();
381
+ this.cachedCollections = /* @__PURE__ */ new Map();
382
+ if (!config.apiKey) {
383
+ throw new RiviumSyncError(1301 /* MISSING_API_KEY */);
384
+ }
385
+ if (!config.serverSecret) {
386
+ throw new RiviumSyncError(1303 /* MISSING_SERVER_SECRET */);
387
+ }
388
+ this.config = {
389
+ ...config,
390
+ baseUrl: _RiviumSyncAdmin.DEFAULT_BASE_URL,
391
+ enableRealtime: config.enableRealtime ?? false,
392
+ logLevel: config.logLevel ?? 1 /* ERROR */,
393
+ timeout: config.timeout ?? 3e4
394
+ };
395
+ this.logLevel = this.config.logLevel;
396
+ this.timeout = this.config.timeout;
397
+ this.log(3 /* INFO */, "RiviumSyncAdmin SDK initialized");
398
+ if (this.config.enableRealtime) {
399
+ this.initRealtime();
400
+ }
401
+ }
402
+ // ==========================================================================
403
+ // Logging
404
+ // ==========================================================================
405
+ log(level, message, ...args) {
406
+ if (level > this.logLevel) return;
407
+ const prefix = "[RiviumSync]";
408
+ switch (level) {
409
+ case 1 /* ERROR */:
410
+ console.error(prefix, message, ...args);
411
+ break;
412
+ case 2 /* WARNING */:
413
+ console.warn(prefix, message, ...args);
414
+ break;
415
+ case 3 /* INFO */:
416
+ console.info(prefix, message, ...args);
417
+ break;
418
+ default:
419
+ console.log(prefix, message, ...args);
420
+ }
421
+ }
422
+ setLogLevel(level) {
423
+ this.logLevel = level;
424
+ }
425
+ // ==========================================================================
426
+ // HTTP Client
427
+ // ==========================================================================
428
+ async request(method, path, body) {
429
+ const url = `${this.config.baseUrl}${path}`;
430
+ const headers = {
431
+ "Content-Type": "application/json",
432
+ "x-api-key": this.config.apiKey,
433
+ "x-server-secret": this.config.serverSecret
434
+ };
435
+ if (this.config.userId) {
436
+ headers["X-User-Id"] = this.config.userId;
437
+ }
438
+ const controller = new AbortController();
439
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
440
+ try {
441
+ const response = await fetch(url, {
442
+ method,
443
+ headers,
444
+ body: body ? JSON.stringify(body) : void 0,
445
+ signal: controller.signal
446
+ });
447
+ clearTimeout(timeoutId);
448
+ if (!response.ok) {
449
+ const errorText = await response.text();
450
+ throw new RiviumSyncError(
451
+ 1200 /* DATA_FETCH_FAILED */,
452
+ `HTTP ${response.status}: ${errorText}`
453
+ );
454
+ }
455
+ const text = await response.text();
456
+ if (!text) {
457
+ return {};
458
+ }
459
+ return JSON.parse(text);
460
+ } catch (error) {
461
+ clearTimeout(timeoutId);
462
+ if (error instanceof RiviumSyncError) {
463
+ throw error;
464
+ }
465
+ if (error.name === "AbortError") {
466
+ throw new RiviumSyncError(1001 /* CONNECTION_TIMEOUT */);
467
+ }
468
+ throw new RiviumSyncError(
469
+ 1200 /* DATA_FETCH_FAILED */,
470
+ error.message
471
+ );
472
+ }
473
+ }
474
+ // ==========================================================================
475
+ // Public API - Database Access
476
+ // ==========================================================================
477
+ /**
478
+ * Get a database reference
479
+ */
480
+ database(databaseId) {
481
+ return new SyncDatabase(this, databaseId);
482
+ }
483
+ /**
484
+ * Create a new write batch
485
+ */
486
+ batch() {
487
+ return new WriteBatch(this);
488
+ }
489
+ // ==========================================================================
490
+ // Document Operations (Internal)
491
+ // ==========================================================================
492
+ async getDocument(databaseId, collectionId, documentId) {
493
+ try {
494
+ const response = await this.request(
495
+ "GET",
496
+ `/databases/${databaseId}/collections/${collectionId}/documents/sdk/${documentId}`
497
+ );
498
+ return response;
499
+ } catch (error) {
500
+ if (error.details?.includes("404")) {
501
+ return null;
502
+ }
503
+ this.log(1 /* ERROR */, "Failed to get document:", error);
504
+ throw error;
505
+ }
506
+ }
507
+ async getDocuments(databaseId, collectionId, options) {
508
+ try {
509
+ const queryParams = new URLSearchParams();
510
+ if (options?.filters) {
511
+ queryParams.set("filters", JSON.stringify(options.filters));
512
+ }
513
+ if (options?.orderBy) {
514
+ queryParams.set("orderBy", options.orderBy);
515
+ queryParams.set("orderDirection", options.orderDirection || "asc");
516
+ }
517
+ if (options?.limit) {
518
+ queryParams.set("limit", options.limit.toString());
519
+ }
520
+ if (options?.offset) {
521
+ queryParams.set("offset", options.offset.toString());
522
+ }
523
+ const queryString = queryParams.toString();
524
+ const path = `/databases/${databaseId}/collections/${collectionId}/documents/sdk${queryString ? `?${queryString}` : ""}`;
525
+ const response = await this.request(
526
+ "GET",
527
+ path
528
+ );
529
+ return Array.isArray(response) ? response : response.documents || [];
530
+ } catch (error) {
531
+ this.log(1 /* ERROR */, "Failed to get documents:", error);
532
+ throw error;
533
+ }
534
+ }
535
+ async addDocument(databaseId, collectionId, data) {
536
+ try {
537
+ const response = await this.request(
538
+ "POST",
539
+ `/databases/${databaseId}/collections/${collectionId}/documents/sdk`,
540
+ { data }
541
+ );
542
+ return response;
543
+ } catch (error) {
544
+ this.log(1 /* ERROR */, "Failed to add document:", error);
545
+ throw error;
546
+ }
547
+ }
548
+ async setDocument(databaseId, collectionId, documentId, data) {
549
+ try {
550
+ await this.request(
551
+ "PUT",
552
+ `/databases/${databaseId}/collections/${collectionId}/documents/sdk/${documentId}`,
553
+ { data }
554
+ );
555
+ } catch (error) {
556
+ this.log(1 /* ERROR */, "Failed to set document:", error);
557
+ throw error;
558
+ }
559
+ }
560
+ async updateDocument(databaseId, collectionId, documentId, data) {
561
+ try {
562
+ await this.request(
563
+ "PATCH",
564
+ `/databases/${databaseId}/collections/${collectionId}/documents/sdk/${documentId}`,
565
+ { data }
566
+ );
567
+ } catch (error) {
568
+ this.log(1 /* ERROR */, "Failed to update document:", error);
569
+ throw error;
570
+ }
571
+ }
572
+ async deleteDocument(databaseId, collectionId, documentId) {
573
+ try {
574
+ await this.request(
575
+ "DELETE",
576
+ `/databases/${databaseId}/collections/${collectionId}/documents/sdk/${documentId}`
577
+ );
578
+ } catch (error) {
579
+ this.log(1 /* ERROR */, "Failed to delete document:", error);
580
+ throw error;
581
+ }
582
+ }
583
+ // ==========================================================================
584
+ // Batch Operations
585
+ // ==========================================================================
586
+ async executeBatch(operations) {
587
+ for (const op of operations) {
588
+ switch (op.type) {
589
+ case "set":
590
+ await this.setDocument(op.databaseId, op.collectionId, op.documentId, op.data);
591
+ break;
592
+ case "update":
593
+ await this.updateDocument(op.databaseId, op.collectionId, op.documentId, op.data);
594
+ break;
595
+ case "delete":
596
+ await this.deleteDocument(op.databaseId, op.collectionId, op.documentId);
597
+ break;
598
+ }
599
+ }
600
+ }
601
+ // ==========================================================================
602
+ // Realtime (Optional)
603
+ // ==========================================================================
604
+ async initRealtime() {
605
+ try {
606
+ this.log(4 /* DEBUG */, "Fetching MQTT token...");
607
+ const tokenData = await this.request("POST", "/connections/token");
608
+ this.mqttConfig = {
609
+ host: tokenData.mqtt.host,
610
+ port: tokenData.mqtt.port,
611
+ wsHost: tokenData.mqtt.host,
612
+ wsPort: tokenData.mqtt.port,
613
+ password: tokenData.token
614
+ };
615
+ this.connectMqtt();
616
+ } catch (error) {
617
+ this.log(1 /* ERROR */, "Failed to fetch MQTT token:", error);
618
+ }
619
+ }
620
+ connectMqtt() {
621
+ if (!this.mqttConfig) {
622
+ return;
623
+ }
624
+ const url = `mqtt://${this.mqttConfig.host}:${this.mqttConfig.port}`;
625
+ const clientId = `rivium_sync_node_${this.generateUUID()}`;
626
+ const options = {
627
+ clientId,
628
+ clean: false,
629
+ connectTimeout: 1e4,
630
+ username: "jwt",
631
+ password: this.mqttConfig.password
632
+ };
633
+ this.log(4 /* DEBUG */, "Connecting to MQTT:", url);
634
+ this.mqttClient = import_mqtt.default.connect(url, options);
635
+ this.mqttClient.on("connect", () => {
636
+ this.log(3 /* INFO */, "MQTT connected");
637
+ this.resubscribeAll();
638
+ });
639
+ this.mqttClient.on("message", (topic, payload) => {
640
+ try {
641
+ const data = JSON.parse(payload.toString());
642
+ this.handleMqttMessage(topic, data);
643
+ } catch (error) {
644
+ this.log(1 /* ERROR */, "MQTT message parse error:", error);
645
+ }
646
+ });
647
+ this.mqttClient.on("error", (error) => {
648
+ this.log(1 /* ERROR */, "MQTT error:", error);
649
+ });
650
+ this.mqttClient.on("close", () => {
651
+ this.log(3 /* INFO */, "MQTT disconnected");
652
+ });
653
+ }
654
+ listenDocument(databaseId, collectionId, documentId, callback) {
655
+ if (!this.config.enableRealtime) {
656
+ this.log(2 /* WARNING */, "Realtime not enabled. Set enableRealtime: true in config.");
657
+ this.getDocument(databaseId, collectionId, documentId).then((doc) => {
658
+ callback(doc);
659
+ });
660
+ return () => {
661
+ };
662
+ }
663
+ const path = `/${databaseId}/${collectionId}/${documentId}`;
664
+ const mqttTopic = `rivium_sync/${this.config.apiKey.substring(0, 16)}/db/${databaseId}/${collectionId}/${documentId}`;
665
+ if (!this.documentListeners.has(path)) {
666
+ this.documentListeners.set(path, /* @__PURE__ */ new Set());
667
+ }
668
+ this.documentListeners.get(path).add(callback);
669
+ if (this.mqttClient?.connected) {
670
+ this.mqttClient.subscribe(mqttTopic, { qos: 1 });
671
+ }
672
+ this.getDocument(databaseId, collectionId, documentId).then((doc) => {
673
+ callback(doc);
674
+ });
675
+ return () => {
676
+ const listeners = this.documentListeners.get(path);
677
+ if (listeners) {
678
+ listeners.delete(callback);
679
+ if (listeners.size === 0) {
680
+ this.documentListeners.delete(path);
681
+ this.mqttClient?.unsubscribe(mqttTopic);
682
+ }
683
+ }
684
+ };
685
+ }
686
+ listenCollection(databaseId, collectionId, callback, options) {
687
+ if (!this.config.enableRealtime) {
688
+ this.log(2 /* WARNING */, "Realtime not enabled. Set enableRealtime: true in config.");
689
+ this.getDocuments(databaseId, collectionId, options).then((docs) => {
690
+ callback(docs);
691
+ });
692
+ return () => {
693
+ };
694
+ }
695
+ const path = `/${databaseId}/${collectionId}`;
696
+ const mqttTopic = `rivium_sync/${this.config.apiKey.substring(0, 16)}/db/${databaseId}/${collectionId}/+`;
697
+ if (!this.collectionListeners.has(path)) {
698
+ this.collectionListeners.set(path, /* @__PURE__ */ new Set());
699
+ }
700
+ this.collectionListeners.get(path).add({
701
+ callback,
702
+ options
703
+ });
704
+ if (this.mqttClient?.connected) {
705
+ this.mqttClient.subscribe(mqttTopic, { qos: 1 });
706
+ }
707
+ this.getDocuments(databaseId, collectionId, options).then((docs) => {
708
+ this.cachedCollections.set(path, docs);
709
+ callback(docs);
710
+ });
711
+ return () => {
712
+ const listeners = this.collectionListeners.get(path);
713
+ if (listeners) {
714
+ const listenerObj = Array.from(listeners).find((l) => l.callback === callback);
715
+ if (listenerObj) {
716
+ listeners.delete(listenerObj);
717
+ }
718
+ if (listeners.size === 0) {
719
+ this.collectionListeners.delete(path);
720
+ this.cachedCollections.delete(path);
721
+ this.mqttClient?.unsubscribe(mqttTopic);
722
+ }
723
+ }
724
+ };
725
+ }
726
+ resubscribeAll() {
727
+ if (!this.mqttClient?.connected) return;
728
+ const appId = this.config.apiKey.substring(0, 16);
729
+ this.documentListeners.forEach((_, path) => {
730
+ const parts = path.split("/").filter((p) => p);
731
+ if (parts.length === 3) {
732
+ const [databaseId, collectionId, documentId] = parts;
733
+ const topic = `rivium_sync/${appId}/db/${databaseId}/${collectionId}/${documentId}`;
734
+ this.mqttClient.subscribe(topic, { qos: 1 });
735
+ }
736
+ });
737
+ this.collectionListeners.forEach((_, path) => {
738
+ const parts = path.split("/").filter((p) => p);
739
+ if (parts.length === 2) {
740
+ const [databaseId, collectionId] = parts;
741
+ const topic = `rivium_sync/${appId}/db/${databaseId}/${collectionId}/+`;
742
+ this.mqttClient.subscribe(topic, { qos: 1 });
743
+ }
744
+ });
745
+ }
746
+ handleMqttMessage(topic, data) {
747
+ const parts = topic.split("/");
748
+ if (parts.length < 6) return;
749
+ const databaseId = parts[3];
750
+ const collectionId = parts[4];
751
+ const documentId = parts[5];
752
+ const documentPath = `/${databaseId}/${collectionId}/${documentId}`;
753
+ const collectionPath = `/${databaseId}/${collectionId}`;
754
+ const typedData = data;
755
+ const docListeners = this.documentListeners.get(documentPath);
756
+ if (docListeners) {
757
+ const document = {
758
+ id: documentId,
759
+ data: typedData.data || typedData,
760
+ createdAt: typedData.createdAt,
761
+ updatedAt: typedData.updatedAt,
762
+ version: typedData.version
763
+ };
764
+ docListeners.forEach((callback) => {
765
+ if (typedData.deleted) {
766
+ callback(null);
767
+ } else {
768
+ callback(document);
769
+ }
770
+ });
771
+ }
772
+ const colListeners = this.collectionListeners.get(collectionPath);
773
+ if (colListeners) {
774
+ let docs = this.cachedCollections.get(collectionPath) || [];
775
+ if (typedData.deleted) {
776
+ docs = docs.filter((d) => d.id !== documentId);
777
+ } else {
778
+ const existingIndex = docs.findIndex((d) => d.id === documentId);
779
+ const newDoc = {
780
+ id: documentId,
781
+ data: typedData.data || typedData,
782
+ createdAt: typedData.createdAt,
783
+ updatedAt: typedData.updatedAt,
784
+ version: typedData.version
785
+ };
786
+ if (existingIndex >= 0) {
787
+ docs[existingIndex] = newDoc;
788
+ } else {
789
+ docs.push(newDoc);
790
+ }
791
+ }
792
+ this.cachedCollections.set(collectionPath, docs);
793
+ colListeners.forEach(({ callback, options }) => {
794
+ let filteredDocs = [...docs];
795
+ if (options?.filters) {
796
+ filteredDocs = this.applyFilters(filteredDocs, options.filters);
797
+ }
798
+ if (options?.orderBy) {
799
+ filteredDocs = this.applyOrdering(filteredDocs, options.orderBy, options.orderDirection);
800
+ }
801
+ if (options?.limit) {
802
+ filteredDocs = filteredDocs.slice(options.offset || 0, (options.offset || 0) + options.limit);
803
+ }
804
+ callback(filteredDocs);
805
+ });
806
+ }
807
+ }
808
+ applyFilters(docs, filters) {
809
+ return docs.filter((doc) => {
810
+ const data = doc.data;
811
+ return filters.every((filter) => {
812
+ const value = data[filter.field];
813
+ switch (filter.operator) {
814
+ case "==":
815
+ return value === filter.value;
816
+ case "!=":
817
+ return value !== filter.value;
818
+ case "<":
819
+ return value < filter.value;
820
+ case "<=":
821
+ return value <= filter.value;
822
+ case ">":
823
+ return value > filter.value;
824
+ case ">=":
825
+ return value >= filter.value;
826
+ case "in":
827
+ return Array.isArray(filter.value) && filter.value.includes(value);
828
+ case "not-in":
829
+ return Array.isArray(filter.value) && !filter.value.includes(value);
830
+ case "array-contains":
831
+ return Array.isArray(value) && value.includes(filter.value);
832
+ default:
833
+ return true;
834
+ }
835
+ });
836
+ });
837
+ }
838
+ applyOrdering(docs, orderBy, direction) {
839
+ return [...docs].sort((a, b) => {
840
+ const aData = a.data;
841
+ const bData = b.data;
842
+ const aVal = aData[orderBy];
843
+ const bVal = bData[orderBy];
844
+ let comparison = 0;
845
+ if (aVal == null && bVal != null) comparison = -1;
846
+ else if (aVal != null && bVal == null) comparison = 1;
847
+ else if (aVal != null && bVal != null) {
848
+ if (aVal < bVal) comparison = -1;
849
+ else if (aVal > bVal) comparison = 1;
850
+ }
851
+ return direction === "desc" ? -comparison : comparison;
852
+ });
853
+ }
854
+ // ==========================================================================
855
+ // Utilities
856
+ // ==========================================================================
857
+ generateUUID() {
858
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
859
+ const r = Math.random() * 16 | 0;
860
+ const v = c === "x" ? r : r & 3 | 8;
861
+ return v.toString(16);
862
+ });
863
+ }
864
+ /**
865
+ * Disconnect from realtime updates
866
+ */
867
+ disconnect() {
868
+ if (this.mqttClient) {
869
+ this.mqttClient.end(true);
870
+ this.mqttClient = null;
871
+ }
872
+ }
873
+ };
874
+ _RiviumSyncAdmin.DEFAULT_BASE_URL = "https://sync.rivium.co";
875
+ var RiviumSyncAdmin = _RiviumSyncAdmin;
876
+ var index_default = RiviumSyncAdmin;
877
+ // Annotate the CommonJS export names for ESM import in node:
878
+ 0 && (module.exports = {
879
+ RiviumSyncAdmin,
880
+ RiviumSyncError,
881
+ RiviumSyncErrorCode,
882
+ RiviumSyncLogLevel,
883
+ SyncCollection,
884
+ SyncDatabase,
885
+ SyncDocumentRef,
886
+ SyncQuery,
887
+ WriteBatch
888
+ });