Data access object library for TypeScript, highly extensible, super typed.
TDM is built on top of the low level libraries @tdm/tixin
, tdm/transformation
, @tdm/core
.
TDM relays on adapters, currently the only adapter is for Angular 2.
Status - Alpha.
Main packages:
Extensible metadata storage and a Class transformation library (serialize / deserialize)
@Exclude()
export class MyClass {
@Identity()
@Prop()
id: number;
@Prop()
name: string;
}
Supports custom mapping (transforming) implementations. Comes with a built in direct mapper. Direct mapping means 1:1 mapping, no schema or document structure.
A JSON API mapper can be used in
@tdm/json-api-mapper
Create custom mappers by implementing the required interface.
@tdm/transformation
comes with basic relationship (@Relation
) support.
Core library, extends @tdm/transformation
and the active record pattern.
@tdm/core
comes with a lot of features...
- Customizable Action based commands
- Build in CRUD actions
- Advanced Relationships (BelongsTo, Owns)
- Static/instance level Actions (e.g.:
query
is always static) - Hooks for built in actions
- Adapter architecture
- Event stream (using observables)
- DAO pattern
- Active records pattern with full Type support. (need to opt in)
- more..
Map data models defined with @tdm/transformation
and @tdm/core
into and back from @angular/forms
FormGroup / FormArray
An adapter implementation for the angular (2) library.
With @tdm/angular-http
Model classes become Resource much like in angular 1 ng-resource
but now with full typescript Type support for model properties, methods and Active record methods.
This is an Active record implementation, you can also go pure and use a DAO leaving your models clean.
@HttpResource({
endpoint: '/api/users/:id?',
urlParams: {
limit: '5' // not in endpoint so will fallback to query string
},
noBuild: true
})
class User_ {
@Identity()
@UrlParam({ // optionally set what methods to use the param on.
methods: [HttpActionMethodType.Get,
HttpActionMethodType.Delete,
HttpActionMethodType.Patch,
HttpActionMethodType.Put]
}) id: number; // this will go into the "endpoint" from the instance!
@Prop({
validation: { // custom validation
name: 'test-validator',
validate(ctx) {
return false;
},
errorMessage(ctx) {
return 'validation error';
}
}
})
username: string;
@Prop({
alias: 'motto_abc' // server returns motto_abc
})
@Exclude()
motto: string;
constructor() { }
@Hook({event: 'before', action: '$refresh'})
bfRef() {
console.log('BeforeRefresh');
}
@HttpAction({ // custom HTTP actions
method: HttpActionMethodType.Get,
raw: {
handler: User_.prototype.rawDeserializedHandler,
deserialize: true
}
})
rawDeserialized: (options?: HttpActionOptions) => RestMixin<User_>;
private rawDeserializedHandler(resp: ExecuteResponse, options?: HttpActionOptions) {
}
@HttpAction({
method: HttpActionMethodType.Get,
raw: User_.prototype.rawHandler
})
raw: (options?: HttpActionOptions) => RestMixin<User_>;
private rawHandler(resp: ExecuteResponse, options?: HttpActionOptions) {
}
@Hook({event: 'before', action: 'query'}) // static hooks (for static actions, like query)
static bfQuery(this: ActiveRecordCollection<RestMixin<User_>>) {
this.$ar.next()
.then( coll => {
console.log(`BeforeQuery-AfterQuery: got ${coll.collection.length}`)
});
console.log('BeforeQuery');
}
@ExtendAction({ // extending a built in action, useful if you need the user to provide more params)
pre: (ctx: ExecuteContext<any>, id: IdentityValueType, a:number, b: number, options: HttpActionOptions) => {
ctx.data[ctx.adapterStore.identity] = id;
return options;
}
})
static find: (id: IdentityValueType, a:number, b: number, options?: HttpActionOptions) => RestMixin<User_>;
}
export const User = RestMixin(User_);
export type User = RestMixin<User_>;
You can also do
@HttpResource({
endpoint: '/api/users/:id?',
urlParams: { // there are hard coded params
limit: '5' // not in path so will go to query string (?param=15)
},
})
export class User extends RestMixin(User_) { }
TDM supports multiple ways to declare a Resource, you can see them all in src/demo/resource/Users
Using RestMixin
is mandatory, at least until the TypeScript team will implement type reflection for ClassDecorator
@tdm/angular-http
Supports AOT but currently Resources are not injectable!
You can then use the resources:
User.find(2).username; // OK
const user: User = new User(); // OK
user.id = 15;
user.$refresh().username; // OK
user.$refresh().abcd; // SHOULD ERROR
// Using async system with promises:
user.$ar.next().then( u => u.id ); // OK
user.$ar.next().then( u => u.f34 ); // SHOULD ERROR
// hadnling collections, coll.collection is of type User[]
UserBaseClass.query().$ar.next().then(coll => coll.collection ); // OK
UserBaseClass.query().$ar.next().then(coll => coll.sdfd ); // SHOULD ERROR
// Using async system with observables
user.$ar.events$.subscribe(...)
// `$events` is an observable of resource events (succes, failure, cancelled etc...)
// Cacnelling
user.$ar.cancel()
// disconnecting all observables
user.$ar.disconnect()
// busy (in-flight) indicator
user.$ar.busy // true / false
// busy (in-flight) indicator STREAM
user.$ar.busy$.subscribe(...) // true / false
The
$ar
object is an optional extension, you can opt in viaimport '@tdm/core/add/active-record-state';
The promise extension
next()
is optional extension, you can opt in viaimport '@tdm/core/add/active-record-state/next';