Skip to content

Commit

Permalink
feat: ✨ add channels to followers
Browse files Browse the repository at this point in the history
  • Loading branch information
iwpnd committed Jul 6, 2024
1 parent 78fe797 commit d86bc58
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 24 deletions.
41 changes: 25 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -954,7 +963,7 @@ await tile38.pDelChan('ware*');

For more information, please refer to:

- [Tile38](https://tile38.com)
- [Tile38](https://tile38.com)

## Roadmap

Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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 && \
Expand All @@ -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 && \
Expand Down
15 changes: 14 additions & 1 deletion src/Follower.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -20,6 +28,7 @@ import {
StatsResponse,
} from './responses';
import {
ChannelInterface,
FollowerInterface,
GetInterface,
IntersectsInterface,
Expand Down Expand Up @@ -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<ConfigGetResponse> {
return this.client.command(Command.CONFIG, [Command.GET, name]);
}
Expand Down
6 changes: 6 additions & 0 deletions src/specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,12 @@ export interface FollowerInterface extends Tile38BaseInterface {
* @returns {Promise<InfoFollowerResponse>}
*/
info(): Promise<InfoFollowerResponse>;

/**
* Get the channel interface
* @returns {ChannelInterface}
*/
channel(): ChannelInterface;
}

/**
Expand Down
144 changes: 139 additions & 5 deletions src/tests/channel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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<CustomerFields>({ f: 1 })
.exec();

const expected: GeofenceSet<Point, CustomerFields, CustomMeta> = {
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(
Expand Down Expand Up @@ -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<CustomerFields>({ f: 1 })
.ex(1)
.exec();

const expected: GeofenceDel<CustomMeta> = {
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) => {
Expand Down Expand Up @@ -169,5 +264,44 @@ describe('channel', () => {
channelName: 'parking2',
});
});

it('should receive set geofence on follower', async () => {
const promise = new Promise((resolve) => {
followerChannel.on<Point, CustomerFields, CustomMeta>(
'message',
(message, channelName) => resolve({ message, channelName })
);
});

await tile38
.set('fleet', 'truck1')
.point(52.54568565984199, 13.427749872207642)
.fields<CustomerFields>({ f: 2 })
.exec();

const expected: GeofenceSet<Point, CustomerFields> = {
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',
});
});
});
});

0 comments on commit d86bc58

Please sign in to comment.