edmaxlabs-core 2.5.6 โ†’ 2.6.7

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/README.md CHANGED
@@ -3,302 +3,445 @@
3
3
  [![npm version](https://badge.fury.io/js/edmaxlabs-core.svg)](https://badge.fury.io/js/edmaxlabs-core)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- `edmaxlabs-core` is the official SDK for EdmaxLabs services, providing seamless integration with Authentication, Database, Storage, and Cloud Functions. **Framework agnostic** - works with any JavaScript framework or vanilla JS.
6
+ `edmaxlabs-core` is the official framework-agnostic SDK for EdmaxLabs services. It supports database access, realtime listeners, offline persistence, authentication, storage, and cloud functions.
7
7
 
8
- ## ๐Ÿš€ Features
9
-
10
- - **Authentication** - Secure user authentication
11
- - **Database** - Real-time database with offline support
12
- - **Storage** - File upload/download
13
- - **Cloud Functions** - Serverless function execution
14
- - **Framework Agnostic** - Works with React, Vue, Angular, vanilla JS, Node.js
15
- - **Offline Support** - Automatic sync when online
16
- - **TypeScript** - Full TypeScript support
17
-
18
- ## ๐Ÿ“ฆ Installation
8
+ ## Installation
19
9
 
20
10
  ```bash
21
11
  npm install edmaxlabs-core
22
12
  ```
23
13
 
24
- ## ๐Ÿง  Quick Start
25
-
26
- ### Basic Usage
14
+ ## Create an App
27
15
 
28
- ```typescript
16
+ ```ts
29
17
  import EdmaxLabs from "edmaxlabs-core";
30
18
 
31
19
  const app = new EdmaxLabs({
32
20
  token: "your-api-token",
33
21
  project: "your-project-id",
34
- persistence: true, // Enable offline persistence
22
+ persistence: true,
23
+ app_name: "my-web-app",
35
24
  });
36
-
37
- // Get data
38
- const messages = await app.getDatabase.collection("messages").get();
39
- console.log(messages);
40
25
  ```
41
26
 
42
- ### React Integration (Recommended)
27
+ Config options:
28
+
29
+ - `token`: required API token
30
+ - `project`: required project id
31
+ - `persistence`: enable offline persistence and sync
32
+ - `app_name`: optional cache namespace for the local persistence layer
33
+ - `default_bucket`: optional default storage bucket
34
+
35
+ ## React Quick Start
43
36
 
44
37
  ```tsx
38
+ import { useEffect, useState } from "react";
45
39
  import EdmaxLabs from "edmaxlabs-core";
46
40
 
47
41
  const app = new EdmaxLabs({
48
- token: "your-token",
42
+ token: "your-api-token",
49
43
  project: "your-project-id",
50
44
  persistence: true,
45
+ app_name: "chat-app",
51
46
  });
52
47
 
53
- function ChatApp() {
54
- const [messages, setMessages] = useState([]);
48
+ export function MessagesList() {
49
+ const [messages, setMessages] = useState<any[]>([]);
55
50
  const [loading, setLoading] = useState(true);
56
51
 
57
52
  useEffect(() => {
58
- const unsubscribe = app.getDatabase.collection("messages").onSnapshot(
59
- (docs) => {
60
- setMessages(docs.map(doc => doc.data()));
53
+ const unsubscribe = app.getDatabase
54
+ .collection("messages")
55
+ .onSnapshot((snapshots) => {
56
+ setMessages(snapshots.map((doc) => doc.data));
61
57
  setLoading(false);
62
- }
63
- );
64
- return unsubscribe; // Important: cleanup to prevent memory leaks
58
+ });
59
+
60
+ return unsubscribe;
65
61
  }, []);
66
62
 
67
- if (loading) return <div>Loading...</div>;
63
+ if (loading) return <p>Loading...</p>;
68
64
 
69
65
  return (
70
- <div>
71
- {messages.map(msg => (
72
- <div key={msg.id}>{msg.text}</div>
66
+ <ul>
67
+ {messages.map((message) => (
68
+ <li key={message.id}>{message.text}</li>
73
69
  ))}
74
- </div>
70
+ </ul>
75
71
  );
76
72
  }
77
73
  ```
78
- <div>
79
- {messages.map(msg => (
80
- <div key={msg.id}>{msg.text}</div>
81
- ))}
82
- </div>
83
- );
84
- }
74
+
75
+ ## Realtime
76
+
77
+ ### `onSnapshot`
78
+
79
+ `onSnapshot` is still fully supported. For collections and queries it returns the full current result set every time something changes.
80
+
81
+ ```ts
82
+ const unsubscribe = app.getDatabase
83
+ .collection("messages")
84
+ .onSnapshot((snapshots, change, changedDocId) => {
85
+ console.log("change:", change);
86
+ console.log("changedDocId:", changedDocId);
87
+ console.log("docs:", snapshots.map((doc) => doc.data));
88
+ });
89
+
90
+ unsubscribe();
85
91
  ```
86
92
 
87
- ## ๐Ÿ”ง Configuration
93
+ Document listeners work the same way:
88
94
 
89
- ```typescript
90
- const app = new EdmaxLabs({
91
- token: "your-api-token", // Required: Your API token
92
- project: "your-project-id", // Required: Project identifier
93
- baseUrl: "https://api.edmaxlabs.com", // Optional: API endpoint
94
- persistence: true, // Optional: Enable offline persistence
95
- appName: "my-app", // Optional: Unique app identifier
96
- });
95
+ ```ts
96
+ const unsubscribe = app.getDatabase
97
+ .collection("messages")
98
+ .doc("message-1")
99
+ .onSnapshot((snapshot, change) => {
100
+ console.log("change:", change);
101
+ console.log("doc:", snapshot?.data ?? null);
102
+ });
103
+ ```
104
+
105
+ ### `onChildListener`
106
+
107
+ Yes, `onChildListener` is now available.
108
+
109
+ Use it when you want Firestore-style granular callbacks instead of diffing the full array yourself.
110
+
111
+ ```ts
112
+ const unsubscribe = app.getDatabase
113
+ .collection("messages")
114
+ .onChildListener({
115
+ onChildAdded(snapshot, context) {
116
+ console.log("added:", snapshot.data);
117
+ console.log("index:", context.newIndex);
118
+ console.log("fromCache:", context.fromCache);
119
+ },
120
+ onChildUpdated(snapshot, context) {
121
+ console.log("updated:", snapshot.data);
122
+ console.log("oldIndex:", context.oldIndex);
123
+ console.log("newIndex:", context.newIndex);
124
+ console.log("previous:", context.previousDoc?.data ?? null);
125
+ },
126
+ onChildRemoved(snapshot, context) {
127
+ console.log("removed:", snapshot.data);
128
+ console.log("oldIndex:", context.oldIndex);
129
+ },
130
+ });
131
+
132
+ unsubscribe();
133
+ ```
134
+
135
+ It also works on queries:
136
+
137
+ ```ts
138
+ const unsubscribe = app.getDatabase
139
+ .collection("messages")
140
+ .query
141
+ .where({ key: "roomId", op: "==", value: "general" })
142
+ .onChildListener({
143
+ onChildAdded(snapshot) {
144
+ console.log("room message:", snapshot.data);
145
+ },
146
+ });
97
147
  ```
98
148
 
99
- ## ๐Ÿ“š API Reference
149
+ Child listener callback context includes:
150
+
151
+ - `snapshots`: the current full query result
152
+ - `source`: `"initial" | "insert" | "update" | "delete"`
153
+ - `changedDocId`: the document that triggered the emission
154
+ - `fromCache`: whether the emission came from local cache
155
+ - `oldIndex`: previous index in the result set
156
+ - `newIndex`: next index in the result set
157
+ - `previousDoc`: previous version of the document when available
158
+
159
+ ## Collections and Documents
160
+
161
+ ### Get all documents
162
+
163
+ ```ts
164
+ const users = await app.getDatabase.collection("users").get();
165
+
166
+ users.forEach((user) => {
167
+ console.log(user.id, user.data);
168
+ });
169
+ ```
100
170
 
101
- ### Database Operations
171
+ ### Get one document
102
172
 
103
- ```typescript
104
- // Get collection
105
- const collection = app.getDatabase.collection("users");
173
+ ```ts
174
+ const user = await app.getDatabase.collection("users").doc("user-1").get();
106
175
 
107
- // Get all documents
108
- const users = await collection.get();
176
+ if (user) {
177
+ console.log(user.data);
178
+ }
179
+ ```
109
180
 
110
- // Get specific document
111
- const user = await collection.doc("user-id").get();
181
+ ### Create with generated id
112
182
 
113
- // Create/update document
114
- await collection.doc("user-id").set({
115
- name: "John Doe",
116
- email: "john@example.com"
183
+ ```ts
184
+ const created = await app.getDatabase.collection("messages").add({
185
+ text: "Hello world",
186
+ roomId: "general",
187
+ createdAt: Date.now(),
117
188
  });
189
+ ```
118
190
 
119
- // Update document
120
- await collection.doc("user-id").update({
121
- lastLogin: new Date()
191
+ ### Set a known id
192
+
193
+ ```ts
194
+ await app.getDatabase.collection("users").doc("user-1").set({
195
+ name: "Jane",
196
+ email: "jane@example.com",
122
197
  });
198
+ ```
123
199
 
124
- // Delete document
125
- await collection.doc("user-id").delete();
200
+ ### Update a document
126
201
 
127
- // Real-time listeners
128
- const unsubscribe = collection.onSnapshot((snapshot) => {
129
- console.log("Collection updated:", snapshot.docs);
202
+ ```ts
203
+ await app.getDatabase.collection("users").doc("user-1").update({
204
+ lastLoginAt: Date.now(),
130
205
  });
131
206
  ```
132
207
 
133
- ### Real-time Listeners
134
-
135
- ```typescript
136
- // Document listener
137
- const unsubscribeDoc = app.getDatabase.collection("users").doc("user-id").onSnapshot(
138
- (snapshot, change) => {
139
- console.log("Document:", snapshot?.data());
140
- }
141
- );
142
-
143
- // Collection listener
144
- const unsubscribeCollection = app.getDatabase.collection("messages").onSnapshot(
145
- (documents, change) => {
146
- console.log("Messages:", documents.map(doc => doc.data()));
147
- }
148
- );
149
-
150
- // Cleanup when done
151
- unsubscribeDoc();
152
- unsubscribeCollection();
208
+ ### Delete a document
209
+
210
+ ```ts
211
+ await app.getDatabase.collection("users").doc("user-1").delete();
153
212
  ```
154
213
 
155
- ### Storage Operations
214
+ ## Queries
156
215
 
157
- ```typescript
158
- // Upload file
159
- const ref = app.getStorage.ref("avatars/user-id.jpg");
160
- await ref.put(file);
216
+ Build queries with `collection.query.where(...)`.
161
217
 
162
- // Get download URL
163
- const url = await ref.getDownloadURL();
218
+ ```ts
219
+ const activeUsers = await app.getDatabase
220
+ .collection("users")
221
+ .query
222
+ .where({ key: "status", op: "==", value: "active" })
223
+ .where({ key: "age", op: ">=", value: 18 })
224
+ .get();
225
+ ```
164
226
 
165
- // Delete file
166
- await ref.delete();
227
+ Supported operators:
228
+
229
+ - `==`
230
+ - `===`
231
+ - `!=`
232
+ - `<`
233
+ - `<=`
234
+ - `>`
235
+ - `>=`
236
+ - `in`
237
+ - `nin`
238
+ - `contains`
239
+
240
+ Query snapshots are supported too:
241
+
242
+ ```ts
243
+ const unsubscribe = app.getDatabase
244
+ .collection("users")
245
+ .query
246
+ .where({ key: "status", op: "==", value: "active" })
247
+ .onSnapshot((snapshots) => {
248
+ console.log("active users:", snapshots.map((doc) => doc.data));
249
+ });
167
250
  ```
168
251
 
169
- ### Authentication
252
+ ## Offline Persistence
170
253
 
171
- ```typescript
172
- // Get current user
173
- const user = app.getAuthentication.currentUser;
254
+ Enable it with `persistence: true`.
174
255
 
175
- // Listen to auth changes
176
- const unsubscribe = app.getAuthentication.onAuthStateChanged((user) => {
177
- console.log("User:", user);
256
+ ```ts
257
+ const app = new EdmaxLabs({
258
+ token: "your-api-token",
259
+ project: "your-project-id",
260
+ persistence: true,
178
261
  });
179
262
  ```
180
263
 
181
- ## ๐Ÿ›ก๏ธ Production Considerations
264
+ What you get:
182
265
 
183
- ### Memory Leak Prevention
266
+ - local cached reads
267
+ - optimistic local writes
268
+ - background sync when the network returns
269
+ - realtime listeners backed by the local cache first
184
270
 
185
- **Always cleanup listeners** when components unmount:
271
+ Useful helpers:
186
272
 
187
- ```tsx
188
- function MyComponent() {
189
- useEffect(() => {
190
- const unsubscribe = app.getDatabase.collection("items").onSnapshot(callback);
191
- return unsubscribe; // โœ… Critical: prevent memory leaks
192
- }, []);
193
- }
194
- ```
273
+ ```ts
274
+ app.isOfflineEnabled;
195
275
 
196
- ### Error Handling
276
+ await app.sync();
277
+ await app.retrySync();
197
278
 
198
- ```typescript
199
- try {
200
- const result = await app.getDatabase.collection("users").doc("id").set(data);
201
- } catch (error) {
202
- if (error.message.includes("quota exceeded")) {
203
- // Handle storage full
204
- alert("Storage is full. Please clear data.");
205
- }
206
- }
279
+ const failed = await app.getFailedMutations();
280
+ console.log(failed);
281
+
282
+ const usage = await app.getStorageUsage();
283
+ console.log(usage);
284
+
285
+ await app.clearCache();
286
+
287
+ const offline = app.offline();
288
+ console.log(offline.enabled);
289
+ console.log(offline.getListenerCount());
207
290
  ```
208
291
 
209
- ### Error Handling
210
-
211
- ```typescript
212
- try {
213
- const result = await app.getDatabase.collection("users").doc("id").set(data);
214
- } catch (error) {
215
- if (error.message.includes("quota exceeded")) {
216
- // Handle storage full
217
- alert("Storage is full. Please clear some data.");
218
- } else if (error.message.includes("Network")) {
219
- // Handle offline
220
- console.log("Will sync when online");
221
- }
222
- }
292
+ ## Authentication
293
+
294
+ ### Current user
295
+
296
+ ```ts
297
+ const user = app.getAuthentication.currentUser();
298
+ console.log(user);
223
299
  ```
224
300
 
225
- ### Storage Limits
301
+ ### Auth state listener
302
+
303
+ ```ts
304
+ const unsubscribe = app.getAuthentication.authState({
305
+ onChange(user) {
306
+ console.log("signed in:", user);
307
+ },
308
+ onSignOut() {
309
+ console.log("signed out");
310
+ },
311
+ onDeleted() {
312
+ console.log("user deleted");
313
+ },
314
+ });
315
+ ```
226
316
 
227
- Monitor storage usage to prevent quota issues:
317
+ ### Create account
228
318
 
229
- ```typescript
230
- const { usage } = await app.getStorageUsage();
231
- if (usage && usage.used > usage.available * 0.8) {
232
- console.warn("Storage usage is over 80%");
233
- }
319
+ ```ts
320
+ const user = await app.getAuthentication.createUserWithEmailAndPassword({
321
+ email: "jane@example.com",
322
+ password: "secret123",
323
+ });
234
324
  ```
235
325
 
236
- ### Input Validation
326
+ ### Sign in
327
+
328
+ ```ts
329
+ const user = await app.getAuthentication.signInWithEmailAndPassword({
330
+ email: "jane@example.com",
331
+ password: "secret123",
332
+ });
333
+ ```
237
334
 
238
- The SDK validates inputs automatically, but you should also validate:
335
+ ### Sign out
239
336
 
240
- ```typescript
241
- // These will throw errors:
242
- await doc.set(null); // Error: data cannot be null
243
- await doc.set({ id: "123" }); // Error: 'id' is reserved
244
- await doc.set([]); // Error: data cannot be an array
337
+ ```ts
338
+ await app.getAuthentication.signOut();
245
339
  ```
246
340
 
247
- ## ๐Ÿงช Testing
341
+ ## Storage
248
342
 
249
- ```bash
250
- # Run tests
251
- npm test
343
+ ```ts
344
+ const uploaded = await app.getStorage.upload(file);
345
+ console.log(uploaded);
346
+
347
+ const fileMeta = await app.getStorage.get("file-id");
348
+ const fileInfo = await app.getStorage.getMeta("file-id");
252
349
 
253
- # Run tests in watch mode
254
- npm run test:watch
350
+ await app.getStorage.delete("file-id");
351
+ ```
255
352
 
256
- # Run tests with coverage
257
- npm run test:coverage
353
+ ## Cloud Functions
354
+
355
+ ```ts
356
+ const result = await app.getFunctions.call("sendWelcomeEmail", {
357
+ userId: "user-1",
358
+ });
258
359
 
259
- # Type checking
260
- npm run lint
360
+ console.log(result);
261
361
  ```
262
362
 
263
- ## ๐Ÿ“– Documentation
363
+ ## Batch Writes
364
+
365
+ ```ts
366
+ const db = app.getDatabase;
367
+ const batch = db.batch();
368
+
369
+ batch.set(db.collection("users").doc("u1"), {
370
+ name: "Jane",
371
+ });
372
+
373
+ batch.update(db.collection("users").doc("u2"), {
374
+ online: true,
375
+ });
376
+
377
+ batch.delete(db.collection("users").doc("u3"));
378
+
379
+ await batch.commit();
380
+ ```
264
381
 
265
- - [Full API Reference](https://edmaxlabs.com/docs/api)
266
- - [Migration Guide](https://edmaxlabs.com/docs/migration)
267
- - [Best Practices](https://edmaxlabs.com/docs/best-practices)
382
+ ## Important Notes
268
383
 
269
- ## ๐Ÿค Contributing
384
+ - Always unsubscribe realtime listeners in React `useEffect` cleanup.
385
+ - `onSnapshot` returns full results for collections and queries.
386
+ - `onChildListener` gives you granular callbacks like Firestore.
387
+ - With persistence enabled, listeners can emit cached data first and then refresh with synced data.
388
+ - Query results while offline reflect the latest local cache available on that device.
270
389
 
271
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md).
390
+ ## Minimal React Pattern
272
391
 
273
- ## ๐Ÿ“„ License
392
+ ```tsx
393
+ useEffect(() => {
394
+ const unsubscribe = app.getDatabase
395
+ .collection("messages")
396
+ .onChildListener({
397
+ onChildAdded(snapshot) {
398
+ console.log("new message", snapshot.data);
399
+ },
400
+ onChildUpdated(snapshot) {
401
+ console.log("edited message", snapshot.data);
402
+ },
403
+ onChildRemoved(snapshot) {
404
+ console.log("deleted message", snapshot.id);
405
+ },
406
+ });
407
+
408
+ return unsubscribe;
409
+ }, []);
410
+ ```
274
411
 
275
- MIT License - see [LICENSE](LICENSE) file for details.
276
- - [https://edmaxlabs.com/docs/database](https://edmaxlabs.com/docs/database)
277
- ---
412
+ ## Manual SDK Testing
278
413
 
279
- ## Who is this for?
414
+ There is now a manual browser test harness inside this repo.
280
415
 
281
- * Frontend developers (React, Vite, Next.js, HTML)
282
- * Backend developers (Node.js)
283
- * Teams building real-time apps or SaaS platforms
416
+ Build the SDK:
284
417
 
285
- ---
418
+ ```bash
419
+ npm run manual:test
420
+ ```
286
421
 
287
- ## Security
422
+ Start the local static server:
288
423
 
289
- * Client tokens are **public**
290
- * Access is enforced via **rules and server validation**
291
- * This SDK provides **access**, not authority
424
+ ```bash
425
+ npm run manual:test:serve
426
+ ```
292
427
 
293
- ---
428
+ Then open:
294
429
 
295
- ## Ecosystem
430
+ ```text
431
+ http://localhost:4173/manual-tests/index.html
432
+ ```
296
433
 
297
- * **edmaxlabs-tools** โ†’ CLI for managing projects, hosting, and rules
298
- * **edmaxlabs-core** โ†’ Runtime SDK for applications
434
+ What you can verify there:
299
435
 
300
- ---
436
+ - `collection.onSnapshot(...)`
437
+ - `query.onSnapshot(...)`
438
+ - `document.onSnapshot(...)`
439
+ - `collection.onChildListener(...)`
440
+ - create, set, update, delete flows
441
+ - persistence-enabled behavior in the browser
442
+ - offline/online transitions using devtools network controls
443
+ - multiple active listeners at once
301
444
 
302
- ## ๐Ÿง  Vision
445
+ ## License
303
446
 
304
- > Build once. Scale everywhere.
447
+ MIT