{"id":29215343,"url":"https://github.com/shlomiassaf/tdm","last_synced_at":"2025-07-03T00:09:22.858Z","repository":{"id":57164739,"uuid":"85106998","full_name":"shlomiassaf/tdm","owner":"shlomiassaf","description":"Typed data models, mappers and adapters","archived":false,"fork":false,"pushed_at":"2019-05-12T14:57:57.000Z","size":26560,"stargazers_count":11,"open_issues_count":4,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-23T15:48:21.977Z","etag":null,"topics":["active-record","angular","dao","orm","typescript"],"latest_commit_sha":null,"homepage":"https://shlomiassaf.github.io/tdm","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shlomiassaf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-15T18:24:30.000Z","updated_at":"2023-04-15T08:45:41.000Z","dependencies_parsed_at":"2022-08-29T18:00:25.985Z","dependency_job_id":null,"html_url":"https://github.com/shlomiassaf/tdm","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/shlomiassaf/tdm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shlomiassaf%2Ftdm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shlomiassaf%2Ftdm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shlomiassaf%2Ftdm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shlomiassaf%2Ftdm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shlomiassaf","download_url":"https://codeload.github.com/shlomiassaf/tdm/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shlomiassaf%2Ftdm/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261808073,"owners_count":23212694,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["active-record","angular","dao","orm","typescript"],"created_at":"2025-07-03T00:09:22.095Z","updated_at":"2025-07-03T00:09:22.803Z","avatar_url":"https://github.com/shlomiassaf.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/shlomiassaf/tdm.svg?branch=master)](https://travis-ci.org/shlomiassaf/tdm)\n\n# TDM - Typed data models\n\nA TypeScript transformation framework. Highly extensible, super typed.\n\nGiven a data structure, instructions and some logic we can do a lot.  \nTransform an object into an http request and and incoming payload into an object or maybe a model into a form?\n\nTransformation can be cosmetic or even 1:1 but can also completely change the way an object looks like.  \n\nTDM is is a collection of libraries that use Models, Meta-Programming and adapters to abstract away mundane by natively describing schematic rules that translate into operations. \n\n```ts\n@Exclude()\nexport class MyModel {\n \n  @Identity()\n  @Prop()\n  id: number;\n  \n  @Prop()\n  name: string;\n}\n```\n\nIn words, the code above says:  \nThere is a `data structure / Model` called **MyModel** that has 2 properties: `id` and `name`.  \n**MyModel** is strict (`@Exclude`) so only selected properties can play the transformation game.  \nThe property **id** is also the identity (`@Identity()`).  \n\nReviewing the opening statement:  \n\u003e Given a data structure, instructions and some logic we can do a lot.  \n \nOur `data structure / Model` is the class.\nThe `instructions / Meta-Programming` are part of the model defined via decorators.  \n\nWe can now use logic units (adapters) to transform the model.  \nWe can change the logic units or build custom logic.  \nWe can extends the required instructions to supports more complex logic units.\n \nTDM is built on top of the low level libraries `@tdm/tixin`, `tdm/transformation`, `@tdm/core`.  \n\nTDM relays on adapters, currently the only adapter is for Angular 2.\n\nStatus - Alpha.\n\nMain packages:\n\n### @tdm/transformation  \nExtensible metadata storage and a Class transformation library (serialize / deserialize)\n\n```ts\n@Exclude()\nexport class MyClass {\n \n  @Identity()\n  @Prop()\n  id: number;\n  \n  @Prop()\n  name: string;\n}\n```\n\nSupports custom mapping (transforming) implementations.\nComes with a built in direct mapper. Direct mapping means 1:1 mapping, no schema or document structure.\n\n\u003e A JSON API mapper can be used in `@tdm/json-api-mapper`\n\nCreate custom mappers by implementing the required interface.\n \n`@tdm/transformation` comes with basic relationship (`@Relation`) support.\n \n### @tdm/core\nCore library, extends `@tdm/transformation`.\n`@tdm/core` comes with a lot of features...\n\n  - Customizable Action based commands\n  - Build in CRUD actions\n  - Advanced Relationships (BelongsTo, Owns)\n  - Static/instance level Actions (e.g.: `query` is always static)\n  - Hooks for built in actions\n  - Adapter architecture\n  - Event stream (using observables)\n  - DAO pattern\n  - Active records pattern with full Type support. (plugin, need to opt in)\n  - more..\n\n### @tdm/angular-forms-mapper (name might change)\nMap data models defined with `@tdm/transformation` and `@tdm/core` into and back from `@angular/forms` **FormGroup** / **FormArray**\n\n### @tdm/angular-http (name might change)\nAn adapter implementation for the angular (2) library.\n\nWith `@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.\n\n\u003e This is an Active record implementation, you can also go pure and use a DAO leaving your models clean.\n\n```ts\n@HttpResource({\n  endpoint: '/api/users/:id?',\n  urlParams: {\n    limit: '5' // not in endpoint so will fallback to query string\n  },\n  noBuild: true\n})\nclass User_ {\n\n  @Identity()\n  @UrlParam({ // optionally set what methods to use the param on.\n    methods: [HttpActionMethodType.Get,\n              HttpActionMethodType.Delete,\n              HttpActionMethodType.Patch,\n              HttpActionMethodType.Put]\n  }) id: number; // this will go into the \"endpoint\" from the instance!\n\n  @Prop({\n    validation: { // custom validation\n      name: 'test-validator',\n      validate(ctx) {\n        return false;\n      },\n      errorMessage(ctx) {\n        return 'validation error';\n      }\n    }\n  })\n  username: string;\n\n\n  @Prop({\n    alias: 'motto_abc' // server returns motto_abc\n  })\n  @Exclude()\n  motto: string;\n\n  constructor() { }\n\n  @Hook({event: 'before', action: '$refresh'})\n  bfRef() {\n    console.log('BeforeRefresh');\n  }\n\n  @HttpAction({ // custom HTTP actions\n    method: HttpActionMethodType.Get,\n    raw: {\n      handler: User_.prototype.rawDeserializedHandler,\n      deserialize: true\n    }\n  })\n  rawDeserialized: (options?: HttpActionOptions) =\u003e RestMixin\u003cUser_\u003e;\n  private rawDeserializedHandler(resp: ExecuteResponse, options?: HttpActionOptions) {\n  }\n\n  @HttpAction({\n    method: HttpActionMethodType.Get,\n    raw: User_.prototype.rawHandler\n  })\n  raw: (options?: HttpActionOptions) =\u003e RestMixin\u003cUser_\u003e;\n  private rawHandler(resp: ExecuteResponse, options?: HttpActionOptions) {\n  }\n\n\n  @Hook({event: 'before', action: 'query'}) // static hooks (for static actions, like query)\n  static bfQuery(this: ActiveRecordCollection\u003cRestMixin\u003cUser_\u003e\u003e) {\n    this.$ar.next()\n      .then( coll =\u003e {\n        console.log(`BeforeQuery-AfterQuery: got ${coll.collection.length}`)\n      });\n    console.log('BeforeQuery');\n  }\n\n  @ExtendAction({ // extending a built in action, useful if you need the user to provide more params)\n    pre: (ctx: ExecuteContext\u003cany\u003e, id: IdentityValueType, a:number, b: number, options: HttpActionOptions) =\u003e {\n      ctx.data[ctx.adapterStore.identity] = id;\n      return options;\n    }\n  })\n  static find: (id: IdentityValueType, a:number, b: number, options?: HttpActionOptions) =\u003e RestMixin\u003cUser_\u003e;\n}\n\nexport const User = RestMixin(User_);\nexport type User = RestMixin\u003cUser_\u003e;\n```\n\nYou can also do\n```ts\n@HttpResource({\n  endpoint: '/api/users/:id?',\n  urlParams: { // there are hard coded params\n    limit: '5' // not in path so will go to query string (?param=15)\n  },\n})\nexport class User extends RestMixin(User_) { }\n```\n\nTDM supports multiple ways to declare a Resource, you can see them all in `src/demo/resource/Users`  \nUsing `RestMixin` is mandatory, at least until the TypeScript team will implement type reflection for `ClassDecorator`\n\n\u003e `@tdm/angular-http` Supports AOT but currently Resources are not injectable!\n\n\n**You can then use the resources:**\n\n```ts\nUser.find(2).username;                             // OK\nconst user: User = new User();                     // OK\nuser.id = 15;\nuser.$refresh().username;                                          // OK\nuser.$refresh().abcd;                                                // SHOULD ERROR\n\n// Using async system with promises:\nuser.$ar.next().then( u =\u003e u.id );                                   // OK\nuser.$ar.next().then( u =\u003e u.f34 );                                  // SHOULD ERROR\n\n// hadnling collections, coll.collection is of type User[]\nUserBaseClass.query().$ar.next().then(coll =\u003e coll.collection );     // OK\nUserBaseClass.query().$ar.next().then(coll =\u003e coll.sdfd );           // SHOULD ERROR\n\n// Using async system with observables\nuser.$ar.events$.subscribe(...)\n\n// `$events` is an observable of resource events (succes, failure, cancelled etc...)\n\n\n\n// Cacnelling \nuser.$ar.cancel()\n\n// disconnecting all observables\nuser.$ar.disconnect()\n\n// busy (in-flight) indicator\nuser.$ar.busy // true / false\n\n// busy (in-flight) indicator STREAM\nuser.$ar.busy$.subscribe(...) // true / false\n```\n\n\u003e The `$ar` object is an optional extension, you can opt in via `import '@tdm/core/add/active-record-state';`\n\n\n\u003e The promise extension `next()` is optional extension, you can opt in via `import '@tdm/core/add/active-record-state/next';`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshlomiassaf%2Ftdm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshlomiassaf%2Ftdm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshlomiassaf%2Ftdm/lists"}