mythix-orm 1.0.3 → 1.2.0

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/.biblorc.js ADDED
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ /* global __dirname */
4
+
5
+ const Path = require('path');
6
+
7
+ module.exports = {
8
+ rootDir: __dirname,
9
+ inputDir: Path.resolve(__dirname),
10
+ outputDir: Path.resolve(__dirname, '..', 'mythix-orm.wiki'),
11
+ files: [
12
+ {
13
+ include: /\/lib\/.*\.js$/,
14
+ parser: 'typescript',
15
+ compiler: 'typescript',
16
+ },
17
+ {
18
+ include: /\/docs\/.*\.md$/,
19
+ parser: 'markdown',
20
+ compiler: 'markdown',
21
+ },
22
+ ],
23
+ exclude: [
24
+ /node_modules|\/spec\//
25
+ ],
26
+ generatorOptions: {
27
+ baseURL: './',
28
+ },
29
+ };
@@ -0,0 +1,482 @@
1
+ # Mythix ORM associations
2
+
3
+ Mythix ORM makes associations really easy.
4
+
5
+ Fields in a Mythix ORM model can be either "virtual" or "concrete".
6
+
7
+ Concrete fields are backed by storage (the database), and will have a direct value they can be associated with.
8
+
9
+ Virtual fields are not backed by storage (at least not directly), and instead will dynamically fetch their value.
10
+
11
+ A field can be virtual in Mythix ORM simply because of its type. For example, the types `Types.Model` and `Types.Models` will by nature define a virtual field. These types define relations to other tables, and so don't have a 1x1 concrete field in the DB themselves, and instead will pull data based on the relationship defined by the type.
12
+
13
+ Before we get started, there are a few things to keep in mind:
14
+
15
+ 1. `Types.Model` is used for a 1x1 relationship
16
+ 2. `Types.Models` is used for a one-to-many, or a many-to-many relationship
17
+ 3. These two types specify field relationships
18
+ 4. In field relationships, it is common to specify a target model, which is done by prefixing the field with the model name plus a single colon (i.e. `User:id`)
19
+ 5. In field relationships, it is common to specify a target field (which optionally comes after the model name and colon)
20
+ 6. In field relationships, if a model isn't defined, then `Self` ("this model") is assumed
21
+ 7. In field relationships, if a field isn't defined, then the target model's primary key is assumed
22
+ 8. Field relationships work by defining a "target", and a "value provider" (where to get the value to match against the target)
23
+ 9. All concrete fields must be manually defined on all models. There is no "automagic" or "hidden" fields that the relationships themselves will create. Said another way, **you must manually define ALL table columns, always, without exception**... none will be auto-defined for you. If you want auto-defined fields, such as "createdAt", and "updatedAt" for all your models, then the recommendation is to define a BaseModel that all of your other models inherit from.
24
+
25
+ To properly understand how Mythix ORM creates table relationships/associations, it helps to view the situation from the perspective of "this model instance". Don't think of "tables", or "many" when you are thinking of the relationship. Think only of "this model instance".
26
+
27
+ ## Example #1 - Defining a 1x1 relationship
28
+
29
+ Let's say you want to have a Users table, and have each user have a single Role.
30
+
31
+ This is a 1x1 relationship, with one User to one Role.
32
+
33
+ You could define the association like so:
34
+
35
+ ```javascript
36
+ class User extends Model {
37
+ static fields = {
38
+ 'id': {
39
+ type: Types.UUIDV4,
40
+ allowNull: false,
41
+ primaryKey: true,
42
+ },
43
+ 'firstName': {
44
+ type: Types.STRING(64),
45
+ allowNull: true,
46
+ index: true,
47
+ },
48
+ 'lastName': {
49
+ type: Types.STRING(64),
50
+ allowNull: true,
51
+ index: true,
52
+ },
53
+ 'roleID': {
54
+ type: Types.UUIDV4,
55
+ allowNull: false,
56
+ index: true,
57
+ },
58
+ // This defines a "virtual" field,
59
+ // that will be used to define
60
+ // methods to interact with the role
61
+ 'role': {
62
+ // Remember "target field" and "value provider"
63
+ // If no model is specified, then "Self" is assumed
64
+ // so 'roleID', could be expanded to 'User:roleID'
65
+ //
66
+ // target value provider
67
+ type: Types.Model('Role:id', 'roleID'),
68
+ },
69
+ };
70
+ }
71
+
72
+ class Role extends Model {
73
+ static fields = {
74
+ 'id': {
75
+ type: Types.UUIDV4,
76
+ allowNull: false,
77
+ primaryKey: true,
78
+ },
79
+ 'name': {
80
+ type: Types.STRING(64),
81
+ allowNull: false,
82
+ index: true,
83
+ },
84
+ // This defines a "virtual" field,
85
+ // that will be used to define
86
+ // methods to interact with the user
87
+ 'user': {
88
+ // Second argument could be omitted here,
89
+ // since the model's PK is assumed if not
90
+ // specified
91
+ type: Types.Model('User:roleID', 'id'),
92
+
93
+ // Equivalent
94
+ // type: Types.Model('User:roleID'),
95
+ },
96
+ };
97
+ }
98
+ ```
99
+
100
+ Let's look at the `User` model `role` field first, which is of the type:
101
+
102
+ ```javascript
103
+ type: Types.Model('Role:id', 'roleID'),
104
+ ```
105
+
106
+ This may seem confusing at first, until you understand the pattern here. The pattern is fairly simple. The `Types.Model` is singular (`Models` on the other hand, if used, defines a one-to-many, or many-to-many relationship), so right away we know this is going to fetch a single model, and not many models. It is a 1x1 relationship. Next, we have the two arguments we provide. It helps a lot to think of these as "target", and "value provider"... as in, "What is the target field we are matching on, and who/what is providing the value to be matched against?" It also helps quite a bit to think of this from the perspective of "this model instance", instead of "this table", or "this relationship". The relationship is always from an instance of a model... a single instance (or a single row in the DB, if that helps instead).
107
+
108
+ With this in mind, the above model definitions should make a little more sense:
109
+
110
+ 1. From the perspective of this specific user, what are we targeting?
111
+ 2. We are targeting the `Role` model, and its `id` field
112
+ 3. Great! Now, who is providing the value to match against the `Role:id` field?
113
+ 4. Oh, that is easy, it is me, this specific user, and the field to pull the `Role:id` from is `User:roleID`... as in, this very user's `roleID` ("self.roleID").
114
+
115
+ Simple!
116
+
117
+ From the perspective of the Role it is very similar, except the "value provider" has switched to the `User` model, where the `roleID` field exists.
118
+
119
+ Let's look at it from the `Role` perspective now.
120
+
121
+ We have:
122
+
123
+ ```javascript
124
+ type: Types.Model('User:roleID', 'id'),
125
+ ```
126
+
127
+ Remember, this could also be written as:
128
+
129
+ ```javascript
130
+ type: Types.Model('User:roleID'),
131
+ ```
132
+
133
+ When no model is specified in the "target", or the "value provider" arguments, then `Self` is implied. So when we say `'id'`, what is really being specified is `Role:id`. The PK of a model is assumed as the default if no "value provider" is given. So, `Role:id`, `id`, and empty are all equivalent in this situation.
134
+
135
+ So now, let's take a look at the process here:
136
+
137
+ 1. From the perspective of this specific role, what are we targeting?
138
+ 2. We are targeting the `User` model, and its `roleID` field
139
+ 3. Great! Now, who is providing the value to match against the `User:roleID` field?
140
+ 4. Oh, that is easy, it is me, this specific role, and the field to pull the `User:roleID` value from is `Role:id`... as in, this very roles's primary key `id`
141
+
142
+ One thing that is important to know is that the model name defined in the *first* argument will be the actual model type fetched. So, from the perspective of the user, we specify `Role` as the model in the first argument, so this is the role model type that will be fetched. From the perspective of the role, we specify `User` as the model in the first argument, so this is the model type that will be fetched.
143
+
144
+ ## Example #2 - Defining a One To Many relationship
145
+
146
+ Okay, so your boss comes in on Monday morning, and yells at you for implementing the previous example, because users only being able to have a single role is "just plain stupid... what were you thinking?"
147
+
148
+ Well, it is hard to argue with the boss, especially since he is correct, users only being able to have a single role is a just a *wee* bit limiting. So, lets update this so a user can have many roles. To do so, we will need to define a one-to-many relationship.
149
+
150
+ Let's jump right into the code this time:
151
+
152
+ ```javascript
153
+ class User extends Model {
154
+ static fields = {
155
+ 'id': {
156
+ type: Types.UUIDV4,
157
+ allowNull: false,
158
+ primaryKey: true,
159
+ },
160
+ 'firstName': {
161
+ type: Types.STRING(64),
162
+ allowNull: true,
163
+ index: true,
164
+ },
165
+ 'lastName': {
166
+ type: Types.STRING(64),
167
+ allowNull: true,
168
+ index: true,
169
+ },
170
+ // Let's drop this column, because now it makes
171
+ // no sense to have it live on the User table
172
+ // 'roleID': {
173
+ // type: Types.UUIDV4,
174
+ // allowNull: false,
175
+ // index: true,
176
+ // },
177
+ //
178
+ // Let's change "role" to "roles", because
179
+ // now it will be plural (one to many)
180
+ 'roles': {
181
+ // Here we don't need to define a second argument
182
+ // because "Self" and "PK" are assumed, so this
183
+ // works fine. We want the User.id to match against
184
+ // the Role.userID, so we can just leave off the
185
+ // second argument
186
+ //
187
+ // Notice the use of "plural" "Models" here
188
+ type: Types.Models('Role:userID'),
189
+
190
+ // Equivalent:
191
+ // type: Types.Models('Role:userID', 'User:id'),
192
+ //
193
+ // or:
194
+ //
195
+ // type: Types.Models('Role:userID', 'id'),
196
+ },
197
+ };
198
+ }
199
+
200
+ class Role extends Model {
201
+ static fields = {
202
+ 'id': {
203
+ type: Types.UUIDV4,
204
+ allowNull: false,
205
+ primaryKey: true,
206
+ },
207
+ 'name': {
208
+ type: Types.STRING(64),
209
+ allowNull: false,
210
+ index: true,
211
+ },
212
+ // Now we need to define the "userID"
213
+ // for each role
214
+ 'userID': {
215
+ type: Types.UUIDV4,
216
+ allowNull: false,
217
+ index: true,
218
+ },
219
+ // This is still correctly named, as
220
+ // each role will only link back to
221
+ // a single user
222
+ 'user': {
223
+ // Now we update this, flipping the
224
+ // relationship. If we store a "userID"
225
+ // on the Role table, then a user can
226
+ // have many roles.
227
+ type: Types.Model('User', 'userID'),
228
+
229
+ // Equivalent
230
+ // type: Types.Model('User:id', 'userID'),
231
+ //
232
+ // or:
233
+ //
234
+ // type: Types.Model('User:id', 'Role:userID'),
235
+ },
236
+ };
237
+ }
238
+ ```
239
+
240
+ Hopefully this makes sense. We simply removed `roleID` from the User model, and moved it over to instead be `userID` on the other side of the relationship. Now a user can have many roles, because instead of each user only defining a single `roleID`, now Roles define a `userID`, and since many roles can define the same `userID`, a user can have many roles.
241
+
242
+ ## Example #3 - Foreign Keys
243
+
244
+ Great! So far so good. Hopefully my reader is still following me. What we have done so far will work, but there is a better way. We can have the database assist us in our relationships. If you don't know what "foreign keys" are, or don't understand the concept behind them, I suggest you go take a moment to read about them. In short, "foreign keys" simultaneously define an index, and one or more constraints. This means the database will disallow certain things. For example, if you specify that a relationship between a user and a role MUST exist, and MUST be valid, then the database will throw an error if you try to add a role without a user.
245
+
246
+ Let's go ahead and do that now. After all, we really shouldn't have a `Role` that points to a `NULL` user.
247
+
248
+ So, let's make the following minor changes:
249
+
250
+ ```javascript
251
+ class User extends Model {
252
+ static fields = {
253
+ 'id': {
254
+ type: Types.UUIDV4,
255
+ allowNull: false,
256
+ primaryKey: true,
257
+ },
258
+ 'firstName': {
259
+ type: Types.STRING(64),
260
+ allowNull: true,
261
+ index: true,
262
+ },
263
+ 'lastName': {
264
+ type: Types.STRING(64),
265
+ allowNull: true,
266
+ index: true,
267
+ },
268
+ 'roles': {
269
+ type: Types.Models('Role:userID'),
270
+ },
271
+ };
272
+ }
273
+
274
+ class Role extends Model {
275
+ static fields = {
276
+ 'id': {
277
+ type: Types.UUIDV4,
278
+ allowNull: false,
279
+ primaryKey: true,
280
+ },
281
+ 'name': {
282
+ type: Types.STRING(64),
283
+ allowNull: false,
284
+ index: true,
285
+ },
286
+ 'userID': {
287
+ type: Types.ForeignKey('User:id', {
288
+ // if User is deleted, also delete all roles belonging to the user
289
+ onDelete: 'CASCADE',
290
+ }),
291
+
292
+ // Disallow `userID` from ever being `NULL`
293
+ allowNull: false,
294
+
295
+ // "index" is no longer needed (but it also won't hurt).
296
+ // ForeignKeys are indexed by default.
297
+ // index: true,
298
+ },
299
+ 'user': {
300
+ type: Types.Model('User', 'userID'),
301
+ },
302
+ };
303
+ }
304
+ ```
305
+
306
+ So this change was fairly simple and straight forward. As you can see, we simply changed the type of the `userID` column on the `Roles` table. Instead of just being a `UUIDV4` type, we instead changed it to be a `ForeignKey` type. The `ForeignKey` type will look up the target field, and pull its type and other options from the target field. So this `ForeignKey('User:id')` will go look-up the `User` model, find its `id` field, and use that to define the `userID` field. Plus, it also defines a foreign-key relationship in the database simultaneously.
307
+
308
+ ## Example #4 - Using a "through" table in relationships
309
+
310
+ So far we have just defined relationships between two different tables, our `User` and `Role` tables. What if we instead want to define more that two tables in our relationship? What if, for example, our boss comes back to us on Friday morning and says "Dang, Shayla, okay, well, this situation is slightly better. Now users can have more than one role, but we need to define role information ON THE RELATIONSHIP itself.".
311
+
312
+ Back to work! Now the boss wants extra information defined ON THE RELATIONSHIP, not on users, and not on roles, but rather on the relationship between the two. The boss wants us to define if a user, with a specific role, is able to use the front-door of the office, the back-door of the office, or both.
313
+
314
+ Okay, strange request boss... but then again, bosses generally don't do the thinking very well, which is why they hired you, right? Great! Let's get r' done.
315
+
316
+ For this to work, we now need to define a third table. We will call this table `UserRole`. This will define the user id, the role id, and which door the user is allowed to use. This will also mean that we need to remove the `userID` from the `Role` table.
317
+
318
+ So to sum up, we need to do the following:
319
+
320
+ 1. Drop the `userID` from the `Role` table
321
+ 2. Create a new table called `UserRole`
322
+ 3. Adjust the relationships between the fields
323
+
324
+ Let's dig in:
325
+
326
+ ```javascript
327
+ class User extends Model {
328
+ static fields = {
329
+ 'id': {
330
+ type: Types.UUIDV4,
331
+ allowNull: false,
332
+ primaryKey: true,
333
+ },
334
+ 'firstName': {
335
+ type: Types.STRING(64),
336
+ allowNull: true,
337
+ index: true,
338
+ },
339
+ 'lastName': {
340
+ type: Types.STRING(64),
341
+ allowNull: true,
342
+ index: true,
343
+ },
344
+ 'roles': {
345
+ // Notice here how we specify the virtual field 'UserRole:role'
346
+ // this will be used to collect all the information
347
+ // necessary to join the tables.
348
+ //
349
+ // Mythix ORM is smart enough to follow all fields
350
+ // until it can build the full relationship.
351
+ // So Mythix ORM will first lookup `UserRole:role`,
352
+ // find that it is a virtual field that targets `roleID`,
353
+ // which it will then look-up, and find that this is a
354
+ // foreign key that points to `Role.id`.
355
+ // So now it knows how to get to the role.
356
+ //
357
+ // But... how do we get to the proper UserRole based
358
+ // on "this instance" of a User? This is why the
359
+ // "value provider" is also targeting a virtual
360
+ // field. When Mythix ORM does a look-up on the field
361
+ // it will notice that it is a virtual field, targeting
362
+ // the "User" model, and so it now knows all relationships.
363
+ type: Types.Models(
364
+ // Target field (will be fully resolved)
365
+ 'UserRole:role',
366
+ // Value provider (will be fully resolved)
367
+ 'UserRole:user',
368
+ ),
369
+ },
370
+ };
371
+ }
372
+
373
+ class Role extends Model {
374
+ static fields = {
375
+ 'id': {
376
+ type: Types.UUIDV4,
377
+ allowNull: false,
378
+ primaryKey: true,
379
+ },
380
+ 'name': {
381
+ type: Types.STRING(64),
382
+ allowNull: false,
383
+ index: true,
384
+ },
385
+ 'user': {
386
+ type: Types.Model(
387
+ // Target field (will be fully resolved)
388
+ 'UserRole:user',
389
+ // Value provider (will be fully resolved)
390
+ 'UserRole:role',
391
+ ),
392
+ },
393
+ };
394
+ }
395
+
396
+ class UserRole extends Model {
397
+ static fields = {
398
+ 'id': {
399
+ type: Types.UUIDV4,
400
+ allowNull: false,
401
+ primaryKey: true,
402
+ },
403
+ // The new data point our boss wanted us to add
404
+ 'doorUsage': {
405
+ type: Types.STRING,
406
+ allowNull: false,
407
+ index: true,
408
+ },
409
+ 'userID': {
410
+ type: Types.ForeignKey('User:id', {
411
+ // if User is deleted, also delete all UserRole belonging to the user
412
+ onDelete: 'CASCADE',
413
+ }),
414
+ allowNull: false,
415
+ },
416
+ 'roleID': {
417
+ type: Types.ForeignKey('Role:id', {
418
+ // if Role is deleted, also delete all UserRole belonging to the role
419
+ onDelete: 'CASCADE',
420
+ }),
421
+ allowNull: false,
422
+ },
423
+ 'role': {
424
+ // 'Role:id' is assumed
425
+ type: Types.Model('Role', 'roleID')
426
+ },
427
+ 'user': {
428
+ // 'User:id' is assumed
429
+ type: Types.Model('User', 'userID'),
430
+ },
431
+ };
432
+ }
433
+ ```
434
+
435
+ This is fantastic! But... we have one problem. This through-table relationship will indeed do a three-way join to collect the information requested from all tables, but what about that new `doorUsage` field we added to the `UserRole` table? Nowhere have we specified where that should be used... We could just inject it onto `User` models on load... but that doesn't make sense, because the user could have multiple roles, and each role link could have a different `doorUsage` value. Instead, it would be better if we injected this `doorUsage` value onto the `Roles` that are loaded. That would make more sense, as the `doorUsage` is based on the role, and not based on user.
436
+
437
+ Okay, that is all gravy... but *how* exactly do we request Mythix ORM inject this field on the Role? Well, Mythix ORM will automatically include the through table model on the **loaded** models by default. This means if you load from the perspective of the user:
438
+
439
+ ```javascript
440
+ let roles = await thisUser.getRoles();
441
+ ```
442
+
443
+ Then every `role` in the `roles` array will also have a `userRole` key, defining the row from the through table relationship.
444
+
445
+ ```javascript
446
+ let roles = await thisUser.getRoles();
447
+ // roles[0].userRole = UserRole { userID, roleID, id, doorUsage }
448
+ ```
449
+
450
+ If instead you load from the perspective of a role, then `userRole` will be added to the user:
451
+
452
+ ```javascript
453
+ let user = await role.getUser();
454
+ // user.userRole = UserRole { userID, roleID, id, doorUsage }
455
+ ```
456
+
457
+ *NOTE:
458
+ If this was a many-to-many relationship, then `userRole` would be added to **both-sides** of the relation, being added to both loaded `User` models, and also to loaded `Role` models.*
459
+
460
+ Great! Now we can easily access this and get the job done as the boss asked:
461
+
462
+ ```javascript
463
+ let role = await pseudoCodeToFetchRoleBasedOnDoorBeingUsed(doorID);
464
+ let user = await role.getUser();
465
+
466
+ if (user.userRole.doorUsage === 'back') {
467
+ // User is only allowed to use the back door
468
+ } else if (user.userRole.doorUsage === 'front') {
469
+ // User is only allowed to use the front door
470
+ } else {
471
+ // User can use either door... the boss has conveniently
472
+ // ensured that this case applies to him
473
+ }
474
+ ```
475
+
476
+ ## Final Notes
477
+
478
+ 1. I would like to bring it to the attention of the reader that concrete types are defined all *UPPERCASE*, whereas virtual types are defined as *CamelCase*.
479
+ 2. "But, what if I am using two, three, or more through-tables?" you ask. Well, you are in luck! Nothing described above changes. You simply target virtual fields as your "target" and "value provider" in the relationships you define, and Mythix ORM will be smart enough to recursively walk all fields, understand all relationships, and join as many tables as it needs to to get the job done.
480
+ 3. Mythix forces you to manually define all table fields. Yes, this is extra overhead, but it comes with the benefit of not needing to painstakingly manually define all *relationships*. This design pattern was also decided upon so that there aren't any table columns that are "hidden" from the user, or ambiguous. By forcing the user to always define all columns manually, it simplifies seeing what fields exist on the table, removes down-stream dependencies, and prevents the user from needing to go look-up documentation to understand how things are working and why. Feel free to write your own helper methods that will automatically inject fields into your schema for you!
481
+
482
+ Happy coding!
package/docs/Home.md ADDED
@@ -0,0 +1 @@
1
+ Welcome to the mythix-orm wiki!
@@ -228,7 +228,7 @@ class ConnectionBase extends EventEmitter {
228
228
 
229
229
  escape(field, _value) {
230
230
  var value = _value;
231
- if (value instanceof Literals.LiteralBase)
231
+ if (LiteralBase.isLiteral(value))
232
232
  return value.toString(this);
233
233
 
234
234
  value = field.type.serialize(value, this);
@@ -256,14 +256,14 @@ class ConnectionBase extends EventEmitter {
256
256
  }
257
257
 
258
258
  escapeID(value) {
259
- if (value instanceof Literals.LiteralBase)
259
+ if (LiteralBase.isLiteral(value))
260
260
  return value.toString(this.connection);
261
261
 
262
262
  return this._escapeID(value);
263
263
  }
264
264
 
265
265
  _averageLiteralToString(literal) {
266
- if (!literal || !(literal instanceof LiteralBase))
266
+ if (!literal || !LiteralBase.isLiteral(literal))
267
267
  return;
268
268
 
269
269
  let queryGenerator = this.getQueryGenerator();
@@ -274,7 +274,7 @@ class ConnectionBase extends EventEmitter {
274
274
  }
275
275
 
276
276
  _countLiteralToString(literal) {
277
- if (!literal || !(literal instanceof LiteralBase))
277
+ if (!literal || !LiteralBase.isLiteral(literal))
278
278
  return;
279
279
 
280
280
  let queryGenerator = this.getQueryGenerator();
@@ -285,7 +285,7 @@ class ConnectionBase extends EventEmitter {
285
285
  }
286
286
 
287
287
  _distinctLiteralToString(literal) {
288
- if (!literal || !(literal instanceof LiteralBase))
288
+ if (!literal || !LiteralBase.isLiteral(literal))
289
289
  return;
290
290
 
291
291
  let queryGenerator = this.getQueryGenerator();
@@ -296,7 +296,7 @@ class ConnectionBase extends EventEmitter {
296
296
  }
297
297
 
298
298
  _maxLiteralToString(literal) {
299
- if (!literal || !(literal instanceof LiteralBase))
299
+ if (!literal || !LiteralBase.isLiteral(literal))
300
300
  return;
301
301
 
302
302
  let queryGenerator = this.getQueryGenerator();
@@ -307,7 +307,7 @@ class ConnectionBase extends EventEmitter {
307
307
  }
308
308
 
309
309
  _minLiteralToString(literal) {
310
- if (!literal || !(literal instanceof LiteralBase))
310
+ if (!literal || !LiteralBase.isLiteral(literal))
311
311
  return;
312
312
 
313
313
  let queryGenerator = this.getQueryGenerator();
@@ -318,7 +318,7 @@ class ConnectionBase extends EventEmitter {
318
318
  }
319
319
 
320
320
  _sumLiteralToString(literal) {
321
- if (!literal || !(literal instanceof LiteralBase))
321
+ if (!literal || !LiteralBase.isLiteral(literal))
322
322
  return;
323
323
 
324
324
  let queryGenerator = this.getQueryGenerator();
@@ -329,19 +329,19 @@ class ConnectionBase extends EventEmitter {
329
329
  }
330
330
 
331
331
  literalToString(literal) {
332
- if (literal instanceof Literals.AverageLiteral)
332
+ if (Literals.AverageLiteral.isLiteralType(literal))
333
333
  return this._averageLiteralToString(literal);
334
- else if (literal instanceof Literals.CountLiteral)
334
+ else if (Literals.CountLiteral.isLiteralType(literal))
335
335
  return this._countLiteralToString(literal);
336
- else if (literal instanceof Literals.DistinctLiteral)
336
+ else if (Literals.DistinctLiteral.isLiteralType(literal))
337
337
  return this._distinctLiteralToString(literal);
338
- else if (literal instanceof Literals.MaxLiteral)
338
+ else if (Literals.MaxLiteral.isLiteralType(literal))
339
339
  return this._maxLiteralToString(literal);
340
- else if (literal instanceof Literals.MinLiteral)
340
+ else if (Literals.MinLiteral.isLiteralType(literal))
341
341
  return this._minLiteralToString(literal);
342
- else if (literal instanceof Literals.SumLiteral)
342
+ else if (Literals.SumLiteral.isLiteralType(literal))
343
343
  return this._sumLiteralToString(literal);
344
- else if (literal instanceof Literals.Literal)
344
+ else if (Literals.Literal.isLiteralType(literal))
345
345
  return literal.toString(this);
346
346
 
347
347
  throw new Error(`${this.constructor.name}::literalToString: Unsupported literal ${literal}.`);
@@ -423,35 +423,35 @@ class ConnectionBase extends EventEmitter {
423
423
  }
424
424
 
425
425
  typeToString(type, options) {
426
- if (type instanceof Types.BigIntType)
426
+ if (Types.BigIntType.isSameType(type))
427
427
  return this._bigintTypeToString(type, options);
428
- else if (type instanceof Types.BlobType)
428
+ else if (Types.BlobType.isSameType(type))
429
429
  return this._blobTypeToString(type, options);
430
- else if (type instanceof Types.BooleanType)
430
+ else if (Types.BooleanType.isSameType(type))
431
431
  return this._booleanTypeToString(type, options);
432
- else if (type instanceof Types.CharType)
432
+ else if (Types.CharType.isSameType(type))
433
433
  return this._charTypeToString(type, options);
434
- else if (type instanceof Types.DateType)
434
+ else if (Types.DateType.isSameType(type))
435
435
  return this._dateTypeToString(type, options);
436
- else if (type instanceof Types.DateTimeType)
436
+ else if (Types.DateTimeType.isSameType(type))
437
437
  return this._datetimeTypeToString(type, options);
438
- else if (type instanceof Types.FloatType)
438
+ else if (Types.FloatType.isSameType(type))
439
439
  return this._floatTypeToString(type, options);
440
- else if (type instanceof Types.IntegerType)
440
+ else if (Types.IntegerType.isSameType(type))
441
441
  return this._integerTypeToString(type, options);
442
- else if (type instanceof Types.StringType)
442
+ else if (Types.StringType.isSameType(type))
443
443
  return this._stringTypeToString(type, options);
444
- else if (type instanceof Types.TextType)
444
+ else if (Types.TextType.isSameType(type))
445
445
  return this._textTypeToString(type, options);
446
- else if (type instanceof Types.UUIDV1Type)
446
+ else if (Types.UUIDV1Type.isSameType(type))
447
447
  return this._uuidV1TypeToString(type, options);
448
- else if (type instanceof Types.UUIDV3Type)
448
+ else if (Types.UUIDV3Type.isSameType(type))
449
449
  return this._uuidV3TypeToString(type, options);
450
- else if (type instanceof Types.UUIDV4Type)
450
+ else if (Types.UUIDV4Type.isSameType(type))
451
451
  return this._uuidV4TypeToString(type, options);
452
- else if (type instanceof Types.UUIDV5Type)
452
+ else if (Types.UUIDV5Type.isSameType(type))
453
453
  return this._uuidV5TypeToString(type, options);
454
- else if (type instanceof Types.XIDType)
454
+ else if (Types.XIDType.isSameType(type))
455
455
  return this._xidTypeToString(type, options);
456
456
 
457
457
  throw new Error(`${this.constructor.name}::typeToString: Unsupported type ${type}.`);
@@ -715,7 +715,7 @@ class ConnectionBase extends EventEmitter {
715
715
  let inputIsArray = false;
716
716
  if (!Array.isArray(models)) {
717
717
  if (!models._mythixPreparedModels) {
718
- if (models instanceof Map || models instanceof Set)
718
+ if (Nife.instanceOf(models, 'map', 'set'))
719
719
  inputIsArray = true;
720
720
 
721
721
  models = Nife.toArray(models).filter(Boolean);
@@ -3,6 +3,38 @@
3
3
  const ModelUtils = require('../../utils/model-utils');
4
4
 
5
5
  class LiteralBase {
6
+ static _isMythixLiteral = true;
7
+
8
+ static isLiteralClass(value) {
9
+ if (!value)
10
+ return false;
11
+
12
+ if (value.prototype instanceof LiteralBase)
13
+ return true;
14
+
15
+ if (value._isMythixLiteral)
16
+ return true;
17
+
18
+ return false;
19
+ }
20
+
21
+ static isLiteral(value) {
22
+ if (!value)
23
+ return false;
24
+
25
+ if (value instanceof LiteralBase)
26
+ return true;
27
+
28
+ if (value.constructor && value.constructor._isMythixLiteral)
29
+ return true;
30
+
31
+ return false;
32
+ }
33
+
34
+ static isLiteralType(value) {
35
+ return (this.isLiteral(value) && value.constructor && value.constructor.name === this.name);
36
+ }
37
+
6
38
  constructor(literal, options) {
7
39
  Object.defineProperties(this, {
8
40
  'literal': {
@@ -21,7 +53,7 @@ class LiteralBase {
21
53
  }
22
54
 
23
55
  fullyQualifiedNameToDefinition(fullyQualifiedName) {
24
- if (fullyQualifiedName instanceof LiteralBase)
56
+ if (LiteralBase.isLiteral(fullyQualifiedName))
25
57
  return fullyQualifiedName;
26
58
 
27
59
  if (!fullyQualifiedName)
@@ -44,7 +76,7 @@ class LiteralBase {
44
76
  }
45
77
 
46
78
  definitionToField(connection, definition) {
47
- if (definition instanceof LiteralBase)
79
+ if (LiteralBase.isLiteral(definition))
48
80
  return definition;
49
81
 
50
82
  let field = connection.getField(definition.fieldNames[0], definition.modelName);
@@ -158,7 +158,7 @@ class QueryGeneratorBase {
158
158
  if (!field)
159
159
  continue;
160
160
 
161
- if (field instanceof LiteralBase) {
161
+ if (LiteralBase.isLiteral(field)) {
162
162
  let result = field.toString(this.connection);
163
163
  let projectionField = this.parseFieldProjection(result, true);
164
164
  if (projectionField === result) {
@@ -195,8 +195,8 @@ class QueryGeneratorBase {
195
195
  // the list of projected fields (required
196
196
  // by some databases)
197
197
  const distinctSortOrder = (a, b) => {
198
- let x = (a instanceof LiteralBase);
199
- let y = (b instanceof LiteralBase);
198
+ let x = LiteralBase.isLiteral(a);
199
+ let y = LiteralBase.isLiteral(b);
200
200
  let xStr = (x) ? a.toString(this.connection) : a;
201
201
  let yStr = (y) ? b.toString(this.connection) : b;
202
202
  let xIsDistinct = (typeof xStr === 'string' && LITERAL_IS_DISTINCT_RE.test(xStr));
@@ -254,7 +254,7 @@ class QueryGeneratorBase {
254
254
  for (let i = 0, il = fields.length; i < il; i++) {
255
255
  let field = fields[i];
256
256
 
257
- if (field instanceof LiteralBase)
257
+ if (LiteralBase.isLiteral(field))
258
258
  return true;
259
259
 
260
260
  if (ModelBase.isModelClass(field))
@@ -360,7 +360,7 @@ class QueryGeneratorBase {
360
360
 
361
361
  let lastField = result[result.length - 1];
362
362
 
363
- if (lastField instanceof LiteralBase)
363
+ if (LiteralBase.isLiteral(lastField))
364
364
  result = [ lastField ];
365
365
  else if (typeof lastField === 'string')
366
366
  result = [ lastField ];
@@ -433,10 +433,10 @@ class QueryGeneratorBase {
433
433
  continue;
434
434
  }
435
435
 
436
- if (projectionValue instanceof LiteralBase) {
436
+ if (LiteralBase.isLiteral(projectionValue)) {
437
437
  // If we already have distinct specified on the query
438
438
  // then skip any distinct values specified by the user
439
- if (hasDistinct && projectionValue instanceof Literals.DistinctLiteral)
439
+ if (hasDistinct && Literals.DistinctLiteral.isLiteralType(projectionValue))
440
440
  continue;
441
441
 
442
442
  let key = projectionValue.toString(this.connection);
@@ -562,7 +562,7 @@ class QueryGeneratorBase {
562
562
  if (!projectionField)
563
563
  continue;
564
564
 
565
- if (projectionField instanceof LiteralBase) {
565
+ if (LiteralBase.isLiteral(projectionField)) {
566
566
  let result = projectionField.toString(this.connection);
567
567
  let fullFieldName = this.parseFieldProjection(result);
568
568
  if (!fullFieldName)
@@ -647,7 +647,7 @@ class QueryGeneratorBase {
647
647
  if (!order)
648
648
  return order;
649
649
 
650
- if (order instanceof LiteralBase)
650
+ if (LiteralBase.isLiteral(order))
651
651
  return order;
652
652
 
653
653
  // Is this a field?
@@ -705,11 +705,11 @@ class QueryGeneratorBase {
705
705
  order = queryPart.value;
706
706
  }
707
707
 
708
- if (Nife.isNotEmpty(order) && !(order instanceof LiteralBase)) {
708
+ if (Nife.isNotEmpty(order) && !LiteralBase.isLiteral(order)) {
709
709
  let allModels = this.getAllModelsUsedInQuery(queryEngine, options);
710
710
 
711
711
  order = order.map((_fieldName) => {
712
- if (_fieldName instanceof LiteralBase)
712
+ if (LiteralBase.isLiteral(_fieldName))
713
713
  return _fieldName;
714
714
 
715
715
  let { fieldName, direction } = this.getFieldDirectionSpecifier(_fieldName);
@@ -771,13 +771,13 @@ class QueryGeneratorBase {
771
771
  }
772
772
 
773
773
  _averageLiteralToString(literal) {
774
- if (!literal || !(literal instanceof LiteralBase))
774
+ if (!literal || !LiteralBase.isLiteral(literal))
775
775
  return;
776
776
 
777
777
  let field = literal.definitionToField(this.connection, literal.definition);
778
778
  let escapedFieldName;
779
779
 
780
- if (field instanceof LiteralBase)
780
+ if (LiteralBase.isLiteral(field))
781
781
  escapedFieldName = field.toString(this.connection);
782
782
  else
783
783
  escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
@@ -786,14 +786,14 @@ class QueryGeneratorBase {
786
786
  }
787
787
 
788
788
  _countLiteralToString(literal) {
789
- if (!literal || !(literal instanceof LiteralBase))
789
+ if (!literal || !LiteralBase.isLiteral(literal))
790
790
  return;
791
791
 
792
792
  let field = (literal.definition) ? literal.definitionToField(this.connection, literal.definition) : null;
793
793
  let escapedFieldName;
794
794
 
795
795
  if (field) {
796
- if (field instanceof LiteralBase)
796
+ if (LiteralBase.isLiteral(field))
797
797
  escapedFieldName = field.toString(this.connection);
798
798
  else
799
799
  escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
@@ -805,24 +805,24 @@ class QueryGeneratorBase {
805
805
  }
806
806
 
807
807
  _distinctLiteralToString(literal) {
808
- if (!literal || !(literal instanceof LiteralBase))
808
+ if (!literal || !LiteralBase.isLiteral(literal))
809
809
  return;
810
810
 
811
811
  let field = literal.definitionToField(this.connection, literal.definition);
812
- if (field instanceof LiteralBase)
812
+ if (LiteralBase.isLiteral(field))
813
813
  return `DISTINCT ${field.toString(this.connection)}`;
814
814
 
815
815
  return `DISTINCT ${this.getEscapedProjectionName(field.Model, field, literal.options)}`;
816
816
  }
817
817
 
818
818
  _maxLiteralToString(literal) {
819
- if (!literal || !(literal instanceof LiteralBase))
819
+ if (!literal || !LiteralBase.isLiteral(literal))
820
820
  return;
821
821
 
822
822
  let field = literal.definitionToField(this.connection, literal.definition);
823
823
  let escapedFieldName;
824
824
 
825
- if (field instanceof LiteralBase)
825
+ if (LiteralBase.isLiteral(field))
826
826
  escapedFieldName = field.toString(this.connection);
827
827
  else
828
828
  escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
@@ -831,13 +831,13 @@ class QueryGeneratorBase {
831
831
  }
832
832
 
833
833
  _minLiteralToString(literal) {
834
- if (!literal || !(literal instanceof LiteralBase))
834
+ if (!literal || !LiteralBase.isLiteral(literal))
835
835
  return;
836
836
 
837
837
  let field = literal.definitionToField(this.connection, literal.definition);
838
838
  let escapedFieldName;
839
839
 
840
- if (field instanceof LiteralBase)
840
+ if (LiteralBase.isLiteral(field))
841
841
  escapedFieldName = field.toString(this.connection);
842
842
  else
843
843
  escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
@@ -846,13 +846,13 @@ class QueryGeneratorBase {
846
846
  }
847
847
 
848
848
  _sumLiteralToString(literal) {
849
- if (!literal || !(literal instanceof LiteralBase))
849
+ if (!literal || !LiteralBase.isLiteral(literal))
850
850
  return;
851
851
 
852
852
  let field = literal.definitionToField(this.connection, literal.definition);
853
853
  let escapedFieldName;
854
854
 
855
- if (field instanceof LiteralBase)
855
+ if (LiteralBase.isLiteral(field))
856
856
  escapedFieldName = field.toString(this.connection);
857
857
  else
858
858
  escapedFieldName = this.getEscapedColumnName(field.Model, field, literal.options);
package/lib/field.js CHANGED
@@ -1,6 +1,34 @@
1
1
  'use strict';
2
2
 
3
3
  class Field {
4
+ static _isMythixField = true;
5
+
6
+ static isFieldClass(value) {
7
+ if (!value)
8
+ return false;
9
+
10
+ if (value.prototype instanceof Field)
11
+ return true;
12
+
13
+ if (value._isMythixField)
14
+ return true;
15
+
16
+ return false;
17
+ }
18
+
19
+ static isField(value) {
20
+ if (!value)
21
+ return false;
22
+
23
+ if (value instanceof Field)
24
+ return true;
25
+
26
+ if (value.constructor && value.constructor._isMythixField)
27
+ return true;
28
+
29
+ return false;
30
+ }
31
+
4
32
  constructor(fieldDefinition) {
5
33
  Object.assign(this, fieldDefinition || {});
6
34
  }
package/lib/model.js CHANGED
@@ -516,7 +516,7 @@ class Model {
516
516
  if (!field)
517
517
  return;
518
518
 
519
- if (field instanceof Field) {
519
+ if (Field.isField(field)) {
520
520
  let clonedField = field.clone();
521
521
 
522
522
  if (Type.isType(clonedField.type)) {
@@ -602,7 +602,7 @@ class Model {
602
602
 
603
603
  field.Model = ModelClass;
604
604
 
605
- if (!(field instanceof Field))
605
+ if (!Field.isField(field))
606
606
  field = new Field(field);
607
607
 
608
608
  if (field.primaryKey)
@@ -1071,15 +1071,31 @@ class Model {
1071
1071
  _getFieldValue(fieldName, field) {
1072
1072
  let value = this.getDataValue(fieldName);
1073
1073
 
1074
- if (typeof field.get === 'function')
1075
- return field.get.call(this, { value, field, fieldName });
1074
+ if (typeof field.get === 'function') {
1075
+ return field.get.call(this, {
1076
+ model: this,
1077
+ set: this.setDataValue.bind(this, fieldName),
1078
+ get: this.getDataValue.bind(this, fieldName),
1079
+ value,
1080
+ field,
1081
+ fieldName,
1082
+ });
1083
+ }
1076
1084
 
1077
1085
  return value;
1078
1086
  }
1079
1087
 
1080
1088
  _setFieldValue(fieldName, field, value) {
1081
1089
  if (typeof field.set === 'function') {
1082
- field.set.call(this, { value, field, fieldName });
1090
+ field.set.call(this, {
1091
+ model: this,
1092
+ set: this.setDataValue.bind(this, fieldName),
1093
+ get: this.getDataValue.bind(this, fieldName),
1094
+ value,
1095
+ field,
1096
+ fieldName,
1097
+ });
1098
+
1083
1099
  return;
1084
1100
  }
1085
1101
 
@@ -1227,7 +1243,7 @@ class Model {
1227
1243
  try {
1228
1244
  let fieldValue = this[fieldName];
1229
1245
  let promise = field.validate.call(this, fieldValue, context);
1230
- if (!(promise instanceof Promise))
1246
+ if (!Nife.instanceOf(promise, 'promise'))
1231
1247
  promise = Promise.resolve(promise);
1232
1248
 
1233
1249
  promises.push(promise);
@@ -1267,7 +1283,7 @@ class Model {
1267
1283
  async save(_options) {
1268
1284
  let options = _options || {};
1269
1285
 
1270
- if (options.force !== true && Nife.isEmpty(this.changes)) {
1286
+ if (this.isPersisted() && options.force !== true && Nife.isEmpty(this.changes)) {
1271
1287
  return false;
1272
1288
  } else if (options.force) {
1273
1289
  // Mark all fields as dirty
@@ -115,7 +115,7 @@ class ModelScope extends QueryEngineBase {
115
115
  return;
116
116
 
117
117
  // Pass literals directly through
118
- if (value instanceof LiteralBase)
118
+ if (LiteralBase.isLiteral(value))
119
119
  return value;
120
120
 
121
121
  // Is the projection a model?
@@ -53,7 +53,7 @@ class DateTimeType extends Type {
53
53
  if (value == null)
54
54
  return (connection) ? null : value;
55
55
 
56
- if (!(value instanceof moment))
56
+ if (!moment.isMoment(value))
57
57
  value = this.deserialize(value);
58
58
 
59
59
  if (connection)
@@ -95,6 +95,8 @@ const AUTO_INCREMENT = defaultValueFlags(function(context) {
95
95
  return context.connection.getDefaultFieldValue('AUTO_INCREMENT', context);
96
96
  }, { literal: true, remote: true });
97
97
 
98
+ AUTO_INCREMENT._mythixIsAutoIncrement = true;
99
+
98
100
  const DATETIME_NOW = generatePermutations(function(context) {
99
101
  return context.connection.getDefaultFieldValue('DATETIME_NOW', context);
100
102
  }, { literal: true, remote: true });
package/lib/types/type.js CHANGED
@@ -35,6 +35,10 @@ class Type {
35
35
  return false;
36
36
  }
37
37
 
38
+ static isSameType(value) {
39
+ return (this.isType(value) && value.constructor && value.constructor.name === this.name);
40
+ }
41
+
38
42
  static instantiateType(_type) {
39
43
  let type = _type;
40
44
  if (!type)
@@ -116,6 +116,10 @@ const TYPE_OPERATIONS = {
116
116
  let query = await type.prepareQuery({ connection: null, self: this, field, options }, args);
117
117
  return await query.exists(options);
118
118
  },
119
+ 'pluck': async function({ field, type }, fields, options, ...args) {
120
+ let query = await type.prepareQuery({ connection: null, self: this, field, options }, args);
121
+ return await query.pluck(fields, options);
122
+ },
119
123
  };
120
124
 
121
125
  class ModelType extends RelationalTypeBase {
@@ -106,7 +106,9 @@ const TYPE_OPERATIONS = {
106
106
  // might be persisted, the through table record
107
107
  // might still be created, which might blow up
108
108
  // if a constraint fails because the records
109
- // already exist
109
+ // already exist. The opposite might also be true,
110
+ // where the target models already exist, and
111
+ // so shouldn't be created.
110
112
  return this.getConnection(options && options.connection).transaction(async (connection) => {
111
113
  let currentModels = this[field.fieldName];
112
114
  if (Nife.isEmpty(currentModels))
@@ -227,6 +229,10 @@ const TYPE_OPERATIONS = {
227
229
  let query = await type.prepareQuery({ connection: null, self: this, field, userQuery, options }, args);
228
230
  return await query.count(null, options);
229
231
  },
232
+ 'pluck': async function({ field, type }, userQuery, fields, options, ...args) {
233
+ let query = await type.prepareQuery({ connection: null, self: this, field, fields, userQuery, options }, args);
234
+ return await query.pluck(fields, options);
235
+ },
230
236
  'has': async function({ count }, userQuery, options, ...args) {
231
237
  let itemCount = await count.call(this, userQuery, options, ...args);
232
238
  return (itemCount > 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mythix-orm",
3
- "version": "1.0.3",
3
+ "version": "1.2.0",
4
4
  "description": "ORM for Mythix framework",
5
5
  "main": "lib/index.js",
6
6
  "type": "commonjs",