mythix-orm 1.4.7 → 1.5.2

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/lib/model.js CHANGED
@@ -110,6 +110,24 @@ function bindStaticWhereToModelClass(ModelClass) {
110
110
  /// from this class. This class provides
111
111
  /// support for all model operations in
112
112
  /// Mythix ORM.
113
+ ///
114
+ /// Note:
115
+ /// Many model methods have both static and instance
116
+ /// methods of the same name that do the same thing.
117
+ /// This is because it is common to access Model
118
+ /// methods directly on the model class as well as an
119
+ /// instance. For example, <see>Model.static getModelName</see> is both
120
+ /// a static method, and an instance method. Nearly all
121
+ /// the methods listed here have both a `static` and
122
+ /// and instance version. We are only listing the `static`
123
+ /// versions because the instance versions generally will
124
+ /// just be proxies to these `static` methods.
125
+ /// Note:
126
+ /// An underscore prefix on a method in Mythix ORM
127
+ /// implies that this method should not be overloaded
128
+ /// unless you know exactly what you are doing.
129
+ /// It also implies that there is another method
130
+ /// that can and should be overloaded instead.
113
131
  class Model {
114
132
  /// This property assists with type checking
115
133
  ///
@@ -142,13 +160,14 @@ class Model {
142
160
 
143
161
  /// Check to see if the provided value is
144
162
  /// an *instance* of a Mythix ORM <see>Model</see>.
145
- /// Unlike <see>Model.isModelClass</see>, which
163
+ /// Unlike <see>Model.static isModelClass</see>, which
146
164
  /// checks if a *class* is a <see>Model</see>, this will check
147
165
  /// to see if an *instance* is an instance of a
148
166
  /// Mythix ORM <see>Model</see>. It will return
149
167
  /// `true` if the provided value is an `instanceof`
150
- /// <see>Model</see>, or if the values `constructor`
151
- /// property has a truthy `_isMythixModel` property.
168
+ /// <see>Model</see>, or if the value's `constructor`
169
+ /// property has a truthy `_isMythixModel` property
170
+ /// (`value.constructor._isMythixModel`)
152
171
  ///
153
172
  /// Return: boolean
154
173
  /// Arguments:
@@ -217,16 +236,30 @@ class Model {
217
236
  /// Otherwise, if not provided, or a falsy value,
218
237
  /// then attempt to fallback to the bound connection,
219
238
  /// if any is available.
220
- /// Note:
221
- /// An underscore prefix on a method in Mythix ORM
222
- /// implies that this method should not be overloaded
223
- /// unless you know exactly what you are doing.
224
- /// It also implies that there is another method
225
- /// that can and should be overloaded instead.
226
239
  static _getConnection(_connection) {
227
240
  return _connection || this._mythixBoundConnection;
228
241
  }
229
242
 
243
+ /// See <see>Model.static _getConnection</see>
244
+ ///
245
+ /// Return: <see>Connection</see>
246
+ /// Arguments:
247
+ /// connection?: <see>Connection</see>
248
+ /// An optional connection that can be provided.
249
+ /// If provided, it will simply be returned.
250
+ /// Otherwise, if not provided, or a falsy value,
251
+ /// then attempt to fallback to `modelInstance._connection`,
252
+ /// if that also fails to find a connection, the finally
253
+ /// the method will call <see>Model.static _getConnection</see>
254
+ /// to get the bound connection, if any is available.
255
+ /// Note:
256
+ /// Pay attention that unlike <see>Model.static _getConnection</see>
257
+ /// this checks the model's instance for `._connection` property. If that is
258
+ /// a valid connection, then it will be returned before the bound
259
+ /// connection. This is helpful when you have chosen not to bind
260
+ /// a connection to your models. This will allow you to provide a
261
+ /// connection directly when you create the model, which can make
262
+ /// interacting with the model less tiresome. i.e. `new Model(null, { connection })`.
230
263
  _getConnection(connection) {
231
264
  if (connection)
232
265
  return connection;
@@ -237,6 +270,26 @@ class Model {
237
270
  return this.constructor._getConnection();
238
271
  }
239
272
 
273
+ /// Get the underlying connection bound to
274
+ /// the model's class. Connection binding is
275
+ /// optional, so this method can also be provided
276
+ /// a connection. If a connection is provided, then
277
+ /// it will simply be returned. Because Mythix ORM
278
+ /// has no globals, this is a design pattern to
279
+ /// supply a connection if you have one available,
280
+ /// or fallback to a "bound" connection (if one can
281
+ /// be found). This method should be overloaded if
282
+ /// you wish to provide your own model specific
283
+ /// connection.
284
+ ///
285
+ /// Return: <see>Connection</see>
286
+ /// Arguments:
287
+ /// connection?: <see>Connection</see>
288
+ /// An optional connection that can be provided.
289
+ /// If provided, it will simply be returned.
290
+ /// Otherwise, if not provided, or a falsy value,
291
+ /// then attempt to fallback to the bound connection,
292
+ /// if any is available.
240
293
  static getConnection(_connection) {
241
294
  let connection = this._getConnection(_connection);
242
295
  if (!connection)
@@ -256,6 +309,21 @@ class Model {
256
309
  return connection;
257
310
  }
258
311
 
312
+ /// A <see>Connection</see> instance will call
313
+ /// this method on a <see>Model</see> class to
314
+ /// bind the model to the connection. Overload
315
+ /// this to change the behavior of binding connections
316
+ /// to models. This method should return a model
317
+ /// class. The default behavior is to return
318
+ /// `this` class. However, you might return a
319
+ /// different class for example if you generated
320
+ /// a new class that inherited from `this` class.
321
+ ///
322
+ /// Return: class extends <see>Model</see>
323
+ /// Arguments:
324
+ /// connection?: <see>Connection</see>
325
+ /// The connection instance to bind to
326
+ /// this model class.
259
327
  static bindConnection(connection) {
260
328
  let ModelClass = this;
261
329
 
@@ -303,6 +371,22 @@ class Model {
303
371
  return ModelClass;
304
372
  }
305
373
 
374
+ /// This method is called every time a `Model.where`
375
+ /// or `Model.$` property is accessed. It should return
376
+ /// a class that inherits from (or is) `QueryEngine`. By default,
377
+ /// it will call `this.getConnection().getQueryEngineClass()`
378
+ /// to get the query class defined in the connection
379
+ /// options. Though this can be overloaded per-model
380
+ /// (creating a different type of `QueryEngine` per-model),
381
+ /// the `QueryEngine` class to instantiate to use for queries will
382
+ /// generally be supplied by the connection.
383
+ ///
384
+ /// Return: class <see>QueryEngine</see>
385
+ /// Arguments:
386
+ /// connection?: <see>Connection</see>
387
+ /// An optional connection to pass through.
388
+ /// This is needed if your models are not
389
+ /// bound to a connection.
306
390
  static getQueryEngineClass(connection) {
307
391
  return this.getConnection(connection).getQueryEngineClass();
308
392
  }
@@ -311,6 +395,21 @@ class Model {
311
395
  return this.constructor.getQueryEngineClass(connection);
312
396
  }
313
397
 
398
+ /// This method is called any time a `query.unscoped()`
399
+ /// call is made. It will return the query's "root class"
400
+ /// `where` with no <see>Model.static defaultScope</see>
401
+ /// scope applied. Calling "unscoped()" will reset the query,
402
+ /// so make sure you always call it first: `Model.where.unscoped()...`
403
+ ///
404
+ /// Return: <see>QueryEngine</see>
405
+ /// Arguments:
406
+ /// connection?: <see>Connection</see>
407
+ /// An optional connection to pass through.
408
+ /// This is needed if your models are not
409
+ /// bound to a connection.
410
+ /// options?: object
411
+ /// Any extra options to pass to the <see>QueryEngine</see>
412
+ /// constructor.
314
413
  static getUnscopedQueryEngine(connection, options) {
315
414
  let QueryEngineClass = this.getQueryEngineClass(connection);
316
415
  let queryEngine = new QueryEngineClass(
@@ -331,10 +430,60 @@ class Model {
331
430
  return this.constructor.getUnscopedQueryEngine(connection, options);
332
431
  }
333
432
 
433
+ /// One can apply a "default scope" to any model simply
434
+ /// by providing this static method on the model. By
435
+ /// default it will simply return the <see name="derp">QueryEngine</see>
436
+ /// instance (a query) that it was provided.
437
+ ///
438
+ /// The way this works is simple. The caller provides the
439
+ /// query as the first and only argument. The user, by
440
+ /// overloading this method can then easily modify this
441
+ /// provided query, changing the "default scope" whenever
442
+ /// the model is used for queries. For example, let's say
443
+ /// you have a User model, and by default, you only want
444
+ /// to query against Users that have their `active` column
445
+ /// set to `true`. In order to do this, you could easily
446
+ /// provide a `static defaultScope` method on your model
447
+ /// class that would modify the base query. See the following example:
448
+ ///
449
+ /// Example:
450
+ /// class User extends Model {
451
+ /// static defaultScope(baseQuery) {
452
+ /// // `baseQuery` is equal to `User.where`
453
+ /// // without a default scope.
454
+ /// return baseQuery.active.EQ(true);
455
+ /// }
456
+ /// }
457
+ /// Return: <see>QueryEngine</see>
458
+ /// Arguments:
459
+ /// query: <see>QueryEngine</see>
460
+ /// The query that you should add onto.
461
+ static defaultScope(query) {
462
+ return query;
463
+ }
464
+
334
465
  defaultScope(queryEngine) {
335
466
  return this.constructor.defaultScope(queryEngine);
336
467
  }
337
468
 
469
+ /// This method is called any time a `Model.where` or
470
+ /// `Model.$` property is accessed. It returns a query,
471
+ /// based off this model class (as the root model).
472
+ /// It will first call <see>Model.static getUnscopedQueryEngine</see>
473
+ /// to get the root query for the model, and then it will
474
+ /// call <see>Model.static defaultScope</see> to apply any
475
+ /// default scope to the root query. Finally, it will return
476
+ /// the query to the user to start interacting with.
477
+ ///
478
+ /// Return: <see>QueryEngine</see>
479
+ /// Arguments:
480
+ /// connection?: <see>Connection</see>
481
+ /// An optional connection to pass through.
482
+ /// This is needed if your models are not
483
+ /// bound to a connection.
484
+ /// options?: object
485
+ /// Any extra options to pass to the <see>QueryEngine</see>
486
+ /// constructor.
338
487
  static getQueryEngine(connection, options) {
339
488
  let queryEngine = this.getUnscopedQueryEngine(connection, options);
340
489
  let defaultScope = this.defaultScope(queryEngine, options);
@@ -346,7 +495,17 @@ class Model {
346
495
  return this.constructor.getQueryEngine(connection, options);
347
496
  }
348
497
 
349
- static getForeignKeyFieldsMap(connection) {
498
+ /// Return the foreign key relationships for the model.
499
+ ///
500
+ /// Return: Map<string, array<object>>
501
+ /// The format is `Map[modelName] = [ { targetFieldName, sourceFieldName }, ... ]`
502
+ /// Arguments:
503
+ /// connection?: <see>Connection</see>
504
+ /// An optional connection to pass through.
505
+ /// This is needed if your models are not
506
+ /// bound to a connection.
507
+ static getForeignKeyFieldsMap(_connection) {
508
+ let connection = this.getConnection(_connection);
350
509
  let foreignKeyFieldsMap = connection._getFromModelCache(this, 'foreignKeyFieldsMap');
351
510
  if (foreignKeyFieldsMap)
352
511
  return foreignKeyFieldsMap;
@@ -379,8 +538,20 @@ class Model {
379
538
  return this.constructor.getForeignKeyFieldsMap(connection);
380
539
  }
381
540
 
382
- static getForeignKeysTargetModels(connection) {
383
- let foreignKeyTargetModels = connection._getFromModelCache(this, 'foreignKeyTargetModels');
541
+ /// Return all the foreign key target models. This will be
542
+ /// all models targeted by all foreign keys defined by
543
+ /// foreign key fields on this model.
544
+ ///
545
+ /// Return: Map<string, Model>
546
+ /// The format is `Map[modelName] = Model`
547
+ /// Arguments:
548
+ /// connection?: <see>Connection</see>
549
+ /// An optional connection to pass through.
550
+ /// This is needed if your models are not
551
+ /// bound to a connection.
552
+ static getForeignKeysTargetModels(_connection) {
553
+ let connection = this.getConnection(_connection);
554
+ let foreignKeyTargetModels = connection._getFromModelCache(this, 'foreignKeyTargetModels');
384
555
  if (foreignKeyTargetModels)
385
556
  return foreignKeyTargetModels;
386
557
 
@@ -405,8 +576,21 @@ class Model {
405
576
  return this.constructor.getForeignKeysTargetModels(connection);
406
577
  }
407
578
 
408
- static getForeignKeysTargetModelNames(connection) {
409
- let foreignKeyTargetModelNames = connection._getFromModelCache(this, 'foreignKeyTargetModelNames');
579
+ /// Return all the foreign key target model names. This will be
580
+ /// all models targeted by all foreign keys defined by
581
+ /// foreign key fields on this model. This will only
582
+ /// return the models name's.
583
+ ///
584
+ /// Return: Array<string>
585
+ /// The format is `Array[] = modelName`
586
+ /// Arguments:
587
+ /// connection?: <see>Connection</see>
588
+ /// An optional connection to pass through.
589
+ /// This is needed if your models are not
590
+ /// bound to a connection.
591
+ static getForeignKeysTargetModelNames(_connection) {
592
+ let connection = this.getConnection(_connection);
593
+ let foreignKeyTargetModelNames = connection._getFromModelCache(this, 'foreignKeyTargetModelNames');
410
594
  if (foreignKeyTargetModelNames)
411
595
  return foreignKeyTargetModelNames;
412
596
 
@@ -422,9 +606,24 @@ class Model {
422
606
  return this.constructor.getForeignKeysTargetModelNames(connection);
423
607
  }
424
608
 
425
- static getForeignKeysTargetFieldNames(connection, modelName) {
426
- let cacheKey = `${modelName}:foreignKeyFieldNames`;
427
- let foreignKeyFieldNames = connection._getFromModelCache(this, cacheKey);
609
+ /// Return all the foreign key target field names.
610
+ /// This will return the target field names of all
611
+ /// foreign key fields specified on the model.
612
+ ///
613
+ /// Return: Array<string>
614
+ /// The format is `Array[] = modelName`
615
+ /// Arguments:
616
+ /// connection?: <see>Connection</see>
617
+ /// An optional connection to pass through.
618
+ /// This is needed if your models are not
619
+ /// bound to a connection.
620
+ /// modelName: string
621
+ /// The model name for which you wish to fetch
622
+ /// foreign key fields from.
623
+ static getForeignKeysTargetFieldNames(_connection, modelName) {
624
+ let connection = this.getConnection(_connection);
625
+ let cacheKey = `${modelName}:foreignKeyFieldNames`;
626
+ let foreignKeyFieldNames = connection._getFromModelCache(this, cacheKey);
428
627
  if (foreignKeyFieldNames)
429
628
  return foreignKeyFieldNames;
430
629
 
@@ -440,7 +639,28 @@ class Model {
440
639
  return this.constructor.getForeignKeysTargetFieldNames(connection, modelName);
441
640
  }
442
641
 
443
- static getForeignKeysTargetField(connection, modelName, fieldName) {
642
+ /// Get a specific foreign key field's relationship
643
+ /// information. This will be what is stored in the
644
+ /// relationship field map for the specified target
645
+ /// field.
646
+ /// See <see>Model.static getForeignKeyFieldsMap</see> for more information.
647
+ ///
648
+ /// Return: object<{ targetFieldName, sourceFieldName }>
649
+ /// This will be the relationship info for this foreign key field
650
+ /// which will be an object of the shape `{ targetFieldName, sourceFieldName }`.
651
+ /// Arguments:
652
+ /// connection?: <see>Connection</see>
653
+ /// An optional connection to pass through.
654
+ /// This is needed if your models are not
655
+ /// bound to a connection.
656
+ /// modelName: string
657
+ /// The model name for which you wish to fetch
658
+ /// foreign key fields from.
659
+ /// fieldName: string
660
+ /// The field name for which you wish to fetch
661
+ /// foreign key relational information about.
662
+ static getForeignKeysTargetField(_connection, modelName, fieldName) {
663
+ let connection = this.getConnection(_connection);
444
664
  let cacheKey = `${modelName}:${fieldName}:foreignKeyTargetField`;
445
665
  let foreignKeyFieldNames = connection._getFromModelCache(this, cacheKey);
446
666
  if (foreignKeyFieldNames)
@@ -451,7 +671,7 @@ class Model {
451
671
  if (!fields)
452
672
  return;
453
673
 
454
- let fieldInfo = fields.find((field) => (field.targetFieldName === fieldName));
674
+ let fieldInfo = fields.find((fieldInfo) => (fieldInfo.targetFieldName === fieldName));
455
675
 
456
676
  connection._setToModelCache(this, cacheKey, fieldInfo);
457
677
 
@@ -462,7 +682,22 @@ class Model {
462
682
  return this.constructor.getForeignKeysTargetField(connection, modelName, fieldName);
463
683
  }
464
684
 
465
- static isForeignKeyTargetModel(connection, modelName) {
685
+ /// Check if the specified model is a model
686
+ /// pointed to by a foreign key field.
687
+ ///
688
+ /// Return: boolean
689
+ /// `true` if the specified `modelName` model is pointed
690
+ /// to by one of the foreign key fields, `false` otherwise.
691
+ /// Arguments:
692
+ /// connection?: <see>Connection</see>
693
+ /// An optional connection to pass through.
694
+ /// This is needed if your models are not
695
+ /// bound to a connection.
696
+ /// modelName: string
697
+ /// The model name for which you wish to fetch
698
+ /// foreign key fields from.
699
+ static isForeignKeyTargetModel(_connection, modelName) {
700
+ let connection = this.getConnection(_connection);
466
701
  let foreignKeyFieldsMap = this.getForeignKeyFieldsMap(connection);
467
702
  return foreignKeyFieldsMap.has(modelName);
468
703
  }
@@ -471,6 +706,17 @@ class Model {
471
706
  return this.constructor.isForeignKeyTargetModel(connection, modelName);
472
707
  }
473
708
 
709
+ /// Get the table name for this model. By default
710
+ /// Mythix ORM will take the model's name, and convert
711
+ /// it to snake_case.
712
+ ///
713
+ /// Return: string
714
+ /// The name of the table for this model.
715
+ /// Arguments:
716
+ /// connection?: <see>Connection</see>
717
+ /// An optional connection to pass through.
718
+ /// This is needed if your models are not
719
+ /// bound to a connection.
474
720
  static getTableName(connection) {
475
721
  if (!connection) {
476
722
  let tableName = Nife.camelCaseToSnakeCase(this.getPluralModelName());
@@ -493,6 +739,13 @@ class Model {
493
739
  return this.constructor.getTableName(connection);
494
740
  }
495
741
 
742
+ /// Get the model name for this model. By default
743
+ /// this will be the name of the class itself, i.e.
744
+ /// `this.name`. Overload this method to provide your
745
+ /// own model name (singular name).
746
+ ///
747
+ /// Return: string
748
+ /// The name the model.
496
749
  static getModelName() {
497
750
  return this.name;
498
751
  }
@@ -501,6 +754,21 @@ class Model {
501
754
  return this.constructor.getModelName();
502
755
  }
503
756
 
757
+ /// Get the singular model name for this model. By default
758
+ /// this will be the name of the class itself, i.e.
759
+ /// `this.name`. You should probably overload <see>Model.static getModelName</see>
760
+ /// to provide your own model name, instead of overloading this method.
761
+ ///
762
+ /// Note:
763
+ /// This will return the "singular" name of the model
764
+ /// which is the same as <see>Model.static getModelName</see>.
765
+ /// Note:
766
+ /// This method simply calls <see>Model.static getModelName</see>
767
+ /// to get the singular model name.
768
+ /// Return: string
769
+ /// The name the model in singular form.
770
+ /// See: Model.static getPluralModelName
771
+ /// See: Model.static getModelName
504
772
  static getSingularName() {
505
773
  return this.getModelName();
506
774
  }
@@ -509,6 +777,17 @@ class Model {
509
777
  return this.constructor.getSingularName();
510
778
  }
511
779
 
780
+ /// Get the plural model name for this model. By default
781
+ /// this will be the singular name of the model, converted
782
+ /// to plural using the [Inflection](https://github.com/dreamerslab/node.inflection)
783
+ /// library. Overload this method to provide your own plural
784
+ /// name for the model.
785
+ ///
786
+ /// Note:
787
+ /// This will return the "plural" name of the model.
788
+ /// Return: string
789
+ /// The name the model in plural form.
790
+ /// See: Model.static getSingularName
512
791
  static getPluralModelName() {
513
792
  return Inflection.pluralize(this.getSingularName());
514
793
  }
@@ -517,6 +796,9 @@ class Model {
517
796
  return this.constructor.getPluralModelName();
518
797
  }
519
798
 
799
+ /// Get the Model class for this model.
800
+ ///
801
+ /// Return: class <see>Model</see>
520
802
  static getModel() {
521
803
  return this;
522
804
  }
@@ -525,6 +807,36 @@ class Model {
525
807
  return this.constructor.getModel();
526
808
  }
527
809
 
810
+ /// Get the fields for this model. You can optionally
811
+ /// specify the `fieldNames` argument, which will cause
812
+ /// the method to return only the fields specified by name.
813
+ /// If the `fieldNames` argument is provided, then the
814
+ /// return value will always be an array of fields. If
815
+ /// the `fieldNames` argument is not provided, then
816
+ /// the return value could be either an Array of fields,
817
+ /// or an object of fields, keyed by field name (depending
818
+ /// on how you defined your model fields).
819
+ ///
820
+ /// Note:
821
+ /// The first time this method is called the model's fields
822
+ /// will be built, and possibly modified. All fields will be
823
+ /// turned into <see>Field</see> instances, and each field
824
+ /// will be properly constructed to include required attributes,
825
+ /// such as their parent `Model`, their `fieldName`, and their
826
+ /// `columnName`. A model's fields are always initialized when
827
+ /// the model is first bound to a connection, or when this method
828
+ /// is first called.
829
+ /// Note:
830
+ /// In Mythix ORM you **never** specify a field via its `columnName`.
831
+ /// You always use a field's defined `fieldName` to match against
832
+ /// a field. Using a `columnName` simply won't work, unless `columnName`
833
+ /// and `fieldName` just happen to have the same value.
834
+ /// Return: Array<<see>Field</see>> | Object<string, <see>Field</see>>
835
+ /// Arguments:
836
+ /// fieldNames?: Array<string>
837
+ /// An array of field names to fetch. If not
838
+ /// provided then all model fields will be returned,
839
+ /// including virtual fields.
528
840
  static getFields(fieldNames) {
529
841
  let fields = this.initializeFields(this.fields);
530
842
  if (fields !== this.fields) {
@@ -564,6 +876,21 @@ class Model {
564
876
  return this.constructor.getFields(fieldNames);
565
877
  }
566
878
 
879
+ /// This method is identical to <see>Model.static getFields</see>
880
+ /// except that it will always return an Array of fields, and the
881
+ /// fields will always be sorted by their `fieldName`.
882
+ ///
883
+ /// Note:
884
+ /// In Mythix ORM you **never** specify a field via its `columnName`.
885
+ /// You always use a field's defined `fieldName` to match against
886
+ /// a field. Using a `columnName` simply won't work, unless `columnName`
887
+ /// and `fieldName` just happen to have the same value.
888
+ /// Return: Array<<see>Field</see>>
889
+ /// Arguments:
890
+ /// fieldNames?: Array<string>
891
+ /// An array of field names to fetch. If not
892
+ /// provided then all model fields will be returned,
893
+ /// including virtual fields.
567
894
  static getSortedFields(fieldNames) {
568
895
  if (Object.prototype.hasOwnProperty.call(this, '_sortedFields') && this._sortedFields)
569
896
  return this._sortedFields;
@@ -600,6 +927,36 @@ class Model {
600
927
  return this.constructor.getSortedFields(fieldNames);
601
928
  }
602
929
 
930
+ /// Merge specified fields into this model's fields.
931
+ /// The `mergeFields` argument is optional. If not
932
+ /// provided, then this method will simply clone
933
+ /// the model's fields. If specified, then the fields
934
+ /// provided will be merged into the existing fields.
935
+ /// Merging takes place based on each field's `fieldName`
936
+ /// attribute. The `mergeFields` argument can be an
937
+ /// `Array`, an `object`, a `Map`, or a `Set`. Fields
938
+ /// are matched based on `fieldName`, but when a match
939
+ /// is found, it is completely overridden by what is
940
+ /// found in the `mergeFields` argument. In short, the
941
+ /// list of fields itself is merged, but the fields themselves
942
+ /// are not merged (a shallow merge). All input objects that
943
+ /// have keys (Object<string, <see>Field</see>> | Map<string, <see>Field</see>>)
944
+ /// must specify the `fieldName` of the field as the key.
945
+ /// If the input is an Array type (Array<<see>Field</see>> | Set<<see>Field</see>>)
946
+ /// then each field **must** contain a `fieldName` attribute, or
947
+ /// an exception will be thrown.
948
+ ///
949
+ /// Note:
950
+ /// Anywhere you see <see>Field</see> as an input in this documentation,
951
+ /// you can also simply provide a raw object with the same shape, and
952
+ /// it will be automatically converted to a <see>Field</see> instance
953
+ /// for you.
954
+ /// Return: Array<<see>Field</see>> | object<string, <see>Field</see>>
955
+ /// Arguments:
956
+ /// mergeFields?: Array<<see>Field</see>> | Set<<see>Field</see>> | Object<string, <see>Field</see>> | Map<string, <see>Field</see>>
957
+ /// A list of fields to merge into the current
958
+ /// model fields. The field list,
959
+ /// as well as all fields will be cloned.
603
960
  static mergeFields(mergeFields) {
604
961
  const cloneField = ({ value: field, key: _fieldName, index, type }) => {
605
962
  if (!field)
@@ -652,6 +1009,29 @@ class Model {
652
1009
  return (Nife.instanceOf(fields, 'array', 'set')) ? Array.from(clonedFields.values()) : mapToObject(clonedFields);
653
1010
  }
654
1011
 
1012
+ /// Initialize all fields for the model. This will be called anytime
1013
+ /// a <see>Model.static getFields</see> call is made. However, it
1014
+ /// caches its results, so it will immediately return if the model's
1015
+ /// fields have already been initialized.
1016
+ ///
1017
+ /// This method does a number of things in the process of "initializing"
1018
+ /// model fields. It first ensures that every field is an instance of
1019
+ /// <see>Field</see>. Second, it ensures that the <see>Type</see> of the
1020
+ /// field is initialized (properly instantiated). Third, it ensures that
1021
+ /// all required attributes of each field is present. Required attributes
1022
+ /// are `Model` (the parent model), `fieldName` (the name of this field),
1023
+ /// and `columnName` (the column name for the DB). `columnName`, if not
1024
+ /// provided, will simply become `fieldName`.
1025
+ ///
1026
+ /// Note:
1027
+ /// The cache for built fields is stored directly on the input `fields`
1028
+ /// argument, under a non-enumerable `_mythixFieldsInitialized` cache key.
1029
+ /// Return: Array<<see>Field</see>> | object<string, <see>Field</see>>
1030
+ /// Arguments:
1031
+ /// fields: Array<<see>Field</see>> | Set<<see>Field</see>> | Object<string, <see>Field</see>> | Map<string, <see>Field</see>>
1032
+ /// A list of fields to work off of. When this method is
1033
+ /// called by <see>Model.static getFields</see>, then
1034
+ /// this argument will be `static Model.fields`.
655
1035
  static initializeFields(fields) {
656
1036
  if (!fields)
657
1037
  return fields;
@@ -725,6 +1105,47 @@ class Model {
725
1105
  return finalizedFields;
726
1106
  }
727
1107
 
1108
+ /// Iterate all model fields. This is a convenience method to
1109
+ /// iterate `static Model.fields`, and is needed as the fields
1110
+ /// of a model can be an `Array`, an `object`, a `Map`, or a `Set`.
1111
+ /// This method works a lot like `Array.prototype.map`. Any return
1112
+ /// value from the provided `callback` will be pushed into an array
1113
+ /// just like `Array.prototype.map`. The `callback` provided, when called,
1114
+ /// will be provided a `context` object, as the single argument to
1115
+ /// the callback.
1116
+ ///
1117
+ /// At any time you can call the `stop` method provided via the context.
1118
+ /// When called, `iterateFields` will stop iterating, and immediately
1119
+ /// return an array of results returned by all calls to `callback`.
1120
+ ///
1121
+ /// The provided `context` object has the following shape:
1122
+ ///
1123
+ /// Interface:
1124
+ /// interface IterationContext {
1125
+ /// field: Field; // The Field instance itself.
1126
+ /// fieldName: string; // The name of the field.
1127
+ /// fields: // All the model's fields.
1128
+ /// Array<Field> |
1129
+ /// Set<Field> |
1130
+ /// Object<string, Field> |
1131
+ // Map<string, Field>;
1132
+ /// index: string | number; // The current index into the list of fields.
1133
+ /// stop: Function; // A method that when called will halt iteration.
1134
+ /// isStopped: Function; // Call this method to see if iteration is about to be halted.
1135
+ /// }
1136
+ ///
1137
+ /// Return: Array<any>
1138
+ /// Arguments:
1139
+ /// callback: Function(context: IterationContext)
1140
+ /// A callback method that will be called for
1141
+ /// every field on the model.
1142
+ /// fields?: Array<<see>Field</see>> | Set<<see>Field</see>> | Object<string, <see>Field</see>> | Map<string, <see>Field</see>> = null
1143
+ /// An optional list of fields to use *instead* of `static Model.fields`. This is
1144
+ /// handy if, for example, you want to iterate a sub-set of the model's fields,
1145
+ /// such as the "dirty" fields reported by the model.
1146
+ /// sorted?: boolean = false
1147
+ /// If `true`, then sort the model fields before iterating. If `false`,
1148
+ /// simply iterate the model's fields in their defined order.
728
1149
  static iterateFields(callback, _fields, sorted) {
729
1150
  if (typeof callback !== 'function')
730
1151
  return [];
@@ -745,6 +1166,18 @@ class Model {
745
1166
  return this.constructor.iterateFields(callback, _fields, sorted);
746
1167
  }
747
1168
 
1169
+ /// Check if any of the model's fields have a "remote value"
1170
+ /// as a `defaultValue`. A `defaultValue` method can itself
1171
+ /// report that it is "remote", meaning it is a value provided
1172
+ /// by the database itself. This method simply iterates all the
1173
+ /// model's fields, and calls `field.type.isRemote()` on each field.
1174
+ /// If `field.type.isRemote()` returns `true` for any field, then
1175
+ /// this method will return `true`, otherwise it will return `false`.
1176
+ /// Use this method to know if the model contains any fields whose
1177
+ /// value is obtained directly from the database itself (i.e. primary key
1178
+ /// date fields, etc...).
1179
+ ///
1180
+ /// Return: boolean
748
1181
  static hasRemoteFieldValues() {
749
1182
  let hasRemote = false;
750
1183
 
@@ -762,6 +1195,12 @@ class Model {
762
1195
  return this.constructor.hasRemoteFieldValues();
763
1196
  }
764
1197
 
1198
+ /// Get the primary key field of the model, if any is defined.
1199
+ /// If no primary key field is defined, then this will return
1200
+ /// `undefined`. A primary key is specified per-model by using
1201
+ /// `primaryKey: true` on one (and only one) of the model's fields.
1202
+ ///
1203
+ /// Return: <see>Field</see>
765
1204
  static getPrimaryKeyField() {
766
1205
  let fields = this.getFields();
767
1206
  if (!fields)
@@ -774,6 +1213,13 @@ class Model {
774
1213
  return this.constructor.getPrimaryKeyField();
775
1214
  }
776
1215
 
1216
+ /// Get the *name* of the primary key field of the model,
1217
+ /// if any is defined.
1218
+ /// If no primary key field is defined, then this will return
1219
+ /// `undefined`. A primary key is specified per-model by using
1220
+ /// `primaryKey: true` on one (and only one) of the model's fields.
1221
+ ///
1222
+ /// Return: string
777
1223
  static getPrimaryKeyFieldName() {
778
1224
  let primaryKeyField = this.getPrimaryKeyField();
779
1225
  if (!primaryKeyField)
@@ -786,6 +1232,17 @@ class Model {
786
1232
  return this.constructor.getPrimaryKeyFieldName();
787
1233
  }
788
1234
 
1235
+ /// Check to see if the primary key field (if any is defined)
1236
+ /// has a "remote" default value. For example, an autoincrementing
1237
+ /// primary key field would return `true` from this method, because
1238
+ /// an autoincrementing field value is retrieved directly from the
1239
+ /// database. This could return `false`, if for example, you were
1240
+ /// using UUIDs, or XIDs for your primary key.
1241
+ ///
1242
+ /// Return: boolean
1243
+ /// `true` if the primary key field of the model has a "remote"
1244
+ /// `defaultValue`, `false` otherwise. Remoteness is checked
1245
+ /// via `this.getPrimaryKeyField().type.isRemote()`.
789
1246
  static primaryKeyHasRemoteValue() {
790
1247
  let primaryKeyField = this.getPrimaryKeyField();
791
1248
  if (!primaryKeyField)
@@ -798,6 +1255,20 @@ class Model {
798
1255
  return this.constructor.primaryKeyHasRemoteValue();
799
1256
  }
800
1257
 
1258
+ /// Get a specific model field by field name. This method
1259
+ /// will return `undefined` if the specified field can not
1260
+ /// be found on the model. This will find both concrete and
1261
+ /// virtual fields that are defined on the model.
1262
+ ///
1263
+ /// Note:
1264
+ /// In Mythix ORM you **never** specify a field via its `columnName`.
1265
+ /// You always use a field's defined `fieldName` to match against
1266
+ /// a field. Using a `columnName` simply won't work, unless `columnName`
1267
+ /// and `fieldName` just happen to have the same value.
1268
+ /// Return: <see>Field</see>
1269
+ /// Arguments:
1270
+ /// fieldName: string
1271
+ /// The specified field name to find. This **is not** the `columnName` of the field.
801
1272
  static getField(findFieldName) {
802
1273
  let fields = this.getFields();
803
1274
  if (!fields || !findFieldName)
@@ -819,6 +1290,19 @@ class Model {
819
1290
  return this.constructor.getField(findFieldName);
820
1291
  }
821
1292
 
1293
+ /// Check if the model has the specified field by name.
1294
+ /// If the specified field is found on the model, then
1295
+ /// return `true`, otherwise `false` will be returned.
1296
+ ///
1297
+ /// Note:
1298
+ /// In Mythix ORM you **never** specify a field via its `columnName`.
1299
+ /// You always use a field's defined `fieldName` to match against
1300
+ /// a field. Using a `columnName` simply won't work, unless `columnName`
1301
+ /// and `fieldName` just happen to have the same value.
1302
+ /// Return: boolean
1303
+ /// Arguments:
1304
+ /// fieldName: string
1305
+ /// The specified field name to find. This **is not** the `columnName` of the field.
822
1306
  static hasField(fieldName) {
823
1307
  return !!this.getField(fieldName);
824
1308
  }
@@ -827,6 +1311,13 @@ class Model {
827
1311
  return this.constructor.hasField(fieldName);
828
1312
  }
829
1313
 
1314
+ /// Count the number of concrete fields on the model.
1315
+ /// "concrete" fields are non-virtual fields that are
1316
+ /// backed by the database. A `FOREIGN_KEY` field **is**
1317
+ /// a concrete field, so `FOREIGN_KEY` fields will be counted.
1318
+ ///
1319
+ /// Return: number
1320
+ /// The number of concrete fields the model has
830
1321
  static getConcreteFieldCount() {
831
1322
  let count = 0;
832
1323
 
@@ -844,13 +1335,51 @@ class Model {
844
1335
  return this.constructor.getConcreteFieldCount();
845
1336
  }
846
1337
 
847
- static defaultScope(queryEngine) {
848
- return queryEngine;
849
- }
850
-
851
- static getDefaultOrder() {
852
- }
853
-
1338
+ /// Specify the default SELECT "ORDER" for the model.
1339
+ /// This method will be called if no "ORDER" was specified
1340
+ /// for any given query. This method should return an
1341
+ /// Array of fully qualified field names. The `options`
1342
+ /// argument is the current options for the specific
1343
+ /// query being operated on.
1344
+ ///
1345
+ /// Note:
1346
+ /// Internally, Mythix ORM will call `this.connection.getDefaultOrder(options)`,
1347
+ /// which--if not overloaded--will simply call this method from the
1348
+ /// model itself.
1349
+ /// Note:
1350
+ /// A "fully qualified field name" in Mythix ORM means
1351
+ /// a full field definition, including the model name.
1352
+ /// An example of a fully qualified field name might be
1353
+ /// `"User:id"`. The model name is separated from the field
1354
+ /// name by a colon `:`. A short hand, *not fully qualified
1355
+ /// field name* example would be simply `"id"`. Since this
1356
+ /// contains no model prefix, this is "short hand", and is
1357
+ /// **not** a fully qualified field name.
1358
+ /// Return: Array<string> | null
1359
+ /// An array of fully qualified field names to specify the ORDER.
1360
+ /// Prefix a field name with `+` to specify ASCending order, i.e.
1361
+ /// `[ "+User:id" ]`. Prefix the field name with `-` to specify
1362
+ /// DESCending order, i.e. `[ "-User:id" ]`.
1363
+ static getDefaultOrder(options) {
1364
+ }
1365
+
1366
+ /// This method is called anytime a `Model.where` or `Model.$`
1367
+ /// attribute is accessed. It will provide the instantiated
1368
+ /// <see>QueryEngine</see> with the connection specified (if any).
1369
+ /// A connection can be specified for example by doing `Model.where(connection)`
1370
+ /// or `Model.$(connection)`. This is generally only useful if you
1371
+ /// have chosen not to bind your models to a connection.
1372
+ ///
1373
+ /// Return: <see>QueryEngine</see>
1374
+ /// A <see>QueryEngine</see> instance (a query) for
1375
+ /// this model.
1376
+ /// Arguments:
1377
+ /// options?: `object { connection: Connection }`
1378
+ /// An object, which if supplied, should contain a
1379
+ /// `connection` key, specifying the connection.
1380
+ /// If no connection is provided, then this will
1381
+ /// fallback to `this.getConnection()` to try and
1382
+ /// find the connection itself.
854
1383
  static getWhereWithConnection(options) {
855
1384
  if (options && options.connection)
856
1385
  return this.where(options.connection);
@@ -862,6 +1391,25 @@ class Model {
862
1391
  return this.constructor.getWhereWithConnection(options);
863
1392
  }
864
1393
 
1394
+ /// Create (insert) new models into the database.
1395
+ /// The `models` argument can be a single model instance,
1396
+ /// a single object containing model attributes,
1397
+ /// an array of model instances, or an array of
1398
+ /// objects containing model attributes. This is
1399
+ /// a "bulk" create method, though it can be used
1400
+ /// to create a single model.
1401
+ ///
1402
+ /// Return: <see>Model</see>
1403
+ /// Return the model instance(s) created.
1404
+ /// Arguments:
1405
+ /// models: Array<<see>Model</see>> | <see>Model</see> | Array<object> | object
1406
+ /// Specify the model(s) to create.
1407
+ /// options?: object
1408
+ /// An "options" object to pass off to the underlying <see>Connection</see> methods.
1409
+ /// If a `connection` key is specified in this object, then that will be used
1410
+ /// as the connection for the operation. This can be important if for example
1411
+ /// you are calling this from a `transaction`, in which case you most certainly
1412
+ /// would want to provide the `connection` for the transaction.
865
1413
  static async create(models, options) {
866
1414
  let connection = this.getConnection() || (options && options.connection);
867
1415
  if (!connection)
@@ -874,14 +1422,70 @@ class Model {
874
1422
  return (Array.isArray(result)) ? result[0] : result;
875
1423
  }
876
1424
 
1425
+ /// Count the rows in the database for this model.
1426
+ ///
1427
+ /// Return: number
1428
+ /// Return the number of models stored in the database for this model type.
1429
+ /// Arguments:
1430
+ /// options?: object
1431
+ /// An "options" object to pass off to the underlying <see>Connection</see> methods.
1432
+ /// If a `connection` key is specified in this object, then that will be used
1433
+ /// as the connection for the operation. This can be important if for example
1434
+ /// you are calling this from a `transaction`, in which case you most certainly
1435
+ /// would want to provide the `connection` for the transaction.
877
1436
  static count(options) {
878
1437
  return this.getWhereWithConnection(options).count(null, options);
879
1438
  }
880
1439
 
1440
+ /// Fetch all models from the database for this model type.
1441
+ ///
1442
+ /// Note:
1443
+ /// This will fetch ALL rows for the model. If this is not what you want,
1444
+ /// then construct a query first, and call `all` from the query itself, i.e.
1445
+ /// `User.where.firstName.EQ('Bob').all(options)`.
1446
+ /// Return: Array<<see>Model</see>>
1447
+ /// Return all models of this type from the database.
1448
+ /// Arguments:
1449
+ /// options?: object
1450
+ /// An "options" object to pass off to the underlying <see>Connection</see> methods.
1451
+ /// If a `connection` key is specified in this object, then that will be used
1452
+ /// as the connection for the operation. This can be important if for example
1453
+ /// you are calling this from a `transaction`, in which case you most certainly
1454
+ /// would want to provide the `connection` for the transaction.
1455
+ /// If a `stream: true` option is provided, then an async generator will be
1456
+ /// returned, allowing you to "stream" the rows from the database in batches.
1457
+ /// The default `batchSize` is `500`, but can be overridden by setting the `batchSize`
1458
+ /// option to some valid positive number, or `Infinity` to pull everything at once.
1459
+ /// If the `stream` option is not specified, or is `false`, then all rows will be
1460
+ /// fetched from the database in batches, collected into an array, and returned
1461
+ /// only once all rows have been fetched. This is the default behavior.
881
1462
  static all(options) {
882
1463
  return this.getWhereWithConnection(options).all(options);
883
1464
  }
884
1465
 
1466
+ /// Get the first (limit) rows from the database for this model type.
1467
+ ///
1468
+ /// Note:
1469
+ /// This will fetch the first rows for the model, in database order.
1470
+ /// If you wish to filter, and specify the order, then construct a
1471
+ /// query first, and call the `first` method from the query itself, i.e.
1472
+ /// `User.where.firstName.EQ('Bob').ORDER('+User:firstName').first(options)`
1473
+ /// Return: <see>Model</see> | Array<<see>Model</see>>
1474
+ /// Return `limit` models of this type from the database. If
1475
+ /// `limit` is `null`, `undefined`, or `1`, then a single model
1476
+ /// instance will be returned (or `undefined` if nothing is found).
1477
+ /// If the `limit` argument is more than `1`, then an array
1478
+ /// of model instances will be returned, or an empty array if
1479
+ /// nothing is found.
1480
+ /// Arguments:
1481
+ /// limit?: number = 1
1482
+ /// The number of model instances to fetch from the database.
1483
+ /// options?: object
1484
+ /// An "options" object to pass off to the underlying <see>Connection</see> methods.
1485
+ /// If a `connection` key is specified in this object, then that will be used
1486
+ /// as the connection for the operation. This can be important if for example
1487
+ /// you are calling this from a `transaction`, in which case you most certainly
1488
+ /// would want to provide the `connection` for the transaction.
885
1489
  static first(limit, options) {
886
1490
  if (limit != null && !Nife.instanceOf(limit, 'number'))
887
1491
  throw new Error(`${this.getModelName()}::first: "limit" must be null, or a number. If you want to supply a query, use "${this.getModelName()}.where.{query}.first(limit)" instead.`);
@@ -889,6 +1493,33 @@ class Model {
889
1493
  return this.getWhereWithConnection(options).first(limit, options);
890
1494
  }
891
1495
 
1496
+ /// Get the last (limit) rows from the database for this model type.
1497
+ ///
1498
+ /// Note:
1499
+ /// This will fetch the last rows for the model, in database order.
1500
+ /// If you wish to filter, and specify the order, then construct a
1501
+ /// query first, and call the `last` method from the query itself, i.e.
1502
+ /// `User.where.firstName.EQ('Bob').ORDER('+User:firstName').last(options)`
1503
+ /// Note:
1504
+ /// This works by telling the underlying query generator to invert
1505
+ /// the specified ORDER of the query, and then it selects the first
1506
+ /// `limit` rows from the result.
1507
+ /// Return: <see>Model</see> | Array<<see>Model</see>>
1508
+ /// Return `limit` models of this type from the database. If
1509
+ /// `limit` is `null`, `undefined`, or `1`, then a single model
1510
+ /// instance will be returned (or `undefined` if nothing is found).
1511
+ /// If the `limit` argument is more than `1`, then an array
1512
+ /// of model instances will be returned, or an empty array if
1513
+ /// nothing is found.
1514
+ /// Arguments:
1515
+ /// limit?: number = 1
1516
+ /// The number of model instances to fetch from the database.
1517
+ /// options?: object
1518
+ /// An "options" object to pass off to the underlying <see>Connection</see> methods.
1519
+ /// If a `connection` key is specified in this object, then that will be used
1520
+ /// as the connection for the operation. This can be important if for example
1521
+ /// you are calling this from a `transaction`, in which case you most certainly
1522
+ /// would want to provide the `connection` for the transaction.
892
1523
  static last(limit, options) {
893
1524
  if (limit != null && !Nife.instanceOf(limit, 'number'))
894
1525
  throw new Error(`${this.getModelName()}::last: "limit" must be null, or a number. If you want to supply a query, use "${this.getModelName()}.where.{query}.last(limit)" instead.`);
@@ -896,10 +1527,72 @@ class Model {
896
1527
  return this.getWhereWithConnection(options).last(limit, options);
897
1528
  }
898
1529
 
1530
+ /// Pluck specific fields (columns) from the database for this model type.
1531
+ ///
1532
+ /// Note:
1533
+ /// This will pluck across ALL rows for the model, in database order.
1534
+ /// If you wish to filter and limit, and specify the order, then construct a
1535
+ /// query first, and call the `pluck` method from the query itself, i.e.
1536
+ /// `User.where.firstName.EQ('Bob').LIMIT(50).ORDER('+User:firstName').pluck([ 'User:firstName' ], options)`
1537
+ /// Note:
1538
+ /// In Mythix ORM you **never** specify a field via its `columnName`.
1539
+ /// You always use a field's defined `fieldName` to match against
1540
+ /// a field. Using a `columnName` simply won't work, unless `columnName`
1541
+ /// and `fieldName` just happen to have the same value.
1542
+ /// Return: Array<any> | Array<Array<any>>
1543
+ /// If only a single field is specified, then a flat array
1544
+ /// of values will be returned across all rows. If more than
1545
+ /// one field is specified, then an array of arrays (rows) will
1546
+ /// be returned for the fields specified.
1547
+ /// Arguments:
1548
+ /// fields?: Array<string>
1549
+ /// An array of fully qualified field names to pluck from the underlying
1550
+ /// database table for this model. **Do not use column names here**... these
1551
+ /// must be fully qualified field names.
1552
+ /// options?: object
1553
+ /// An "options" object to pass off to the underlying <see>Connection</see> methods.
1554
+ /// If a `connection` key is specified in this object, then that will be used
1555
+ /// as the connection for the operation. This can be important if for example
1556
+ /// you are calling this from a `transaction`, in which case you most certainly
1557
+ /// would want to provide the `connection` for the transaction.
899
1558
  static pluck(fields, options) {
900
1559
  return this.getWhereWithConnection(options).pluck(fields);
901
1560
  }
902
1561
 
1562
+ /// Construct a new <see>Model</see> instance.
1563
+ ///
1564
+ /// Return: <see>Model</see>
1565
+ /// Arguments:
1566
+ /// data?: object
1567
+ /// Attributes to provide to the new model instance.
1568
+ /// The key names should be a `fieldName`, not a `columnName`.
1569
+ /// Any model attribute missing from `data`, or whose value is
1570
+ /// `undefined`, will be given a default value from the `defaultValue`,
1571
+ /// field property for the field. If the `defaultValue` for the
1572
+ /// field is marked as a "remote" value (a value provided by the database)
1573
+ /// or if `defaultValue` is *not* marked with the flag
1574
+ /// `FLAG_ON_INITIALIZE`, then the field will remain with
1575
+ /// a value of `undefined`, at least until the model is stored
1576
+ /// to the database. The default for all methods provided to the
1577
+ /// `defaultValue` key of the field is to have the `FLAG_ON_INITIALIZE`
1578
+ /// flag, so the value will be created on model instantiation.
1579
+ /// Some default value methods however, such as `AUTOINCREMENT`,
1580
+ /// do not have the `FLAG_ON_INITIALIZE` set, because the defaultValue
1581
+ /// is provided by the database.
1582
+ /// `null` is a valid value, and if provided for
1583
+ /// an attribute, then the `defaultValue` will not be used.
1584
+ /// Any attribute value provided will go through
1585
+ /// `field.type.castValue` from the field `type` property
1586
+ /// (this includes `null` values). Any attributes provided
1587
+ /// will immediately be marked as "dirty" as soon as the model
1588
+ /// is instantiated.
1589
+ /// options?: object
1590
+ /// Options for the model. The only property Mythix ORM
1591
+ /// will use is the `connection` key. If provided, it will set
1592
+ /// this as the `_connection` property on the instance. Otherwise,
1593
+ /// these options are intended to be used by you, the developer,
1594
+ /// to do with as you please on your models. They can be fetched
1595
+ /// from any model instance simply by calling <see>Model.getOptions</see>.
903
1596
  constructor(data, _options) {
904
1597
  let options = _options || {};
905
1598
 
@@ -913,6 +1606,12 @@ class Model {
913
1606
  };
914
1607
 
915
1608
  Object.defineProperties(this, {
1609
+ '_options': {
1610
+ writable: true,
1611
+ enumberable: false,
1612
+ configurable: true,
1613
+ value: options,
1614
+ },
916
1615
  '_mythixModelInstance': {
917
1616
  writable: false,
918
1617
  enumberable: false,
@@ -937,6 +1636,12 @@ class Model {
937
1636
  configurable: true,
938
1637
  value: {},
939
1638
  },
1639
+ '_typeData': {
1640
+ writable: true,
1641
+ enumberable: false,
1642
+ configurable: true,
1643
+ value: {},
1644
+ },
940
1645
  'dirtyID': {
941
1646
  writable: true,
942
1647
  enumberable: false,
@@ -976,11 +1681,55 @@ class Model {
976
1681
  this._constructor(data);
977
1682
  }
978
1683
 
1684
+ /// Get the options provided to the model, if any.
1685
+ /// This will always return an object, even if no
1686
+ /// options were provided, in which case the object
1687
+ /// will simply be an empty object.
1688
+ ///
1689
+ /// Return: object
1690
+ /// Options object provided by the user to the model
1691
+ /// when the model was constructed.
1692
+ getOptions() {
1693
+ return this._options;
1694
+ }
1695
+
1696
+ /// This is the final call the `constructor` makes
1697
+ /// before it finalizes instantiating the model.
1698
+ /// It calls <see>Model._constructFields</see> to
1699
+ /// construct the model's fields, and then it calls
1700
+ /// <see>Model._initializeModelData</see> to initialize
1701
+ /// the field values for the model.
1702
+ ///
1703
+ /// Return: `undefined`
1704
+ /// Arguments:
1705
+ /// data?: object
1706
+ /// Attributes provided to the model through
1707
+ /// the `constructor` call.
979
1708
  _constructor(data) {
980
1709
  this._constructFields();
981
1710
  this._initializeModelData(data);
982
1711
  }
983
1712
 
1713
+ /// Construct the model's fields on the model instance.
1714
+ /// This method will iterate the `static Model.fields`
1715
+ /// fields for the model. For each field (except virtual
1716
+ /// fields), it will call `field.type.exposeToModel()`.
1717
+ /// If this method returns `true`, then the field will
1718
+ /// be added as a getter/setter to the model instance,
1719
+ /// with the name defined as the field's `fieldName`.
1720
+ /// For example, if a `User` model had a field named
1721
+ /// `fieldName: 'firstName'`, then the `firstName`
1722
+ /// property will be added to the model instance, as a
1723
+ /// getter/setter property on the instance. The getter/
1724
+ /// setter provide the functionality of sending data in
1725
+ /// and out of `get` and `set` methods on the field itself,
1726
+ /// and through each `field.type.castValue` method on set.
1727
+ /// The setter will also mark a field as dirty if it is set
1728
+ /// to a value that is different from the current value of
1729
+ /// the field. The <see>Model.getDataValue</see> and
1730
+ /// <see>Model.setDataValue</see> instance methods will bypass
1731
+ /// the getter/setter of each field, allowing direct access
1732
+ /// to the underlying attribute value.
984
1733
  _constructFields() {
985
1734
  this.iterateFields(({ field, fieldName }) => {
986
1735
  field.type.initialize(this._getConnection(), this);
@@ -989,6 +1738,8 @@ class Model {
989
1738
  });
990
1739
  }
991
1740
 
1741
+ /// This method simply creates the getters/setters
1742
+ /// for fields as described by <see>Model._constructFields</see>.
992
1743
  _constructField(fieldName, field) {
993
1744
  Object.defineProperties(this, {
994
1745
  [fieldName]: {
@@ -1004,6 +1755,38 @@ class Model {
1004
1755
  });
1005
1756
  }
1006
1757
 
1758
+ /// Initialize the field value for each field/attribute.
1759
+ /// If no `data` argument is passed to the constructor,
1760
+ /// then each field will be initialized to its default
1761
+ /// value, as defined by the `defaultValue` property on
1762
+ /// each field. A `null` value is a valid value, and
1763
+ /// though it will still be piped through each field's
1764
+ /// `field.type.castValue` method, it will discount the
1765
+ /// need to call `defaultValue` on the field.
1766
+ /// This method calls <see>Model._initializeFieldData</see>
1767
+ /// to actually set the value of each field. Field values
1768
+ /// are set from the provided `data` object to the `constructor`
1769
+ /// first, for all fields, before any `defaultValue` calls
1770
+ /// for any fields are attempted.
1771
+ ///
1772
+ /// Note:
1773
+ /// All fields have already been constructed before this
1774
+ /// method is called via <see>Model._constructFields</see>.
1775
+ /// Note:
1776
+ /// Virtual fields are not constructed against the model instance,
1777
+ /// however, field types *can* modify the instance, and some do.
1778
+ /// For example, the `Types.Model` and `Types.Models` virtual field
1779
+ /// types inject relational methods on the model.
1780
+ /// Note:
1781
+ /// Only concrete fields are considered "attributes" of the model,
1782
+ /// and can have an initial value provided via the `data` argument
1783
+ /// to the constructor, or via a `defaultValue` property on the field.
1784
+ /// Arguments:
1785
+ /// data?: object
1786
+ /// Attributes values for fields. Can be nothing, in which case
1787
+ /// the `defaultValue` property for each field will be used instead.
1788
+ /// `null` is a valid value, and if an attribute has a `null` value,
1789
+ /// it will not be given a default value from `defaultValue`.
1007
1790
  _initializeModelData(_data) {
1008
1791
  let dirtyFieldData = this._dirtyFieldData;
1009
1792
  let data = _data || {};
@@ -1031,6 +1814,22 @@ class Model {
1031
1814
  });
1032
1815
  }
1033
1816
 
1817
+ /// This casts a field value to its type,
1818
+ /// by calling the field's `field.type.castValue`
1819
+ /// method for the type.
1820
+ ///
1821
+ /// Return: any
1822
+ /// The value provided, cast to the type of the field.
1823
+ /// Arguments:
1824
+ /// field: <see>Field</see>
1825
+ /// The field to use to cast the value. The `type` property
1826
+ /// on the field will be accessed, and the `type` properties
1827
+ /// `castValue` method will be called on the provided `value`
1828
+ /// argument.
1829
+ /// value: any
1830
+ /// The value to cast to the field's type. `null` is a valid
1831
+ /// value, and all `castValue` methods on types should properly
1832
+ /// handle it.
1034
1833
  _castFieldValue(field, value) {
1035
1834
  let type = field.type;
1036
1835
  if (!type)
@@ -1040,10 +1839,33 @@ class Model {
1040
1839
  connection: this.getConnection(),
1041
1840
  Model: this.getModel(),
1042
1841
  self: this,
1842
+ field,
1043
1843
  value,
1044
1844
  });
1045
1845
  }
1046
1846
 
1847
+ /// Set each field's initial value. Each field's value
1848
+ /// will first be fetched from the `data` argument
1849
+ /// given to the `constructor`--if any was given. After
1850
+ /// all field attributes have been set this way, then
1851
+ /// this method is called, being provided that value (if
1852
+ /// any) via the `fieldValue` argument. If `fieldValue`
1853
+ /// is `undefined`, then call the `defaultValue` property
1854
+ /// of the field to get the field's initial value.
1855
+ ///
1856
+ /// Arguments:
1857
+ /// fieldName: string
1858
+ /// The name of the field we are initializing.
1859
+ /// field: <see>Field</see>
1860
+ /// The field definition itself for the field
1861
+ /// we are initializing.
1862
+ /// fieldValue: any
1863
+ /// The field value as provided by the `data`
1864
+ /// argument to the model `constructor`, if
1865
+ /// any was provided.
1866
+ /// data: object
1867
+ /// The `data` argument, as provided to the
1868
+ /// `constructor` of the model.
1047
1869
  _initializeFieldData(fieldName, field, fieldValue, data) {
1048
1870
  let dirtyFieldData = this._dirtyFieldData;
1049
1871
  let fieldData = this._fieldData;
@@ -1075,6 +1897,7 @@ class Model {
1075
1897
  if (shouldRunDefaultValueOnInitialize()) {
1076
1898
  defaultValue = defaultValue({
1077
1899
  model: this,
1900
+ self: this,
1078
1901
  connection: this.getConnection(),
1079
1902
  _initial: true,
1080
1903
  field,
@@ -1087,12 +1910,104 @@ class Model {
1087
1910
  }
1088
1911
  }
1089
1912
 
1090
- if (defaultValue === undefined || !data)
1091
- fieldData[fieldName] = (defaultValue != null) ? this._castFieldValue(field, defaultValue) : defaultValue;
1092
- else
1093
- dirtyFieldData[fieldName] = this._castFieldValue(field, defaultValue);
1913
+ let initialValue;
1914
+
1915
+ if (defaultValue === undefined || !data) {
1916
+ initialValue = defaultValue;
1917
+
1918
+ if (defaultValue != null)
1919
+ initialValue = this._castFieldValue(field, defaultValue);
1920
+
1921
+ fieldData[fieldName] = initialValue;
1922
+ } else {
1923
+ initialValue = this._castFieldValue(field, defaultValue);
1924
+ dirtyFieldData[fieldName] = initialValue;
1925
+ }
1926
+
1927
+ field.type.onSetFieldValue({
1928
+ self: this,
1929
+ value: initialValue,
1930
+ field,
1931
+ fieldName,
1932
+ });
1094
1933
  }
1095
1934
 
1935
+ /// Get the dirty fields of the model instance.
1936
+ /// The model instance property `Model.changes`
1937
+ /// calls this method to get the list of dirty
1938
+ /// fields for the model instance. Unlike
1939
+ /// `Model.changes`, which is a getter, you can
1940
+ /// pass `options` to this method, which can change
1941
+ /// its behavior.
1942
+ ///
1943
+ /// If this method is provided a <see>Connection</see> via the
1944
+ /// `connection` options value, then it will assume it is being
1945
+ /// used in a connection method to fetch the dirty fields for
1946
+ /// a database operation (update, or insert). If it has a
1947
+ /// <see>Connection</see> then it will behave a little differently.
1948
+ ///
1949
+ /// The first difference is that if a model field is not marked
1950
+ /// as dirty, then it will be passed through <see>connection.dirtyFieldHelper</see>.
1951
+ /// If this method returns any value other than `undefined`, then
1952
+ /// that value will be used as the "dirty" value for the field, and
1953
+ /// the field will be marked as dirty. This can be used when you
1954
+ /// want the connection itself to control the dirty state of a field.
1955
+ /// This is what is done with SQLite BigInt AUTOINCREMENT emulation.
1956
+ /// Any time an autoincrementing BigInt is being stored to a SQLite
1957
+ /// database with the `emulateBigIntAutoIncrement` option enabled,
1958
+ /// an internal counter for this field is incremented, and the field
1959
+ /// is automatically updated as "dirty" (on insert only, if the field
1960
+ /// has and `undefined` value).
1961
+ ///
1962
+ /// The next difference when using a `connection` in this method is
1963
+ /// that it will check if this is an "update" or an "insert" operation
1964
+ /// by checking the `update` and `insert` "option" keys respectively.
1965
+ /// If one of these is `true`, then it will invoke `defaultValue` property
1966
+ /// on the field, if said `defaultValue` has the `onUpdate`, or `onInsert`
1967
+ /// flags set to `true`. This can be used for example on `created_at`,
1968
+ /// and `updated_at` fields, which will only be marked dirty and given
1969
+ /// a value on an `insert` or `update` operation through the connection.
1970
+ ///
1971
+ /// Without a `connection` provided to this method, it will simply
1972
+ /// return the "change list" of all dirty fields on the model. The
1973
+ /// "change list" is an object, where each key is the name of the field,
1974
+ /// and each value is another object, with `previous` and `current` keys
1975
+ /// describe the change that took place to the field. For example:
1976
+ /// ```javascript
1977
+ /// DirtyFieldChangeList = {
1978
+ /// firstName: {
1979
+ /// previous: undefined,
1980
+ /// current: 'Bob',
1981
+ /// },
1982
+ /// }
1983
+ /// ```
1984
+ ///
1985
+ /// If no fields on the model are dirty, then an empty object
1986
+ /// will be returned. A field is considered "dirty" if its value
1987
+ /// is different from what it was when the model was first instantiated,
1988
+ /// or since the last time the <see>Model.clearDirty</see> method was
1989
+ /// called.
1990
+ ///
1991
+ /// The `options` argument has the
1992
+ /// following shape:
1993
+ ///
1994
+ /// Interface:
1995
+ /// interface DirtyFieldOptions {
1996
+ /// connection: Connection; // The connection for this operation.
1997
+ /// update: boolean; // If `true`, then this is an update operation.
1998
+ /// insert: boolean; // If `true`, then this is an insert operation.
1999
+ /// // ... any options you wish to pass to `connection.dirtyFieldHelper`
2000
+ /// }
2001
+ ///
2002
+ /// Note:
2003
+ /// If this is an "update" or "insert" operation, as defined by
2004
+ /// the `update` or `insert` "options", then the default value
2005
+ /// for the field will be fetched via the <see>QueryGenerator.getFieldDefaultValue</see>
2006
+ /// method through the provided `connection`.
2007
+ /// Return: DirtyFieldChangeList
2008
+ /// Arguments:
2009
+ /// options?: DirtyFieldOptions
2010
+ /// Options to provide to the method.
1096
2011
  _getDirtyFields(_options) {
1097
2012
  let options = _options || {};
1098
2013
  let fieldData = this._fieldData;
@@ -1110,6 +2025,30 @@ class Model {
1110
2025
  return;
1111
2026
  }
1112
2027
 
2028
+ // Does the type itself report that we are dirty?
2029
+ let value = field.type.isDirty({
2030
+ self: this,
2031
+ value: this.getDataValue(fieldName),
2032
+ field,
2033
+ fieldName,
2034
+ connection,
2035
+ });
2036
+
2037
+ if (value !== undefined) {
2038
+ // Cache this result so subsequent calls
2039
+ // to dirty fields doesn't get a new value
2040
+ dirtyFieldData[fieldName] = value;
2041
+
2042
+ dirtyFields[fieldName] = {
2043
+ previous: fieldData[fieldName],
2044
+ current: value,
2045
+ };
2046
+
2047
+ return;
2048
+ }
2049
+
2050
+ // Does the connection dirtyFieldHelper report that we are
2051
+ // dirty?
1113
2052
  if (connection && typeof connection.dirtyFieldHelper === 'function') {
1114
2053
  let value = connection.dirtyFieldHelper({ options, fieldData, dirtyFieldData, dirtyFields, field, fieldName });
1115
2054
  if (value !== undefined) {
@@ -1157,12 +2096,36 @@ class Model {
1157
2096
  return dirtyFields;
1158
2097
  }
1159
2098
 
2099
+ /// Get a field's value. Calling this is no different
2100
+ /// from accessing the field directly on the model instance
2101
+ /// itself. For example, `userInstance.firstName` is identical
2102
+ /// to calling `userInstance._getFieldValue('firstName', User.fields.firstName)`.
2103
+ ///
2104
+ /// This will call the `get` method defined on the field, if any is defined.
2105
+ /// If a `get` method is defined, then whatever value it returns will be
2106
+ /// the result of this method. If no `get` method is defined on the field,
2107
+ /// then `this.getDataValue(fieldName)` is called instead, and will provide
2108
+ /// the resulting return value for this method.
2109
+ ///
2110
+ /// Note:
2111
+ /// Never call this method (or the model attribute by name), while
2112
+ /// inside a field's `get` method. If you do, you will enter an
2113
+ /// infinite recursive loop. Instead, call the provided `get`
2114
+ /// method (as provided by the context), or call <see>Model.getDataValue</see>.
2115
+ /// Return: any
2116
+ /// Arguments:
2117
+ /// fieldName: string
2118
+ /// The name of the field whose value we wish to get.
2119
+ /// field: <see>Field</see>
2120
+ /// The field definition itself for the value we wish to get.
2121
+ /// See: Field
1160
2122
  _getFieldValue(fieldName, field) {
1161
2123
  let value = this.getDataValue(fieldName);
1162
2124
 
1163
2125
  if (typeof field.get === 'function') {
1164
2126
  return field.get.call(this, {
1165
2127
  model: this,
2128
+ self: this,
1166
2129
  set: this.setDataValue.bind(this, fieldName),
1167
2130
  get: this.getDataValue.bind(this, fieldName),
1168
2131
  value,
@@ -1174,10 +2137,37 @@ class Model {
1174
2137
  return value;
1175
2138
  }
1176
2139
 
2140
+ /// Set a field's value. Calling this is no different
2141
+ /// from setting the field directly on the model instance
2142
+ /// itself. For example, `userInstance.firstName = 'Bob'` is identical
2143
+ /// to calling `userInstance._setFieldValue('firstName', User.fields.firstName, 'Bob')`.
2144
+ ///
2145
+ /// This will call the `set` method defined on the field, if any is defined.
2146
+ /// If a `set` method is defined, then it is entirely up to the `set` method
2147
+ /// to call `this.setDataValue` to set the field's value.
2148
+ /// If no `set` method is defined on the field, then `this.setDataValue(fieldName, value)`
2149
+ /// is called instead, and will set the value on the field.
2150
+ ///
2151
+ /// Note:
2152
+ /// This method will mark the field as dirty if the value provided
2153
+ /// differs from the current value the field has.
2154
+ /// Note:
2155
+ /// Never call this method (or set the model attribute by name), while
2156
+ /// inside a field's `set` method. If you do, you will enter an
2157
+ /// infinite recursive loop. Instead, call the provided `set`
2158
+ /// method (as provided by the context), or call <see>Model.setDataValue</see>.
2159
+ /// Return: undefined
2160
+ /// Arguments:
2161
+ /// fieldName: string
2162
+ /// The name of the field whose value we wish to get.
2163
+ /// field: <see>Field</see>
2164
+ /// The field definition itself for the value we wish to get.
2165
+ /// See: Field
1177
2166
  _setFieldValue(fieldName, field, value) {
1178
2167
  if (typeof field.set === 'function') {
1179
2168
  field.set.call(this, {
1180
2169
  model: this,
2170
+ self: this,
1181
2171
  set: this.setDataValue.bind(this, fieldName),
1182
2172
  get: this.getDataValue.bind(this, fieldName),
1183
2173
  value,
@@ -1191,21 +2181,84 @@ class Model {
1191
2181
  this.setDataValue(fieldName, value);
1192
2182
  }
1193
2183
 
2184
+ /// Check if the model is persisted or not.
2185
+ /// This has nothing to do with the model's "dirty" state.
2186
+ /// if the model is dirty, then this will still report
2187
+ /// `true` if the model was initially loaded from the database.
2188
+ ///
2189
+ /// Return: boolean
2190
+ /// `true` if the model was loaded from the database,
2191
+ /// `false` otherwise.
1194
2192
  isPersisted() {
1195
2193
  return this._persisted;
1196
2194
  }
1197
2195
 
2196
+ /// Update the `this.dirtyID` cache key of the model.
2197
+ /// This is called any time a field is marked as "dirty" on the model.
2198
+ /// The `dirtyID` property of a model is an instance of
2199
+ /// a <see>CacheKey</see> that is intended to be used for
2200
+ /// caching systems. The <see>CacheKey</see> instance can
2201
+ /// be used for a key into a WeakMap, which can cache things.
2202
+ /// Because this is updated to a new instance of a <see>CacheKey</see>
2203
+ /// any time *any* field on the model is marked as dirty,
2204
+ /// any cache for this model will be invalidated as soon
2205
+ /// as any field on the model is dirty.
1198
2206
  updateDirtyID() {
1199
2207
  this.dirtyID = new CacheKey(this.dirtyID);
1200
2208
  }
1201
2209
 
2210
+ /// Check if the model is dirty or not.
2211
+ /// If a `fieldName` is provided as an argument,
2212
+ /// it will only check if the specified field
2213
+ /// is dirty or not. If not provided, then the
2214
+ /// entire model will be considered "dirty" if
2215
+ /// one or more fields are marked as "dirty".
2216
+ ///
2217
+ /// Return: boolean
2218
+ /// Arguments:
2219
+ /// fieldName?: string
2220
+ /// If provided, check if this one field is dirty. If not
2221
+ /// provided, then check if any field on the model is dirty.
1202
2222
  isDirty(fieldName) {
1203
- if (!fieldName)
1204
- return (Object.keys(this._dirtyFieldData).length > 0);
1205
- else
1206
- return Object.prototype.hasOwnProperty.call(this._dirtyFieldData, fieldName);
2223
+ if (!fieldName) {
2224
+ // Do this check first, because
2225
+ // it takes way less computation
2226
+ let isDirty = (Object.keys(this._dirtyFieldData).length > 0);
2227
+ if (isDirty)
2228
+ return true;
2229
+
2230
+ let changes = this.changes;
2231
+ return (Object.keys(changes).length > 0);
2232
+ } else {
2233
+ // Do this check first, because
2234
+ // it takes way less computation
2235
+ let hasDirtyField = Object.prototype.hasOwnProperty.call(this._dirtyFieldData, fieldName);
2236
+ if (hasDirtyField)
2237
+ return true;
2238
+
2239
+ let changes = this.changes;
2240
+ return Object.prototype.hasOwnProperty.call(changes, fieldName);
2241
+ }
1207
2242
  }
1208
2243
 
2244
+ /// Clear the dirty state of the model, or of a single field.
2245
+ /// If a `fieldName` argument is provided, then only the specified
2246
+ /// field's "dirty" status will be cleared. If no `fieldName`
2247
+ /// argument is provided, then all field's on the model will have
2248
+ /// their "dirty" status cleared, and the model will no longer be
2249
+ /// "dirty".
2250
+ ///
2251
+ /// The "dirty" status of a field is cleared by 1) setting the real
2252
+ /// underlying field value (this._fieldData) to the dirty field value,
2253
+ /// and 2) removing the entry in `this._dirtyFieldData` on the model for
2254
+ /// the field.
2255
+ ///
2256
+ /// Return: boolean
2257
+ /// Arguments:
2258
+ /// fieldName?: string
2259
+ /// If specified, clear only this field's "dirty" status.
2260
+ /// If not specified, clear all fields on the model, making
2261
+ /// the entire model no longer dirty.
1209
2262
  clearDirty(fieldName) {
1210
2263
  if (fieldName && Object.prototype.hasOwnProperty.call(this._dirtyFieldData, fieldName)) {
1211
2264
  this._fieldData[fieldName] = this._dirtyFieldData[fieldName];
@@ -1218,6 +2271,22 @@ class Model {
1218
2271
  this.updateDirtyID();
1219
2272
  }
1220
2273
 
2274
+ /// Get the dirty field descriptors themselves
2275
+ /// for all fields that are dirty.
2276
+ ///
2277
+ /// Whereas <see>Model._getDirtyFields</see> returns
2278
+ /// a "change list" of dirty fields, this method
2279
+ /// will return the field descriptors themselves.
2280
+ /// An array of <see>Field</see> will be returned
2281
+ /// for each dirty field, or an empty array if no
2282
+ /// fields are dirty.
2283
+ ///
2284
+ /// Return: Array<<see>Field</see>>
2285
+ /// The dirty fields of the model.
2286
+ /// Arguments:
2287
+ /// options?: object
2288
+ /// If provided, this will be the "options" supplied to
2289
+ /// <see>Model._getDirtyFields</see>.
1221
2290
  getDirtyFields(options) {
1222
2291
  let modelChanges = this._getDirtyFields(options);
1223
2292
  let dirtyFieldNames = Object.keys(modelChanges);
@@ -1225,6 +2294,24 @@ class Model {
1225
2294
  return this.getFields(dirtyFieldNames);
1226
2295
  }
1227
2296
 
2297
+ /// Get the current value of any field on the model.
2298
+ /// The "current" value will be the dirty value if
2299
+ /// the field is dirty, or it will be the clean
2300
+ /// value of the field if the field isn't dirty.
2301
+ ///
2302
+ /// This method is a "raw" get of the field's value
2303
+ /// in that it won't call `get` on the field descriptor,
2304
+ /// if any is defined. Instead it will first attempt to
2305
+ /// fetch the current value of the field from `this._dirtyFieldData`,
2306
+ /// if there is an entry for the field in the `this._dirtyFieldData`
2307
+ /// property of the model instance. If there is no "dirty" entry for
2308
+ /// the field, then its "clean value" (or raw value) will be fetched
2309
+ /// from the underlying `this._fieldData` storage area for field values.
2310
+ ///
2311
+ /// Return: any
2312
+ /// Arguments:
2313
+ /// fieldName: string
2314
+ /// The name of the field whose value you wish to fetch.
1228
2315
  getDataValue(fieldName) {
1229
2316
  let fieldData = this._fieldData;
1230
2317
  let dirtyFieldData = this._dirtyFieldData;
@@ -1238,6 +2325,32 @@ class Model {
1238
2325
  return value;
1239
2326
  }
1240
2327
 
2328
+ /// Set the current value of any field on the model.
2329
+ /// The "current" value will be the dirty value if
2330
+ /// the field is dirty, or it will be the clean
2331
+ /// value of the field if the field isn't dirty.
2332
+ ///
2333
+ /// This method is a "raw" set of the field's value
2334
+ /// in that it won't call `set` on the field descriptor,
2335
+ /// if any is defined. Instead, it will mark the field as
2336
+ /// dirty by setting the value provided as an entry for
2337
+ /// the field in the `this._dirtyFieldData` property of
2338
+ /// the model instance. If the value set is equal to the
2339
+ /// "clean value" of the field, as found in `this._fieldData`,
2340
+ /// then the dirty entry for the field will be cleared (deleted)
2341
+ /// instead.
2342
+ ///
2343
+ /// Note:
2344
+ /// This method will always call <see>Model.updateDirtyID</see>,
2345
+ /// invalidating all down-stream cache for this model instance.
2346
+ /// Return: undefined
2347
+ /// Arguments:
2348
+ /// fieldName: string
2349
+ /// The name of the field whose value you wish to set.
2350
+ /// value: any
2351
+ /// Value to set the field to. The will be sent through
2352
+ /// `field.type.castValue` to cast the incoming value
2353
+ /// to the field's type.
1241
2354
  setDataValue(fieldName, value) {
1242
2355
  let fieldData = this._fieldData;
1243
2356
  let dirtyFieldData = this._dirtyFieldData;
@@ -1248,6 +2361,13 @@ class Model {
1248
2361
 
1249
2362
  let newValue = this._castFieldValue(field, value);
1250
2363
 
2364
+ field.type.onSetFieldValue({
2365
+ self: this,
2366
+ value: newValue,
2367
+ field,
2368
+ fieldName,
2369
+ });
2370
+
1251
2371
  // If the values are exactly the same,
1252
2372
  // then we are no longer dirty, and
1253
2373
  // can just return
@@ -1264,6 +2384,27 @@ class Model {
1264
2384
  this.updateDirtyID();
1265
2385
  }
1266
2386
 
2387
+ /// Get all the models current raw attributes.
2388
+ /// This will return an object, where each key
2389
+ /// is the `fieldName` of each non-virtual (concrete)
2390
+ /// field on the model, and each value is the "current"
2391
+ /// value of the field on the model. The "current"
2392
+ /// value for each field could either be a dirty field
2393
+ /// value, or a clean (or raw) field value.
2394
+ ///
2395
+ /// This method is nearly identical to <see>Model.toJSON</see>
2396
+ /// except that <see>Model.toJSON</see> always adds the primary
2397
+ /// key to the result first, which only matters for JavaScript
2398
+ /// engines that honor the insertion order of keys in native
2399
+ /// Objects, which is never guaranteed. <see>Model.toJSON</see>
2400
+ /// also differs from this method by calling `field.type.serialize`
2401
+ /// for every attribute on the model.
2402
+ ///
2403
+ /// Note:
2404
+ /// Field's with `undefined` values will not be included in the
2405
+ /// resulting object.
2406
+ /// Return: object
2407
+ /// All models attributes (field values).
1267
2408
  getAttributes() {
1268
2409
  let result = {};
1269
2410
 
@@ -1281,6 +2422,34 @@ class Model {
1281
2422
  return result;
1282
2423
  }
1283
2424
 
2425
+ /// Set attributes on the model via the provided object.
2426
+ /// The provided object should contain keys that are
2427
+ /// field names for each field you wish to set. The
2428
+ /// value of each key will be what is set onto the field.
2429
+ /// This method ignores virtual fields, so if any virtual
2430
+ /// field names are supplied, then will be silently skipped.
2431
+ /// This method is identical to calling `modelInstance[fieldName] = value`
2432
+ /// on every key in the object provided.
2433
+ ///
2434
+ /// Note:
2435
+ /// Any `undefined` value in the provided object will be
2436
+ /// ignored, and no update will occur for the field.
2437
+ /// Note:
2438
+ /// If a raw object instead of a model instance is provided,
2439
+ /// then `hasOwnProperty` is called for each field name,
2440
+ /// and if the provided object doesn't contain its "own"
2441
+ /// property matching the field name, the field will be
2442
+ /// silently skipped, and not set.
2443
+ /// Return: undefined
2444
+ /// Arguments:
2445
+ /// attributes: object | Model
2446
+ /// The attributes to set on the model. The keys
2447
+ /// of the model should be field names.
2448
+ /// noPrimaryKey: boolean
2449
+ /// If `true`, then the primary key of the model
2450
+ /// (if any is defined) will be skipped, and won't
2451
+ /// be set, even if the provided `attributes` argument
2452
+ /// contains a value for the primary key.
1284
2453
  setAttributes(attributes, noPrimaryKey) {
1285
2454
  let isObject = Nife.instanceOf(attributes, 'object');
1286
2455
 
@@ -1302,6 +2471,22 @@ class Model {
1302
2471
  });
1303
2472
  }
1304
2473
 
2474
+ /// Checks if the primary key's value is valid.
2475
+ /// Validity checks are handled by the field's `type`.
2476
+ /// When this method is called, the primary key
2477
+ /// field for the model is fetched, and if found,
2478
+ /// the `field.type.isValidValue` method will be called
2479
+ /// and provided with the current value of the
2480
+ /// primary key field.
2481
+ ///
2482
+ /// <see>Type.isValidValue</see>
2483
+ /// is commonly implemented to be a strict check
2484
+ /// against the type. `null` will generally
2485
+ /// return `false` for an `isValidValue` call, however
2486
+ /// implementation details are entirely left up to
2487
+ /// the `isValidValue` method defined on the type.
2488
+ ///
2489
+ /// Return: boolean
1305
2490
  hasValidPrimaryKey() {
1306
2491
  let pkField = this.getPrimaryKeyField();
1307
2492
  if (!pkField)
@@ -1319,6 +2504,51 @@ class Model {
1319
2504
  });
1320
2505
  }
1321
2506
 
2507
+ /// Validate all concrete fields on the model.
2508
+ /// This method is called before any "insert"
2509
+ /// or "update" operation happens for the model.
2510
+ /// It will iterate all the fields of the model,
2511
+ /// and call the `validate` method defined on
2512
+ /// each field descriptor (<see>Field</see>),
2513
+ /// if any is defined.
2514
+ /// Each `validate` method is asynchronous, so
2515
+ /// all promises will be collected from all `validate`
2516
+ /// method calls, and will be awaited upon via
2517
+ /// `await Promise.all(validatePromises);`.
2518
+ ///
2519
+ /// Model validation will fail if any `validate`
2520
+ /// method throws an exception.
2521
+ ///
2522
+ /// `onValidate` is called directly from <see>Model.onBeforeSave</see>,
2523
+ /// so make certain you call `super.onBeforeSave(...args)` if you
2524
+ /// overload `onBeforeSave`. If you don't, your validation hooks
2525
+ /// won't be called. This design was deliberate, so the developer
2526
+ /// could choose to bypass model validation on purpose simply
2527
+ /// by not calling `super.onBeforeSave(...args)` should they choose not to.
2528
+ ///
2529
+ /// Feel free to overload `onValidate` to provide your own implementation.
2530
+ ///
2531
+ /// Note:
2532
+ /// `validate` properties on fields **must** always be methods.
2533
+ /// Unlike other ORMs, we don't allow a pattern, a RegExp, or
2534
+ /// anything else. You must provide the implementation of each
2535
+ /// `validate` method directly yourself (which could for example
2536
+ /// use a RegExp to check the field's value).
2537
+ /// Return: Array<any>
2538
+ /// Results of each `validate` call from each field. Will generally
2539
+ /// be `undefined` for each call. The validation for a field will
2540
+ /// only fail if an exception is thrown. `false` or `null` as a
2541
+ /// return value from a `validate` call is meaningless
2542
+ /// (unless you yourself wish to do something with it).
2543
+ /// Arguments:
2544
+ /// context: object
2545
+ /// A context object that is provided to all model hooks.
2546
+ /// The shape of this object is `{ connection, Model, options }`.
2547
+ /// It will contain the current `connection` for the operation
2548
+ /// (which might be a transaction connection), the `Model` for
2549
+ /// the operation, which is the "root model" of the operation,
2550
+ /// not necessarily "this" model (the model whose hook is being
2551
+ /// called), and the "options" defined for the current operation.
1322
2552
  async onValidate(context) {
1323
2553
  let promises = [];
1324
2554
 
@@ -1344,31 +2574,214 @@ class Model {
1344
2574
  await Promise.all(promises);
1345
2575
  }
1346
2576
 
2577
+ /// Before insert model hook.
2578
+ ///
2579
+ /// This method will be called on each model instance
2580
+ /// before it is inserted into the database. This method
2581
+ /// is called before dirty fields are calculated for
2582
+ /// the insert operation, so you can set field values
2583
+ /// here before insert, and they will be captured and
2584
+ /// inserted into the database.
2585
+ ///
2586
+ /// Arguments:
2587
+ /// context: object
2588
+ /// A context object that is provided to all model hooks.
2589
+ /// The shape of this object is `{ connection, Model, options }`.
2590
+ /// It will contain the current `connection` for the operation
2591
+ /// (which might be a transaction connection), the `Model` for
2592
+ /// the operation, which is the "root model" of the operation,
2593
+ /// not necessarily "this" model (the model whose hook is being
2594
+ /// called), and the "options" defined for the current operation.
1347
2595
  // eslint-disable-next-line no-unused-vars
1348
2596
  async onBeforeCreate(context) {
1349
2597
  }
1350
2598
 
2599
+ /// Before update model hook.
2600
+ ///
2601
+ /// This method will be called on each model instance
2602
+ /// before it is updated in the database. This method
2603
+ /// is called before dirty fields are calculated for
2604
+ /// the update operation, so you can set field values
2605
+ /// here before update, and they will be captured and
2606
+ /// updated in the database.
2607
+ ///
2608
+ /// Arguments:
2609
+ /// context: object
2610
+ /// A context object that is provided to all model hooks.
2611
+ /// The shape of this object is `{ connection, Model, options }`.
2612
+ /// It will contain the current `connection` for the operation
2613
+ /// (which might be a transaction connection), the `Model` for
2614
+ /// the operation, which is the "root model" of the operation,
2615
+ /// not necessarily "this" model (the model whose hook is being
2616
+ /// called), and the "options" defined for the current operation.
1351
2617
  // eslint-disable-next-line no-unused-vars
1352
2618
  async onBeforeUpdate(context) {
1353
2619
  }
1354
2620
 
2621
+ /// Before save model hook.
2622
+ ///
2623
+ /// This method will be called on each model instance
2624
+ /// before it is inserted or updated in the database.
2625
+ /// This method is called before dirty fields are calculated
2626
+ /// for the insert or update operation, so you can set field
2627
+ /// values here before update, and they will be captured and
2628
+ /// updated in the database.
2629
+ ///
2630
+ /// Note:
2631
+ /// This is the final hook call before the insert or update
2632
+ /// operation sends your data off to the database.
2633
+ ///
2634
+ /// Arguments:
2635
+ /// context: object
2636
+ /// A context object that is provided to all model hooks.
2637
+ /// The shape of this object is `{ connection, Model, options }`.
2638
+ /// It will contain the current `connection` for the operation
2639
+ /// (which might be a transaction connection), the `Model` for
2640
+ /// the operation, which is the "root model" of the operation,
2641
+ /// not necessarily "this" model (the model whose hook is being
2642
+ /// called), and the "options" defined for the current operation.
1355
2643
  // eslint-disable-next-line no-unused-vars
1356
2644
  async onBeforeSave(context) {
1357
2645
  await this.onValidate(context);
1358
2646
  }
1359
2647
 
2648
+ /// After insert model hook.
2649
+ ///
2650
+ /// This method will be called on each model instance
2651
+ /// after it has been inserted into the database.
2652
+ /// This method is called **after** each model instance
2653
+ /// has been fully stored, marked as persisted, and marked
2654
+ /// as not dirty. If you set any model fields in this method,
2655
+ /// then the model will be marked as dirty, and will be
2656
+ /// re-saved at the end of the insert operation.
2657
+ ///
2658
+ /// Note:
2659
+ /// **Use care when updating model attributes in this method**.
2660
+ /// At the end of each insert operation, the models just stored
2661
+ /// are scanned to see if any of them are dirty. If they are dirty
2662
+ /// they will be re-stored to the database using an update operation.
2663
+ /// This can happen for example if foreign keys are in use. We might
2664
+ /// not be able to update a foreign key field value until after the insert
2665
+ /// operation is completed. When a foreign key value is updated
2666
+ /// post-insert, then the model might again become dirty, and be
2667
+ /// updated post-insert. Because of this, you should be careful
2668
+ /// about making your model dirty in any "after" hooks, as you
2669
+ /// might unintentionally incur the cost of a post-insert update.
2670
+ ///
2671
+ /// Arguments:
2672
+ /// context: object
2673
+ /// A context object that is provided to all model hooks.
2674
+ /// The shape of this object is `{ connection, Model, options }`.
2675
+ /// It will contain the current `connection` for the operation
2676
+ /// (which might be a transaction connection), the `Model` for
2677
+ /// the operation, which is the "root model" of the operation,
2678
+ /// not necessarily "this" model (the model whose hook is being
2679
+ /// called), and the "options" defined for the current operation.
1360
2680
  // eslint-disable-next-line no-unused-vars
1361
2681
  async onAfterCreate(context) {
1362
2682
  }
1363
2683
 
2684
+ /// After update model hook.
2685
+ ///
2686
+ /// This method will be called on each model instance
2687
+ /// after it has been update in the database.
2688
+ /// This method is called **after** each model instance
2689
+ /// has been fully stored, marked as persisted, and marked
2690
+ /// as not dirty. Unlike an insert operation, update operations
2691
+ /// do not have a post-operation dirty model scan and re-update.
2692
+ /// You can safely update model attributes in this method without
2693
+ /// the worry of incurring the cost of an extra store operation.
2694
+ /// However, if you do set model attributes in this method, then
2695
+ /// the model will be marked dirty when the update operation is complete.
2696
+ ///
2697
+ /// Arguments:
2698
+ /// context: object
2699
+ /// A context object that is provided to all model hooks.
2700
+ /// The shape of this object is `{ connection, Model, options }`.
2701
+ /// It will contain the current `connection` for the operation
2702
+ /// (which might be a transaction connection), the `Model` for
2703
+ /// the operation, which is the "root model" of the operation,
2704
+ /// not necessarily "this" model (the model whose hook is being
2705
+ /// called), and the "options" defined for the current operation.
1364
2706
  // eslint-disable-next-line no-unused-vars
1365
2707
  async onAfterUpdate(context) {
1366
2708
  }
1367
2709
 
2710
+ /// After insert or update model hook.
2711
+ ///
2712
+ /// This method will be called on each model instance
2713
+ /// after it has been inserted or updated in the database.
2714
+ /// This method is called **after** each model instance
2715
+ /// has been fully stored, marked as persisted, and marked
2716
+ /// as not dirty. If this is an insert operation, and
2717
+ /// if you set any model fields in this method,
2718
+ /// then the model will be marked as dirty, and will be
2719
+ /// re-saved at the end of the insert operation.
2720
+ ///
2721
+ /// Note:
2722
+ /// **Use care when updating model attributes in this method**.
2723
+ /// At the end of each insert operation, the models just stored
2724
+ /// are scanned to see if any of them are dirty. If they are dirty
2725
+ /// they will be re-stored to the database using an update operation.
2726
+ /// This can happen for example if foreign keys are in use. We might
2727
+ /// not be able to update a foreign key field value until after the insert
2728
+ /// operation is completed. When a foreign key value is updated
2729
+ /// post-insert, then the model might again become dirty, and be
2730
+ /// updated post-insert. Because of this, you should be careful
2731
+ /// about making your model dirty in any "after" hooks, as you
2732
+ /// might unintentionally incur the cost of a post-insert update.
2733
+ ///
2734
+ /// Arguments:
2735
+ /// context: object
2736
+ /// A context object that is provided to all model hooks.
2737
+ /// The shape of this object is `{ connection, Model, options }`.
2738
+ /// It will contain the current `connection` for the operation
2739
+ /// (which might be a transaction connection), the `Model` for
2740
+ /// the operation, which is the "root model" of the operation,
2741
+ /// not necessarily "this" model (the model whose hook is being
2742
+ /// called), and the "options" defined for the current operation.
1368
2743
  // eslint-disable-next-line no-unused-vars
1369
2744
  async onAfterSave(context) {
1370
2745
  }
1371
2746
 
2747
+ /// Persist the model to the database.
2748
+ ///
2749
+ /// This method will either initiate an insert
2750
+ /// operation against the model (if `this.isPersisted()`
2751
+ /// returns `false`), or initiate an update operation (
2752
+ /// if `this.isPersisted()` returns `true`).
2753
+ ///
2754
+ /// This method will do nothing, and immediately return
2755
+ /// `false` if the model is not dirty. If you provide
2756
+ /// the option `force: true` as a property in the `options`
2757
+ /// argument, then all fields will forcefully be marked as
2758
+ /// dirty, using the current field's value as the "dirty"
2759
+ /// value for each field, and then the model will be either
2760
+ /// inserted or updated, based on the result from `isPersisted`.
2761
+ /// Using `force: true` will essentially send and insert or
2762
+ /// update operation through the connection that will update
2763
+ /// ALL columns for the model row in the database, regardless
2764
+ /// of if they were actually dirty or not.
2765
+ ///
2766
+ /// At the end of this method--assuming the operation was successful--
2767
+ /// `this.clearDirty()` will be called on the model instance to clear its
2768
+ /// dirty state, and then the model will be marked as persisted.
2769
+ /// Finally, `true` will be returned, letting the caller
2770
+ /// know the operation completed successfully.
2771
+ ///
2772
+ /// Return: boolean
2773
+ /// `true` if the model was persisted through the database,
2774
+ /// or `false` otherwise. `false` doesn't mean there was an
2775
+ /// error. Errors will be thrown. `false` simply means that
2776
+ /// the model had no dirty fields, so it was never sent to
2777
+ /// the database.
2778
+ /// Arguments:
2779
+ /// options?: object
2780
+ /// Options to pass to the underlying connection
2781
+ /// <see>Connection.insert</see> or <see>Connection.update</see>
2782
+ /// call. If a `connection` property is present on this
2783
+ /// options object, then it will be used as the connection
2784
+ /// for this operation.
1372
2785
  async save(_options) {
1373
2786
  let options = _options || {};
1374
2787
 
@@ -1398,6 +2811,34 @@ class Model {
1398
2811
  return this;
1399
2812
  }
1400
2813
 
2814
+ /// Reload the model in-place.
2815
+ ///
2816
+ /// This method will create a query selecting this model
2817
+ /// from the database by its primary key. It will then
2818
+ /// take the columns from the select operation, and will
2819
+ /// apply the values to the model's fields in-place.
2820
+ ///
2821
+ /// If a row is returned from the database, then the model's
2822
+ /// fields will be updated, `this.clearDirty()` will be
2823
+ /// called to clear the dirty status of the model, and
2824
+ /// finally the model will be marked as persisted.
2825
+ ///
2826
+ /// If no row is returned from the database, then this method
2827
+ /// will immediately return, and the model won't be updated.
2828
+ ///
2829
+ /// If the model has no primary key field defined, then an
2830
+ /// exception will be thrown.
2831
+ ///
2832
+ /// Note:
2833
+ /// A primary key is required on the model for this method
2834
+ /// to work. If you don't have a primary key defined, then
2835
+ /// you will need to create your own "reload" operation.
2836
+ /// Return: undefined
2837
+ /// Arguments:
2838
+ /// options?: object
2839
+ /// Options to pass to the underlying <see>QueryEngine.first</see>
2840
+ /// call. If a `connection` property is present on this options object,
2841
+ /// then it will be used as the connection for this operation.
1401
2842
  async reload(options) {
1402
2843
  let pkFieldName = this.getPrimaryKeyFieldName();
1403
2844
  if (!pkFieldName)
@@ -1423,6 +2864,29 @@ class Model {
1423
2864
  this.clearDirty();
1424
2865
  }
1425
2866
 
2867
+ /// Destroy this model instance.
2868
+ ///
2869
+ /// Calling this method will destroy this model from the database.
2870
+ /// If the model is not persisted, then nothing will happen, and
2871
+ /// `0` will be returned. If the model is persisted, the result
2872
+ /// of <see>Connection.destroy</see> is returned, which is the number
2873
+ /// of rows modified in the database, which in this case should always
2874
+ /// be `1`.
2875
+ ///
2876
+ /// If the model has no primary key field defined, then an
2877
+ /// exception will be thrown.
2878
+ ///
2879
+ /// Note:
2880
+ /// A primary key is required on the model for this method
2881
+ /// to work. If you don't have a primary key defined, then
2882
+ /// you will need to create your own "destroy" operation.
2883
+ /// Return: number
2884
+ /// The number of rows modified in the database.
2885
+ /// Arguments:
2886
+ /// options?: object
2887
+ /// Options to pass to the underlying <see>QueryEngine.destroy</see>
2888
+ /// call. If a `connection` property is present on this options object,
2889
+ /// then it will be used as the connection for this operation.
1426
2890
  async destroy(options) {
1427
2891
  if (!this.isPersisted())
1428
2892
  return 0;
@@ -1434,10 +2898,26 @@ class Model {
1434
2898
  return await this.getModel().getWhereWithConnection(options)[primaryKeyFieldName].EQ(this.id).destroy(options);
1435
2899
  }
1436
2900
 
2901
+ /// Get a string representation of this model.
2902
+ ///
2903
+ /// Return: string
2904
+ /// The model's name and attributes as a string.
1437
2905
  toString() {
1438
2906
  return `${this.getModelName()} ${JSON.stringify(this.toJSON(), undefined, 2)}`;
1439
2907
  }
1440
2908
 
2909
+ /// Return a raw Object of the model's attributes,
2910
+ /// ready to be passed through `JSON.stringify`. This
2911
+ /// method is nearly identical to <see>Model.getAttributes</see>
2912
+ /// except that it always inserts the primary key of the model
2913
+ /// (if the model has one) first. The other difference
2914
+ /// between this method and <see>Model.getAttributes</see>
2915
+ /// is that this method will call `field.type.serialize` on
2916
+ /// each field value.
2917
+ ///
2918
+ /// Return: object
2919
+ /// All model attributes, ready to be serialized.
2920
+ /// See: Type.serialize
1441
2921
  toJSON() {
1442
2922
  let result = {};
1443
2923
  let pkField = this.getPrimaryKeyField();
@@ -1469,8 +2949,8 @@ class Model {
1469
2949
  }
1470
2950
 
1471
2951
  // eslint-disable-next-line no-unused-vars
1472
- [Symbol.for('nodejs.util.inspect.custom')](depth, inspectOptions, inspect) {
1473
- return inspect(this.toJSON(), inspectOptions);
2952
+ [Symbol.for('nodejs.util.inspect.custom')]() {
2953
+ return this.toJSON();
1474
2954
  }
1475
2955
  }
1476
2956