Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove generic parameter from Model #36

Merged
merged 1 commit into from
Sep 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Let's define a model:
```typescript
import { Model } from 'octonom';

export class PersonModel extends Model<PersonModel> {
export class Person extends Model {
@Model.Property({type: 'string', default: () => '42'})
public id: string;

Expand All @@ -57,7 +57,7 @@ Let's create an instance:

```typescript
// create an instance with initial data
const person = new PersonModel({name: 'Marx', age: 200});
const person = new Person({name: 'Marx', age: 200});

// call a model instance method
person.makeOlder();
Expand All @@ -79,7 +79,7 @@ import { MongoClient } from 'mongodb';
import { MongoCollection } from 'octonom';

// create people collection
const people = new MongoCollection('people', PersonModel, {modelIdField: 'id'});
const people = new MongoCollection('people', Person, {modelIdField: 'id'});

// connect with to database
const db = await MongoClient.connect('mongodb://localhost:27017/mydb');
Expand All @@ -92,7 +92,7 @@ Inserting and retrieving models is straight-forward:

```typescript
// insert a person
const karl = new PersonModel({id: 'C4p1T4l', name: 'Marx', age: 200});
const karl = new Person({id: 'C4p1T4l', name: 'Marx', age: 200});
await people.insertOne(karl);

// retrieve a person
Expand Down
20 changes: 10 additions & 10 deletions packages/octonom-mongodb/lib/mongo-collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,28 @@ import { Collection as DbCollection, CollectionInsertManyOptions, Cursor,

import { Collection, ICollectionOptions, IModelConstructor, Model, ModelArray, utils } from 'octonom';

export class MongoCollection<TModel extends Model<TModel>> extends Collection<TModel> {
export class MongoCollection<T extends Model> extends Collection<T> {
protected collection: DbCollection;

constructor(
protected name: string,
model: IModelConstructor<TModel>,
model: IModelConstructor<T>,
options: ICollectionOptions = {},
) {
super(model, options);
}

public async insertMany(models: TModel[], options?: CollectionInsertManyOptions) {
public async insertMany(models: T[], options?: CollectionInsertManyOptions) {
const docs = models.map(model => this.toDb(model));
await this.collection.insertMany(docs, options);
}

public async insertOne(model: TModel) {
public async insertOne(model: T) {
const doc = this.toDb(model);
await this.collection.insertOne(doc);
}

public async delete(model: TModel) {
public async delete(model: T) {
const result = await this.collection.deleteOne({_id: model[this.modelIdField]});
if (result.deletedCount === 0) {
throw new Error('document not found in collection');
Expand All @@ -33,7 +33,7 @@ export class MongoCollection<TModel extends Model<TModel>> extends Collection<TM

public find(query: object) {
return this.collection.find(query)
.map(obj => this.fromDb(obj)) as Cursor<TModel>;
.map(obj => this.fromDb(obj)) as Cursor<T>;
}

public async findById(id: string) {
Expand All @@ -42,10 +42,10 @@ export class MongoCollection<TModel extends Model<TModel>> extends Collection<TM

public async findByIds(ids: string[]) {
const docs = await this.collection.find({_id: {$in: ids}}).toArray();
const idInstanceMap: {[k: string]: TModel} = {};
const idInstanceMap: {[k: string]: T} = {};
// note: ids that could not be found won't be present in the docs result array
docs.forEach(doc => idInstanceMap[doc._id] = this.fromDb(doc));
return new ModelArray<TModel>(this.model, ids.map(id => idInstanceMap[id]));
return new ModelArray<T>(this.model, ids.map(id => idInstanceMap[id]));
}

public async findOne(query: object, options?: FindOneOptions) {
Expand All @@ -60,15 +60,15 @@ export class MongoCollection<TModel extends Model<TModel>> extends Collection<TM
this.collection = await db.createCollection(this.name);
}

public toDb(model: TModel) {
public toDb(model: T) {
return utils.rename(super.toDb(model), {[this.modelIdField]: '_id'});
}

public fromDb(doc: object) {
return super.fromDb(utils.rename(doc, {_id: this.modelIdField}));
}

public async update(model: TModel) {
public async update(model: T) {
const doc = this.toDb(model);
const result = await this.collection.replaceOne({_id: model[this.modelIdField]}, doc);
if (result.matchedCount === 0) {
Expand Down
4 changes: 2 additions & 2 deletions packages/octonom/lib/array-collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { Model } from './model';

// simple collection with an in-memory array
// note: we can't test Collection directly since it's abstract
export class ArrayCollection<TModel extends Model<object>> extends Collection<TModel> {
export class ArrayCollection<T extends Model> extends Collection<T> {
public array: object[] = [];

public clear() {
this.array.splice(0, this.array.length);
}

public insert(model: TModel) {
public insert(model: T) {
const doc = find(this.array, {[this.modelIdField]: model[this.modelIdField]});
if (doc) {
throw new Error('duplicate key error');
Expand Down
14 changes: 7 additions & 7 deletions packages/octonom/lib/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,31 @@ export interface ICollectionOptions {
modelIdField?: string;
}

export abstract class Collection<TModel extends Model<object>> {
export abstract class Collection<T extends Model> {
public readonly modelIdField: string;

constructor(
public readonly model: IModelConstructor<TModel>,
public readonly model: IModelConstructor<T>,
protected options: ICollectionOptions = {},
) {
this.modelIdField = options.modelIdField || 'id';
}

public abstract async findById(id: string): Promise<TModel>;
public abstract async findById(id: string): Promise<T>;

// trivial implementation, should be implemented efficiently for specific database
public async findByIds(ids: string[]): Promise<ModelArray<TModel>> {
return new ModelArray<TModel>(
public async findByIds(ids: string[]): Promise<ModelArray<T>> {
return new ModelArray<T>(
this.model,
await Promise.all(ids.map(id => this.findById(id))),
);
}

public toDb(model: TModel): object {
public toDb(model: T): object {
return model.toObject({unpopulate: true});
}

public fromDb(doc: object): TModel {
public fromDb(doc: object): T {
return new this.model(doc);
}
}
2 changes: 1 addition & 1 deletion packages/octonom/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class ValidationError extends ExtendableError {
public reason?: string,
public value?: any,
public path?: Array<string | number>,
public instance?: Model<object>,
public instance?: Model,
) {
super(message, ValidationError.prototype);
}
Expand Down
18 changes: 9 additions & 9 deletions packages/octonom/lib/model-array.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { IModelConstructor, Model } from './model';

export class ModelArray<TModel extends Model<object>> extends Array<TModel> {
export class ModelArray<T extends Model> extends Array<T> {
constructor(
public readonly model: IModelConstructor<TModel>,
data: Array<Partial<TModel>> = [],
public readonly model: IModelConstructor<T>,
data: Array<Partial<T>> = [],
) {
super();
data.forEach(element => this.push(element));
Expand All @@ -21,30 +21,30 @@ export class ModelArray<TModel extends Model<object>> extends Array<TModel> {
});
}

public fill(value: Partial<TModel>, start?: number, end?: number) {
public fill(value: Partial<T>, start?: number, end?: number) {
return super.fill(this.toModel(value), start, end);
}

public push(value: Partial<TModel>) {
public push(value: Partial<T>) {
return super.push(this.toModel(value));
}

public splice(start: number, deleteCount?: number, ...values: Array<Partial<TModel>>) {
public splice(start: number, deleteCount?: number, ...values: Array<Partial<T>>) {
const models = values ? values.map(value => this.toModel(value)) : [];
return super.splice(start, deleteCount, ...models);
}

public toModel(value: Partial<TModel>) {
public toModel(value: Partial<T>) {
if (value === undefined) {
return undefined;
}

return value instanceof this.model
? value as TModel
? value as T
: new this.model(value);
}

public unshift(...values: Array<Partial<TModel>>) {
public unshift(...values: Array<Partial<T>>) {
const models = values ? values.map(value => this.toModel(value)) : [];
return super.unshift(...models);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/octonom/lib/model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ describe('Model', () => {
});

describe('validate()', () => {
class TestModel extends Model<TestModel> {
class TestModel extends Model {
@Model.Property({type: 'string', required: true})
public required: string;

Expand Down
15 changes: 8 additions & 7 deletions packages/octonom/lib/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import { SchemaMap, SchemaValue } from './schema';
import { IToObjectOptions, toObject } from './to-object';
import { validateObject } from './validate';

export interface IModelConstructor<TModel extends Model<object>> {
export interface IModelConstructor<T extends Model> {
schema: SchemaMap;
new (data: Partial<TModel>): TModel;
new (data: Partial<T>): T;
}

interface IModel {
constructor: typeof Model;
}

export abstract class Model<T extends object> {
export abstract class Model {
public static schema: SchemaMap;

/**
Expand All @@ -30,7 +30,8 @@ export abstract class Model<T extends object> {
};
}

constructor(data?: Partial<T>) {
// TODO: ideally we'd also use Partial<this> as the type for data
constructor(data?) {
const constructor = this.constructor as typeof Model;
const schema = constructor.schema;

Expand All @@ -57,14 +58,14 @@ export abstract class Model<T extends object> {
}

// TODO: sanitize is called twice when this is called via the proxy
public set(data: object, options: ISanitizeOptions = {}) {
public set(data: Partial<this>, options: ISanitizeOptions = {}) {
const constructor = this.constructor as typeof Model;
setObjectSanitized(constructor.schema, this, data, options);
}

public toObject(options?: IToObjectOptions): Partial<T> {
public toObject(options?: IToObjectOptions): Partial<this> {
const constructor = this.constructor as typeof Model;
return toObject(constructor.schema, this, options) as Partial<T>;
return toObject(constructor.schema, this, options) as Partial<this>;
}

public toJSON() {
Expand Down
4 changes: 2 additions & 2 deletions packages/octonom/lib/sanitize.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('sanitize()', () => {
});

describe('type model', () => {
class Cat extends Model<Cat> {
class Cat extends Model {
@Cat.Property({type: 'string'})
public name: string;
}
Expand Down Expand Up @@ -124,7 +124,7 @@ describe('sanitize()', () => {
});

describe('type reference', () => {
class Cat extends Model<Cat> {
class Cat extends Model {
@Cat.Property({type: 'string'})
public id: string;

Expand Down
20 changes: 10 additions & 10 deletions packages/octonom/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,35 @@ export interface ISchemaValueBase {
export interface ISchemaValueAny extends ISchemaValueBase {
type: 'any';
default?: () => any;
validate?(value: any, path: Array<string | number>, instance: Model<any>): Promise<void>;
validate?(value: any, path: Array<string | number>, instance: Model): Promise<void>;
}

export interface ISchemaValueArray extends ISchemaValueBase {
type: 'array';
definition: SchemaValue;
minLength?: number;
maxLength?: number;
validate?(value: any[], path: Array<string | number>, instance: Model<any>): Promise<void>;
validate?(value: any[], path: Array<string | number>, instance: Model): Promise<void>;
}

export interface ISchemaValueBoolean extends ISchemaValueBase {
type: 'boolean';
default?: boolean | (() => boolean);
validate?(value: boolean, path: Array<string | number>, instance: Model<any>): Promise<void>;
validate?(value: boolean, path: Array<string | number>, instance: Model): Promise<void>;
}

export interface ISchemaValueDate extends ISchemaValueBase {
type: 'date';
default?: Date | (() => Date);
min?: Date;
max?: Date;
validate?(value: Date, path: Array<string | number>, instance: Model<any>): Promise<void>;
validate?(value: Date, path: Array<string | number>, instance: Model): Promise<void>;
}

export interface ISchemaValueModel extends ISchemaValueBase {
type: 'model';
model: IModelConstructor<Model<object>>;
validate?(value: Model<object>, path: Array<string | number>, instance: Model<any>): Promise<void>;
model: IModelConstructor<Model>;
validate?(value: Model, path: Array<string | number>, instance: Model): Promise<void>;
}

export interface ISchemaValueNumber extends ISchemaValueBase {
Expand All @@ -45,19 +45,19 @@ export interface ISchemaValueNumber extends ISchemaValueBase {
min?: number;
max?: number;
integer?: boolean;
validate?(value: number, path: Array<string | number>, instance: Model<any>): Promise<void>;
validate?(value: number, path: Array<string | number>, instance: Model): Promise<void>;
}

export interface ISchemaValueObject extends ISchemaValueBase {
type: 'object';
definition: ISchemaMap;
validate?(value: object, path: Array<string | number>, instance: Model<any>): Promise<void>;
validate?(value: object, path: Array<string | number>, instance: Model): Promise<void>;
}

export interface ISchemaValueReference extends ISchemaValueBase {
type: 'reference';
collection: () => any; // TODO
validate?(value: any, path: Array<string | number>, instance: Model<any>): Promise<void>;
validate?(value: any, path: Array<string | number>, instance: Model): Promise<void>;
}

export interface ISchemaValueString extends ISchemaValueBase {
Expand All @@ -67,7 +67,7 @@ export interface ISchemaValueString extends ISchemaValueBase {
min?: number;
max?: number;
regex?: RegExp;
validate?(value: string, path: Array<string | number>, instance: Model<any>): Promise<void>;
validate?(value: string, path: Array<string | number>, instance: Model): Promise<void>;
}

export type SchemaValue = ISchemaValueAny | ISchemaValueArray | ISchemaValueBoolean |
Expand Down
2 changes: 1 addition & 1 deletion packages/octonom/lib/to-object.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('toObject()', () => {
});

describe('toObjectValue()', () => {
class Cat extends Model<Cat> {
class Cat extends Model {
@Cat.Property({type: 'string'})
public id: string;

Expand Down
Loading