diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/model.ts | 87 | ||||
| -rw-r--r-- | lib/schema.ts | 35 | 
2 files changed, 122 insertions, 0 deletions
diff --git a/lib/model.ts b/lib/model.ts new file mode 100644 index 0000000..77fede0 --- /dev/null +++ b/lib/model.ts @@ -0,0 +1,87 @@ +import { model, Schema, Model } from 'mongoose'; +import createEventSchema, { EventDocument } from './schema'; +import cron from 'cron'; + +interface Event<Context> extends EventDocument<Context> { +  log(message: string): void; +  start(): void; +  complete(): void; +  fail(): void; +  computeNextRunAt(): Date; +} + +const CronJob = cron.CronJob; + +const createEventModel = <Context extends Schema>(name: string, contextSchema: Context) => { +  const schema = createEventSchema(contextSchema); + +  // Schema methods +  schema.method('log', function(message: string) { +    // TODO: Actually create logs +    console.log(message); +  }); + +  schema.method('start', function() { +    this.log('Event started') +    this.lastRunAt = new Date(); +    this.status = 'running'; +    return this.save(); +  }); + +  schema.method('complete', function() { +    this.log('Event complete') +    this.status = 'complete'; +    return this.save(); +  }); + +  schema.method('fail', function(error: Error) { +    this.log(error); +    this.error = error; +    this.status = 'failed'; +    return this.save(); +  }); + +  schema.method('computeNextRunAt', function() { +    const job = new CronJob(this.schedule); +    const nextRunAt = job.nextDates(); +    return nextRunAt.toDate(); +  }); + +  // Statics +  schema.static('findMissedEvents', async function () { +    return this.find({ +      nextRunAt: { +        // TODO: skip single-fire events +        $lt: new Date() +      }, +    }); +  }); + +  schema.static('findNextEvents', function(limit = 10) { +    return this.find( +      { +        nextRunAt: { +          $gt: new Date() +        }, +      }, +      null, +      { +        sort: { +          nextRunAt: 1 +        }, +        limit +      } +    ) +  }); + +  // Hooks +  schema.pre<Event<Context>>('save', async function() { +    this.nextRunAt = this.computeNextRunAt(); +  }); + +  return model<Event<Context>>(name, schema); +}; + + +export default createEventModel; + diff --git a/lib/schema.ts b/lib/schema.ts new file mode 100644 index 0000000..4e5dd69 --- /dev/null +++ b/lib/schema.ts @@ -0,0 +1,35 @@ +import { Schema, Document } from 'mongoose'; + +export interface EventDocument<Context> extends Document { +  name: string; +  schedule: string; +  status: 'notStarted' | 'running' | 'complete' | 'failed'; +  error?: string; +  context: Context; +  nextRunAt: Date; +  lastRunAt: Date; +} + +const createEventSchema = (contextSchema: Schema) => new Schema({ +  name: { +    type: String, +    required: true, +    unique: true +  }, +  schedule: { +    type: String, +    required: true +  }, +  status: { +    type: String, +    default: 'notStarted' +  }, +  error: String, +  context: contextSchema, +  nextRunAt: Date, +  lastRunAt: Date +}, { timestamps: true }); + + +export default createEventSchema; +  |