alchemymvc 1.2.5 → 1.2.7

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.
Files changed (86) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +0 -0
  3. package/lib/app/assets/scripts/.gitkeep +0 -0
  4. package/lib/app/assets/stylesheets/alchemy-info.less +0 -0
  5. package/lib/app/behaviour/publishable_behaviour.js +0 -0
  6. package/lib/app/behaviour/revision_behaviour.js +0 -0
  7. package/lib/app/behaviour/sluggable_behaviour.js +0 -0
  8. package/lib/app/component/.gitkeep +0 -0
  9. package/lib/app/conduit/electron_conduit.js +0 -0
  10. package/lib/app/conduit/http_conduit.js +173 -173
  11. package/lib/app/conduit/loopback_conduit.js +2 -3
  12. package/lib/app/conduit/socket_conduit.js +620 -620
  13. package/lib/app/controller/alchemy_info_controller.js +0 -0
  14. package/lib/app/datasource/mongo_datasource.js +0 -0
  15. package/lib/app/element/time_ago.js +2 -4
  16. package/lib/app/helper/alchemy_helper.js +22 -11
  17. package/lib/app/helper/client_collection.js +0 -0
  18. package/lib/app/helper/pagination_helper.js +0 -0
  19. package/lib/app/helper/router_helper.js +20 -7
  20. package/lib/app/helper/socket_helper.js +613 -613
  21. package/lib/app/helper_component/paginate_component.js +0 -0
  22. package/lib/app/helper_controller/component.js +0 -0
  23. package/lib/app/helper_controller/conduit.js +0 -0
  24. package/lib/app/helper_controller/controller.js +0 -0
  25. package/lib/app/helper_datasource/00-nosql_datasource.js +0 -0
  26. package/lib/app/helper_datasource/05-fallback_datasource.js +0 -0
  27. package/lib/app/helper_datasource/idb_datasource.js +0 -0
  28. package/lib/app/helper_datasource/indexed_db.js +0 -0
  29. package/lib/app/helper_field/00-objectid_field.js +0 -0
  30. package/lib/app/helper_field/06-text_field.js +0 -0
  31. package/lib/app/helper_field/10-number_field.js +0 -0
  32. package/lib/app/helper_field/boolean_field.js +0 -0
  33. package/lib/app/helper_field/date_field.js +0 -0
  34. package/lib/app/helper_field/datetime_field.js +0 -0
  35. package/lib/app/helper_field/enum_field.js +0 -0
  36. package/lib/app/helper_field/geopoint_field.js +0 -0
  37. package/lib/app/helper_field/habtm_field.js +0 -0
  38. package/lib/app/helper_field/hasoneparent_field.js +0 -0
  39. package/lib/app/helper_field/html_field.js +0 -0
  40. package/lib/app/helper_field/integer_field.js +0 -0
  41. package/lib/app/helper_field/object_field.js +0 -0
  42. package/lib/app/helper_field/regexp_field.js +0 -0
  43. package/lib/app/helper_field/schema_field.js +23 -2
  44. package/lib/app/helper_field/time_field.js +0 -0
  45. package/lib/app/helper_field/url_field.js +0 -0
  46. package/lib/app/helper_model/criteria.js +21 -3
  47. package/lib/app/helper_model/db_query.js +0 -0
  48. package/lib/app/helper_model/document_list.js +0 -0
  49. package/lib/app/helper_model/field_config.js +2 -2
  50. package/lib/app/helper_model/model.js +72 -5
  51. package/lib/app/model/alchemy_task_model.js +0 -0
  52. package/lib/app/routes.js +0 -0
  53. package/lib/app/view/alchemy/info.ejs +0 -0
  54. package/lib/app/view/error/unknown.ejs +0 -0
  55. package/lib/app/view/paginate/navlist.ejs +0 -0
  56. package/lib/bootstrap.js +0 -0
  57. package/lib/class/behaviour.js +0 -0
  58. package/lib/class/component.js +0 -0
  59. package/lib/class/conduit.js +2572 -2552
  60. package/lib/class/controller.js +4 -1
  61. package/lib/class/document.js +18 -4
  62. package/lib/class/document_list.js +0 -0
  63. package/lib/class/helper.js +0 -0
  64. package/lib/class/inode.js +0 -0
  65. package/lib/class/inode_dir.js +0 -0
  66. package/lib/class/inode_file.js +136 -112
  67. package/lib/class/inode_list.js +0 -0
  68. package/lib/class/model.js +1740 -1769
  69. package/lib/class/path_definition.js +0 -0
  70. package/lib/class/route.js +0 -0
  71. package/lib/class/router.js +6 -3
  72. package/lib/class/session.js +0 -0
  73. package/lib/class/task.js +0 -0
  74. package/lib/core/base.js +41 -18
  75. package/lib/core/client_alchemy.js +48 -2
  76. package/lib/core/discovery.js +0 -0
  77. package/lib/core/middleware.js +6 -4
  78. package/lib/core/routing.js +0 -0
  79. package/lib/core/socket.js +159 -159
  80. package/lib/init/alchemy.js +1848 -1823
  81. package/lib/init/constants.js +0 -0
  82. package/lib/init/functions.js +8 -4
  83. package/lib/init/load_functions.js +0 -0
  84. package/lib/init/requirements.js +101 -101
  85. package/lib/stages.js +10 -2
  86. package/package.json +74 -74
@@ -1,1770 +1,1741 @@
1
- var nameCache = {},
2
- mongo = alchemy.use('mongodb'),
3
- all_prefixes = alchemy.shared('Routing.prefixes'),
4
- fs = alchemy.use('fs'),
5
- createdModel;
6
-
7
- /**
8
- * The Model class
9
- *
10
- * @constructor
11
- *
12
- * @author Jelle De Loecker <jelle@develry.be>
13
- * @since 0.0.1
14
- * @version 1.1.0
15
- */
16
- var Model = Function.inherits('Alchemy.Base', 'Alchemy.Model', function Model(options) {
17
- this.init(options);
18
- });
19
-
20
- /**
21
- * Set the modelName property after class creation
22
- *
23
- * @author Jelle De Loecker <jelle@develry.be>
24
- * @since 0.2.0
25
- * @version 1.1.0
26
- */
27
- Model.constitute(function setModelName() {
28
- this.model_name = this.name;
29
- this.setProperty('model_name', this.model_name);
30
-
31
- if (!this.table) {
32
- this.table = this.model_name.tableize();
33
- }
34
- });
35
-
36
- /**
37
- * This is a model constructor
38
- *
39
- * @type {Boolean}
40
- */
41
- Model.setStaticProperty('model', true);
42
-
43
- /**
44
- * The cache duration static getter/setter
45
- *
46
- * @author Jelle De Loecker <jelle@develry.be>
47
- * @since 0.1.0
48
- * @version 1.0.3
49
- *
50
- * @property cache_duration
51
- * @type {String}
52
- */
53
- Model.setStaticProperty(function cache_duration() {
54
-
55
- if (this._cache_duration == null) {
56
- this._cache_duration = alchemy.settings.model_query_cache_duration;
57
- }
58
-
59
- return this._cache_duration;
60
- }, function setCacheDuration(duration) {
61
- this._cache_duration = duration;
62
-
63
- // @todo: reset cache
64
- });
65
-
66
- /**
67
- * Get the cache object
68
- *
69
- * @author Jelle De Loecker <jelle@develry.be>
70
- * @since 0.1.0
71
- * @version 1.0.3
72
- *
73
- * @property cache
74
- * @type {Object}
75
- */
76
- Model.setStaticProperty(function cache() {
77
-
78
- if (this.cache_duration) {
79
-
80
- if (this._cache) {
81
- return this._cache;
82
- }
83
-
84
- this._cache = alchemy.getCache(this.name, this.cache_duration);
85
- return this._cache;
86
- }
87
-
88
- return false;
89
- }, function setCache(value) {
90
- return this._cache = value;
91
- });
92
-
93
-
94
- /**
95
- * Is this an abstract model?
96
- *
97
- * @author Jelle De Loecker <jelle@develry.be>
98
- * @since 1.1.0
99
- * @version 1.1.0
100
- *
101
- * @type {Boolean}
102
- */
103
- Model.setStaticProperty(function is_abstract() {
104
-
105
- // Do simple is_abstract_class check
106
- if (this.is_abstract_class != null) {
107
- return !!this.is_abstract_class;
108
- }
109
-
110
- // If we need to load an external schema, it's also not abstract
111
- if (this.prototype.load_external_schema) {
112
- return false;
113
- }
114
-
115
- // See if this model has other fields than the default ones
116
- let field_count = this.schema.array.length;
117
-
118
- if (this.schema.has('_id')) {
119
- field_count--;
120
- }
121
-
122
- if (this.schema.has('created')) {
123
- field_count--;
124
- }
125
-
126
- if (this.schema.has('updated')) {
127
- field_count--;
128
- }
129
-
130
- return field_count < 1;
131
- });
132
-
133
-
134
- /**
135
- * Get the document class constructor
136
- *
137
- * @type {Alchemy.Document}
138
- */
139
- Model.prepareStaticProperty('Document', function getDocumentClass() {
140
- return Classes.Alchemy.Document.Document.getDocumentClass(this);
141
- });
142
-
143
- /**
144
- * Get the client document class constructor
145
- *
146
- * @type {Hawkejs.Document}
147
- */
148
- Model.prepareStaticProperty('ClientDocument', function getClientDocumentClass() {
149
- return this.Document.getClientDocumentClass();
150
- });
151
-
152
- /**
153
- * Set the static per-model schema
154
- *
155
- * @version 1.1.0
156
- *
157
- * @type {Schema}
158
- */
159
- Model.staticCompose('schema', function createSchema(doNext) {
160
-
161
- var that = this,
162
- model = this.compositorParent,
163
- schema = new Classes.Alchemy.Schema();
164
-
165
- // The base Model does not have a schema
166
- if (model.name == 'Model') {
167
- return false;
168
- } else {
169
-
170
- // Link the schema to this model
171
- schema.setModel(model);
172
-
173
- // Set the schema name
174
- schema.setName(model.name);
175
-
176
- if (model.prototype.add_basic_fields !== false) {
177
-
178
- // Set default model fields immediately after this function ends
179
- // This has to be scheduled next, because addField would call createSchema
180
- // again, resulting in an infinite loop
181
- doNext(function addSchemaBasics() {
182
- model.addField('_id', 'ObjectId', {default: Field.createPathEvaluator('alchemy.ObjectId')});
183
- model.addField('created', 'Datetime', {default: Field.createPathEvaluator('Date.create')});
184
- model.addField('updated', 'Datetime', {default: Field.createPathEvaluator('Date.create')});
185
- });
186
- }
187
- }
188
-
189
- return schema;
190
- }, [
191
- 'addEnumValues',
192
- 'setEnumValues',
193
- 'belongsTo',
194
- 'hasOneParent',
195
- 'hasAndBelongsToMany',
196
- 'hasMany',
197
- 'hasOneChild',
198
- 'addIndex',
199
- 'addRule',
200
- ]);
201
-
202
- Model.setDeprecatedProperty('modelName', 'model_name');
203
- Model.setDeprecatedProperty('blueprint', 'schema');
204
-
205
- /**
206
- * The default database config to use
207
- *
208
- * @type {String}
209
- */
210
- Model.setProperty('dbConfig', 'default');
211
-
212
- /**
213
- * The default field to use as display
214
- *
215
- * @type {String}
216
- */
217
- Model.setProperty('displayField', 'title');
218
-
219
- /**
220
- * Translate is on by default
221
- *
222
- * @type {Boolean}
223
- */
224
- Model.setProperty('translate', true);
225
-
226
- /**
227
- * Set the name of the primary key field
228
- *
229
- * @author Jelle De Loecker <jelle@develry.be>
230
- * @since 0.1.0
231
- * @version 0.1.0
232
- */
233
- Model.setProperty('primary_key', '_id');
234
-
235
- /**
236
- * Should we load the schema from the database?
237
- *
238
- * @author Jelle De Loecker <jelle@develry.be>
239
- * @since 1.1.0
240
- * @version 1.1.0
241
- */
242
- Model.setProperty('load_external_schema', false);
243
-
244
- /**
245
- * Object where behaviours are stored
246
- *
247
- * @type {Object}
248
- */
249
- Model.prepareProperty('behaviours', Object);
250
-
251
- /**
252
- * Associations
253
- *
254
- * @type {Object}
255
- */
256
- Model.setProperty(function associations() {
257
- return this.schema.associations;
258
- });
259
-
260
- /**
261
- * Instance access to static cache
262
- *
263
- * @type {Expirable}
264
- */
265
- Model.prepareProperty('cache', function cache() {
266
- return this.constructor.cache;
267
- });
268
-
269
- /**
270
- * Instance access to static schema
271
- *
272
- * @type {Schema}
273
- */
274
- Model.setProperty(function schema() {
275
- return this.constructor.schema;
276
- });
277
-
278
- /**
279
- * Is this an abstract model?
280
- *
281
- * @type {Boolean}
282
- */
283
- Model.setProperty(function is_abstract() {
284
- return this.constructor.is_abstract;
285
- });
286
-
287
- /**
288
- * This is a wrapper class
289
- */
290
- Model.makeAbstractClass();
291
-
292
- /**
293
- * This wrapper class starts a new group
294
- */
295
- Model.startNewGroup();
296
-
297
- /**
298
- * The connection
299
- *
300
- * @type {Object}
301
- */
302
- Model.prepareProperty('datasource', function datasource() {
303
- if (this.table) return Datasource.get(this.dbConfig);
304
- });
305
-
306
- /**
307
- * The default sort options
308
- *
309
- * @type {Object}
310
- */
311
- Model.prepareProperty('sort', function sort() {
312
- return {created: 1};
313
- });
314
-
315
- /**
316
- * Check a url value
317
- *
318
- * @author Jelle De Loecker <jelle@develry.be>
319
- * @since 1.0.0
320
- * @version 1.2.5
321
- *
322
- * @param {String} value The value in the url
323
- * @param {String} name The name of the url parameter
324
- * @param {String} field_name The name of the field to check
325
- * @param {Conduit} conduit The optional conduit
326
- *
327
- * @return {Pledge}
328
- */
329
- Model.setStatic(async function checkPathValue(value, name, field_name, conduit) {
330
-
331
- var instance,
332
- pledge,
333
- crit;
334
-
335
- if (!field_name) {
336
- if (name == 'id') {
337
- field_name = this.prototype.primary_key;
338
- } else {
339
- field_name = name;
340
- }
341
- }
342
-
343
- if (conduit) {
344
- instance = conduit.getModel(this);
345
- } else {
346
- instance = new this;
347
- }
348
-
349
- // Create new criteria instance
350
- crit = instance.find();
351
-
352
- // Look for the wanted field
353
- crit.where(field_name).equals(value);
354
-
355
- let result = await instance.find('first', crit);
356
-
357
- if (result) {
358
- let found_value = result[field_name];
359
-
360
- if (found_value != value && !Object.alike(value, found_value)) {
361
- conduit.rewriteRequestRouteParam(name, found_value);
362
- }
363
- }
364
-
365
- return result;
366
- });
367
-
368
- /**
369
- * Add a field to this model's schema
370
- *
371
- * @author Jelle De Loecker <jelle@develry.be>
372
- * @since 0.2.0
373
- * @version 1.2.4
374
- *
375
- * @return {Alchemy.Field}
376
- */
377
- Model.setStatic(function addField(name, type, options) {
378
-
379
- var field,
380
- is_new;
381
-
382
- is_new = !this.schema.has(name);
383
-
384
- // Add it to the schema
385
- field = this.schema.addField(name, type, options);
386
-
387
- if (is_new) {
388
- // Add it to the Document class
389
- this.Document.setFieldGetter(name);
390
-
391
- // False means it should not be set on the server implementation
392
- // (because that's where it's coming from)
393
- // Yes, this also sets private fields on the server-side client document.
394
- this.ClientDocument.setFieldGetter(name, null, null, false);
395
- }
396
-
397
- return field;
398
- });
399
-
400
-
401
- /**
402
- * Add a behaviour to this model
403
- *
404
- * @author Jelle De Loecker <jelle@develry.be>
405
- * @since 0.2.0
406
- * @version 0.2.0
407
- */
408
- Model.setStatic(function addBehaviour(behaviour_name, options) {
409
- return this.schema.addBehaviour(behaviour_name, options);
410
- });
411
-
412
- /**
413
- * Add an association to this model's schema
414
- * and set it on the Document as a getter
415
- *
416
- * @author Jelle De Loecker <jelle@develry.be>
417
- * @since 0.2.0
418
- * @version 1.2.4
419
- */
420
- Model.setStatic(function addAssociation(type, alias, model_name, options) {
421
- var data = this.schema.addAssociation(type, alias, model_name, options);
422
- this.Document.setAliasGetter(data.alias);
423
-
424
- // False means it should not be set on the server implementation
425
- // (because that's where it's coming from)
426
- // Yes, this also sets private fields on the server-side client document.
427
- this.ClientDocument.setAliasGetter(name);
428
- });
429
-
430
- /**
431
- * Set a method on the document class
432
- *
433
- * @author Jelle De Loecker <jelle@develry.be>
434
- * @since 0.2.0
435
- * @version 1.0.6
436
- */
437
- Model.setStatic(function setDocumentMethod(name, fnc) {
438
-
439
- var that = this;
440
-
441
- if (typeof name == 'function') {
442
- fnc = name;
443
- name = fnc.name;
444
- }
445
-
446
- Blast.loaded(function whenLoaded() {
447
- that.Document.setMethod(name, fnc);
448
- });
449
- });
450
-
451
- /**
452
- * Set a property on the document class
453
- *
454
- * @author Jelle De Loecker <jelle@develry.be>
455
- * @since 0.2.0
456
- * @version 1.0.6
457
- */
458
- Model.setStatic(function setDocumentProperty(name, fnc) {
459
-
460
- var that = this,
461
- args = arguments;
462
-
463
- Blast.loaded(function whenLoaded() {
464
- that.Document.setProperty.apply(that.Document, args);
465
- });
466
- });
467
-
468
- /**
469
- * Get a field
470
- *
471
- * @author Jelle De Loecker <jelle@develry.be>
472
- * @since 0.2.0
473
- * @version 0.2.0
474
- *
475
- * @return {FieldType}
476
- */
477
- Model.setStatic(function getField(name) {
478
-
479
- var fieldPath,
480
- alias,
481
- model,
482
- split;
483
-
484
- if (name.indexOf('.') > -1) {
485
- split = name.split('.');
486
-
487
- alias = name[0];
488
-
489
- if (this.schema.associations[alias] == null) {
490
- model = this;
491
- fieldPath = name;
492
- } else {
493
- model = Model.get(this.schema.associations[alias].modelName).constructor;
494
- split.shift();
495
- fieldPath = split.join('.');
496
- }
497
- } else {
498
- model = this;
499
- fieldPath = name;
500
- }
501
-
502
- return model.schema.get(fieldPath);
503
- });
504
-
505
- /**
506
- * Get the model's public configuration
507
- * (This is used to create the client-side Model instances)
508
- *
509
- * @author Jelle De Loecker <jelle@develry.be>
510
- * @since 1.0.0
511
- * @version 1.1.0
512
- */
513
- Model.setStatic(function getClientConfig() {
514
-
515
- var result = {
516
- name : this.model_name,
517
- schema : this.schema,
518
- primary_key : this.prototype.primary_key
519
- };
520
-
521
- if (this.super.name != 'Model') {
522
- result.parent = this.super.name;
523
- }
524
-
525
- return result;
526
- });
527
-
528
- /**
529
- * Initialize behaviours
530
- *
531
- * @author Jelle De Loecker <jelle@develry.be>
532
- * @since 0.2.0
533
- * @version 0.2.0
534
- *
535
- * @return {Document}
536
- */
537
- Model.setMethod(function initBehaviours() {
538
-
539
- var behaviour,
540
- key;
541
-
542
- this.behaviours = {};
543
-
544
- for (key in this.schema.behaviours) {
545
- behaviour = this.schema.behaviours[key];
546
-
547
- this.behaviours[key] = new behaviour.constructor(this, behaviour.options);
548
- }
549
-
550
- });
551
-
552
- /**
553
- * Enable a behaviour on-the-fly
554
- *
555
- * @author Jelle De Loecker <jelle@develry.be>
556
- * @since 0.0.1
557
- * @version 0.2.0
558
- */
559
- Model.setMethod(function addBehaviour(behaviourname, options) {
560
-
561
- var instance;
562
-
563
- if (!options) {
564
- options = {};
565
- }
566
-
567
- instance = Behaviour.get(behaviourname, this, options);
568
- this.behaviours[behaviourname] = instance;
569
-
570
- return instance;
571
- });
572
-
573
- /**
574
- * Get a behaviour instance
575
- *
576
- * @author Jelle De Loecker <jelle@develry.be>
577
- * @since 1.0.3
578
- * @version 1.0.3
579
- *
580
- * @param {String} name
581
- *
582
- * @return {Behaviour}
583
- */
584
- Model.setMethod(function getBehaviour(name) {
585
-
586
- name = name.camelize();
587
-
588
- if (!name.endsWith('Behaviour')) {
589
- name += 'Behaviour';
590
- }
591
-
592
- return this.behaviours[name];
593
- });
594
-
595
- /**
596
- * Enable translations
597
- *
598
- * @author Jelle De Loecker <jelle@develry.be>
599
- * @since 0.2.0
600
- * @version 0.2.0
601
- */
602
- Model.setMethod(function enableTranslations() {
603
- this.translate = true;
604
- });
605
-
606
- /**
607
- * Disable translations
608
- *
609
- * @author Jelle De Loecker <jelle@develry.be>
610
- * @since 0.2.0
611
- * @version 0.2.0
612
- */
613
- Model.setMethod(function disableTranslations() {
614
- this.translate = false;
615
- });
616
-
617
- /**
618
- * Aggregate
619
- *
620
- * @author Jelle De Loecker <jelle@develry.be>
621
- * @since 0.5.0
622
- * @version 0.5.0
623
- *
624
- * @param {Array} pipeline
625
- * @param {Function} callback
626
- */
627
- Model.setMethod(function aggregate(pipeline, callback) {
628
-
629
- this.datasource.collection(this.table, function gotCollection(err, collection) {
630
-
631
- if (err) {
632
- return callback(err);
633
- }
634
-
635
- collection.aggregate(pipeline, callback);
636
- });
637
- });
638
-
639
- /**
640
- * Translate the given records
641
- *
642
- * @author Jelle De Loecker <jelle@develry.be>
643
- * @since 0.2.0
644
- * @version 1.1.4
645
- *
646
- * @param {Array} items
647
- * @param {Object} options Optional options object
648
- * @param {Function} callback
649
- */
650
- Model.setMethod(function translateItems(items, options, callback) {
651
-
652
- var collection,
653
- fieldName,
654
- prefixes,
655
- conduit,
656
- prefix,
657
- record,
658
- alias,
659
- found,
660
- item,
661
- key,
662
- i,
663
- j;
664
-
665
- if (options && options instanceof Classes.Alchemy.Criteria.Criteria) {
666
- options = options.options;
667
- }
668
-
669
- // No items to translate
670
- if (!items.length) {
671
- return callback();
672
- }
673
-
674
- // No fields in this schema are translatable
675
- if (!this.schema.hasTranslations) {
676
- return callback();
677
- }
678
-
679
- // Do nothing if there are no translatable fields
680
- // or translate is disabled
681
- if (!this.translate || (!this.conduit && !options.locale)) {
682
- return callback();
683
- }
684
-
685
- // Get the alias we need to translate
686
- alias = options.forAlias || this.name;
687
-
688
- // Get the (optional) attached conduit
689
- conduit = this.conduit;
690
-
691
- // If prefixes are given as an option, only use those
692
- if (options.prefixes) {
693
- prefix = options.prefixes;
694
- } else {
695
- // Possible prefixes
696
- prefix = [];
697
-
698
- // Prefixes set in the options get precedence
699
- if (options.locale && options.locale !== true) {
700
- prefix.include(options.locale);
701
- }
702
-
703
- // Append the visited prefix after that (if there is one)
704
- if (conduit && conduit.prefix) {
705
- prefix.include(conduit.prefix);
706
- }
707
-
708
- // Append all the allowed locales after that
709
- if (conduit && conduit.locales) {
710
- prefix.include(conduit.locales);
711
- }
712
-
713
- // Add all available prefixes last
714
- for (key in all_prefixes) {
715
- prefix.push(key);
716
- }
717
-
718
- // The fallback prefix
719
- prefix.push('__');
720
-
721
- // @DEPRECATED: empty keys should no longer be allowed
722
- prefix.push('');
723
- }
724
-
725
- for (i = 0; i < items.length; i++) {
726
- item = items[i];
727
-
728
- if (!options.ungrouped_items) {
729
- item = item[alias];
730
- }
731
-
732
- collection = Array.cast(item);
733
-
734
- // Clone the prefixes
735
- prefixes = prefix.slice(0);
736
-
737
- // If one of the query conditions searched through a translatable field,
738
- // the prefix found should get preference
739
- if (options.use_found_prefix && items.item_prefixes && items.item_prefixes[i]) {
740
- prefixes.unshift(items.item_prefixes[i]);
741
- }
742
-
743
- let field;
744
-
745
- for (j = 0; j < collection.length; j++) {
746
- record = collection[j];
747
-
748
- for (fieldName in this.schema.translatableFields) {
749
- field = this.schema.translatableFields[fieldName];
750
- field.translateRecord(prefixes, record, options.allow_empty);
751
- }
752
- }
753
- }
754
-
755
- callback();
756
- });
757
-
758
- /**
759
- * Create the given record if the id does not exist in the database
760
- *
761
- * @author Jelle De Loecker <jelle@develry.be>
762
- * @since 0.0.1
763
- * @version 1.1.0
764
- *
765
- * @param {Array} list A list of all the records that need to be in the db
766
- * @param {Function} callback
767
- */
768
- Model.setMethod(function ensureIds(list, callback) {
769
-
770
- var that = this;
771
-
772
- list = Array.cast(list);
773
-
774
- return Function.forEach.parallel(list, function checkEntry(entry, key, next) {
775
- var id;
776
-
777
- id = entry[that.primary_key];
778
-
779
- if (!id && entry[that.name]) {
780
- id = entry[that.name][that.primary_key];
781
- }
782
-
783
- if (!id) {
784
- return next(new Classes.Alchemy.Error.Model('`Model#ensureIds()` can\'t ensure an entry without an _id'));
785
- }
786
-
787
- that.findById(id, function gotItem(err, result) {
788
-
789
- if (err) {
790
- return next(err);
791
- }
792
-
793
- if (result) {
794
- return next();
795
- }
796
-
797
- that.save(entry, {create: true, document: false}, next);
798
- });
799
- }, callback);
800
- });
801
-
802
- /**
803
- * Save one record
804
- *
805
- * @author Jelle De Loecker <jelle@develry.be>
806
- * @since 0.0.1
807
- * @version 1.2.0
808
- *
809
- * @param {Document} document
810
- * @param {Object} options
811
- * @param {Function} callback
812
- *
813
- * @return {Pledge}
814
- */
815
- Model.setMethod(function saveRecord(document, options, callback) {
816
-
817
- var that = this,
818
- saved_record,
819
- creating,
820
- results,
821
- pledge,
822
- main,
823
- iter;
824
-
825
- if (!document) {
826
- pledge = Pledge.reject(new Error('Unable to save record: given document is undefined'));
827
- pledge.done(callback);
828
- return pledge;
829
- }
830
-
831
- // Normalize the arguments
832
- if (typeof options == 'function') {
833
- callback = options;
834
- }
835
-
836
- if (typeof options !== 'object') {
837
- options = {};
838
- }
839
-
840
- pledge = Function.series(function doAudit(next) {
841
-
842
- if (Object.isPlainObject(document)) {
843
- return next(new Error('Model#saveRecord() expects a Document, not a plain object'));
844
- }
845
-
846
- // Look through unique indexes if no _id is present
847
- that.auditRecord(document, options, function afterAudit(err, doc) {
848
-
849
- if (err) {
850
- return next(err);
851
- }
852
-
853
- // Is a new record being created?
854
- creating = options.create || doc[that.primary_key] == null;
855
- next();
856
- });
857
- }, function doBeforeSave(next) {
858
-
859
- if (typeof that.beforeSave == 'function') {
860
- let promise = that.beforeSave(document, options, next);
861
-
862
- if (promise) {
863
- Pledge.done(promise, next);
864
- } else if (that.beforeSave.length < 3) {
865
- // If the method accepts no `next` callback, call it now
866
- next();
867
- }
868
- } else {
869
- next();
870
- }
871
-
872
- }, function emitSavingEvent(next) {
873
- that.emit('saving', document, options, creating, function afterSavingEvent(err, stopped) {
874
- return next(err);
875
- });
876
- }, function doDatabase(next) {
877
-
878
- if (options.debug) {
879
- console.log('Saving document', document, 'Creating?', creating);
880
- }
881
-
882
- function gotRecord(err, result) {
883
- if (err) {
884
- return next(err);
885
- }
886
-
887
- saved_record = result;
888
- next();
889
- }
890
-
891
- if (creating) {
892
- that.createRecord(document, options, gotRecord);
893
- } else {
894
- that.updateRecord(document, options, gotRecord);
895
- }
896
- }, function doAssociated(next) {
897
-
898
- var tasks = [],
899
- assoc,
900
- entry,
901
- key;
902
-
903
- Object.each(document.$record, function eachEntry(entry, key) {
904
-
905
- // Skip our own record
906
- if (key == that.name) {
907
- return;
908
- }
909
-
910
- // Get the association configuration
911
- assoc = that.schema.associations[key];
912
-
913
- // If the association doesn't exist, do nothing
914
- if (!assoc) {
915
- return;
916
- }
917
-
918
- // Add the saved _id
919
- //@TODO: this throws an error sometimes.
920
- entry[assoc.options.foreignKey] = saved_record[assoc.options.localKey];
921
-
922
- // Add the task
923
- tasks.push(function doSave(next) {
924
- var a_model = that.getModel(assoc.modelName);
925
- a_model.save(entry, next);
926
- });
927
- });
928
-
929
- Function.parallel(tasks, next);
930
- }, function done(err) {
931
-
932
- if (err) {
933
- return;
934
- }
935
-
936
- return saved_record;
937
- });
938
-
939
- pledge.handleCallback(callback);
940
-
941
- return pledge;
942
- });
943
-
944
- /**
945
- * Look for the record id by checking the indexes
946
- *
947
- * @author Jelle De Loecker <jelle@develry.be>
948
- * @since 0.1.0
949
- * @version 1.1.0
950
- *
951
- * @param {Document} document
952
- * @param {Object} options
953
- * @param {Function} callback
954
- */
955
- Model.setMethod(function auditRecord(document, options, callback) {
956
-
957
- var that = this,
958
- results,
959
- schema,
960
- tasks;
961
-
962
- if (!document) {
963
- return callback(new Error('No record was given to audit'));
964
- }
965
-
966
- schema = this.schema;
967
-
968
- if (schema && document[this.primary_key] == null && options.audit !== false) {
969
- tasks = {};
970
- results = {};
971
-
972
- if (options.debug) {
973
- console.log('Pre-save audit record', document);
974
- }
975
-
976
- schema.eachAlternateIndex(document, function iterIndex(index, indexName) {
977
-
978
- if (options.debug) {
979
- console.log('Checking alternate index', indexName);
980
- }
981
-
982
- tasks[indexName] = function auditIndex(next) {
983
- var query = {},
984
- fieldName;
985
-
986
- for (fieldName in index.fields) {
987
- if (document[fieldName] != null) {
988
- query[fieldName] = document[fieldName];
989
-
990
- // @todo: should run through the FieldType instance
991
- if (String(query[fieldName]).isObjectId()) {
992
- query[fieldName] = alchemy.castObjectId(query[fieldName]);
993
- }
994
- }
995
- }
996
-
997
- that.datasource.read(that, query, {}, function gotRecordInfo(err, records) {
998
-
999
- if (err != null) {
1000
- return next(err);
1001
- }
1002
-
1003
- if (records[0] != null) {
1004
- results[indexName] = records[0];
1005
- }
1006
-
1007
- next();
1008
- });
1009
-
1010
- };
1011
- });
1012
-
1013
- Function.parallel(tasks, function doneAudit(err) {
1014
-
1015
- var indexName,
1016
- record,
1017
- count,
1018
- ids;
1019
-
1020
- if (err != null) {
1021
- return callback(err);
1022
- }
1023
-
1024
- if (!Object.isEmpty(results)) {
1025
-
1026
- count = 0;
1027
- ids = {};
1028
-
1029
- for (indexName in results) {
1030
- record = results[indexName];
1031
-
1032
- // First make sure this index is allowed during the audit
1033
- // If it's not, this means it should be considered a duplicate
1034
- if (options.allowedIndexes != null && !Object.hasValue(options.allowedIndexes, indexName)) {
1035
- if (callback) callback(new Error('Duplicate index found other than _id: ' + indexName), null);
1036
- return;
1037
- }
1038
-
1039
- // Add the id a first time
1040
- if (ids[record[that.primary_key]] == null) {
1041
- count++;
1042
- ids[record[that.primary_key]] = true;
1043
- }
1044
- }
1045
-
1046
- // If more than 1 ids are found, we can't update the item
1047
- // because we don't know which record is the actual owner
1048
- if (count > 1) {
1049
- if (callback) callback(new Error('Multiple unique records found'));
1050
- return;
1051
- }
1052
-
1053
- // Use the last found record to get the id
1054
- document[that.primary_key] = record[that.primary_key];
1055
- }
1056
-
1057
- if (options.debug) {
1058
- console.log('Audit done, found pk:', document[that.primary_key]);
1059
- }
1060
-
1061
- callback(null, document);
1062
- });
1063
-
1064
- return;
1065
- }
1066
-
1067
- setImmediate(function skippedAudit() {
1068
- callback(null, document);
1069
- });
1070
- });
1071
-
1072
- /**
1073
- * Turn a record into something the database will understand
1074
- *
1075
- * @author Jelle De Loecker <jelle@develry.be>
1076
- * @since 1.0.3
1077
- * @version 1.0.4
1078
- *
1079
- * @param {Document} record
1080
- * @param {Object} options
1081
- * @param {Function} callback
1082
- *
1083
- * @return {Pledge}
1084
- */
1085
- Model.setMethod(function convertRecordToDatasourceFormat(record, options, callback) {
1086
-
1087
- var that = this,
1088
- pledge,
1089
- data;
1090
-
1091
- if (typeof options == 'function') {
1092
- callback = options;
1093
- options = {};
1094
- }
1095
-
1096
- if (!options) {
1097
- options = {};
1098
- }
1099
-
1100
- data = record[this.name] || record;
1101
-
1102
- // Normalize the data
1103
- data = this.compose(data, options);
1104
-
1105
- pledge = this.datasource.toDatasource(this, data);
1106
-
1107
- pledge.handleCallback(callback);
1108
-
1109
- return pledge;
1110
- });
1111
-
1112
- /**
1113
- * Process an object of datasource format
1114
- *
1115
- * @author Jelle De Loecker <jelle@develry.be>
1116
- * @since 1.0.3
1117
- * @version 1.0.3
1118
- *
1119
- * @param {Object} ds_data
1120
- * @param {Object} options
1121
- * @param {Function} callback
1122
- *
1123
- * @return {Pledge}
1124
- */
1125
- Model.setMethod(function processDatasourceFormat(ds_data, options, callback) {
1126
-
1127
- var that = this,
1128
- pledge,
1129
- data;
1130
-
1131
- if (typeof options == 'function') {
1132
- callback = options;
1133
- options = {};
1134
- }
1135
-
1136
- if (!options) {
1137
- options = {};
1138
- }
1139
-
1140
- pledge = this.datasource.toApp(this.schema, {}, options, ds_data);
1141
-
1142
- pledge.handleCallback(callback);
1143
-
1144
- return pledge;
1145
- });
1146
-
1147
- /**
1148
- * Get the title to display for this record
1149
- *
1150
- * @author Jelle De Loecker <jelle@develry.be>
1151
- * @since 0.0.1
1152
- * @version 0.1.0
1153
- *
1154
- * @param {Object} item The record item of this model
1155
- * @param {String|Array} fallbacks Extra fallbacks to use
1156
- *
1157
- * @return {String} The display title to use
1158
- */
1159
- Model.setMethod(function getDisplayTitle(item, fallbacks) {
1160
-
1161
- var fields,
1162
- field,
1163
- main,
1164
- val,
1165
- i;
1166
-
1167
- if (!item) {
1168
- return 'Undefined item';
1169
- }
1170
-
1171
- if (item[this.modelName]) {
1172
- main = item[this.modelName];
1173
- } else {
1174
- main = item;
1175
- }
1176
-
1177
- if (!main) {
1178
- return 'Undefined item';
1179
- }
1180
-
1181
- fields = Array.cast(this.displayField);
1182
-
1183
- if (fallbacks) {
1184
- fields = fields.concat(fallbacks);
1185
- }
1186
-
1187
- for (i = 0; i < fields.length; i++) {
1188
- val = main[fields[i]];
1189
-
1190
- if (Object.isObject(val)) {
1191
- field = this.getField(fields[i]);
1192
-
1193
- if (field && field.isTranslatable) {
1194
- val = alchemy.pickTranslation(this.conduit, val).result;
1195
- }
1196
- }
1197
-
1198
- if (val && typeof val == 'string') {
1199
- return val;
1200
- }
1201
- }
1202
-
1203
- return main[this.primary_key] || '';
1204
- });
1205
-
1206
- /**
1207
- * Clear the cache of this and all associated models
1208
- *
1209
- * @author Jelle De Loecker <jelle@develry.be>
1210
- * @since 0.0.1
1211
- * @version 1.1.6
1212
- *
1213
- * @param {Boolean} associated Also nuke associated models
1214
- * @param {Branch} parent
1215
- */
1216
- Model.setMethod(function nukeCache(associated, parent) {
1217
-
1218
- let model_name,
1219
- branch,
1220
- alias;
1221
-
1222
- // Nuke associated caches by default
1223
- if (typeof associated == 'undefined') {
1224
- associated = true;
1225
- }
1226
-
1227
- // Create the parent branch object
1228
- if (!parent) {
1229
- branch = parent = new Classes.Branch.Data(this.name);
1230
- }
1231
-
1232
- if (branch || !parent.root.seen(this.name)) {
1233
-
1234
- if (!branch) {
1235
- branch = parent.append(this.name);
1236
- }
1237
-
1238
- if (this.cache) {
1239
- this.cache.reset();
1240
- }
1241
-
1242
- // Also nuke the cache of the client model, if it exists
1243
- if (Classes.Alchemy.Client.Model[this.constructor.name] && Classes.Alchemy.Client.Model[this.constructor.name].cache) {
1244
- Classes.Alchemy.Client.Model[this.constructor.name].cache.reset();
1245
- }
1246
- }
1247
-
1248
- // Return if we don't need to nuke associated models
1249
- if (!associated) {
1250
- return;
1251
- }
1252
-
1253
- for (alias in this.associations) {
1254
- model_name = this.associations[alias].modelName;
1255
-
1256
- if (!parent.root.seen(model_name)) {
1257
- let assoc_model = this.getModel(model_name);
1258
- assoc_model.nukeCache(true, branch);
1259
- }
1260
- }
1261
- });
1262
-
1263
- /**
1264
- * Delete the given record id
1265
- *
1266
- * @author Jelle De Loecker <jelle@develry.be>
1267
- * @since 0.0.1
1268
- * @version 1.0.7
1269
- *
1270
- * @param {String} id The object id
1271
- * @param {Function} callback
1272
- *
1273
- * @return {Pledge}
1274
- */
1275
- Model.setMethod(function remove(id, callback) {
1276
-
1277
- var that = this,
1278
- pledge = new Pledge(),
1279
- id;
1280
-
1281
- pledge.handleCallback(callback);
1282
-
1283
- if (!id) {
1284
- pledge.reject(new Error('Invalid id given!'));
1285
- return pledge;
1286
- }
1287
-
1288
- if (this.datasource.supports('objectid')) {
1289
- id = alchemy.castObjectId(id);
1290
- } else {
1291
- id = String(id);
1292
- }
1293
-
1294
- let query = {};
1295
- query[this.primary_key] = id;
1296
-
1297
- this.datasource.remove(this, query, {}, function afterRemove(err, result) {
1298
-
1299
- if (err != null) {
1300
- return pledge.reject(err);
1301
- }
1302
-
1303
- that.emit('removed', result, {}, false, function afterRemovedEvent() {
1304
- pledge.resolve(result);
1305
- });
1306
- });
1307
-
1308
- return pledge;
1309
- });
1310
-
1311
- /**
1312
- * Get all the records and perform the given task on them
1313
- *
1314
- * @author Jelle De Loecker <jelle@develry.be>
1315
- * @since 0.5.0
1316
- * @version 1.2.0
1317
- *
1318
- * @param {Object} options Find options
1319
- * @param {Function} task Task to perform on each record
1320
- * @param {Function} callback Function to call when done
1321
- */
1322
- Model.setMethod(function eachRecord(options, task, callback) {
1323
-
1324
- var that = this,
1325
- parallel_limit,
1326
- available = null,
1327
- last_id = null,
1328
- pledge = new Classes.Pledge(),
1329
- index = 0;
1330
-
1331
- if (typeof options == 'function') {
1332
- callback = task;
1333
- task = options;
1334
- options = {};
1335
- } else if (!options) {
1336
- options = {};
1337
- }
1338
-
1339
- if (!callback) {
1340
- callback = Function.thrower;
1341
- }
1342
-
1343
- // Apply default limit of 50 records per fetch
1344
- options = Object.assign({}, {limit: 50}, options);
1345
-
1346
- // Get amount of tasks to do in parallel
1347
- parallel_limit = options.parallel_limit || 8;
1348
-
1349
- // Sort by _id ascending
1350
- if (!options.sort) {
1351
- options.sort = {};
1352
- options.sort[this.primary_key] = 1;
1353
- }
1354
-
1355
- // Make sure there is a conditions object
1356
- if (!options.conditions) {
1357
- options.conditions = {};
1358
- }
1359
-
1360
- this.find('all', options, function gotRecords(err, result) {
1361
-
1362
- var tasks = [];
1363
-
1364
- if (!result.length) {
1365
- pledge.reportProgress(100);
1366
- pledge.resolve();
1367
- return callback(null);
1368
- }
1369
-
1370
- if (available == null) {
1371
- available = result.available;
1372
- options.available = false;
1373
- } else {
1374
- result.available = available;
1375
- }
1376
-
1377
- result.forEach(function eachRecord(record) {
1378
-
1379
- var record_index = index++;
1380
-
1381
- last_id = record[that.model_name][that.primary_key];
1382
-
1383
- tasks.push(function doSave(next) {
1384
- pledge.reportProgress(((record_index - 1) / available) * 100);
1385
- task.call(that, record, record_index, next);
1386
- });
1387
- });
1388
-
1389
- Function.parallel(parallel_limit, tasks, function done(err) {
1390
-
1391
- if (err) {
1392
- pledge.reject(err);
1393
- return callback(err);
1394
- }
1395
-
1396
- let next_options = Object.assign({}, options);
1397
-
1398
- // Get records with a bigger _id than the last found
1399
- next_options.conditions[that.primary_key] = {$gt: last_id};
1400
-
1401
- that.find('all', next_options, gotRecords);
1402
- });
1403
- });
1404
-
1405
- return pledge;
1406
- });
1407
-
1408
- /**
1409
- * Strip out private fields
1410
- *
1411
- * @author Jelle De Loecker <jelle@develry.be>
1412
- * @since 1.0.0
1413
- * @version 1.0.0
1414
- *
1415
- * @param {Array} records
1416
- */
1417
- Model.setMethod(function removePrivateFields(records) {
1418
-
1419
- var has_private_fields,
1420
- fields = this.schema.getSorted(false),
1421
- record,
1422
- field,
1423
- i,
1424
- j;
1425
-
1426
- records = Array.cast(records);
1427
-
1428
- for (i = 0; i < records.length; i++) {
1429
- record = records[i];
1430
-
1431
- for (j = 0; j < fields.length; j++) {
1432
- field = fields[j];
1433
-
1434
- if (field.is_private) {
1435
- has_private_fields = true;
1436
- delete record[field.name];
1437
- }
1438
- }
1439
-
1440
- // If there are no private fields, break loop
1441
- if (!has_private_fields) {
1442
- break;
1443
- }
1444
- }
1445
-
1446
- return records;
1447
- });
1448
-
1449
- /**
1450
- * Create an export stream
1451
- *
1452
- * @author Jelle De Loecker <jelle@develry.be>
1453
- * @since 1.0.5
1454
- * @version 1.0.5
1455
- *
1456
- * @param {Stream} output
1457
- * @param {Object} options
1458
- *
1459
- * @return {Pledge}
1460
- */
1461
- Model.setMethod(function exportToStream(output, options) {
1462
-
1463
- if (!alchemy.isStream(output)) {
1464
- if (!options) {
1465
- options = output;
1466
- output = null;
1467
- }
1468
-
1469
- output = options.output;
1470
- }
1471
-
1472
- if (!output) {
1473
- return Pledge.reject(new Error('No target output stream has been given'));
1474
- }
1475
-
1476
- if (!options) {
1477
- options = {};
1478
- }
1479
-
1480
- // Only allow 1 task to run at a time
1481
- options.parallel_limit = 1;
1482
-
1483
- let that = this,
1484
- name_buf = Buffer.from(this.model_name),
1485
- head_buf;
1486
-
1487
- // 0x01 is a model
1488
- head_buf = Buffer.concat([Buffer.from([0x01, name_buf.length]), name_buf]);
1489
-
1490
- output.write(head_buf);
1491
-
1492
- return this.eachRecord(options, function eachRecord(record, index, next) {
1493
- record.exportToStream(output).done(next);
1494
- }, function done(err) {
1495
-
1496
- });
1497
- });
1498
-
1499
- /**
1500
- * Import from a stream
1501
- *
1502
- * @author Jelle De Loecker <jelle@develry.be>
1503
- * @since 1.0.5
1504
- * @version 1.0.5
1505
- *
1506
- * @param {Stream} input
1507
- * @param {Object} options
1508
- *
1509
- * @return {Pledge}
1510
- */
1511
- Model.setMethod(function importFromStream(input, options) {
1512
-
1513
- if (!alchemy.isStream(input)) {
1514
- if (!options) {
1515
- options = input;
1516
- input = null;
1517
- }
1518
-
1519
- input = options.input;
1520
- }
1521
-
1522
- if (!input) {
1523
- return Pledge.reject(new Error('No source input stream has been given'));
1524
- }
1525
-
1526
- let that = this,
1527
- current_type = null,
1528
- extra_stream,
1529
- pledge = new Pledge(),
1530
- stopped,
1531
- paused,
1532
- buffer,
1533
- value,
1534
- seen = 0,
1535
- left,
1536
- size,
1537
- doc;
1538
-
1539
- input.on('data', function onData(data) {
1540
-
1541
- if (stopped) {
1542
- return;
1543
- }
1544
-
1545
- if (buffer) {
1546
- buffer = Buffer.concat([buffer, data]);
1547
- } else {
1548
- buffer = data;
1549
- }
1550
-
1551
- handleBuffer();
1552
- });
1553
-
1554
- function handleBuffer() {
1555
-
1556
- if (paused) {
1557
- return;
1558
- }
1559
-
1560
- if (!current_type && buffer.length < 2) {
1561
- return;
1562
- }
1563
-
1564
- if (!current_type) {
1565
- current_type = buffer.readUInt8(0);
1566
-
1567
- if (current_type == 0x01) {
1568
- size = buffer.readUInt8(1);
1569
- buffer = buffer.slice(2);
1570
- } else if (current_type == 0x02 && buffer.length >= 5) {
1571
- size = buffer.readUInt32BE(1);
1572
- buffer = buffer.slice(5);
1573
- } else if (current_type == 0xFF) {
1574
- size = buffer.readUInt32BE(1);
1575
- buffer = buffer.slice(5);
1576
- seen = 0;
1577
-
1578
- if (!doc) {
1579
- stopped = true;
1580
- pledge.reject(new Error('Found extra import data, but no active document'));
1581
- } else {
1582
- extra_stream = new require('stream').PassThrough();
1583
- doc.extraImportFromStream(extra_stream);
1584
- }
1585
- } else {
1586
- // Not enough data? Wait
1587
- current_type = null;
1588
- return;
1589
- }
1590
- }
1591
-
1592
- handleRest();
1593
- }
1594
-
1595
- function handleRest() {
1596
-
1597
- if (current_type == 0xFF) {
1598
- left = size - seen;
1599
- value = buffer.slice(0, left);
1600
-
1601
- seen += value.length;
1602
-
1603
- if (value.length == buffer.length) {
1604
- buffer = null;
1605
- } else if (value.length < buffer.length) {
1606
- buffer = buffer.slice(left);
1607
- }
1608
-
1609
- extra_stream.write(value);
1610
-
1611
- if (value.length == left) {
1612
- extra_stream.end();
1613
- current_type = null;
1614
-
1615
- if (buffer) {
1616
- handleBuffer();
1617
- }
1618
- }
1619
-
1620
- return;
1621
- }
1622
-
1623
- if (buffer.length >= size) {
1624
- value = buffer.slice(0, size);
1625
- buffer = buffer.slice(size);
1626
- } else {
1627
- // Wait for next call
1628
- return;
1629
- }
1630
-
1631
- if (current_type == 0x01) {
1632
- value = value.toString();
1633
-
1634
- if (value == that.model_name) {
1635
- // Found name!
1636
- current_type = null;
1637
- size = 0;
1638
- } else {
1639
- stopped = true;
1640
- return pledge.reject(new Error('Model names do not match'));
1641
- }
1642
- } else if (current_type == 0x02) {
1643
- doc = that.createDocument();
1644
- input.pause();
1645
- paused = true;
1646
-
1647
- doc.importFromBuffer(value).done(function done(err, result) {
1648
-
1649
- if (err) {
1650
- stopped = true;
1651
- return pledge.reject(err);
1652
- }
1653
-
1654
- current_type = null;
1655
- paused = false;
1656
- input.resume();
1657
-
1658
- handleBuffer();
1659
- });
1660
-
1661
- return;
1662
- }
1663
-
1664
- if (buffer && buffer.length) {
1665
- handleBuffer();
1666
- }
1667
- }
1668
-
1669
- return pledge;
1670
- });
1671
-
1672
- /**
1673
- * Get a model
1674
- *
1675
- * @author Jelle De Loecker <jelle@develry.be>
1676
- * @since 0.0.1
1677
- * @version 1.1.0
1678
- *
1679
- * @param {String} name
1680
- * @param {Boolean} init
1681
- *
1682
- * @return {Model}
1683
- */
1684
- Model.get = function get(name, init, options) {
1685
-
1686
- var constructor,
1687
- namespace,
1688
- pieces,
1689
- path,
1690
- obj;
1691
-
1692
- if (typeof name == 'function') {
1693
- name = name.name;
1694
- }
1695
-
1696
- if (!name) {
1697
- throw new TypeError('Model name should be a non-empty string');
1698
- }
1699
-
1700
- if (init && typeof init == 'object') {
1701
- options = init;
1702
- init = true;
1703
- }
1704
-
1705
- if (!options) {
1706
- options = {};
1707
- }
1708
-
1709
- if (nameCache[name]) {
1710
- if (init === false) {
1711
- return nameCache[name];
1712
- } else {
1713
- return new nameCache[name];
1714
- }
1715
- }
1716
-
1717
- pieces = name.split('.');
1718
-
1719
- if (pieces.length > 1) {
1720
- // The first part is the namespace
1721
- namespace = pieces.shift();
1722
-
1723
- // The rest should be the path
1724
- path = pieces.join('.');
1725
-
1726
- obj = Classes[namespace];
1727
-
1728
- if (!obj) {
1729
- if (init === false) {
1730
- return null;
1731
- }
1732
-
1733
- throw new TypeError('Namespace "' + namespace + '" could not be found');
1734
- }
1735
- } else {
1736
- path = name;
1737
- obj = Classes.Alchemy.Model;
1738
- }
1739
-
1740
- constructor = Object.path(obj, path) || obj[String(path).modelName()];
1741
-
1742
- if (constructor == null) {
1743
- if (init === false) {
1744
- return null;
1745
- }
1746
-
1747
- throw new Error('Could not find model "' + name + '"');
1748
- }
1749
-
1750
- // Store this name in the cache,
1751
- // so we don't have to perform the expensive #modelName() method again
1752
- nameCache[name] = constructor;
1753
-
1754
- if (init === false) {
1755
- return constructor;
1756
- } else {
1757
- return new constructor;
1758
- }
1759
- };
1760
-
1761
- /**
1762
- * Make the base Model class a global
1763
- *
1764
- * @author Jelle De Loecker <jelle@develry.be>
1765
- * @since 0.0.1
1766
- * @version 0.2.0
1767
- *
1768
- * @type {Object}
1769
- */
1
+ var nameCache = {},
2
+ mongo = alchemy.use('mongodb'),
3
+ all_prefixes = alchemy.shared('Routing.prefixes'),
4
+ fs = alchemy.use('fs'),
5
+ createdModel;
6
+
7
+ /**
8
+ * The Model class
9
+ *
10
+ * @constructor
11
+ *
12
+ * @author Jelle De Loecker <jelle@develry.be>
13
+ * @since 0.0.1
14
+ * @version 1.1.0
15
+ */
16
+ var Model = Function.inherits('Alchemy.Base', 'Alchemy.Model', function Model(options) {
17
+ this.init(options);
18
+ });
19
+
20
+ /**
21
+ * Set the modelName property after class creation
22
+ *
23
+ * @author Jelle De Loecker <jelle@develry.be>
24
+ * @since 0.2.0
25
+ * @version 1.1.0
26
+ */
27
+ Model.constitute(function setModelName() {
28
+ this.model_name = this.name;
29
+ this.setProperty('model_name', this.model_name);
30
+
31
+ if (!this.table) {
32
+ this.table = this.model_name.tableize();
33
+ }
34
+ });
35
+
36
+ /**
37
+ * This is a model constructor
38
+ *
39
+ * @type {Boolean}
40
+ */
41
+ Model.setStaticProperty('model', true);
42
+
43
+ /**
44
+ * The cache duration static getter/setter
45
+ *
46
+ * @author Jelle De Loecker <jelle@develry.be>
47
+ * @since 0.1.0
48
+ * @version 1.0.3
49
+ *
50
+ * @property cache_duration
51
+ * @type {String}
52
+ */
53
+ Model.setStaticProperty(function cache_duration() {
54
+
55
+ if (this._cache_duration == null) {
56
+ this._cache_duration = alchemy.settings.model_query_cache_duration;
57
+ }
58
+
59
+ return this._cache_duration;
60
+ }, function setCacheDuration(duration) {
61
+ this._cache_duration = duration;
62
+
63
+ // @todo: reset cache
64
+ });
65
+
66
+ /**
67
+ * Get the cache object
68
+ *
69
+ * @author Jelle De Loecker <jelle@develry.be>
70
+ * @since 0.1.0
71
+ * @version 1.0.3
72
+ *
73
+ * @property cache
74
+ * @type {Object}
75
+ */
76
+ Model.setStaticProperty(function cache() {
77
+
78
+ if (this.cache_duration) {
79
+
80
+ if (this._cache) {
81
+ return this._cache;
82
+ }
83
+
84
+ this._cache = alchemy.getCache(this.name, this.cache_duration);
85
+ return this._cache;
86
+ }
87
+
88
+ return false;
89
+ }, function setCache(value) {
90
+ return this._cache = value;
91
+ });
92
+
93
+
94
+ /**
95
+ * Is this an abstract model?
96
+ *
97
+ * @author Jelle De Loecker <jelle@develry.be>
98
+ * @since 1.1.0
99
+ * @version 1.1.0
100
+ *
101
+ * @type {Boolean}
102
+ */
103
+ Model.setStaticProperty(function is_abstract() {
104
+
105
+ // Do simple is_abstract_class check
106
+ if (this.is_abstract_class != null) {
107
+ return !!this.is_abstract_class;
108
+ }
109
+
110
+ // If we need to load an external schema, it's also not abstract
111
+ if (this.prototype.load_external_schema) {
112
+ return false;
113
+ }
114
+
115
+ // See if this model has other fields than the default ones
116
+ let field_count = this.schema.array.length;
117
+
118
+ if (this.schema.has('_id')) {
119
+ field_count--;
120
+ }
121
+
122
+ if (this.schema.has('created')) {
123
+ field_count--;
124
+ }
125
+
126
+ if (this.schema.has('updated')) {
127
+ field_count--;
128
+ }
129
+
130
+ return field_count < 1;
131
+ });
132
+
133
+
134
+ /**
135
+ * Get the document class constructor
136
+ *
137
+ * @type {Alchemy.Document}
138
+ */
139
+ Model.prepareStaticProperty('Document', function getDocumentClass() {
140
+ return Classes.Alchemy.Document.Document.getDocumentClass(this);
141
+ });
142
+
143
+ /**
144
+ * Get the client document class constructor
145
+ *
146
+ * @type {Hawkejs.Document}
147
+ */
148
+ Model.prepareStaticProperty('ClientDocument', function getClientDocumentClass() {
149
+ return this.Document.getClientDocumentClass();
150
+ });
151
+
152
+ /**
153
+ * Set the static per-model schema
154
+ *
155
+ * @version 1.1.0
156
+ *
157
+ * @type {Schema}
158
+ */
159
+ Model.staticCompose('schema', function createSchema(doNext) {
160
+
161
+ var that = this,
162
+ model = this.compositorParent,
163
+ schema = new Classes.Alchemy.Schema();
164
+
165
+ // The base Model does not have a schema
166
+ if (model.name == 'Model') {
167
+ return false;
168
+ } else {
169
+
170
+ // Link the schema to this model
171
+ schema.setModel(model);
172
+
173
+ // Set the schema name
174
+ schema.setName(model.name);
175
+
176
+ if (model.prototype.add_basic_fields !== false) {
177
+
178
+ // Set default model fields immediately after this function ends
179
+ // This has to be scheduled next, because addField would call createSchema
180
+ // again, resulting in an infinite loop
181
+ doNext(function addSchemaBasics() {
182
+ model.addField('_id', 'ObjectId', {default: Field.createPathEvaluator('alchemy.ObjectId')});
183
+ model.addField('created', 'Datetime', {default: Field.createPathEvaluator('Date.create')});
184
+ model.addField('updated', 'Datetime', {default: Field.createPathEvaluator('Date.create')});
185
+ });
186
+ }
187
+ }
188
+
189
+ return schema;
190
+ }, [
191
+ 'addEnumValues',
192
+ 'setEnumValues',
193
+ 'belongsTo',
194
+ 'hasOneParent',
195
+ 'hasAndBelongsToMany',
196
+ 'hasMany',
197
+ 'hasOneChild',
198
+ 'addIndex',
199
+ 'addRule',
200
+ ]);
201
+
202
+ Model.setDeprecatedProperty('modelName', 'model_name');
203
+ Model.setDeprecatedProperty('blueprint', 'schema');
204
+ Model.setDeprecatedProperty('displayField', 'display_field');
205
+
206
+ /**
207
+ * The default database config to use
208
+ *
209
+ * @type {String}
210
+ */
211
+ Model.setProperty('dbConfig', 'default');
212
+
213
+ /**
214
+ * The default field to use as display
215
+ *
216
+ * @type {String}
217
+ */
218
+ Model.setProperty('display_field', 'title');
219
+
220
+ /**
221
+ * Translate is on by default
222
+ *
223
+ * @type {Boolean}
224
+ */
225
+ Model.setProperty('translate', true);
226
+
227
+ /**
228
+ * Set the name of the primary key field
229
+ *
230
+ * @author Jelle De Loecker <jelle@develry.be>
231
+ * @since 0.1.0
232
+ * @version 0.1.0
233
+ */
234
+ Model.setProperty('primary_key', '_id');
235
+
236
+ /**
237
+ * Should we load the schema from the database?
238
+ *
239
+ * @author Jelle De Loecker <jelle@develry.be>
240
+ * @since 1.1.0
241
+ * @version 1.1.0
242
+ */
243
+ Model.setProperty('load_external_schema', false);
244
+
245
+ /**
246
+ * Object where behaviours are stored
247
+ *
248
+ * @type {Object}
249
+ */
250
+ Model.prepareProperty('behaviours', Object);
251
+
252
+ /**
253
+ * Associations
254
+ *
255
+ * @type {Object}
256
+ */
257
+ Model.setProperty(function associations() {
258
+ return this.schema.associations;
259
+ });
260
+
261
+ /**
262
+ * Instance access to static cache
263
+ *
264
+ * @type {Expirable}
265
+ */
266
+ Model.prepareProperty('cache', function cache() {
267
+ return this.constructor.cache;
268
+ });
269
+
270
+ /**
271
+ * Instance access to static schema
272
+ *
273
+ * @type {Schema}
274
+ */
275
+ Model.setProperty(function schema() {
276
+ return this.constructor.schema;
277
+ });
278
+
279
+ /**
280
+ * Is this an abstract model?
281
+ *
282
+ * @type {Boolean}
283
+ */
284
+ Model.setProperty(function is_abstract() {
285
+ return this.constructor.is_abstract;
286
+ });
287
+
288
+ /**
289
+ * This is a wrapper class
290
+ */
291
+ Model.makeAbstractClass();
292
+
293
+ /**
294
+ * This wrapper class starts a new group
295
+ */
296
+ Model.startNewGroup();
297
+
298
+ /**
299
+ * The connection
300
+ *
301
+ * @type {Object}
302
+ */
303
+ Model.prepareProperty('datasource', function datasource() {
304
+ if (this.table) return Datasource.get(this.dbConfig);
305
+ });
306
+
307
+ /**
308
+ * The default sort options
309
+ *
310
+ * @type {Object}
311
+ */
312
+ Model.prepareProperty('sort', function sort() {
313
+ return {created: 1};
314
+ });
315
+
316
+ /**
317
+ * Check a url value
318
+ *
319
+ * @author Jelle De Loecker <jelle@develry.be>
320
+ * @since 1.0.0
321
+ * @version 1.2.5
322
+ *
323
+ * @param {String} value The value in the url
324
+ * @param {String} name The name of the url parameter
325
+ * @param {String} field_name The name of the field to check
326
+ * @param {Conduit} conduit The optional conduit
327
+ *
328
+ * @return {Pledge}
329
+ */
330
+ Model.setStatic(async function checkPathValue(value, name, field_name, conduit) {
331
+
332
+ var instance,
333
+ pledge,
334
+ crit;
335
+
336
+ if (!field_name) {
337
+ if (name == 'id') {
338
+ field_name = this.prototype.primary_key;
339
+ } else {
340
+ field_name = name;
341
+ }
342
+ }
343
+
344
+ if (conduit) {
345
+ instance = conduit.getModel(this);
346
+ } else {
347
+ instance = new this;
348
+ }
349
+
350
+ // Create new criteria instance
351
+ crit = instance.find();
352
+
353
+ // Look for the wanted field
354
+ crit.where(field_name).equals(value);
355
+
356
+ let result = await instance.find('first', crit);
357
+
358
+ if (result) {
359
+ let found_value = result[field_name];
360
+
361
+ if (found_value != value && !Object.alike(value, found_value)) {
362
+ conduit.rewriteRequestRouteParam(name, found_value);
363
+ }
364
+ }
365
+
366
+ return result;
367
+ });
368
+
369
+ /**
370
+ * Add a field to this model's schema
371
+ *
372
+ * @author Jelle De Loecker <jelle@develry.be>
373
+ * @since 0.2.0
374
+ * @version 1.2.4
375
+ *
376
+ * @return {Alchemy.Field}
377
+ */
378
+ Model.setStatic(function addField(name, type, options) {
379
+
380
+ var field,
381
+ is_new;
382
+
383
+ is_new = !this.schema.has(name);
384
+
385
+ // Add it to the schema
386
+ field = this.schema.addField(name, type, options);
387
+
388
+ if (is_new) {
389
+ // Add it to the Document class
390
+ this.Document.setFieldGetter(name);
391
+
392
+ // False means it should not be set on the server implementation
393
+ // (because that's where it's coming from)
394
+ // Yes, this also sets private fields on the server-side client document.
395
+ this.ClientDocument.setFieldGetter(name, null, null, false);
396
+ }
397
+
398
+ return field;
399
+ });
400
+
401
+
402
+ /**
403
+ * Add a behaviour to this model
404
+ *
405
+ * @author Jelle De Loecker <jelle@develry.be>
406
+ * @since 0.2.0
407
+ * @version 0.2.0
408
+ */
409
+ Model.setStatic(function addBehaviour(behaviour_name, options) {
410
+ return this.schema.addBehaviour(behaviour_name, options);
411
+ });
412
+
413
+ /**
414
+ * Add an association to this model's schema
415
+ * and set it on the Document as a getter
416
+ *
417
+ * @author Jelle De Loecker <jelle@develry.be>
418
+ * @since 0.2.0
419
+ * @version 1.2.4
420
+ */
421
+ Model.setStatic(function addAssociation(type, alias, model_name, options) {
422
+ var data = this.schema.addAssociation(type, alias, model_name, options);
423
+ this.Document.setAliasGetter(data.alias);
424
+
425
+ // False means it should not be set on the server implementation
426
+ // (because that's where it's coming from)
427
+ // Yes, this also sets private fields on the server-side client document.
428
+ this.ClientDocument.setAliasGetter(name);
429
+ });
430
+
431
+ /**
432
+ * Set a method on the document class
433
+ *
434
+ * @author Jelle De Loecker <jelle@develry.be>
435
+ * @since 0.2.0
436
+ * @version 1.0.6
437
+ */
438
+ Model.setStatic(function setDocumentMethod(name, fnc) {
439
+
440
+ var that = this;
441
+
442
+ if (typeof name == 'function') {
443
+ fnc = name;
444
+ name = fnc.name;
445
+ }
446
+
447
+ Blast.loaded(function whenLoaded() {
448
+ that.Document.setMethod(name, fnc);
449
+ });
450
+ });
451
+
452
+ /**
453
+ * Set a property on the document class
454
+ *
455
+ * @author Jelle De Loecker <jelle@develry.be>
456
+ * @since 0.2.0
457
+ * @version 1.0.6
458
+ */
459
+ Model.setStatic(function setDocumentProperty(name, fnc) {
460
+
461
+ var that = this,
462
+ args = arguments;
463
+
464
+ Blast.loaded(function whenLoaded() {
465
+ that.Document.setProperty.apply(that.Document, args);
466
+ });
467
+ });
468
+
469
+ /**
470
+ * Get a field
471
+ *
472
+ * @author Jelle De Loecker <jelle@develry.be>
473
+ * @since 0.2.0
474
+ * @version 0.2.0
475
+ *
476
+ * @return {FieldType}
477
+ */
478
+ Model.setStatic(function getField(name) {
479
+
480
+ var fieldPath,
481
+ alias,
482
+ model,
483
+ split;
484
+
485
+ if (name.indexOf('.') > -1) {
486
+ split = name.split('.');
487
+
488
+ alias = name[0];
489
+
490
+ if (this.schema.associations[alias] == null) {
491
+ model = this;
492
+ fieldPath = name;
493
+ } else {
494
+ model = Model.get(this.schema.associations[alias].modelName).constructor;
495
+ split.shift();
496
+ fieldPath = split.join('.');
497
+ }
498
+ } else {
499
+ model = this;
500
+ fieldPath = name;
501
+ }
502
+
503
+ return model.schema.get(fieldPath);
504
+ });
505
+
506
+ /**
507
+ * Get the model's public configuration
508
+ * (This is used to create the client-side Model instances)
509
+ *
510
+ * @author Jelle De Loecker <jelle@elevenways.be>
511
+ * @since 1.0.0
512
+ * @version 1.2.7
513
+ */
514
+ Model.setStatic(function getClientConfig() {
515
+
516
+ const result = {
517
+ name : this.model_name,
518
+ schema : this.schema,
519
+ primary_key : this.prototype.primary_key,
520
+ display_field : this.prototype.display_field,
521
+ };
522
+
523
+ if (this.super.name != 'Model') {
524
+ result.parent = this.super.name;
525
+ }
526
+
527
+ return result;
528
+ });
529
+
530
+ /**
531
+ * Initialize behaviours
532
+ *
533
+ * @author Jelle De Loecker <jelle@develry.be>
534
+ * @since 0.2.0
535
+ * @version 0.2.0
536
+ *
537
+ * @return {Document}
538
+ */
539
+ Model.setMethod(function initBehaviours() {
540
+
541
+ var behaviour,
542
+ key;
543
+
544
+ this.behaviours = {};
545
+
546
+ for (key in this.schema.behaviours) {
547
+ behaviour = this.schema.behaviours[key];
548
+
549
+ this.behaviours[key] = new behaviour.constructor(this, behaviour.options);
550
+ }
551
+
552
+ });
553
+
554
+ /**
555
+ * Enable a behaviour on-the-fly
556
+ *
557
+ * @author Jelle De Loecker <jelle@develry.be>
558
+ * @since 0.0.1
559
+ * @version 0.2.0
560
+ */
561
+ Model.setMethod(function addBehaviour(behaviourname, options) {
562
+
563
+ var instance;
564
+
565
+ if (!options) {
566
+ options = {};
567
+ }
568
+
569
+ instance = Behaviour.get(behaviourname, this, options);
570
+ this.behaviours[behaviourname] = instance;
571
+
572
+ return instance;
573
+ });
574
+
575
+ /**
576
+ * Get a behaviour instance
577
+ *
578
+ * @author Jelle De Loecker <jelle@develry.be>
579
+ * @since 1.0.3
580
+ * @version 1.0.3
581
+ *
582
+ * @param {String} name
583
+ *
584
+ * @return {Behaviour}
585
+ */
586
+ Model.setMethod(function getBehaviour(name) {
587
+
588
+ name = name.camelize();
589
+
590
+ if (!name.endsWith('Behaviour')) {
591
+ name += 'Behaviour';
592
+ }
593
+
594
+ return this.behaviours[name];
595
+ });
596
+
597
+ /**
598
+ * Enable translations
599
+ *
600
+ * @author Jelle De Loecker <jelle@develry.be>
601
+ * @since 0.2.0
602
+ * @version 0.2.0
603
+ */
604
+ Model.setMethod(function enableTranslations() {
605
+ this.translate = true;
606
+ });
607
+
608
+ /**
609
+ * Disable translations
610
+ *
611
+ * @author Jelle De Loecker <jelle@develry.be>
612
+ * @since 0.2.0
613
+ * @version 0.2.0
614
+ */
615
+ Model.setMethod(function disableTranslations() {
616
+ this.translate = false;
617
+ });
618
+
619
+ /**
620
+ * Aggregate
621
+ *
622
+ * @author Jelle De Loecker <jelle@develry.be>
623
+ * @since 0.5.0
624
+ * @version 0.5.0
625
+ *
626
+ * @param {Array} pipeline
627
+ * @param {Function} callback
628
+ */
629
+ Model.setMethod(function aggregate(pipeline, callback) {
630
+
631
+ this.datasource.collection(this.table, function gotCollection(err, collection) {
632
+
633
+ if (err) {
634
+ return callback(err);
635
+ }
636
+
637
+ collection.aggregate(pipeline, callback);
638
+ });
639
+ });
640
+
641
+ /**
642
+ * Translate the given records
643
+ *
644
+ * @author Jelle De Loecker <jelle@develry.be>
645
+ * @since 0.2.0
646
+ * @version 1.2.7
647
+ *
648
+ * @param {Array} items
649
+ * @param {Object} options Optional options object
650
+ * @param {Function} callback
651
+ */
652
+ Model.setMethod(function translateItems(items, options, callback) {
653
+
654
+ if (options && options instanceof Classes.Alchemy.Criteria.Criteria) {
655
+ options = options.options;
656
+ }
657
+
658
+ // No items to translate
659
+ if (!items.length) {
660
+ return callback();
661
+ }
662
+
663
+ // No fields in this schema are translatable
664
+ if (!this.schema.hasTranslations) {
665
+ return callback();
666
+ }
667
+
668
+ // Do nothing if there are no translatable fields
669
+ // or translate is disabled
670
+ if (!this.translate || (!this.conduit && !options.locale)) {
671
+ return callback();
672
+ }
673
+
674
+ let collection,
675
+ fieldName,
676
+ prefixes,
677
+ prefix,
678
+ record,
679
+ found,
680
+ item,
681
+ key,
682
+ i,
683
+ j;
684
+
685
+ // Get the alias we need to translate
686
+ let alias = options.forAlias || this.name;
687
+
688
+ // Get the (optional) attached conduit
689
+ let conduit = this.conduit;
690
+
691
+ // Should we use fallback translations?
692
+ const allow_fallbacks = options.allow_fallback_translations ?? alchemy.settings.allow_fallback_translations ?? false;
693
+
694
+ let use_predefined_prefixes = false;
695
+
696
+ // If prefixes are given as an option, only use those
697
+ if (options.prefixes) {
698
+ prefix = options.prefixes;
699
+ use_predefined_prefixes = true;
700
+ } else {
701
+ // Possible prefixes
702
+ prefix = [];
703
+
704
+ // Prefixes set in the options get precedence
705
+ if (options.locale && options.locale !== true) {
706
+ prefix.include(options.locale);
707
+ }
708
+
709
+ if (conduit) {
710
+
711
+ // Append the visited prefix after that (if there is one)
712
+ if (conduit.prefix) {
713
+ prefix.include(conduit.prefix);
714
+ }
715
+
716
+ // Add the active prefix
717
+ if (conduit.active_prefix) {
718
+ prefix.include(conduit.active_prefix);
719
+ }
720
+
721
+ // Append all the allowed locales after that
722
+ if (conduit.locales) {
723
+ prefix.include(conduit.locales);
724
+ }
725
+ }
726
+
727
+ // Add all available prefixes last
728
+ for (key in all_prefixes) {
729
+ prefix.push(key);
730
+ }
731
+
732
+ // The fallback prefix
733
+ prefix.push('__');
734
+
735
+ // @DEPRECATED: empty keys should no longer be allowed
736
+ prefix.push('');
737
+
738
+ if (!allow_fallbacks) {
739
+ prefix = [prefix[0]];
740
+ }
741
+ }
742
+
743
+ // Deduplicate the prefixes
744
+ prefix = Array.from(new Set(prefix));
745
+
746
+ for (i = 0; i < items.length; i++) {
747
+ item = items[i];
748
+
749
+ if (!options.ungrouped_items) {
750
+ item = item[alias];
751
+ }
752
+
753
+ // Don't translate items twice
754
+ if (item?.$translated_fields && !Object.isEmpty(item.$translated_fields)) {
755
+ continue;
756
+ }
757
+
758
+ collection = Array.cast(item);
759
+
760
+ // Clone the prefixes
761
+ prefixes = prefix.slice(0);
762
+
763
+ // If one of the query conditions searched through a translatable field,
764
+ // the prefix found should get preference
765
+ if (options.use_found_prefix && items.item_prefixes && items.item_prefixes[i]) {
766
+ prefixes.unshift(items.item_prefixes[i]);
767
+ }
768
+
769
+ if (!allow_fallbacks && !use_predefined_prefixes) {
770
+ prefixes = [prefixes[0]];
771
+ }
772
+
773
+ let field;
774
+
775
+ for (j = 0; j < collection.length; j++) {
776
+ record = collection[j];
777
+
778
+ for (fieldName in this.schema.translatableFields) {
779
+ field = this.schema.translatableFields[fieldName];
780
+ field.translateRecord(prefixes, record, options.allow_empty);
781
+ }
782
+ }
783
+ }
784
+
785
+ callback();
786
+ });
787
+
788
+ /**
789
+ * Create the given record if the id does not exist in the database
790
+ *
791
+ * @author Jelle De Loecker <jelle@develry.be>
792
+ * @since 0.0.1
793
+ * @version 1.1.0
794
+ *
795
+ * @param {Array} list A list of all the records that need to be in the db
796
+ * @param {Function} callback
797
+ */
798
+ Model.setMethod(function ensureIds(list, callback) {
799
+
800
+ var that = this;
801
+
802
+ list = Array.cast(list);
803
+
804
+ return Function.forEach.parallel(list, function checkEntry(entry, key, next) {
805
+ var id;
806
+
807
+ id = entry[that.primary_key];
808
+
809
+ if (!id && entry[that.name]) {
810
+ id = entry[that.name][that.primary_key];
811
+ }
812
+
813
+ if (!id) {
814
+ return next(new Classes.Alchemy.Error.Model('`Model#ensureIds()` can\'t ensure an entry without an _id'));
815
+ }
816
+
817
+ that.findById(id, function gotItem(err, result) {
818
+
819
+ if (err) {
820
+ return next(err);
821
+ }
822
+
823
+ if (result) {
824
+ return next();
825
+ }
826
+
827
+ that.save(entry, {create: true, document: false}, next);
828
+ });
829
+ }, callback);
830
+ });
831
+
832
+ /**
833
+ * Save one record
834
+ *
835
+ * @author Jelle De Loecker <jelle@develry.be>
836
+ * @since 0.0.1
837
+ * @version 1.2.0
838
+ *
839
+ * @param {Document} document
840
+ * @param {Object} options
841
+ * @param {Function} callback
842
+ *
843
+ * @return {Pledge}
844
+ */
845
+ Model.setMethod(function saveRecord(document, options, callback) {
846
+
847
+ var that = this,
848
+ saved_record,
849
+ creating,
850
+ results,
851
+ pledge,
852
+ main,
853
+ iter;
854
+
855
+ if (!document) {
856
+ pledge = Pledge.reject(new Error('Unable to save record: given document is undefined'));
857
+ pledge.done(callback);
858
+ return pledge;
859
+ }
860
+
861
+ // Normalize the arguments
862
+ if (typeof options == 'function') {
863
+ callback = options;
864
+ }
865
+
866
+ if (typeof options !== 'object') {
867
+ options = {};
868
+ }
869
+
870
+ pledge = Function.series(function doAudit(next) {
871
+
872
+ if (Object.isPlainObject(document)) {
873
+ return next(new Error('Model#saveRecord() expects a Document, not a plain object'));
874
+ }
875
+
876
+ // Look through unique indexes if no _id is present
877
+ that.auditRecord(document, options, function afterAudit(err, doc) {
878
+
879
+ if (err) {
880
+ return next(err);
881
+ }
882
+
883
+ // Is a new record being created?
884
+ creating = options.create || doc[that.primary_key] == null;
885
+ next();
886
+ });
887
+ }, function doBeforeSave(next) {
888
+
889
+ if (typeof that.beforeSave == 'function') {
890
+ let promise = that.beforeSave(document, options, next);
891
+
892
+ if (promise) {
893
+ Pledge.done(promise, next);
894
+ } else if (that.beforeSave.length < 3) {
895
+ // If the method accepts no `next` callback, call it now
896
+ next();
897
+ }
898
+ } else {
899
+ next();
900
+ }
901
+
902
+ }, function emitSavingEvent(next) {
903
+ that.emit('saving', document, options, creating, function afterSavingEvent(err, stopped) {
904
+ return next(err);
905
+ });
906
+ }, function doDatabase(next) {
907
+
908
+ if (options.debug) {
909
+ console.log('Saving document', document, 'Creating?', creating);
910
+ }
911
+
912
+ function gotRecord(err, result) {
913
+ if (err) {
914
+ return next(err);
915
+ }
916
+
917
+ saved_record = result;
918
+ next();
919
+ }
920
+
921
+ if (creating) {
922
+ that.createRecord(document, options, gotRecord);
923
+ } else {
924
+ that.updateRecord(document, options, gotRecord);
925
+ }
926
+ }, function doAssociated(next) {
927
+
928
+ var tasks = [],
929
+ assoc,
930
+ entry,
931
+ key;
932
+
933
+ Object.each(document.$record, function eachEntry(entry, key) {
934
+
935
+ // Skip our own record
936
+ if (key == that.name) {
937
+ return;
938
+ }
939
+
940
+ // Get the association configuration
941
+ assoc = that.schema.associations[key];
942
+
943
+ // If the association doesn't exist, do nothing
944
+ if (!assoc) {
945
+ return;
946
+ }
947
+
948
+ // Add the saved _id
949
+ //@TODO: this throws an error sometimes.
950
+ entry[assoc.options.foreignKey] = saved_record[assoc.options.localKey];
951
+
952
+ // Add the task
953
+ tasks.push(function doSave(next) {
954
+ var a_model = that.getModel(assoc.modelName);
955
+ a_model.save(entry, next);
956
+ });
957
+ });
958
+
959
+ Function.parallel(tasks, next);
960
+ }, function done(err) {
961
+
962
+ if (err) {
963
+ return;
964
+ }
965
+
966
+ return saved_record;
967
+ });
968
+
969
+ pledge.handleCallback(callback);
970
+
971
+ return pledge;
972
+ });
973
+
974
+ /**
975
+ * Look for the record id by checking the indexes
976
+ *
977
+ * @author Jelle De Loecker <jelle@develry.be>
978
+ * @since 0.1.0
979
+ * @version 1.1.0
980
+ *
981
+ * @param {Document} document
982
+ * @param {Object} options
983
+ * @param {Function} callback
984
+ */
985
+ Model.setMethod(function auditRecord(document, options, callback) {
986
+
987
+ var that = this,
988
+ results,
989
+ schema,
990
+ tasks;
991
+
992
+ if (!document) {
993
+ return callback(new Error('No record was given to audit'));
994
+ }
995
+
996
+ schema = this.schema;
997
+
998
+ if (schema && document[this.primary_key] == null && options.audit !== false) {
999
+ tasks = {};
1000
+ results = {};
1001
+
1002
+ if (options.debug) {
1003
+ console.log('Pre-save audit record', document);
1004
+ }
1005
+
1006
+ schema.eachAlternateIndex(document, function iterIndex(index, indexName) {
1007
+
1008
+ if (options.debug) {
1009
+ console.log('Checking alternate index', indexName);
1010
+ }
1011
+
1012
+ tasks[indexName] = function auditIndex(next) {
1013
+ var query = {},
1014
+ fieldName;
1015
+
1016
+ for (fieldName in index.fields) {
1017
+ if (document[fieldName] != null) {
1018
+ query[fieldName] = document[fieldName];
1019
+
1020
+ // @todo: should run through the FieldType instance
1021
+ if (String(query[fieldName]).isObjectId()) {
1022
+ query[fieldName] = alchemy.castObjectId(query[fieldName]);
1023
+ }
1024
+ }
1025
+ }
1026
+
1027
+ that.datasource.read(that, query, {}, function gotRecordInfo(err, records) {
1028
+
1029
+ if (err != null) {
1030
+ return next(err);
1031
+ }
1032
+
1033
+ if (records[0] != null) {
1034
+ results[indexName] = records[0];
1035
+ }
1036
+
1037
+ next();
1038
+ });
1039
+
1040
+ };
1041
+ });
1042
+
1043
+ Function.parallel(tasks, function doneAudit(err) {
1044
+
1045
+ var indexName,
1046
+ record,
1047
+ count,
1048
+ ids;
1049
+
1050
+ if (err != null) {
1051
+ return callback(err);
1052
+ }
1053
+
1054
+ if (!Object.isEmpty(results)) {
1055
+
1056
+ count = 0;
1057
+ ids = {};
1058
+
1059
+ for (indexName in results) {
1060
+ record = results[indexName];
1061
+
1062
+ // First make sure this index is allowed during the audit
1063
+ // If it's not, this means it should be considered a duplicate
1064
+ if (options.allowedIndexes != null && !Object.hasValue(options.allowedIndexes, indexName)) {
1065
+ if (callback) callback(new Error('Duplicate index found other than _id: ' + indexName), null);
1066
+ return;
1067
+ }
1068
+
1069
+ // Add the id a first time
1070
+ if (ids[record[that.primary_key]] == null) {
1071
+ count++;
1072
+ ids[record[that.primary_key]] = true;
1073
+ }
1074
+ }
1075
+
1076
+ // If more than 1 ids are found, we can't update the item
1077
+ // because we don't know which record is the actual owner
1078
+ if (count > 1) {
1079
+ if (callback) callback(new Error('Multiple unique records found'));
1080
+ return;
1081
+ }
1082
+
1083
+ // Use the last found record to get the id
1084
+ document[that.primary_key] = record[that.primary_key];
1085
+ }
1086
+
1087
+ if (options.debug) {
1088
+ console.log('Audit done, found pk:', document[that.primary_key]);
1089
+ }
1090
+
1091
+ callback(null, document);
1092
+ });
1093
+
1094
+ return;
1095
+ }
1096
+
1097
+ setImmediate(function skippedAudit() {
1098
+ callback(null, document);
1099
+ });
1100
+ });
1101
+
1102
+ /**
1103
+ * Turn a record into something the database will understand
1104
+ *
1105
+ * @author Jelle De Loecker <jelle@develry.be>
1106
+ * @since 1.0.3
1107
+ * @version 1.0.4
1108
+ *
1109
+ * @param {Document} record
1110
+ * @param {Object} options
1111
+ * @param {Function} callback
1112
+ *
1113
+ * @return {Pledge}
1114
+ */
1115
+ Model.setMethod(function convertRecordToDatasourceFormat(record, options, callback) {
1116
+
1117
+ var that = this,
1118
+ pledge,
1119
+ data;
1120
+
1121
+ if (typeof options == 'function') {
1122
+ callback = options;
1123
+ options = {};
1124
+ }
1125
+
1126
+ if (!options) {
1127
+ options = {};
1128
+ }
1129
+
1130
+ data = record[this.name] || record;
1131
+
1132
+ // Normalize the data
1133
+ data = this.compose(data, options);
1134
+
1135
+ pledge = this.datasource.toDatasource(this, data);
1136
+
1137
+ pledge.handleCallback(callback);
1138
+
1139
+ return pledge;
1140
+ });
1141
+
1142
+ /**
1143
+ * Process an object of datasource format
1144
+ *
1145
+ * @author Jelle De Loecker <jelle@develry.be>
1146
+ * @since 1.0.3
1147
+ * @version 1.0.3
1148
+ *
1149
+ * @param {Object} ds_data
1150
+ * @param {Object} options
1151
+ * @param {Function} callback
1152
+ *
1153
+ * @return {Pledge}
1154
+ */
1155
+ Model.setMethod(function processDatasourceFormat(ds_data, options, callback) {
1156
+
1157
+ var that = this,
1158
+ pledge,
1159
+ data;
1160
+
1161
+ if (typeof options == 'function') {
1162
+ callback = options;
1163
+ options = {};
1164
+ }
1165
+
1166
+ if (!options) {
1167
+ options = {};
1168
+ }
1169
+
1170
+ pledge = this.datasource.toApp(this.schema, {}, options, ds_data);
1171
+
1172
+ pledge.handleCallback(callback);
1173
+
1174
+ return pledge;
1175
+ });
1176
+
1177
+ /**
1178
+ * Clear the cache of this and all associated models
1179
+ *
1180
+ * @author Jelle De Loecker <jelle@develry.be>
1181
+ * @since 0.0.1
1182
+ * @version 1.1.6
1183
+ *
1184
+ * @param {Boolean} associated Also nuke associated models
1185
+ * @param {Branch} parent
1186
+ */
1187
+ Model.setMethod(function nukeCache(associated, parent) {
1188
+
1189
+ let model_name,
1190
+ branch,
1191
+ alias;
1192
+
1193
+ // Nuke associated caches by default
1194
+ if (typeof associated == 'undefined') {
1195
+ associated = true;
1196
+ }
1197
+
1198
+ // Create the parent branch object
1199
+ if (!parent) {
1200
+ branch = parent = new Classes.Branch.Data(this.name);
1201
+ }
1202
+
1203
+ if (branch || !parent.root.seen(this.name)) {
1204
+
1205
+ if (!branch) {
1206
+ branch = parent.append(this.name);
1207
+ }
1208
+
1209
+ if (this.cache) {
1210
+ this.cache.reset();
1211
+ }
1212
+
1213
+ // Also nuke the cache of the client model, if it exists
1214
+ if (Classes.Alchemy.Client.Model[this.constructor.name] && Classes.Alchemy.Client.Model[this.constructor.name].cache) {
1215
+ Classes.Alchemy.Client.Model[this.constructor.name].cache.reset();
1216
+ }
1217
+ }
1218
+
1219
+ // Return if we don't need to nuke associated models
1220
+ if (!associated) {
1221
+ return;
1222
+ }
1223
+
1224
+ for (alias in this.associations) {
1225
+ model_name = this.associations[alias].modelName;
1226
+
1227
+ if (!parent.root.seen(model_name)) {
1228
+ let assoc_model = this.getModel(model_name);
1229
+ assoc_model.nukeCache(true, branch);
1230
+ }
1231
+ }
1232
+ });
1233
+
1234
+ /**
1235
+ * Delete the given record id
1236
+ *
1237
+ * @author Jelle De Loecker <jelle@develry.be>
1238
+ * @since 0.0.1
1239
+ * @version 1.0.7
1240
+ *
1241
+ * @param {String} id The object id
1242
+ * @param {Function} callback
1243
+ *
1244
+ * @return {Pledge}
1245
+ */
1246
+ Model.setMethod(function remove(id, callback) {
1247
+
1248
+ var that = this,
1249
+ pledge = new Pledge(),
1250
+ id;
1251
+
1252
+ pledge.handleCallback(callback);
1253
+
1254
+ if (!id) {
1255
+ pledge.reject(new Error('Invalid id given!'));
1256
+ return pledge;
1257
+ }
1258
+
1259
+ if (this.datasource.supports('objectid')) {
1260
+ id = alchemy.castObjectId(id);
1261
+ } else {
1262
+ id = String(id);
1263
+ }
1264
+
1265
+ let query = {};
1266
+ query[this.primary_key] = id;
1267
+
1268
+ this.datasource.remove(this, query, {}, function afterRemove(err, result) {
1269
+
1270
+ if (err != null) {
1271
+ return pledge.reject(err);
1272
+ }
1273
+
1274
+ that.emit('removed', result, {}, false, function afterRemovedEvent() {
1275
+ pledge.resolve(result);
1276
+ });
1277
+ });
1278
+
1279
+ return pledge;
1280
+ });
1281
+
1282
+ /**
1283
+ * Get all the records and perform the given task on them
1284
+ *
1285
+ * @author Jelle De Loecker <jelle@develry.be>
1286
+ * @since 0.5.0
1287
+ * @version 1.2.0
1288
+ *
1289
+ * @param {Object} options Find options
1290
+ * @param {Function} task Task to perform on each record
1291
+ * @param {Function} callback Function to call when done
1292
+ */
1293
+ Model.setMethod(function eachRecord(options, task, callback) {
1294
+
1295
+ var that = this,
1296
+ parallel_limit,
1297
+ available = null,
1298
+ last_id = null,
1299
+ pledge = new Classes.Pledge(),
1300
+ index = 0;
1301
+
1302
+ if (typeof options == 'function') {
1303
+ callback = task;
1304
+ task = options;
1305
+ options = {};
1306
+ } else if (!options) {
1307
+ options = {};
1308
+ }
1309
+
1310
+ if (!callback) {
1311
+ callback = Function.thrower;
1312
+ }
1313
+
1314
+ // Apply default limit of 50 records per fetch
1315
+ options = Object.assign({}, {limit: 50}, options);
1316
+
1317
+ // Get amount of tasks to do in parallel
1318
+ parallel_limit = options.parallel_limit || 8;
1319
+
1320
+ // Sort by _id ascending
1321
+ if (!options.sort) {
1322
+ options.sort = {};
1323
+ options.sort[this.primary_key] = 1;
1324
+ }
1325
+
1326
+ // Make sure there is a conditions object
1327
+ if (!options.conditions) {
1328
+ options.conditions = {};
1329
+ }
1330
+
1331
+ this.find('all', options, function gotRecords(err, result) {
1332
+
1333
+ var tasks = [];
1334
+
1335
+ if (!result.length) {
1336
+ pledge.reportProgress(100);
1337
+ pledge.resolve();
1338
+ return callback(null);
1339
+ }
1340
+
1341
+ if (available == null) {
1342
+ available = result.available;
1343
+ options.available = false;
1344
+ } else {
1345
+ result.available = available;
1346
+ }
1347
+
1348
+ result.forEach(function eachRecord(record) {
1349
+
1350
+ var record_index = index++;
1351
+
1352
+ last_id = record[that.model_name][that.primary_key];
1353
+
1354
+ tasks.push(function doSave(next) {
1355
+ pledge.reportProgress(((record_index - 1) / available) * 100);
1356
+ task.call(that, record, record_index, next);
1357
+ });
1358
+ });
1359
+
1360
+ Function.parallel(parallel_limit, tasks, function done(err) {
1361
+
1362
+ if (err) {
1363
+ pledge.reject(err);
1364
+ return callback(err);
1365
+ }
1366
+
1367
+ let next_options = Object.assign({}, options);
1368
+
1369
+ // Get records with a bigger _id than the last found
1370
+ next_options.conditions[that.primary_key] = {$gt: last_id};
1371
+
1372
+ that.find('all', next_options, gotRecords);
1373
+ });
1374
+ });
1375
+
1376
+ return pledge;
1377
+ });
1378
+
1379
+ /**
1380
+ * Strip out private fields
1381
+ *
1382
+ * @author Jelle De Loecker <jelle@develry.be>
1383
+ * @since 1.0.0
1384
+ * @version 1.0.0
1385
+ *
1386
+ * @param {Array} records
1387
+ */
1388
+ Model.setMethod(function removePrivateFields(records) {
1389
+
1390
+ var has_private_fields,
1391
+ fields = this.schema.getSorted(false),
1392
+ record,
1393
+ field,
1394
+ i,
1395
+ j;
1396
+
1397
+ records = Array.cast(records);
1398
+
1399
+ for (i = 0; i < records.length; i++) {
1400
+ record = records[i];
1401
+
1402
+ for (j = 0; j < fields.length; j++) {
1403
+ field = fields[j];
1404
+
1405
+ if (field.is_private) {
1406
+ has_private_fields = true;
1407
+ delete record[field.name];
1408
+ }
1409
+ }
1410
+
1411
+ // If there are no private fields, break loop
1412
+ if (!has_private_fields) {
1413
+ break;
1414
+ }
1415
+ }
1416
+
1417
+ return records;
1418
+ });
1419
+
1420
+ /**
1421
+ * Create an export stream
1422
+ *
1423
+ * @author Jelle De Loecker <jelle@develry.be>
1424
+ * @since 1.0.5
1425
+ * @version 1.0.5
1426
+ *
1427
+ * @param {Stream} output
1428
+ * @param {Object} options
1429
+ *
1430
+ * @return {Pledge}
1431
+ */
1432
+ Model.setMethod(function exportToStream(output, options) {
1433
+
1434
+ if (!alchemy.isStream(output)) {
1435
+ if (!options) {
1436
+ options = output;
1437
+ output = null;
1438
+ }
1439
+
1440
+ output = options.output;
1441
+ }
1442
+
1443
+ if (!output) {
1444
+ return Pledge.reject(new Error('No target output stream has been given'));
1445
+ }
1446
+
1447
+ if (!options) {
1448
+ options = {};
1449
+ }
1450
+
1451
+ // Only allow 1 task to run at a time
1452
+ options.parallel_limit = 1;
1453
+
1454
+ let that = this,
1455
+ name_buf = Buffer.from(this.model_name),
1456
+ head_buf;
1457
+
1458
+ // 0x01 is a model
1459
+ head_buf = Buffer.concat([Buffer.from([0x01, name_buf.length]), name_buf]);
1460
+
1461
+ output.write(head_buf);
1462
+
1463
+ return this.eachRecord(options, function eachRecord(record, index, next) {
1464
+ record.exportToStream(output).done(next);
1465
+ }, function done(err) {
1466
+
1467
+ });
1468
+ });
1469
+
1470
+ /**
1471
+ * Import from a stream
1472
+ *
1473
+ * @author Jelle De Loecker <jelle@develry.be>
1474
+ * @since 1.0.5
1475
+ * @version 1.0.5
1476
+ *
1477
+ * @param {Stream} input
1478
+ * @param {Object} options
1479
+ *
1480
+ * @return {Pledge}
1481
+ */
1482
+ Model.setMethod(function importFromStream(input, options) {
1483
+
1484
+ if (!alchemy.isStream(input)) {
1485
+ if (!options) {
1486
+ options = input;
1487
+ input = null;
1488
+ }
1489
+
1490
+ input = options.input;
1491
+ }
1492
+
1493
+ if (!input) {
1494
+ return Pledge.reject(new Error('No source input stream has been given'));
1495
+ }
1496
+
1497
+ let that = this,
1498
+ current_type = null,
1499
+ extra_stream,
1500
+ pledge = new Pledge(),
1501
+ stopped,
1502
+ paused,
1503
+ buffer,
1504
+ value,
1505
+ seen = 0,
1506
+ left,
1507
+ size,
1508
+ doc;
1509
+
1510
+ input.on('data', function onData(data) {
1511
+
1512
+ if (stopped) {
1513
+ return;
1514
+ }
1515
+
1516
+ if (buffer) {
1517
+ buffer = Buffer.concat([buffer, data]);
1518
+ } else {
1519
+ buffer = data;
1520
+ }
1521
+
1522
+ handleBuffer();
1523
+ });
1524
+
1525
+ function handleBuffer() {
1526
+
1527
+ if (paused) {
1528
+ return;
1529
+ }
1530
+
1531
+ if (!current_type && buffer.length < 2) {
1532
+ return;
1533
+ }
1534
+
1535
+ if (!current_type) {
1536
+ current_type = buffer.readUInt8(0);
1537
+
1538
+ if (current_type == 0x01) {
1539
+ size = buffer.readUInt8(1);
1540
+ buffer = buffer.slice(2);
1541
+ } else if (current_type == 0x02 && buffer.length >= 5) {
1542
+ size = buffer.readUInt32BE(1);
1543
+ buffer = buffer.slice(5);
1544
+ } else if (current_type == 0xFF) {
1545
+ size = buffer.readUInt32BE(1);
1546
+ buffer = buffer.slice(5);
1547
+ seen = 0;
1548
+
1549
+ if (!doc) {
1550
+ stopped = true;
1551
+ pledge.reject(new Error('Found extra import data, but no active document'));
1552
+ } else {
1553
+ extra_stream = new require('stream').PassThrough();
1554
+ doc.extraImportFromStream(extra_stream);
1555
+ }
1556
+ } else {
1557
+ // Not enough data? Wait
1558
+ current_type = null;
1559
+ return;
1560
+ }
1561
+ }
1562
+
1563
+ handleRest();
1564
+ }
1565
+
1566
+ function handleRest() {
1567
+
1568
+ if (current_type == 0xFF) {
1569
+ left = size - seen;
1570
+ value = buffer.slice(0, left);
1571
+
1572
+ seen += value.length;
1573
+
1574
+ if (value.length == buffer.length) {
1575
+ buffer = null;
1576
+ } else if (value.length < buffer.length) {
1577
+ buffer = buffer.slice(left);
1578
+ }
1579
+
1580
+ extra_stream.write(value);
1581
+
1582
+ if (value.length == left) {
1583
+ extra_stream.end();
1584
+ current_type = null;
1585
+
1586
+ if (buffer) {
1587
+ handleBuffer();
1588
+ }
1589
+ }
1590
+
1591
+ return;
1592
+ }
1593
+
1594
+ if (buffer.length >= size) {
1595
+ value = buffer.slice(0, size);
1596
+ buffer = buffer.slice(size);
1597
+ } else {
1598
+ // Wait for next call
1599
+ return;
1600
+ }
1601
+
1602
+ if (current_type == 0x01) {
1603
+ value = value.toString();
1604
+
1605
+ if (value == that.model_name) {
1606
+ // Found name!
1607
+ current_type = null;
1608
+ size = 0;
1609
+ } else {
1610
+ stopped = true;
1611
+ return pledge.reject(new Error('Model names do not match'));
1612
+ }
1613
+ } else if (current_type == 0x02) {
1614
+ doc = that.createDocument();
1615
+ input.pause();
1616
+ paused = true;
1617
+
1618
+ doc.importFromBuffer(value).done(function done(err, result) {
1619
+
1620
+ if (err) {
1621
+ stopped = true;
1622
+ return pledge.reject(err);
1623
+ }
1624
+
1625
+ current_type = null;
1626
+ paused = false;
1627
+ input.resume();
1628
+
1629
+ handleBuffer();
1630
+ });
1631
+
1632
+ return;
1633
+ }
1634
+
1635
+ if (buffer && buffer.length) {
1636
+ handleBuffer();
1637
+ }
1638
+ }
1639
+
1640
+ return pledge;
1641
+ });
1642
+
1643
+ /**
1644
+ * Get a model
1645
+ *
1646
+ * @author Jelle De Loecker <jelle@develry.be>
1647
+ * @since 0.0.1
1648
+ * @version 1.1.0
1649
+ *
1650
+ * @param {String} name
1651
+ * @param {Boolean} init
1652
+ *
1653
+ * @return {Model}
1654
+ */
1655
+ Model.get = function get(name, init, options) {
1656
+
1657
+ var constructor,
1658
+ namespace,
1659
+ pieces,
1660
+ path,
1661
+ obj;
1662
+
1663
+ if (typeof name == 'function') {
1664
+ name = name.name;
1665
+ }
1666
+
1667
+ if (!name) {
1668
+ throw new TypeError('Model name should be a non-empty string');
1669
+ }
1670
+
1671
+ if (init && typeof init == 'object') {
1672
+ options = init;
1673
+ init = true;
1674
+ }
1675
+
1676
+ if (!options) {
1677
+ options = {};
1678
+ }
1679
+
1680
+ if (nameCache[name]) {
1681
+ if (init === false) {
1682
+ return nameCache[name];
1683
+ } else {
1684
+ return new nameCache[name];
1685
+ }
1686
+ }
1687
+
1688
+ pieces = name.split('.');
1689
+
1690
+ if (pieces.length > 1) {
1691
+ // The first part is the namespace
1692
+ namespace = pieces.shift();
1693
+
1694
+ // The rest should be the path
1695
+ path = pieces.join('.');
1696
+
1697
+ obj = Classes[namespace];
1698
+
1699
+ if (!obj) {
1700
+ if (init === false) {
1701
+ return null;
1702
+ }
1703
+
1704
+ throw new TypeError('Namespace "' + namespace + '" could not be found');
1705
+ }
1706
+ } else {
1707
+ path = name;
1708
+ obj = Classes.Alchemy.Model;
1709
+ }
1710
+
1711
+ constructor = Object.path(obj, path) || obj[String(path).modelName()];
1712
+
1713
+ if (constructor == null) {
1714
+ if (init === false) {
1715
+ return null;
1716
+ }
1717
+
1718
+ throw new Error('Could not find model "' + name + '"');
1719
+ }
1720
+
1721
+ // Store this name in the cache,
1722
+ // so we don't have to perform the expensive #modelName() method again
1723
+ nameCache[name] = constructor;
1724
+
1725
+ if (init === false) {
1726
+ return constructor;
1727
+ } else {
1728
+ return new constructor;
1729
+ }
1730
+ };
1731
+
1732
+ /**
1733
+ * Make the base Model class a global
1734
+ *
1735
+ * @author Jelle De Loecker <jelle@develry.be>
1736
+ * @since 0.0.1
1737
+ * @version 0.2.0
1738
+ *
1739
+ * @type {Object}
1740
+ */
1770
1741
  global.Model = Model;