1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-06 02:15:48 +03:00

V5 bringing RESP3, Sentinel and TypeMapping to node-redis

RESP3 Support
   - Some commands responses in RESP3 aren't stable yet and therefore return an "untyped" ReplyUnion.
 
Sentinel

TypeMapping

Correctly types Multi commands

Note: some API changes to be further documented in v4-to-v5.md
This commit is contained in:
Shaya Potter
2024-10-15 17:46:52 +03:00
committed by GitHub
parent 2fc79bdfb3
commit b2d35c5286
1174 changed files with 45931 additions and 36274 deletions

View File

@@ -1,55 +1,102 @@
import { RedisCommandArgument, RedisCommandArguments } from '@redis/client/dist/lib/commands/index';
import { pushQueryArguments, QueryOptionsBackwardCompatible } from '.';
import { RedisArgument, ArrayReply, BlobStringReply, NumberReply, NullReply, TuplesReply, UnwrapReply, Command } from '@redis/client/dist/lib/RESP/types';
export const FIRST_KEY_INDEX = 1;
type Headers = ArrayReply<BlobStringReply>;
export function transformArguments(
graph: RedisCommandArgument,
query: RedisCommandArgument,
options?: QueryOptionsBackwardCompatible,
compact?: boolean
): RedisCommandArguments {
return pushQueryArguments(
['GRAPH.QUERY'],
graph,
query,
options,
compact
);
}
type Data = ArrayReply<BlobStringReply | NumberReply | NullReply | Data>;
type Headers = Array<string>;
type Metadata = ArrayReply<BlobStringReply>;
type Data = Array<string | number | null | Data>;
type Metadata = Array<string>;
type QueryRawReply = [
headers: Headers,
data: Data,
metadata: Metadata
type QueryRawReply = TuplesReply<[
headers: Headers,
data: Data,
metadata: Metadata
] | [
metadata: Metadata
];
metadata: Metadata
]>;
export type QueryReply = {
headers: undefined;
data: undefined;
metadata: Metadata;
} | {
headers: Headers;
data: Data;
metadata: Metadata;
type QueryParam = null | string | number | boolean | QueryParams | Array<QueryParam>;
type QueryParams = {
[key: string]: QueryParam;
};
export function transformReply(reply: QueryRawReply): QueryReply {
return reply.length === 1 ? {
headers: undefined,
data: undefined,
metadata: reply[0]
} : {
headers: reply[0],
data: reply[1],
metadata: reply[2]
};
export interface QueryOptions {
params?: QueryParams;
TIMEOUT?: number;
}
export function transformQueryArguments(
command: RedisArgument,
graph: RedisArgument,
query: RedisArgument,
options?: QueryOptions,
compact?: boolean
) {
const args = [
command,
graph,
options?.params ?
`CYPHER ${queryParamsToString(options.params)} ${query}` :
query
];
if (options?.TIMEOUT !== undefined) {
args.push('TIMEOUT', options.TIMEOUT.toString());
}
if (compact) {
args.push('--compact');
}
return args;
}
function queryParamsToString(params: QueryParams) {
return Object.entries(params)
.map(([key, value]) => `${key}=${queryParamToString(value)}`)
.join(' ');
}
function queryParamToString(param: QueryParam): string {
if (param === null) {
return 'null';
}
switch (typeof param) {
case 'string':
return `"${param.replace(/["\\]/g, '\\$&')}"`;
case 'number':
case 'boolean':
return param.toString();
}
if (Array.isArray(param)) {
return `[${param.map(queryParamToString).join(',')}]`;
} else if (typeof param === 'object') {
const body = [];
for (const [key, value] of Object.entries(param)) {
body.push(`${key}:${queryParamToString(value)}`);
}
return `{${body.join(',')}}`;
} else {
throw new TypeError(`Unexpected param type ${typeof param} ${param}`)
}
}
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: false,
transformArguments: transformQueryArguments.bind(undefined, 'GRAPH.QUERY'),
transformReply(reply: UnwrapReply<QueryRawReply>) {
return reply.length === 1 ? {
headers: undefined,
data: undefined,
metadata: reply[0]
} : {
headers: reply[0],
data: reply[1],
metadata: reply[2]
};
}
} as const satisfies Command;