@hivedev/hivesdk 1.0.39 → 1.0.41

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
@@ -10,6 +10,7 @@ Every service in the Hive ecosystem (NoteHive, and any future service) is built
10
10
 
11
11
  - [Concepts](#concepts)
12
12
  - [Installation](#installation)
13
+ - [ESM vs CJS — Which to Use](#esm-vs-cjs--which-to-use)
13
14
  - [Server SDK](#server-sdk)
14
15
  - [1. Initialising the Server](#1-initialising-the-server)
15
16
  - [2. Registering the Service](#2-registering-the-service)
@@ -54,9 +55,47 @@ npm install @hivedev/hivesdk
54
55
  ```
55
56
 
56
57
  The SDK ships three pre-built files:
57
- - `hive-server.js` — ESM bundle for Node.js backends
58
- - `hive-server.cjs` CJS bundle for Node.js backends
59
- - `hive-client.js` — ESM bundle for browser frontends
58
+
59
+ | File | Format | For |
60
+ |------|--------|-----|
61
+ | `hive-server.js` | ESM | Node.js backends with `"type": "module"` in `package.json` |
62
+ | `hive-server.cjs` | CJS | Node.js backends without `"type": "module"` |
63
+ | `hive-client.js` | ESM | Browser frontends only |
64
+
65
+ > **Peer dependencies:** `nodemailer` and `mongodb` are not bundled into the SDK. Install them in your service:
66
+ > ```bash
67
+ > npm install nodemailer mongodb
68
+ > ```
69
+
70
+ ---
71
+
72
+ ## ESM vs CJS — Which to Use
73
+
74
+ The SDK provides both an ESM and a CJS build for the server. **You use one or the other depending on your own application's module system — never both.**
75
+
76
+ **If your `package.json` has `"type": "module"`**, your project is ESM. Use `import`:
77
+
78
+ ```js
79
+ import { initServer } from "@hivedev/hivesdk/server";
80
+ ```
81
+
82
+ **If your `package.json` does not have `"type": "module"`** (or explicitly has `"type": "commonjs"`), your project is CJS. Use `require`:
83
+
84
+ ```js
85
+ const { initServer } = require("@hivedev/hivesdk/server");
86
+ ```
87
+
88
+ Mixing them will fail. Using `require()` in an ESM project throws:
89
+
90
+ ```
91
+ ReferenceError: require is not defined in ES module scope
92
+ ```
93
+
94
+ Using `import` in a CJS project throws a syntax error. Check your `package.json` once and use the matching syntax everywhere.
95
+
96
+ All examples below show both variants. Use whichever matches your project.
97
+
98
+ > **Note:** CJS does not support top-level `await`. All async SDK calls in CJS must be wrapped in an `async` function or an async IIFE — `(async () => { ... })()`. The examples below show this where needed.
60
99
 
61
100
  ---
62
101
 
@@ -71,10 +110,10 @@ Call `initServer` once at the very start of your service, before anything else.
71
110
  import { initServer } from "@hivedev/hivesdk/server";
72
111
 
73
112
  await initServer({
74
- serviceName: "NoteHive", // Must match the name registered in HivePortal
75
- servicePort: 49161, // The port this service listens on
76
- remoteUrl: "", // Public-facing URL if accessible from outside LAN, empty string if LAN-only
77
- serverPassword: "" // Optional — see password resolution below
113
+ serviceName: "NoteHive", // Must match the name registered in HivePortal
114
+ servicePort: 49161, // The port this service listens on
115
+ remoteUrl: "", // Public-facing URL if accessible from outside LAN, empty if LAN-only
116
+ serverPassword: "" // Optional — see password resolution below
78
117
  });
79
118
  ```
80
119
 
@@ -82,19 +121,22 @@ await initServer({
82
121
  // CJS
83
122
  const { initServer } = require("@hivedev/hivesdk/server");
84
123
 
85
- await initServer({
86
- serviceName: "NoteHive",
87
- servicePort: 49161,
88
- remoteUrl: "",
89
- serverPassword: ""
90
- });
124
+ (async () =>
125
+ {
126
+ await initServer({
127
+ serviceName: "NoteHive",
128
+ servicePort: 49161,
129
+ remoteUrl: "",
130
+ serverPassword: ""
131
+ });
132
+ })();
91
133
  ```
92
134
 
93
135
  **Password resolution order:**
94
136
 
95
137
  1. The `serverPassword` argument, if provided.
96
138
  2. The `SERVER_PASSWORD` environment variable, if set.
97
- 3. An interactive terminal prompt, if neither of the above is available it will ask the user to create a password on first run, or enter their existing password on subsequent runs.
139
+ 3. An interactive terminal prompt — asks the user to create a password on first run, or enter their existing password on subsequent runs.
98
140
 
99
141
  The password is used as the encryption key for all `.dat` configuration files. Losing it means losing access to the stored configuration.
100
142
 
@@ -117,34 +159,33 @@ await registerService();
117
159
  // CJS
118
160
  const { registerService } = require("@hivedev/hivesdk/server");
119
161
 
120
- await registerService();
162
+ await registerService(); // inside an async function or IIFE
121
163
  ```
122
164
 
123
165
  What this does internally:
124
166
 
125
167
  1. Sets the service's local URL (`http://<hostIp>:<servicePort>`) and remote URL in the internal registry.
126
168
  2. POSTs to HivePortal's `/RegisterService` endpoint.
127
- 3. If HivePortal accepts the request, begins polling HivePortal's `/Configuration` endpoint every 2 seconds.
128
- 4. Once an administrator approves the service in HivePortal, the configuration payload is received, decrypted, and saved to disk via `saveConfiguration`.
169
+ 3. Begins polling HivePortal's `/Configuration` endpoint every 2 seconds.
170
+ 4. Once an administrator approves the service in HivePortal, the configuration payload is received, decrypted, and saved to disk.
129
171
 
130
172
  **On first run**, the terminal will show:
131
173
 
132
174
  ```
133
175
  Registering service...
134
176
  Service registration hasn't been approved yet. Please contact administrators!
135
- Service registration hasn't been approved yet. Please contact administrators!
136
177
  ...
137
178
  Configuration received.
138
179
  Saving configuration...
139
180
  Service registered.
140
181
  ```
141
182
 
142
- Once registered and approved, subsequent startups load configuration from disk directly via `loadConfiguration` and do not need to poll again, unless credentials change in HivePortal.
183
+ Once registered and approved, subsequent startups load configuration from disk directly and do not need to poll again.
143
184
 
144
185
  > In a clustered setup, call `registerService` only from the primary process, after a short delay to allow workers to start up first.
145
186
 
146
187
  ```js
147
- // ESM
188
+ // ESM — clustered
148
189
  import cluster from "cluster";
149
190
  import os from "os";
150
191
  import { initServer, registerService } from "@hivedev/hivesdk/server";
@@ -153,46 +194,29 @@ if (cluster.isPrimary)
153
194
  {
154
195
  await initServer({ serviceName: "NoteHive", servicePort: 49161, remoteUrl: "" });
155
196
 
156
- setTimeout(async () =>
157
- {
158
- await registerService();
159
- }, 3000);
197
+ setTimeout(async () => { await registerService(); }, 3000);
160
198
 
161
- for (let i = 0; i < os.cpus().length; i++)
162
- {
163
- cluster.fork({ ...process.env });
164
- }
165
- }
166
- else
167
- {
168
- // Start your HTTP server in workers
199
+ for (let i = 0; i < os.cpus().length; i++) cluster.fork({ ...process.env });
169
200
  }
170
201
  ```
171
202
 
172
203
  ```js
173
- // CJS
204
+ // CJS — clustered
174
205
  const cluster = require("cluster");
175
206
  const os = require("os");
176
207
  const { initServer, registerService } = require("@hivedev/hivesdk/server");
177
208
 
178
- if (cluster.isPrimary)
209
+ (async () =>
179
210
  {
180
- await initServer({ serviceName: "NoteHive", servicePort: 49161, remoteUrl: "" });
181
-
182
- setTimeout(async () =>
211
+ if (cluster.isPrimary)
183
212
  {
184
- await registerService();
185
- }, 3000);
213
+ await initServer({ serviceName: "NoteHive", servicePort: 49161, remoteUrl: "" });
186
214
 
187
- for (let i = 0; i < os.cpus().length; i++)
188
- {
189
- cluster.fork({ ...process.env });
215
+ setTimeout(async () => { await registerService(); }, 3000);
216
+
217
+ for (let i = 0; i < os.cpus().length; i++) cluster.fork({ ...process.env });
190
218
  }
191
- }
192
- else
193
- {
194
- // Start your HTTP server in workers
195
- }
219
+ })();
196
220
  ```
197
221
 
198
222
  ---
@@ -212,8 +236,6 @@ const app = express();
212
236
  app.use(express.json());
213
237
  app.use(cookieParser());
214
238
  app.use(handleHiveRequests); // Must come before your own routes
215
-
216
- app.get("/my-route", (request, response) => { ... });
217
239
  ```
218
240
 
219
241
  ```js
@@ -227,22 +249,20 @@ const app = express();
227
249
  app.use(express.json());
228
250
  app.use(cookieParser());
229
251
  app.use(handleHiveRequests);
230
-
231
- app.get("/my-route", (request, response) => { ... });
232
252
  ```
233
253
 
234
254
  **Routes handled automatically by this middleware:**
235
255
 
236
256
  | Method | Route | What it does |
237
257
  |--------|-------|--------------|
238
- | `POST` | `/Login` | Proxies login credentials to HivePortal, sets session cookie and/or `x-session-token` header |
239
- | `POST` | `/Logout` | Proxies logout to HivePortal, clears session |
258
+ | `POST` | `/Login` | Proxies credentials to HivePortal, sets session cookie and/or `x-session-token` header |
259
+ | `POST` | `/Logout` | Proxies logout to HivePortal |
240
260
  | `GET` | `/ServiceUrls` | Returns local or remote URL for a named service |
241
261
  | `POST` | `/IsLoggedIn` | Checks if the current session is valid with HivePortal |
242
- | `POST` | `/IsLoggedInWithPermission` | Checks session validity and a specific named permission |
262
+ | `POST` | `/IsLoggedInWithPermission` | Checks session validity and a named permission |
243
263
  | `POST` | `/SaveUserFilter` | Saves a user-defined data filter (requires `FILTER_OPERATIONS` permission) |
244
264
  | `POST` | `/PushNotificationToken` | Registers a push notification token for the current device |
245
- | `GET` | `/hive-client.js` | Serves the built client-side SDK bundle (see [Client SDK](#client-sdk)) |
265
+ | `GET` | `/hive-client.js` | Serves the built client-side SDK bundle |
246
266
 
247
267
  You do not need to implement any of these routes yourself.
248
268
 
@@ -263,7 +283,7 @@ await loadConfiguration();
263
283
  // CJS
264
284
  const { loadConfiguration } = require("@hivedev/hivesdk/server");
265
285
 
266
- await loadConfiguration();
286
+ await loadConfiguration(); // inside an async function or IIFE
267
287
  ```
268
288
 
269
289
  This populates the following environment variables (only those with saved `.dat` files on disk are loaded):
@@ -274,22 +294,33 @@ This populates the following environment variables (only those with saved `.dat`
274
294
  | `DATABASE_CREDENTIALS` | JSON string of database credentials, keyed by service name |
275
295
  | `PERMISSIONS` | JSON string of permission definitions |
276
296
  | `SMTP_CREDENTIALS` | JSON string of SMTP credentials |
277
- | `SMTP_PASSWORD` | Extracted SMTP password, ready for use in nodemailer |
297
+ | `SMTP_PASSWORD` | Extracted SMTP password, ready for nodemailer |
278
298
 
279
- It also calls `DatabaseConnector.setCredentials` internally using the credentials stored under your service's name, so the database is ready to connect after this call.
299
+ It also calls `DatabaseConnector.setCredentials` internally using the credentials for your service name, so the database is ready to connect after this call.
280
300
 
281
301
  If a `.dat` file does not exist yet (e.g. first run before registration is approved), that credential is silently skipped.
282
302
 
283
- > Call `loadConfiguration` before `registerService`. On first run the files won't exist yet and it will skip gracefully. Once `registerService` completes and the administrator approves it, files are written to disk and will be fully loaded on the next startup.
303
+ > Call `loadConfiguration` before `registerService`. On first run the files won't exist yet and it will skip gracefully. Once the administrator approves the service and `registerService` saves the config, it will be fully loaded on the next startup.
284
304
 
285
305
  ---
286
306
 
287
307
  ### 5. Connecting to the Database
288
308
 
289
- After `loadConfiguration`, use `DatabaseConnector` to establish a MongoDB connection.
309
+ `DatabaseConnector` supports two connection modes. **You use one or the other not both at the same time.**
310
+
311
+ #### Option A — MongoDB Atlas
312
+
313
+ Set the `MONGODB_ATLAS_URL` environment variable to your Atlas connection string. When this variable is present, `connect()` uses it directly and skips the local credentials system entirely — no `setCredentials` call, no `loadConfiguration` needed for the database.
314
+
315
+ The database name is read from the URL path if present (e.g. the `mydb` in `...mongodb.net/mydb`). If the URL has no path, the name falls back to whatever was set via `setCredentials`, or `"untitled"` if neither was called.
290
316
 
291
317
  ```js
292
- // ESM import directly from the SDK's built file
318
+ // Set in your environment before connect() is called
319
+ process.env.MONGODB_ATLAS_URL = "mongodb+srv://user:pass@cluster.mongodb.net/mydb";
320
+ ```
321
+
322
+ ```js
323
+ // ESM
293
324
  import DatabaseConnector from "./node_modules/@hivedev/hivesdk/DatabaseConnector.js";
294
325
 
295
326
  const connected = await DatabaseConnector.connect();
@@ -300,14 +331,35 @@ if (!connected)
300
331
  process.exit(1);
301
332
  }
302
333
 
303
- // DatabaseConnector.databaseObject is now a live MongoDB db instance
304
- const collection = DatabaseConnector.databaseObject.collection("my_collection");
334
+ const collection = DatabaseConnector.getDatabase().collection("my_collection");
305
335
  ```
306
336
 
307
337
  ```js
308
338
  // CJS
309
339
  const DatabaseConnector = require("./node_modules/@hivedev/hivesdk/DatabaseConnector.js");
310
340
 
341
+ (async () =>
342
+ {
343
+ const connected = await DatabaseConnector.connect();
344
+
345
+ if (!connected)
346
+ {
347
+ console.error("Failed to connect to database.");
348
+ process.exit(1);
349
+ }
350
+
351
+ const collection = DatabaseConnector.getDatabase().collection("my_collection");
352
+ })();
353
+ ```
354
+
355
+ #### Option B — Self-hosted / Local MongoDB
356
+
357
+ If `MONGODB_ATLAS_URL` is not set, `connect()` falls back to the local credentials flow. Credentials are set automatically by `loadConfiguration` using the service-name-keyed entry in the `DATABASE_CREDENTIALS` env var. If credentials have not been configured when `connect()` is called, it will attempt to call `loadConfiguration` itself.
358
+
359
+ ```js
360
+ // ESM — credentials are loaded automatically via loadConfiguration()
361
+ import DatabaseConnector from "./node_modules/@hivedev/hivesdk/DatabaseConnector.js";
362
+
311
363
  const connected = await DatabaseConnector.connect();
312
364
 
313
365
  if (!connected)
@@ -316,10 +368,38 @@ if (!connected)
316
368
  process.exit(1);
317
369
  }
318
370
 
319
- const collection = DatabaseConnector.databaseObject.collection("my_collection");
371
+ const collection = DatabaseConnector.getDatabase().collection("my_collection");
372
+ ```
373
+
374
+ ```js
375
+ // CJS
376
+ const DatabaseConnector = require("./node_modules/@hivedev/hivesdk/DatabaseConnector.js");
377
+
378
+ (async () =>
379
+ {
380
+ const connected = await DatabaseConnector.connect();
381
+
382
+ if (!connected)
383
+ {
384
+ console.error("Failed to connect to database.");
385
+ process.exit(1);
386
+ }
387
+
388
+ const collection = DatabaseConnector.getDatabase().collection("my_collection");
389
+ })();
320
390
  ```
321
391
 
322
- If `loadConfiguration` was not called first, `DatabaseConnector.connect()` will attempt to call it automatically.
392
+ #### Accessing the Database Object
393
+
394
+ Two equivalent ways to get the active database instance after a successful `connect()`:
395
+
396
+ ```js
397
+ // Recommended — throws a clear error if not connected
398
+ const db = DatabaseConnector.getDatabase();
399
+
400
+ // Also works — direct field access, returns null if not connected
401
+ const db = DatabaseConnector.databaseObject;
402
+ ```
323
403
 
324
404
  **Disconnecting:**
325
405
 
@@ -335,11 +415,11 @@ Use `isLoggedIn` and `isLoggedInWithPermission` to protect your own route handle
335
415
 
336
416
  When `bSendResponse` is `false`, the function returns a boolean and leaves the response untouched — use this to guard logic inside your own handlers.
337
417
 
338
- When `bSendResponse` is `true`, the function writes the result directly to the response — used internally by `handleHiveRequests` for the `/IsLoggedIn` and `/IsLoggedInWithPermission` endpoints.
418
+ When `bSendResponse` is `true`, the function writes the result directly to the response — used internally by `handleHiveRequests`.
339
419
 
340
- Both functions handle **session refresh transparently** — if HivePortal signals that the session token should be refreshed, a new cookie and/or `x-session-token` header is set automatically before your handler continues.
420
+ Both functions handle **session refresh transparently** — if HivePortal signals that the token should be refreshed, a new cookie and/or `x-session-token` header is set automatically before your handler continues.
341
421
 
342
- **Guarding a route with a login check:**
422
+ **Login check:**
343
423
 
344
424
  ```js
345
425
  // ESM
@@ -379,7 +459,7 @@ app.get("/my-protected-route", async (request, response) =>
379
459
  });
380
460
  ```
381
461
 
382
- **Guarding a route with a permission check:**
462
+ **Permission check:**
383
463
 
384
464
  The permission name must be set on `request.body.permissionName` before calling `isLoggedInWithPermission`.
385
465
 
@@ -436,8 +516,8 @@ import { sendMail } from "@hivedev/hivesdk/server";
436
516
  await sendMail(
437
517
  "sender@yourdomain.com",
438
518
  "recipient@example.com",
439
- "Subject line here",
440
- "<p>HTML body here</p>"
519
+ "Subject line",
520
+ "<p>HTML body</p>"
441
521
  );
442
522
  ```
443
523
 
@@ -448,137 +528,110 @@ const { sendMail } = require("@hivedev/hivesdk/server");
448
528
  await sendMail(
449
529
  "sender@yourdomain.com",
450
530
  "recipient@example.com",
451
- "Subject line here",
452
- "<p>HTML body here</p>"
531
+ "Subject line",
532
+ "<p>HTML body</p>"
453
533
  );
454
534
  ```
455
535
 
456
- Uses Gmail SMTP via nodemailer. The SMTP password is read from `process.env.SMTP_PASSWORD`, which is populated automatically by `loadConfiguration`. The sender Gmail address is currently hardcoded in `SendMail.js` — edit it there if needed.
536
+ Uses Gmail SMTP via nodemailer. The SMTP password is read from `process.env.SMTP_PASSWORD`, populated automatically by `loadConfiguration`. The sender Gmail address is hardcoded in `SendMail.js` — edit it in the SDK source if needed.
457
537
 
458
538
  ---
459
539
 
460
540
  ### 8. Utility Functions
461
541
 
462
- All utilities are exported from `@hivedev/hivesdk/server`.
463
-
464
- **`getAppDataDirectory()`**
465
-
466
- Returns the platform-appropriate data directory for your service, creating it if it doesn't exist. On Windows this is `%APPDATA%\<ServiceName>`, on macOS `~/Library/Application Support/<ServiceName>`, on Linux `~/.config/<ServiceName>`.
542
+ **`getAppDataDirectory()`** Platform-appropriate data directory for your service. On Windows: `%APPDATA%\<ServiceName>`, macOS: `~/Library/Application Support/<ServiceName>`, Linux: `~/.config/<ServiceName>`.
467
543
 
468
544
  ```js
469
545
  // ESM
470
546
  import { getAppDataDirectory } from "@hivedev/hivesdk/server";
471
- const dataDir = getAppDataDirectory();
472
- // e.g. "/home/user/.config/NoteHive"
547
+ const dataDir = getAppDataDirectory(); // e.g. "/home/user/.config/NoteHive"
473
548
  ```
474
-
475
549
  ```js
476
550
  // CJS
477
551
  const { getAppDataDirectory } = require("@hivedev/hivesdk/server");
478
552
  const dataDir = getAppDataDirectory();
479
553
  ```
480
554
 
481
- **`getHostIp()`**
555
+ ---
482
556
 
483
- Returns the first non-loopback IPv4 address of the host machine.
557
+ **`getHostIp()`** First non-loopback IPv4 address of the host machine.
484
558
 
485
559
  ```js
486
560
  // ESM
487
561
  import { getHostIp } from "@hivedev/hivesdk/server";
488
562
  const ip = getHostIp(); // e.g. "192.168.1.42"
489
563
  ```
490
-
491
564
  ```js
492
565
  // CJS
493
566
  const { getHostIp } = require("@hivedev/hivesdk/server");
494
567
  const ip = getHostIp();
495
568
  ```
496
569
 
497
- **`findService(serviceName)`**
570
+ ---
498
571
 
499
- Discovers another Hive service on the LAN via UDP to port 49153. Returns `{ name, urls: { local, remote } }` or `null` if not found within 3 seconds. Requires `HIVE_PORTAL_LAN_IP` environment variable to be set.
572
+ **`findService(serviceName)`** — Discovers another Hive service on the LAN via UDP to port 49153. Returns `{ name, urls: { local, remote } }` or `null`. Requires `HIVE_PORTAL_LAN_IP` to be set.
500
573
 
501
574
  ```js
502
575
  // ESM
503
576
  import { findService } from "@hivedev/hivesdk/server";
504
-
505
577
  const service = await findService("NoteHive");
506
-
507
- if (service)
508
- {
509
- console.log(service.urls.local); // "http://192.168.1.42:49161"
510
- }
578
+ if (service) console.log(service.urls.local); // "http://192.168.1.42:49161"
511
579
  ```
512
-
513
580
  ```js
514
581
  // CJS
515
582
  const { findService } = require("@hivedev/hivesdk/server");
516
-
517
583
  const service = await findService("NoteHive");
518
-
519
- if (service)
520
- {
521
- console.log(service.urls.local);
522
- }
584
+ if (service) console.log(service.urls.local);
523
585
  ```
524
586
 
525
- **`extractConfigurationForService()`**
587
+ ---
526
588
 
527
- Parses `process.env` and returns the configuration as structured objects, extracting the correct database credentials for the current service name.
589
+ **`extractConfigurationForService()`** — Parses `process.env` and returns the four credential sets as objects. Database credentials are extracted for the current service name specifically.
528
590
 
529
591
  ```js
530
592
  // ESM
531
593
  import { extractConfigurationForService } from "@hivedev/hivesdk/server";
532
-
533
594
  const { firebaseAdminCredentials, databaseCredentials, permissions, smtpCredentials } = extractConfigurationForService();
534
595
  ```
535
-
536
596
  ```js
537
597
  // CJS
538
598
  const { extractConfigurationForService } = require("@hivedev/hivesdk/server");
539
-
540
599
  const { firebaseAdminCredentials, databaseCredentials, permissions, smtpCredentials } = extractConfigurationForService();
541
600
  ```
542
601
 
543
- **`encryptAndStoreFile(data, fullPath, password)`**
602
+ ---
544
603
 
545
- Encrypts a string or Buffer with AES-256-CBC and writes it to disk. Creates the directory if it doesn't exist.
604
+ **`encryptAndStoreFile(data, fullPath, password)`** — Encrypts a string or Buffer with AES-256-CBC and writes it to disk. Creates the directory if it doesn't exist.
546
605
 
547
606
  ```js
548
607
  // ESM
549
608
  import { encryptAndStoreFile } from "@hivedev/hivesdk/server";
550
-
551
- await encryptAndStoreFile("my secret data", "/path/to/file.dat", process.env.SERVER_PASSWORD);
609
+ await encryptAndStoreFile("secret", "/path/to/file.dat", process.env.SERVER_PASSWORD);
552
610
  ```
553
-
554
611
  ```js
555
612
  // CJS
556
613
  const { encryptAndStoreFile } = require("@hivedev/hivesdk/server");
557
-
558
- await encryptAndStoreFile("my secret data", "/path/to/file.dat", process.env.SERVER_PASSWORD);
614
+ await encryptAndStoreFile("secret", "/path/to/file.dat", process.env.SERVER_PASSWORD);
559
615
  ```
560
616
 
561
- **`decryptFileWithPassword(fullPath, password)`**
617
+ ---
562
618
 
563
- Reads and decrypts an encrypted `.dat` file. Returns the decrypted string, or `null` on failure.
619
+ **`decryptFileWithPassword(fullPath, password)`** — Reads and decrypts an encrypted `.dat` file. Returns the decrypted string, or `null` on failure.
564
620
 
565
621
  ```js
566
622
  // ESM
567
623
  import { decryptFileWithPassword } from "@hivedev/hivesdk/server";
568
-
569
624
  const contents = await decryptFileWithPassword("/path/to/file.dat", process.env.SERVER_PASSWORD);
570
625
  ```
571
-
572
626
  ```js
573
627
  // CJS
574
628
  const { decryptFileWithPassword } = require("@hivedev/hivesdk/server");
575
-
576
629
  const contents = await decryptFileWithPassword("/path/to/file.dat", process.env.SERVER_PASSWORD);
577
630
  ```
578
631
 
579
- **`saveConfiguration(configurationObject)` / `loadConfiguration()`**
632
+ ---
580
633
 
581
- Normally called automatically by `registerService` and during startup respectively. Available for manual use if needed. `saveConfiguration` only writes keys that are present in the object — missing keys do not overwrite existing files.
634
+ **`saveConfiguration(configurationObject)` / `loadConfiguration()`** Normally called automatically. `saveConfiguration` only writes keys present in the object — missing keys do not overwrite existing files.
582
635
 
583
636
  ```js
584
637
  // ESM
@@ -591,7 +644,6 @@ await saveConfiguration({
591
644
 
592
645
  await loadConfiguration();
593
646
  ```
594
-
595
647
  ```js
596
648
  // CJS
597
649
  const { saveConfiguration, loadConfiguration } = require("@hivedev/hivesdk/server");
@@ -608,8 +660,6 @@ await loadConfiguration();
608
660
 
609
661
  ### Full Server Bootstrap Example
610
662
 
611
- This is the recommended startup sequence for any Hive microservice.
612
-
613
663
  ```js
614
664
  // ESM
615
665
  import express from "express";
@@ -731,13 +781,13 @@ app.get("/", (request, response) => response.redirect("/Client/Pages/HomePage.ht
731
781
 
732
782
  ## Client SDK
733
783
 
734
- The client SDK is a browser-only ESM bundle (`hive-client.js`). There is no CJS variant — it runs exclusively in the browser.
784
+ The client SDK is a browser-only ESM bundle. There is no CJS variant — it runs exclusively in the browser.
735
785
 
736
786
  ### 1. Loading the Client Bundle
737
787
 
738
- The `handleHiveRequests` middleware automatically serves the client bundle at `/hive-client.js` by reading it from `node_modules/@hivedev/hivesdk/hive-client.js`. **This works as long as your service's Node.js process is started from the root of the package** (i.e. the directory that contains `node_modules`), which is the recommended setup.
788
+ The `handleHiveRequests` middleware automatically serves the client bundle at `/hive-client.js` by reading it from `node_modules/@hivedev/hivesdk/hive-client.js`. **This works as long as your service's Node.js process is started from the root of the package** (the directory containing `node_modules`), which is the recommended setup.
739
789
 
740
- If your service is started from a different working directory, the automatic route will fail to locate the file. In that case, serve it yourself:
790
+ If your service is started from a different working directory, the automatic route will fail to locate the file. In that case, serve it manually:
741
791
 
742
792
  ```js
743
793
  import path from "path";
@@ -745,26 +795,18 @@ import { createReadStream } from "fs";
745
795
 
746
796
  app.get("/hive-client.js", (request, response) =>
747
797
  {
748
- const filePath = path.join("/absolute/path/to/node_modules/@hivedev/hivesdk/hive-client.js");
798
+ const filePath = "/absolute/path/to/node_modules/@hivedev/hivesdk/hive-client.js";
749
799
  response.setHeader("Content-Type", "application/javascript");
750
800
  createReadStream(filePath).pipe(response);
751
801
  });
752
802
  ```
753
803
 
754
- Include it in your HTML pages as a module script:
804
+ Include it in your HTML pages:
755
805
 
756
806
  ```html
757
807
  <script type="module" src="/hive-client.js"></script>
758
808
  ```
759
809
 
760
- Or import from it in another module script on the page:
761
-
762
- ```html
763
- <script type="module">
764
- import { login } from "/hive-client.js";
765
- </script>
766
- ```
767
-
768
810
  ---
769
811
 
770
812
  ### 2. Initialising the Client
@@ -777,8 +819,6 @@ import { initClient } from "/hive-client.js";
777
819
  await initClient({});
778
820
  ```
779
821
 
780
- `initClient` accepts one optional parameter:
781
-
782
822
  | Parameter | Type | Default | Description |
783
823
  |-----------|------|---------|-------------|
784
824
  | `loginTokenStorageMethod` | `sessionStorageMethod` enum | `HTTP_ONLY_COOKIE` | How session tokens are stored on the client |
@@ -787,7 +827,7 @@ await initClient({});
787
827
 
788
828
  ### 3. Login
789
829
 
790
- Call `login()` to open the HivePortal login page. The SDK handles the entire flow — the popup opens, the user enters their credentials, and on success the page reloads at `/`.
830
+ Call `login()` to open the HivePortal login page. The SDK handles the entire flow — the popup opens, the user enters their credentials, and on success the page redirects to `/`.
791
831
 
792
832
  ```js
793
833
  import { login } from "/hive-client.js";
@@ -795,11 +835,9 @@ import { login } from "/hive-client.js";
795
835
  login();
796
836
  ```
797
837
 
798
- You can wire it to any element:
838
+ Wire it to any element:
799
839
 
800
840
  ```js
801
- import { login } from "/hive-client.js";
802
-
803
841
  document.getElementById("login-button").addEventListener("click", login);
804
842
  ```
805
843
 
@@ -809,7 +847,7 @@ When running inside the native mobile app (detected automatically), the login po
809
847
 
810
848
  ### 4. Checking Login State
811
849
 
812
- `initClient` automatically starts a periodic login check (every 30 seconds, clamped between 10 and 60 seconds). Each check fires a `login-state-changed` custom event on `window`.
850
+ `initClient` starts a periodic login check (every 30 seconds, clamped between 10 and 60 seconds). Each check fires a `login-state-changed` custom event on `window`.
813
851
 
814
852
  ```js
815
853
  window.addEventListener("login-state-changed", (event) =>
@@ -821,7 +859,7 @@ window.addEventListener("login-state-changed", (event) =>
821
859
  });
822
860
  ```
823
861
 
824
- The current login state is also available synchronously at any time:
862
+ Current login state is also available synchronously at any time:
825
863
 
826
864
  ```js
827
865
  if (window.IS_LOGGED_IN)
@@ -874,8 +912,6 @@ if (window.IS_LOGGED_IN)
874
912
 
875
913
  ## Enumerations
876
914
 
877
- The SDK exports several enumerations used throughout the Hive ecosystem.
878
-
879
915
  **`userPrivileges`** — Numeric privilege levels assigned to users.
880
916
 
881
917
  | Key | Value |
@@ -921,7 +957,7 @@ The SDK exports several enumerations used throughout the Hive ecosystem.
921
957
  | `USER_REGISTERATION` | 0 |
922
958
  | `SERVICE_REGISTERATION` | 1 |
923
959
 
924
- **`filterTypes`**, **`filterOperators`**, **`userDataFetchFilterComparision`**, **`userFieldTypes`**, **`userFieldFormats`**, **`fileTreeNodeTypes`** — Domain-specific enumerations used for data querying, user field definitions, and file tree structures. Refer to their respective source files for values.
960
+ **`filterTypes`**, **`filterOperators`**, **`userDataFetchFilterComparision`**, **`userFieldTypes`**, **`userFieldFormats`**, **`fileTreeNodeTypes`** — Domain-specific enumerations for data querying, user field definitions, and file tree structures. Refer to their respective source files for values.
925
961
 
926
962
  ---
927
963
 
@@ -934,6 +970,7 @@ The SDK exports several enumerations used throughout the Hive ecosystem.
934
970
  | `SERVICE_PORT` | `initServer` | Port this service listens on |
935
971
  | `REMOTE_URL` | `initServer` | Public-facing URL of this service (empty if LAN-only) |
936
972
  | `HIVE_PORTAL_LAN_IP` | External | LAN IP of HivePortal, used by `findService` for UDP discovery |
973
+ | `MONGODB_ATLAS_URL` | External | Atlas connection string — if set, bypasses local DB credentials entirely |
937
974
  | `FIREBASE_ADMIN_CREDENTIALS` | `loadConfiguration` | JSON string of Firebase Admin credentials |
938
975
  | `DATABASE_CREDENTIALS` | `loadConfiguration` | JSON string of DB credentials keyed by service name |
939
976
  | `PERMISSIONS` | `loadConfiguration` | JSON string of permission definitions |
@@ -980,8 +1017,8 @@ Every authentication check a service performs is a proxied call to HivePortal. H
980
1017
 
981
1018
  ## Known Limitations
982
1019
 
983
- - **`ScheduleServiceUrlUpdate`** — This function is currently a stub and does nothing. It is intended for future use to periodically re-broadcast service URLs to HivePortal if the host IP changes.
984
- - **`loginTokenStorageMethod`** — The `LOCAL_STORAGE` and `SESSION_STORAGE` options in the `sessionStorageMethod` enum are defined but not yet implemented. Only `HTTP_ONLY_COOKIE` (with the `x-session-token` header fallback for native app clients) is fully functional.
985
- - **SMTP sender address** — The sender Gmail address in `SendMail.js` is hardcoded. Edit it in the SDK source if your service needs a different address.
986
- - **MongoDB only** — `DatabaseConnector` is built for MongoDB. There is no support for other databases.
987
- - **MongoDB port hardcoded** — The MongoDB port is hardcoded to `27017`. The host is configurable via credentials but the port is not.
1020
+ - **`ScheduleServiceUrlUpdate`** — Currently a stub and does nothing. Intended for future use to re-broadcast service URLs if the host IP changes.
1021
+ - **`loginTokenStorageMethod`** — `LOCAL_STORAGE` and `SESSION_STORAGE` are defined in the enum but not yet implemented. Only `HTTP_ONLY_COOKIE` (with the `x-session-token` header fallback for native app clients) is fully functional.
1022
+ - **SMTP sender address** — Hardcoded in `SendMail.js`. Edit it in the SDK source if your service needs a different address.
1023
+ - **MongoDB only** — `DatabaseConnector` is built for MongoDB only.
1024
+ - **MongoDB port hardcoded** — For local connections, the port is fixed at `27017`. The host is configurable via credentials but the port is not. Use Atlas (`MONGODB_ATLAS_URL`) if you need a non-standard port.
package/hive-server.cjs CHANGED
@@ -326,29 +326,52 @@ var DatabaseConnector = class _DatabaseConnector {
326
326
  static databaseObject = null;
327
327
  /**
328
328
  * Connects to the MongoDB database.
329
- * If the database configuration is not set, it will call loadConfiguration() to load the configuration.
330
- * If the database configuration is still not set after calling loadConfiguration(), it will return false.
331
- * If the MongoClient object is not set, it will create a new MongoClient object with the database URL and credentials.
332
- * It will then try to connect to the database using the MongoClient object.
333
- * If the connection is successful, it will set the databaseObject to the database object returned by the MongoClient.
334
- * If the connection fails, it will log an error message and return false.
335
- * @returns {boolean} True if the connection is successful, false otherwise.
329
+ *
330
+ * If the MONGODB_ATLAS_URL environment variable is set, it is used directly as the
331
+ * connection string and the local credentials configuration is bypassed entirely.
332
+ * The database name is parsed from the URL path if present (e.g. the "mydb" in
333
+ * "mongodb+srv://user:pass@cluster.mongodb.net/mydb"), otherwise the value set via
334
+ * setCredentials is used.
335
+ *
336
+ * If MONGODB_ATLAS_URL is not set, the normal local credentials flow applies:
337
+ * if credentials have not been configured, loadConfiguration() is called first.
338
+ *
339
+ * @returns {Promise<boolean>} True if the connection is successful, false otherwise.
336
340
  */
337
341
  static async connect() {
338
- if (!_DatabaseConnector.#bConfigured) {
339
- await loadConfiguration();
340
- if (!_DatabaseConnector.#bConfigured) {
341
- console.log("Database configuration is not set! Please provide database credentials first.");
342
- return false;
342
+ const atlasUrl = process.env.MONGODB_ATLAS_URL;
343
+ if (atlasUrl) {
344
+ if (!_DatabaseConnector.#mongoClient) {
345
+ let databaseName = _DatabaseConnector.#databaseName;
346
+ try {
347
+ const parsedUrl = new URL(atlasUrl);
348
+ const nameFromUrl = parsedUrl.pathname.replace("/", "").trim();
349
+ if (nameFromUrl) {
350
+ databaseName = nameFromUrl;
351
+ }
352
+ } catch (error) {
353
+ console.error("DatabaseConnector: Failed to parse MONGODB_ATLAS_URL.", error);
354
+ return false;
355
+ }
356
+ _DatabaseConnector.#mongoClient = new import_mongodb.MongoClient(atlasUrl);
357
+ _DatabaseConnector.#databaseName = databaseName;
343
358
  }
344
- }
345
- if (!_DatabaseConnector.#mongoClient) {
346
- _DatabaseConnector.#mongoClient = new import_mongodb.MongoClient(
347
- _DatabaseConnector.#databaseUrl,
348
- {
349
- auth: { username: _DatabaseConnector.#username, password: _DatabaseConnector.#password }
359
+ } else {
360
+ if (!_DatabaseConnector.#bConfigured) {
361
+ await loadConfiguration();
362
+ if (!_DatabaseConnector.#bConfigured) {
363
+ console.log("Database configuration is not set! Please provide database credentials first.");
364
+ return false;
350
365
  }
351
- );
366
+ }
367
+ if (!_DatabaseConnector.#mongoClient) {
368
+ _DatabaseConnector.#mongoClient = new import_mongodb.MongoClient(
369
+ _DatabaseConnector.#databaseUrl,
370
+ {
371
+ auth: { username: _DatabaseConnector.#username, password: _DatabaseConnector.#password }
372
+ }
373
+ );
374
+ }
352
375
  }
353
376
  try {
354
377
  await _DatabaseConnector.#mongoClient.connect();
@@ -375,7 +398,8 @@ var DatabaseConnector = class _DatabaseConnector {
375
398
  console.log("Disconnected from database");
376
399
  }
377
400
  /**
378
- * Sets the database credentials for the DatabaseConnector
401
+ * Sets the database credentials for the DatabaseConnector.
402
+ * Used for local/self-hosted MongoDB instances. Not required when using MONGODB_ATLAS_URL.
379
403
  * @param {Object} obj - contains the database host, username, password, and name
380
404
  * @param {string} obj.host - The host of the database
381
405
  * @param {string} obj.username - The username of the database
@@ -394,6 +418,17 @@ var DatabaseConnector = class _DatabaseConnector {
394
418
  else throw new Error("Database name not set!");
395
419
  _DatabaseConnector.#bConfigured = true;
396
420
  }
421
+ /**
422
+ * Returns the active MongoDB database object.
423
+ * @returns {import("mongodb").Db} The active database object.
424
+ * @throws {Error} If connect() has not been called or the connection failed.
425
+ */
426
+ static getDatabase() {
427
+ if (!_DatabaseConnector.databaseObject) {
428
+ throw new Error("DatabaseConnector: No active database connection. Call connect() first.");
429
+ }
430
+ return _DatabaseConnector.databaseObject;
431
+ }
397
432
  };
398
433
  var DatabaseConnector_default = DatabaseConnector;
399
434
 
package/hive-server.js CHANGED
@@ -299,29 +299,52 @@ var DatabaseConnector = class _DatabaseConnector {
299
299
  static databaseObject = null;
300
300
  /**
301
301
  * Connects to the MongoDB database.
302
- * If the database configuration is not set, it will call loadConfiguration() to load the configuration.
303
- * If the database configuration is still not set after calling loadConfiguration(), it will return false.
304
- * If the MongoClient object is not set, it will create a new MongoClient object with the database URL and credentials.
305
- * It will then try to connect to the database using the MongoClient object.
306
- * If the connection is successful, it will set the databaseObject to the database object returned by the MongoClient.
307
- * If the connection fails, it will log an error message and return false.
308
- * @returns {boolean} True if the connection is successful, false otherwise.
302
+ *
303
+ * If the MONGODB_ATLAS_URL environment variable is set, it is used directly as the
304
+ * connection string and the local credentials configuration is bypassed entirely.
305
+ * The database name is parsed from the URL path if present (e.g. the "mydb" in
306
+ * "mongodb+srv://user:pass@cluster.mongodb.net/mydb"), otherwise the value set via
307
+ * setCredentials is used.
308
+ *
309
+ * If MONGODB_ATLAS_URL is not set, the normal local credentials flow applies:
310
+ * if credentials have not been configured, loadConfiguration() is called first.
311
+ *
312
+ * @returns {Promise<boolean>} True if the connection is successful, false otherwise.
309
313
  */
310
314
  static async connect() {
311
- if (!_DatabaseConnector.#bConfigured) {
312
- await loadConfiguration();
313
- if (!_DatabaseConnector.#bConfigured) {
314
- console.log("Database configuration is not set! Please provide database credentials first.");
315
- return false;
315
+ const atlasUrl = process.env.MONGODB_ATLAS_URL;
316
+ if (atlasUrl) {
317
+ if (!_DatabaseConnector.#mongoClient) {
318
+ let databaseName = _DatabaseConnector.#databaseName;
319
+ try {
320
+ const parsedUrl = new URL(atlasUrl);
321
+ const nameFromUrl = parsedUrl.pathname.replace("/", "").trim();
322
+ if (nameFromUrl) {
323
+ databaseName = nameFromUrl;
324
+ }
325
+ } catch (error) {
326
+ console.error("DatabaseConnector: Failed to parse MONGODB_ATLAS_URL.", error);
327
+ return false;
328
+ }
329
+ _DatabaseConnector.#mongoClient = new MongoClient(atlasUrl);
330
+ _DatabaseConnector.#databaseName = databaseName;
316
331
  }
317
- }
318
- if (!_DatabaseConnector.#mongoClient) {
319
- _DatabaseConnector.#mongoClient = new MongoClient(
320
- _DatabaseConnector.#databaseUrl,
321
- {
322
- auth: { username: _DatabaseConnector.#username, password: _DatabaseConnector.#password }
332
+ } else {
333
+ if (!_DatabaseConnector.#bConfigured) {
334
+ await loadConfiguration();
335
+ if (!_DatabaseConnector.#bConfigured) {
336
+ console.log("Database configuration is not set! Please provide database credentials first.");
337
+ return false;
323
338
  }
324
- );
339
+ }
340
+ if (!_DatabaseConnector.#mongoClient) {
341
+ _DatabaseConnector.#mongoClient = new MongoClient(
342
+ _DatabaseConnector.#databaseUrl,
343
+ {
344
+ auth: { username: _DatabaseConnector.#username, password: _DatabaseConnector.#password }
345
+ }
346
+ );
347
+ }
325
348
  }
326
349
  try {
327
350
  await _DatabaseConnector.#mongoClient.connect();
@@ -348,7 +371,8 @@ var DatabaseConnector = class _DatabaseConnector {
348
371
  console.log("Disconnected from database");
349
372
  }
350
373
  /**
351
- * Sets the database credentials for the DatabaseConnector
374
+ * Sets the database credentials for the DatabaseConnector.
375
+ * Used for local/self-hosted MongoDB instances. Not required when using MONGODB_ATLAS_URL.
352
376
  * @param {Object} obj - contains the database host, username, password, and name
353
377
  * @param {string} obj.host - The host of the database
354
378
  * @param {string} obj.username - The username of the database
@@ -367,6 +391,17 @@ var DatabaseConnector = class _DatabaseConnector {
367
391
  else throw new Error("Database name not set!");
368
392
  _DatabaseConnector.#bConfigured = true;
369
393
  }
394
+ /**
395
+ * Returns the active MongoDB database object.
396
+ * @returns {import("mongodb").Db} The active database object.
397
+ * @throws {Error} If connect() has not been called or the connection failed.
398
+ */
399
+ static getDatabase() {
400
+ if (!_DatabaseConnector.databaseObject) {
401
+ throw new Error("DatabaseConnector: No active database connection. Call connect() first.");
402
+ }
403
+ return _DatabaseConnector.databaseObject;
404
+ }
370
405
  };
371
406
  var DatabaseConnector_default = DatabaseConnector;
372
407
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hivedev/hivesdk",
3
3
  "type": "module",
4
- "version": "1.0.39",
4
+ "version": "1.0.41",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
7
7
  "scripts": {