diff --git a/.github/workflows/doctests.yml b/.github/workflows/doctests.yml new file mode 100644 index 00000000000..a85d116154b --- /dev/null +++ b/.github/workflows/doctests.yml @@ -0,0 +1,41 @@ +name: Documentation Tests + +on: + push: + pull_request: + + workflow_dispatch: + +permissions: + contents: read + +jobs: + doctests: + runs-on: ubuntu-latest + services: + redis-stack: + image: redis/redis-stack-server:latest + options: >- + --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 + ports: + - 6379:6379 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v2.3.0 + with: + node-version: 18 + - name: Install Packages + run: npm ci + - name: Build + run: npm run build-all + - run: | + sudo apt update + sudo apt install -y redis-tools + - name: install doctest deps + run: | + npm install + working-directory: doctests + - name: run tests + run: | + sh run_examples.sh + working-directory: doctests \ No newline at end of file diff --git a/doctests/README.md b/doctests/README.md new file mode 100644 index 00000000000..b596eb0b114 --- /dev/null +++ b/doctests/README.md @@ -0,0 +1,32 @@ +# Command examples for redis.io + +## Setup + +To set up the examples folder so that you can run an example / develop one of your own: + +``` +$ git clone https://github.com/redis/node-redis.git +$ cd node-redis +$ npm install -ws && npm run build-all +$ cd doctests +$ npm install +``` + +## How to add examples + +Create regular node file in the current folder with meaningful name. It makes sense prefix example files with +command category (e.g. string, set, list, hash, etc) to make navigation in the folder easier. + +### Special markup + +See https://github.com/redis-stack/redis-stack-website#readme for more details. + +## How to test the examples + +Just include necessary assertions in the example file and run +```bash +sh doctests/run_examples.sh +``` +to test all examples in the current folder. + +See `tests.js` for more details. diff --git a/doctests/package.json b/doctests/package.json new file mode 100644 index 00000000000..76db8b3d8c7 --- /dev/null +++ b/doctests/package.json @@ -0,0 +1,12 @@ +{ + "name": "node-redis-doctests", + "version": "1.0.0", + "description": "Code examples for redis.io", + "main": "index.js", + "private": true, + "type": "module", + "dependencies": { + "redis": "../" + } +} + diff --git a/doctests/run_examples.sh b/doctests/run_examples.sh new file mode 100755 index 00000000000..ee7b50dc69a --- /dev/null +++ b/doctests/run_examples.sh @@ -0,0 +1,15 @@ +#!/bin/sh + + +basepath=`readlink -f $1` +if [ $? -ne 0 ]; then +basepath=`readlink -f $(dirname $0)` +fi +echo "No path specified, using ${basepath}" + +set -e +cd ${basepath} +for i in `ls ${basepath}/*.js`; do + redis-cli flushdb + node $i +done \ No newline at end of file diff --git a/doctests/search-quickstart.js b/doctests/search-quickstart.js new file mode 100644 index 00000000000..c25f79f3c13 --- /dev/null +++ b/doctests/search-quickstart.js @@ -0,0 +1,239 @@ +// EXAMPLE: search_quickstart +// REMOVE_START +import assert from "assert"; +// REMOVE_END +// HIDE_START +import {AggregateGroupByReducers, AggregateSteps, createClient, SchemaFieldTypes} from 'redis'; +// HIDE_END +// STEP_START connect +const client = createClient(); +client.on('error', err => console.log('Redis Client Error', err)); + +await client.connect(); +// STEP_END + +// STEP_START data_sample +let bicycle1 = { + "brand": "Diaz Ltd", + "model": "Dealer Sl", + "price": 7315.58, + "description": "The Diaz Ltd Dealer Sl is a reliable choice" + + " for urban cycling. The Diaz Ltd Dealer Sl " + + "is a comfortable choice for urban cycling.", + "condition": "used", +} +// STEP_END +let bicycles = [ + bicycle1, + { + "brand": "Bridges Group", + "model": "Project Pro", + "price": 3610.82, + "description": "This mountain bike is perfect for mountain biking. The Bridges Group Project Pro is a responsive choice for mountain biking.", + "condition": "used", + }, + { + "brand": "Vega, Cole and Miller", + "model": "Group Advanced", + "price": 8961.42, + "description": "The Vega, Cole and Miller Group Advanced provides a excellent ride. With its fast carbon frame and 24 gears, this bicycle is perfect for any terrain.", + "condition": "used", + }, + { + "brand": "Powell-Montgomery", + "model": "Angle Race", + "price": 4050.27, + "description": "The Powell-Montgomery Angle Race is a smooth choice for road cycling. The Powell-Montgomery Angle Race provides a durable ride.", + "condition": "used", + }, + { + "brand": "Gill-Lewis", + "model": "Action Evo", + "price": 283.68, + "description": "The Gill-Lewis Action Evo provides a smooth ride. The Gill-Lewis Action Evo provides a excellent ride.", + "condition": "used", + }, + { + "brand": "Rodriguez-Guerrero", + "model": "Drama Comp", + "price": 4462.55, + "description": "This kids bike is perfect for young riders. With its excellent aluminum frame and 12 gears, this bicycle is perfect for any terrain.", + "condition": "new", + }, + { + "brand": "Moore PLC", + "model": "Award Race", + "price": 3790.76, + "description": "This olive folding bike features a carbon frame and 27.5 inch wheels. This folding bike is perfect for compact storage and transportation.", + "condition": "new", + }, + { + "brand": "Hall, Haley and Hayes", + "model": "Weekend Plus", + "price": 2008.4, + "description": "The Hall, Haley and Hayes Weekend Plus provides a comfortable ride. This blue kids bike features a steel frame and 29.0 inch wheels.", + "condition": "new", + }, + { + "brand": "Peck-Carson", + "model": "Sun Hybrid", + "price": 9874.95, + "description": "With its comfortable aluminum frame and 25 gears, this bicycle is perfect for any terrain. The Peck-Carson Sun Hybrid provides a comfortable ride.", + "condition": "new", + }, + { + "brand": "Fowler Ltd", + "model": "Weekend Trail", + "price": 3833.71, + "description": "The Fowler Ltd Letter Trail is a comfortable choice for transporting cargo. This cargo bike is perfect for transporting cargo.", + "condition": "refurbished", + }, +] +// STEP_START define_index +let schema = { + '$.brand': { + type: SchemaFieldTypes.TEXT, + sortable: true, + AS: 'brand' + }, + '$.model': { + type: SchemaFieldTypes.TEXT, + AS: 'model' + }, + '$.description': { + type: SchemaFieldTypes.TEXT, + AS: 'description' + }, + '$.price': { + type: SchemaFieldTypes.NUMERIC, + AS: 'price' + }, + '$.condition': { + type: SchemaFieldTypes.TAG, + AS: 'condition' + }, +} +// STEP_END + +// STEP_START create_index +try { + await client.ft.create('idx:bicycle', schema, { + ON: 'JSON', + PREFIX: 'bicycle:' + }); +} catch (e) { + if (e.message === 'Index already exists') { + console.log('Index exists already, skipped creation.'); + } else { + // Something went wrong, perhaps RediSearch isn't installed... + console.error(e); + process.exit(1); + } +} +// STEP_END + +// STEP_START add_documents +for (let i = 0; i < bicycles.length; i++) { + await client.json.set(`bicycle:${i}`, '$', bicycles[i]); +} +// STEP_END + +// STEP_START query_single_term_and_num_range +let result = await client.ft.search( + 'idx:bicycle', + 'folding @price:[1000 4000]' +); + +console.log(JSON.stringify(result, null, 2)); +/* +{ + "total": 1, + "documents": [ + { + "id": "bicycle:6", + "value": { + "brand": "Moore PLC", + "model": "Award Race", + "price": 3790.76, + "description": "This olive folding bike features a carbon frame and 27.5 inch wheels. This folding bike is perfect for compact storage and transportation.", + "condition": "new" + } + } + ] +} +*/ +// STEP_END +// REMOVE_START +assert.equal(result.documents[0].id, "bicycle:6"); +// REMOVE_END + +// STEP_START query_single_term_limit_fields +result = await client.ft.search( + 'idx:bicycle', + 'cargo', + { + RETURN: ['$.price'] + } +); + +console.log(JSON.stringify(result, null, 2)); +/* +{ + "total": 1, + "documents": [ + { + "id": "bicycle:9", + "value": { + "$.price": "3833.71" + } + } + ] +} + */ +// STEP_END +// REMOVE_START +assert.equal(result.documents[0].id, "bicycle:9"); +// REMOVE_END + +// STEP_START simple_aggregation +result = await client.ft.aggregate('idx:bicycle', '*', { + STEPS: [ + { + type: AggregateSteps.GROUPBY, + properties: ['@condition'], + REDUCE: [ + { + type: AggregateGroupByReducers.COUNT, + AS: 'count' + } + ] + } + ] +}) + +console.log(JSON.stringify(result, null, 2)); +/* +{ + "total": 3, + "results": [ + { + "condition": "refurbished", + "count": "1" + }, + { + "condition": "used", + "count": "5" + }, + { + "condition": "new", + "count": "4" + } + ] +} + */ +// STEP_END +// REMOVE_START +assert.equal(result.total, 3); +// REMOVE_END + +await client.quit(); \ No newline at end of file diff --git a/doctests/string-set-get-example.js b/doctests/string-set-get-example.js new file mode 100644 index 00000000000..0b0c9138f5a --- /dev/null +++ b/doctests/string-set-get-example.js @@ -0,0 +1,26 @@ +// EXAMPLE: set_and_get +// REMOVE_START +import assert from "assert"; +// REMOVE_END + +// HIDE_START +import { createClient } from 'redis'; + +const client = createClient(); + +client.on('error', err => console.log('Redis Client Error', err)); + +await client.connect(); + +// HIDE_END +await client.set('bike:1', 'Process 134'); +const value = await client.get('bike:1'); +console.log(value); +// returns 'Process 134' +//REMOVE_START +assert.equal(value, 'Process 134'); +//REMOVE_END + +// HIDE_START +await client.quit(); +// HIDE_END \ No newline at end of file