diff --git a/.github/release-drafter/graph-config.yml b/.github/release-drafter/graph-config.yml deleted file mode 100644 index 88d76b78b55..00000000000 --- a/.github/release-drafter/graph-config.yml +++ /dev/null @@ -1,49 +0,0 @@ -name-template: 'graph@$NEXT_PATCH_VERSION' -tag-template: 'graph@$NEXT_PATCH_VERSION' -autolabeler: - - label: 'chore' - files: - - '*.md' - - '.github/*' - - label: 'bug' - branch: - - '/bug-.+' - - label: 'chore' - branch: - - '/chore-.+' - - label: 'feature' - branch: - - '/feature-.+' -categories: - - title: 'Breaking Changes' - labels: - - 'breakingchange' - - title: '🚀 New Features' - labels: - - 'feature' - - 'enhancement' - - title: '🐛 Bug Fixes' - labels: - - 'fix' - - 'bugfix' - - 'bug' - - title: '🧰 Maintenance' - label: - - 'chore' - - 'maintenance' - - 'documentation' - - 'docs' -change-template: '- $TITLE (#$NUMBER)' -include-paths: - - 'packages/graph' -exclude-labels: - - 'skip-changelog' -template: | - ## Changes - - $CHANGES - - ## Contributors - We'd like to thank all the contributors who worked on this release! - - $CONTRIBUTORS diff --git a/.github/workflows/release-drafter-graph.yml b/.github/workflows/release-drafter-graph.yml deleted file mode 100644 index 4d664e5f19e..00000000000 --- a/.github/workflows/release-drafter-graph.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Release Drafter - -on: - push: - # branches to consider in the event; optional, defaults to all - branches: - - master - -jobs: - - update_release_draft: - - permissions: - contents: write - pull-requests: write - runs-on: ubuntu-latest - steps: - # Drafts your next Release notes as Pull Requests are merged into "master" - - uses: release-drafter/release-drafter@v5 - with: - # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml - config-name: release-drafter/graph-config.yml - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 457f3d2c2cf..8e7e3845256 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ npm install redis | [`redis`](./packages/redis) | The client with all the ["redis-stack"](https://github.com/redis-stack/redis-stack) modules | | [`@redis/client`](./packages/client) | The base clients (i.e `RedisClient`, `RedisCluster`, etc.) | | [`@redis/bloom`](./packages/bloom) | [Redis Bloom](https://redis.io/docs/data-types/probabilistic/) commands | -| [`@redis/graph`](./packages/graph) | [Redis Graph](https://redis.io/docs/data-types/probabilistic/) commands | | [`@redis/json`](./packages/json) | [Redis JSON](https://redis.io/docs/data-types/json/) commands | | [`@redis/search`](./packages/search) | [RediSearch](https://redis.io/docs/interact/search-and-query/) commands | | [`@redis/time-series`](./packages/time-series) | [Redis Time-Series](https://redis.io/docs/data-types/timeseries/) commands | diff --git a/docs/v4-to-v5.md b/docs/v4-to-v5.md index 95c2230ce23..57ad3f9bbf6 100644 --- a/docs/v4-to-v5.md +++ b/docs/v4-to-v5.md @@ -211,10 +211,6 @@ In v5, any unwritten commands (in the same pipeline) will be discarded. - `TOPK.QUERY`: `Array` -> `Array` -### Graph - -- `GRAPH.SLOWLOG`: `timestamp` has been changed from `Date` to `number` - ### JSON - `JSON.ARRINDEX`: `start` and `end` arguments moved to `{ range: { start: number; end: number; }; }` [^future-proofing] diff --git a/packages/graph/.nycrc.json b/packages/graph/.nycrc.json deleted file mode 100644 index 367a89ad32c..00000000000 --- a/packages/graph/.nycrc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "@istanbuljs/nyc-config-typescript", - "exclude": ["dist", "**/*.spec.ts", "lib/test-utils.ts"] -} diff --git a/packages/graph/.release-it.json b/packages/graph/.release-it.json deleted file mode 100644 index 7797dd0b4dd..00000000000 --- a/packages/graph/.release-it.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "git": { - "tagName": "graph@${version}", - "commitMessage": "Release ${tagName}", - "tagAnnotation": "Release ${tagName}" - }, - "npm": { - "versionArgs": ["--workspaces-update=false"], - "publishArgs": ["--access", "public"] - } -} diff --git a/packages/graph/README.md b/packages/graph/README.md deleted file mode 100644 index 4c712bfd820..00000000000 --- a/packages/graph/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# @redis/graph - -Example usage: -```javascript -import { createClient, Graph } from 'redis'; - -const client = createClient(); -client.on('error', (err) => console.log('Redis Client Error', err)); - -await client.connect(); - -const graph = new Graph(client, 'graph'); - -await graph.query( - 'CREATE (:Rider { name: $riderName })-[:rides]->(:Team { name: $teamName })', - { - params: { - riderName: 'Buzz Aldrin', - teamName: 'Apollo' - } - } -); - -const result = await graph.roQuery( - 'MATCH (r:Rider)-[:rides]->(t:Team { name: $name }) RETURN r.name AS name', - { - params: { - name: 'Apollo' - } - } -); - -console.log(result.data); // [{ name: 'Buzz Aldrin' }] -``` diff --git a/packages/graph/lib/commands/CONFIG_GET.spec.ts b/packages/graph/lib/commands/CONFIG_GET.spec.ts deleted file mode 100644 index 9a427867c63..00000000000 --- a/packages/graph/lib/commands/CONFIG_GET.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { strict as assert } from 'node:assert'; -import testUtils, { GLOBAL } from '../test-utils'; -import CONFIG_GET from './CONFIG_GET'; -import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; - -describe('GRAPH.CONFIG GET', () => { - it('transformArguments', () => { - assert.deepEqual( - parseArgs(CONFIG_GET, 'TIMEOUT'), - ['GRAPH.CONFIG', 'GET', 'TIMEOUT'] - ); - }); - - testUtils.testWithClient('client.graph.configGet', async client => { - assert.deepEqual( - await client.graph.configGet('TIMEOUT'), - [ - 'TIMEOUT', - 0 - ] - ); - }, GLOBAL.SERVERS.OPEN); -}); diff --git a/packages/graph/lib/commands/CONFIG_GET.ts b/packages/graph/lib/commands/CONFIG_GET.ts deleted file mode 100644 index 4986dfc1816..00000000000 --- a/packages/graph/lib/commands/CONFIG_GET.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { RedisArgument, TuplesReply, ArrayReply, BlobStringReply, NumberReply, Command } from '@redis/client/dist/lib/RESP/types'; -import { CommandParser } from '@redis/client/dist/lib/client/parser'; - -type ConfigItemReply = TuplesReply<[ - configKey: BlobStringReply, - value: NumberReply -]>; - -export default { - NOT_KEYED_COMMAND: true, - IS_READ_ONLY: true, - parseCommand(parser: CommandParser, configKey: RedisArgument) { - parser.push('GRAPH.CONFIG', 'GET', configKey); - }, - transformReply: undefined as unknown as () => ConfigItemReply | ArrayReply -} as const satisfies Command; diff --git a/packages/graph/lib/commands/CONFIG_SET.spec.ts b/packages/graph/lib/commands/CONFIG_SET.spec.ts deleted file mode 100644 index ae6e296699c..00000000000 --- a/packages/graph/lib/commands/CONFIG_SET.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { strict as assert } from 'node:assert'; -import testUtils, { GLOBAL } from '../test-utils'; -import CONFIG_SET from './CONFIG_SET'; -import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; - -describe('GRAPH.CONFIG SET', () => { - it('transformArguments', () => { - assert.deepEqual( - parseArgs(CONFIG_SET, 'TIMEOUT', 0), - ['GRAPH.CONFIG', 'SET', 'TIMEOUT', '0'] - ); - }); - - testUtils.testWithClient('client.graph.configSet', async client => { - assert.equal( - await client.graph.configSet('TIMEOUT', 0), - 'OK' - ); - }, GLOBAL.SERVERS.OPEN); -}); diff --git a/packages/graph/lib/commands/CONFIG_SET.ts b/packages/graph/lib/commands/CONFIG_SET.ts deleted file mode 100644 index 63f604402ec..00000000000 --- a/packages/graph/lib/commands/CONFIG_SET.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types'; -import { CommandParser } from '@redis/client/dist/lib/client/parser'; - -export default { - NOT_KEYED_COMMAND: true, - IS_READ_ONLY: false, - parseCommand(parser: CommandParser, configKey: RedisArgument, value: number) { - parser.push('GRAPH.CONFIG', 'SET', configKey, value.toString()); - }, - transformReply: undefined as unknown as () => SimpleStringReply<'OK'> -} as const satisfies Command; diff --git a/packages/graph/lib/commands/DELETE.spec.ts b/packages/graph/lib/commands/DELETE.spec.ts deleted file mode 100644 index 5977c646307..00000000000 --- a/packages/graph/lib/commands/DELETE.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { strict as assert } from 'node:assert'; -import testUtils, { GLOBAL } from '../test-utils'; -import DELETE from './DELETE'; -import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; - -describe('GRAPH.DELETE', () => { - it('transformArguments', () => { - assert.deepEqual( - parseArgs(DELETE, 'key'), - ['GRAPH.DELETE', 'key'] - ); - }); - - testUtils.testWithClient('client.graph.delete', async client => { - const [, reply] = await Promise.all([ - client.graph.query('key', 'RETURN 1'), - client.graph.delete('key') - ]); - - assert.equal(typeof reply, 'string'); - }, GLOBAL.SERVERS.OPEN); -}); diff --git a/packages/graph/lib/commands/DELETE.ts b/packages/graph/lib/commands/DELETE.ts deleted file mode 100644 index 43af38d6fb0..00000000000 --- a/packages/graph/lib/commands/DELETE.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RedisArgument, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types'; -import { CommandParser } from '@redis/client/dist/lib/client/parser'; - -export default { - IS_READ_ONLY: false, - parseCommand(parser: CommandParser, key: RedisArgument) { - parser.push('GRAPH.DELETE'); - parser.pushKey(key); - }, - transformReply: undefined as unknown as () => BlobStringReply -} as const satisfies Command; diff --git a/packages/graph/lib/commands/EXPLAIN.spec.ts b/packages/graph/lib/commands/EXPLAIN.spec.ts deleted file mode 100644 index 28f30cd17b3..00000000000 --- a/packages/graph/lib/commands/EXPLAIN.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { strict as assert } from 'node:assert'; -import testUtils, { GLOBAL } from '../test-utils'; -import EXPLAIN from './EXPLAIN'; -import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; - -describe('GRAPH.EXPLAIN', () => { - it('transformArguments', () => { - assert.deepEqual( - parseArgs(EXPLAIN, 'key', 'RETURN 0'), - ['GRAPH.EXPLAIN', 'key', 'RETURN 0'] - ); - }); - - testUtils.testWithClient('client.graph.explain', async client => { - const [, reply] = await Promise.all([ - client.graph.query('key', 'RETURN 0'), // make sure to create a graph first - client.graph.explain('key', 'RETURN 0') - ]); - assert.ok(Array.isArray(reply)); - for (const item of reply) { - assert.equal(typeof item, 'string'); - } - }, GLOBAL.SERVERS.OPEN); -}); diff --git a/packages/graph/lib/commands/EXPLAIN.ts b/packages/graph/lib/commands/EXPLAIN.ts deleted file mode 100644 index a9af7e73a20..00000000000 --- a/packages/graph/lib/commands/EXPLAIN.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { RedisArgument, ArrayReply, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types'; -import { CommandParser } from '@redis/client/dist/lib/client/parser'; - -export default { - IS_READ_ONLY: true, - parseCommand(parser: CommandParser, key: RedisArgument, query: RedisArgument) { - parser.push('GRAPH.EXPLAIN'); - parser.pushKey(key); - parser.push(query); - }, - transformReply: undefined as unknown as () => ArrayReply -} as const satisfies Command; diff --git a/packages/graph/lib/commands/LIST.spec.ts b/packages/graph/lib/commands/LIST.spec.ts deleted file mode 100644 index 19f18a0e30b..00000000000 --- a/packages/graph/lib/commands/LIST.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { strict as assert } from 'node:assert'; -import testUtils, { GLOBAL } from '../test-utils'; -import LIST from './LIST'; -import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; - -describe('GRAPH.LIST', () => { - it('transformArguments', () => { - assert.deepEqual( - parseArgs(LIST), - ['GRAPH.LIST'] - ); - }); - - testUtils.testWithClient('client.graph.list', async client => { - assert.deepEqual( - await client.graph.list(), - [] - ); - }, GLOBAL.SERVERS.OPEN); -}); diff --git a/packages/graph/lib/commands/LIST.ts b/packages/graph/lib/commands/LIST.ts deleted file mode 100644 index 70fa8bda812..00000000000 --- a/packages/graph/lib/commands/LIST.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ArrayReply, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types'; -import { CommandParser } from '@redis/client/dist/lib/client/parser'; - -export default { - NOT_KEYED_COMMAND: true, - IS_READ_ONLY: true, - parseCommand(parser: CommandParser) { - parser.push('GRAPH.LIST'); - }, - transformReply: undefined as unknown as () => ArrayReply -} as const satisfies Command; diff --git a/packages/graph/lib/commands/PROFILE.spec.ts b/packages/graph/lib/commands/PROFILE.spec.ts deleted file mode 100644 index 7f16fd3ba58..00000000000 --- a/packages/graph/lib/commands/PROFILE.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { strict as assert } from 'node:assert'; -import testUtils, { GLOBAL } from '../test-utils'; -import PROFILE from './PROFILE'; -import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; - -describe('GRAPH.PROFILE', () => { - it('transformArguments', () => { - assert.deepEqual( - parseArgs(PROFILE, 'key', 'RETURN 0'), - ['GRAPH.PROFILE', 'key', 'RETURN 0'] - ); - }); - - testUtils.testWithClient('client.graph.profile', async client => { - const reply = await client.graph.profile('key', 'RETURN 0'); - assert.ok(Array.isArray(reply)); - for (const item of reply) { - assert.equal(typeof item, 'string'); - } - }, GLOBAL.SERVERS.OPEN); -}); diff --git a/packages/graph/lib/commands/PROFILE.ts b/packages/graph/lib/commands/PROFILE.ts deleted file mode 100644 index c3229fb9a04..00000000000 --- a/packages/graph/lib/commands/PROFILE.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { RedisArgument, ArrayReply, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types'; -import { CommandParser } from '@redis/client/dist/lib/client/parser'; - -export default { - IS_READ_ONLY: true, - parseCommand(parser: CommandParser, key: RedisArgument, query: RedisArgument) { - parser.push('GRAPH.PROFILE'); - parser.pushKey(key); - parser.push(query); - }, - transformReply: undefined as unknown as () => ArrayReply -} as const satisfies Command; diff --git a/packages/graph/lib/commands/QUERY.spec.ts b/packages/graph/lib/commands/QUERY.spec.ts deleted file mode 100644 index 28c2189645b..00000000000 --- a/packages/graph/lib/commands/QUERY.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { strict as assert } from 'node:assert'; -import testUtils, { GLOBAL } from '../test-utils'; -import QUERY from './QUERY'; -import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; - -describe('GRAPH.QUERY', () => { - describe('transformArguments', () => { - it('simple', () => { - assert.deepEqual( - parseArgs(QUERY, 'key', 'query'), - ['GRAPH.QUERY', 'key', 'query'] - ); - }); - - describe('params', () => { - it('all types', () => { - assert.deepEqual( - parseArgs(QUERY, 'key', 'query', { - params: { - null: null, - string: '"\\', - number: 0, - boolean: false, - array: [0], - object: {a: 0} - } - }), - ['GRAPH.QUERY', 'key', 'CYPHER null=null string="\\"\\\\" number=0 boolean=false array=[0] object={a:0} query'] - ); - }); - - it('TypeError', () => { - assert.throws(() => { - parseArgs(QUERY, 'key', 'query', { - params: { - a: Symbol() - } - }) - }, TypeError); - }); - }); - - it('TIMEOUT', () => { - assert.deepEqual( - parseArgs(QUERY, 'key', 'query', { - TIMEOUT: 1 - }), - ['GRAPH.QUERY', 'key', 'query', 'TIMEOUT', '1'] - ); - }); - - it('compact', () => { - assert.deepEqual( - parseArgs(QUERY, 'key', 'query', undefined, true), - ['GRAPH.QUERY', 'key', 'query', '--compact'] - ); - }); - }); - - testUtils.testWithClient('client.graph.query', async client => { - const { data } = await client.graph.query('key', 'RETURN 0'); - assert.deepEqual(data, [[0]]); - }, GLOBAL.SERVERS.OPEN); -}); diff --git a/packages/graph/lib/commands/QUERY.ts b/packages/graph/lib/commands/QUERY.ts deleted file mode 100644 index 23ea24c5768..00000000000 --- a/packages/graph/lib/commands/QUERY.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { RedisArgument, ArrayReply, BlobStringReply, NumberReply, NullReply, TuplesReply, UnwrapReply, Command } from '@redis/client/dist/lib/RESP/types'; -import { CommandParser } from '@redis/client/dist/lib/client/parser'; - -type Headers = ArrayReply; - -type Data = ArrayReply; - -type Metadata = ArrayReply; - -type QueryRawReply = TuplesReply<[ - headers: Headers, - data: Data, - metadata: Metadata -] | [ - metadata: Metadata -]>; - -type QueryParam = null | string | number | boolean | QueryParams | Array; - -type QueryParams = { - [key: string]: QueryParam; -}; - -export interface QueryOptions { - params?: QueryParams; - TIMEOUT?: number; -} - -export function parseQueryArguments( - command: RedisArgument, - parser: CommandParser, - graph: RedisArgument, - query: RedisArgument, - options?: QueryOptions, - compact?: boolean -) { - parser.push(command); - parser.pushKey(graph); - const param = options?.params ? - `CYPHER ${queryParamsToString(options.params)} ${query}` : - query; - parser.push(param); - - if (options?.TIMEOUT !== undefined) { - parser.push('TIMEOUT', options.TIMEOUT.toString()); - } - - if (compact) { - parser.push('--compact'); - } -} - -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 { - IS_READ_ONLY: false, - parseCommand: parseQueryArguments.bind(undefined, 'GRAPH.QUERY'), - transformReply(reply: UnwrapReply) { - return reply.length === 1 ? { - headers: undefined, - data: undefined, - metadata: reply[0] - } : { - headers: reply[0], - data: reply[1], - metadata: reply[2] - }; - } -} as const satisfies Command; diff --git a/packages/graph/lib/commands/RO_QUERY.spec.ts b/packages/graph/lib/commands/RO_QUERY.spec.ts deleted file mode 100644 index fa9459cf642..00000000000 --- a/packages/graph/lib/commands/RO_QUERY.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { strict as assert } from 'node:assert'; -import testUtils, { GLOBAL } from '../test-utils'; -import RO_QUERY from './RO_QUERY'; -import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; - -describe('GRAPH.RO_QUERY', () => { - it('transformArguments', () => { - assert.deepEqual( - parseArgs(RO_QUERY, 'key', 'query'), - ['GRAPH.RO_QUERY', 'key', 'query'] - ); - }); - - testUtils.testWithClient('client.graph.roQuery', async client => { - const [, { data }] = await Promise.all([ - client.graph.query('key', 'RETURN 0'), // make sure to create a graph first - client.graph.roQuery('key', 'RETURN 0') - ]); - assert.deepEqual(data, [[0]]); - }, GLOBAL.SERVERS.OPEN); -}); \ No newline at end of file diff --git a/packages/graph/lib/commands/RO_QUERY.ts b/packages/graph/lib/commands/RO_QUERY.ts deleted file mode 100644 index f052877b99d..00000000000 --- a/packages/graph/lib/commands/RO_QUERY.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Command } from '@redis/client/dist/lib/RESP/types'; -import QUERY, { parseQueryArguments } from './QUERY'; - -export default { - IS_READ_ONLY: true, - parseCommand: parseQueryArguments.bind(undefined, 'GRAPH.RO_QUERY'), - transformReply: QUERY.transformReply -} as const satisfies Command; diff --git a/packages/graph/lib/commands/SLOWLOG.spec.ts b/packages/graph/lib/commands/SLOWLOG.spec.ts deleted file mode 100644 index b991d6e7b96..00000000000 --- a/packages/graph/lib/commands/SLOWLOG.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { strict as assert } from 'node:assert'; -import testUtils, { GLOBAL } from '../test-utils'; -import SLOWLOG from './SLOWLOG'; -import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; - -describe('GRAPH.SLOWLOG', () => { - it('transformArguments', () => { - assert.deepEqual( - parseArgs(SLOWLOG, 'key'), - ['GRAPH.SLOWLOG', 'key'] - ); - }); - - testUtils.testWithClient('client.graph.slowLog', async client => { - const [, reply] = await Promise.all([ - client.graph.query('key', 'RETURN 1'), - client.graph.slowLog('key') - ]); - assert.equal(reply.length, 1); - }, GLOBAL.SERVERS.OPEN); -}); diff --git a/packages/graph/lib/commands/SLOWLOG.ts b/packages/graph/lib/commands/SLOWLOG.ts deleted file mode 100644 index 4110335f922..00000000000 --- a/packages/graph/lib/commands/SLOWLOG.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { RedisArgument, ArrayReply, TuplesReply, BlobStringReply, UnwrapReply, Command } from '@redis/client/dist/lib/RESP/types'; -import { CommandParser } from '@redis/client/dist/lib/client/parser'; - -type SlowLogRawReply = ArrayReply>; - -export default { - IS_READ_ONLY: true, - parseCommand(parser: CommandParser, key: RedisArgument) { - parser.push('GRAPH.SLOWLOG'); - parser.pushKey(key); - }, - transformReply(reply: UnwrapReply) { - return reply.map(log => { - const [timestamp, command, query, took] = log as unknown as UnwrapReply; - return { - timestamp: Number(timestamp), - command, - query, - took: Number(took) - }; - }); - } -} as const satisfies Command; diff --git a/packages/graph/lib/commands/index.ts b/packages/graph/lib/commands/index.ts deleted file mode 100644 index e93356aa951..00000000000 --- a/packages/graph/lib/commands/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { RedisCommands } from '@redis/client/dist/lib/RESP/types'; -import CONFIG_GET from './CONFIG_GET'; -import CONFIG_SET from './CONFIG_SET';; -import DELETE from './DELETE'; -import EXPLAIN from './EXPLAIN'; -import LIST from './LIST'; -import PROFILE from './PROFILE'; -import QUERY from './QUERY'; -import RO_QUERY from './RO_QUERY'; -import SLOWLOG from './SLOWLOG'; - -export default { - CONFIG_GET, - configGet: CONFIG_GET, - CONFIG_SET, - configSet: CONFIG_SET, - DELETE, - delete: DELETE, - EXPLAIN, - explain: EXPLAIN, - LIST, - list: LIST, - PROFILE, - profile: PROFILE, - QUERY, - query: QUERY, - RO_QUERY, - roQuery: RO_QUERY, - SLOWLOG, - slowLog: SLOWLOG -} as const satisfies RedisCommands; diff --git a/packages/graph/lib/graph.spec.ts b/packages/graph/lib/graph.spec.ts deleted file mode 100644 index ab506c43a4b..00000000000 --- a/packages/graph/lib/graph.spec.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { strict as assert } from 'node:assert'; -import testUtils, { GLOBAL } from './test-utils'; -import Graph from './graph'; - -describe('Graph', () => { - testUtils.testWithClient('null', async client => { - const graph = new Graph(client as any, 'graph'), - { data } = await graph.query('RETURN null AS key'); - - assert.deepEqual( - data, - [{ key: null }] - ); - }, GLOBAL.SERVERS.OPEN); - - testUtils.testWithClient('string', async client => { - const graph = new Graph(client as any, 'graph'), - { data } = await graph.query('RETURN "string" AS key'); - - assert.deepEqual( - data, - [{ key: 'string' }] - ); - }, GLOBAL.SERVERS.OPEN); - - testUtils.testWithClient('integer', async client => { - const graph = new Graph(client as any, 'graph'), - { data } = await graph.query('RETURN 0 AS key'); - - assert.deepEqual( - data, - [{ key: 0 }] - ); - }, GLOBAL.SERVERS.OPEN); - - testUtils.testWithClient('boolean', async client => { - const graph = new Graph(client as any, 'graph'), - { data } = await graph.query('RETURN false AS key'); - - assert.deepEqual( - data, - [{ key: false }] - ); - }, GLOBAL.SERVERS.OPEN); - - testUtils.testWithClient('double', async client => { - const graph = new Graph(client as any, 'graph'), - { data } = await graph.query('RETURN 0.1 AS key'); - - assert.deepEqual( - data, - [{ key: 0.1 }] - ); - }, GLOBAL.SERVERS.OPEN); - - testUtils.testWithClient('array', async client => { - const graph = new Graph(client as any, 'graph'), - { data } = await graph.query('RETURN [null] AS key'); - - assert.deepEqual( - data, - [{ key: [null] }] - ); - }, GLOBAL.SERVERS.OPEN); - - testUtils.testWithClient('edge', async client => { - const graph = new Graph(client as any, 'graph'); - - // check with and without metadata cache - for (let i = 0; i < 2; i++) { - const { data } = await graph.query('CREATE ()-[edge :edge]->() RETURN edge'); - assert.ok(Array.isArray(data)); - assert.equal(data.length, 1); - assert.equal(typeof data[0].edge.id, 'number'); - assert.equal(data[0].edge.relationshipType, 'edge'); - assert.equal(typeof data[0].edge.sourceId, 'number'); - assert.equal(typeof data[0].edge.destinationId, 'number'); - assert.deepEqual(data[0].edge.properties, {}); - } - - }, GLOBAL.SERVERS.OPEN); - - testUtils.testWithClient('node', async client => { - const graph = new Graph(client as any, 'graph'); - - // check with and without metadata cache - for (let i = 0; i < 2; i++) { - const { data } = await graph.query('CREATE (node :node { p: 0 }) RETURN node'); - assert.ok(Array.isArray(data)); - assert.equal(data.length, 1); - assert.equal(typeof data[0].node.id, 'number'); - assert.deepEqual(data[0].node.labels, ['node']); - assert.deepEqual(data[0].node.properties, { p: 0 }); - } - }, GLOBAL.SERVERS.OPEN); - - testUtils.testWithClient('path', async client => { - const graph = new Graph(client as any, 'graph'), - [, { data }] = await Promise.all([ - await graph.query('CREATE ()-[:edge]->()'), - await graph.roQuery('MATCH path = ()-[:edge]->() RETURN path') - ]); - - assert.ok(Array.isArray(data)); - assert.equal(data.length, 1); - - assert.ok(Array.isArray(data[0].path.nodes)); - assert.equal(data[0].path.nodes.length, 2); - for (const node of data[0].path.nodes) { - assert.equal(typeof node.id, 'number'); - assert.deepEqual(node.labels, []); - assert.deepEqual(node.properties, {}); - } - - assert.ok(Array.isArray(data[0].path.edges)); - assert.equal(data[0].path.edges.length, 1); - for (const edge of data[0].path.edges) { - assert.equal(typeof edge.id, 'number'); - assert.equal(edge.relationshipType, 'edge'); - assert.equal(typeof edge.sourceId, 'number'); - assert.equal(typeof edge.destinationId, 'number'); - assert.deepEqual(edge.properties, {}); - } - }, GLOBAL.SERVERS.OPEN); - - testUtils.testWithClient('map', async client => { - const graph = new Graph(client as any, 'graph'), - { data } = await graph.query('RETURN { key: "value" } AS map'); - - assert.deepEqual(data, [{ - map: { - key: 'value' - } - }]); - }, GLOBAL.SERVERS.OPEN); - - testUtils.testWithClient('point', async client => { - const graph = new Graph(client as any, 'graph'), - { data } = await graph.query('RETURN point({ latitude: 1, longitude: 2 }) AS point'); - - assert.deepEqual(data, [{ - point: { - latitude: 1, - longitude: 2 - } - }]); - }, GLOBAL.SERVERS.OPEN); -}); diff --git a/packages/graph/lib/graph.ts b/packages/graph/lib/graph.ts deleted file mode 100644 index 348c8b7155f..00000000000 --- a/packages/graph/lib/graph.ts +++ /dev/null @@ -1,359 +0,0 @@ -import { RedisClientType } from '@redis/client'; -import { RedisArgument, RedisFunctions, RedisScripts } from '@redis/client/dist/lib/RESP/types'; -import QUERY, { QueryOptions } from './commands/QUERY'; - -interface GraphMetadata { - labels: Array; - relationshipTypes: Array; - propertyKeys: Array; -} - -// https://github.com/RedisGraph/RedisGraph/blob/master/src/resultset/formatters/resultset_formatter.h#L20 -enum GraphValueTypes { - UNKNOWN = 0, - NULL = 1, - STRING = 2, - INTEGER = 3, - BOOLEAN = 4, - DOUBLE = 5, - ARRAY = 6, - EDGE = 7, - NODE = 8, - PATH = 9, - MAP = 10, - POINT = 11 -} - -type GraphEntityRawProperties = Array<[ - id: number, - ...value: GraphRawValue -]>; - -type GraphEdgeRawValue = [ - GraphValueTypes.EDGE, - [ - id: number, - relationshipTypeId: number, - sourceId: number, - destinationId: number, - properties: GraphEntityRawProperties - ] -]; - -type GraphNodeRawValue = [ - GraphValueTypes.NODE, - [ - id: number, - labelIds: Array, - properties: GraphEntityRawProperties - ] -]; - -type GraphPathRawValue = [ - GraphValueTypes.PATH, - [ - nodes: [ - GraphValueTypes.ARRAY, - Array - ], - edges: [ - GraphValueTypes.ARRAY, - Array - ] - ] -]; - -type GraphMapRawValue = [ - GraphValueTypes.MAP, - Array -]; - -type GraphRawValue = [ - GraphValueTypes.NULL, - null -] | [ - GraphValueTypes.STRING, - string -] | [ - GraphValueTypes.INTEGER, - number -] | [ - GraphValueTypes.BOOLEAN, - string -] | [ - GraphValueTypes.DOUBLE, - string -] | [ - GraphValueTypes.ARRAY, - Array -] | GraphEdgeRawValue | GraphNodeRawValue | GraphPathRawValue | GraphMapRawValue | [ - GraphValueTypes.POINT, - [ - latitude: string, - longitude: string - ] -]; - -type GraphEntityProperties = Record; - -interface GraphEdge { - id: number; - relationshipType: string; - sourceId: number; - destinationId: number; - properties: GraphEntityProperties; -} - -interface GraphNode { - id: number; - labels: Array; - properties: GraphEntityProperties; -} - -interface GraphPath { - nodes: Array; - edges: Array; -} - -type GraphMap = { - [key: string]: GraphValue; -}; - -type GraphValue = null | string | number | boolean | Array | { -} | GraphEdge | GraphNode | GraphPath | GraphMap | { - latitude: string; - longitude: string; -}; - -export type GraphReply = { - data?: Array; -}; - -export type GraphClientType = RedisClientType<{ - graph: { - query: typeof QUERY, - roQuery: typeof import('./commands/RO_QUERY.js').default - } -}, RedisFunctions, RedisScripts>; - -export default class Graph { - #client: GraphClientType; - #name: string; - #metadata?: GraphMetadata; - - constructor( - client: GraphClientType, - name: string - ) { - this.#client = client; - this.#name = name; - } - - async query( - query: RedisArgument, - options?: QueryOptions - ) { - return this.#parseReply( - await this.#client.graph.query( - this.#name, - query, - options, - true - ) - ); - } - - async roQuery( - query: RedisArgument, - options?: QueryOptions - ) { - return this.#parseReply( - await this.#client.graph.roQuery( - this.#name, - query, - options, - true - ) - ); - } - - #setMetadataPromise?: Promise; - - #updateMetadata(): Promise { - this.#setMetadataPromise ??= this.#setMetadata() - .finally(() => this.#setMetadataPromise = undefined); - return this.#setMetadataPromise; - } - - // DO NOT use directly, use #updateMetadata instead - async #setMetadata(): Promise { - const [labels, relationshipTypes, propertyKeys] = await Promise.all([ - this.#client.graph.roQuery(this.#name, 'CALL db.labels()'), - this.#client.graph.roQuery(this.#name, 'CALL db.relationshipTypes()'), - this.#client.graph.roQuery(this.#name, 'CALL db.propertyKeys()') - ]); - - this.#metadata = { - labels: this.#cleanMetadataArray(labels.data as Array<[string]>), - relationshipTypes: this.#cleanMetadataArray(relationshipTypes.data as Array<[string]>), - propertyKeys: this.#cleanMetadataArray(propertyKeys.data as Array<[string]>) - }; - - return this.#metadata; - } - - #cleanMetadataArray(arr: Array<[string]>): Array { - return arr.map(([value]) => value); - } - - #getMetadata( - key: T, - id: number - ): GraphMetadata[T][number] | Promise { - return this.#metadata?.[key][id] ?? this.#getMetadataAsync(key, id); - } - - // DO NOT use directly, use #getMetadata instead - async #getMetadataAsync( - key: T, - id: number - ): Promise { - const value = (await this.#updateMetadata())[key][id]; - if (value === undefined) throw new Error(`Cannot find value from ${key}[${id}]`); - return value; - } - - // TODO: reply type - async #parseReply(reply: any): Promise> { - if (!reply.data) return reply; - - const promises: Array> = [], - parsed = { - metadata: reply.metadata, - data: reply.data!.map((row: any) => { - const data: Record = {}; - for (let i = 0; i < row.length; i++) { - data[reply.headers[i][1]] = this.#parseValue(row[i], promises); - } - - return data as unknown as T; - }) - }; - - if (promises.length) await Promise.all(promises); - - return parsed; - } - - #parseValue([valueType, value]: GraphRawValue, promises: Array>): GraphValue { - switch (valueType) { - case GraphValueTypes.NULL: - return null; - - case GraphValueTypes.STRING: - case GraphValueTypes.INTEGER: - return value; - - case GraphValueTypes.BOOLEAN: - return value === 'true'; - - case GraphValueTypes.DOUBLE: - return parseFloat(value); - - case GraphValueTypes.ARRAY: - return value.map(x => this.#parseValue(x, promises)); - - case GraphValueTypes.EDGE: - return this.#parseEdge(value, promises); - - case GraphValueTypes.NODE: - return this.#parseNode(value, promises); - - case GraphValueTypes.PATH: - return { - nodes: value[0][1].map(([, node]) => this.#parseNode(node, promises)), - edges: value[1][1].map(([, edge]) => this.#parseEdge(edge, promises)) - }; - - case GraphValueTypes.MAP: - const map: GraphMap = {}; - for (let i = 0; i < value.length; i++) { - map[value[i++] as string] = this.#parseValue(value[i] as GraphRawValue, promises); - } - - return map; - - case GraphValueTypes.POINT: - return { - latitude: parseFloat(value[0]), - longitude: parseFloat(value[1]) - }; - - default: - throw new Error(`unknown scalar type: ${valueType}`); - } - } - - #parseEdge([ - id, - relationshipTypeId, - sourceId, - destinationId, - properties - ]: GraphEdgeRawValue[1], promises: Array>): GraphEdge { - const edge = { - id, - sourceId, - destinationId, - properties: this.#parseProperties(properties, promises) - } as GraphEdge; - - const relationshipType = this.#getMetadata('relationshipTypes', relationshipTypeId); - if (relationshipType instanceof Promise) { - promises.push( - relationshipType.then(value => edge.relationshipType = value) - ); - } else { - edge.relationshipType = relationshipType; - } - - return edge; - } - - #parseNode([ - id, - labelIds, - properties - ]: GraphNodeRawValue[1], promises: Array>): GraphNode { - const labels = new Array(labelIds.length); - for (let i = 0; i < labelIds.length; i++) { - const value = this.#getMetadata('labels', labelIds[i]); - if (value instanceof Promise) { - promises.push(value.then(value => labels[i] = value)); - } else { - labels[i] = value; - } - } - - return { - id, - labels, - properties: this.#parseProperties(properties, promises) - }; - } - - #parseProperties(raw: GraphEntityRawProperties, promises: Array>): GraphEntityProperties { - const parsed: GraphEntityProperties = {}; - for (const [id, type, value] of raw) { - const parsedValue = this.#parseValue([type, value] as GraphRawValue, promises), - key = this.#getMetadata('propertyKeys', id); - if (key instanceof Promise) { - promises.push(key.then(key => parsed[key] = parsedValue)); - } else { - parsed[key] = parsedValue; - } - } - - return parsed; - } -} diff --git a/packages/graph/lib/index.ts b/packages/graph/lib/index.ts deleted file mode 100644 index e9f15ab1fd9..00000000000 --- a/packages/graph/lib/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './commands'; -export { default as Graph } from './graph'; diff --git a/packages/graph/lib/test-utils.ts b/packages/graph/lib/test-utils.ts deleted file mode 100644 index 16c44582061..00000000000 --- a/packages/graph/lib/test-utils.ts +++ /dev/null @@ -1,22 +0,0 @@ -import TestUtils from '@redis/test-utils'; -import RedisGraph from '.'; - - -export default TestUtils.createFromConfig({ - dockerImageName: 'redislabs/client-libs-test', - dockerImageVersionArgument: 'redis-version', - defaultDockerVersion: '8.0-M04-pre' -}); - -export const GLOBAL = { - SERVERS: { - OPEN: { - serverArguments: [], - clientOptions: { - modules: { - graph: RedisGraph - } - } - } - } -}; diff --git a/packages/graph/package.json b/packages/graph/package.json deleted file mode 100644 index 1ea08401f1b..00000000000 --- a/packages/graph/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@redis/graph", - "version": "5.0.0-next.6", - "license": "MIT", - "main": "./dist/lib/index.js", - "types": "./dist/lib/index.d.ts", - "files": [ - "dist/", - "!dist/tsconfig.tsbuildinfo" - ], - "scripts": { - "test-disable": "nyc -r text-summary -r lcov mocha -r tsx './lib/**/*.spec.ts'" - }, - "peerDependencies": { - "@redis/client": "^5.0.0-next.6" - }, - "devDependencies": { - "@redis/test-utils": "*" - }, - "engines": { - "node": ">= 18" - }, - "repository": { - "type": "git", - "url": "git://github.com/redis/node-redis.git" - }, - "bugs": { - "url": "https://github.com/redis/node-redis/issues" - }, - "homepage": "https://github.com/redis/node-redis/tree/master/packages/graph", - "keywords": [ - "redis", - "RedisGraph" - ] -} diff --git a/packages/graph/tsconfig.json b/packages/graph/tsconfig.json deleted file mode 100644 index 9d17cb63371..00000000000 --- a/packages/graph/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./dist" - }, - "include": [ - "./lib/**/*.ts" - ], - "exclude": [ - "./lib/test-utils.ts", - "./lib/**/*.spec.ts" - ], - "typedocOptions": { - "entryPoints": [ - "./lib" - ], - "entryPointStrategy": "expand", - "out": "../../documentation/graph" - } -} diff --git a/packages/redis/index.ts b/packages/redis/index.ts index 572b45b707b..73477363d3b 100644 --- a/packages/redis/index.ts +++ b/packages/redis/index.ts @@ -15,21 +15,18 @@ import { createSentinel as genericCreateSentinel } from '@redis/client'; import RedisBloomModules from '@redis/bloom'; -import RedisGraph from '@redis/graph'; import RedisJSON from '@redis/json'; import RediSearch from '@redis/search'; import RedisTimeSeries from '@redis/time-series'; // export * from '@redis/client'; // export * from '@redis/bloom'; -// export * from '@redis/graph'; // export * from '@redis/json'; // export * from '@redis/search'; // export * from '@redis/time-series'; const modules = { ...RedisBloomModules, - graph: RedisGraph, json: RedisJSON, ft: RediSearch, ts: RedisTimeSeries diff --git a/packages/redis/package.json b/packages/redis/package.json index 5069d936ba8..c9719370e88 100644 --- a/packages/redis/package.json +++ b/packages/redis/package.json @@ -12,7 +12,6 @@ "dependencies": { "@redis/bloom": "5.0.0-next.6", "@redis/client": "5.0.0-next.6", - "@redis/graph": "5.0.0-next.6", "@redis/json": "5.0.0-next.6", "@redis/search": "5.0.0-next.6", "@redis/time-series": "5.0.0-next.6" diff --git a/tsconfig.json b/tsconfig.json index 8f43ab41d22..180b3fc2ba7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,9 +10,6 @@ { "path": "./packages/bloom" }, - { - "path": "./packages/graph" - }, { "path": "./packages/json" },