alchemymvc 1.2.4 → 1.2.6

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