@ts-awesome/orm 1.3.0-rc2 → 1.3.0-rc3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,102 +1,297 @@
1
- # ts-orm
1
+ # @ts-awesome/orm
2
2
 
3
- ## @dbTable(
4
- * **`tableName`**: *`string`* - Table name
5
- * **`uniqueIndexes?[]`** - Array of uniqueIndexes meta
6
- * **`name`**: *`string`* - Index name
7
- * **`fields`**: *`string[]`* - Array of field related to index
8
- * **`default?`**: *`boolean`* - Determines used that index as a default in query like upsert
9
- * **`where?`**: *WhereBuilder* - Where condition that used in upsert query (This is specific for postgres db)
3
+ TypeScript friendly minimalistic Object Relation Mapping library
10
4
 
11
- )
5
+ Key features:
12
6
 
13
- ## @dbField({
14
- * **`name?`**: *`string`* - Field name. Use model property name by default.
15
- * **`primaryKey?`**: *`true`* - Is field primary key or not (*false by default*).
16
- * **`readonly?`**: *`true`* - Is field readonly (*false by default*).
17
- * **`autoIncrement?`**: *`true`* - Is field auto increment (*false by default*).
18
- * **`default?`**: *`T | DbDefault`* - Field has default value. Specify value or indicate db column has default with DbDefault
19
- * **`sensitive?`**: *`true`* - Fields contains sensitive data and will be masked with undefined (*false by default*)
20
- * **`kind?`**: *`IDbField`* - Field parser/serializer options
7
+ * strong object mapping with [@ts-awesome/model-reader](https://github.com/ts-awesome/model-reader)
8
+ * no relation navigation - intentional
9
+ * heavy use of type checks and lambdas
10
+ * support common subset of SQL
21
11
 
22
- })
12
+ ## Model declaration
23
13
 
24
- ## @dbManyField({
25
- * **`table`**: *`string | TableClass`* - Name of related table.
26
- * **`keyField`**: *`string`* - Field from **table** that is matched with this table's primary key.
27
- * **`valueField`**: *`string`* - Related table's field that is used as a source of values.
14
+ Each model metadata is defined with `dbTable` and `dbField` decorators
28
15
 
29
- })
16
+ ```ts
17
+ import {dbField, dbField} from "@ts-awesome/orm";
18
+ import {DB_JSON} from "@ts-awesome/orm-pg"; // or other driver
30
19
 
31
- ## Example
20
+ @dbTable('first_table')
21
+ class FirstModel {
22
+ // numeric autoincrement primary key
23
+ @dbField({primaryKey: true, autoIncrement: true})
24
+ public id!: number;
32
25
 
33
- ```ts
34
- @dbTable("user")
35
- export class UserModel {
26
+ // just another field
27
+ @dbField
28
+ public title!: string;
36
29
 
30
+ // lets map prop to different field
31
+ @dbField({name: 'author_id'})
32
+ public authorId!: number;
33
+
34
+ // nullable field requires explicit model and nullable
35
+ // these are direct match to @ts-awesome/model-reader
37
36
  @dbField({
38
- autoIncrement: true,
39
- primaryKey: true,
40
- name: "id"
37
+ model: String,
38
+ nullable: true,
41
39
  })
42
- id: number;
40
+ public description!: string | null;
43
41
 
44
- @dbField({
45
- kind: UUID,
42
+ // advanced use case
43
+ @dbModel({
44
+ kind: DB_JSON, // data will be stored as JSON
45
+ model: SubDocumentModel, // and will be converted to instance of SubDocumentModel
46
+ nullable: true,
46
47
  })
47
- uid: string;
48
+ public document!: SubDocumentModel | null
48
49
 
49
- @dbField("username")
50
- userName: string;
50
+ // readonly field with database default
51
+ @dbField({name: 'created_at', readonly: true})
52
+ public createdAt!: Date;
53
+ }
54
+ ```
51
55
 
52
- @dbField({
53
- sensetive: true,
54
- })
55
- password: string;
56
+ ## Vanilla select
57
+
58
+ ```ts
59
+ import {IBuildableQuery, IQueryExecutor, Select} from "@ts-awesome/orm";
60
+ import {ISqlQuery, PgCompiler} from "@ts-awesome/orm-pg"; // or other driver
61
+
62
+ const compiler = new PgCompiler();
63
+ const driver: IQueryExecutor<ISqlQuery>;
64
+
65
+ const query: IBuildableQuery = Select(FirstModel).where({authorId: 5}).limit(10);
66
+ const compiled: ISqlQuery = compiler.compile(query);
67
+ const results: FirstModel[] = await driver.execute(compiled, FirstModel);
68
+ ```
69
+
70
+ For more streamlined use please check [@ts-awesome/entity](https://github.com/ts-awesome/model-reader)
71
+
72
+ ## Select builder
73
+
74
+ ORM provides a way to use model declaration to your advantage: TypeScript will check is fields exists.
75
+ And TypeScript will check operands for compatible types.
76
+
77
+ ```ts
78
+ const query = Select(FirstModel)
79
+ // authorId = 5;
80
+ .where({authorId: '5'}) // gives error, it can be number only
81
+ .limit(10);
82
+ ```
56
83
 
84
+ For more complex logic ORM provides WhereBuilder
85
+
86
+ ```ts
87
+ const query = Select(FirstModel)
88
+ // authorId = 5;
89
+ .where(({authorId}) => authorId.eq(5))
90
+ .limit(10);
91
+ ```
92
+
93
+ ```ts
94
+ const query = Select(FirstModel)
95
+ // authorId in (5, 6)
96
+ .where(({authorId, description}) => authorId.in([5, 6]))
97
+ .limit(10);
98
+ ```
99
+
100
+ ```ts
101
+ const query = Select(FirstModel)
102
+ // authorId = 5 AND description LIKE 'some%';
103
+ .where(({authorId, description}) => and(authorId.eq(5), description.like('some%')))
104
+ .limit(10);
105
+ ```
106
+
107
+ #### Overview of operators and functions:
108
+
109
+ * Generic comparable:
110
+ * left.`eq`(right) equivalent to left `=` right or left `IS NULL` if right === null
111
+ * left.`neq`(right) equivalent to left `<>` right or left `IS NOT NULL` if right === null
112
+ * left.`gt`(right) equivalent to left `>` right
113
+ * left.`gte`(right) equivalent to left `>=` right
114
+ * left.`lt`(right) equivalent to left `<` right
115
+ * left.`lte`(right) equivalent to left `<=` right
116
+ * left.`between`(a, b) equivalent left BETWEEN (a, b)
117
+ * Strings
118
+ * left.`like`(right) equivalent to left `LIKE` right
119
+ * Arrays
120
+ * left.`in`(right) equivalent to left `IN` right
121
+ * left.`has`(right) equivalent to right `IN` left
122
+ * Math
123
+ * left.`add`(right) equivalent to left `+` right
124
+ * left.`sub`(right) equivalent to left `-` right
125
+ * left.`mul`(right) equivalent to left `*` right
126
+ * left.`div`(right) equivalent to left `/` right
127
+ * left.`mod`(right) equivalent to left `%` right
128
+ * Binary logic
129
+ * left.`and`(right) equivalent to left `&` right
130
+ * left.`or`(right) equivalent to left `|` right
131
+ * left.`xor`(right) equivalent to left `^` right
132
+ * Logic
133
+ * `and`(op1, op2, op3) equivalent to op1 `AND` op2 `AND` op3
134
+ * `or`(op1, op2, op3) equivalent to op1 `OR` op2 `OR` op3
135
+ * `not`(op) equivalent to `NOT` op
136
+ * Subqueries
137
+ * `all`(query) equivalent to `ALL` (compiled query)
138
+ * `any`(query) equivalent to `ANY` (compiled query)
139
+ * `exists`(query) equivalent to `EXISTS` (compiled query)
140
+ * Aggregation functions
141
+ * `avg`(expr) equivalent to `AVG` (expr)
142
+ * `max`(expr) equivalent to `MAX` (expr)
143
+ * `min`(expr) equivalent to `MIN` (expr)
144
+ * `sum`(expr) equivalent to `SUM` (expr)
145
+ * `count`(expr) equivalent to `count` (expr)
146
+
147
+ ### Joining
148
+
149
+ Sometimes you may need to perform some joins for filtering
150
+
151
+ ```ts
152
+ import {dbTable, dbField} from "@ts-awesome/orm";
153
+
154
+ @dbTable('second_table')
155
+ class SecondModel {
156
+ @dbField({primatyKey: true, autoIncrement: true})
157
+ public id!: number;
158
+
57
159
  @dbField
58
- email?: string | null;
160
+ public name!: string;
161
+ }
162
+
163
+ const query = Select(FirstModel)
164
+ // lets join SecondModel by FK
165
+ .join(SecondModel, (root, other) => root.authorId.eq(other.id))
166
+ // lets filter by author name
167
+ .where(() => of(SecondModel, 'name').like('John%'))
168
+ .limit(10)
169
+ ```
59
170
 
171
+ In some cases `TableRef` might be handy, especially of need to join same table multiple times
172
+
173
+ ```ts
174
+ import {dbTable, dbField} from "@ts-awesome/orm";
175
+
176
+ @dbTable('second_table')
177
+ class SecondModel {
178
+ @dbField({primatyKey: true, autoIncrement: true})
179
+ public id!: number;
180
+
60
181
  @dbField
61
- type?: UserType;
182
+ public name!: string;
183
+ }
62
184
 
63
- @dbField({
64
- name: 'creationdate',
65
- readonly: true,
66
- })
67
- creationDate?: Date;
185
+ @dbTable('third_table')
186
+ class ThirdModel {
187
+ @dbField({primatyKey: true, autoIncrement: true})
188
+ public id!: number;
68
189
 
69
- @dbField({
70
- name: 'lastmodified',
71
- readonly: true,
190
+ @dbField
191
+ public createdBy!: number;
192
+
193
+ @dbField
194
+ public ownedBy!: number;
195
+ }
196
+
197
+ const ownerRef = new TableRef(SecondModel);
198
+ const creatorRef = new TableRef(SecondModel);
199
+ const query = Select(ThirdModel)
200
+ // lets join SecondModel by FK
201
+ .join(SecondModel, ownerRef, (root, other) => root.ownedBy.eq(other.id))
202
+ // lets join SecondModel by FK
203
+ .join(SecondModel, creatorRef, (root, other) => root.createdBy.eq(other.id))
204
+ // lets filter by owner or creator name
205
+ .where(() => or(
206
+ of(ownerRef, 'name').like('John%'),
207
+ of(creatorRef, 'name').like('John%'),
208
+ ))
209
+ .limit(10)
210
+ ```
211
+
212
+ ### Grouping
213
+
214
+ ```ts
215
+ import {Select, min, count, alias} from '@ts-awesome/orm'
216
+
217
+ const ts: Date; // some timestamp in past
218
+ const query = Select(FirstModel)
219
+ // we need titles to contain `key`
220
+ .where(({title}) => title.like('%key%'))
221
+ // group by authors
222
+ .groupBy(['authorId'])
223
+ // filter to have first publication not before ts
224
+ .having(({createdAt}) => min(createdAt).gte(ts))
225
+ // result should have 2 columns: authorId and count
226
+ .columns(({authorId}) => [authorId, alias(count(), 'count')])
227
+ ```
228
+
229
+ ### Ordering
230
+
231
+ ```ts
232
+ import {Select, desc} from '@ts-awesome/orm'
233
+
234
+ const query = Select(FirstModel)
235
+ // lets join SecondModel by FK
236
+ .join(SecondModel, (root, other) => root.authorId.eq(other.id))
237
+ // lets sort by author and title reverse
238
+ .orderby(({title}) => [of(SecondModel, 'name'), desc(title)])
239
+ .limit(10)
240
+ ```
241
+
242
+ ## Other builders
243
+
244
+ ORM provides `Insert`, `Update`, `Upset` and `Delete` builders
245
+
246
+ ### Insert
247
+
248
+ ```ts
249
+ import {Insert} from '@ts-awesome/orm';
250
+
251
+ const query = Insert(FirstModel)
252
+ .values({
253
+ title: 'New book'
72
254
  })
73
- lastModified?: Date;
255
+ ```
256
+
257
+ ### Update
258
+
259
+ ```ts
260
+ import {Update} from '@ts-awesome/orm';
74
261
 
75
- @dbManyField({
76
- table: 'user_bus',
77
- keyField: 'userId',
78
- valueField: 'busId',
262
+ const query = Update(FirstModel)
263
+ .values({
264
+ title: 'New book'
79
265
  })
80
- busIds?: number[];
81
- }
266
+ .where(({id}) => id.eq(2))
82
267
  ```
83
268
 
84
- ### Setup your container and bindings with `@ts-awesome/orm-pg`
269
+ ### Upsert
85
270
 
86
271
  ```ts
87
- container.bind<pg.Pool>(Symbol.for('PgPool'))
88
- .toConstantValue(new pg.Pool({})); // TODO: provide db config
272
+ import {Upsert} from '@ts-awesome/orm';
89
273
 
90
- container
91
- .bind<IBuildableQueryCompiler<ISqlQuery>>(Symbols.SqlQueryCompiler)
92
- .to(PgCompiler);
274
+ const query = Upsert(FirstModel)
275
+ .values({
276
+ title: 'New book'
277
+ })
278
+ .where(({id}) => id.eq(2))
279
+ // conflict resolution index is defined in @dbTable decorator
280
+ .conflict('index_name')
281
+ ```
93
282
 
94
- container.bind<IQueryDriver<ISqlQuery>>(Symbols.SqlQueryDriver)
95
- .toDynamicValue(({container}: interfaces.Context) => {
96
- return new PgDriver(container.get<pg.Pool>(Symbol.for('PgPool')));
97
- });
98
283
 
99
- container
100
- .bind<IDbDataReader<UserModel>>(Symbols.dbReaderFor(UserModel))
101
- .toConstantValue(new DbReader(UserModel));
284
+ ### Delete
285
+
286
+ ```ts
287
+ import {Delete} from '@ts-awesome/orm';
288
+
289
+ const query = Delete(FirstModel)
290
+ .where(({authorId}) => authorId.eq(2))
102
291
  ```
292
+
293
+
294
+ # License
295
+ May be freely distributed under the [MIT license](https://opensource.org/licenses/MIT).
296
+
297
+ Copyright (c) 2022 Volodymyr Iatsyshyn and other contributors
@@ -7,7 +7,7 @@ interface IDBIndexMeta<T> {
7
7
  }
8
8
  export declare function dbTable<TFunction extends Function>(target: TFunction): TFunction | void;
9
9
  export declare function dbTable<T>(tableName?: string, uniqueIndexes?: readonly IDBIndexMeta<T>[]): ClassDecorator;
10
- interface IDBFieldMeta extends Omit<IFieldInfo, 'getValue' | 'relatedTo' | 'name'> {
10
+ interface IDBFieldMeta extends Omit<IFieldInfo, 'getValue' | 'relatedTo' | 'name' | 'builder'> {
11
11
  name?: string;
12
12
  }
13
13
  export declare function dbField(target: any, key: string): void;
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@ts-awesome/orm",
3
- "version": "1.3.0-rc2",
4
- "description": "TypeScript ORM",
3
+ "version": "1.3.0-rc3",
4
+ "description": "TypeScript friendly minimalistic ORM",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/ts-awesome/orm"
10
+ },
7
11
  "scripts": {
8
12
  "build:dev": "tsc --project tsconfig.json",
9
13
  "build": "npm run build:dev && cp src/wrappers* dist/",