@ignex/ninox 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,10 +29,16 @@ declare const createConsoleLogger: (level?: LogLevel) => LoggerLike;
29
29
  interface LRUOptions {
30
30
  /** Maximum number of entries before the least-recently-used item is evicted. Must be > 0. */
31
31
  max: number;
32
+ /**
33
+ * Called when an entry is evicted by capacity pressure (NOT on explicit
34
+ * delete/clear). Lets owners release side structures keyed by the entry.
35
+ */
36
+ onEvict?: (key: never, value: never) => void;
32
37
  }
33
38
  declare class LRU<K, V> {
34
39
  readonly max: number;
35
40
  private map;
41
+ private onEvict;
36
42
  /** Number of entries evicted to make room for new ones (capacity evictions only). */
37
43
  evictions: number;
38
44
  constructor(options: LRUOptions);
@@ -58,7 +64,13 @@ declare class LRU<K, V> {
58
64
  * into primitive markers. Result is deterministic for a given logical value.
59
65
  */
60
66
  declare const stableStringify: (value: unknown) => string;
61
- /** djb2 string hash → base-36 (fits comfortably in a double, no BigInt). */
67
+ /**
68
+ * Dual-round 32-bit digest → ~64 bits of collision resistance, base-36.
69
+ * Round 1 is djb2; round 2 mixes FNV-1a over the same input so two distinct
70
+ * strings colliding in one round almost never collide in both. Cache keys
71
+ * store ONLY this digest, so collision resistance matters: a hit must never
72
+ * be able to serve another query's rows.
73
+ */
62
74
  declare const hashString: (input: string) => string;
63
75
  /** Convenience: hash any value through a stable stringification. */
64
76
  declare const stableHash: (value: unknown) => string;
@@ -84,12 +96,20 @@ declare const createCachedAsyncFactory: <K, V>(factory: (key: K) => Promise<V>,
84
96
  * returns the original promise untouched (zero-overhead fast path).
85
97
  */
86
98
  declare const withTimeout: <T>(promise: Promise<T>, timeoutMs: number, label: string) => Promise<T>;
87
- /** Sleep helper used by retry backoff. */
88
- declare const sleep: (ms: number) => Promise<void>;
99
+ /**
100
+ * Sleep helper used by retry backoff.
101
+ *
102
+ * `unref` (default false) detaches the timer from the event loop, so a
103
+ * background retry loop never keeps the process alive on its own (the
104
+ * consumer's server/sockets do that). Watcher backoff passes `unref: true`;
105
+ * awaited query retries keep the default so a pending call is never dropped.
106
+ */
107
+ declare const sleep: (ms: number, unref?: boolean) => Promise<void>;
89
108
  /**
90
109
  * Sleep for `ms` plus random jitter (0..jitterMs, capped at `ms`) so retry
91
110
  * loops don't resynchronize into a thundering herd after a replica failover.
111
+ * Accepts the same `unref` option as {@link sleep}.
92
112
  */
93
- declare const sleepJittered: (ms: number, jitterMs?: number) => Promise<void>;
113
+ declare const sleepJittered: (ms: number, jitterMs?: number, unref?: boolean) => Promise<void>;
94
114
 
95
115
  export { type CacheOptions as C, type LoggerLike as L, LRU as a, type LRUOptions as b, type LogFn as c, type LogLevel as d, createCachedAsyncFactory as e, createCachedFactory as f, createConsoleLogger as g, createNoopLogger as h, hashString as i, sleepJittered as j, stableHash as k, stableStringify as l, sleep as s, withTimeout as w };
package/dist/utils.d.ts CHANGED
@@ -1,13 +1,37 @@
1
- export { C as CacheOptions, a as LRU, b as LRUOptions, c as LogFn, d as LogLevel, L as LoggerLike, 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-BgrqtMnd.js';
1
+ export { C as CacheOptions, a as LRU, b as LRUOptions, c as LogFn, d as LogLevel, L as LoggerLike, 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';
2
2
 
3
3
  /**
4
- * Deep-clone a value while preserving the BSON-ish types that
5
- * `JSON.parse(JSON.stringify())` would corrupt: `Date`, `ObjectId`, and
6
- * `RegExp`. Arrays and plain objects are copied recursively; primitives pass
7
- * through by reference. Used by the optional `clone` mode on `QueryCache` /
8
- * `HotCache` so a caller can't mutate a cached result and poison the shared
9
- * cache.
4
+ * Deep-clone a value while preserving the types `JSON.parse(JSON.stringify())`
5
+ * would corrupt or flatten: `Date`, `ObjectId`, `RegExp`, the numeric/binary
6
+ * BSON wrappers (`Decimal128`, `Long`, `Int32`, `Double`, `Timestamp`,
7
+ * `Binary`, `MinKey`, `MaxKey`, `Code`, `DBRef`), and the JS containers
8
+ * `Map` / `Set` / `Buffer` / typed arrays — without this, cached money fields
9
+ * (`Decimal128`) or int64 values (`Long`) would clone into `{}`, and a
10
+ * `Map` (no enumerable own properties) or `Buffer` would silently lose both
11
+ * type and data in the generic-object fallback. Arrays and plain objects are
12
+ * copied recursively; primitives pass through.
13
+ * Used by the optional `clone` mode on `QueryCache` / `HotCache` so a caller
14
+ * can't mutate a cached result and poison the shared cache.
10
15
  */
11
16
  declare const cloneDeep: <T>(value: T) => T;
12
17
 
13
- export { cloneDeep };
18
+ /**
19
+ * Permanent change-stream error classification, shared by every `$changeStream`
20
+ * watcher (service `CacheInvalidator` + hot-cache `WatchCoordinator`).
21
+ *
22
+ * These errors can NEVER be fixed by retrying — retrying only logs the same
23
+ * warning every 1–5s forever and (with an un-unref'd timer) keeps the process
24
+ * alive, which is wrong for a background watcher whose host server or build
25
+ * should own the process lifetime. Watchers must classify them once, log once,
26
+ * and stop (disable the watcher / fall back to the standalone ticker).
27
+ *
28
+ * Deliberately NARROW on purpose: replica errors that ARE transient — e.g.
29
+ * "change stream history lost" (resume-token expiry, collection dropped),
30
+ * network drops, failovers — must stay in the retry path, where
31
+ * invalidate-on-reopen restores correctness. Broad patterns (like `not
32
+ * supported` or `ChangeStream`) are rejected here because they can misfire on
33
+ * those recoverable errors.
34
+ */
35
+ declare const isPermanentWatchError: (message: string) => boolean;
36
+
37
+ export { cloneDeep, isPermanentWatchError };
package/dist/utils.js CHANGED
@@ -1,10 +1,50 @@
1
- import { ObjectId } from 'mongodb';
1
+ import { ObjectId, Decimal128, Long, Int32, Double, Timestamp, Binary, MinKey, MaxKey, DBRef, Code } from 'mongodb';
2
2
 
3
3
  // src/utils/clone.ts
4
4
  var cloneDeep = (value) => {
5
5
  if (value instanceof Date) return new Date(value.getTime());
6
6
  if (value instanceof ObjectId) return new ObjectId(value.toHexString());
7
+ if (value instanceof Decimal128) return Decimal128.fromString(value.toString());
8
+ if (value instanceof Long) return Long.fromString(value.toString());
9
+ if (value instanceof Int32) return new Int32(value.valueOf());
10
+ if (value instanceof Double) return new Double(value.valueOf());
11
+ if (value instanceof Timestamp)
12
+ return Timestamp.fromBits(value.getLowBits(), value.getHighBits());
13
+ if (value instanceof Binary) {
14
+ const bytes = Buffer.from(value.buffer);
15
+ return new Binary(bytes, value.sub_type);
16
+ }
17
+ if (value instanceof MinKey) return new MinKey();
18
+ if (value instanceof MaxKey) return new MaxKey();
19
+ if (value instanceof DBRef) {
20
+ return new DBRef(value.collection, cloneDeep(value.oid), value.db);
21
+ }
22
+ if (value instanceof Code) {
23
+ return new Code(
24
+ value.code,
25
+ value.scope !== void 0 ? cloneDeep(value.scope) : void 0
26
+ );
27
+ }
7
28
  if (value instanceof RegExp) return new RegExp(value.source, value.flags);
29
+ if (value instanceof Map) {
30
+ const out = /* @__PURE__ */ new Map();
31
+ for (const [k, v] of value) out.set(cloneDeep(k), cloneDeep(v));
32
+ return out;
33
+ }
34
+ if (value instanceof Set) {
35
+ const out = /* @__PURE__ */ new Set();
36
+ for (const v of value) out.add(cloneDeep(v));
37
+ return out;
38
+ }
39
+ if (Buffer.isBuffer(value)) return Buffer.from(value);
40
+ if (ArrayBuffer.isView(value)) {
41
+ const view = value;
42
+ if (view instanceof DataView) {
43
+ const bytes = view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
44
+ return new DataView(bytes);
45
+ }
46
+ return typeof view.slice === "function" ? view.slice() : value;
47
+ }
8
48
  if (Array.isArray(value)) return value.map((item) => cloneDeep(item));
9
49
  if (value !== null && typeof value === "object") {
10
50
  const out = {};
@@ -17,7 +57,10 @@ var cloneDeep = (value) => {
17
57
  };
18
58
 
19
59
  // src/utils/hash.ts
20
- var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
60
+ var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof Date) && !(v instanceof RegExp) && // Mongo driver ObjectId (and other BSON values with a toHexString marker)
61
+ // must fall through to `toPrimitive` — treating them as plain objects
62
+ // would serialize every ObjectId as `{}` and collapse distinct values.
63
+ typeof v.toHexString !== "function";
21
64
  var toPrimitive = (v) => {
22
65
  if (v === void 0 || v === null) return null;
23
66
  if (typeof v === "bigint") return v.toString();
@@ -42,11 +85,15 @@ var stableStringify = (value) => {
42
85
  return JSON.stringify(visit(value));
43
86
  };
44
87
  var hashString = (input) => {
45
- let hash = 5381;
88
+ let h1 = 5381;
89
+ let h2 = 2166136261;
46
90
  for (let i = 0; i < input.length; i++) {
47
- hash = (hash << 5) + hash + input.charCodeAt(i) | 0;
91
+ const c = input.charCodeAt(i);
92
+ h1 = (h1 << 5) + h1 + c | 0;
93
+ h2 = (h2 ^ c) * 16777619 | 0;
94
+ h2 = h2 << 3 | h2 >>> 29;
48
95
  }
49
- return (hash >>> 0).toString(36);
96
+ return `${(h1 >>> 0).toString(36)}${(h2 >>> 0).toString(36)}`;
50
97
  };
51
98
  var stableHash = (value) => hashString(stableStringify(value));
52
99
 
@@ -98,11 +145,13 @@ var createConsoleLogger = (level = "info") => {
98
145
  var LRU = class {
99
146
  max;
100
147
  map = /* @__PURE__ */ new Map();
148
+ onEvict;
101
149
  /** Number of entries evicted to make room for new ones (capacity evictions only). */
102
150
  evictions = 0;
103
151
  constructor(options) {
104
152
  if (options.max <= 0) throw new Error("LRU max must be > 0");
105
153
  this.max = options.max;
154
+ this.onEvict = options.onEvict;
106
155
  }
107
156
  get size() {
108
157
  return this.map.size;
@@ -124,10 +173,16 @@ var LRU = class {
124
173
  if (this.map.has(key)) this.map.delete(key);
125
174
  this.map.set(key, value);
126
175
  if (this.map.size > this.max) {
127
- const oldest = this.map.keys().next().value;
128
- if (oldest !== void 0) {
129
- this.map.delete(oldest);
176
+ const next = this.map.keys().next();
177
+ if (next.done !== true) {
178
+ const evictedKey = next.value;
179
+ const evictedValue = this.map.get(evictedKey);
180
+ this.map.delete(evictedKey);
130
181
  this.evictions++;
182
+ try {
183
+ this.onEvict?.(evictedKey, evictedValue);
184
+ } catch {
185
+ }
131
186
  }
132
187
  }
133
188
  return this;
@@ -215,9 +270,24 @@ var withTimeout = (promise, timeoutMs, label) => {
215
270
  if (timer !== void 0) clearTimeout(timer);
216
271
  });
217
272
  };
218
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
219
- var sleepJittered = (ms, jitterMs = 1e3) => sleep(ms + Math.floor(Math.random() * Math.min(ms, jitterMs)));
273
+ var sleep = (ms, unref = false) => new Promise((resolve) => {
274
+ const timer = setTimeout(resolve, ms);
275
+ if (unref) timer.unref?.();
276
+ });
277
+ var sleepJittered = (ms, jitterMs = 1e3, unref = false) => sleep(ms + Math.floor(Math.random() * Math.min(ms, jitterMs)), unref);
278
+
279
+ // src/utils/watch-errors.ts
280
+ var isPermanentWatchError = (message) => (
281
+ // Standalone server: `$changeStream` is not supported at all.
282
+ /only supported on replica sets/i.test(message) || // Credentials/authorization problems: the user cannot run `aggregate` on
283
+ // this collection/server. Wrong creds, missing roles, or a user created
284
+ // without the `read`/`changeStream` privilege will never start working by
285
+ // retrying — fail once and stop instead of retrying forever.
286
+ /requires authentication|not authorized|unauthorized|authentication failed|Command .* requires authentication/i.test(
287
+ message
288
+ )
289
+ );
220
290
 
221
- export { LRU, cloneDeep, createCachedAsyncFactory, createCachedFactory, createConsoleLogger, createNoopLogger, hashString, sleep, sleepJittered, stableHash, stableStringify, withTimeout };
291
+ export { LRU, cloneDeep, createCachedAsyncFactory, createCachedFactory, createConsoleLogger, createNoopLogger, hashString, isPermanentWatchError, sleep, sleepJittered, stableHash, stableStringify, withTimeout };
222
292
  //# sourceMappingURL=utils.js.map
223
293
  //# sourceMappingURL=utils.js.map
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/clone.ts","../src/utils/hash.ts","../src/utils/logger.ts","../src/utils/lru.ts","../src/utils/memoize.ts","../src/utils/timeout.ts"],"names":[],"mappings":"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","file":"utils.js","sourcesContent":["import { ObjectId } from 'mongodb';\n\n/**\n * Deep-clone a value while preserving the BSON-ish types that\n * `JSON.parse(JSON.stringify())` would corrupt: `Date`, `ObjectId`, and\n * `RegExp`. Arrays and plain objects are copied recursively; primitives pass\n * through by reference. Used by the optional `clone` mode on `QueryCache` /\n * `HotCache` so a caller can't mutate a cached result and poison the shared\n * cache.\n */\nexport const cloneDeep = <T>(value: T): T => {\n if (value instanceof Date) return new Date(value.getTime()) as T;\n if (value instanceof ObjectId) return new ObjectId(value.toHexString()) as T;\n if (value instanceof RegExp) return new RegExp(value.source, value.flags) as T;\n if (Array.isArray(value)) return value.map((item) => cloneDeep(item)) as T;\n if (value !== null && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n out[k] = cloneDeep(v);\n }\n return out as T;\n }\n return value;\n};\n","/**\n * Deterministic hashing utilities used for cache keys / in-flight dedup keys.\n * `stableStringify` produces an order-insensitive serialization so two logically\n * identical filters yield the same hash regardless of key insertion order.\n */\n\ntype Primitive = string | number | boolean | bigint | null | undefined;\n\nconst isPlainObject = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\n\nconst toPrimitive = (v: unknown): Primitive => {\n if (v === undefined || v === null) return null;\n if (typeof v === 'bigint') return v.toString();\n if (v instanceof Date) return `date:${v.getTime()}`;\n if (typeof v === 'object') {\n // Mongo driver ObjectId has toHexString(); RegExp serializes as a string.\n const anyV = v as { toHexString?: () => string; source?: string; flags?: string };\n if (typeof anyV.toHexString === 'function') return `oid:${anyV.toHexString()}`;\n if (anyV instanceof RegExp) return `re:${anyV.source}/${anyV.flags}`;\n }\n return v as Primitive;\n};\n\n/**\n * Stable stringify: recursively sorts object keys, coerces Date/ObjectId/RegExp\n * into primitive markers. Result is deterministic for a given logical value.\n */\nexport const stableStringify = (value: unknown): string => {\n const visit = (v: unknown): Primitive | Record<string, unknown> | unknown[] => {\n if (Array.isArray(v)) return v.map(visit);\n if (isPlainObject(v)) {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(v).sort()) out[key] = visit(v[key]);\n return out;\n }\n return toPrimitive(v);\n };\n return JSON.stringify(visit(value));\n};\n\n/** djb2 string hash → base-36 (fits comfortably in a double, no BigInt). */\nexport const hashString = (input: string): string => {\n let hash = 5381;\n for (let i = 0; i < input.length; i++) {\n hash = ((hash << 5) + hash + input.charCodeAt(i)) | 0;\n }\n return (hash >>> 0).toString(36);\n};\n\n/** Convenience: hash any value through a stable stringification. */\nexport const stableHash = (value: unknown): string => hashString(stableStringify(value));\n","/**\n * Logger abstraction. The ORM never logs directly — it accepts any logger that\n * satisfies `LoggerLike` (pino, a console wrapper, a test stub, ...). This keeps\n * the package free of logging dependencies while remaining observability-agnostic.\n */\nexport interface LogFn {\n (obj: Record<string, unknown>, msg?: string): void;\n (msg: string): void;\n}\n\nexport interface LoggerLike {\n debug: LogFn;\n info: LogFn;\n warn: LogFn;\n error: LogFn;\n}\n\n/** A logger that swallows everything. Useful in tests / perf runs. */\nexport const createNoopLogger = (): LoggerLike => ({\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n});\n\nconst LEVELS = ['debug', 'info', 'warn', 'error'] as const;\nexport type LogLevel = (typeof LEVELS)[number];\n\nconst LEVEL_RANK: Record<LogLevel, number> = { debug: 10, info: 20, warn: 30, error: 40 };\n\nconst fmtObj = (obj: Record<string, unknown>): string => {\n try {\n return JSON.stringify(obj);\n } catch {\n return String(obj);\n }\n};\n\n/**\n * A tiny console logger matching the `LoggerLike` shape. `level` filters output.\n */\nexport const createConsoleLogger = (level: LogLevel = 'info'): LoggerLike => {\n const threshold = LEVEL_RANK[level];\n const make = (method: 'debug' | 'info' | 'warn' | 'error') => {\n const rank = LEVEL_RANK[method];\n const fn: LogFn = (a: unknown, b?: string) => {\n if (rank < threshold) return;\n const obj = typeof a === 'string' ? undefined : (a as Record<string, unknown>);\n const msg = typeof a === 'string' ? a : b;\n const prefix = obj !== undefined ? fmtObj(obj) : '';\n if (msg && prefix) {\n // eslint-disable-next-line no-console\n console[method](`[${method.toUpperCase()}]`, prefix, msg);\n } else {\n // eslint-disable-next-line no-console\n console[method](`[${method.toUpperCase()}]`, (msg ?? prefix) as string);\n }\n };\n return fn;\n };\n return {\n debug: make('debug'),\n info: make('info'),\n warn: make('warn'),\n error: make('error'),\n };\n};\n","/**\n * Minimal dependency-free LRU cache backed by a Map (recency = insertion order).\n * get() re-inserts the entry so the most-recently-used item is always last.\n */\nexport interface LRUOptions {\n /** Maximum number of entries before the least-recently-used item is evicted. Must be > 0. */\n max: number;\n}\n\nexport class LRU<K, V> {\n readonly max: number;\n private map = new Map<K, V>();\n /** Number of entries evicted to make room for new ones (capacity evictions only). */\n evictions = 0;\n\n constructor(options: LRUOptions) {\n if (options.max <= 0) throw new Error('LRU max must be > 0');\n this.max = options.max;\n }\n\n get size(): number {\n return this.map.size;\n }\n\n get(key: K): V | undefined {\n const value = this.map.get(key);\n if (value === undefined) return undefined;\n // Refresh recency.\n this.map.delete(key);\n this.map.set(key, value);\n return value;\n }\n\n peek(key: K): V | undefined {\n return this.map.get(key);\n }\n\n has(key: K): boolean {\n return this.map.has(key);\n }\n\n set(key: K, value: V): this {\n if (this.map.has(key)) this.map.delete(key);\n this.map.set(key, value);\n if (this.map.size > this.max) {\n const oldest = this.map.keys().next().value as K | undefined;\n if (oldest !== undefined) {\n this.map.delete(oldest);\n this.evictions++;\n }\n }\n return this;\n }\n\n delete(key: K): boolean {\n return this.map.delete(key);\n }\n\n clear(): void {\n this.map.clear();\n }\n\n keys(): IterableIterator<K> {\n return this.map.keys();\n }\n\n entries(): IterableIterator<[K, V]> {\n return this.map.entries();\n }\n\n [Symbol.iterator](): IterableIterator<[K, V]> {\n return this.map.entries();\n }\n}\n","/**\n * Generic keyed factory caches (memoization). Unlike the ORM's `QueryCache`\n * (per-collection, TTL, invalidation-aware) and `HotCache` (read-through,\n * event/timer-refreshed), these are dependency-free building blocks for\n * memoizing any expensive computation by key — including in-flight dedup so a\n * keyed factory never runs twice concurrently.\n */\nimport { LRU } from './lru.ts';\n\nexport interface CacheOptions {\n /** Max cached entries. `0` or negative → unbounded Map (default 100). */\n maxSize?: number;\n}\n\ntype BackingStore<K, V> = {\n get(k: K): V | undefined;\n set(k: K, v: V): void;\n has(k: K): boolean;\n delete(k: K): void;\n};\n\nconst makeStore = <K, V>(maxSize: number): BackingStore<K, V> => {\n if (maxSize <= 0) {\n const map = new Map<K, V>();\n return {\n get: (k) => map.get(k),\n set: (k, v) => {\n map.set(k, v);\n },\n has: (k) => map.has(k),\n delete: (k) => {\n map.delete(k);\n },\n };\n }\n return new LRU<K, V>({ max: maxSize });\n};\n\n/**\n * Synchronous keyed factory cache: one value per key, LRU-bounded.\n * Used for cheap singleton factories (connection pools, resolvers, ...).\n */\nexport const createCachedFactory = <K, V>(\n factory: (key: K) => V,\n options: CacheOptions = {},\n): ((key: K) => V) => {\n const maxSize = options.maxSize ?? 100;\n const cache = makeStore<K, V>(maxSize);\n return (key: K): V => {\n const existing = cache.get(key);\n if (existing !== undefined) return existing;\n const value = factory(key);\n cache.set(key, value);\n return value;\n };\n};\n\n/**\n * Async keyed factory cache with **in-flight deduplication**: concurrent calls\n * with the same key share a single Promise so the factory runs only once.\n * On settle (success or failure) the in-flight entry is removed.\n */\nexport const createCachedAsyncFactory = <K, V>(\n factory: (key: K) => Promise<V>,\n options: CacheOptions = {},\n): ((key: K) => Promise<V>) => {\n const maxSize = options.maxSize ?? 100;\n const cache = makeStore<K, Promise<V>>(maxSize);\n const inFlight = new Map<K, Promise<V>>();\n\n return (key: K): Promise<V> => {\n const inflight = inFlight.get(key);\n if (inflight) return inflight;\n\n const cached = cache.get(key);\n if (cached) return cached;\n\n const promise = factory(key);\n inFlight.set(key, promise);\n cache.set(key, promise);\n promise.then(\n () => {\n inFlight.delete(key);\n },\n () => {\n inFlight.delete(key);\n // Never cache a failure — evict (identity-guarded) so the next call\n // retries instead of re-surfacing the same rejected promise.\n if (cache.get(key) === promise) cache.delete(key);\n },\n );\n return promise;\n };\n};\n","/**\n * Race a promise against a timeout. A `timeoutMs` <= 0 disables the timer and\n * returns the original promise untouched (zero-overhead fast path).\n */\nexport const withTimeout = <T>(\n promise: Promise<T>,\n timeoutMs: number,\n label: string,\n): Promise<T> => {\n if (timeoutMs <= 0) return promise;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(new Error(`Operation timed out after ${timeoutMs}ms: ${label}`));\n }, timeoutMs);\n });\n return Promise.race([promise, timeout]).finally(() => {\n if (timer !== undefined) clearTimeout(timer);\n });\n};\n\n/** Sleep helper used by retry backoff. */\nexport const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Sleep for `ms` plus random jitter (0..jitterMs, capped at `ms`) so retry\n * loops don't resynchronize into a thundering herd after a replica failover.\n */\nexport const sleepJittered = (ms: number, jitterMs = 1000): Promise<void> =>\n sleep(ms + Math.floor(Math.random() * Math.min(ms, jitterMs)));\n"]}
1
+ {"version":3,"sources":["../src/utils/clone.ts","../src/utils/hash.ts","../src/utils/logger.ts","../src/utils/lru.ts","../src/utils/memoize.ts","../src/utils/timeout.ts","../src/utils/watch-errors.ts"],"names":[],"mappings":"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","file":"utils.js","sourcesContent":["import {\n Binary,\n Code,\n DBRef,\n Decimal128,\n Double,\n Int32,\n Long,\n MaxKey,\n MinKey,\n ObjectId,\n Timestamp,\n} from 'mongodb';\n\n/**\n * Deep-clone a value while preserving the types `JSON.parse(JSON.stringify())`\n * would corrupt or flatten: `Date`, `ObjectId`, `RegExp`, the numeric/binary\n * BSON wrappers (`Decimal128`, `Long`, `Int32`, `Double`, `Timestamp`,\n * `Binary`, `MinKey`, `MaxKey`, `Code`, `DBRef`), and the JS containers\n * `Map` / `Set` / `Buffer` / typed arrays — without this, cached money fields\n * (`Decimal128`) or int64 values (`Long`) would clone into `{}`, and a\n * `Map` (no enumerable own properties) or `Buffer` would silently lose both\n * type and data in the generic-object fallback. Arrays and plain objects are\n * copied recursively; primitives pass through.\n * Used by the optional `clone` mode on `QueryCache` / `HotCache` so a caller\n * can't mutate a cached result and poison the shared cache.\n */\nexport const cloneDeep = <T>(value: T): T => {\n if (value instanceof Date) return new Date(value.getTime()) as T;\n if (value instanceof ObjectId) return new ObjectId(value.toHexString()) as T;\n if (value instanceof Decimal128) return Decimal128.fromString(value.toString()) as T;\n if (value instanceof Long) return Long.fromString(value.toString()) as T;\n if (value instanceof Int32) return new Int32(value.valueOf()) as T;\n if (value instanceof Double) return new Double(value.valueOf()) as T;\n // fromBits round-trips exactly; fromNumber loses low bits above 2^53.\n if (value instanceof Timestamp)\n return Timestamp.fromBits(value.getLowBits(), value.getHighBits()) as T;\n if (value instanceof Binary) {\n const bytes = Buffer.from(value.buffer);\n return new Binary(bytes, value.sub_type) as T;\n }\n if (value instanceof MinKey) return new MinKey() as T;\n if (value instanceof MaxKey) return new MaxKey() as T;\n if (value instanceof DBRef) {\n return new DBRef(value.collection, cloneDeep(value.oid), value.db) as T;\n }\n if (value instanceof Code) {\n return new Code(\n value.code,\n value.scope !== undefined ? cloneDeep(value.scope) : undefined,\n ) as T;\n }\n if (value instanceof RegExp) return new RegExp(value.source, value.flags) as T;\n if (value instanceof Map) {\n const out = new Map<unknown, unknown>();\n for (const [k, v] of value) out.set(cloneDeep(k), cloneDeep(v));\n return out as T;\n }\n if (value instanceof Set) {\n const out = new Set<unknown>();\n for (const v of value) out.add(cloneDeep(v));\n return out as T;\n }\n // Buffer first (a Uint8Array subclass); then any other typed array view.\n if (Buffer.isBuffer(value)) return Buffer.from(value) as T;\n if (ArrayBuffer.isView(value)) {\n const view = value as unknown as ArrayBufferView & { slice?: () => unknown };\n if (view instanceof DataView) {\n const bytes = view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);\n return new DataView(bytes) as T;\n }\n return typeof view.slice === 'function' ? (view.slice() as T) : value;\n }\n if (Array.isArray(value)) return value.map((item) => cloneDeep(item)) as T;\n if (value !== null && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n out[k] = cloneDeep(v);\n }\n return out as T;\n }\n return value;\n};\n","/**\n * Deterministic hashing utilities used for cache keys / in-flight dedup keys.\n * `stableStringify` produces an order-insensitive serialization so two logically\n * identical filters yield the same hash regardless of key insertion order.\n */\n\ntype Primitive = string | number | boolean | bigint | null | undefined;\n\nconst isPlainObject = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' &&\n v !== null &&\n !Array.isArray(v) &&\n !(v instanceof Date) &&\n !(v instanceof RegExp) &&\n // Mongo driver ObjectId (and other BSON values with a toHexString marker)\n // must fall through to `toPrimitive` — treating them as plain objects\n // would serialize every ObjectId as `{}` and collapse distinct values.\n typeof (v as { toHexString?: unknown }).toHexString !== 'function';\n\nconst toPrimitive = (v: unknown): Primitive => {\n if (v === undefined || v === null) return null;\n if (typeof v === 'bigint') return v.toString();\n if (v instanceof Date) return `date:${v.getTime()}`;\n if (typeof v === 'object') {\n // Mongo driver ObjectId has toHexString(); RegExp serializes as a string.\n const anyV = v as { toHexString?: () => string; source?: string; flags?: string };\n if (typeof anyV.toHexString === 'function') return `oid:${anyV.toHexString()}`;\n if (anyV instanceof RegExp) return `re:${anyV.source}/${anyV.flags}`;\n }\n return v as Primitive;\n};\n\n/**\n * Stable stringify: recursively sorts object keys, coerces Date/ObjectId/RegExp\n * into primitive markers. Result is deterministic for a given logical value.\n */\nexport const stableStringify = (value: unknown): string => {\n const visit = (v: unknown): Primitive | Record<string, unknown> | unknown[] => {\n if (Array.isArray(v)) return v.map(visit);\n if (isPlainObject(v)) {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(v).sort()) out[key] = visit(v[key]);\n return out;\n }\n return toPrimitive(v);\n };\n return JSON.stringify(visit(value));\n};\n\n/**\n * Dual-round 32-bit digest → ~64 bits of collision resistance, base-36.\n * Round 1 is djb2; round 2 mixes FNV-1a over the same input so two distinct\n * strings colliding in one round almost never collide in both. Cache keys\n * store ONLY this digest, so collision resistance matters: a hit must never\n * be able to serve another query's rows.\n */\nexport const hashString = (input: string): string => {\n let h1 = 5381;\n let h2 = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n const c = input.charCodeAt(i);\n h1 = ((h1 << 5) + h1 + c) | 0;\n h2 = ((h2 ^ c) * 0x01000193) | 0;\n // Extra diffusion so low-entropy inputs still spread across rounds.\n h2 = (h2 << 3) | (h2 >>> 29);\n }\n return `${(h1 >>> 0).toString(36)}${(h2 >>> 0).toString(36)}`;\n};\n\n/** Convenience: hash any value through a stable stringification. */\nexport const stableHash = (value: unknown): string => hashString(stableStringify(value));\n","/**\n * Logger abstraction. The ORM never logs directly — it accepts any logger that\n * satisfies `LoggerLike` (pino, a console wrapper, a test stub, ...). This keeps\n * the package free of logging dependencies while remaining observability-agnostic.\n */\nexport interface LogFn {\n (obj: Record<string, unknown>, msg?: string): void;\n (msg: string): void;\n}\n\nexport interface LoggerLike {\n debug: LogFn;\n info: LogFn;\n warn: LogFn;\n error: LogFn;\n}\n\n/** A logger that swallows everything. Useful in tests / perf runs. */\nexport const createNoopLogger = (): LoggerLike => ({\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n});\n\nconst LEVELS = ['debug', 'info', 'warn', 'error'] as const;\nexport type LogLevel = (typeof LEVELS)[number];\n\nconst LEVEL_RANK: Record<LogLevel, number> = { debug: 10, info: 20, warn: 30, error: 40 };\n\nconst fmtObj = (obj: Record<string, unknown>): string => {\n try {\n return JSON.stringify(obj);\n } catch {\n return String(obj);\n }\n};\n\n/**\n * A tiny console logger matching the `LoggerLike` shape. `level` filters output.\n */\nexport const createConsoleLogger = (level: LogLevel = 'info'): LoggerLike => {\n const threshold = LEVEL_RANK[level];\n const make = (method: 'debug' | 'info' | 'warn' | 'error') => {\n const rank = LEVEL_RANK[method];\n const fn: LogFn = (a: unknown, b?: string) => {\n if (rank < threshold) return;\n const obj = typeof a === 'string' ? undefined : (a as Record<string, unknown>);\n const msg = typeof a === 'string' ? a : b;\n const prefix = obj !== undefined ? fmtObj(obj) : '';\n if (msg && prefix) {\n // eslint-disable-next-line no-console\n console[method](`[${method.toUpperCase()}]`, prefix, msg);\n } else {\n // eslint-disable-next-line no-console\n console[method](`[${method.toUpperCase()}]`, (msg ?? prefix) as string);\n }\n };\n return fn;\n };\n return {\n debug: make('debug'),\n info: make('info'),\n warn: make('warn'),\n error: make('error'),\n };\n};\n","/**\n * Minimal dependency-free LRU cache backed by a Map (recency = insertion order).\n * get() re-inserts the entry so the most-recently-used item is always last.\n */\nexport interface LRUOptions {\n /** Maximum number of entries before the least-recently-used item is evicted. Must be > 0. */\n max: number;\n /**\n * Called when an entry is evicted by capacity pressure (NOT on explicit\n * delete/clear). Lets owners release side structures keyed by the entry.\n */\n onEvict?: (key: never, value: never) => void;\n}\n\nexport class LRU<K, V> {\n readonly max: number;\n private map = new Map<K, V>();\n private onEvict: ((key: K, value: V) => void) | undefined;\n /** Number of entries evicted to make room for new ones (capacity evictions only). */\n evictions = 0;\n\n constructor(options: LRUOptions) {\n if (options.max <= 0) throw new Error('LRU max must be > 0');\n this.max = options.max;\n // Contravariance-safe: `(key: never) => void` accepts any handler shape.\n this.onEvict = options.onEvict as ((key: K, value: V) => void) | undefined;\n }\n\n get size(): number {\n return this.map.size;\n }\n\n get(key: K): V | undefined {\n const value = this.map.get(key);\n if (value === undefined) return undefined;\n // Refresh recency.\n this.map.delete(key);\n this.map.set(key, value);\n return value;\n }\n\n peek(key: K): V | undefined {\n return this.map.get(key);\n }\n\n has(key: K): boolean {\n return this.map.has(key);\n }\n\n set(key: K, value: V): this {\n if (this.map.has(key)) this.map.delete(key);\n this.map.set(key, value);\n if (this.map.size > this.max) {\n const next = this.map.keys().next();\n if (next.done !== true) {\n const evictedKey = next.value;\n const evictedValue = this.map.get(evictedKey) as V;\n this.map.delete(evictedKey);\n this.evictions++;\n try {\n // Eviction hooks must never break the cache write path.\n this.onEvict?.(evictedKey, evictedValue);\n } catch {\n /* ignore */\n }\n }\n }\n return this;\n }\n\n delete(key: K): boolean {\n return this.map.delete(key);\n }\n\n clear(): void {\n this.map.clear();\n }\n\n keys(): IterableIterator<K> {\n return this.map.keys();\n }\n\n entries(): IterableIterator<[K, V]> {\n return this.map.entries();\n }\n\n [Symbol.iterator](): IterableIterator<[K, V]> {\n return this.map.entries();\n }\n}\n","/**\n * Generic keyed factory caches (memoization). Unlike the ORM's `QueryCache`\n * (per-collection, TTL, invalidation-aware) and `HotCache` (read-through,\n * event/timer-refreshed), these are dependency-free building blocks for\n * memoizing any expensive computation by key — including in-flight dedup so a\n * keyed factory never runs twice concurrently.\n */\nimport { LRU } from './lru.ts';\n\nexport interface CacheOptions {\n /** Max cached entries. `0` or negative → unbounded Map (default 100). */\n maxSize?: number;\n}\n\ntype BackingStore<K, V> = {\n get(k: K): V | undefined;\n set(k: K, v: V): void;\n has(k: K): boolean;\n delete(k: K): void;\n};\n\nconst makeStore = <K, V>(maxSize: number): BackingStore<K, V> => {\n if (maxSize <= 0) {\n const map = new Map<K, V>();\n return {\n get: (k) => map.get(k),\n set: (k, v) => {\n map.set(k, v);\n },\n has: (k) => map.has(k),\n delete: (k) => {\n map.delete(k);\n },\n };\n }\n return new LRU<K, V>({ max: maxSize });\n};\n\n/**\n * Synchronous keyed factory cache: one value per key, LRU-bounded.\n * Used for cheap singleton factories (connection pools, resolvers, ...).\n */\nexport const createCachedFactory = <K, V>(\n factory: (key: K) => V,\n options: CacheOptions = {},\n): ((key: K) => V) => {\n const maxSize = options.maxSize ?? 100;\n const cache = makeStore<K, V>(maxSize);\n return (key: K): V => {\n const existing = cache.get(key);\n if (existing !== undefined) return existing;\n const value = factory(key);\n cache.set(key, value);\n return value;\n };\n};\n\n/**\n * Async keyed factory cache with **in-flight deduplication**: concurrent calls\n * with the same key share a single Promise so the factory runs only once.\n * On settle (success or failure) the in-flight entry is removed.\n */\nexport const createCachedAsyncFactory = <K, V>(\n factory: (key: K) => Promise<V>,\n options: CacheOptions = {},\n): ((key: K) => Promise<V>) => {\n const maxSize = options.maxSize ?? 100;\n const cache = makeStore<K, Promise<V>>(maxSize);\n const inFlight = new Map<K, Promise<V>>();\n\n return (key: K): Promise<V> => {\n const inflight = inFlight.get(key);\n if (inflight) return inflight;\n\n const cached = cache.get(key);\n if (cached) return cached;\n\n const promise = factory(key);\n inFlight.set(key, promise);\n cache.set(key, promise);\n promise.then(\n () => {\n inFlight.delete(key);\n },\n () => {\n inFlight.delete(key);\n // Never cache a failure — evict (identity-guarded) so the next call\n // retries instead of re-surfacing the same rejected promise.\n if (cache.get(key) === promise) cache.delete(key);\n },\n );\n return promise;\n };\n};\n","/**\n * Race a promise against a timeout. A `timeoutMs` <= 0 disables the timer and\n * returns the original promise untouched (zero-overhead fast path).\n */\nexport const withTimeout = <T>(\n promise: Promise<T>,\n timeoutMs: number,\n label: string,\n): Promise<T> => {\n if (timeoutMs <= 0) return promise;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(new Error(`Operation timed out after ${timeoutMs}ms: ${label}`));\n }, timeoutMs);\n });\n return Promise.race([promise, timeout]).finally(() => {\n if (timer !== undefined) clearTimeout(timer);\n });\n};\n\n/**\n * Sleep helper used by retry backoff.\n *\n * `unref` (default false) detaches the timer from the event loop, so a\n * background retry loop never keeps the process alive on its own (the\n * consumer's server/sockets do that). Watcher backoff passes `unref: true`;\n * awaited query retries keep the default so a pending call is never dropped.\n */\nexport const sleep = (ms: number, unref = false): Promise<void> =>\n new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n if (unref) timer.unref?.();\n });\n\n/**\n * Sleep for `ms` plus random jitter (0..jitterMs, capped at `ms`) so retry\n * loops don't resynchronize into a thundering herd after a replica failover.\n * Accepts the same `unref` option as {@link sleep}.\n */\nexport const sleepJittered = (ms: number, jitterMs = 1000, unref = false): Promise<void> =>\n sleep(ms + Math.floor(Math.random() * Math.min(ms, jitterMs)), unref);\n","/**\n * Permanent change-stream error classification, shared by every `$changeStream`\n * watcher (service `CacheInvalidator` + hot-cache `WatchCoordinator`).\n *\n * These errors can NEVER be fixed by retrying — retrying only logs the same\n * warning every 1–5s forever and (with an un-unref'd timer) keeps the process\n * alive, which is wrong for a background watcher whose host server or build\n * should own the process lifetime. Watchers must classify them once, log once,\n * and stop (disable the watcher / fall back to the standalone ticker).\n *\n * Deliberately NARROW on purpose: replica errors that ARE transient — e.g.\n * \"change stream history lost\" (resume-token expiry, collection dropped),\n * network drops, failovers — must stay in the retry path, where\n * invalidate-on-reopen restores correctness. Broad patterns (like `not\n * supported` or `ChangeStream`) are rejected here because they can misfire on\n * those recoverable errors.\n */\nexport const isPermanentWatchError = (message: string): boolean =>\n // Standalone server: `$changeStream` is not supported at all.\n /only supported on replica sets/i.test(message) ||\n // Credentials/authorization problems: the user cannot run `aggregate` on\n // this collection/server. Wrong creds, missing roles, or a user created\n // without the `read`/`changeStream` privilege will never start working by\n // retrying — fail once and stop instead of retrying forever.\n /requires authentication|not authorized|unauthorized|authentication failed|Command .* requires authentication/i.test(\n message,\n );\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ignex/ninox",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Schema-first MongoDB ORM with DataLoader-batched relations, query caching, in-flight dedup, and $facet pagination",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -37,6 +37,7 @@
37
37
  "test:types": "tsc --noEmit",
38
38
  "bench": "bun run bench/run-bench.ts",
39
39
  "check:api": "bun run scripts/check-api.ts",
40
+ "gen:ai-map": "bun run scripts/gen-ai-map.ts",
40
41
  "typecheck": "tsc --noEmit",
41
42
  "lint": "biome check .",
42
43
  "lint:fix": "biome check --write .",