Some community attempts I've found:
https://github.com/zekelevu/typeframework/blob/master/lib/waterline.d.ts
https://github.com/aclave1/sails-types/blob/master/sails.d.ts
If no one puts their hand up, I'll see if I can make time to do it myself. Would be great to have :+1:
Finally got the chance, just spent a brief stint and wrote them all up:
declare var waterline: WaterlineMod.Wl;
declare module WaterlineMod {
export interface Wl {
new (): Wl;
_collections: string[];
_connections: {};
loadCollection(collection: {}): Model;
initialize(options: {}, cb: (err: any, ontology: any) => void): Model;
teardown(cb: (err: any, res: any) => void): void;
bootstrap(cb: (err: any, res: any) => void): void;
Collection: Collection;
Model: Model;
}
interface Collection extends Core {
new (waterline: Wl, connections: {}, cb: cb): Collection;
connections: {};
waterline: Wl;
attributes: {};
}
interface Core {
_attributes: {};
_cast: Cast;
_schema: Schema;
_validator: Validator;
_callbacks: {};
_instanceMethods;
new (options: {}): Core;
hasSchema: boolean;
migrate: string;
adapter: {};
connections: {};
defaults: {};
}
interface Cast {
_types: {};
new (): Cast;
initialize(attrs: {}): Cast;
run(values: {}): {};
string(str: string): string;
integer(key: string, value: any): number;
float(value: number): number;
boolean(value: boolean): boolean;
date(value: typeof Date): typeof Date;
array(value: any): any[];
}
interface Schema {
new (self: any): Cast;
initialize(attrs: {}, hasSchema: boolean, reservedAttributes: {}): Schema;
context: {};
schema: {};
objectAttribute(attrName: string, value: {}): {};
cleanValues(values: {}): {};
}
interface Validator {
new (adapter: string): Validator;
validations: {};
initialize(attrs: {}, types: {}, defaults: {}): {};
validate(values: {}, presentOnly: boolean, cb: cb): Error[];
}
interface Query {
adapter: AdapterBase;
sync(cb: cb): any;
}
interface AdapterBase extends Adapter {
/* anything go here? */
}
interface Adapter extends Dql, Ddl, compoundQueries, aggregateQueries, sync, stream {
new (options: {}): Adapter;
connections: {};
dictionary: {};
query: {};
collection: string;
identity: string;
}
interface Dql {
hasJoin(): boolean;
join(criteria: {}, cb: cb): void;
create(values: {}, cb: cb): {};
find(criteria: {}, cb: cb): {};
findOne(criteria: {}, cb: cb): {};
count(criteria: {}, cb: cb): number;
update(criteria: {}, values: {}, cb: cb): {};
destroy(criteria: {}, cb: cb): {};
}
interface Ddl {
define(cb: cb): { describe(err, existingAttributes): any };
describe(cb: cb): any;
drop(relations: {}, cb: cb): any;
alter(cb: cb): any;
}
interface compoundQueries {
findOrCreate(criteria: {}, values: {}, cb: cb): {};
findOne(criteria: {}, cb: cb): {};
}
interface aggregateQueries {
createEach(valuesList: any[], cb: cb): cbList;
findOrCreateEach(attributesToCheck: any[], valuesList: any[], cb: cb): cbList;
}
interface setupTeardown {
teardown(cb: cb): any;
}
interface sync {
migrateDrop(cb: cb): any;
migrateAlter(cb: cb): any;
migrateCreate(cb: cb): any;
migrateSafe(cb: cb): any;
}
interface stream {
stream(criteria: {}, stream: any): any;
}
interface cb {
(err: Error, res: any);
}
interface cbList {
(err: Error, res: any[]);
}
}
declare module "waterline" {
export = waterline;
}
Tested an earlier version based on a combination of typeframework+sails-types, once I've tested and gotten this one working I'll submit a pull-request.
Thanks @SamuelMarks not sure how this would fit into Waterline? We have no plans to move to typescript anytime in the future. If you make this as a gist and post a PR to the waterline-docs repo we could maybe link to it or something.
Sure, I'll send something over [once it's thoroughly tested].
Not trying to make you move to TypeScript, just writing definitions for it. Definitions enable code-completion + type-checking (without editing any of your actual code).
@SamuelMarks This doesn't need to be integrated into waterline, it just needs to be submitted to https://github.com/borisyankov/DefinitelyTyped. There's actually a feature request for sails typings in there, so i'm sure they want waterline too.
FYI rewrote it completely:
declare var waterline: waterline.waterline;
declare module waterline {
export interface waterline {
new (): waterline;
_collections: Collection[];
_connections: Connection[];
Collection: Collection;
Model: Model;
loadCollection(collection: Collection): Collection[];
initialize(options: ConfigOptions, cb: cb): void;
schema: Schema;
teardown(cb: cb): void;
bootstrap(cb: cb): void;
}
export interface Collection extends Core, Query {
new (waterline: waterline, connections: Connection[], cb: cb): Collection;
connections: Connection[];
waterline: waterline;
attributes: Attributes;
extend(protoProps: {}, staticProps?: {}): any;
}
export interface Attributes {
migrate?: string;
}
export interface CollectionLoader {
new (collection: Collection, connections: Connection[], defaults: {}): CollectionLoader;
initialize(context: {}): Collection;
_validate(collection: Collection, connections: Connection[]): Collection;
_getConnections(collection: Collection, connections: Connection[]): {};
}
export interface Core {
new (options: {}): Core;
adapter: Adapter;
_attributes: {};
connections: Connection[];
defaults: Attributes;
_cast: Cast;
_schema: Schema;
_validator: Validator;
_callbacks: {
afterCreate: any[],
beforeCreate: any[]
};
_instanceMethods: {};
hasSchema: boolean;
migrate: string;
_initialize(options: {}): void;
_model: Model;
_transformer: Transformer;
adapterDictionary: Dictionary;
_normalizeSchemaFlag(): boolean;
}
export interface Dictionary {
// TODO
}
export interface Transformer {
// TODO
}
export interface Validator {
// TODO
}
export interface Cast {
// TODO
}
export interface Query extends validate, ddl, dql, aggregate, composite, findersBasic, findersHelpers, findersDynamicFinders, stream {
new (): Query;
adapter: Adapter;
buildDynamicFinders(): void;
sync(cb: cb): void;
extend(protoProps: {}, staticProps: {}): {};
}
export interface validate {
validate: (values: any[], presentOnly?: cb | boolean, cb?: cb) => void;
}
export interface ddl {
describe(cb: cb): void;
alter(cb: cb): void;
drop(cb: cb): void;
}
export interface dql {
create(values: any | any[], cb?: cb): any;
update(criteria: {}, values: {}, cb?: cb): any;
destroy(criteria: {}, cb?: cb): any;
count(criteria: {}, options: {}, cb?: cb): any;
join(collection, fk, pk, cb): void;
}
export interface aggregate {
createEach(valuesList: any[], cb?: cb): any;
findOrCreateEach(criteria: {}, valuesList: any[], cb?: cb): any;
}
export interface composite {
findOrCreate(criteria: {}, values?: {}, cb?: cb): any;
}
export interface findersBasic {
findOne(criteria: {}, cb?: cb): any;
find(criteria?: {}, options?: {}, cb?: cb): any;
where(...arguments: any[]): findersBasic;
select(...arguments: any[]): findersBasic;
}
export interface findersHelpers {
findOneLike(criteria: {}, options: {}, cb: cb): void;
findLike(criteria: {}, options: {}, cb: cb): void;
startsWith(criteria: {}, options: {}, cb: cb): void;
endsWith(criteria: {}, options: {}, cb: cb): void;
contains(criteria: {}, options: {}, cb: cb): void;
}
export interface findersDynamicFinders {
buildDynamicFinders(): void;
generateDynamicFinder(attrName: string, method: string, dontCapitalize: boolean): any;
generateAssociationFinders(attrName: string): any;
}
export interface stream {
stream(criteria: {}, transformation?: {}): ModelStream;
}
export interface ModelStream {
// TODO
}
export interface Connection {
postgres: any; // Fix this line
}
export interface Model {
(context: {}, mixins: {}): Model;
toObject(): Object;
save(options: {}, cb: cb): any;
destory(cb): any;
_defineAssociations(): void;
_normalizeAssociations(): void;
_cast(values: any[]): void;
validate(cb: cb): void | any;
toJSON(): JSON;
}
export interface ConfigOptions {
adapters: Adapter;
connections: Connection;
}
export interface cb {
(error: Error, result: any): any;
}
export interface Adapter {
url?: string;
postgres?: any; // TODO: Fix this line
/*
connections: Connection[],
query: any,
collection: string,
identity: string,
dictionary: adapterDictionary
*/
}
export interface adapterDictionary {
// TODO
}
export interface Schema {
// TODO, in waterline-schema.d.ts
}
export interface Record {
id: number;
createdAt: Date;
updatedAt: Date;
}
}
declare module "waterline" {
export = waterline;
}
@SamuelMarks are you opening a pull request against DefinitelyTyped for this? If not, I might make one myself so I can use Waterline in a project that I'm working on. This is probably just the first step in DefinitelyTyped/DefinitelyTyped#1784
And I hadn't seen this yet, so.... here's what I have so far ^_^
declare module 'waterline' {
module waterline {
interface WaterlineStatic {
new (): Waterline;
Collection: Collection;
WaterlineConfig: WaterlineConfig;
}
interface Waterline {
(): Waterline;
loadCollection(collection: Collection);
initialize(config: WaterlineConfig, callback: (err: any, ontology: Ontology) => any);
}
interface Collection {
extend<AttributeCollectionT, AdditionalModelT>(config: AdditionalModelT & ModelDefinition<AttributeCollectionT>): Collection;
}
interface Model<AttributeT> {
create(attributes: AttributeT);
}
interface WaterlinePromise<T> {
then<ReturnT>(func: (value: T) => ReturnT): ReturnT;
catch<ReturnT>(func: (error: any) => ReturnT): ReturnT;
}
interface WaterlineConfig {
adapters: any;
connections: any;
defaults?: any;
}
interface ModelDefinition<AttributeCollectionT> {
identity?: string;
connection?: string;
attributes: AttributeCollectionT;
migrate?: string;
autoPK?: boolean;
autoCreatedAt?: boolean;
autoUpdatedAt?: boolean;
schema?: boolean;
tableName?: string;
}
interface Ontology {
collections: any;
}
type Attribute = AttributeObject & AttributeValidations;
/**
* Attribute Properties https://github.com/balderdashy/waterline-docs/blob/master/models/data-types-attributes.md#attribute-properties
*/
interface AttributeObject {
type: string,
defaultsTo?: any,
autoIncrement?: boolean,
unique?: boolean,
index?: boolean,
primaryKey?: boolean,
enum?: any[],
size?: number,
columnName?: string,
}
/**
* Validation settings https://github.com/balderdashy/waterline-docs/blob/master/models/validations.md#validation-rules
* Based on Anchor validations https://github.com/sailsjs/anchor/blob/master/lib/match/rules.js
*/
interface AttributeValidations {
after?: string,
alpha?: boolean,
alphanumeric?: boolean,
array?: boolean,
before?: string,
boolean?: boolean,
contains?: string,
creditcard?: boolean,
date?: boolean,
decimal?: boolean,
email?: boolean,
empty?: boolean,
equals?: string,
falsey?: boolean,
finite?: boolean,
float?: boolean,
hexColor?: boolean,
hexadecimal?: boolean,
in?: string[],
int?: boolean,
integer?: boolean,
ip?: boolean,
ipv4?: boolean,
ipv6?: boolean,
is?: RegExp,
len?: number,
lowercase?: boolean,
max?: number,
maxLength?: number
min?: number,
minLength?: number,
not?: RegExp,
notContains?: string,
notEmpty?: boolean,
notIn?: string[],
notNull?: boolean,
notRegex?: RegExp,
null?: boolean,
number?: boolean,
numeric?: boolean,
regex?: RegExp,
required?: boolean,
string?: boolean,
truthy?: boolean,
undefined?: boolean,
uppercase?: boolean,
url?: boolean,
urlish?: boolean,
uuid?: boolean,
uuidv3?: boolean,
uuidv4?: boolean,
}
}
var waterline: waterline.WaterlineStatic;
export = waterline;
}
Cool, I'll take a look at your approach when I get home.
I'm the meantime, here's where I'm maintaining mine: https://github.com/SamuelMarks/restify_waterline_scaffold/blob/master/cust_typings/waterline.d.ts
@enriched: How about we join efforts, drop me a PR :)
@SamuelMarks @enriched also have an interest in getting definitions up, do one of you guys want to start the PR process on DefinitelyTyped and we can go from there?
Sorry, went with a different ORM, but feel free to use what I posted here.
will work on it over there :+1:
this issue can be closed now
Hi guys. I have came up with definitions for waterline as well, but I created only the needed interfaces for working with sailsJs rather than working only with Waterline.
declare module waterline {
export interface ModelInstance {
id?: number | string;
createdAt?: Date;
updatedAt?: Date;
destroy(): Promise<this>;
toJSON(): any;
save(): WaterlinePromise<this>;
}
export interface Model<instance extends ModelInstance> {
create(params: any): WaterlinePromise<instance>;
create(params: any[]): WaterlinePromise<instance>;
find(): QueryBuilder<instance[]>;
find(criteria: any): QueryBuilder<instance[]>;
findOne(criteria: any): QueryBuilder<instance>;
findOne(criteria: string): QueryBuilder<instance>;
findOne(criteria: number): QueryBuilder<instance>;
findOne(): QueryBuilder<instance>;
count(criteria: any): WaterlinePromise<number>;
count(criteria: any[]): WaterlinePromise<number>;
count(criteria: string): WaterlinePromise<number>;
count(criteria: number): WaterlinePromise<number>;
destroy(criteria: any): WaterlinePromise<instance[]>;
destroy(criteria: any[]): WaterlinePromise<instance[]>;
destroy(criteria: string): WaterlinePromise<instance[]>;
destroy(criteria: number): WaterlinePromise<instance[]>;
update(criteria: any, changes: any): WaterlinePromise<instance[]>;
update(criteria: any, changes: any[]): WaterlinePromise<instance[]>;
update(criteria: any[], changes: any): WaterlinePromise<instance[]>;
update(criteria: any[], changes: any[]): WaterlinePromise<instance[]>;
update(criteria: string, changes: any): WaterlinePromise<instance[]>;
update(criteria: string, changes: any[]): WaterlinePromise<instance[]>;
update(criteria: number, changes: any): WaterlinePromise<instance[]>;
update(criteria: number, changes: any[]): WaterlinePromise<instance[]>;
query(sqlQuery: string, cb: (err: Error, results: any[]) => void);
native(cb: (err: Error, collection: any) => void);
stream(criteria: any, writeEnd: any): NodeJS.WritableStream;
stream(criteria: any[], writeEnd: any): NodeJS.WritableStream;
stream(criteria: string, writeEnd: any): NodeJS.WritableStream;
stream(criteria: number, writeEnd: any): NodeJS.WritableStream;
stream(criteria: any, writeEnd: any): Error;
stream(criteria: any[], writeEnd: any): Error;
stream(criteria: string, writeEnd: any): Error;
stream(criteria: number, writeEnd: any): Error;
}
export class WaterlinePromise<T> extends Promise<T>{
exec(cb: (err: Error, result: T) => void);
}
export interface QueryBuilder<T> extends WaterlinePromise<T> {
where(condition: any): QueryBuilder<T>;
limit(lim: number): QueryBuilder<T>;
skip(num: number): QueryBuilder<T>;
sort(criteria: string): QueryBuilder<T>;
paginate(pagination?: { page: number, limit: number }): QueryBuilder<T>;
populate(association: string): QueryBuilder<T>;
populate(association: string, filter: any): QueryBuilder<T>;
}
export interface ModelDefinition<Instance extends ModelInstance, InstanceAttributes extends AttributeCollection> extends LifecycleCallbacks<Instance> {
identity?: string;
globalId?: string;
connection?: string;
attributes: InstanceAttributes;
migrate?: string;
autoPK?: boolean;
autoCreatedAt?: boolean;
autoUpdatedAt?: boolean;
schema?: boolean;
tableName?: string;
types?: Dictionary<Function>;
}
export interface LifecycleCallbacks<Instance extends ModelInstance> {
beforeValidate?: { (vaues: any, next: Function): void }[] | { (vaues: any, next: Function): void };
beforeCreate?: { (values: any, next: Function): void }[] | { (vaues: any, next: Function): void };
afterCreate?: { (newlyCreatedRecord: Instance, next: Function): void }[] | { (newlyCreatedRecord: Instance, next: Function): void };
beforeUpdate?: { (valuesToUpdate: any, next: Function): void }[] | { (valuesToUpdate: any, next: Function): void };
afterUpdate?: { (valuesToUpdate: any, next: Function): void }[] | { (valuesToUpdate: any, next: Function): void };
beforeDestroy?: { (criteria: any, next: Function): void }[] | { (valuesToUpdate: any, next: Function): void };
afterDestroy?: { (destroyedInstance: Instance, next: Function): void }[] | { (destroyedInstance: Instance, next: Function): void };
}
export interface Dictionary<T> {
[k: string]: T
}
export interface AttributeCollection {
[attributeName: string]: Attribute | Function;
}
export interface Attribute extends AttributeObject, AttributeValidations, Dictionary<any> { }
/**
* Attribute Properties https://github.com/balderdashy/waterline-docs/blob/master/models/data-types-attributes.md#attribute-properties
*/
export interface AttributeObject {
type: string,
defaultsTo?: any,
autoIncrement?: boolean,
unique?: boolean,
index?: boolean,
primaryKey?: boolean,
enum?: any[],
size?: number,
columnName?: string,
special?: boolean,
model?: string;
collection?: string;
via?: string;
dominant?: boolean;
}
/**
* Validation settings https://github.com/balderdashy/waterline-docs/blob/master/models/validations.md#validation-rules
* Based on Anchor validations https://github.com/sailsjs/anchor/blob/master/lib/match/rules.js
*/
export interface AttributeValidations {
after?: string,
alpha?: boolean,
alphanumeric?: boolean,
array?: boolean,
before?: string,
boolean?: boolean,
contains?: string,
creditcard?: boolean,
date?: boolean,
decimal?: boolean,
email?: boolean,
empty?: boolean,
equals?: string,
falsey?: boolean,
finite?: boolean,
float?: boolean,
hexColor?: boolean,
hexadecimal?: boolean,
in?: string[],
int?: boolean,
integer?: boolean,
ip?: boolean,
ipv4?: boolean,
ipv6?: boolean,
is?: RegExp,
len?: number,
lowercase?: boolean,
max?: number,
maxLength?: number
min?: number,
minLength?: number,
not?: RegExp,
notContains?: string,
notEmpty?: boolean,
notIn?: string[],
notNull?: boolean,
notRegex?: RegExp,
null?: boolean,
number?: boolean,
numeric?: boolean,
regex?: RegExp,
required?: boolean,
string?: boolean,
truthy?: boolean,
undefined?: boolean,
uppercase?: boolean,
url?: boolean,
urlish?: boolean,
uuid?: boolean,
uuidv3?: boolean,
uuidv4?: boolean,
}
}
declare module 'waterline' {
export = waterline;
}
Usage of the above definitions would be something like this.
/// <reference path="waterline.d.ts" />
declare module Models {
module User {
// set instance props here
interface UserInstanceProps extends waterline.ModelInstance {
id: string;
email: string;
firstName: string;
lastName: string;
gender: string;
hashedPassword: string;
}
// set instance methods
interface UserInstanceMethods {
getPublicName(): string;
checkPassword(plain: string, cb: { (err: Error, valid: boolean) }): void
toJSON(): any;
}
// model attributes definition
interface UserAttributes extends UserInstanceMethods, waterline.AttributeCollection {
id: waterline.Attribute;
email: waterline.Attribute;
firstName: waterline.Attribute;
lastName: waterline.Attribute;
gender: waterline.Attribute;
hashedPassword: waterline.Attribute;
}
// instance is formed of props and methods
export interface UserInstance extends UserInstanceProps, UserInstanceMethods { }
export interface UserCollection extends waterline.Model<UserInstance> {
populateCustom(rawValues: UserInstance): Promise<UserInstance>;
encryptPassword(values: { password: string, hashedPassword: string }, cb: Function): void;
}
// model definition that should be exported from model
export interface UserDefs extends waterline.ModelDefinition<UserInstance, UserAttributes> { }
}
}
And then you can create your definitions for user by using UserDefs interface:
// you'll have full autocomplete features and also errors will be shown if you don't implement
// correctly the interface
let User: Models.User.UserDefs = {
// implementation of the interface here...
};
And from your controller, you can use the UserCollection:
let UserCol = <Models.User.UserCollection>sails.models.user;
// and you'll have your definitions, besides those inherited from waterline...
UserCol.findOne("user-id-here").then((user) => {
// user will contain all your instance attributes and methods...
res.json(user.toJSON());
console.log(user.id); // and so on...
});
Note: The UserCollection does have most of the methods inherited from waterline but without the magic methods like: findOneById or findOneByTitle e.t.c. Those are dynamically added methods and didn't knew how to add those based on model attribute list.
@pfzero Your type definitions look very good. Is it possible to setup Model Associations this way as well?
BTW: Here's where I maintain them nowadays - https://github.com/SamuelMarks/cust_typings
typings.json of project that uses it:
{
"globalDependencies": {
"async": "registry:dt/async#2.0.1+20160804113311"
},
"dependencies": {
"waterline": "github:SamuelMarks/cust_typings/waterline/waterline.d.ts",
"waterline-postgresql": "github:SamuelMarks/cust_typings/waterline-postgresql/waterline-postgresql.d.ts"
}
}
Extracted a quick bit of code to show how to use it:
user.ts
import {IUser} from './models.d';
export const User = {
identity: 'user_tbl',
attributes: {
title: {
type: 'string'
},
email: {
type: 'string',
required: true,
primaryKey: true
},
password: {
type: 'string',
required: true
}
}
};
user.d.ts
import {Model, Record} from 'waterline';
export interface IUser extends Model, Record, IUserBase {
}
export interface IUserBase {
email: string;
password: string;
title?: string;
}
user_use.ts
import {Query, WLError} from 'waterline';
import {IUser} from './user.d';
const User: Query = collections['user_tbl'];
User.findOne({email: '[email protected]'}, (err: WLError, user: IUser) => {
if (err) console.error(err);
else if (!user) console.error('user not found');
else console.info('user.password =', user.password);
})
@danielbaak Yes, I am cross-referencing my model definitions and for example I can have something like this:
owner: string | User.UserInstance;
There's been a while since I didn't used sails anymore but AFAIK I did some improvements on those libraries at the time. Talking about my experience, I think it's a bit cumbersome to maintain your model defs synced with your model implementations and I'm not sure if in the long run was worth it... While you definetely gain something, you're introducing a lot of complexities with the defs.
It's a pity that waterline / sails haven't changed to an OOP style declaration of models and controllers and they choosed to remain stuck in the ES5 era.
I'll post my definitions again here.
This is sails.d.ts
/// <reference path="../node/node.d.ts"/>
/// <reference path="../express/express.d.ts" />
/// <reference path="./waterline.d.ts"/>
declare module Upstream {
interface UpstreamFile {
fd: any;
size: number;
type: string;
filename: string;
status: string;
field: string;
extra: any;
stream: NodeJS.ReadableStream;
}
interface UpstreamCallback {
(err: Error, files: UpstreamFile[]): void;
}
export interface Upstream extends NodeJS.ReadableStream {
upload(opts: any, cb: UpstreamCallback): void
}
}
declare module Express {
interface RequestOpts extends waterline.Dictionary<any> {
values: any;
where: any;
populate?: any;
mirror?: boolean;
associations?: any | any[];
}
export interface Request {
options: RequestOpts;
allParams(): any;
file(field: string): Upstream.Upstream;
isSocket: boolean;
wantsJSON: boolean;
_sails: sailsjs.SailsObject;
}
interface ResponseHandlerFn {
(data: any, pathToView?: string): void;
}
export interface Response {
badRequest: ResponseHandlerFn;
forbidden: ResponseHandlerFn;
negotiate: (err: any) => void;
notFound(data: any, pathToView?: string): void;
notFound(): void;
created(instance: any): void;
ok: ResponseHandlerFn;
serverError: ResponseHandlerFn;
view(pathToView: string): void;
view(pathToView: string, locals: any): void;
view(locals: any): void;
view(): void;
}
}
declare module winston {
export interface LoggerInstance {
silly: (message: string, ...args: any[]) => LoggerInstance
}
}
declare module waterline {
export interface Model<instance extends waterline.ModelInstance> extends waterline.ModelSettings {
// http://sailsjs.org/documentation/reference/web-sockets/resourceful-pub-sub/message
message(id: number | string, payload: any, request?: sailsjs.SailsRequest): void;
// http://sailsjs.org/documentation/reference/web-sockets/resourceful-pub-sub/publish-add
publishAdd(updatedRecordID: number | string, attribute: string, addedRecordID: string | number, request?: sailsjs.SailsRequest, opts?: any): void;
// http://sailsjs.org/documentation/reference/web-sockets/resourceful-pub-sub/publish-remove
publishRemove(updatedRecordID: number | string, attribute: string, removedRecordID: string | number, request?: sailsjs.SailsRequest, opts?: any): void;
// http://sailsjs.org/documentation/reference/web-sockets/resourceful-pub-sub/publish-update
publishUpdate(updatedRecordID: number | string, updatedValues: any, request?: sailsjs.SailsRequest, opts?: any): void;
// http://sailsjs.org/documentation/reference/web-sockets/resourceful-pub-sub/publish-destroy
publishDestroy(removedRecordID: number | string, request?: sailsjs.SailsRequest, opts?: any): void;
// http://sailsjs.org/documentation/reference/web-sockets/resourceful-pub-sub/publish-create
publishCreate(dataToSend: any, request?: sailsjs.SailsRequest): void;
// http://sailsjs.org/documentation/reference/web-sockets/resourceful-pub-sub/subscribe
subscribe(req: sailsjs.SailsRequest, ids: string, contexts?: string | string[]): void;
subscribe(req: sailsjs.SailsRequest, ids: string[], contexts?: string | string[]): void;
subscribe(req: sailsjs.SailsRequest, ids: number, contexts?: string | string[]): void;
subscribe(req: sailsjs.SailsRequest, ids: number[], contexts?: string | string[]): void;
subscribe(req: sailsjs.SailsRequest, instance: waterline.ModelInstance, contexts?: string | string[]): void;
subscribe(req: sailsjs.SailsRequest, instances: waterline.ModelInstance[], contexts?: string | string[]): void;
subscribe(socketId: string | string[], ids: string, contexts?: string | string[]): void;
subscribe(socketId: string | string[], ids: string[], contexts?: string | string[]): void;
subscribe(socketId: string | string[], instance: waterline.ModelInstance, contexts?: string | string[]): void;
subscribe(socketId: string | string[], instance: waterline.ModelInstance[], contexts?: string | string[]): void;
// http://sailsjs.org/documentation/reference/web-sockets/resourceful-pub-sub/unsubscribe
unsubscribe(req: sailsjs.SailsRequest, ids: string[] | number[], contexts?: string | string[]): void;
unsubscribe<T extends waterline.ModelInstance>(req: sailsjs.SailsRequest, record: T, contexts?: string | string[]): void;
introduce(records: waterline.ModelInstance | waterline.ModelInstance[]): void;
introduce<T extends waterline.ModelInstance>(records: T | T[]): void;
retire(records: waterline.ModelInstance | waterline.ModelInstance[]): void;
retire<T extends waterline.ModelInstance>(records: T | T): void;
// http://sailsjs.org/documentation/reference/web-sockets/resourceful-pub-sub/subscribers
subscribers(record: instance | number | string, contexts?: string | string[]): string[];
// http://sailsjs.org/documentation/reference/web-sockets/resourceful-pub-sub/watch
watch(req: sailsjs.SailsRequest): void;
// http://sailsjs.org/documentation/reference/web-sockets/resourceful-pub-sub/unwatch
unwatch(req: sailsjs.SailsRequest): void;
}
}
declare module sailsjs {
interface Dictionary<T> {
[k: string]: T
}
interface SailsLogger extends winston.LoggerInstance { }
interface BlueprintsConfig {
populate: boolean;
actions: boolean;
index: boolean;
shortcuts: boolean;
rest: boolean;
prefix: string;
restPrefix: string;
pluralize: boolean;
autoWatch: boolean;
mirror: boolean;
defaultLimit?: number;
}
interface DBConnectionConfig {
adapter: string;
host?: string;
port?: string;
user?: string;
password?: string;
database?: string;
keepAlive?: boolean;
sniffOnStart?: boolean;
maxRetries?: number;
deadTimeout?: number;
sniffOnConnectionFault?: boolean;
apiVersion?: string;
}
interface ConnectionsConfig {
[con: string]: DBConnectionConfig
}
interface CorsConfig {
allRoutes: boolean;
origin: string;
credentials: boolean;
methods: string;
headers: string;
exposeHeaders: string;
securityLevel: number;
}
interface GlobalsConfig {
_: boolean;
sails: boolean;
adapters: boolean;
models: boolean;
services: boolean;
controllers: boolean;
}
interface I18nConfig {
locales: string[];
defaultLocale: string;
localesDirectory: string;
cookie?: any;
directory: string;
updateFiles: boolean;
extension: string;
requestLocale: string;
}
interface LogConfig {
level: string;
}
interface ModelsConfig {
connection: string;
migrate: string;
}
interface hookTimeout {
_hookTimeout: number;
}
interface OrmConfig extends hookTimeout { }
interface PubsubConfig extends hookTimeout { }
interface ValidationConfig extends hookTimeout { }
interface CsrfToken {
cors: {
origin: string;
credentials: boolean;
};
}
interface Routes extends Dictionary<Object | String> {
'/csrfToken': CsrfToken
}
interface SocketsConfig {
adapterOptions: Dictionary<string>;
sendResponseHeaders: boolean;
sendStatusCode: boolean;
grant3rdPartyCookie: boolean;
path: string;
serveClient: boolean;
pingTimeout: number;
pingInterval: number;
maxHttpBufferSize: number;
transports: string[];
allowUpgrades: boolean;
cookie: boolean;
}
interface PathsConfig {
tmp: string;
config: string;
controllers: string;
policies: string;
services: string;
adapters: string;
models: string;
hooks: string;
blueprints: string;
responses: string;
views: string;
layout: string;
public: string;
}
interface CsrfConfig {
grantTokenViaAjax: boolean;
protectionEnabled: boolean;
origin: string;
routesDisabled: string;
route: string;
}
interface CacheConfig {
maxAge: number;
}
interface SailsConfig extends waterline.Dictionary<any> {
blueprints: BlueprintsConfig;
connections: ConnectionsConfig;
cors: CorsConfig;
globals: GlobalsConfig;
i18n: I18nConfig;
port: number;
host: string;
explicitHost: string;
log: LogConfig;
models: ModelsConfig;
orm: OrmConfig;
pubsub: PubsubConfig;
validation: ValidationConfig;
routes: Routes;
sockets: SocketsConfig;
environment: string;
appPath: string;
paths: PathsConfig;
middleware: Dictionary<Function>;
config: string;
csrf: CsrfConfig;
cache: CacheConfig;
ssl: any;
}
interface SailsHooks extends Dictionary<any> {
pubsub: any;
}
interface SailsSockets {
// http://sailsjs.org/documentation/reference/web-sockets/sails-sockets/sails-sockets-blast
blast(data: any, socketToOmit?: SocketIO.Socket): void;
blast(eventName: string, data: any, socketToOmit: SocketIO.Socket): void;
// http://sailsjs.org/documentation/reference/web-sockets/sails-sockets/sails-sockets-broadcast
broadcast(roomName: string, data: any): void;
broadcast(roomName: string, eventName: string, data: any): void;
broadcast(roomName: string, data: any, socketToOmit: SocketIO.Socket): void;
broadcast(roomName: string, eventName: string, data: any, socketToOmit: SocketIO.Socket): void;
// http://sailsjs.org/documentation/reference/web-sockets/sails-sockets/sails-sockets-join
join(socket: SocketIO.Socket, roomName: string): boolean;
join(sockets: SocketIO.Socket[], roomName: string): boolean;
// http://sailsjs.org/documentation/reference/web-sockets/sails-sockets/sails-sockets-emit
emit(socketIds: string[], eventName: string, messageData: any): void;
// http://sailsjs.org/documentation/reference/web-sockets/sails-sockets/sails-sockets-id
id(socket: SocketIO.Socket): string;
// http://sailsjs.org/documentation/reference/web-sockets/sails-sockets/sails-sockets-leave
leave(socket: SocketIO.Socket, roomName: string): boolean;
leave(sockets: SocketIO.Socket[], roomName: string): boolean;
// http://sailsjs.org/documentation/reference/web-sockets/sails-sockets/sails-sockets-rooms
rooms(): string[];
// http://sailsjs.org/documentation/reference/web-sockets/sails-sockets/sails-sockets-socket-rooms
socketRooms(socket: SocketIO.Socket): string[];
// http://sailsjs.org/documentation/reference/web-sockets/sails-sockets/sails-sockets-subscribers
subscribers(roomName: string): string[];
}
// extending express as a namespace sucks;
// the drawback here is that you must manually include all the definitions where
// the express Request is extended
// this interface should be enough for handling requests;
// since the express request doesn't expose all the properties without
// requiring it as a module; and also, on sails, the request's socket property
// is actually a SailsIo.Socket type, and this cannot be written on a express request object
export interface SailsRequest {
readable: boolean;
domain: any;
writable: boolean;
allowHalfOpen: boolean;
method: string;
url: string;
headers: Dictionary<string>;
rawHeaders: string[];
params: any;
query: Dictionary<string>;
body: any;
file(field: string): Upstream.Upstream;
// imported from multer
files: {
[fieldname: string]: Express.Multer.File | Express.Multer.File[]
}
/** */
/* imported from passport */
authInfo?: any;
user?: any;
// These declarations are merged into express's Request type
login(user: any, done: (err: any) => void): void;
login(user: any, options: Object, done: (err: any) => void): void;
logIn(user: any, done: (err: any) => void): void;
logIn(user: any, options: Object, done: (err: any) => void): void;
logout(): void;
logOut(): void;
isAuthenticated(): boolean;
isUnauthenticated(): boolean;
/** */
/**
* Return the value of param `name` when present or `defaultValue`.
*
* - Checks route placeholders, ex: _/user/:id_
* - Checks body params, ex: id=12, {"id":12}
* - Checks query string params, ex: ?id=12
*
* To utilize request bodies, `req.body`
* should be an object. This can be done by using
* the `connect.bodyParser()` middleware.
*
* @param name
* @param defaultValue
*/
param(name: string, defaultValue?: any): string;
wantsJSON: boolean;
transport: string;
protocol: string;
isSocket: boolean;
ip: string;
port: number;
socket: SocketIO.Socket;
secret: string;
cookies: any;
signedCookies: any;
sessionStore: any;
sessionID: string;
session: any;
route: any;
options: any
allParams(): any;
flash(message: string, data: any): void;
baseUrl: string;
validate(cb: (err: Error) => void): void;
languages: string[];
regions: string[];
language: string;
region: string;
locale: string;
getLocale(): string;
setLocale(locale: string): void;
getCatalog(): Dictionary<string>;
_sails: sailsjs.SailsObject;
}
// should be extended at app level to provide models and services
// maybe controllers to?
export interface SailsObject {
config: SailsConfig;
// will be implemented by used
models: any;
// will be implemented by used
controllers: any;
// will be implemented by used
services: any;
io: SocketIO.Server;
sockets: SailsSockets;
log: SailsLogger;
hooks: SailsHooks;
// ToDO: document this
util: any;
}
}
declare module "sails" {
export const sails: sailsjs.SailsObject;
}
And waterline.d.ts:
/// <reference path="../mongodb/mongodb.d.ts" />
declare module waterline {
export interface CollectionRel<T> extends Array<T> {
[k: number]: T
add(relatedResources: string): void;
add(relatedResources: string[]): void;
add(relatedResources: number): void;
add(relatedResources: number[]): void;
remove(relatedResources: string): void;
remove(relatedResources: string[]): void;
remove(relatedResources: number): void;
remove(relatedResources: number[]): void;
}
export interface ModelInstance extends Dictionary<any> {
id?: number | string;
associationsCache: Dictionary<Attribute>;
createdAt?: Date;
updatedAt?: Date;
destroy(): Promise<any>;
destroy<T extends ModelInstance>(): Promise<T>;
inspect(): string;
toObject(): any;
toObject<T>(): T;
toJSON(): any;
toJSON<T>(): T;
save(): WaterlinePromise<any>;
save(): WaterlinePromise<void>;
add(relatedResources: string): void;
}
export interface ModelInstancePopulator<T extends ModelInstance> {
// optional custom function to populate the collection
// allways check if this function exists and was implemented
populateCustom?(rawValues: T, loggedUser?: any): Promise<T>;
populateCustom?(rawValues: T[], loggedUser?: any): Promise<T[]>;
}
// the one to many or many-to-many collections within a model instance also contains
// a remove method
export interface ModelInstanceCollection<instance extends ModelInstance> {
[k: number]: instance;
remove(id: number | string): void;
}
export interface Model<instance extends ModelInstance> extends ModelSettings, ModelInstancePopulator<instance> {
new <TProps extends {}>(instanceProps: TProps): instance;
new (instanceProps: any): instance;
adapterDictionary: Dictionary<string | string[]>;
// list of associations the current collection has
associations: {
alias: string;
type: string;
model?: string;
collection?: string;
}[];
autoSubscribe: boolean;
// does the collection have a schema?
hasSchema: boolean;
// collection's primary key; all models from this collection will have
// the same primary key (e.g. 'id')
primaryKey: string;
pkFormat: string;
syncable: boolean;
create(params: any[]): WaterlinePromise<instance[]>;
create(params: any): WaterlinePromise<instance>;
find(): QueryBuilder<instance[]>;
find(criteria: any): QueryBuilder<instance[]>;
find(criteria: any, extra: any): QueryBuilder<instance[]>;
findOne(criteria: any): QueryBuilder<instance>;
findOne(criteria: string): QueryBuilder<instance>;
findOne(criteria: number): QueryBuilder<instance>;
findOne(criteria: any, extra: any): QueryBuilder<instance>;
findOne(): QueryBuilder<instance>;
findOrCreate<T extends {}>(criteria: any, resourceData: T): WaterlinePromise<instance>;
count(criteria: any): WaterlinePromise<number>;
count(criteria: any[]): WaterlinePromise<number>;
count(criteria: string): WaterlinePromise<number>;
count(criteria: number): WaterlinePromise<number>;
destroy(criteria: any): WaterlinePromise<instance[]>;
destroy(criteria: any[]): WaterlinePromise<instance[]>;
destroy(criteria: string): WaterlinePromise<instance[]>;
destroy(criteria: number): WaterlinePromise<instance[]>;
query(sqlQuery: string, queryParams: any, cb: (err: Error, results: any[]) => void): void;
query(sqlQuery: string, cb: (err: Error, results: any[]) => void): void;
stream(criteria: any, writeEnd: any): NodeJS.WritableStream;
stream(criteria: any[], writeEnd: any): NodeJS.WritableStream;
stream(criteria: string, writeEnd: any): NodeJS.WritableStream;
stream(criteria: number, writeEnd: any): NodeJS.WritableStream;
stream(criteria: any, writeEnd: any): Error;
stream(criteria: any[], writeEnd: any): Error;
stream(criteria: string, writeEnd: any): Error;
stream(criteria: number, writeEnd: any): Error;
update(criteria: any, changes: any): WaterlinePromise<instance[]>;
update(criteria: any, changes: any[]): WaterlinePromise<instance[]>;
update(criteria: any[], changes: any): WaterlinePromise<instance[]>;
update(criteria: any[], changes: any[]): WaterlinePromise<instance[]>;
update(criteria: string, changes: any): WaterlinePromise<instance[]>;
update(criteria: string, changes: any[]): WaterlinePromise<instance[]>;
update(criteria: number, changes: any): WaterlinePromise<instance[]>;
update(criteria: number, changes: any[]): WaterlinePromise<instance[]>;
native(cb: (err: Error, collection: mongodb.Collection) => void): void;
}
export class WaterlinePromise<T> extends Promise<T>{
exec(cb: (err: Error, result: T) => void): void;
}
export interface QueryBuilder<T> extends WaterlinePromise<T> {
where(condition: any): QueryBuilder<T>;
limit(lim: number): QueryBuilder<T>;
skip(num: number): QueryBuilder<T>;
sort(criteria: string): QueryBuilder<T>;
paginate(pagination?: { page: number, limit: number }): QueryBuilder<T>;
populate(association: string): QueryBuilder<T>;
populate(association: string, filter: any): QueryBuilder<T>;
populateAll(): QueryBuilder<T>;
}
export interface ModelSettings {
identity?: string;
globalId?: string;
connection?: string | string[];
migrate?: string;
autoPK?: boolean;
autoCreatedAt?: boolean;
autoUpdatedAt?: boolean;
schema?: boolean;
tableName?: string;
}
export interface ModelDefinition<Instance extends ModelInstance, InstanceAttributes extends AttributeCollection> extends ModelSettings, LifecycleCallbacks<Instance> {
attributes: InstanceAttributes;
types?: Dictionary<Function>;
}
export interface LifecycleCallbacks<Instance extends ModelInstance> {
beforeValidate?: { (vaues: any, next: Function): void }[] | { (vaues: any, next: Function): void };
beforeCreate?: { (values: any, next: Function): void }[] | { (vaues: any, next: Function): void };
afterCreate?: { (newlyCreatedRecord: Instance, next: Function): void }[] | { (newlyCreatedRecord: Instance, next: Function): void };
beforeUpdate?: { (valuesToUpdate: any, next: Function): void }[] | { (valuesToUpdate: any, next: Function): void };
afterUpdate?: { (valuesToUpdate: any, next: Function): void }[] | { (valuesToUpdate: any, next: Function): void };
beforeDestroy?: { (criteria: any, next: Function): void }[] | { (valuesToUpdate: any, next: Function): void };
afterDestroy?: { (destroyedInstance: Instance[], next: Function): void }[] | { (destroyedInstance: Instance, next: Function): void };
}
export interface Dictionary<T> {
[k: string]: T
}
export interface AttributeCollection {
[attributeName: string]: Attribute | Function;
}
export interface Attribute extends AttributeObject, AttributeValidations, Dictionary<any> { }
/**
* Attribute Properties https://github.com/balderdashy/waterline-docs/blob/master/models/data-types-attributes.md#attribute-properties
*/
export interface AttributeObject {
type?: string,
defaultsTo?: any,
autoIncrement?: boolean,
unique?: boolean,
index?: boolean,
primaryKey?: boolean,
enum?: any[],
size?: number,
columnName?: string,
special?: boolean,
model?: string;
collection?: string;
via?: string;
dominant?: boolean;
}
/**
* Validation settings https://github.com/balderdashy/waterline-docs/blob/master/models/validations.md#validation-rules
* Based on Anchor validations https://github.com/sailsjs/anchor/blob/master/lib/match/rules.js
*/
export interface AttributeValidations {
after?: string,
alpha?: boolean,
alphanumeric?: boolean,
array?: boolean,
before?: string,
boolean?: boolean,
contains?: string,
creditcard?: boolean,
date?: boolean,
decimal?: boolean,
email?: boolean,
empty?: boolean,
equals?: string,
falsey?: boolean,
finite?: boolean,
float?: boolean,
hexColor?: boolean,
hexadecimal?: boolean,
in?: string[],
int?: boolean,
integer?: boolean,
ip?: boolean,
ipv4?: boolean,
ipv6?: boolean,
is?: RegExp,
len?: number,
lowercase?: boolean,
max?: number,
maxLength?: number
min?: number,
minLength?: number,
not?: RegExp,
notContains?: string,
notEmpty?: boolean,
notIn?: string[],
notNull?: boolean,
notRegex?: RegExp,
null?: boolean,
number?: boolean,
numeric?: boolean,
regex?: RegExp,
required?: boolean,
string?: boolean,
truthy?: boolean,
undefined?: boolean,
uppercase?: boolean,
url?: boolean,
urlish?: boolean,
uuid?: boolean,
uuidv3?: boolean,
uuidv4?: boolean,
}
}
declare module 'waterline' {
export = waterline;
}
And usage: Note that I'll just drop some code from my project (disregard any project-related terminology, although it shouldn't be hard to figure it out :D)
// reference to all the 3rd party imported typings
// and all the other models
/// <reference path="./index.d.ts" />
declare module Models {
module Comment {
// set instance props here
interface CommentInstanceProps extends Models.User.UserLikedMeta, waterline.ModelInstance {
id: string;
comment: string;
parent: string | CommentInstance;
likes: number;
dislikes: number;
targetId: string;
targetType: string;
owner: string | User.UserInstance;
}
// set instance methods
interface CommentInstanceMethods {
toNotificationObject(): Promise<{ id: string; title: string; link: string; type: string }>;
}
// model attributes definition
interface CommentAttributes extends CommentInstanceMethods, waterline.AttributeCollection {
comment: waterline.Attribute;
parent: waterline.Attribute;
likes: waterline.Attribute;
dislikes: waterline.Attribute;
targetId: waterline.Attribute;
targetType: waterline.Attribute;
owner: waterline.Attribute;
}
// instance is formed of props and methods
export interface CommentInstance extends CommentInstanceProps, CommentInstanceMethods { }
export interface CommentCollectionMethods {
/**
* getByTarget fetches comments based on the given target informations (e.g. artwork id)
*
* This function is more special as it fetches the comments differently than the normal usage of the
* sails REST interface;
*
* Given a few predefined constants from configuration, this function will fetch comments in the following
* order:
* 1. Fetch comments that were made by the target resource's (e.g. artwork) owner; The number of comments made by the owner
* is defined in configs as ownerCommentsLimit
* 2. Fetch comments sorted by the number of likes. This sort is applied only for comments with the number of likes bigger than
* a like threshold defined in configs as likeThreshold
* 3. Fetch comments sorted by comment createdAt field. The sort will apply to comments that are not found in one of the above lists
* 4. With the above criteria, apply limit and skip field
* 5. Optional: If the includeComment field is included in the query object, it will also fetch that comment too, with its parent comment
* (if any)
*
* @param {String} targetId e.g. artwork's id
* @param {String} targetType e.g. artwork
* @param {Object} [query] configuration object
* @param {Number} query.limit the number of parent comments to fetch
* @param {Number} query.skip the number of parent comments to skip
* @param {String} query.includeComment the additional comment to include in the list (will overflow limit)
* @param {Number} query.ownerCommentsLimit the number of comments to fetch, that were made by the owner
* @param {Number} query.likeThreshold the number of likes for which to sort by the number of likes
* @param {Number} query.childrenToFetch the number of child comments to fetch for each of parent comments
* @return {Promise} a promise which resolves to the list of fetched comments
*/
getByTarget(targetId: string, targetType: string, query: any): Promise<CommentInstance[]>;
fetchIfExists(criteria: any): Promise<CommentInstance>;
addLike(commentId: string, fromUserId: string): Promise<void>;
removeLike(commentId: string, fromUserId: string): Promise<void>;
addDislike(commentId: string, fromUserId: string): Promise<void>;
removeDislike(commentId: string, fromUserId: string): Promise<void>;
/**
* @private
*/
modifyRating(commentId: string, type: string, value: number): Promise<void>;
}
export interface CommentCollection extends CommentCollectionMethods, waterline.Model<CommentInstance> {
}
// model definition that should be exported from model
export interface CommentDefs extends CommentCollectionMethods, waterline.ModelInstancePopulator<CommentInstance>, waterline.ModelDefinition<CommentInstance, CommentAttributes> { }
}
}
Good luck :)
Good use of 'Promise'.
BTW: To get around the cumbersome model definition synchronisation problem I just wrote compilers using TypeScript's API.
@SamuelMarks Hmm... Now that's something worth checking :+1: So does this gets included in your library mentioned above?
I so, I can't believe I've polluted this thread so much just to find out a library that does all these things for you automatically... :)
Most helpful comment
Hi guys. I have came up with definitions for waterline as well, but I created only the needed interfaces for working with sailsJs rather than working only with Waterline.