diff --git a/README.md b/README.md index 514ecb1..3797ab9 100644 --- a/README.md +++ b/README.md @@ -46,16 +46,16 @@ This is a Typescript client for Tile38 that allows for a type-safe interaction w ### Features -- fully typed -- lazy client -- optional build-in leader/follower logic -- easy to use and integrate -- no external dependencies other than redis -- build in auto-pagination +- fully typed +- lazy client +- optional build-in leader/follower logic +- easy to use and integrate +- no external dependencies other than redis +- build in auto-pagination ### Built With -- [ioredis](https://www.npmjs.com/package/ioredis) +- [ioredis](https://www.npmjs.com/package/ioredis) ## Getting Started @@ -800,17 +800,17 @@ await tile38.info(); Geofence events can be: -- `inside` (object in specified area), -- `outside` (object outside specified area), -- `enter` (object enters specified area), -- `exit` (object exits specified area), -- `crosses` (object that was not in specified area, has enter/exit it). +- `inside` (object in specified area), +- `outside` (object outside specified area), +- `enter` (object enters specified area), +- `exit` (object exits specified area), +- `crosses` (object that was not in specified area, has enter/exit it). Geofence events can be send on upon commands: -- `set` which sends an event when an object is `.set()` -- `del` which sends a last event when the object that resides in the geosearch is deleted via `.del()` -- `drop`which sends a message when the entire collection is dropped +- `set` which sends an event when an object is `.set()` +- `del` which sends a last event when the object that resides in the geosearch is deleted via `.del()` +- `drop`which sends a message when the entire collection is dropped #### SETHOOK @@ -894,6 +894,10 @@ Now add a receiving channel and add an event handler. ```typescript const channel = await tile38.channel(); channel.on('message', (message) => console.log(message)); + +// also as of tile38 v1.33.1 followers can open a channel +const followerChannel = await tile38.follower().channel(); +followerChannel.on('message', (message) => console.log(message)); ``` Now that channel can: @@ -903,6 +907,11 @@ Now that channel can: await channel.subscribe('warehouse'); // or pattern subscribed to await channel.pSubscribe('ware*'); + +// also as of tile38 v1.33.1 followers can open a channel +await followerChannel.subscribe('warehouse'); +// or pattern subscribed to +await followerChannel.pSubscribe('ware*'); ``` Every set `.set()` results in: @@ -954,7 +963,7 @@ await tile38.pDelChan('ware*'); For more information, please refer to: -- [Tile38](https://tile38.com) +- [Tile38](https://tile38.com) ## Roadmap diff --git a/docker-compose.yml b/docker-compose.yml index b7c8af8..7d3788a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3' services: tile38: - image: tile38/tile38:1.33.0 + image: tile38/tile38:1.33.1 container_name: tile38 command: > /bin/sh -c 'mkdir -p tmp/data && \ @@ -11,7 +11,7 @@ services: ports: - 9851:9851 tile38-follower: - image: tile38/tile38:1.33.0 + image: tile38/tile38:1.33.1 container_name: tile38-follower command: > /bin/sh -c 'mkdir -p tmp/data && \ diff --git a/src/Follower.ts b/src/Follower.ts index 2735754..57960c3 100644 --- a/src/Follower.ts +++ b/src/Follower.ts @@ -1,6 +1,14 @@ import EventEmitter from 'events'; import { Client, Command, ConstructorArgs, SubCommand } from './Client'; -import { Get, Intersects, Nearby, Scan, Search, Within } from './commands'; +import { + Channel, + Get, + Intersects, + Nearby, + Scan, + Search, + Within, +} from './commands'; import { forwardEvents } from './events'; import { BoundsResponse, @@ -20,6 +28,7 @@ import { StatsResponse, } from './responses'; import { + ChannelInterface, FollowerInterface, GetInterface, IntersectsInterface, @@ -57,6 +66,10 @@ export class Follower extends EventEmitter implements FollowerInterface { return this.client.command(Command.CHANS, [pattern]); } + channel(): ChannelInterface { + return new Channel(this.client); + } + configGet(name: ConfigKeys): Promise { return this.client.command(Command.CONFIG, [Command.GET, name]); } diff --git a/src/specs.ts b/src/specs.ts index 20bf0a9..a45a00f 100644 --- a/src/specs.ts +++ b/src/specs.ts @@ -1570,6 +1570,12 @@ export interface FollowerInterface extends Tile38BaseInterface { * @returns {Promise} */ info(): Promise; + + /** + * Get the channel interface + * @returns {ChannelInterface} + */ + channel(): ChannelInterface; } /** diff --git a/src/tests/channel.test.ts b/src/tests/channel.test.ts index 12c6d10..14e9745 100644 --- a/src/tests/channel.test.ts +++ b/src/tests/channel.test.ts @@ -10,7 +10,10 @@ interface CustomerFields extends Fields { } describe('channel', () => { - const tile38 = new Tile38(); + const tile38 = new Tile38( + 'redis://localhost:9851/', + 'redis://localhost:9852/' + ); afterAll(() => tile38.quit()); @@ -45,10 +48,18 @@ describe('channel', () => { describe('subscribe to channel', () => { const channel = tile38.channel(); + const followerChannel = tile38.follower().channel(); - beforeAll(() => channel.subscribe('parking1')); + beforeAll(() => + Promise.all([ + channel.subscribe('parking1'), + followerChannel.subscribe('parking1'), + ]) + ); - afterAll(() => channel.unsubscribe()); + afterAll(() => + Promise.all([channel.unsubscribe(), followerChannel.unsubscribe()]) + ); it('should receive set geofence', async () => { const promise = new Promise((resolve) => { @@ -91,6 +102,47 @@ describe('channel', () => { }); }); + it('should receive set geofence on follower', async () => { + const promise = new Promise((resolve) => { + followerChannel.on( + 'message', + (message, channelName) => + message.command === 'set' && + resolve({ message, channelName }) + ); + }); + + await tile38 + .set('fleet', 'truck1') + .point(52.5514366408197, 13.43018531799316) + .fields({ f: 1 }) + .exec(); + + const expected: GeofenceSet = { + command: 'set', + group: expect.any(String) as string, + detect: 'inside', + hook: 'parking1', + key: 'fleet', + time: expect.any(String) as string, + fields: { f: 1 }, + meta: { m: 'p1' }, + id: 'truck1', + object: { + type: 'Point', + coordinates: [ + expect.any(Number) as number, + expect.any(Number) as number, + ], + }, + }; + + await expect(promise).resolves.toEqual({ + message: expected, + channelName: 'parking1', + }); + }); + it('should receive del geofence', async () => { const promise = new Promise((resolve) => { channel.on( @@ -122,14 +174,57 @@ describe('channel', () => { channelName: 'parking1', }); }); + + it('should receive del geofence on follower', async () => { + const promise = new Promise((resolve) => { + followerChannel.on( + 'message', + (message, channelName) => + message.command === 'del' && + resolve({ message, channelName }) + ); + }); + + await tile38 + .set('fleet', 'truck1') + .point(52.5514366408197, 13.43018531799316) + .fields({ f: 1 }) + .ex(1) + .exec(); + + const expected: GeofenceDel = { + command: 'del', + hook: 'parking1', + key: 'fleet', + time: expect.any(String) as string, + meta: { m: 'p1' }, + id: 'truck1', + }; + + await expect(promise).resolves.toEqual({ + message: expected, + channelName: 'parking1', + }); + }); }); describe('pattern subscribe to channel', () => { const channel = tile38.channel(); + const followerChannel = tile38.follower().channel(); - beforeAll(() => channel.pSubscribe('parking*')); + beforeAll(() => + Promise.all([ + channel.pSubscribe('parking*'), + followerChannel.pSubscribe('parking*'), + ]) + ); - afterAll(() => channel.unsubscribe()); + afterAll(() => + Promise.resolve([ + channel.unsubscribe(), + followerChannel.unsubscribe(), + ]) + ); it('should receive set geofence', async () => { const promise = new Promise((resolve) => { @@ -169,5 +264,44 @@ describe('channel', () => { channelName: 'parking2', }); }); + + it('should receive set geofence on follower', async () => { + const promise = new Promise((resolve) => { + followerChannel.on( + 'message', + (message, channelName) => resolve({ message, channelName }) + ); + }); + + await tile38 + .set('fleet', 'truck1') + .point(52.54568565984199, 13.427749872207642) + .fields({ f: 2 }) + .exec(); + + const expected: GeofenceSet = { + command: 'set', + group: expect.any(String) as string, + detect: 'inside', + hook: 'parking2', + key: 'fleet', + time: expect.any(String) as string, + id: 'truck1', + fields: { f: 2 }, + meta: undefined, + object: { + type: 'Point', + coordinates: [ + expect.any(Number) as number, + expect.any(Number) as number, + ], + }, + }; + + await expect(promise).resolves.toEqual({ + message: expected, + channelName: 'parking2', + }); + }); }); });