@zero-server/sdk 0.9.5 → 0.9.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 +54 -64
- package/index.js +116 -4
- package/lib/app.js +22 -22
- package/lib/auth/authorize.js +11 -11
- package/lib/auth/enrollment.js +5 -5
- package/lib/auth/jwt.js +9 -9
- package/lib/auth/oauth.js +1 -1
- package/lib/auth/session.js +5 -5
- package/lib/auth/trustedDevice.js +2 -2
- package/lib/auth/twoFactor.js +11 -11
- package/lib/auth/webauthn.js +6 -6
- package/lib/body/json.js +1 -1
- package/lib/body/raw.js +1 -1
- package/lib/body/rawBuffer.js +1 -1
- package/lib/body/text.js +1 -1
- package/lib/body/urlencoded.js +3 -3
- package/lib/cli.js +19 -4
- package/lib/cluster.js +3 -3
- package/lib/debug.js +10 -10
- package/lib/env/index.js +11 -11
- package/lib/errors.js +131 -16
- package/lib/fetch/index.js +1 -1
- package/lib/grpc/call.js +14 -14
- package/lib/grpc/client.js +4 -4
- package/lib/grpc/codec.js +7 -7
- package/lib/grpc/credentials.js +2 -2
- package/lib/grpc/frame.js +2 -2
- package/lib/grpc/health.js +3 -3
- package/lib/grpc/index.js +3 -3
- package/lib/grpc/metadata.js +3 -3
- package/lib/grpc/proto.js +5 -5
- package/lib/grpc/reflection.js +2 -2
- package/lib/grpc/server.js +3 -3
- package/lib/grpc/status.js +2 -2
- package/lib/grpc/watch.js +1 -1
- package/lib/http/request.js +13 -13
- package/lib/http/response.js +2 -2
- package/lib/lifecycle.js +5 -5
- package/lib/middleware/compress.js +4 -4
- package/lib/observe/health.js +1 -1
- package/lib/observe/index.js +1 -1
- package/lib/observe/logger.js +3 -3
- package/lib/observe/metrics.js +4 -4
- package/lib/observe/tracing.js +4 -4
- package/lib/orm/adapters/json.js +1 -1
- package/lib/orm/adapters/memory.js +2 -2
- package/lib/orm/adapters/mongo.js +2 -2
- package/lib/orm/adapters/mysql.js +2 -2
- package/lib/orm/adapters/postgres.js +2 -2
- package/lib/orm/adapters/sqlite.js +3 -3
- package/lib/orm/audit.js +1 -1
- package/lib/orm/index.js +7 -7
- package/lib/orm/migrate.js +1 -1
- package/lib/orm/model.js +15 -15
- package/lib/orm/procedures.js +1 -1
- package/lib/orm/profiler.js +1 -1
- package/lib/orm/query.js +9 -9
- package/lib/orm/schema.js +1 -1
- package/lib/orm/seed/data/person.js +1 -1
- package/lib/orm/seed/fake.js +10 -10
- package/lib/orm/seed/index.js +4 -4
- package/lib/orm/seed/rng.js +1 -1
- package/lib/orm/snapshot.js +2 -2
- package/lib/orm/tenancy.js +6 -6
- package/lib/orm/views.js +1 -1
- package/lib/router/index.js +9 -9
- package/lib/webrtc/bot.js +361 -0
- package/lib/webrtc/cli.js +182 -0
- package/lib/webrtc/cluster.js +350 -0
- package/lib/webrtc/e2ee.js +282 -0
- package/lib/webrtc/ice.js +370 -0
- package/lib/webrtc/index.js +132 -0
- package/lib/webrtc/joinToken.js +116 -0
- package/lib/webrtc/observe.js +229 -0
- package/lib/webrtc/peer.js +116 -0
- package/lib/webrtc/room.js +171 -0
- package/lib/webrtc/sdp.js +508 -0
- package/lib/webrtc/sfu/index.js +201 -0
- package/lib/webrtc/sfu/livekit.js +301 -0
- package/lib/webrtc/sfu/mediasoup.js +317 -0
- package/lib/webrtc/sfu/memory.js +204 -0
- package/lib/webrtc/signaling.js +546 -0
- package/lib/webrtc/stun.js +492 -0
- package/lib/webrtc/turn/codec.js +370 -0
- package/lib/webrtc/turn/credentials.js +141 -0
- package/lib/webrtc/turn/server.js +633 -0
- package/package.json +2 -2
- package/types/body.d.ts +1 -1
- package/types/cli.d.ts +1 -1
- package/types/index.d.ts +16 -4
- package/types/middleware.d.ts +1 -1
- package/types/orm.d.ts +3 -3
- package/types/request.d.ts +3 -3
- package/types/webrtc.d.ts +501 -0
package/lib/grpc/watch.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Watches `.proto` files for changes using `fs.watch()` and
|
|
5
5
|
* re-parses/re-registers gRPC services automatically.
|
|
6
6
|
*
|
|
7
|
-
* **Dev-only**
|
|
7
|
+
* **Dev-only** - disabled by default when `NODE_ENV=production`.
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* const { createApp, watchProto } = require('@zero-server/sdk');
|
package/lib/http/request.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* to correctly resolve `req.ip`, `req.ips`, `req.protocol`, `req.secure`,
|
|
8
8
|
* and `req.hostname` when behind reverse proxies.
|
|
9
9
|
*
|
|
10
|
-
* HTTP/2 compatible
|
|
10
|
+
* HTTP/2 compatible - detects pseudo-headers (`:method`, `:path`,
|
|
11
11
|
* `:authority`) from HTTP/2 requests automatically.
|
|
12
12
|
*/
|
|
13
13
|
const net = require('net');
|
|
@@ -147,12 +147,12 @@ function _inCIDR(addr, cidr)
|
|
|
147
147
|
* Compile a `trust proxy` setting into a function `(addr, index) => boolean`.
|
|
148
148
|
*
|
|
149
149
|
* Supported values:
|
|
150
|
-
* - `true` / `'loopback'`
|
|
151
|
-
* - `false`
|
|
152
|
-
* - `number`
|
|
153
|
-
* - `string`
|
|
154
|
-
* - `string[]`
|
|
155
|
-
* - `function`
|
|
150
|
+
* - `true` / `'loopback'` - trust loopback IPs (127.0.0.0/8, ::1)
|
|
151
|
+
* - `false` - trust nothing
|
|
152
|
+
* - `number` - trust the first N hops (proxies)
|
|
153
|
+
* - `string` - comma-separated IPs or CIDR ranges
|
|
154
|
+
* - `string[]` - array of IPs or CIDR ranges
|
|
155
|
+
* - `function` - custom `(addr, index) => boolean`
|
|
156
156
|
*
|
|
157
157
|
* @param {*} val - The `trust proxy` setting value.
|
|
158
158
|
* @returns {Function} `(addr: string, hopIndex: number) => boolean`
|
|
@@ -177,7 +177,7 @@ function compileTrust(val)
|
|
|
177
177
|
if (val === false || val === undefined || val === null || val === 0) return () => false;
|
|
178
178
|
if (typeof val === 'number' && val > 0)
|
|
179
179
|
{
|
|
180
|
-
// Trust all addresses
|
|
180
|
+
// Trust all addresses - the hop count is handled by _resolveProxyChain
|
|
181
181
|
return () => true;
|
|
182
182
|
}
|
|
183
183
|
|
|
@@ -270,12 +270,12 @@ function _resolveProxyChain(socketAddr, xffHeader, trustFn, trustSetting)
|
|
|
270
270
|
{
|
|
271
271
|
if (!trustFn(addrs[i], addrs.length - i))
|
|
272
272
|
{
|
|
273
|
-
// This address is not trusted
|
|
273
|
+
// This address is not trusted - it's the client
|
|
274
274
|
return { ip: addrs[i], ips: chain };
|
|
275
275
|
}
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
// All addresses are trusted
|
|
278
|
+
// All addresses are trusted - leftmost is the client
|
|
279
279
|
return { ip: addrs[0] || socketAddr, ips: chain };
|
|
280
280
|
}
|
|
281
281
|
|
|
@@ -316,7 +316,7 @@ class Request
|
|
|
316
316
|
/** HTTP version (e.g. '2.0' for HTTP/2). @type {string} */
|
|
317
317
|
this.httpVersion = req.httpVersion;
|
|
318
318
|
|
|
319
|
-
/** `true` when the connection is over TLS (HTTPS)
|
|
319
|
+
/** `true` when the connection is over TLS (HTTPS) - raw socket check. @private */
|
|
320
320
|
this._socketSecure = !!(req.socket && req.socket.encrypted);
|
|
321
321
|
|
|
322
322
|
/** ALPN protocol negotiated (e.g. 'h2', 'http/1.1'). @type {string|null} */
|
|
@@ -335,7 +335,7 @@ class Request
|
|
|
335
335
|
this.locals = {};
|
|
336
336
|
|
|
337
337
|
/**
|
|
338
|
-
* The original URL as received
|
|
338
|
+
* The original URL as received - never rewritten by middleware.
|
|
339
339
|
* Set by `app.handle()`.
|
|
340
340
|
* @type {string}
|
|
341
341
|
*/
|
|
@@ -580,7 +580,7 @@ class Request
|
|
|
580
580
|
}
|
|
581
581
|
|
|
582
582
|
/**
|
|
583
|
-
* Content negotiation
|
|
583
|
+
* Content negotiation - check if the client accepts the given type(s).
|
|
584
584
|
* Returns the best match, or `false` if none match.
|
|
585
585
|
*
|
|
586
586
|
* @param {...string} types - MIME types to check (e.g. 'json', 'html', 'text/plain').
|
package/lib/http/response.js
CHANGED
|
@@ -167,7 +167,7 @@ class Response
|
|
|
167
167
|
if (!hasContentType)
|
|
168
168
|
{
|
|
169
169
|
// Heuristic: if it looks like HTML, set text/html
|
|
170
|
-
// Avoid trimStart() allocation
|
|
170
|
+
// Avoid trimStart() allocation - scan for first non-whitespace char
|
|
171
171
|
let isHTML = false;
|
|
172
172
|
for (let i = 0; i < body.length; i++)
|
|
173
173
|
{
|
|
@@ -655,7 +655,7 @@ class Response
|
|
|
655
655
|
// Handle RST_STREAM from client (browser doesn't want the push)
|
|
656
656
|
stream.on('error', (e) =>
|
|
657
657
|
{
|
|
658
|
-
if (e.code === 'ERR_HTTP2_STREAM_CANCEL') return; // Normal
|
|
658
|
+
if (e.code === 'ERR_HTTP2_STREAM_CANCEL') return; // Normal - client cancelled
|
|
659
659
|
log.debug('push stream error for %s: %s', path, e.message);
|
|
660
660
|
});
|
|
661
661
|
|
package/lib/lifecycle.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* const app = createApp();
|
|
10
10
|
* app.listen(3000);
|
|
11
11
|
*
|
|
12
|
-
* // Automatic
|
|
12
|
+
* // Automatic - SIGTERM/SIGINT handlers registered by listen()
|
|
13
13
|
* // Manual trigger:
|
|
14
14
|
* await app.shutdown();
|
|
15
15
|
*/
|
|
@@ -248,7 +248,7 @@ class LifecycleManager
|
|
|
248
248
|
/**
|
|
249
249
|
* Install `SIGTERM` and `SIGINT` process signal handlers that trigger
|
|
250
250
|
* graceful shutdown. Called automatically by `app.listen()`.
|
|
251
|
-
* Safe to call multiple times
|
|
251
|
+
* Safe to call multiple times - handlers are only installed once.
|
|
252
252
|
*/
|
|
253
253
|
installSignalHandlers()
|
|
254
254
|
{
|
|
@@ -300,14 +300,14 @@ class LifecycleManager
|
|
|
300
300
|
* Perform a full graceful shutdown.
|
|
301
301
|
*
|
|
302
302
|
* Shutdown sequence:
|
|
303
|
-
* 1. Emit `'beforeShutdown'`
|
|
303
|
+
* 1. Emit `'beforeShutdown'` - run pre-shutdown hooks (flush metrics, etc.)
|
|
304
304
|
* 2. Stop accepting new connections (server.close)
|
|
305
305
|
* 3. Close all WebSocket connections with code `1001` (Going Away)
|
|
306
306
|
* 4. Close all SSE streams
|
|
307
307
|
* 5. Drain active gRPC calls
|
|
308
308
|
* 6. Wait for in-flight HTTP requests to complete (with timeout)
|
|
309
309
|
* 7. Close all registered ORM database connections
|
|
310
|
-
* 8. Emit `'shutdown'`
|
|
310
|
+
* 8. Emit `'shutdown'` - final cleanup complete
|
|
311
311
|
*
|
|
312
312
|
* If in-flight requests do not complete within the configured
|
|
313
313
|
* timeout (default 30s), they are forcefully terminated.
|
|
@@ -345,7 +345,7 @@ class LifecycleManager
|
|
|
345
345
|
this.state = LIFECYCLE_STATE.DRAINING;
|
|
346
346
|
await this._emit('beforeShutdown');
|
|
347
347
|
|
|
348
|
-
// 2. Stop accepting new connections (non-blocking
|
|
348
|
+
// 2. Stop accepting new connections (non-blocking - server.close
|
|
349
349
|
// only resolves after all existing connections end, so we don't
|
|
350
350
|
// await it here; it will resolve after the drain step finishes).
|
|
351
351
|
const serverClosePromise = this._closeServer();
|
|
@@ -28,7 +28,7 @@ const COMPRESSIBLE = /^text\/|^application\/(json|javascript|xml|x-www-form-urle
|
|
|
28
28
|
* @param {number} [opts.threshold=1024] - Minimum body size in bytes to compress.
|
|
29
29
|
* @param {number} [opts.level] - Compression level (zlib.constants.Z_DEFAULT_COMPRESSION).
|
|
30
30
|
* @param {string|string[]} [opts.encoding] - Force specific encoding(s). Default: auto-negotiate.
|
|
31
|
-
* @param {Function} [opts.filter] - `(req, res) => boolean`
|
|
31
|
+
* @param {Function} [opts.filter] - `(req, res) => boolean` - return false to skip compression.
|
|
32
32
|
* @returns {Function} Middleware `(req, res, next) => void`.
|
|
33
33
|
*
|
|
34
34
|
* @example
|
|
@@ -139,14 +139,14 @@ function compress(opts = {})
|
|
|
139
139
|
// writeHead before write), we can no longer modify them.
|
|
140
140
|
if (raw.headersSent) return false;
|
|
141
141
|
|
|
142
|
-
// Check Content-Type
|
|
142
|
+
// Check Content-Type - skip non-compressible types
|
|
143
143
|
const ct = raw.getHeader('content-type') || '';
|
|
144
144
|
if (ct && !COMPRESSIBLE.test(ct))
|
|
145
145
|
{
|
|
146
146
|
return false;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
// Never compress SSE streams
|
|
149
|
+
// Never compress SSE streams - compression buffers
|
|
150
150
|
// the small frames and prevents real-time delivery.
|
|
151
151
|
if (ct.includes('text/event-stream'))
|
|
152
152
|
{
|
|
@@ -200,7 +200,7 @@ function compress(opts = {})
|
|
|
200
200
|
{
|
|
201
201
|
headersWritten = true;
|
|
202
202
|
|
|
203
|
-
// Check threshold
|
|
203
|
+
// Check threshold - if the total body is small, skip compression
|
|
204
204
|
const totalChunk = chunk ? (Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))) : null;
|
|
205
205
|
if (totalChunk && totalChunk.length < threshold)
|
|
206
206
|
{
|
package/lib/observe/health.js
CHANGED
|
@@ -157,7 +157,7 @@ function diskSpaceCheck(opts = {})
|
|
|
157
157
|
* @param {Object<string, Function>} [opts.checks] - Named check functions. Each returns `{ healthy, details }` or a boolean/Promise.
|
|
158
158
|
* @param {number} [opts.timeout=5000] - Max time to wait for all checks in ms.
|
|
159
159
|
* @param {boolean} [opts.verbose=true] - Include check details in response.
|
|
160
|
-
* @param {Function} [opts.onFailure] - `(results) => void`
|
|
160
|
+
* @param {Function} [opts.onFailure] - `(results) => void` - called when any check fails.
|
|
161
161
|
* @returns {Function} Route handler `(req, res) => void`.
|
|
162
162
|
*
|
|
163
163
|
* @example
|
package/lib/observe/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module observe
|
|
3
|
-
* @description Observability suite
|
|
3
|
+
* @description Observability suite - structured logging, metrics, tracing,
|
|
4
4
|
* and health checks. Zero external dependencies.
|
|
5
5
|
*/
|
|
6
6
|
const { Logger, structuredLogger, LEVELS, LEVEL_NAMES } = require('./logger');
|
package/lib/observe/logger.js
CHANGED
|
@@ -256,7 +256,7 @@ class Logger
|
|
|
256
256
|
* `requestId`, `method`, `url`, `status`, `duration`, `ip`, `userAgent`,
|
|
257
257
|
* and `contentLength`.
|
|
258
258
|
*
|
|
259
|
-
* Also attaches `req.log`
|
|
259
|
+
* Also attaches `req.log` - a child logger with bound request context
|
|
260
260
|
* so handlers can log with full correlation.
|
|
261
261
|
*
|
|
262
262
|
* @param {object} [opts] - Configuration options.
|
|
@@ -266,8 +266,8 @@ class Logger
|
|
|
266
266
|
* @param {boolean} [opts.colors] - ANSI colors (default: TTY detection). Only applies to pretty format.
|
|
267
267
|
* @param {boolean} [opts.timestamps=true] - Include timestamps.
|
|
268
268
|
* @param {WritableStream} [opts.stream] - Output stream override.
|
|
269
|
-
* @param {Function} [opts.skip] - `(req, res) => boolean`
|
|
270
|
-
* @param {Function} [opts.customFields] - `(req, res) => object`
|
|
269
|
+
* @param {Function} [opts.skip] - `(req, res) => boolean` - skip logging for certain requests.
|
|
270
|
+
* @param {Function} [opts.customFields] - `(req, res) => object` - extra fields to merge into each request log entry.
|
|
271
271
|
* @param {string} [opts.msg] - Custom message template. Supports placeholders: `:method`, `:url`, `:status`, `:duration`.
|
|
272
272
|
* @returns {Function} Middleware `(req, res, next) => void`.
|
|
273
273
|
*
|
package/lib/observe/metrics.js
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Prometheus-compatible Counter. Monotonically increasing.
|
|
38
|
-
* Thread-safe for single-threaded Node
|
|
38
|
+
* Thread-safe for single-threaded Node - no locking needed.
|
|
39
39
|
*/
|
|
40
40
|
class Counter
|
|
41
41
|
{
|
|
@@ -271,7 +271,7 @@ class Histogram
|
|
|
271
271
|
* Start a timer that, when stopped, observes the elapsed duration in seconds.
|
|
272
272
|
*
|
|
273
273
|
* @param {object} [labels] - Label values.
|
|
274
|
-
* @returns {Function} Stop function
|
|
274
|
+
* @returns {Function} Stop function - call it to record the duration.
|
|
275
275
|
*
|
|
276
276
|
* @example
|
|
277
277
|
* const end = histogram.startTimer({ method: 'GET' });
|
|
@@ -654,8 +654,8 @@ function createDefaultMetrics(registry)
|
|
|
654
654
|
*
|
|
655
655
|
* @param {object} [opts] - Options.
|
|
656
656
|
* @param {MetricsRegistry} [opts.registry] - Metrics registry. Creates a new one if not provided.
|
|
657
|
-
* @param {Function} [opts.routeLabel] - `(req) => string`
|
|
658
|
-
* @param {Function} [opts.skip] - `(req) => boolean`
|
|
657
|
+
* @param {Function} [opts.routeLabel] - `(req) => string` - extract route label for metrics. Default: `req.route || req.url`.
|
|
658
|
+
* @param {Function} [opts.skip] - `(req) => boolean` - skip metrics for certain requests.
|
|
659
659
|
* @returns {Function} Middleware `(req, res, next) => void`.
|
|
660
660
|
*
|
|
661
661
|
* @example
|
package/lib/observe/tracing.js
CHANGED
|
@@ -288,7 +288,7 @@ class Tracer
|
|
|
288
288
|
* @constructor
|
|
289
289
|
* @param {object} [opts] - Tracer options.
|
|
290
290
|
* @param {string} [opts.serviceName='unknown'] - Service name for all spans.
|
|
291
|
-
* @param {Function} [opts.exporter] - `(spans: object[]) => void | Promise<void>`
|
|
291
|
+
* @param {Function} [opts.exporter] - `(spans: object[]) => void | Promise<void>` - called with batches of serialised spans.
|
|
292
292
|
* @param {number} [opts.batchSize=100] - Max spans per export batch.
|
|
293
293
|
* @param {number} [opts.flushInterval=5000] - Auto-flush interval in ms.
|
|
294
294
|
* @param {number} [opts.sampleRate=1.0] - Sampling rate (0.0 to 1.0). 1.0 = sample everything.
|
|
@@ -414,7 +414,7 @@ class Tracer
|
|
|
414
414
|
}
|
|
415
415
|
catch (_)
|
|
416
416
|
{
|
|
417
|
-
// Silently drop export errors
|
|
417
|
+
// Silently drop export errors - tracing should never break the app
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
|
|
@@ -439,8 +439,8 @@ class Tracer
|
|
|
439
439
|
*
|
|
440
440
|
* @param {object} [opts] - Options.
|
|
441
441
|
* @param {Tracer} [opts.tracer] - Tracer instance. Creates a default if not provided.
|
|
442
|
-
* @param {Function} [opts.routeLabel] - `(req) => string`
|
|
443
|
-
* @param {Function} [opts.skip] - `(req) => boolean`
|
|
442
|
+
* @param {Function} [opts.routeLabel] - `(req) => string` - extract route label for span name.
|
|
443
|
+
* @param {Function} [opts.skip] - `(req) => boolean` - skip tracing for certain requests.
|
|
444
444
|
* @param {boolean} [opts.propagate=true] - Propagate W3C trace context via response headers.
|
|
445
445
|
* @returns {Function} Middleware `(req, res, next) => void`.
|
|
446
446
|
*
|
package/lib/orm/adapters/json.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module orm/adapters/json
|
|
3
3
|
* @description JSON file-backed database adapter.
|
|
4
|
-
* Persists data to JSON files on disk
|
|
4
|
+
* Persists data to JSON files on disk - one file per table.
|
|
5
5
|
* Zero-dependency, suitable for prototyping, small apps, and
|
|
6
6
|
* embedded scenarios. Uses atomic writes for safety.
|
|
7
7
|
*
|
|
@@ -413,7 +413,7 @@ class MemoryAdapter
|
|
|
413
413
|
for (let i = 0; i < where.length; i++)
|
|
414
414
|
{
|
|
415
415
|
const clause = where[i];
|
|
416
|
-
// Skip raw SQL clauses
|
|
416
|
+
// Skip raw SQL clauses - not supported in memory adapter
|
|
417
417
|
if (clause.raw) continue;
|
|
418
418
|
const matches = this._matchClause(row, clause);
|
|
419
419
|
|
|
@@ -695,7 +695,7 @@ class MemoryAdapter
|
|
|
695
695
|
|
|
696
696
|
/**
|
|
697
697
|
* Drop an index.
|
|
698
|
-
* @param {string} _table - Table name (unused
|
|
698
|
+
* @param {string} _table - Table name (unused - searches all tables).
|
|
699
699
|
* @param {string} name - Index name.
|
|
700
700
|
* @returns {Promise<void>}
|
|
701
701
|
*/
|
|
@@ -369,7 +369,7 @@ class MongoAdapter
|
|
|
369
369
|
|
|
370
370
|
let results = await cursor.toArray();
|
|
371
371
|
|
|
372
|
-
// Distinct
|
|
372
|
+
// Distinct - in-memory since MongoDB distinct() only returns values for a single field
|
|
373
373
|
if (distinct && fields && fields.length > 0)
|
|
374
374
|
{
|
|
375
375
|
const seen = new Set();
|
|
@@ -420,7 +420,7 @@ class MongoAdapter
|
|
|
420
420
|
for (let i = 0; i < where.length; i++)
|
|
421
421
|
{
|
|
422
422
|
const w = where[i];
|
|
423
|
-
// Skip raw SQL clauses
|
|
423
|
+
// Skip raw SQL clauses - not applicable to MongoDB
|
|
424
424
|
if (w.raw) continue;
|
|
425
425
|
const { field, op, value, logic } = w;
|
|
426
426
|
const clause = this._opToMongo(field, op, value);
|
|
@@ -685,7 +685,7 @@ class MysqlAdapter extends BaseSqlAdapter
|
|
|
685
685
|
}
|
|
686
686
|
|
|
687
687
|
/**
|
|
688
|
-
* Get full database overview
|
|
688
|
+
* Get full database overview - all tables with size and row counts.
|
|
689
689
|
* Returns a structured database summary.
|
|
690
690
|
* @returns {Promise<{ tables: Array, totalSize: string, totalRows: number }>}
|
|
691
691
|
*/
|
|
@@ -724,7 +724,7 @@ class MysqlAdapter extends BaseSqlAdapter
|
|
|
724
724
|
}
|
|
725
725
|
|
|
726
726
|
/**
|
|
727
|
-
* Get processlist
|
|
727
|
+
* Get processlist - active connections/queries.
|
|
728
728
|
* @returns {Promise<Array<{ id: number, user: string, host: string, db: string, command: string, time: number, state: string, info: string }>>}
|
|
729
729
|
*/
|
|
730
730
|
async processlist()
|
|
@@ -129,7 +129,7 @@ class PostgresAdapter extends BaseSqlAdapter
|
|
|
129
129
|
{
|
|
130
130
|
const w = where[i];
|
|
131
131
|
|
|
132
|
-
// Handle raw WHERE clauses (from whereRaw)
|
|
132
|
+
// Handle raw WHERE clauses (from whereRaw) - convert ? to $N
|
|
133
133
|
if (w.raw)
|
|
134
134
|
{
|
|
135
135
|
let rawExpr = w.raw;
|
|
@@ -847,7 +847,7 @@ class PostgresAdapter extends BaseSqlAdapter
|
|
|
847
847
|
}
|
|
848
848
|
|
|
849
849
|
/**
|
|
850
|
-
* Get full database overview
|
|
850
|
+
* Get full database overview - all tables with size and row counts.
|
|
851
851
|
* @returns {Promise<{ tables: Array, totalSize: string, totalRows: number }>}
|
|
852
852
|
*/
|
|
853
853
|
async overview()
|
|
@@ -84,7 +84,7 @@ class SqliteAdapter extends BaseSqlAdapter
|
|
|
84
84
|
this._db = new Database(filename, dbOpts);
|
|
85
85
|
this._filename = filename;
|
|
86
86
|
|
|
87
|
-
/** @private Prepared statement cache
|
|
87
|
+
/** @private Prepared statement cache - avoids recompilation overhead */
|
|
88
88
|
this._stmtCache = new Map();
|
|
89
89
|
/** @private Maximum cached statements before LRU eviction */
|
|
90
90
|
this._stmtCacheMax = options.stmtCacheSize || 256;
|
|
@@ -685,7 +685,7 @@ class SqliteAdapter extends BaseSqlAdapter
|
|
|
685
685
|
}
|
|
686
686
|
|
|
687
687
|
/**
|
|
688
|
-
* Get counts for all tables
|
|
688
|
+
* Get counts for all tables - structured database overview.
|
|
689
689
|
* @returns {{ tables: Array<{ name: string, rows: number }>, totalRows: number, fileSize: string }}
|
|
690
690
|
*/
|
|
691
691
|
overview()
|
|
@@ -807,7 +807,7 @@ class SqliteAdapter extends BaseSqlAdapter
|
|
|
807
807
|
|
|
808
808
|
/**
|
|
809
809
|
* Drop an index.
|
|
810
|
-
* @param {string} _table - Table name (unused
|
|
810
|
+
* @param {string} _table - Table name (unused - SQLite indexes are schema-scoped).
|
|
811
811
|
* @param {string} name - Index name.
|
|
812
812
|
*/
|
|
813
813
|
dropIndex(_table, name)
|
package/lib/orm/audit.js
CHANGED
package/lib/orm/index.js
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* `TYPES` enum, and schema helpers.
|
|
6
6
|
*
|
|
7
7
|
* Supported adapters (all optional "bring your own driver"):
|
|
8
|
-
* - `memory`
|
|
9
|
-
* - `json`
|
|
10
|
-
* - `sqlite`
|
|
11
|
-
* - `mysql`
|
|
12
|
-
* - `postgres`
|
|
13
|
-
* - `mongo`
|
|
8
|
+
* - `memory` - in-process (no driver needed)
|
|
9
|
+
* - `json` - JSON file persistence (no driver needed)
|
|
10
|
+
* - `sqlite` - requires `better-sqlite3`
|
|
11
|
+
* - `mysql` - requires `mysql2`
|
|
12
|
+
* - `postgres` - requires `pg`
|
|
13
|
+
* - `mongo` - requires `mongodb`
|
|
14
14
|
*
|
|
15
15
|
* @example
|
|
16
16
|
* const { Database, Model, TYPES } = require('@zero-server/sdk');
|
|
@@ -231,7 +231,7 @@ class Database
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
/**
|
|
234
|
-
* Synchronise all registered models
|
|
234
|
+
* Synchronise all registered models - create tables if they don't exist.
|
|
235
235
|
* Tables are ordered so referenced tables are created first (topological sort).
|
|
236
236
|
* @returns {Promise<void>}
|
|
237
237
|
*/
|
package/lib/orm/migrate.js
CHANGED
|
@@ -322,7 +322,7 @@ class Migrator
|
|
|
322
322
|
|
|
323
323
|
/**
|
|
324
324
|
* Fresh start: drop ALL tables (not just migrated ones) then re-migrate.
|
|
325
|
-
* ⚠️ DESTRUCTIVE
|
|
325
|
+
* ⚠️ DESTRUCTIVE - use with caution.
|
|
326
326
|
*
|
|
327
327
|
* @returns {Promise<{ migrated: string[], batch: number }>}
|
|
328
328
|
*/
|
package/lib/orm/model.js
CHANGED
|
@@ -38,13 +38,13 @@ const { EventEmitter } = require('events');
|
|
|
38
38
|
class Model
|
|
39
39
|
{
|
|
40
40
|
/**
|
|
41
|
-
* Table name
|
|
41
|
+
* Table name - override in subclass.
|
|
42
42
|
* @type {string}
|
|
43
43
|
*/
|
|
44
44
|
static table = '';
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Column schema
|
|
47
|
+
* Column schema - override in subclass.
|
|
48
48
|
* @type {Object<string, object>}
|
|
49
49
|
*/
|
|
50
50
|
static schema = {};
|
|
@@ -74,7 +74,7 @@ class Model
|
|
|
74
74
|
static hidden = [];
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
|
-
* Named query scopes
|
|
77
|
+
* Named query scopes - reusable query conditions.
|
|
78
78
|
* Each scope is a function that receives a Query and returns it.
|
|
79
79
|
* @type {Object<string, Function>}
|
|
80
80
|
*
|
|
@@ -103,7 +103,7 @@ class Model
|
|
|
103
103
|
// -- Computed & Virtual Columns ---------------------
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
|
-
* Computed column definitions
|
|
106
|
+
* Computed column definitions - virtual columns derived from other fields.
|
|
107
107
|
* Not stored in the database; calculated on the fly.
|
|
108
108
|
* Each entry maps a column name to a getter function `(instance) => value`.
|
|
109
109
|
* @type {Object<string, Function>}
|
|
@@ -122,17 +122,17 @@ class Model
|
|
|
122
122
|
static computed = {};
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
|
-
* Attribute casts
|
|
125
|
+
* Attribute casts - automatic type transformations on get/set.
|
|
126
126
|
* Maps column names to cast types or custom cast objects.
|
|
127
127
|
*
|
|
128
128
|
* Built-in cast types:
|
|
129
|
-
* - `'json'`
|
|
130
|
-
* - `'boolean'`
|
|
131
|
-
* - `'integer'`
|
|
132
|
-
* - `'float'`
|
|
133
|
-
* - `'date'`
|
|
134
|
-
* - `'string'`
|
|
135
|
-
* - `'array'`
|
|
129
|
+
* - `'json'` - JSON.parse on get, JSON.stringify on set
|
|
130
|
+
* - `'boolean'` - Cast to true/false
|
|
131
|
+
* - `'integer'` - parseInt
|
|
132
|
+
* - `'float'` - parseFloat
|
|
133
|
+
* - `'date'` - Cast to Date object
|
|
134
|
+
* - `'string'` - Cast to String
|
|
135
|
+
* - `'array'` - JSON parse/stringify for array data
|
|
136
136
|
*
|
|
137
137
|
* Custom casts:
|
|
138
138
|
* - `{ get: (v) => transformed, set: (v) => transformed }`
|
|
@@ -211,7 +211,7 @@ class Model
|
|
|
211
211
|
static _relations = {};
|
|
212
212
|
|
|
213
213
|
/**
|
|
214
|
-
* Database adapter reference
|
|
214
|
+
* Database adapter reference - set by Database.register().
|
|
215
215
|
* @type {object|null}
|
|
216
216
|
* @private
|
|
217
217
|
*/
|
|
@@ -222,7 +222,7 @@ class Model
|
|
|
222
222
|
/**
|
|
223
223
|
* @constructor
|
|
224
224
|
* Create a model instance from a data row.
|
|
225
|
-
* Generally you won't call this directly
|
|
225
|
+
* Generally you won't call this directly - use static methods.
|
|
226
226
|
*
|
|
227
227
|
* @param {object} data - Row data.
|
|
228
228
|
*/
|
|
@@ -825,7 +825,7 @@ class Model
|
|
|
825
825
|
|
|
826
826
|
/**
|
|
827
827
|
* Get all records, optionally filtered.
|
|
828
|
-
* Alias for find()
|
|
828
|
+
* Alias for find() - for LINQ-familiarity.
|
|
829
829
|
*
|
|
830
830
|
* @param {object} [conditions={}] - WHERE conditions.
|
|
831
831
|
* @returns {Promise<Model[]>} All matching records.
|
package/lib/orm/procedures.js
CHANGED
|
@@ -56,7 +56,7 @@ class StoredProcedure
|
|
|
56
56
|
throw new Error('StoredProcedure requires a "body" string');
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// Sanitize name
|
|
59
|
+
// Sanitize name - only allow identifiers
|
|
60
60
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name))
|
|
61
61
|
{
|
|
62
62
|
throw new Error(`Invalid procedure name: "${name}"`);
|
package/lib/orm/profiler.js
CHANGED
|
@@ -117,7 +117,7 @@ class QueryProfiler
|
|
|
117
117
|
|
|
118
118
|
if (!counter || (now - counter.firstTs) >= this._n1Window)
|
|
119
119
|
{
|
|
120
|
-
// Window expired or first query
|
|
120
|
+
// Window expired or first query - start a new window
|
|
121
121
|
counter = { count: 1, firstTs: now };
|
|
122
122
|
this._selectCounters.set(table, counter);
|
|
123
123
|
return;
|
package/lib/orm/query.js
CHANGED
|
@@ -388,7 +388,7 @@ class Query
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
/**
|
|
391
|
-
* Alias for with()
|
|
391
|
+
* Alias for with() - mirrors Entity Framework include syntax.
|
|
392
392
|
* @param {...string|object} relations - Relation names or config objects to eager-load.
|
|
393
393
|
* @returns {Query} This query for chaining.
|
|
394
394
|
*/
|
|
@@ -998,7 +998,7 @@ class Query
|
|
|
998
998
|
}
|
|
999
999
|
|
|
1000
1000
|
/**
|
|
1001
|
-
* Make Query thenable
|
|
1001
|
+
* Make Query thenable - allows `await query`.
|
|
1002
1002
|
* @param {Function} resolve - Fulfillment handler.
|
|
1003
1003
|
* @param {Function} reject - Rejection handler.
|
|
1004
1004
|
* @returns {Promise} Result of exec().
|
|
@@ -1043,7 +1043,7 @@ class Query
|
|
|
1043
1043
|
}
|
|
1044
1044
|
|
|
1045
1045
|
/**
|
|
1046
|
-
* Alias for exec
|
|
1046
|
+
* Alias for exec - explicitly convert to array.
|
|
1047
1047
|
* @returns {Promise<Array>} Matching rows as an array.
|
|
1048
1048
|
*/
|
|
1049
1049
|
toArray()
|
|
@@ -1072,7 +1072,7 @@ class Query
|
|
|
1072
1072
|
}
|
|
1073
1073
|
|
|
1074
1074
|
/**
|
|
1075
|
-
* Alias for first()
|
|
1075
|
+
* Alias for first() - C# FirstOrDefault returns null on empty.
|
|
1076
1076
|
* @returns {Promise<object|null>} Matching row or null.
|
|
1077
1077
|
*/
|
|
1078
1078
|
firstOrDefault()
|
|
@@ -1081,7 +1081,7 @@ class Query
|
|
|
1081
1081
|
}
|
|
1082
1082
|
|
|
1083
1083
|
/**
|
|
1084
|
-
* Alias for avg()
|
|
1084
|
+
* Alias for avg() - C# naming.
|
|
1085
1085
|
* @param {string} field - Column name.
|
|
1086
1086
|
* @returns {Promise<number>} Average of the column values.
|
|
1087
1087
|
*/
|
|
@@ -1091,7 +1091,7 @@ class Query
|
|
|
1091
1091
|
}
|
|
1092
1092
|
|
|
1093
1093
|
/**
|
|
1094
|
-
* Alias for reduce()
|
|
1094
|
+
* Alias for reduce() - C# Aggregate naming.
|
|
1095
1095
|
* @param {Function} fn - Callback function.
|
|
1096
1096
|
* @param {*} seed - Initial accumulator value.
|
|
1097
1097
|
* @returns {Promise<*>} Accumulated value.
|
|
@@ -1126,7 +1126,7 @@ class Query
|
|
|
1126
1126
|
}
|
|
1127
1127
|
|
|
1128
1128
|
/**
|
|
1129
|
-
* Alias for last()
|
|
1129
|
+
* Alias for last() - C# naming.
|
|
1130
1130
|
* @returns {Promise<object|null>} Matching row or null.
|
|
1131
1131
|
*/
|
|
1132
1132
|
lastOrDefault()
|
|
@@ -1346,7 +1346,7 @@ class Query
|
|
|
1346
1346
|
// -- Projection --
|
|
1347
1347
|
|
|
1348
1348
|
/**
|
|
1349
|
-
* FlatMap
|
|
1349
|
+
* FlatMap - project each element to an array and flatten.
|
|
1350
1350
|
* @param {Function} fn - (item, index) => Array
|
|
1351
1351
|
* @returns {Promise<Array>} Flattened projected results.
|
|
1352
1352
|
*/
|
|
@@ -1599,7 +1599,7 @@ class Query
|
|
|
1599
1599
|
}
|
|
1600
1600
|
|
|
1601
1601
|
/**
|
|
1602
|
-
* Inverse of when
|
|
1602
|
+
* Inverse of when - apply query logic when condition is falsy.
|
|
1603
1603
|
*
|
|
1604
1604
|
* @param {*} condition - Condition to evaluate.
|
|
1605
1605
|
* @param {Function} fn - Callback function.
|
package/lib/orm/schema.js
CHANGED
|
@@ -180,7 +180,7 @@ function validateValue(value, colDef, colName)
|
|
|
180
180
|
try { return JSON.parse(value); }
|
|
181
181
|
catch (e) { throw new Error(`"${colName}" must be valid JSON`); }
|
|
182
182
|
}
|
|
183
|
-
// Already an object/array
|
|
183
|
+
// Already an object/array - return as-is for storage
|
|
184
184
|
return value;
|
|
185
185
|
}
|
|
186
186
|
case 'uuid':
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* job titles, prefixes/suffixes, gender, bio phrases, zodiac signs.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
/** Title prefixes
|
|
9
|
+
/** Title prefixes - separate lists per target sex for contextual use. */
|
|
10
10
|
const NAME_PREFIXES = {
|
|
11
11
|
male: ['Mr.', 'Dr.', 'Prof.'],
|
|
12
12
|
female: ['Ms.', 'Mrs.', 'Dr.', 'Prof.', 'Miss'],
|