dolphin-server-modules 2.2.1 → 2.2.5

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 (70) hide show
  1. package/DOLPHIN_MASTER_GUIDE_NEPALI.md +64 -24
  2. package/LICENSE +15 -0
  3. package/README.md +74 -143
  4. package/TUTORIAL_NEPALI.md +73 -193
  5. package/dist/adapters/mongoose/index.js.map +1 -1
  6. package/dist/adapters/mongoose/index.test.js.map +1 -1
  7. package/dist/adapters/mongoose/integration.test.js.map +1 -1
  8. package/dist/auth/auth.js.map +1 -1
  9. package/dist/auth/auth.test.js.map +1 -1
  10. package/dist/authController/authController.js.map +1 -1
  11. package/dist/authController/authController.test.js.map +1 -1
  12. package/dist/bin/cli.d.ts +2 -0
  13. package/dist/bin/cli.js +405 -0
  14. package/dist/bin/cli.js.map +1 -0
  15. package/dist/controller/controller.js.map +1 -1
  16. package/dist/controller/controller.test.js.map +1 -1
  17. package/dist/curd/crud.d.ts +1 -0
  18. package/dist/curd/crud.js +37 -12
  19. package/dist/curd/crud.js.map +1 -1
  20. package/dist/curd/crud.test.js.map +1 -1
  21. package/dist/demo-server.js.map +1 -1
  22. package/dist/djson/djson.js.map +1 -1
  23. package/dist/djson/djson.test.js.map +1 -1
  24. package/dist/dolphin-bench.d.ts +1 -0
  25. package/dist/dolphin-bench.js +68 -0
  26. package/dist/dolphin-bench.js.map +1 -0
  27. package/dist/hard-performance-test.d.ts +1 -0
  28. package/dist/hard-performance-test.js +102 -0
  29. package/dist/hard-performance-test.js.map +1 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/middleware/zod.js.map +1 -1
  32. package/dist/middleware/zod.test.js.map +1 -1
  33. package/dist/performance-test.d.ts +1 -0
  34. package/dist/performance-test.js +97 -0
  35. package/dist/performance-test.js.map +1 -0
  36. package/dist/real-test-mongoose.d.ts +1 -0
  37. package/dist/real-test-mongoose.js +109 -0
  38. package/dist/real-test-mongoose.js.map +1 -0
  39. package/dist/realtime/codec.js +5 -5
  40. package/dist/realtime/codec.js.map +1 -1
  41. package/dist/realtime/core.d.ts +1 -1
  42. package/dist/realtime/core.js +44 -14
  43. package/dist/realtime/core.js.map +1 -1
  44. package/dist/realtime/devicemanager.js.map +1 -1
  45. package/dist/realtime/index.js.map +1 -1
  46. package/dist/realtime/plugins.js.map +1 -1
  47. package/dist/realtime/realtime.test.js.map +1 -1
  48. package/dist/realtime/trie.js.map +1 -1
  49. package/dist/router/router.js +1 -0
  50. package/dist/router/router.js.map +1 -1
  51. package/dist/router/router.test.js.map +1 -1
  52. package/dist/server/server.js +2 -1
  53. package/dist/server/server.js.map +1 -1
  54. package/dist/server/server.test.js.map +1 -1
  55. package/dist/signaling/index.js.map +1 -1
  56. package/dist/signaling/signaling.test.d.ts +1 -0
  57. package/dist/signaling/signaling.test.js +114 -0
  58. package/dist/signaling/signaling.test.js.map +1 -0
  59. package/dist/swagger/swagger.js.map +1 -1
  60. package/dist/swagger/swagger.test.js.map +1 -1
  61. package/dist/test-2fa-real.d.ts +1 -0
  62. package/dist/test-2fa-real.js +110 -0
  63. package/dist/test-2fa-real.js.map +1 -0
  64. package/dist/test-dolphin.d.ts +1 -0
  65. package/dist/test-dolphin.js +100 -0
  66. package/dist/test-dolphin.js.map +1 -0
  67. package/dist/tsconfig.tsbuildinfo +1 -0
  68. package/package.json +82 -20
  69. package/scripts/client.js +176 -8
  70. package/scripts/dolphin-persist.js +211 -0
package/scripts/client.js CHANGED
@@ -176,11 +176,6 @@ class AuthHandler {
176
176
  this.user = null;
177
177
  }
178
178
 
179
- /**
180
- * @param {string} email
181
- * @param {string} password
182
- * @returns {Promise<any>}
183
- */
184
179
  async login(email, password) {
185
180
  const res = await this.client.api.post('/auth/login', { email, password });
186
181
  if (res.accessToken) {
@@ -190,12 +185,10 @@ class AuthHandler {
190
185
  return res;
191
186
  }
192
187
 
193
- /** @param {any} data */
194
188
  async register(data) {
195
189
  return await this.client.api.post('/auth/register', data);
196
190
  }
197
191
 
198
- /** @returns {Promise<DolphinResponse>} */
199
192
  async me() {
200
193
  const res = await this.client.api.get('/auth/me');
201
194
  if (res.success) {
@@ -216,6 +209,170 @@ class AuthHandler {
216
209
  }
217
210
  }
218
211
 
212
+ /**
213
+ * DolphinStore - Reactive State Sync (Zustand Alternative)
214
+ * Automatically syncs database collections with local state.
215
+ */
216
+ class DolphinStore {
217
+ /** @param {DolphinClient} client */
218
+ constructor(client) {
219
+ this.client = client;
220
+ /** @type {Map<string, { items: any[], loading: boolean, error: string|null, success: boolean }>} */
221
+ this.data = new Map();
222
+ /** @type {Set<function()>} */
223
+ this.listeners = new Set();
224
+ /** @type {Set<string>} */
225
+ this.subscribed = new Set();
226
+
227
+ return new Proxy(this, {
228
+ get: (target, prop) => {
229
+ if (prop in target) return target[prop];
230
+ if (typeof prop === 'string') {
231
+ return this._getCollection(prop);
232
+ }
233
+ }
234
+ });
235
+ }
236
+
237
+ /** @private */
238
+ _getCollection(name) {
239
+ if (!this.data.has(name)) {
240
+ const collection = {
241
+ _rawItems: [],
242
+ items: [],
243
+ loading: true,
244
+ error: null,
245
+ success: false,
246
+ _filter: null,
247
+ _sort: null,
248
+
249
+ /**
250
+ * फिल्टर सेट गर्ने (Local filtering)
251
+ * @param {function(any): boolean} fn
252
+ */
253
+ where: (fn) => {
254
+ collection._filter = fn;
255
+ this._applyTransform(collection);
256
+ return collection;
257
+ },
258
+
259
+ /**
260
+ * सर्टिङ सेट गर्ने
261
+ * @param {string} key
262
+ * @param {'asc'|'desc'} [direction]
263
+ */
264
+ orderBy: (key, direction = 'asc') => {
265
+ collection._sort = { key, direction };
266
+ this._applyTransform(collection);
267
+ return collection;
268
+ },
269
+
270
+ /**
271
+ * फिल्टर र सर्ट हटाउने
272
+ */
273
+ clear: () => {
274
+ collection._filter = null;
275
+ collection._sort = null;
276
+ this._applyTransform(collection);
277
+ return collection;
278
+ }
279
+ };
280
+
281
+ this.data.set(name, collection);
282
+ this._fetchAndSync(name);
283
+ }
284
+ return this.data.get(name);
285
+ }
286
+
287
+ /** @private */
288
+ async _fetchAndSync(name) {
289
+ const state = this.data.get(name);
290
+ try {
291
+ // 1. Initial Fetch
292
+ const res = await this.client.api.get(`/${name.toLowerCase()}`);
293
+ state._rawItems = Array.isArray(res) ? res : (res.data || []);
294
+ state.loading = false;
295
+ state.success = true;
296
+ state.error = null;
297
+ this._applyTransform(state);
298
+
299
+ // 2. Realtime Sync (if connected)
300
+ if (!this.subscribed.has(name)) {
301
+ const topic = `db:sync/${name.toLowerCase()}`;
302
+ this.client.subscribe(topic, (update) => {
303
+ this._handleRemoteUpdate(name, update);
304
+ });
305
+ this.subscribed.add(name);
306
+ }
307
+ } catch (e) {
308
+ state.loading = false;
309
+ state.success = false;
310
+ state.error = e.data?.error || e.message || 'Fetch failed';
311
+ this._notify();
312
+ console.error(`[DolphinStore] Sync failed for ${name}:`, e);
313
+ }
314
+ }
315
+
316
+ /** @private */
317
+ _applyTransform(state) {
318
+ let result = [...state._rawItems];
319
+
320
+ // 1. Filter
321
+ if (state._filter) {
322
+ result = result.filter(state._filter);
323
+ }
324
+
325
+ // 2. Sort
326
+ if (state._sort) {
327
+ const { key, direction } = state._sort;
328
+ result.sort((a, b) => {
329
+ const av = a[key];
330
+ const bv = b[key];
331
+ if (av === bv) return 0;
332
+ const compare = av > bv ? 1 : -1;
333
+ return direction === 'asc' ? compare : -compare;
334
+ });
335
+ }
336
+
337
+ state.items = result;
338
+ this._notify();
339
+ }
340
+
341
+ /** @private */
342
+ _handleRemoteUpdate(collection, update) {
343
+ const state = this.data.get(collection);
344
+ if (!state) return;
345
+
346
+ let items = state._rawItems;
347
+ const { type, data } = update; // type: 'create', 'update', 'delete'
348
+
349
+ if (type === 'create') {
350
+ items = [...items, data];
351
+ } else if (type === 'update') {
352
+ items = items.map(item => (item.id === data.id || item._id === data._id) ? { ...item, ...data } : item);
353
+ } else if (type === 'delete') {
354
+ items = items.filter(item => (item.id !== data.id && item._id !== data._id));
355
+ }
356
+
357
+ state._rawItems = items;
358
+ this._applyTransform(state);
359
+ }
360
+
361
+ /** Subscribe for React components (useSyncExternalStore) */
362
+ subscribe(listener) {
363
+ this.listeners.add(listener);
364
+ return () => this.listeners.delete(listener);
365
+ }
366
+
367
+ getSnapshot(collection) {
368
+ return this.data.get(collection) || { items: [], loading: false, error: null, success: false };
369
+ }
370
+
371
+ _notify() {
372
+ this.listeners.forEach(l => l());
373
+ }
374
+ }
375
+
219
376
  class DolphinClient {
220
377
  /**
221
378
  * @param {string} [url]
@@ -256,6 +413,7 @@ class DolphinClient {
256
413
  // Sub-handlers
257
414
  this.api = new APIHandler(this);
258
415
  this.auth = new AuthHandler(this);
416
+ this.store = new DolphinStore(this);
259
417
 
260
418
  /** @type {Map<string, Set<TopicCallback>>} */
261
419
  this.handlers = new Map(); // topic -> Set of callbacks
@@ -395,7 +553,13 @@ class DolphinClient {
395
553
  * @param {TopicCallback} callback
396
554
  */
397
555
  subscribe(topic, callback) {
398
- if (!this.handlers.has(topic)) this.handlers.set(topic, new Set());
556
+ if (!this.handlers.has(topic)) {
557
+ this.handlers.set(topic, new Set());
558
+ // Tell server we want to sub
559
+ if (this.socket && this.socket.readyState === WebSocket.OPEN) {
560
+ this.socket.send(JSON.stringify({ type: 'sub', topic }));
561
+ }
562
+ }
399
563
  this.handlers.get(topic).add(callback);
400
564
  }
401
565
 
@@ -409,6 +573,10 @@ class DolphinClient {
409
573
  callbacks.delete(callback);
410
574
  if (callbacks.size === 0) {
411
575
  this.handlers.delete(topic);
576
+ // Tell server we want to unsub
577
+ if (this.socket && this.socket.readyState === WebSocket.OPEN) {
578
+ this.socket.send(JSON.stringify({ type: 'unsub', topic }));
579
+ }
412
580
  }
413
581
  }
414
582
  }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * DolphinPersist - Offline Cache Plugin for Dolphin Client
3
+ *
4
+ * Optional, zero-dependency persistence layer for DolphinStore.
5
+ * Supports both localStorage (simple) and IndexedDB (large data).
6
+ *
7
+ * Usage:
8
+ * // Auto-detect best storage:
9
+ * const persist = new DolphinPersist();
10
+ * dolphin.store.use(persist);
11
+ *
12
+ * // Force localStorage:
13
+ * const persist = new DolphinPersist({ driver: 'localstorage' });
14
+ *
15
+ * // Force IndexedDB:
16
+ * const persist = new DolphinPersist({ driver: 'indexeddb' });
17
+ */
18
+
19
+ class DolphinPersist {
20
+ /**
21
+ * @param {{ driver?: 'auto'|'localstorage'|'indexeddb', prefix?: string, ttl?: number }} options
22
+ */
23
+ constructor(options = {}) {
24
+ this.driver = options.driver || 'auto';
25
+ this.prefix = options.prefix || 'dolphin_persist_';
26
+ this.ttl = options.ttl || 0; // 0 = no expiry (milliseconds)
27
+ this._db = null;
28
+ this._ready = false;
29
+ this._readyPromise = this._init();
30
+ }
31
+
32
+ async _init() {
33
+ if (this.driver === 'auto') {
34
+ this.driver = typeof indexedDB !== 'undefined' ? 'indexeddb' : 'localstorage';
35
+ }
36
+
37
+ if (this.driver === 'indexeddb') {
38
+ try {
39
+ await this._openIndexedDB();
40
+ } catch {
41
+ console.warn('[DolphinPersist] IndexedDB unavailable, falling back to localStorage');
42
+ this.driver = 'localstorage';
43
+ }
44
+ }
45
+
46
+ this._ready = true;
47
+ }
48
+
49
+ _openIndexedDB() {
50
+ return new Promise((resolve, reject) => {
51
+ const req = indexedDB.open('dolphin_persist', 1);
52
+ req.onupgradeneeded = (e) => {
53
+ const db = e.target.result;
54
+ if (!db.objectStoreNames.contains('cache')) {
55
+ db.createObjectStore('cache', { keyPath: 'key' });
56
+ }
57
+ };
58
+ req.onsuccess = (e) => { this._db = e.target.result; resolve(); };
59
+ req.onerror = () => reject(req.error);
60
+ });
61
+ }
62
+
63
+ async set(collection, data) {
64
+ await this._readyPromise;
65
+ const entry = {
66
+ data,
67
+ savedAt: Date.now(),
68
+ expiresAt: this.ttl ? Date.now() + this.ttl : null
69
+ };
70
+
71
+ if (this.driver === 'indexeddb') {
72
+ return new Promise((resolve, reject) => {
73
+ const tx = this._db.transaction('cache', 'readwrite');
74
+ tx.objectStore('cache').put({ key: this.prefix + collection, ...entry });
75
+ tx.oncomplete = resolve;
76
+ tx.onerror = reject;
77
+ });
78
+ } else {
79
+ try {
80
+ localStorage.setItem(this.prefix + collection, JSON.stringify(entry));
81
+ } catch (e) {
82
+ console.warn('[DolphinPersist] localStorage write failed:', e.message);
83
+ }
84
+ }
85
+ }
86
+
87
+ async get(collection) {
88
+ await this._readyPromise;
89
+ let entry = null;
90
+
91
+ if (this.driver === 'indexeddb') {
92
+ entry = await new Promise((resolve, reject) => {
93
+ const tx = this._db.transaction('cache', 'readonly');
94
+ const req = tx.objectStore('cache').get(this.prefix + collection);
95
+ req.onsuccess = () => resolve(req.result || null);
96
+ req.onerror = reject;
97
+ });
98
+ } else {
99
+ try {
100
+ const raw = localStorage.getItem(this.prefix + collection);
101
+ entry = raw ? JSON.parse(raw) : null;
102
+ } catch { entry = null; }
103
+ }
104
+
105
+ if (!entry) return null;
106
+
107
+ // TTL check
108
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
109
+ await this.clear(collection);
110
+ return null;
111
+ }
112
+
113
+ return entry.data;
114
+ }
115
+
116
+ async clear(collection) {
117
+ await this._readyPromise;
118
+ if (this.driver === 'indexeddb') {
119
+ return new Promise((resolve) => {
120
+ const tx = this._db.transaction('cache', 'readwrite');
121
+ tx.objectStore('cache').delete(this.prefix + collection);
122
+ tx.oncomplete = resolve;
123
+ });
124
+ } else {
125
+ localStorage.removeItem(this.prefix + collection);
126
+ }
127
+ }
128
+
129
+ async clearAll() {
130
+ await this._readyPromise;
131
+ if (this.driver === 'indexeddb') {
132
+ return new Promise((resolve) => {
133
+ const tx = this._db.transaction('cache', 'readwrite');
134
+ tx.objectStore('cache').clear();
135
+ tx.oncomplete = resolve;
136
+ });
137
+ } else {
138
+ const keysToRemove = [];
139
+ for (let i = 0; i < localStorage.length; i++) {
140
+ const k = localStorage.key(i);
141
+ if (k && k.startsWith(this.prefix)) keysToRemove.push(k);
142
+ }
143
+ keysToRemove.forEach(k => localStorage.removeItem(k));
144
+ }
145
+ }
146
+
147
+ /** Check what's cached */
148
+ async info(collection) {
149
+ await this._readyPromise;
150
+ const entry = await this.get(collection);
151
+ if (!entry) return null;
152
+ return {
153
+ collection,
154
+ items: entry.length,
155
+ driver: this.driver,
156
+ savedAt: new Date(entry.savedAt)
157
+ };
158
+ }
159
+ }
160
+
161
+ // ============================================
162
+ // Extend DolphinStore to support persist plugin
163
+ // ============================================
164
+
165
+ /**
166
+ * Monkey-patches the DolphinStore to support .use(persist) plugin.
167
+ * Call this after DolphinClient is loaded.
168
+ */
169
+ function enablePersist(storeInstance, persist) {
170
+ const originalFetch = storeInstance._fetchAndSync.bind(storeInstance);
171
+
172
+ storeInstance._fetchAndSync = async function(name) {
173
+ // 1. Load from cache first (instant render)
174
+ const cached = await persist.get(name);
175
+ if (cached && cached.length > 0) {
176
+ storeInstance.data.set(name, cached);
177
+ storeInstance._notify();
178
+ console.log(`[DolphinPersist] Loaded "${name}" from ${persist.driver} cache (${cached.length} items)`);
179
+ }
180
+
181
+ // 2. Fetch fresh from server
182
+ await originalFetch(name);
183
+
184
+ // 3. Save updated data to cache
185
+ const fresh = storeInstance.data.get(name) || [];
186
+ await persist.set(name, fresh);
187
+ };
188
+
189
+ // Also persist on every realtime update
190
+ const originalUpdate = storeInstance._handleRemoteUpdate.bind(storeInstance);
191
+ storeInstance._handleRemoteUpdate = async function(collection, update) {
192
+ originalUpdate(collection, update);
193
+ const updated = storeInstance.data.get(collection) || [];
194
+ await persist.set(collection, updated);
195
+ };
196
+
197
+ console.log(`[DolphinPersist] Persistence enabled using ${persist.driver}`);
198
+ }
199
+
200
+ // ============================================
201
+ // Exports
202
+ // ============================================
203
+
204
+ if (typeof window !== 'undefined') {
205
+ window.DolphinPersist = DolphinPersist;
206
+ window.enablePersist = enablePersist;
207
+ }
208
+
209
+ if (typeof module !== 'undefined' && module.exports) {
210
+ module.exports = { DolphinPersist, enablePersist };
211
+ }