@ignex/ninox 0.1.5 → 0.1.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 +15 -8
- package/dist/index.d.ts +73 -26
- package/dist/index.js +235 -82
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +3 -14
- package/dist/utils.js +20 -10
- package/dist/utils.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -787,10 +787,15 @@ you need the code → status mapping yourself.
|
|
|
787
787
|
> transient errors.
|
|
788
788
|
|
|
789
789
|
Schema validation is enforced **by MongoDB** — the DB `$jsonSchema` validator
|
|
790
|
-
derived from your schema is installed with `createSchema
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
790
|
+
derived from your schema is installed with `createSchema`, and the DB rejects
|
|
791
|
+
violating writes with `VALIDATION_FAILED`. `createSchema` is **idempotent and
|
|
792
|
+
self-reconciling**: it provisions a NEW collection, and when the collection
|
|
793
|
+
already exists it upgrades the DB schema to match the current ORM schema
|
|
794
|
+
(hot-swapping the validator via `collMod` only when it has drifted, and
|
|
795
|
+
creating any newly-declared indexes) — so calling it on every boot keeps
|
|
796
|
+
Mongo's schema in lockstep with your models as they change. `updateSchema`
|
|
797
|
+
hot-swaps a validator explicitly (via `collMod`). ninox deliberately does
|
|
798
|
+
**not** validate *incoming user input* at runtime (no client-side/zod layer).
|
|
794
799
|
|
|
795
800
|
- **Strict by default** — `additionalProperties: false` is emitted unless a
|
|
796
801
|
field opts out (`s.object({ ... }, { additionalProperties: true })`), so
|
|
@@ -814,7 +819,8 @@ client-side/zod layer).
|
|
|
814
819
|
`DomainError` carries the offending field paths (`extra.fields`), the failing
|
|
815
820
|
document id (`extra.documentId`), and the raw `extra.details`, so a rejected
|
|
816
821
|
write names exactly what (and which document) violated the schema.
|
|
817
|
-
- **Index lifecycle** — `createSchema` installs the declared indexes
|
|
822
|
+
- **Index lifecycle** — `createSchema` installs the declared indexes (and, on
|
|
823
|
+
an existing collection, adds any newly-declared ones without dropping);
|
|
818
824
|
`syncIndexes(collection)` reconciles drift (`await db.syncIndexes('products')`
|
|
819
825
|
creates missing declared keys and drops undeclared ones, `_id_` kept).
|
|
820
826
|
|
|
@@ -1015,9 +1021,10 @@ unknown keys in object literals AND non-literal patches. The only way an unknown
|
|
|
1015
1021
|
key passes is a variable explicitly typed with an index signature
|
|
1016
1022
|
(`Record<string, any>`) — prefer inline literals for full checking.
|
|
1017
1023
|
|
|
1018
|
-
**Index drift.** `createSchema` installs declared indexes
|
|
1019
|
-
|
|
1020
|
-
|
|
1024
|
+
**Index drift.** `createSchema` installs declared indexes on creation and adds
|
|
1025
|
+
newly-declared ones on re-provision; if indexes drift out of band (manual
|
|
1026
|
+
drops, other tooling), `syncIndexes('collection')` reconciles — creates
|
|
1027
|
+
missing declared indexes and drops undeclared ones (`_id_` kept).
|
|
1021
1028
|
|
|
1022
1029
|
**Observability.** `QueryCache.stats()` (hits/misses/evictions/…) and
|
|
1023
1030
|
`hot.stats()` (per-query hits/misses/refreshes/loadErrors/evictions/…) give you
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import * as mongodb from 'mongodb';
|
|
|
2
2
|
import { Db, MongoClient, ClientSession, Document, Filter, Hint, CollationOptions, ReadPreferenceLike, ReadConcern, WriteConcern, WithId, Condition, RootFilterOperators, Collection, FindOptions, Sort, FindCursor, ObjectId, Decimal128, ReadConcernLike, AggregateOptions, AggregationCursor, CreateCollectionOptions, TransactionOptions, InsertOneOptions, InsertOneResult, InsertManyResult, UpdateOptions, UpdateResult, FindOneAndUpdateOptions, DeleteResult, ChangeStreamOptions, ChangeStream } from 'mongodb';
|
|
3
3
|
import { L as LoggerLike } from './timeout-B10CTiGm.js';
|
|
4
4
|
export { C as CacheOptions, a as LRU, b as LRUOptions, c as LogFn, d as LogLevel, e as createCachedAsyncFactory, f as createCachedFactory, g as createConsoleLogger, h as createNoopLogger, i as hashString, s as sleep, j as sleepJittered, k as stableHash, l as stableStringify, w as withTimeout } from './timeout-B10CTiGm.js';
|
|
5
|
-
import * as bson from 'bson';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Types + constants for the HotCache layer. All type-only (plus the stream-key
|
|
@@ -411,14 +410,6 @@ declare const createMongoCapabilitiesStore: () => MongoCapabilitiesStore;
|
|
|
411
410
|
/** Probe a connected client/db via `hello`. */
|
|
412
411
|
declare const probeMongoCapabilities: (client: MongoClient | Db | null | undefined) => Promise<MongoCapabilities>;
|
|
413
412
|
|
|
414
|
-
/**
|
|
415
|
-
* The ORM's error classes (`AppError` → `DomainError` / `InfraError` /
|
|
416
|
-
* `BadRequest`), their type guards, and the `ERROR_HTTP_STATUS` table that sets
|
|
417
|
-
* each error's `statusCode` at construction.
|
|
418
|
-
*
|
|
419
|
-
* A leaf module: nothing else in `src/errors/` imports this (so `driver-map.ts`
|
|
420
|
-
* and `http-status.ts` can depend on it without cycles).
|
|
421
|
-
*/
|
|
422
413
|
interface ErrorContext {
|
|
423
414
|
db?: string;
|
|
424
415
|
collection?: string;
|
|
@@ -437,23 +428,47 @@ declare class AppError extends Error {
|
|
|
437
428
|
readonly code: string;
|
|
438
429
|
readonly statusCode: number;
|
|
439
430
|
readonly extra?: Record<string, unknown>;
|
|
440
|
-
constructor(code: string, message: string, statusCode?: number, extra?: Record<string, unknown>);
|
|
441
431
|
/**
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
432
|
+
* Whether `message`/`extra` may be sent to a CLIENT.
|
|
433
|
+
*
|
|
434
|
+
* Fail-closed: a 4xx message is the caller's contract, a 5xx message is an
|
|
435
|
+
* operator detail that may quote a driver, a host or a query. Pass `expose`
|
|
436
|
+
* explicitly to override — only for a 5xx whose text was written for callers
|
|
437
|
+
* (e.g. `new InfraError("DRAINING", "queue is draining", extra, true)`).
|
|
438
|
+
*/
|
|
439
|
+
readonly expose: boolean;
|
|
440
|
+
constructor(code: string, message: string, statusCode?: number, extra?: Record<string, unknown>, expose?: boolean);
|
|
441
|
+
/**
|
|
442
|
+
* Full, stack-free shape for LOGS and typed consumers:
|
|
443
|
+
* `{ name, code, message, statusCode, ...(extra) }`.
|
|
444
|
+
*
|
|
445
|
+
* It carries the real message, so it is NOT the client path — use
|
|
446
|
+
* {@link toClientPayload} (or `serializeError`) for anything a caller sees, and
|
|
447
|
+
* prefer `logger.error({ err })` so pino's own serializer keeps the stack in the
|
|
448
|
+
* log rather than in a payload.
|
|
446
449
|
*/
|
|
447
450
|
toJSON(): Record<string, unknown>;
|
|
451
|
+
/**
|
|
452
|
+
* The client-safe payload — the same shape as {@link toJSON}, except that a
|
|
453
|
+
* non-exposed error's `message` is replaced by the canonical reason phrase and
|
|
454
|
+
* its `extra` is dropped. A framework can forward this (or `serializeError`'s
|
|
455
|
+
* result) without auditing what the message might contain.
|
|
456
|
+
*
|
|
457
|
+
* @param options - `expose: true` forces the real (masked) message + extra.
|
|
458
|
+
* @returns The client payload.
|
|
459
|
+
*/
|
|
460
|
+
toClientPayload(options?: {
|
|
461
|
+
expose?: boolean;
|
|
462
|
+
}): Record<string, unknown>;
|
|
448
463
|
}
|
|
449
464
|
declare class DomainError extends AppError {
|
|
450
|
-
constructor(code: string, message: string, extra?: Record<string, unknown
|
|
465
|
+
constructor(code: string, message: string, extra?: Record<string, unknown>, expose?: boolean);
|
|
451
466
|
}
|
|
452
467
|
declare class InfraError extends AppError {
|
|
453
|
-
constructor(code: string, message: string, extra?: Record<string, unknown
|
|
468
|
+
constructor(code: string, message: string, extra?: Record<string, unknown>, expose?: boolean);
|
|
454
469
|
}
|
|
455
470
|
declare class BadRequest extends DomainError {
|
|
456
|
-
constructor(message: string, extra?: Record<string, unknown
|
|
471
|
+
constructor(message: string, extra?: Record<string, unknown>, expose?: boolean);
|
|
457
472
|
}
|
|
458
473
|
declare const isAppError: (err: unknown) => err is AppError;
|
|
459
474
|
declare const isDomainError: (err: unknown) => err is DomainError;
|
|
@@ -483,8 +498,16 @@ declare const isMappedMongoError: (error: unknown) => error is AppError;
|
|
|
483
498
|
|
|
484
499
|
/** Effective HTTP status for any thrown value: AppError-aware, raw → 500 (503 transient). */
|
|
485
500
|
declare const httpStatusForError: (err: unknown) => number;
|
|
501
|
+
/** Options for {@link serializeError}. */
|
|
502
|
+
interface SerializeErrorOptions {
|
|
503
|
+
/**
|
|
504
|
+
* Include the real message (and `extra`) even for a non-exposed error — the
|
|
505
|
+
* development / trusted-caller path. It is still credential-masked.
|
|
506
|
+
*/
|
|
507
|
+
expose?: boolean;
|
|
508
|
+
}
|
|
486
509
|
/**
|
|
487
|
-
* Reduce any thrown value to a stable,
|
|
510
|
+
* Reduce any thrown value to a stable, CLIENT-SAFE error payload:
|
|
488
511
|
* `{ name, code, message, statusCode, ...(extra) }`.
|
|
489
512
|
*
|
|
490
513
|
* Raw driver errors are mapped through `mapMongoDriverError` first, so a
|
|
@@ -495,8 +518,15 @@ declare const httpStatusForError: (err: unknown) => number;
|
|
|
495
518
|
* const payload = serializeError(err, { db, collection, op });
|
|
496
519
|
* res.status(payload.statusCode).json(payload);
|
|
497
520
|
* });
|
|
521
|
+
*
|
|
522
|
+
* Fail-closed by default: a 4xx message/extra is the caller's contract, while a
|
|
523
|
+
* 5xx message is replaced by the canonical reason phrase (`Internal Server
|
|
524
|
+
* Error`, `Service Unavailable`, …) and `extra` is dropped — a driver message
|
|
525
|
+
* that quotes a host, a query or a stored value never reaches a client. The
|
|
526
|
+
* detail stays on the error itself for logs; pass `{ expose: true }` (or a
|
|
527
|
+
* per-error `expose`) to opt out for a trusted caller.
|
|
498
528
|
*/
|
|
499
|
-
declare const serializeError: (err: unknown, ctx?: ErrorContext) => Record<string, unknown>;
|
|
529
|
+
declare const serializeError: (err: unknown, ctx?: ErrorContext, options?: SerializeErrorOptions) => Record<string, unknown>;
|
|
500
530
|
|
|
501
531
|
/**
|
|
502
532
|
* Transient-error detection: which MongoDB errors are safe to retry. This is a
|
|
@@ -1467,6 +1497,13 @@ interface DbOpMeta {
|
|
|
1467
1497
|
physicalCollection?: string;
|
|
1468
1498
|
db: string;
|
|
1469
1499
|
op: string;
|
|
1500
|
+
/**
|
|
1501
|
+
* What was sent to the database (filter / document / update / pipeline /
|
|
1502
|
+
* options). Surfaced in the ignex debugbar as the span's `params`, so the
|
|
1503
|
+
* dashboard shows exactly what the query carried — not just its name.
|
|
1504
|
+
* Optional: ops without a payload worth showing omit it.
|
|
1505
|
+
*/
|
|
1506
|
+
params?: unknown;
|
|
1470
1507
|
}
|
|
1471
1508
|
|
|
1472
1509
|
/**
|
|
@@ -1549,7 +1586,7 @@ interface TextSearchPagination {
|
|
|
1549
1586
|
|
|
1550
1587
|
interface CrudOpDeps<TCollection> {
|
|
1551
1588
|
trace: <T>(meta: DbOpMeta, fn: () => T | Promise<T>) => Promise<T>;
|
|
1552
|
-
meta: (collection: TCollection, op: string) => DbOpMeta;
|
|
1589
|
+
meta: (collection: TCollection, op: string, params?: unknown) => DbOpMeta;
|
|
1553
1590
|
}
|
|
1554
1591
|
|
|
1555
1592
|
/**
|
|
@@ -2031,7 +2068,7 @@ interface CrudOpsOptions {
|
|
|
2031
2068
|
* and fire lifecycle hooks.
|
|
2032
2069
|
*/
|
|
2033
2070
|
declare const makeCrudOps: <TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>>(client: Db, dbLabel: string, logger: LoggerLike, opts: CrudOpsOptions) => {
|
|
2034
|
-
watchCollection: <X extends ColNames<TClients, TDb>>(collection: X, pipeline?:
|
|
2071
|
+
watchCollection: <X extends ColNames<TClients, TDb>>(collection: X, pipeline?: mongodb.Document[], options?: mongodb.ChangeStreamOptions) => mongodb.ChangeStream<CollectionDoc<TClients[TDb]["collections"][X]>, mongodb.ChangeStreamDocument<CollectionDoc<TClients[TDb]["collections"][X]>>>;
|
|
2035
2072
|
query: <X extends ColNames<TClients, TDb>>(collection: X) => QueryBuilder<CollectionDoc<TClients[TDb]["collections"][X]>, CollectionDoc<TClients[TDb]["collections"][X]>>;
|
|
2036
2073
|
insertOne: <X extends ColNames<TClients, TDb>>(collection: X, doc: InsertInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.InsertOneOptions & QueryOptions) => Promise<mongodb.InsertOneResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
|
|
2037
2074
|
insertMany: <X extends ColNames<TClients, TDb>>(collection: X, docs: readonly InsertInput<CollectionDoc<TClients[TDb]["collections"][X]>>[], options?: mongodb.BulkWriteOptions & QueryOptions) => Promise<mongodb.InsertManyResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
|
|
@@ -2047,8 +2084,8 @@ declare const makeCrudOps: <TClients extends DbClientsDefinition, TDb extends Ex
|
|
|
2047
2084
|
filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>;
|
|
2048
2085
|
update: UpdateInput<CollectionDoc<TClients[TDb]["collections"][X]>>;
|
|
2049
2086
|
upsert?: boolean;
|
|
2050
|
-
}[], options?: mongodb.BulkWriteOptions & QueryOptions) => Promise<
|
|
2051
|
-
bulkWrite: <X extends ColNames<TClients, TDb>>(collection: X, operations: mongodb.AnyBulkWriteOperation<CollectionDoc<TClients[TDb]["collections"][X]>>[], options?: mongodb.BulkWriteOptions & QueryOptions) => Promise<
|
|
2087
|
+
}[], options?: mongodb.BulkWriteOptions & QueryOptions) => Promise<mongodb.Document>;
|
|
2088
|
+
bulkWrite: <X extends ColNames<TClients, TDb>>(collection: X, operations: mongodb.AnyBulkWriteOperation<CollectionDoc<TClients[TDb]["collections"][X]>>[], options?: mongodb.BulkWriteOptions & QueryOptions) => Promise<mongodb.Document>;
|
|
2052
2089
|
updateWithVersion: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, update: UpdateInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.FindOneAndUpdateOptions & QueryOptions) => Promise<VersionedUpdateResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
|
|
2053
2090
|
findOneAndDelete: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.FindOneAndDeleteOptions & QueryOptions) => Promise<CollectionDoc<TClients[TDb]["collections"][X]> | null>;
|
|
2054
2091
|
replaceOne: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, replacement: InsertInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.ReplaceOptions & QueryOptions) => Promise<mongodb.UpdateResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
|
|
@@ -2420,9 +2457,19 @@ interface SchemaOpsOptions {
|
|
|
2420
2457
|
getDefinition: (logical: string) => CollectionRegistryEntry | undefined;
|
|
2421
2458
|
}
|
|
2422
2459
|
/**
|
|
2423
|
-
* Schema-first collection management.
|
|
2424
|
-
*
|
|
2425
|
-
*
|
|
2460
|
+
* Schema-first collection management.
|
|
2461
|
+
*
|
|
2462
|
+
* `createSchema` is IDEMPOTENT and SELF-RECONCILING: on a NEW collection it
|
|
2463
|
+
* provisions the `$jsonSchema` validator derived from the ORM schema plus the
|
|
2464
|
+
* declared indexes; when the collection ALREADY exists it upgrades the DB
|
|
2465
|
+
* schema to match the current ORM schema — hot-swapping the validator via
|
|
2466
|
+
* `collMod` only when it has actually drifted, and creating any newly-declared
|
|
2467
|
+
* indexes. It never drops indexes or data (destructive reconciliation stays
|
|
2468
|
+
* explicit via `syncIndexes`), so it is safe to call on every boot: the Mongo
|
|
2469
|
+
* schema follows the app schema automatically as models change.
|
|
2470
|
+
*
|
|
2471
|
+
* `updateSchema` hot-swaps a validator explicitly (via `collMod`);
|
|
2472
|
+
* `syncIndexes` reconciles index drift (create missing, drop undeclared).
|
|
2426
2473
|
*/
|
|
2427
2474
|
declare const makeSchemaOps: (client: Db, _logger: LoggerLike, opts: SchemaOpsOptions) => {
|
|
2428
2475
|
createSchema: (collection: string, createCollectionOptions?: CreateCollectionOptions) => Promise<void>;
|