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.
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Schema Manager - Validates and manages entity and list schemas
3
+ */
4
+ import { Schema, EntitySchema, ListSchema, IndexedListSchema, RelationDefinition } from '../types';
5
+ export declare class SchemaManager {
6
+ private schemas;
7
+ private entitySchemas;
8
+ private listSchemas;
9
+ private indexedListSchemas;
10
+ private validationCache;
11
+ private defaultTTL;
12
+ constructor(schema: Schema, defaultTTL?: number);
13
+ /**
14
+ * Resolve the TTL (seconds) to apply when writing an entity. Schema-level
15
+ * `ttl` wins; otherwise falls back to the engine-wide defaultTTL. Returns
16
+ * 0 when no TTL should be applied (key persists indefinitely).
17
+ */
18
+ resolveEntityTTL(entityType: string): number;
19
+ /**
20
+ * Resolve the TTL (seconds) for list keys. Schema-level `ttl` on the list
21
+ * wins; otherwise falls back to engine defaultTTL.
22
+ */
23
+ resolveListTTL(listType: string): number;
24
+ /**
25
+ * Resolve TTL for an indexed-list ZSET key.
26
+ */
27
+ resolveIndexedListTTL(listType: string): number;
28
+ /**
29
+ * Internal namespace prefix used for membership back-index keys. Kept
30
+ * isolated so user schemas cannot accidentally collide with it.
31
+ */
32
+ static readonly MEMBERSHIP_PREFIX = "__rse_membership";
33
+ /**
34
+ * Build the back-index key tracking which list keys currently contain a
35
+ * given entity. Used by indexed lists with `trackMembership: true` so
36
+ * cascade invalidation can find and remove the entity from every list.
37
+ */
38
+ generateMembershipKey(entityType: string, id: string | number): string;
39
+ /**
40
+ * Register and validate a complete schema
41
+ */
42
+ registerSchema(schema: Schema): void;
43
+ /**
44
+ * Get entity schema by name
45
+ */
46
+ getEntitySchema(entityType: string): EntitySchema;
47
+ /**
48
+ * Get list schema by name
49
+ */
50
+ getListSchema(listType: string): ListSchema;
51
+ /**
52
+ * Get indexed-list schema by name. Throws if not registered or not the
53
+ * indexed kind, so callers can rely on the returned schema's structure.
54
+ */
55
+ getIndexedListSchema(listType: string): IndexedListSchema;
56
+ hasIndexedListSchema(listType: string): boolean;
57
+ getIndexedListSchemaNames(): string[];
58
+ /**
59
+ * Generate Redis key for an indexed list, using its schema-supplied key
60
+ * function. Same pattern as `generateListKey` for plain lists.
61
+ */
62
+ generateIndexedListKey(listType: string, ...params: any[]): string;
63
+ /**
64
+ * Check if entity schema exists
65
+ */
66
+ hasEntitySchema(entityType: string): boolean;
67
+ /**
68
+ * Check if list schema exists
69
+ */
70
+ hasListSchema(listType: string): boolean;
71
+ /**
72
+ * Get all entity schema names
73
+ */
74
+ getEntitySchemaNames(): string[];
75
+ /**
76
+ * Get all list schema names
77
+ */
78
+ getListSchemaNames(): string[];
79
+ /**
80
+ * Validate complete schema for consistency and relationships
81
+ */
82
+ private validateCompleteSchema;
83
+ /**
84
+ * Validate individual entity schema
85
+ */
86
+ private validateEntitySchema;
87
+ /**
88
+ * Validate individual list schema
89
+ */
90
+ private validateListSchema;
91
+ /**
92
+ * Validate an indexed list schema. Mirrors validateListSchema and adds
93
+ * the ZSET-specific checks. The score field, when given, isn't bound to
94
+ * any field type because we only call `Number(...)` on it at insert time.
95
+ */
96
+ private validateIndexedListSchema;
97
+ /**
98
+ * Validate relationship definition
99
+ */
100
+ private validateRelationDefinition;
101
+ /**
102
+ * Detect circular dependencies in relationships
103
+ */
104
+ private detectCircularDependencies;
105
+ /**
106
+ * Generate Redis key for entity
107
+ */
108
+ generateEntityKey(entityType: string, id: string | number): string;
109
+ /**
110
+ * Generate Redis key for list
111
+ */
112
+ generateListKey(listType: string, ...params: any[]): string;
113
+ /**
114
+ * Get entity ID field name
115
+ */
116
+ getEntityIdField(entityType: string): string;
117
+ /**
118
+ * Get list ID field name
119
+ */
120
+ getListIdField(listType: string): string;
121
+ /**
122
+ * Check if field is defined in entity schema
123
+ */
124
+ hasEntityField(entityType: string, fieldName: string): boolean;
125
+ /**
126
+ * Check if relation is defined in entity schema
127
+ */
128
+ hasEntityRelation(entityType: string, relationName: string): boolean;
129
+ /**
130
+ * Get entity relation definition
131
+ */
132
+ getEntityRelation(entityType: string, relationName: string): RelationDefinition;
133
+ }
@@ -0,0 +1,432 @@
1
+ "use strict";
2
+ /**
3
+ * Schema Manager - Validates and manages entity and list schemas
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SchemaManager = void 0;
7
+ const types_1 = require("../types");
8
+ class SchemaManager {
9
+ constructor(schema, defaultTTL = 0) {
10
+ this.schemas = new Map();
11
+ this.entitySchemas = new Map();
12
+ this.listSchemas = new Map();
13
+ this.indexedListSchemas = new Map();
14
+ this.validationCache = new Map();
15
+ this.defaultTTL = defaultTTL;
16
+ this.registerSchema(schema);
17
+ }
18
+ /**
19
+ * Resolve the TTL (seconds) to apply when writing an entity. Schema-level
20
+ * `ttl` wins; otherwise falls back to the engine-wide defaultTTL. Returns
21
+ * 0 when no TTL should be applied (key persists indefinitely).
22
+ */
23
+ resolveEntityTTL(entityType) {
24
+ const schema = this.entitySchemas.get(entityType);
25
+ if (schema?.ttl !== undefined)
26
+ return schema.ttl;
27
+ return this.defaultTTL;
28
+ }
29
+ /**
30
+ * Resolve the TTL (seconds) for list keys. Schema-level `ttl` on the list
31
+ * wins; otherwise falls back to engine defaultTTL.
32
+ */
33
+ resolveListTTL(listType) {
34
+ const schema = this.listSchemas.get(listType);
35
+ if (schema?.ttl !== undefined)
36
+ return schema.ttl;
37
+ return this.defaultTTL;
38
+ }
39
+ /**
40
+ * Resolve TTL for an indexed-list ZSET key.
41
+ */
42
+ resolveIndexedListTTL(listType) {
43
+ const schema = this.indexedListSchemas.get(listType);
44
+ if (schema?.ttl !== undefined)
45
+ return schema.ttl;
46
+ return this.defaultTTL;
47
+ }
48
+ /**
49
+ * Build the back-index key tracking which list keys currently contain a
50
+ * given entity. Used by indexed lists with `trackMembership: true` so
51
+ * cascade invalidation can find and remove the entity from every list.
52
+ */
53
+ generateMembershipKey(entityType, id) {
54
+ return `${SchemaManager.MEMBERSHIP_PREFIX}:${entityType}:${id}`;
55
+ }
56
+ /**
57
+ * Register and validate a complete schema
58
+ */
59
+ registerSchema(schema) {
60
+ // Clear existing schemas
61
+ this.schemas.clear();
62
+ this.entitySchemas.clear();
63
+ this.listSchemas.clear();
64
+ this.indexedListSchemas.clear();
65
+ this.validationCache.clear();
66
+ // First pass: register all schemas keyed by name and bucket by kind so
67
+ // later validation passes can reference siblings by name.
68
+ for (const [name, definition] of Object.entries(schema)) {
69
+ this.schemas.set(name, definition);
70
+ if (definition.type === 'entity') {
71
+ this.entitySchemas.set(name, definition);
72
+ }
73
+ else if (definition.type === 'list') {
74
+ this.listSchemas.set(name, definition);
75
+ }
76
+ else if (definition.type === 'indexedList') {
77
+ this.indexedListSchemas.set(name, definition);
78
+ }
79
+ }
80
+ // Second pass: Validate all schemas
81
+ const validationResult = this.validateCompleteSchema();
82
+ if (!validationResult.isValid) {
83
+ throw new types_1.SchemaValidationError(`Schema validation failed: ${validationResult.errors.join(', ')}`, {
84
+ errors: validationResult.errors,
85
+ warnings: validationResult.warnings,
86
+ });
87
+ }
88
+ }
89
+ /**
90
+ * Get entity schema by name
91
+ */
92
+ getEntitySchema(entityType) {
93
+ const schema = this.entitySchemas.get(entityType);
94
+ if (!schema) {
95
+ throw new types_1.InvalidOperationError('getEntitySchema', `Entity schema '${entityType}' not found`, { entityType, availableSchemas: Array.from(this.entitySchemas.keys()) });
96
+ }
97
+ return schema;
98
+ }
99
+ /**
100
+ * Get list schema by name
101
+ */
102
+ getListSchema(listType) {
103
+ const schema = this.listSchemas.get(listType);
104
+ if (!schema) {
105
+ throw new types_1.InvalidOperationError('getListSchema', `List schema '${listType}' not found`, { listType, availableSchemas: Array.from(this.listSchemas.keys()) });
106
+ }
107
+ return schema;
108
+ }
109
+ /**
110
+ * Get indexed-list schema by name. Throws if not registered or not the
111
+ * indexed kind, so callers can rely on the returned schema's structure.
112
+ */
113
+ getIndexedListSchema(listType) {
114
+ const schema = this.indexedListSchemas.get(listType);
115
+ if (!schema) {
116
+ throw new types_1.InvalidOperationError('getIndexedListSchema', `Indexed list schema '${listType}' not found`, {
117
+ listType,
118
+ availableSchemas: Array.from(this.indexedListSchemas.keys()),
119
+ });
120
+ }
121
+ return schema;
122
+ }
123
+ hasIndexedListSchema(listType) {
124
+ return this.indexedListSchemas.has(listType);
125
+ }
126
+ getIndexedListSchemaNames() {
127
+ return Array.from(this.indexedListSchemas.keys());
128
+ }
129
+ /**
130
+ * Generate Redis key for an indexed list, using its schema-supplied key
131
+ * function. Same pattern as `generateListKey` for plain lists.
132
+ */
133
+ generateIndexedListKey(listType, ...params) {
134
+ const schema = this.getIndexedListSchema(listType);
135
+ return schema.key(...params);
136
+ }
137
+ /**
138
+ * Check if entity schema exists
139
+ */
140
+ hasEntitySchema(entityType) {
141
+ return this.entitySchemas.has(entityType);
142
+ }
143
+ /**
144
+ * Check if list schema exists
145
+ */
146
+ hasListSchema(listType) {
147
+ return this.listSchemas.has(listType);
148
+ }
149
+ /**
150
+ * Get all entity schema names
151
+ */
152
+ getEntitySchemaNames() {
153
+ return Array.from(this.entitySchemas.keys());
154
+ }
155
+ /**
156
+ * Get all list schema names
157
+ */
158
+ getListSchemaNames() {
159
+ return Array.from(this.listSchemas.keys());
160
+ }
161
+ /**
162
+ * Validate complete schema for consistency and relationships
163
+ */
164
+ validateCompleteSchema() {
165
+ const errors = [];
166
+ const warnings = [];
167
+ // Validate each entity schema
168
+ for (const name of Array.from(this.entitySchemas.keys())) {
169
+ const schema = this.entitySchemas.get(name);
170
+ const result = this.validateEntitySchema(name, schema);
171
+ errors.push(...result.errors);
172
+ warnings.push(...result.warnings);
173
+ }
174
+ // Validate each list schema
175
+ for (const name of Array.from(this.listSchemas.keys())) {
176
+ const schema = this.listSchemas.get(name);
177
+ const result = this.validateListSchema(name, schema);
178
+ errors.push(...result.errors);
179
+ warnings.push(...result.warnings);
180
+ }
181
+ // Validate each indexed-list schema. Same baseline rules as list, plus
182
+ // ZSET-specific checks (scoreField name, maxSize positivity).
183
+ for (const name of Array.from(this.indexedListSchemas.keys())) {
184
+ const schema = this.indexedListSchemas.get(name);
185
+ const result = this.validateIndexedListSchema(name, schema);
186
+ errors.push(...result.errors);
187
+ warnings.push(...result.warnings);
188
+ }
189
+ // Check for circular dependencies
190
+ const circularDeps = this.detectCircularDependencies();
191
+ if (circularDeps.length > 0) {
192
+ warnings.push(`Circular dependencies detected: ${circularDeps.join(', ')}`);
193
+ }
194
+ return {
195
+ isValid: errors.length === 0,
196
+ errors,
197
+ warnings,
198
+ };
199
+ }
200
+ /**
201
+ * Validate individual entity schema
202
+ */
203
+ validateEntitySchema(name, schema) {
204
+ const errors = [];
205
+ const warnings = [];
206
+ // Validate required fields
207
+ if (!schema.id || typeof schema.id !== 'string') {
208
+ errors.push(`Entity '${name}': id field must be a non-empty string`);
209
+ }
210
+ if (!schema.key || typeof schema.key !== 'function') {
211
+ errors.push(`Entity '${name}': key must be a function`);
212
+ }
213
+ // Validate field definitions
214
+ if (schema.fields) {
215
+ for (const [fieldName, fieldDef] of Object.entries(schema.fields)) {
216
+ if (!fieldDef.type ||
217
+ ![
218
+ 'string',
219
+ 'number',
220
+ 'boolean',
221
+ 'object',
222
+ 'array',
223
+ // Extended types preserved losslessly by the default
224
+ // tagged serializer. Listed here purely for schema
225
+ // validation; the engine still does not coerce values
226
+ // based on the declared type.
227
+ 'date',
228
+ 'bigint',
229
+ 'map',
230
+ 'set',
231
+ 'regexp',
232
+ 'buffer',
233
+ ].includes(fieldDef.type)) {
234
+ errors.push(`Entity '${name}', field '${fieldName}': invalid field type '${fieldDef.type}'`);
235
+ }
236
+ // Check for internal field prefix conflicts
237
+ if (fieldName.startsWith('__rse_')) {
238
+ errors.push(`Entity '${name}', field '${fieldName}': field names cannot start with '__rse_' (reserved for internal use)`);
239
+ }
240
+ }
241
+ }
242
+ // Validate relationship definitions
243
+ if (schema.relations) {
244
+ for (const [relationName, relationDef] of Object.entries(schema.relations)) {
245
+ const relationErrors = this.validateRelationDefinition(name, relationName, relationDef);
246
+ errors.push(...relationErrors);
247
+ // Check for internal field prefix conflicts
248
+ if (relationName.startsWith('__rse_')) {
249
+ errors.push(`Entity '${name}', relation '${relationName}': relation names cannot start with '__rse_' (reserved for internal use)`);
250
+ }
251
+ }
252
+ }
253
+ // Validate TTL
254
+ if (schema.ttl !== undefined &&
255
+ (typeof schema.ttl !== 'number' || schema.ttl < 0)) {
256
+ errors.push(`Entity '${name}': ttl must be a positive number`);
257
+ }
258
+ return { isValid: errors.length === 0, errors, warnings };
259
+ }
260
+ /**
261
+ * Validate individual list schema
262
+ */
263
+ validateListSchema(name, schema) {
264
+ const errors = [];
265
+ const warnings = [];
266
+ // Validate required fields
267
+ if (!schema.entityType || typeof schema.entityType !== 'string') {
268
+ errors.push(`List '${name}': entityType must be a non-empty string`);
269
+ }
270
+ else if (!this.hasEntitySchema(schema.entityType)) {
271
+ errors.push(`List '${name}': referenced entity type '${schema.entityType}' does not exist`);
272
+ }
273
+ if (!schema.key || typeof schema.key !== 'function') {
274
+ errors.push(`List '${name}': key must be a function`);
275
+ }
276
+ if (!schema.idField || typeof schema.idField !== 'string') {
277
+ errors.push(`List '${name}': idField must be a non-empty string`);
278
+ }
279
+ // Validate TTL
280
+ if (schema.ttl !== undefined &&
281
+ (typeof schema.ttl !== 'number' || schema.ttl < 0)) {
282
+ errors.push(`List '${name}': ttl must be a positive number`);
283
+ }
284
+ return { isValid: errors.length === 0, errors, warnings };
285
+ }
286
+ /**
287
+ * Validate an indexed list schema. Mirrors validateListSchema and adds
288
+ * the ZSET-specific checks. The score field, when given, isn't bound to
289
+ * any field type because we only call `Number(...)` on it at insert time.
290
+ */
291
+ validateIndexedListSchema(name, schema) {
292
+ const errors = [];
293
+ const warnings = [];
294
+ if (!schema.entityType || typeof schema.entityType !== 'string') {
295
+ errors.push(`Indexed list '${name}': entityType must be a non-empty string`);
296
+ }
297
+ else if (!this.hasEntitySchema(schema.entityType)) {
298
+ errors.push(`Indexed list '${name}': referenced entity type '${schema.entityType}' does not exist`);
299
+ }
300
+ if (!schema.key || typeof schema.key !== 'function') {
301
+ errors.push(`Indexed list '${name}': key must be a function`);
302
+ }
303
+ if (!schema.idField || typeof schema.idField !== 'string') {
304
+ errors.push(`Indexed list '${name}': idField must be a non-empty string`);
305
+ }
306
+ if (schema.scoreField !== undefined &&
307
+ (typeof schema.scoreField !== 'string' || schema.scoreField.length === 0)) {
308
+ errors.push(`Indexed list '${name}': scoreField must be a non-empty string when provided`);
309
+ }
310
+ if (schema.maxSize !== undefined &&
311
+ (!Number.isFinite(schema.maxSize) || schema.maxSize <= 0)) {
312
+ errors.push(`Indexed list '${name}': maxSize must be a positive number`);
313
+ }
314
+ if (schema.ttl !== undefined &&
315
+ (typeof schema.ttl !== 'number' || schema.ttl < 0)) {
316
+ errors.push(`Indexed list '${name}': ttl must be a positive number`);
317
+ }
318
+ return { isValid: errors.length === 0, errors, warnings };
319
+ }
320
+ /**
321
+ * Validate relationship definition
322
+ */
323
+ validateRelationDefinition(entityName, relationName, relation) {
324
+ const errors = [];
325
+ // Validate relation type exists
326
+ if (!relation.type || typeof relation.type !== 'string') {
327
+ errors.push(`Entity '${entityName}', relation '${relationName}': type must be a non-empty string`);
328
+ }
329
+ else if (!this.hasEntitySchema(relation.type)) {
330
+ errors.push(`Entity '${entityName}', relation '${relationName}': referenced entity type '${relation.type}' does not exist`);
331
+ }
332
+ // Validate relation kind
333
+ if (!relation.kind || !['one', 'many'].includes(relation.kind)) {
334
+ errors.push(`Entity '${entityName}', relation '${relationName}': kind must be 'one' or 'many'`);
335
+ }
336
+ return errors;
337
+ }
338
+ /**
339
+ * Detect circular dependencies in relationships
340
+ */
341
+ detectCircularDependencies() {
342
+ const visited = new Set();
343
+ const recursionStack = new Set();
344
+ const cycles = [];
345
+ const dfs = (entityType, path) => {
346
+ if (recursionStack.has(entityType)) {
347
+ const cycleStart = path.indexOf(entityType);
348
+ const cycle = path.slice(cycleStart).concat(entityType);
349
+ cycles.push(cycle.join(' -> '));
350
+ return;
351
+ }
352
+ if (visited.has(entityType)) {
353
+ return;
354
+ }
355
+ visited.add(entityType);
356
+ recursionStack.add(entityType);
357
+ const schema = this.entitySchemas.get(entityType);
358
+ if (schema?.relations) {
359
+ for (const relation of Object.values(schema.relations)) {
360
+ if (this.hasEntitySchema(relation.type)) {
361
+ dfs(relation.type, [...path, entityType]);
362
+ }
363
+ }
364
+ }
365
+ recursionStack.delete(entityType);
366
+ };
367
+ for (const entityType of Array.from(this.entitySchemas.keys())) {
368
+ if (!visited.has(entityType)) {
369
+ dfs(entityType, []);
370
+ }
371
+ }
372
+ return cycles;
373
+ }
374
+ /**
375
+ * Generate Redis key for entity
376
+ */
377
+ generateEntityKey(entityType, id) {
378
+ const schema = this.getEntitySchema(entityType);
379
+ return schema.key(id);
380
+ }
381
+ /**
382
+ * Generate Redis key for list
383
+ */
384
+ generateListKey(listType, ...params) {
385
+ const schema = this.getListSchema(listType);
386
+ return schema.key(...params);
387
+ }
388
+ /**
389
+ * Get entity ID field name
390
+ */
391
+ getEntityIdField(entityType) {
392
+ const schema = this.getEntitySchema(entityType);
393
+ return schema.id;
394
+ }
395
+ /**
396
+ * Get list ID field name
397
+ */
398
+ getListIdField(listType) {
399
+ const schema = this.getListSchema(listType);
400
+ return schema.idField;
401
+ }
402
+ /**
403
+ * Check if field is defined in entity schema
404
+ */
405
+ hasEntityField(entityType, fieldName) {
406
+ const schema = this.getEntitySchema(entityType);
407
+ return schema.fields ? fieldName in schema.fields : false;
408
+ }
409
+ /**
410
+ * Check if relation is defined in entity schema
411
+ */
412
+ hasEntityRelation(entityType, relationName) {
413
+ const schema = this.getEntitySchema(entityType);
414
+ return schema.relations ? relationName in schema.relations : false;
415
+ }
416
+ /**
417
+ * Get entity relation definition
418
+ */
419
+ getEntityRelation(entityType, relationName) {
420
+ const schema = this.getEntitySchema(entityType);
421
+ if (!schema.relations || !(relationName in schema.relations)) {
422
+ throw new types_1.InvalidOperationError('getEntityRelation', `Relation '${relationName}' not found in entity '${entityType}'`, { entityType, relationName });
423
+ }
424
+ return schema.relations[relationName];
425
+ }
426
+ }
427
+ exports.SchemaManager = SchemaManager;
428
+ /**
429
+ * Internal namespace prefix used for membership back-index keys. Kept
430
+ * isolated so user schemas cannot accidentally collide with it.
431
+ */
432
+ SchemaManager.MEMBERSHIP_PREFIX = '__rse_membership';
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Serializer - Pluggable (de)serialization for entity payloads.
3
+ *
4
+ * The default serializer (`TAGGED_SERIALIZER`) is a lossless wrapper
5
+ * around `JSON.stringify` / `JSON.parse` that preserves JS types JSON
6
+ * cannot represent natively. It uses a tag-based envelope only for
7
+ * non-JSON-native values; everything else round-trips identically to
8
+ * plain `JSON.stringify` so the on-wire format is unchanged for
9
+ * ordinary data (strings, numbers, booleans, plain objects, arrays,
10
+ * nulls).
11
+ *
12
+ * Why the engine needs this:
13
+ * - `Date` becomes an ISO string via `Date.prototype.toJSON` and
14
+ * parses back as a string, breaking `post.createdAt.getTime()`.
15
+ * - `BigInt` throws `TypeError` from `JSON.stringify`.
16
+ * - `Map`, `Set`, `RegExp`, `Buffer` serialize to `{}`, losing all
17
+ * contents.
18
+ * - `NaN`, `Infinity`, `-Infinity` become `null`, silently corrupting
19
+ * numeric stats fields.
20
+ *
21
+ * Out of scope (intentionally NOT tagged):
22
+ * - `undefined`. The engine's merge semantics treat
23
+ * "field is undefined" identically to "field is omitted" so that
24
+ * partial updates don't accidentally clobber existing values.
25
+ * See `NULL_UNDEFINED_HANDLING.md`. Tagging undefined would
26
+ * silently change that contract.
27
+ *
28
+ * Backward compatibility:
29
+ * - Reads of plain JSON written by older versions (or by any process
30
+ * without this engine) work unchanged. The reviver only transforms
31
+ * objects that carry the internal tag key.
32
+ * - Writes that contain only JSON-native values produce byte-for-byte
33
+ * identical output to the previous `JSON.stringify` path.
34
+ *
35
+ * Pluggability:
36
+ * - Consumers can opt out by passing `cache.serializer = JSON_SERIALIZER`
37
+ * (raw, no tagging — original behaviour).
38
+ * - Consumers can plug in a third-party codec like `superjson` or
39
+ * `devalue` by passing any object that implements `Serializer`.
40
+ */
41
+ /**
42
+ * Minimal contract every serializer must satisfy. The engine never
43
+ * inspects intermediate forms; it only ever calls `stringify` to
44
+ * produce the on-wire string and `parse` to recover the structured
45
+ * value.
46
+ */
47
+ export interface Serializer {
48
+ /** Serialize a structured value to the string Redis stores. */
49
+ stringify(value: any): string;
50
+ /** Deserialize a string read from Redis back to a structured value. */
51
+ parse(value: string): any;
52
+ }
53
+ /**
54
+ * Default serializer. Lossless for `Date`, `BigInt`, `Map`, `Set`,
55
+ * `RegExp`, `Buffer`, `NaN`, `+Infinity`, `-Infinity`. Identical to
56
+ * plain JSON for all other values.
57
+ */
58
+ export declare const TAGGED_SERIALIZER: Serializer;
59
+ /**
60
+ * Plain JSON serializer. No tagging, no reviver. Use this when you
61
+ * know your data is JSON-native and want zero serialization overhead,
62
+ * or when interoperating with non-engine readers that expect raw JSON.
63
+ *
64
+ * Trade-off: `Date` survives as ISO string only, `BigInt` will throw,
65
+ * `Map`/`Set`/`RegExp`/`Buffer` lose all data, `NaN`/`Infinity` become
66
+ * `null`. This is the historical engine behaviour; kept available for
67
+ * opt-out.
68
+ */
69
+ export declare const JSON_SERIALIZER: Serializer;