redis-graph-cache 1.0.0
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/LICENSE +21 -0
- package/README.md +857 -0
- package/dist/core/compression.d.ts +58 -0
- package/dist/core/compression.js +115 -0
- package/dist/core/hydration-engine.d.ts +68 -0
- package/dist/core/hydration-engine.js +288 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +29 -0
- package/dist/core/lua-scripts.d.ts +148 -0
- package/dist/core/lua-scripts.js +259 -0
- package/dist/core/normalization-engine.d.ts +76 -0
- package/dist/core/normalization-engine.js +286 -0
- package/dist/core/redis-connection-manager.d.ts +255 -0
- package/dist/core/redis-connection-manager.js +745 -0
- package/dist/core/schema-manager.d.ts +133 -0
- package/dist/core/schema-manager.js +432 -0
- package/dist/core/serializer.d.ts +69 -0
- package/dist/core/serializer.js +187 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +32 -0
- package/dist/redis-graph-cache.d.ts +302 -0
- package/dist/redis-graph-cache.js +839 -0
- package/dist/types/config.types.d.ts +138 -0
- package/dist/types/config.types.js +5 -0
- package/dist/types/error.types.d.ts +54 -0
- package/dist/types/error.types.js +89 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.js +29 -0
- package/dist/types/internal.types.d.ts +67 -0
- package/dist/types/internal.types.js +73 -0
- package/dist/types/operation.types.d.ts +101 -0
- package/dist/types/operation.types.js +5 -0
- package/dist/types/schema.types.d.ts +104 -0
- package/dist/types/schema.types.js +5 -0
- package/package.json +44 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Serializer - Pluggable (de)serialization for entity payloads.
|
|
4
|
+
*
|
|
5
|
+
* The default serializer (`TAGGED_SERIALIZER`) is a lossless wrapper
|
|
6
|
+
* around `JSON.stringify` / `JSON.parse` that preserves JS types JSON
|
|
7
|
+
* cannot represent natively. It uses a tag-based envelope only for
|
|
8
|
+
* non-JSON-native values; everything else round-trips identically to
|
|
9
|
+
* plain `JSON.stringify` so the on-wire format is unchanged for
|
|
10
|
+
* ordinary data (strings, numbers, booleans, plain objects, arrays,
|
|
11
|
+
* nulls).
|
|
12
|
+
*
|
|
13
|
+
* Why the engine needs this:
|
|
14
|
+
* - `Date` becomes an ISO string via `Date.prototype.toJSON` and
|
|
15
|
+
* parses back as a string, breaking `post.createdAt.getTime()`.
|
|
16
|
+
* - `BigInt` throws `TypeError` from `JSON.stringify`.
|
|
17
|
+
* - `Map`, `Set`, `RegExp`, `Buffer` serialize to `{}`, losing all
|
|
18
|
+
* contents.
|
|
19
|
+
* - `NaN`, `Infinity`, `-Infinity` become `null`, silently corrupting
|
|
20
|
+
* numeric stats fields.
|
|
21
|
+
*
|
|
22
|
+
* Out of scope (intentionally NOT tagged):
|
|
23
|
+
* - `undefined`. The engine's merge semantics treat
|
|
24
|
+
* "field is undefined" identically to "field is omitted" so that
|
|
25
|
+
* partial updates don't accidentally clobber existing values.
|
|
26
|
+
* See `NULL_UNDEFINED_HANDLING.md`. Tagging undefined would
|
|
27
|
+
* silently change that contract.
|
|
28
|
+
*
|
|
29
|
+
* Backward compatibility:
|
|
30
|
+
* - Reads of plain JSON written by older versions (or by any process
|
|
31
|
+
* without this engine) work unchanged. The reviver only transforms
|
|
32
|
+
* objects that carry the internal tag key.
|
|
33
|
+
* - Writes that contain only JSON-native values produce byte-for-byte
|
|
34
|
+
* identical output to the previous `JSON.stringify` path.
|
|
35
|
+
*
|
|
36
|
+
* Pluggability:
|
|
37
|
+
* - Consumers can opt out by passing `cache.serializer = JSON_SERIALIZER`
|
|
38
|
+
* (raw, no tagging — original behaviour).
|
|
39
|
+
* - Consumers can plug in a third-party codec like `superjson` or
|
|
40
|
+
* `devalue` by passing any object that implements `Serializer`.
|
|
41
|
+
*/
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.JSON_SERIALIZER = exports.TAGGED_SERIALIZER = void 0;
|
|
44
|
+
/**
|
|
45
|
+
* Internal tag key used by the default tagged serializer. It is
|
|
46
|
+
* deliberately under the engine's reserved `__rse_` prefix so it
|
|
47
|
+
* cannot collide with user fields (schema validation rejects field
|
|
48
|
+
* names starting with `__rse_`).
|
|
49
|
+
*/
|
|
50
|
+
const TAG = '__rse_t';
|
|
51
|
+
/**
|
|
52
|
+
* Replacer used by `TAGGED_SERIALIZER.stringify`. Receives the
|
|
53
|
+
* pre-`toJSON` value via `this[key]` so we can detect `Date`,
|
|
54
|
+
* `Buffer`, etc. before their built-in `toJSON` clobbers them.
|
|
55
|
+
*
|
|
56
|
+
* Any value not matched here is returned unchanged, so plain JSON
|
|
57
|
+
* data round-trips with zero envelope overhead.
|
|
58
|
+
*/
|
|
59
|
+
function tagReplacer(key, value) {
|
|
60
|
+
// `this` is the holder object; `value` is the post-`toJSON` value.
|
|
61
|
+
// We need the raw value to detect Date/Buffer/etc. since their
|
|
62
|
+
// `toJSON` runs before the replacer.
|
|
63
|
+
const original = this[key];
|
|
64
|
+
if (original instanceof Date) {
|
|
65
|
+
// `getTime()` preserves invalid dates (NaN) which `toISOString`
|
|
66
|
+
// would throw on. Storing the numeric timestamp is also slightly
|
|
67
|
+
// smaller and unambiguous across timezones.
|
|
68
|
+
const t = original.getTime();
|
|
69
|
+
return { [TAG]: 'Date', v: Number.isNaN(t) ? null : t };
|
|
70
|
+
}
|
|
71
|
+
if (typeof original === 'bigint') {
|
|
72
|
+
return { [TAG]: 'BigInt', v: original.toString() };
|
|
73
|
+
}
|
|
74
|
+
if (original instanceof Map) {
|
|
75
|
+
// Entries are arrays of [key, value]; both will recurse through
|
|
76
|
+
// the replacer so nested Dates/BigInts inside a Map work too.
|
|
77
|
+
return { [TAG]: 'Map', v: Array.from(original.entries()) };
|
|
78
|
+
}
|
|
79
|
+
if (original instanceof Set) {
|
|
80
|
+
return { [TAG]: 'Set', v: Array.from(original.values()) };
|
|
81
|
+
}
|
|
82
|
+
if (original instanceof RegExp) {
|
|
83
|
+
return { [TAG]: 'RegExp', src: original.source, flags: original.flags };
|
|
84
|
+
}
|
|
85
|
+
// Buffer check is guarded so the serializer also works in non-Node
|
|
86
|
+
// environments (Edge, browsers) where `Buffer` is undefined.
|
|
87
|
+
if (typeof Buffer !== 'undefined' &&
|
|
88
|
+
typeof Buffer.isBuffer === 'function' &&
|
|
89
|
+
Buffer.isBuffer(original)) {
|
|
90
|
+
return { [TAG]: 'Buffer', v: original.toString('base64') };
|
|
91
|
+
}
|
|
92
|
+
if (typeof original === 'number' && !Number.isFinite(original)) {
|
|
93
|
+
if (Number.isNaN(original))
|
|
94
|
+
return { [TAG]: 'Number', v: 'NaN' };
|
|
95
|
+
return {
|
|
96
|
+
[TAG]: 'Number',
|
|
97
|
+
v: original > 0 ? 'Infinity' : '-Infinity',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Reviver used by `TAGGED_SERIALIZER.parse`. JSON.parse calls the
|
|
104
|
+
* reviver bottom-up (children first), so by the time we see a tagged
|
|
105
|
+
* envelope its inner `v` has already been revived. That means
|
|
106
|
+
* `Map<Date, BigInt>` round-trips correctly: the inner Dates and
|
|
107
|
+
* BigInts are reconstructed before the Map wraps them.
|
|
108
|
+
*
|
|
109
|
+
* Untagged objects (plain JSON written by any other producer) are
|
|
110
|
+
* returned unchanged, which is what makes this safe to deploy on top
|
|
111
|
+
* of an existing cache.
|
|
112
|
+
*/
|
|
113
|
+
function tagReviver(_key, value) {
|
|
114
|
+
if (value === null ||
|
|
115
|
+
typeof value !== 'object' ||
|
|
116
|
+
Array.isArray(value) ||
|
|
117
|
+
!(TAG in value)) {
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
switch (value[TAG]) {
|
|
121
|
+
case 'Date':
|
|
122
|
+
// `null` in `v` means we serialized an Invalid Date; reconstruct
|
|
123
|
+
// it so identity round-trips even for that weird case.
|
|
124
|
+
return value.v === null ? new Date(NaN) : new Date(value.v);
|
|
125
|
+
case 'BigInt':
|
|
126
|
+
return BigInt(value.v);
|
|
127
|
+
case 'Map':
|
|
128
|
+
return new Map(value.v);
|
|
129
|
+
case 'Set':
|
|
130
|
+
return new Set(value.v);
|
|
131
|
+
case 'RegExp':
|
|
132
|
+
return new RegExp(value.src, value.flags);
|
|
133
|
+
case 'Buffer':
|
|
134
|
+
// Guarded for non-Node environments. If Buffer is unavailable
|
|
135
|
+
// we return the raw envelope so the consumer at least sees the
|
|
136
|
+
// base64 payload rather than getting `undefined`.
|
|
137
|
+
if (typeof Buffer !== 'undefined') {
|
|
138
|
+
return Buffer.from(value.v, 'base64');
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
case 'Number':
|
|
142
|
+
if (value.v === 'NaN')
|
|
143
|
+
return NaN;
|
|
144
|
+
if (value.v === 'Infinity')
|
|
145
|
+
return Infinity;
|
|
146
|
+
if (value.v === '-Infinity')
|
|
147
|
+
return -Infinity;
|
|
148
|
+
return Number(value.v);
|
|
149
|
+
default:
|
|
150
|
+
// Unknown tag — return the envelope as-is. This is forward
|
|
151
|
+
// compatible: a future engine version may add new tags and
|
|
152
|
+
// older readers will simply pass them through instead of
|
|
153
|
+
// crashing.
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Default serializer. Lossless for `Date`, `BigInt`, `Map`, `Set`,
|
|
159
|
+
* `RegExp`, `Buffer`, `NaN`, `+Infinity`, `-Infinity`. Identical to
|
|
160
|
+
* plain JSON for all other values.
|
|
161
|
+
*/
|
|
162
|
+
exports.TAGGED_SERIALIZER = {
|
|
163
|
+
stringify(value) {
|
|
164
|
+
return JSON.stringify(value, tagReplacer);
|
|
165
|
+
},
|
|
166
|
+
parse(value) {
|
|
167
|
+
return JSON.parse(value, tagReviver);
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Plain JSON serializer. No tagging, no reviver. Use this when you
|
|
172
|
+
* know your data is JSON-native and want zero serialization overhead,
|
|
173
|
+
* or when interoperating with non-engine readers that expect raw JSON.
|
|
174
|
+
*
|
|
175
|
+
* Trade-off: `Date` survives as ISO string only, `BigInt` will throw,
|
|
176
|
+
* `Map`/`Set`/`RegExp`/`Buffer` lose all data, `NaN`/`Infinity` become
|
|
177
|
+
* `null`. This is the historical engine behaviour; kept available for
|
|
178
|
+
* opt-out.
|
|
179
|
+
*/
|
|
180
|
+
exports.JSON_SERIALIZER = {
|
|
181
|
+
stringify(value) {
|
|
182
|
+
return JSON.stringify(value);
|
|
183
|
+
},
|
|
184
|
+
parse(value) {
|
|
185
|
+
return JSON.parse(value);
|
|
186
|
+
},
|
|
187
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* redis-graph-cache
|
|
3
|
+
*
|
|
4
|
+
* A TypeScript-first Redis data layer with schema-driven normalization,
|
|
5
|
+
* relationship management, and graph-based hydration for high-performance
|
|
6
|
+
* Node.js applications handling millions of entities with complex
|
|
7
|
+
* relationships.
|
|
8
|
+
*/
|
|
9
|
+
export { RedisGraphCache } from './redis-graph-cache';
|
|
10
|
+
export * from './core';
|
|
11
|
+
export * from './types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* redis-graph-cache
|
|
4
|
+
*
|
|
5
|
+
* A TypeScript-first Redis data layer with schema-driven normalization,
|
|
6
|
+
* relationship management, and graph-based hydration for high-performance
|
|
7
|
+
* Node.js applications handling millions of entities with complex
|
|
8
|
+
* relationships.
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
22
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.RedisGraphCache = void 0;
|
|
26
|
+
// Main engine class (the primary export — what 95% of consumers need)
|
|
27
|
+
var redis_graph_cache_1 = require("./redis-graph-cache");
|
|
28
|
+
Object.defineProperty(exports, "RedisGraphCache", { enumerable: true, get: function () { return redis_graph_cache_1.RedisGraphCache; } });
|
|
29
|
+
// Core components, utilities, and serializers
|
|
30
|
+
__exportStar(require("./core"), exports);
|
|
31
|
+
// All public types (schemas, configs, operations, errors, utilities)
|
|
32
|
+
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis Schema Engine - Main class providing the public API
|
|
3
|
+
*/
|
|
4
|
+
import { Schema, PartialRedisSchemaEngineConfig, WriteResult, ListWriteResult, CacheMetrics, HealthStatus, HydrationOptions } from './types';
|
|
5
|
+
export declare class RedisGraphCache<TSchema extends Schema> {
|
|
6
|
+
private schemaManager;
|
|
7
|
+
private connectionManager;
|
|
8
|
+
private normalizationEngine;
|
|
9
|
+
private hydrationEngine;
|
|
10
|
+
private config;
|
|
11
|
+
private activeOperations;
|
|
12
|
+
constructor(schema: TSchema, config?: PartialRedisSchemaEngineConfig);
|
|
13
|
+
/**
|
|
14
|
+
* Wrap a public operation with active-counter bookkeeping. Lets the
|
|
15
|
+
* health endpoint report a real (not stubbed) `activeOperations` value.
|
|
16
|
+
*/
|
|
17
|
+
private tracked;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the TTL for a given normalized entity key. The key format is
|
|
20
|
+
* always `entityType:id` so the prefix tells us which schema to consult.
|
|
21
|
+
*/
|
|
22
|
+
private resolveEntityKeyTTL;
|
|
23
|
+
/**
|
|
24
|
+
* Build a TTL resolver for a single write call that, when `cascadeTTL`
|
|
25
|
+
* is true, applies a floor: every entity written in this call uses at
|
|
26
|
+
* least `parentTtl` even if its own schema TTL is shorter. This is the
|
|
27
|
+
* fix for the "parent post (1h) embeds short-TTL category (1m); after
|
|
28
|
+
* 1m the post hydrates without its category" coherence bug.
|
|
29
|
+
*
|
|
30
|
+
* Special cases:
|
|
31
|
+
* - parentTtl = 0 (no expiry on parent): children also get 0,
|
|
32
|
+
* guaranteeing they outlive a never-expiring parent.
|
|
33
|
+
* - cascadeTTL = false (default): returns the unmodified resolver,
|
|
34
|
+
* so existing behaviour is bit-for-bit preserved.
|
|
35
|
+
* - parentTtl negative or NaN: ignored, falls back to per-key TTL.
|
|
36
|
+
*/
|
|
37
|
+
private buildTTLResolver;
|
|
38
|
+
/**
|
|
39
|
+
* Runtime helper for the dual positional/object-style public API.
|
|
40
|
+
* `typeof x === 'object' && x !== null` distinguishes the object
|
|
41
|
+
* form from the positional-string-first form, since `entityType`
|
|
42
|
+
* and `listType` are always strings.
|
|
43
|
+
*/
|
|
44
|
+
private isArgsObject;
|
|
45
|
+
/**
|
|
46
|
+
* Write an entity with nested relationships to Redis.
|
|
47
|
+
*
|
|
48
|
+
* Two equivalent call styles are supported:
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* // Object form (recommended for new code)
|
|
52
|
+
* await engine.writeEntity({ entityType: 'post', data, ttl, cascadeTTL });
|
|
53
|
+
*
|
|
54
|
+
* // Positional form (preserved for existing callers)
|
|
55
|
+
* await engine.writeEntity('post', data, { ttl, cascadeTTL });
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* When `ttl` is provided it overrides the entity's schema TTL for
|
|
59
|
+
* THIS WRITE ONLY, pinned to the root entity's key. Embedded
|
|
60
|
+
* relations still use their own schema TTL unless `cascadeTTL: true`
|
|
61
|
+
* is set — in which case the override also becomes the cascade
|
|
62
|
+
* floor for every child written by this call.
|
|
63
|
+
*/
|
|
64
|
+
writeEntity<T>(args: {
|
|
65
|
+
entityType: keyof TSchema;
|
|
66
|
+
data: T;
|
|
67
|
+
ttl?: number;
|
|
68
|
+
cascadeTTL?: boolean;
|
|
69
|
+
forceTTL?: boolean;
|
|
70
|
+
}): Promise<WriteResult>;
|
|
71
|
+
writeEntity<T>(entityType: keyof TSchema, data: T, options?: {
|
|
72
|
+
ttl?: number;
|
|
73
|
+
cascadeTTL?: boolean;
|
|
74
|
+
forceTTL?: boolean;
|
|
75
|
+
}): Promise<WriteResult>;
|
|
76
|
+
/**
|
|
77
|
+
* Read and hydrate an entity with all its relationships
|
|
78
|
+
*/
|
|
79
|
+
readEntity(entityType: keyof TSchema, id: string | number, options?: HydrationOptions): Promise<any>;
|
|
80
|
+
/**
|
|
81
|
+
* Delete an entity from Redis
|
|
82
|
+
*/
|
|
83
|
+
deleteEntity(entityType: keyof TSchema, id: string | number): Promise<boolean>;
|
|
84
|
+
/**
|
|
85
|
+
* Update an entity ONLY if it already exists in cache
|
|
86
|
+
* This is useful for cache invalidation scenarios where you don't want to
|
|
87
|
+
* create cache entries for data that hasn't been accessed yet
|
|
88
|
+
*
|
|
89
|
+
* @returns WriteResult if entity exists and was updated, null if entity doesn't exist
|
|
90
|
+
*/
|
|
91
|
+
updateEntityIfExists(args: {
|
|
92
|
+
entityType: keyof TSchema;
|
|
93
|
+
data: any;
|
|
94
|
+
ttl?: number;
|
|
95
|
+
cascadeTTL?: boolean;
|
|
96
|
+
forceTTL?: boolean;
|
|
97
|
+
}): Promise<WriteResult | null>;
|
|
98
|
+
updateEntityIfExists(entityType: keyof TSchema, data: any, options?: {
|
|
99
|
+
ttl?: number;
|
|
100
|
+
cascadeTTL?: boolean;
|
|
101
|
+
forceTTL?: boolean;
|
|
102
|
+
}): Promise<WriteResult | null>;
|
|
103
|
+
/**
|
|
104
|
+
* Write a list of entities to Redis
|
|
105
|
+
*/
|
|
106
|
+
writeList(args: {
|
|
107
|
+
listType: keyof TSchema;
|
|
108
|
+
params: any;
|
|
109
|
+
items: any[];
|
|
110
|
+
ttl?: number;
|
|
111
|
+
cascadeTTL?: boolean;
|
|
112
|
+
forceTTL?: boolean;
|
|
113
|
+
}): Promise<ListWriteResult>;
|
|
114
|
+
writeList(listType: keyof TSchema, params: any, items: any[], options?: {
|
|
115
|
+
ttl?: number;
|
|
116
|
+
cascadeTTL?: boolean;
|
|
117
|
+
forceTTL?: boolean;
|
|
118
|
+
}): Promise<ListWriteResult>;
|
|
119
|
+
/**
|
|
120
|
+
* Read and hydrate a list of entities
|
|
121
|
+
*/
|
|
122
|
+
readList(listType: keyof TSchema, params: any, options?: HydrationOptions): Promise<any[]>;
|
|
123
|
+
/**
|
|
124
|
+
* Delete a list from Redis
|
|
125
|
+
*/
|
|
126
|
+
deleteList(listType: keyof TSchema, params: any): Promise<boolean>;
|
|
127
|
+
/**
|
|
128
|
+
* Add an item to a list (with entity data)
|
|
129
|
+
* This method writes the entity to cache and adds it to the list
|
|
130
|
+
*/
|
|
131
|
+
addListItem(args: {
|
|
132
|
+
listType: keyof TSchema;
|
|
133
|
+
params: any;
|
|
134
|
+
entityData: any;
|
|
135
|
+
ttl?: number;
|
|
136
|
+
cascadeTTL?: boolean;
|
|
137
|
+
forceTTL?: boolean;
|
|
138
|
+
}): Promise<boolean>;
|
|
139
|
+
addListItem(listType: keyof TSchema, params: any, entityData: any, options?: {
|
|
140
|
+
ttl?: number;
|
|
141
|
+
cascadeTTL?: boolean;
|
|
142
|
+
forceTTL?: boolean;
|
|
143
|
+
}): Promise<boolean>;
|
|
144
|
+
/**
|
|
145
|
+
* Remove an item from a list (and optionally delete the entity)
|
|
146
|
+
*/
|
|
147
|
+
removeListItem(listType: keyof TSchema, params: any, entityId: any, options?: {
|
|
148
|
+
deleteEntity?: boolean;
|
|
149
|
+
}): Promise<boolean>;
|
|
150
|
+
/**
|
|
151
|
+
* Resolve the ZSET score for an entity from its schema's `scoreField`,
|
|
152
|
+
* with fallbacks: numeric strings, ISO timestamps via `Date.parse`, or
|
|
153
|
+
* insertion time when no `scoreField` is configured. Throws on values
|
|
154
|
+
* that cannot be coerced to a finite number, since silently scoring at
|
|
155
|
+
* 0 would scramble feed ordering.
|
|
156
|
+
*/
|
|
157
|
+
private resolveIndexedListScore;
|
|
158
|
+
/**
|
|
159
|
+
* Bulk upsert an indexed list. Each item is normalized + written like a
|
|
160
|
+
* regular entity, then added to the ZSET with its computed score. This
|
|
161
|
+
* does NOT clear existing members; for full replace, call
|
|
162
|
+
* `deleteIndexedList` first.
|
|
163
|
+
*/
|
|
164
|
+
writeIndexedList(args: {
|
|
165
|
+
listType: keyof TSchema;
|
|
166
|
+
params: any;
|
|
167
|
+
items: any[];
|
|
168
|
+
ttl?: number;
|
|
169
|
+
cascadeTTL?: boolean;
|
|
170
|
+
forceTTL?: boolean;
|
|
171
|
+
}): Promise<ListWriteResult>;
|
|
172
|
+
writeIndexedList(listType: keyof TSchema, params: any, items: any[], options?: {
|
|
173
|
+
ttl?: number;
|
|
174
|
+
cascadeTTL?: boolean;
|
|
175
|
+
forceTTL?: boolean;
|
|
176
|
+
}): Promise<ListWriteResult>;
|
|
177
|
+
/**
|
|
178
|
+
* Read a hydrated, paginated slice of an indexed list. Returns at most
|
|
179
|
+
* `limit` (default 50) hydrated entities. Combines ZSET pagination
|
|
180
|
+
* options (`offset`, `limit`, `reverse`, `minScore`, `maxScore`) with
|
|
181
|
+
* the standard hydration options (`maxDepth`, `selectiveFields`,
|
|
182
|
+
* `excludeRelations`).
|
|
183
|
+
*/
|
|
184
|
+
readIndexedList(listType: keyof TSchema, params: any, opts?: HydrationOptions & {
|
|
185
|
+
offset?: number;
|
|
186
|
+
limit?: number;
|
|
187
|
+
reverse?: boolean;
|
|
188
|
+
minScore?: number;
|
|
189
|
+
maxScore?: number;
|
|
190
|
+
}): Promise<any[]>;
|
|
191
|
+
/**
|
|
192
|
+
* Atomic single-item upsert into an indexed list. Writes the entity in
|
|
193
|
+
* full normalized form first, then issues an atomic ZADD with the
|
|
194
|
+
* resolved score and any configured trim/membership tracking. Returns
|
|
195
|
+
* true if a new member was added, false if the score was updated on
|
|
196
|
+
* an already-present id (still considered success).
|
|
197
|
+
*/
|
|
198
|
+
addIndexedListItem(args: {
|
|
199
|
+
listType: keyof TSchema;
|
|
200
|
+
params: any;
|
|
201
|
+
entityData: any;
|
|
202
|
+
ttl?: number;
|
|
203
|
+
cascadeTTL?: boolean;
|
|
204
|
+
forceTTL?: boolean;
|
|
205
|
+
}): Promise<boolean>;
|
|
206
|
+
addIndexedListItem(listType: keyof TSchema, params: any, entityData: any, options?: {
|
|
207
|
+
ttl?: number;
|
|
208
|
+
cascadeTTL?: boolean;
|
|
209
|
+
forceTTL?: boolean;
|
|
210
|
+
}): Promise<boolean>;
|
|
211
|
+
/**
|
|
212
|
+
* Atomic remove of a member from a single indexed list. By default the
|
|
213
|
+
* entity itself remains intact (it may still be referenced by other
|
|
214
|
+
* lists). Pass `{ deleteEntity: true }` to also call `invalidateEntity`,
|
|
215
|
+
* which will cascade-remove the entity from every tracked list.
|
|
216
|
+
*/
|
|
217
|
+
removeIndexedListItem(listType: keyof TSchema, params: any, entityId: any, options?: {
|
|
218
|
+
deleteEntity?: boolean;
|
|
219
|
+
}): Promise<boolean>;
|
|
220
|
+
/**
|
|
221
|
+
* Number of members currently in the indexed list. O(1).
|
|
222
|
+
*/
|
|
223
|
+
indexedListSize(listType: keyof TSchema, params: any): Promise<number>;
|
|
224
|
+
/**
|
|
225
|
+
* Delete the entire indexed list. Entities remain in cache. Membership
|
|
226
|
+
* back-indexes are NOT pruned here for performance reasons (they're
|
|
227
|
+
* harmless and self-clean as part of cascade invalidation or via TTL).
|
|
228
|
+
*/
|
|
229
|
+
deleteIndexedList(listType: keyof TSchema, params: any): Promise<boolean>;
|
|
230
|
+
/**
|
|
231
|
+
* Invalidate an entity from cache. When the entity is referenced by any
|
|
232
|
+
* indexed list with `trackMembership: true`, this also atomically ZREMs
|
|
233
|
+
* the entity from every such list — so consumers don't have to chase
|
|
234
|
+
* down list keys themselves. For entities not tracked by any list, the
|
|
235
|
+
* behaviour is identical to a plain entity delete.
|
|
236
|
+
*
|
|
237
|
+
* Returns the number of tracked lists the entity was removed from (0
|
|
238
|
+
* when no membership back-index exists, which is the common case).
|
|
239
|
+
*/
|
|
240
|
+
invalidateEntity(entityType: keyof TSchema, id: string | number): Promise<number>;
|
|
241
|
+
/**
|
|
242
|
+
* Clear every cache key owned by this engine. Destructive and
|
|
243
|
+
* irreversible.
|
|
244
|
+
*
|
|
245
|
+
* Behaviour depends on `redis.keyPrefix`:
|
|
246
|
+
* - **Prefix set** (recommended): uses non-blocking `SCAN` +
|
|
247
|
+
* `UNLINK` to delete only keys starting with the prefix. Safe to
|
|
248
|
+
* run against a Redis instance shared with other applications or
|
|
249
|
+
* environments — their keys are untouched.
|
|
250
|
+
* - **Prefix empty** (default, backward-compatible): falls back to
|
|
251
|
+
* `FLUSHDB`, wiping the entire selected database. Only use this
|
|
252
|
+
* when the engine owns the whole DB.
|
|
253
|
+
*
|
|
254
|
+
* Two safeguards apply in both modes:
|
|
255
|
+
* 1. The caller MUST pass `{ confirm: 'YES_WIPE_ALL' }` explicitly.
|
|
256
|
+
* 2. When `safety.productionMode` is true the operation is blocked
|
|
257
|
+
* unless `allowProduction: true` is also set.
|
|
258
|
+
*
|
|
259
|
+
* `safety.productionMode` defaults to `process.env.NODE_ENV ===
|
|
260
|
+
* 'production'` at construction time but is fully consumer-overridable
|
|
261
|
+
* via the engine config. The check below reads only the resolved
|
|
262
|
+
* config — no env access at call time.
|
|
263
|
+
*/
|
|
264
|
+
clearAllCache(opts: {
|
|
265
|
+
confirm: 'YES_WIPE_ALL';
|
|
266
|
+
allowProduction?: boolean;
|
|
267
|
+
}): Promise<void>;
|
|
268
|
+
/**
|
|
269
|
+
* Real cache metrics. Hits/misses are counted at the connection-manager
|
|
270
|
+
* boundary on every GET/MGET; latency, totals, failures come from the
|
|
271
|
+
* same shared HealthMetrics tracker. `memoryUsage` reports the Node-side
|
|
272
|
+
* estimate and is 0 when no operation has run yet; Redis-side memory is
|
|
273
|
+
* exposed via `getHealthStatus()`.
|
|
274
|
+
*/
|
|
275
|
+
getMetrics(): CacheMetrics;
|
|
276
|
+
/**
|
|
277
|
+
* Get health status of the engine and Redis connection
|
|
278
|
+
*/
|
|
279
|
+
getHealthStatus(): Promise<HealthStatus>;
|
|
280
|
+
/**
|
|
281
|
+
* Enable or disable debug mode
|
|
282
|
+
*/
|
|
283
|
+
enableDebugMode(enabled: boolean): void;
|
|
284
|
+
/**
|
|
285
|
+
* Gracefully disconnect and cleanup resources
|
|
286
|
+
*/
|
|
287
|
+
disconnect(): Promise<void>;
|
|
288
|
+
/**
|
|
289
|
+
* Initialize all engine components
|
|
290
|
+
*/
|
|
291
|
+
private initializeComponents;
|
|
292
|
+
/**
|
|
293
|
+
* Merge user config with defaults
|
|
294
|
+
*/
|
|
295
|
+
private mergeWithDefaults;
|
|
296
|
+
/**
|
|
297
|
+
* UUIDv4 collisions are practically impossible, unlike the previous
|
|
298
|
+
* Math.random()-based IDs which collided regularly under high concurrency.
|
|
299
|
+
*/
|
|
300
|
+
private generateOperationId;
|
|
301
|
+
private generateRequestId;
|
|
302
|
+
}
|