alchemymvc 1.2.5 → 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 (76) 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 +0 -0
  17. package/lib/app/helper/socket_helper.js +613 -613
  18. package/lib/app/helper_component/paginate_component.js +0 -0
  19. package/lib/app/helper_controller/component.js +0 -0
  20. package/lib/app/helper_controller/conduit.js +0 -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 +0 -0
  44. package/lib/app/helper_model/db_query.js +0 -0
  45. package/lib/app/helper_model/document_list.js +0 -0
  46. package/lib/app/model/alchemy_task_model.js +0 -0
  47. package/lib/app/routes.js +0 -0
  48. package/lib/app/view/alchemy/info.ejs +0 -0
  49. package/lib/app/view/error/unknown.ejs +0 -0
  50. package/lib/app/view/paginate/navlist.ejs +0 -0
  51. package/lib/bootstrap.js +0 -0
  52. package/lib/class/behaviour.js +0 -0
  53. package/lib/class/component.js +0 -0
  54. package/lib/class/conduit.js +2555 -2552
  55. package/lib/class/controller.js +4 -1
  56. package/lib/class/document_list.js +0 -0
  57. package/lib/class/helper.js +0 -0
  58. package/lib/class/inode.js +0 -0
  59. package/lib/class/inode_dir.js +0 -0
  60. package/lib/class/inode_file.js +112 -112
  61. package/lib/class/inode_list.js +0 -0
  62. package/lib/class/model.js +1772 -1769
  63. package/lib/class/path_definition.js +0 -0
  64. package/lib/class/route.js +0 -0
  65. package/lib/class/session.js +0 -0
  66. package/lib/class/task.js +0 -0
  67. package/lib/core/base.js +50 -9
  68. package/lib/core/discovery.js +0 -0
  69. package/lib/core/routing.js +0 -0
  70. package/lib/core/socket.js +159 -159
  71. package/lib/init/alchemy.js +1823 -1823
  72. package/lib/init/constants.js +0 -0
  73. package/lib/init/functions.js +8 -4
  74. package/lib/init/load_functions.js +0 -0
  75. package/lib/init/requirements.js +101 -101
  76. package/package.json +74 -74
@@ -1,1770 +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.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
+
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
+ */
1770
1773
  global.Model = Model;