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,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration types for Redis Schema Engine
|
|
3
|
+
*/
|
|
4
|
+
import type { RedisOptions } from 'ioredis';
|
|
5
|
+
import type { Serializer } from '../core';
|
|
6
|
+
export interface RedisClient {
|
|
7
|
+
get(key: string): Promise<string | null>;
|
|
8
|
+
mget(...keys: string[]): Promise<Array<string | null>>;
|
|
9
|
+
set(key: string, value: string): Promise<'OK'>;
|
|
10
|
+
del(...keys: string[]): Promise<number>;
|
|
11
|
+
ping(): Promise<'PONG'>;
|
|
12
|
+
quit(): Promise<'OK'>;
|
|
13
|
+
flushdb(): Promise<'OK'>;
|
|
14
|
+
pipeline(): any;
|
|
15
|
+
on(event: string, listener: (...args: any[]) => void): void;
|
|
16
|
+
}
|
|
17
|
+
export interface RedisConnectionPoolConfig {
|
|
18
|
+
min: number;
|
|
19
|
+
max: number;
|
|
20
|
+
acquireTimeoutMillis: number;
|
|
21
|
+
idleTimeoutMillis: number;
|
|
22
|
+
}
|
|
23
|
+
export interface RedisConfig extends RedisOptions {
|
|
24
|
+
keyPrefix?: string;
|
|
25
|
+
connectionPool?: RedisConnectionPoolConfig;
|
|
26
|
+
/**
|
|
27
|
+
* Number of ioredis clients to open against the same Redis instance.
|
|
28
|
+
* Each client is an independent TCP connection; commands are
|
|
29
|
+
* round-robined across them. Increases throughput at high concurrency
|
|
30
|
+
* by reducing head-of-line blocking on a single socket and using more
|
|
31
|
+
* of Redis's parallelism. Default 1 preserves current behaviour.
|
|
32
|
+
*
|
|
33
|
+
* Recommended: 1 for dev / low traffic, 4-8 for typical production
|
|
34
|
+
* workloads, 8-16 for sustained >10k ops/sec single-instance loads.
|
|
35
|
+
* Going much higher rarely helps and consumes Redis connection slots.
|
|
36
|
+
*/
|
|
37
|
+
poolSize?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface PerformanceLimits {
|
|
40
|
+
maxHydrationDepth: number;
|
|
41
|
+
maxEntitiesPerRequest: number;
|
|
42
|
+
maxMemoryUsagePerOperation: number;
|
|
43
|
+
maxConcurrentOperations: number;
|
|
44
|
+
batchSize: number;
|
|
45
|
+
}
|
|
46
|
+
export interface CircuitBreakerConfig {
|
|
47
|
+
threshold: number;
|
|
48
|
+
timeout: number;
|
|
49
|
+
resetTimeout: number;
|
|
50
|
+
}
|
|
51
|
+
export interface RetryConfig {
|
|
52
|
+
maxAttempts: number;
|
|
53
|
+
baseDelay: number;
|
|
54
|
+
maxDelay: number;
|
|
55
|
+
backoffFactor: number;
|
|
56
|
+
}
|
|
57
|
+
export type FallbackStrategy = 'null' | 'empty' | 'cached' | 'custom';
|
|
58
|
+
export interface FallbackConfig {
|
|
59
|
+
enabled: boolean;
|
|
60
|
+
strategy: FallbackStrategy;
|
|
61
|
+
}
|
|
62
|
+
export interface ResilienceConfig {
|
|
63
|
+
circuitBreaker: CircuitBreakerConfig;
|
|
64
|
+
retry: RetryConfig;
|
|
65
|
+
fallback: FallbackConfig;
|
|
66
|
+
}
|
|
67
|
+
export type LogLevel = 'error' | 'warn' | 'info' | 'debug';
|
|
68
|
+
export interface MonitoringConfig {
|
|
69
|
+
enableMetrics: boolean;
|
|
70
|
+
enableDebugMode: boolean;
|
|
71
|
+
enableAuditLog: boolean;
|
|
72
|
+
metricsInterval: number;
|
|
73
|
+
logLevel: LogLevel;
|
|
74
|
+
}
|
|
75
|
+
export interface CacheConfig {
|
|
76
|
+
defaultTTL: number;
|
|
77
|
+
enableL1Cache: boolean;
|
|
78
|
+
l1CacheSize: number;
|
|
79
|
+
enableCompression: boolean;
|
|
80
|
+
compressionThreshold: number;
|
|
81
|
+
/**
|
|
82
|
+
* (De)serializer used for entity payloads. Defaults to the engine's
|
|
83
|
+
* lossless tagged JSON serializer, which preserves `Date`, `BigInt`,
|
|
84
|
+
* `Map`, `Set`, `RegExp`, `Buffer`, `NaN`, and `±Infinity` across
|
|
85
|
+
* cache round-trips while remaining backward-compatible with plain
|
|
86
|
+
* JSON values written by older versions or other producers.
|
|
87
|
+
*
|
|
88
|
+
* Pass `JSON_SERIALIZER` to opt out (raw `JSON.stringify`/`parse`,
|
|
89
|
+
* fastest but lossy for the types listed above), or supply any
|
|
90
|
+
* `{ stringify, parse }` pair (e.g. wrapping `superjson`) to plug in
|
|
91
|
+
* a third-party codec. The same instance is used for writes and
|
|
92
|
+
* reads, so consistency is the consumer's responsibility when
|
|
93
|
+
* swapping serializers against an existing cache.
|
|
94
|
+
*
|
|
95
|
+
* Note: list bodies (the JSON id-array stored under a `list`
|
|
96
|
+
* schema's key) are intentionally NOT routed through this serializer
|
|
97
|
+
* because Lua scripts on the Redis side parse them directly. Lists
|
|
98
|
+
* always use plain JSON. Entity values (which contain user data)
|
|
99
|
+
* are the only thing the serializer touches.
|
|
100
|
+
*/
|
|
101
|
+
serializer?: Serializer;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Safety guards for destructive operations. Kept in a dedicated config
|
|
105
|
+
* block so the engine itself never has to read `process.env` at runtime
|
|
106
|
+
* — important for npm-distributed builds where consumers' build systems
|
|
107
|
+
* may not set NODE_ENV the way Node apps do.
|
|
108
|
+
*/
|
|
109
|
+
export interface SafetyConfig {
|
|
110
|
+
/**
|
|
111
|
+
* When true, `clearAllCache()` requires an additional
|
|
112
|
+
* `{ allowProduction: true }` argument to succeed. Intended as a
|
|
113
|
+
* safety net so a stray call from a test harness or a mis-targeted
|
|
114
|
+
* CLI cannot wipe a production database.
|
|
115
|
+
*
|
|
116
|
+
* Defaults to `process.env.NODE_ENV === 'production'` at engine
|
|
117
|
+
* construction time so existing consumers keep the same behaviour
|
|
118
|
+
* with no config change. Library consumers should pass this
|
|
119
|
+
* explicitly rather than relying on the env default.
|
|
120
|
+
*/
|
|
121
|
+
productionMode: boolean;
|
|
122
|
+
}
|
|
123
|
+
export interface RedisSchemaEngineConfig {
|
|
124
|
+
redis: RedisConfig;
|
|
125
|
+
limits: PerformanceLimits;
|
|
126
|
+
resilience: ResilienceConfig;
|
|
127
|
+
monitoring: MonitoringConfig;
|
|
128
|
+
cache: CacheConfig;
|
|
129
|
+
safety: SafetyConfig;
|
|
130
|
+
}
|
|
131
|
+
export type PartialRedisSchemaEngineConfig = Partial<RedisSchemaEngineConfig> & {
|
|
132
|
+
redis?: Partial<RedisConfig>;
|
|
133
|
+
limits?: Partial<PerformanceLimits>;
|
|
134
|
+
resilience?: Partial<ResilienceConfig>;
|
|
135
|
+
monitoring?: Partial<MonitoringConfig>;
|
|
136
|
+
cache?: Partial<CacheConfig>;
|
|
137
|
+
safety?: Partial<SafetyConfig>;
|
|
138
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error types and classification for Redis Schema Engine
|
|
3
|
+
*/
|
|
4
|
+
export declare enum RedisSchemaEngineErrorType {
|
|
5
|
+
SCHEMA_VALIDATION = "SCHEMA_VALIDATION",
|
|
6
|
+
ENTITY_NOT_FOUND = "ENTITY_NOT_FOUND",
|
|
7
|
+
INVALID_OPERATION = "INVALID_OPERATION",
|
|
8
|
+
REDIS_CONNECTION = "REDIS_CONNECTION",
|
|
9
|
+
MEMORY_LIMIT = "MEMORY_LIMIT",
|
|
10
|
+
TIMEOUT = "TIMEOUT",
|
|
11
|
+
CONCURRENT_MODIFICATION = "CONCURRENT_MODIFICATION",
|
|
12
|
+
CIRCUIT_BREAKER_OPEN = "CIRCUIT_BREAKER_OPEN",
|
|
13
|
+
HYDRATION_DEPTH_EXCEEDED = "HYDRATION_DEPTH_EXCEEDED",
|
|
14
|
+
INVALID_SCHEMA = "INVALID_SCHEMA",
|
|
15
|
+
MISSING_RELATION = "MISSING_RELATION"
|
|
16
|
+
}
|
|
17
|
+
export declare class RedisSchemaEngineError extends Error {
|
|
18
|
+
readonly type: RedisSchemaEngineErrorType;
|
|
19
|
+
readonly context?: any;
|
|
20
|
+
readonly cause?: Error;
|
|
21
|
+
readonly timestamp: number;
|
|
22
|
+
readonly requestId?: string;
|
|
23
|
+
constructor(type: RedisSchemaEngineErrorType, message: string, context?: any, cause?: Error, requestId?: string);
|
|
24
|
+
toJSON(): {
|
|
25
|
+
name: string;
|
|
26
|
+
type: RedisSchemaEngineErrorType;
|
|
27
|
+
message: string;
|
|
28
|
+
context: any;
|
|
29
|
+
timestamp: number;
|
|
30
|
+
requestId: string | undefined;
|
|
31
|
+
stack: string | undefined;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export declare class SchemaValidationError extends RedisSchemaEngineError {
|
|
35
|
+
constructor(message: string, context?: any, requestId?: string);
|
|
36
|
+
}
|
|
37
|
+
export declare class EntityNotFoundError extends RedisSchemaEngineError {
|
|
38
|
+
constructor(entityType: string, id: string | number, requestId?: string);
|
|
39
|
+
}
|
|
40
|
+
export declare class InvalidOperationError extends RedisSchemaEngineError {
|
|
41
|
+
constructor(operation: string, reason: string, context?: any, requestId?: string);
|
|
42
|
+
}
|
|
43
|
+
export declare class RedisConnectionError extends RedisSchemaEngineError {
|
|
44
|
+
constructor(message: string, cause?: Error, requestId?: string);
|
|
45
|
+
}
|
|
46
|
+
export declare class MemoryLimitError extends RedisSchemaEngineError {
|
|
47
|
+
constructor(currentUsage: number, limit: number, requestId?: string);
|
|
48
|
+
}
|
|
49
|
+
export declare class CircuitBreakerOpenError extends RedisSchemaEngineError {
|
|
50
|
+
constructor(requestId?: string);
|
|
51
|
+
}
|
|
52
|
+
export declare class HydrationDepthExceededError extends RedisSchemaEngineError {
|
|
53
|
+
constructor(currentDepth: number, maxDepth: number, requestId?: string);
|
|
54
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Error types and classification for Redis Schema Engine
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.HydrationDepthExceededError = exports.CircuitBreakerOpenError = exports.MemoryLimitError = exports.RedisConnectionError = exports.InvalidOperationError = exports.EntityNotFoundError = exports.SchemaValidationError = exports.RedisSchemaEngineError = exports.RedisSchemaEngineErrorType = void 0;
|
|
7
|
+
var RedisSchemaEngineErrorType;
|
|
8
|
+
(function (RedisSchemaEngineErrorType) {
|
|
9
|
+
RedisSchemaEngineErrorType["SCHEMA_VALIDATION"] = "SCHEMA_VALIDATION";
|
|
10
|
+
RedisSchemaEngineErrorType["ENTITY_NOT_FOUND"] = "ENTITY_NOT_FOUND";
|
|
11
|
+
RedisSchemaEngineErrorType["INVALID_OPERATION"] = "INVALID_OPERATION";
|
|
12
|
+
RedisSchemaEngineErrorType["REDIS_CONNECTION"] = "REDIS_CONNECTION";
|
|
13
|
+
RedisSchemaEngineErrorType["MEMORY_LIMIT"] = "MEMORY_LIMIT";
|
|
14
|
+
RedisSchemaEngineErrorType["TIMEOUT"] = "TIMEOUT";
|
|
15
|
+
RedisSchemaEngineErrorType["CONCURRENT_MODIFICATION"] = "CONCURRENT_MODIFICATION";
|
|
16
|
+
RedisSchemaEngineErrorType["CIRCUIT_BREAKER_OPEN"] = "CIRCUIT_BREAKER_OPEN";
|
|
17
|
+
RedisSchemaEngineErrorType["HYDRATION_DEPTH_EXCEEDED"] = "HYDRATION_DEPTH_EXCEEDED";
|
|
18
|
+
RedisSchemaEngineErrorType["INVALID_SCHEMA"] = "INVALID_SCHEMA";
|
|
19
|
+
RedisSchemaEngineErrorType["MISSING_RELATION"] = "MISSING_RELATION";
|
|
20
|
+
})(RedisSchemaEngineErrorType || (exports.RedisSchemaEngineErrorType = RedisSchemaEngineErrorType = {}));
|
|
21
|
+
class RedisSchemaEngineError extends Error {
|
|
22
|
+
constructor(type, message, context, cause, requestId) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'RedisSchemaEngineError';
|
|
25
|
+
this.type = type;
|
|
26
|
+
this.context = context;
|
|
27
|
+
this.cause = cause;
|
|
28
|
+
this.timestamp = Date.now();
|
|
29
|
+
this.requestId = requestId;
|
|
30
|
+
// Maintain proper stack trace (Node.js / V8 specific). On
|
|
31
|
+
// non-V8 runtimes `captureStackTrace` is undefined and the
|
|
32
|
+
// optional chain short-circuits cleanly.
|
|
33
|
+
Error.captureStackTrace?.(this, RedisSchemaEngineError);
|
|
34
|
+
}
|
|
35
|
+
toJSON() {
|
|
36
|
+
return {
|
|
37
|
+
name: this.name,
|
|
38
|
+
type: this.type,
|
|
39
|
+
message: this.message,
|
|
40
|
+
context: this.context,
|
|
41
|
+
timestamp: this.timestamp,
|
|
42
|
+
requestId: this.requestId,
|
|
43
|
+
stack: this.stack,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.RedisSchemaEngineError = RedisSchemaEngineError;
|
|
48
|
+
class SchemaValidationError extends RedisSchemaEngineError {
|
|
49
|
+
constructor(message, context, requestId) {
|
|
50
|
+
super(RedisSchemaEngineErrorType.SCHEMA_VALIDATION, message, context, undefined, requestId);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.SchemaValidationError = SchemaValidationError;
|
|
54
|
+
class EntityNotFoundError extends RedisSchemaEngineError {
|
|
55
|
+
constructor(entityType, id, requestId) {
|
|
56
|
+
super(RedisSchemaEngineErrorType.ENTITY_NOT_FOUND, `Entity '${entityType}' with id '${id}' not found`, { entityType, id }, undefined, requestId);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.EntityNotFoundError = EntityNotFoundError;
|
|
60
|
+
class InvalidOperationError extends RedisSchemaEngineError {
|
|
61
|
+
constructor(operation, reason, context, requestId) {
|
|
62
|
+
super(RedisSchemaEngineErrorType.INVALID_OPERATION, `Invalid operation '${operation}': ${reason}`, { operation, reason, ...context }, undefined, requestId);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.InvalidOperationError = InvalidOperationError;
|
|
66
|
+
class RedisConnectionError extends RedisSchemaEngineError {
|
|
67
|
+
constructor(message, cause, requestId) {
|
|
68
|
+
super(RedisSchemaEngineErrorType.REDIS_CONNECTION, `Redis connection error: ${message}`, undefined, cause, requestId);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.RedisConnectionError = RedisConnectionError;
|
|
72
|
+
class MemoryLimitError extends RedisSchemaEngineError {
|
|
73
|
+
constructor(currentUsage, limit, requestId) {
|
|
74
|
+
super(RedisSchemaEngineErrorType.MEMORY_LIMIT, `Memory limit exceeded: ${currentUsage}MB > ${limit}MB`, { currentUsage, limit }, undefined, requestId);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.MemoryLimitError = MemoryLimitError;
|
|
78
|
+
class CircuitBreakerOpenError extends RedisSchemaEngineError {
|
|
79
|
+
constructor(requestId) {
|
|
80
|
+
super(RedisSchemaEngineErrorType.CIRCUIT_BREAKER_OPEN, 'Circuit breaker is OPEN - Redis operations are temporarily disabled', undefined, undefined, requestId);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.CircuitBreakerOpenError = CircuitBreakerOpenError;
|
|
84
|
+
class HydrationDepthExceededError extends RedisSchemaEngineError {
|
|
85
|
+
constructor(currentDepth, maxDepth, requestId) {
|
|
86
|
+
super(RedisSchemaEngineErrorType.HYDRATION_DEPTH_EXCEEDED, `Hydration depth exceeded: ${currentDepth} > ${maxDepth}`, { currentDepth, maxDepth }, undefined, requestId);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.HydrationDepthExceededError = HydrationDepthExceededError;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Barrel export for all Redis Schema Engine types
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
// Schema types
|
|
21
|
+
__exportStar(require("./schema.types"), exports);
|
|
22
|
+
// Configuration types
|
|
23
|
+
__exportStar(require("./config.types"), exports);
|
|
24
|
+
// Operation types
|
|
25
|
+
__exportStar(require("./operation.types"), exports);
|
|
26
|
+
// Internal types and utilities
|
|
27
|
+
__exportStar(require("./internal.types"), exports);
|
|
28
|
+
// Error types
|
|
29
|
+
__exportStar(require("./error.types"), exports);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal data structures and metadata types for Redis Schema Engine
|
|
3
|
+
*/
|
|
4
|
+
export interface EntityMetadata {
|
|
5
|
+
entityType: string;
|
|
6
|
+
originalId: string | number;
|
|
7
|
+
relationshipIds: Record<string, any>;
|
|
8
|
+
lastUpdated: number;
|
|
9
|
+
source: string;
|
|
10
|
+
version?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface NormalizedEntity {
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
__rse_version?: string;
|
|
15
|
+
__rse_timestamp?: number;
|
|
16
|
+
__rse_metadata?: EntityMetadata;
|
|
17
|
+
}
|
|
18
|
+
export interface HydrationContext {
|
|
19
|
+
visitedEntities: Set<string>;
|
|
20
|
+
currentDepth: number;
|
|
21
|
+
maxDepth: number;
|
|
22
|
+
memoryUsage: number;
|
|
23
|
+
maxMemory: number;
|
|
24
|
+
requestId: string;
|
|
25
|
+
selectiveFields?: string[];
|
|
26
|
+
excludeRelations?: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface PipelineOperation {
|
|
29
|
+
type: 'GET' | 'SET' | 'DEL' | 'MGET' | 'MSET';
|
|
30
|
+
key?: string;
|
|
31
|
+
keys?: string[];
|
|
32
|
+
value?: string;
|
|
33
|
+
values?: Record<string, string>;
|
|
34
|
+
}
|
|
35
|
+
export interface PipelineResult {
|
|
36
|
+
success: boolean;
|
|
37
|
+
results: any[];
|
|
38
|
+
errors: Error[];
|
|
39
|
+
operationCount: number;
|
|
40
|
+
executionTime: number;
|
|
41
|
+
}
|
|
42
|
+
export interface CircuitBreakerState {
|
|
43
|
+
state: 'CLOSED' | 'OPEN' | 'HALF_OPEN';
|
|
44
|
+
failures: number;
|
|
45
|
+
lastFailureTime: number;
|
|
46
|
+
lastSuccessTime: number;
|
|
47
|
+
}
|
|
48
|
+
export interface RetryOptions {
|
|
49
|
+
maxAttempts?: number;
|
|
50
|
+
baseDelay?: number;
|
|
51
|
+
maxDelay?: number;
|
|
52
|
+
backoffFactor?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface FallbackOptions {
|
|
55
|
+
enableFallback?: boolean;
|
|
56
|
+
strategy?: 'null' | 'empty' | 'cached' | 'custom';
|
|
57
|
+
customFallback?: () => Promise<any>;
|
|
58
|
+
}
|
|
59
|
+
export declare const INTERNAL_FIELD_PREFIX = "__rse_";
|
|
60
|
+
export declare const VERSION_FIELD = "__rse_version";
|
|
61
|
+
export declare const TIMESTAMP_FIELD = "__rse_timestamp";
|
|
62
|
+
export declare const METADATA_FIELD = "__rse_metadata";
|
|
63
|
+
export declare const generateEntityKey: (entityType: string, id: string | number) => string;
|
|
64
|
+
export declare const generateListKey: (listType: string, ...params: any[]) => string;
|
|
65
|
+
export declare const generateRelationIdField: (relationName: string, kind: "one" | "many") => string;
|
|
66
|
+
export declare const isInternalField: (fieldName: string) => boolean;
|
|
67
|
+
export declare const stripInternalFields: (obj: any) => any;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Internal data structures and metadata types for Redis Schema Engine
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.stripInternalFields = exports.isInternalField = exports.generateRelationIdField = exports.generateListKey = exports.generateEntityKey = exports.METADATA_FIELD = exports.TIMESTAMP_FIELD = exports.VERSION_FIELD = exports.INTERNAL_FIELD_PREFIX = void 0;
|
|
7
|
+
// Internal field naming constants
|
|
8
|
+
exports.INTERNAL_FIELD_PREFIX = '__rse_';
|
|
9
|
+
exports.VERSION_FIELD = '__rse_version';
|
|
10
|
+
exports.TIMESTAMP_FIELD = '__rse_timestamp';
|
|
11
|
+
exports.METADATA_FIELD = '__rse_metadata';
|
|
12
|
+
// Key generation utilities
|
|
13
|
+
const generateEntityKey = (entityType, id) => {
|
|
14
|
+
return `${entityType}:${id}`;
|
|
15
|
+
};
|
|
16
|
+
exports.generateEntityKey = generateEntityKey;
|
|
17
|
+
const generateListKey = (listType, ...params) => {
|
|
18
|
+
return `${listType}:${params.join(':')}`;
|
|
19
|
+
};
|
|
20
|
+
exports.generateListKey = generateListKey;
|
|
21
|
+
const generateRelationIdField = (relationName, kind) => {
|
|
22
|
+
return kind === 'one'
|
|
23
|
+
? `${exports.INTERNAL_FIELD_PREFIX}${relationName}Id`
|
|
24
|
+
: `${exports.INTERNAL_FIELD_PREFIX}${relationName}Ids`;
|
|
25
|
+
};
|
|
26
|
+
exports.generateRelationIdField = generateRelationIdField;
|
|
27
|
+
const isInternalField = (fieldName) => {
|
|
28
|
+
return fieldName.startsWith(exports.INTERNAL_FIELD_PREFIX);
|
|
29
|
+
};
|
|
30
|
+
exports.isInternalField = isInternalField;
|
|
31
|
+
/**
|
|
32
|
+
* Return true iff `value` is a plain `{ ... }` object literal (as
|
|
33
|
+
* opposed to an array, a Date/Map/Set/RegExp/Buffer, or some other
|
|
34
|
+
* class instance). Used by `stripInternalFields` to decide whether to
|
|
35
|
+
* recurse: recursing into a class instance would destroy it by copying
|
|
36
|
+
* its enumerable own properties into a fresh `{}` (a `Date` has none,
|
|
37
|
+
* so it becomes `{}` — a subtle data-loss bug the engine has to avoid
|
|
38
|
+
* now that the tagged serializer can surface real class instances).
|
|
39
|
+
*/
|
|
40
|
+
const isPlainObject = (value) => {
|
|
41
|
+
if (value === null || typeof value !== 'object')
|
|
42
|
+
return false;
|
|
43
|
+
const proto = Object.getPrototypeOf(value);
|
|
44
|
+
return proto === Object.prototype || proto === null;
|
|
45
|
+
};
|
|
46
|
+
const stripInternalFields = (obj) => {
|
|
47
|
+
if (!obj || typeof obj !== 'object')
|
|
48
|
+
return obj;
|
|
49
|
+
if (Array.isArray(obj)) {
|
|
50
|
+
return obj.map((item) => (0, exports.stripInternalFields)(item));
|
|
51
|
+
}
|
|
52
|
+
// Preserve class instances (Date, Map, Set, RegExp, Buffer, or any
|
|
53
|
+
// user-supplied class) verbatim. Recursing into them would call
|
|
54
|
+
// `Object.entries` which returns `[]` for most built-ins, collapsing
|
|
55
|
+
// them into an empty `{}` and silently losing the value.
|
|
56
|
+
if (!isPlainObject(obj))
|
|
57
|
+
return obj;
|
|
58
|
+
const result = {};
|
|
59
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
60
|
+
if (!(0, exports.isInternalField)(key)) {
|
|
61
|
+
// Only recurse into plain objects / arrays. Class instances are
|
|
62
|
+
// assigned by reference so their identity and type survive.
|
|
63
|
+
result[key] =
|
|
64
|
+
value &&
|
|
65
|
+
typeof value === 'object' &&
|
|
66
|
+
(Array.isArray(value) || isPlainObject(value))
|
|
67
|
+
? (0, exports.stripInternalFields)(value)
|
|
68
|
+
: value;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
};
|
|
73
|
+
exports.stripInternalFields = stripInternalFields;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operation result and context types for Redis Schema Engine
|
|
3
|
+
*/
|
|
4
|
+
export interface WriteResult {
|
|
5
|
+
success: boolean;
|
|
6
|
+
keys: string[];
|
|
7
|
+
operationId: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
metadata?: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
export interface ListWriteResult {
|
|
12
|
+
success: boolean;
|
|
13
|
+
key: string;
|
|
14
|
+
ids: any[];
|
|
15
|
+
operationId: string;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
}
|
|
18
|
+
export interface BulkWriteOperation {
|
|
19
|
+
entityType: string;
|
|
20
|
+
id: string | number;
|
|
21
|
+
data: any;
|
|
22
|
+
}
|
|
23
|
+
export interface BulkDeleteOperation {
|
|
24
|
+
entityType: string;
|
|
25
|
+
id: string | number;
|
|
26
|
+
}
|
|
27
|
+
export interface BulkWriteResult {
|
|
28
|
+
success: boolean;
|
|
29
|
+
totalOperations: number;
|
|
30
|
+
successfulOperations: number;
|
|
31
|
+
failedOperations: BulkOperationFailure[];
|
|
32
|
+
operationId: string;
|
|
33
|
+
timestamp: number;
|
|
34
|
+
}
|
|
35
|
+
export interface BulkDeleteResult {
|
|
36
|
+
success: boolean;
|
|
37
|
+
totalOperations: number;
|
|
38
|
+
successfulOperations: number;
|
|
39
|
+
failedOperations: BulkOperationFailure[];
|
|
40
|
+
operationId: string;
|
|
41
|
+
timestamp: number;
|
|
42
|
+
}
|
|
43
|
+
export interface BulkOperationFailure {
|
|
44
|
+
operation: BulkWriteOperation | BulkDeleteOperation;
|
|
45
|
+
error: string;
|
|
46
|
+
index: number;
|
|
47
|
+
}
|
|
48
|
+
export interface CacheMetrics {
|
|
49
|
+
cacheHits: number;
|
|
50
|
+
cacheMisses: number;
|
|
51
|
+
hitRate: number;
|
|
52
|
+
totalOperations: number;
|
|
53
|
+
avgResponseTime: number;
|
|
54
|
+
memoryUsage: number;
|
|
55
|
+
activeConnections: number;
|
|
56
|
+
failedOperations: number;
|
|
57
|
+
lastUpdated: number;
|
|
58
|
+
}
|
|
59
|
+
export interface HealthStatus {
|
|
60
|
+
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
61
|
+
redis: {
|
|
62
|
+
connected: boolean;
|
|
63
|
+
latency: number;
|
|
64
|
+
memoryUsage: number;
|
|
65
|
+
};
|
|
66
|
+
engine: {
|
|
67
|
+
activeOperations: number;
|
|
68
|
+
memoryUsage: number;
|
|
69
|
+
errorRate: number;
|
|
70
|
+
};
|
|
71
|
+
timestamp: number;
|
|
72
|
+
}
|
|
73
|
+
export interface HydrationOptions {
|
|
74
|
+
maxDepth?: number;
|
|
75
|
+
selectiveFields?: string[];
|
|
76
|
+
excludeRelations?: string[];
|
|
77
|
+
memoryLimit?: number;
|
|
78
|
+
}
|
|
79
|
+
export interface WriteContext {
|
|
80
|
+
entityType: string;
|
|
81
|
+
id: string | number;
|
|
82
|
+
data: any;
|
|
83
|
+
options?: any;
|
|
84
|
+
requestId: string;
|
|
85
|
+
timestamp: number;
|
|
86
|
+
}
|
|
87
|
+
export interface ReadContext {
|
|
88
|
+
entityType: string;
|
|
89
|
+
id: string | number;
|
|
90
|
+
options?: HydrationOptions;
|
|
91
|
+
requestId: string;
|
|
92
|
+
timestamp: number;
|
|
93
|
+
}
|
|
94
|
+
export interface OperationContext {
|
|
95
|
+
operation: string;
|
|
96
|
+
entityType?: string;
|
|
97
|
+
id?: string | number;
|
|
98
|
+
requestId: string;
|
|
99
|
+
timestamp: number;
|
|
100
|
+
metadata?: Record<string, any>;
|
|
101
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core schema type definitions for Redis Schema Engine
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Recognized field types for documentation / schema validation.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: field types are **advisory** — the engine does NOT coerce
|
|
8
|
+
* or transform values based on them. They exist so schemas are
|
|
9
|
+
* self-documenting and so obvious typos (e.g. `type: 'stirng'`) are
|
|
10
|
+
* caught at registration time. Actual type preservation across cache
|
|
11
|
+
* round-trips is handled by the configured `Serializer`.
|
|
12
|
+
*
|
|
13
|
+
* The first-class JSON types (`string`, `number`, `boolean`, `object`,
|
|
14
|
+
* `array`) round-trip through raw JSON unchanged. The extended types
|
|
15
|
+
* (`date`, `bigint`, `map`, `set`, `regexp`, `buffer`) require the
|
|
16
|
+
* default `TAGGED_SERIALIZER` (enabled by default) to round-trip
|
|
17
|
+
* losslessly; with `JSON_SERIALIZER` they degrade per the compatibility
|
|
18
|
+
* table in `USAGE.md`.
|
|
19
|
+
*/
|
|
20
|
+
export type FieldType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'date' | 'bigint' | 'map' | 'set' | 'regexp' | 'buffer';
|
|
21
|
+
export type RelationKind = 'one' | 'many';
|
|
22
|
+
export interface FieldDefinition {
|
|
23
|
+
type: FieldType;
|
|
24
|
+
required?: boolean;
|
|
25
|
+
validation?: (value: any) => boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface RelationDefinition {
|
|
28
|
+
type: string;
|
|
29
|
+
kind: RelationKind;
|
|
30
|
+
cascade?: boolean;
|
|
31
|
+
lazy?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface EntitySchema {
|
|
34
|
+
type: 'entity';
|
|
35
|
+
id: string;
|
|
36
|
+
key: (...args: any[]) => string;
|
|
37
|
+
fields?: Record<string, FieldDefinition>;
|
|
38
|
+
relations?: Record<string, RelationDefinition>;
|
|
39
|
+
ttl?: number;
|
|
40
|
+
version?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ListSchema {
|
|
43
|
+
type: 'list';
|
|
44
|
+
entityType: string;
|
|
45
|
+
key: (...args: any[]) => string;
|
|
46
|
+
idField: string;
|
|
47
|
+
ttl?: number;
|
|
48
|
+
version?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* ZSET-backed list schema. Use this instead of `ListSchema` for any list
|
|
52
|
+
* that may grow beyond a few hundred entries, needs paginated reads, needs
|
|
53
|
+
* efficient atomic add/remove, or needs to be sorted by something other
|
|
54
|
+
* than insertion order.
|
|
55
|
+
*
|
|
56
|
+
* Storage: a Redis sorted set (`ZSET`) where each member is the entity id
|
|
57
|
+
* and each score is either the value of `scoreField` on the entity (when
|
|
58
|
+
* specified and numeric) or the insertion timestamp in milliseconds.
|
|
59
|
+
*
|
|
60
|
+
* Why a separate type: keeping `ListSchema` and `IndexedListSchema`
|
|
61
|
+
* distinct lets existing code that uses the JSON-array list type keep
|
|
62
|
+
* working with no migration. New high-traffic lists adopt the indexed
|
|
63
|
+
* type, old ones stay where they are.
|
|
64
|
+
*/
|
|
65
|
+
export interface IndexedListSchema {
|
|
66
|
+
type: 'indexedList';
|
|
67
|
+
entityType: string;
|
|
68
|
+
key: (...args: any[]) => string;
|
|
69
|
+
idField: string;
|
|
70
|
+
/**
|
|
71
|
+
* Name of the entity field used as the ZSET score. Must resolve to a
|
|
72
|
+
* finite number on the entity (or a value that `Number()` converts to
|
|
73
|
+
* one — e.g. an ISO timestamp string, a numeric string, a Date). When
|
|
74
|
+
* omitted, members are scored by `Date.now()` at insertion time, which
|
|
75
|
+
* yields newest-first feeds when read in reverse order.
|
|
76
|
+
*/
|
|
77
|
+
scoreField?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Optional cap on list size. When set, every atomic insert trims the
|
|
80
|
+
* list back to at most `maxSize` entries by ZREMRANGEBYRANK, dropping
|
|
81
|
+
* the lowest-scored members. Use this for "latest N" feeds where older
|
|
82
|
+
* items can be discarded.
|
|
83
|
+
*/
|
|
84
|
+
maxSize?: number;
|
|
85
|
+
/**
|
|
86
|
+
* When true, every insert also records a back-index entry mapping
|
|
87
|
+
* `entityId -> listKey` so `invalidateEntity` can cascade through and
|
|
88
|
+
* atomically remove the id from every tracked list it appears in.
|
|
89
|
+
* Default false (no overhead) since membership tracking only matters
|
|
90
|
+
* for entities that participate in many lists.
|
|
91
|
+
*/
|
|
92
|
+
trackMembership?: boolean;
|
|
93
|
+
ttl?: number;
|
|
94
|
+
version?: string;
|
|
95
|
+
}
|
|
96
|
+
export type SchemaDefinition = EntitySchema | ListSchema | IndexedListSchema;
|
|
97
|
+
export interface Schema {
|
|
98
|
+
[key: string]: SchemaDefinition;
|
|
99
|
+
}
|
|
100
|
+
export interface SchemaValidationResult {
|
|
101
|
+
isValid: boolean;
|
|
102
|
+
errors: string[];
|
|
103
|
+
warnings: string[];
|
|
104
|
+
}
|