Skip to content

Commit

Permalink
feat: tick entity for interval to consume, add intervals to schema
Browse files Browse the repository at this point in the history
  • Loading branch information
ssube committed Dec 29, 2018
1 parent 5fa8a5e commit 628d91b
Show file tree
Hide file tree
Showing 16 changed files with 203 additions and 35 deletions.
9 changes: 9 additions & 0 deletions docs/interval/command-interval.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
metadata:
kind: command-interval
name: test-interval-cmd
data:
defaultCommand:
noun: time
verb: get
frequency:
zeit: 30 seconds
2 changes: 2 additions & 0 deletions docs/isolex.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ data:
- !include ../docs/controller/token-controller.yml
- !include ../docs/controller/user-controller.yml
- !include ../docs/controller/weather-controller-owm.yml
intervals:
- !include ../docs/interval/command-interval.yml
listeners:
- !include ../docs/listener/discord-listener.yml
- !include ../docs/listener/express-listener.yml
Expand Down
2 changes: 1 addition & 1 deletion src/BaseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export abstract class BaseService<TData extends BaseServiceData> implements Serv
// validate the data
const result = options.schema.match(options.data, schemaPath);
if (!result.valid) {
this.logger.error({ errors: result.errors }, 'failed to validate config');
this.logger.error({ data: options.data, errors: result.errors }, 'failed to validate config');
throw new SchemaError('failed to validate config');
} else {
this.logger.debug('validated config data');
Expand Down
9 changes: 9 additions & 0 deletions src/Bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { BaseService, BaseServiceOptions } from 'src/BaseService';
import { Controller, ControllerData } from 'src/controller/Controller';
import { Command } from 'src/entity/Command';
import { Message } from 'src/entity/Message';
import { Interval, IntervalData } from 'src/interval/Interval';
import { ContextFetchOptions, Listener, ListenerData } from 'src/listener/Listener';
import { ServiceModule } from 'src/module/ServiceModule';
import { Parser, ParserData } from 'src/parser/Parser';
Expand All @@ -20,6 +21,7 @@ import { StorageLogger, StorageLoggerOptions } from 'src/utils/StorageLogger';
export interface BotData {
filters: Array<ServiceDefinition>;
controllers: Array<ServiceDefinition<ControllerData>>;
intervals: Array<ServiceDefinition>;
listeners: Array<ServiceDefinition>;
logger: {
level: LogLevel;
Expand Down Expand Up @@ -48,6 +50,7 @@ export class Bot extends BaseService<BotData> implements Service {

// services
protected controllers: Array<Controller>;
protected intervals: Array<Interval>;
protected listeners: Array<Listener>;
protected parsers: Array<Parser>;
protected services: ServiceModule;
Expand All @@ -68,6 +71,7 @@ export class Bot extends BaseService<BotData> implements Service {

// set up deps
this.controllers = [];
this.intervals = [];
this.listeners = [];
this.parsers = [];

Expand Down Expand Up @@ -296,6 +300,11 @@ export class Bot extends BaseService<BotData> implements Service {
this.controllers.push(await this.services.createService<Controller, ControllerData>(data));
}

this.logger.info('setting up intervals');
for (const data of this.data.intervals) {
this.intervals.push(await this.services.createService<Interval, IntervalData>(data));
}

this.logger.info('setting up listeners');
for (const data of this.data.listeners) {
this.listeners.push(await this.services.createService<Listener, ListenerData>(data));
Expand Down
5 changes: 4 additions & 1 deletion src/entity/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Token } from 'src/entity/auth/Token';
import { GRAPH_OUTPUT_USER, User } from 'src/entity/auth/User';
import { Listener } from 'src/listener/Listener';
import { Parser } from 'src/parser/Parser';
import { BaseEntity } from './base/BaseEntity';

export interface ChannelData {
id: string;
Expand Down Expand Up @@ -44,7 +45,7 @@ export interface ContextData {
export const TABLE_CONTEXT = 'context';

@Entity(TABLE_CONTEXT)
export class Context implements ContextData {
export class Context extends BaseEntity implements ContextData {
@Column('simple-json')
public channel: ChannelData;

Expand All @@ -68,6 +69,8 @@ export class Context implements ContextData {
public user?: User;

constructor(options?: ContextData) {
super();

if (options) {
if (!options.name || !options.uid) {
throw new MissingValueError('name and uid must be specified in context options');
Expand Down
51 changes: 51 additions & 0 deletions src/entity/Tick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { GraphQLObjectType, GraphQLString } from 'graphql';
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';

import { BaseEntity } from './base/BaseEntity';

export const TABLE_TICK = 'tick';

@Entity(TABLE_TICK)
export class Tick extends BaseEntity {
@CreateDateColumn()
public createdAt: number;

@PrimaryGeneratedColumn('uuid')
public id: string;

@Column()
public intervalId: string;

@Column()
public status: number;

@UpdateDateColumn()
public updatedAt: number;

public toJSON(): object {
return {
createdAt: this.createdAt,
id: this.id,
intervalId: this.intervalId,
updatedAt: this.updatedAt,
};
}
}

export const GRAPH_OUTPUT_TICK = new GraphQLObjectType({
fields: {
createdAt: {
type: GraphQLString,
},
id: {
type: GraphQLString,
},
intervalId: {
type: GraphQLString,
},
updatedAt: {
type: GraphQLString,
},
},
name: 'Tick',
});
44 changes: 33 additions & 11 deletions src/interval/BaseInterval.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,60 @@
import { Inject } from 'noicejs';
import { Repository } from 'typeorm';
import { Equal, Repository } from 'typeorm';

import { BotService } from 'src/BotService';
import { Context } from 'src/entity/Context';
import { Interval, IntervalData, IntervalJob, IntervalOptions } from 'src/interval/Interval';
import { Tick } from 'src/entity/Tick';
import { Interval, IntervalData, IntervalOptions } from 'src/interval/Interval';
import { Clock } from 'src/utils/Clock';

@Inject('clock', 'storage')
export abstract class BaseInterval<TData extends IntervalData> extends BotService<TData> implements Interval {
protected readonly clock: Clock;
protected readonly tickRepository: Repository<IntervalJob>;
protected readonly tickRepository: Repository<Tick>;

protected interval: number;

constructor(options: IntervalOptions<TData>, schemaPath: string) {
super(options, schemaPath);

this.clock = options.clock;
this.tickRepository = options.storage.getRepository(''); // @TODO interval tick/job entity
this.tickRepository = options.storage.getRepository(Tick);
}

public abstract tick(context: Context, last: IntervalJob): Promise<number>;
public async start() {
await super.start();

// set up the interval
this.interval = this.clock.setInterval(() => this.nextTick, 1000); // TODO: frequency from config
}

public async stop() {
await super.stop();

this.clock.clearInterval(this.interval);
}

public abstract tick(context: Context, last: Tick): Promise<number>;

protected async nextTick() {
const last = await this.tickRepository.findOneOrFail({
// TODO: most recent
intervalId: this.id,
const last = await this.tickRepository.find({
order: {
toString: undefined, // needs to be included, otherwise object's toString conflicts with typeorm interface
updatedAt: 'DESC',
},
take: 1,
where: {
intervalId: Equal(this.id),
},
});
const context = await this.createTickContext();
const status = await this.tick(context, last);
const next: IntervalJob = {
const status = await this.tick(context, last[0]);
const next = this.tickRepository.create({
createdAt: this.clock.getSeconds(),
intervalId: this.id,
status,
updatedAt: this.clock.getSeconds(),
};
});
await this.tickRepository.save(next);
}

Expand Down
5 changes: 3 additions & 2 deletions src/interval/CommandInterval.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Command, CommandOptions } from 'src/entity/Command';
import { Context } from 'src/entity/Context';
import { Tick } from 'src/entity/Tick';
import { BaseInterval } from 'src/interval/BaseInterval';
import { IntervalData, IntervalJob, IntervalOptions } from 'src/interval/Interval';
import { IntervalData, IntervalOptions } from 'src/interval/Interval';

export interface CommandIntervalData extends IntervalData {
defaultCommand: CommandOptions;
Expand All @@ -14,7 +15,7 @@ export class CommandInterval extends BaseInterval<CommandIntervalData> {
super(options, 'isolex#/definitions/service-interval-command');
}

public async tick(context: Context, last: IntervalJob): Promise<number> {
public async tick(context: Context, last: Tick): Promise<number> {
const cmd = new Command({
...this.data.defaultCommand,
context,
Expand Down
5 changes: 3 additions & 2 deletions src/interval/EventInterval.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Context } from 'src/entity/Context';
import { Tick } from 'src/entity/Tick';
import { BaseInterval } from 'src/interval/BaseInterval';
import { IntervalData, IntervalJob, IntervalOptions } from 'src/interval/Interval';
import { IntervalData, IntervalOptions } from 'src/interval/Interval';
import { ServiceLifecycle, ServiceMetadata } from 'src/Service';

export interface EventIntervalData extends IntervalData {
Expand All @@ -15,7 +16,7 @@ export class EventInterval extends BaseInterval<EventIntervalData> {
super(options, 'isolex#/definitions/service-interval-event');
}

public async tick(context: Context, last: IntervalJob): Promise<number> {
public async tick(context: Context, last: Tick): Promise<number> {
for (const def of this.data.services) {
const svc = this.services.getService(def);
await svc.notify(this.data.event);
Expand Down
13 changes: 4 additions & 9 deletions src/interval/Interval.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { BotServiceData, BotServiceOptions } from 'src/BotService';
import { Context } from 'src/entity/Context';

export interface IntervalJob {
createdAt: number;
intervalId: string;
status: number;
updatedAt: number;
}
import { Tick } from 'src/entity/Tick';
import { Service } from 'src/Service';

export interface IntervalData extends BotServiceData {
frequency: {
Expand All @@ -16,9 +11,9 @@ export interface IntervalData extends BotServiceData {
}

export type IntervalOptions<TData extends IntervalData> = BotServiceOptions<TData>;
export interface Interval {
export interface Interval extends Service {
/**
* Based on the results of the last job, run a new one.
*/
tick(context: Context, last: IntervalJob): Promise<number>;
tick(context: Context, last: Tick): Promise<number>;
}
5 changes: 3 additions & 2 deletions src/interval/MessageInterval.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Context } from 'src/entity/Context';
import { Message, MessageOptions } from 'src/entity/Message';
import { Tick } from 'src/entity/Tick';
import { BaseInterval } from 'src/interval/BaseInterval';
import { IntervalData, IntervalJob, IntervalOptions } from 'src/interval/Interval';
import { IntervalData, IntervalOptions } from 'src/interval/Interval';

export interface MessageIntervalData extends IntervalData {
defaultMessage: MessageOptions;
Expand All @@ -14,7 +15,7 @@ export class MessageInterval extends BaseInterval<MessageIntervalData> {
super(options, 'isolex#/definitions/service-interval-message');
}

public async tick(context: Context, last: IntervalJob): Promise<number> {
public async tick(context: Context, last: Tick): Promise<number> {
const msg = new Message({
...this.data.defaultMessage,
body: `last fired: ${last.createdAt}`,
Expand Down
32 changes: 32 additions & 0 deletions src/migration/0001546063195-CreateTick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { MigrationInterface, QueryRunner, Table } from 'typeorm';

import { TABLE_TICK } from 'src/entity/Tick';

export class CreateTick0001546063195 implements MigrationInterface {
public async up(query: QueryRunner): Promise<any> {
await query.createTable(new Table({
columns: [{
isPrimary: true,
name: 'id',
type: 'varchar',
}, {
name: 'createdAt',
type: 'int',
}, {
name: 'intervalId',
type: 'varchar',
}, {
name: 'status',
type: 'int',
}, {
name: 'updatedAt',
type: 'int',
}],
name: TABLE_TICK,
}));
}

public async down(query: QueryRunner): Promise<any> {
await query.dropTable(TABLE_TICK);
}
}
2 changes: 2 additions & 0 deletions src/module/EntityModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Fragment } from 'src/entity/Fragment';
import { Message } from 'src/entity/Message';
import { Counter } from 'src/entity/misc/Counter';
import { Keyword } from 'src/entity/misc/Keyword';
import { Tick } from 'src/entity/Tick';

export class EntityModule extends Module {
public async configure(options: ModuleOptions): Promise<void> {
Expand All @@ -23,6 +24,7 @@ export class EntityModule extends Module {
Context,
Fragment,
Message,
Tick,
/* auth */
Role,
Token,
Expand Down
2 changes: 2 additions & 0 deletions src/module/MigrationModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CreateRole0001544312069 } from 'src/migration/0001544312069-CreateRole'
import { CreateUser0001544312112 } from 'src/migration/0001544312112-CreateUser';
import { CreateToken0001544317462 } from 'src/migration/0001544317462-CreateToken';
import { KeywordCommand0001545509108 } from 'src/migration/0001545509108-KeywordCommand';
import { CreateTick0001546063195 } from 'src/migration/0001546063195-CreateTick';

export class MigrationModule extends Module {
public async configure(options: ModuleOptions): Promise<void> {
Expand All @@ -27,6 +28,7 @@ export class MigrationModule extends Module {
CreateUser0001544312112,
CreateToken0001544317462,
KeywordCommand0001545509108,
CreateTick0001546063195,
]);
}
}
Loading

0 comments on commit 628d91b

Please sign in to comment.