alchemymvc 1.1.7 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/lib/app/behaviour/sluggable_behaviour.js +2 -4
  2. package/lib/app/conduit/http_conduit.js +173 -192
  3. package/lib/app/conduit/loopback_conduit.js +0 -0
  4. package/lib/app/conduit/socket_conduit.js +620 -620
  5. package/lib/app/datasource/mongo_datasource.js +75 -10
  6. package/lib/app/element/time_ago.js +0 -0
  7. package/lib/app/helper/socket_helper.js +613 -613
  8. package/lib/app/helper_datasource/00-nosql_datasource.js +73 -19
  9. package/lib/app/helper_datasource/remote_datasource.js +0 -0
  10. package/lib/app/helper_field/05-string_field.js +0 -0
  11. package/lib/app/helper_field/schema_field.js +7 -2
  12. package/lib/app/helper_model/criteria.js +76 -18
  13. package/lib/app/helper_model/field_config.js +24 -5
  14. package/lib/app/helper_model/model.js +65 -7
  15. package/lib/app/model/alchemy_migration_model.js +33 -0
  16. package/lib/bootstrap.js +9 -0
  17. package/lib/class/conduit.js +2474 -2412
  18. package/lib/class/datasource.js +8 -8
  19. package/lib/class/field.js +7 -1
  20. package/lib/class/inode.js +62 -0
  21. package/lib/class/inode_dir.js +115 -0
  22. package/lib/class/inode_file.js +112 -111
  23. package/lib/class/migration.js +138 -0
  24. package/lib/class/model.js +1765 -1772
  25. package/lib/class/reciprocal.js +8 -2
  26. package/lib/class/router.js +0 -0
  27. package/lib/class/schema.js +14 -12
  28. package/lib/core/base.js +37 -10
  29. package/lib/core/client_base.js +1 -9
  30. package/lib/core/middleware.js +4 -8
  31. package/lib/core/socket.js +159 -159
  32. package/lib/init/alchemy.js +1779 -1779
  33. package/lib/init/devwatch.js +1 -1
  34. package/lib/init/functions.js +2 -56
  35. package/lib/init/load_functions.js +8 -2
  36. package/lib/init/requirements.js +101 -96
  37. package/lib/stages.js +12 -0
  38. package/package.json +74 -76
  39. package/CHANGELOG.md +0 -445
@@ -165,7 +165,7 @@ Datasource.setMethod(function getSchema(schema) {
165
165
  *
166
166
  * @author Jelle De Loecker <jelle@develry.be>
167
167
  * @since 0.2.0
168
- * @version 1.1.0
168
+ * @version 1.2.0
169
169
  *
170
170
  * @param {Schema|Model} schema
171
171
  * @param {Object} data
@@ -187,7 +187,7 @@ Datasource.setMethod(function toDatasource(schema, data, callback) {
187
187
  }
188
188
 
189
189
  if (!schema) {
190
- log.todo('Schema not found: not normalizing data');
190
+ log.todo('Schema not found: not normalizing data', data);
191
191
  pledge = Pledge.resolve(data);
192
192
  pledge.done(callback);
193
193
  return pledge;
@@ -285,6 +285,12 @@ Datasource.setMethod(function toApp(schema, query, options, data, callback) {
285
285
  return pledge;
286
286
  }
287
287
 
288
+ options = Object.assign({}, options);
289
+
290
+ if (!options._root_data) {
291
+ options._root_data = data;
292
+ }
293
+
288
294
  if (data[schema.name]) {
289
295
  tasks = {};
290
296
 
@@ -338,12 +344,6 @@ Datasource.setMethod(function toApp(schema, query, options, data, callback) {
338
344
  data = Object.assign({}, data);
339
345
  tasks = {};
340
346
 
341
- options = Object.assign({}, options);
342
-
343
- if (!options._root_data) {
344
- options._root_data = data;
345
- }
346
-
347
347
  Object.each(data, function eachField(value, fieldName) {
348
348
 
349
349
  var field = schema.get(fieldName);
@@ -594,7 +594,7 @@ Field.setMethod(function castCondition(value, field_paths) {
594
594
  *
595
595
  * @author Jelle De Loecker <jelle@develry.be>
596
596
  * @since 0.5.0
597
- * @version 1.0.7
597
+ * @version 1.2.0
598
598
  *
599
599
  * @param {Mixed} value
600
600
  * @param {Array} field_paths The path to the field
@@ -602,6 +602,12 @@ Field.setMethod(function castCondition(value, field_paths) {
602
602
  * @return {Mixed}
603
603
  */
604
604
  Field.setMethod(function _castCondition(value, field_paths) {
605
+
606
+ // Always allow regex values, we'll use those in the projection stage
607
+ if (value && RegExp.isRegExp(value)) {
608
+ return value;
609
+ }
610
+
605
611
  return this.cast(value, true);
606
612
  });
607
613
 
@@ -137,4 +137,66 @@ Inode.setProperty(function rootname() {
137
137
  }
138
138
 
139
139
  return libpath.basename(this.name, extension);
140
+ });
141
+
142
+ /**
143
+ * Get the stat object
144
+ *
145
+ * @author Jelle De Loecker <jelle@elevenways.be>
146
+ * @since 1.1.8
147
+ * @version 1.1.8
148
+ *
149
+ * @param {boolean} refresh
150
+ *
151
+ * @return {Pledge<fs.Stats>}
152
+ */
153
+ Inode.setMethod(function getStats(refresh) {
154
+
155
+ if (!refresh && this.stat) {
156
+ return Pledge.resolve(this.stat);
157
+ }
158
+
159
+ let pledge = new Pledge();
160
+
161
+ fs.stat(this.path, (err, stat) => {
162
+
163
+ if (err) {
164
+ return pledge.reject(err);
165
+ }
166
+
167
+ this.stat = stat;
168
+ pledge.resolve(stat);
169
+ });
170
+
171
+ return pledge;
172
+ });
173
+
174
+ /**
175
+ * Does this inode still exist?
176
+ *
177
+ * @author Jelle De Loecker <jelle@elevenways.be>
178
+ * @since 1.1.8
179
+ * @version 1.1.8
180
+ *
181
+ * @return {boolean}
182
+ */
183
+ Inode.setMethod(async function exists() {
184
+
185
+ let stat;
186
+
187
+ try {
188
+ stat = await this.getStats(true);
189
+ } catch (err) {
190
+ if (err.code == 'ENOENT') {
191
+ return false;
192
+ } else {
193
+ return null;
194
+ }
195
+ }
196
+
197
+ if (!stat) {
198
+ return false;
199
+ }
200
+
201
+ return true;
140
202
  });
@@ -1,3 +1,5 @@
1
+ const libpath = alchemy.use('path');
2
+
1
3
  /**
2
4
  * The Directory class
3
5
  *
@@ -108,3 +110,116 @@ Directory.setMethod(Symbol.iterator, function* iterate() {
108
110
  yield this.contents.entries[i];
109
111
  }
110
112
  });
113
+
114
+ /**
115
+ * Get a file from this directory, or its subdirectory
116
+ *
117
+ * @author Jelle De Loecker <jelle@elevenways.be>
118
+ * @since 1.1.8
119
+ * @version 1.1.8
120
+ *
121
+ * @param {string|string[]} path
122
+ *
123
+ * @return {Alchemy.Inode}
124
+ */
125
+ Directory.setMethod(async function get(path) {
126
+
127
+ if (!Array.isArray(path)) {
128
+ path = path.split(libpath.sep);
129
+ }
130
+
131
+ if (!this.contents.length) {
132
+ await this.loadContents();
133
+ }
134
+
135
+ let current,
136
+ result = this,
137
+ piece,
138
+ entry;
139
+
140
+ for (piece of path) {
141
+ current = result;
142
+ result = null;
143
+
144
+ // If the current piece isn't a directory we can end now
145
+ if (!current.is_directory) {
146
+ break;
147
+ }
148
+
149
+ // Loop over all the entries of the current directory
150
+ for (entry of current) {
151
+ if (piece == entry.name) {
152
+ result = entry;
153
+ break;
154
+ }
155
+ }
156
+
157
+ if (!result) {
158
+ return null;
159
+ }
160
+ }
161
+
162
+ return result;
163
+ });
164
+
165
+ /**
166
+ * Return a list of all files
167
+ *
168
+ * @author Jelle De Loecker <jelle@elevenways.be>
169
+ * @since 1.1.8
170
+ * @version 1.1.8
171
+ *
172
+ * @return {Alchemy.Inode.List}
173
+ */
174
+ Directory.setMethod(async function flatten() {
175
+
176
+ if (!this.contents.length) {
177
+ await this.loadContents({recursive: true});
178
+ }
179
+
180
+ let info = {
181
+ start : this.path,
182
+ seen : new Set(),
183
+ list : new Classes.Alchemy.Inode.List([]),
184
+ };
185
+
186
+ flattenFiles(this, info);
187
+
188
+ return info.list;
189
+ });
190
+
191
+ /**
192
+ * Add all files to the given list
193
+ *
194
+ * @author Jelle De Loecker <jelle@elevenways.be>
195
+ * @since 1.1.8
196
+ * @version 1.1.8
197
+ *
198
+ * @param {Alchemy.Inode.Directory} current
199
+ * @param {Object} info
200
+ *
201
+ * @return {Alchemy.Inode.List}
202
+ */
203
+ function flattenFiles(current, info) {
204
+
205
+ if (info.seen.has(current.path)) {
206
+ return;
207
+ }
208
+
209
+ info.seen.add(current.path);
210
+
211
+ let entry,
212
+ dirs = [];
213
+
214
+ for (entry of current) {
215
+ if (entry.is_file) {
216
+ info.list.entries.push(entry);
217
+ } else {
218
+ dirs.push(entry);
219
+ }
220
+ }
221
+
222
+ for (entry of dirs) {
223
+ flattenFiles(entry, info);
224
+ }
225
+ }
@@ -1,112 +1,113 @@
1
- const fs = alchemy.use('fs');
2
-
3
- /**
4
- * The File class
5
- *
6
- * @constructor
7
- *
8
- * @author Jelle De Loecker <jelle@elevenways.be>
9
- * @since 1.1.0
10
- * @version 1.1.0
11
- *
12
- * @param {String} path Path to the file
13
- */
14
- var File = Function.inherits('Alchemy.Inode', function File(path) {
15
-
16
- if (typeof path == 'object') {
17
- return File.from(path);
18
- }
19
-
20
- this.type = null;
21
- this.hash = null;
22
-
23
- File.super.call(this, path);
24
- });
25
-
26
- /**
27
- * Files are obviously files
28
- *
29
- * @author Jelle De Loecker <jelle@develry.be>
30
- * @since 1.1.0
31
- * @version 1.1.0
32
- *
33
- * @type {Boolean}
34
- */
35
- File.setProperty('is_file', true);
36
-
37
- /**
38
- * Create a File instance from the given variable
39
- *
40
- * @author Jelle De Loecker <jelle@develry.be>
41
- * @since 1.1.0
42
- * @version 1.1.0
43
- */
44
- File.setStatic(function from(obj) {
45
-
46
- var file,
47
- path;
48
-
49
- if (typeof obj == 'string') {
50
- path = obj;
51
- obj = null;
52
- } else {
53
- path = obj.path;
54
- }
55
-
56
- file = new File(path);
57
-
58
- if (obj) {
59
- file.stat = {
60
- size : obj.size
61
- };
62
-
63
- file.name = obj.name;
64
- file.type = obj.type;
65
- file.hash = obj.hash;
66
- }
67
-
68
- return file;
69
- });
70
-
71
- /**
72
- * Read and return the contents
73
- *
74
- * @author Jelle De Loecker <jelle@develry.be>
75
- * @since 1.1.0
76
- * @version 1.1.0
77
- */
78
- File.setMethod(function read(options) {
79
-
80
- let pledge = new Pledge();
81
-
82
- if (!options) {
83
- options = {flag: 'r'};
84
- }
85
-
86
- fs.readFile(this.path, options, function done(err, data) {
87
-
88
- if (err) {
89
- return pledge.reject(err);
90
- }
91
-
92
- pledge.resolve(data);
93
- });
94
-
95
- return pledge;
96
- });
97
-
98
- /**
99
- * Read and return the contents as a string
100
- *
101
- * @author Jelle De Loecker <jelle@develry.be>
102
- * @since 1.1.0
103
- * @version 1.1.0
104
- */
105
- File.setMethod(function readString(encoding) {
106
-
107
- if (!encoding) {
108
- encoding = 'utf8';
109
- }
110
-
111
- return this.read({encoding: encoding});
1
+ const fs = alchemy.use('fs');
2
+
3
+ /**
4
+ * The File class
5
+ *
6
+ * @constructor
7
+ *
8
+ * @author Jelle De Loecker <jelle@elevenways.be>
9
+ * @since 1.1.0
10
+ * @version 1.1.0
11
+ *
12
+ * @param {String} path Path to the file
13
+ */
14
+ var File = Function.inherits('Alchemy.Inode', function File(path) {
15
+
16
+ if (typeof path == 'object') {
17
+ return File.from(path);
18
+ }
19
+
20
+ this.type = null;
21
+ this.hash = null;
22
+
23
+ File.super.call(this, path);
24
+ });
25
+
26
+ /**
27
+ * Files are obviously files
28
+ *
29
+ * @author Jelle De Loecker <jelle@develry.be>
30
+ * @since 1.1.0
31
+ * @version 1.1.0
32
+ *
33
+ * @type {Boolean}
34
+ */
35
+ File.setProperty('is_file', true);
36
+
37
+ /**
38
+ * Create a File instance from the given variable
39
+ *
40
+ * @author Jelle De Loecker <jelle@develry.be>
41
+ * @since 1.1.0
42
+ * @version 1.2.0
43
+ */
44
+ File.setStatic(function from(obj) {
45
+
46
+ let path;
47
+
48
+ if (typeof obj == 'string') {
49
+ path = obj;
50
+ obj = null;
51
+ } else {
52
+ path = obj.path;
53
+ }
54
+
55
+ let file = new File(path);
56
+
57
+ if (obj) {
58
+ file.stat = {
59
+ size : obj.size
60
+ };
61
+
62
+ file.name = obj.name;
63
+ file.type = obj.type;
64
+ file.hash = obj.hash;
65
+ }
66
+
67
+ return file;
68
+ });
69
+
70
+ /**
71
+ * Read and return the contents
72
+ *
73
+ * @author Jelle De Loecker <jelle@develry.be>
74
+ * @since 1.1.0
75
+ * @version 1.1.8
76
+ */
77
+ File.setMethod(function read(options) {
78
+
79
+ let pledge = new Pledge();
80
+
81
+ if (!options) {
82
+ options = {flag: 'r'};
83
+ } else if (!options.flag) {
84
+ options.flag = 'r';
85
+ }
86
+
87
+ fs.readFile(this.path, options, function done(err, data) {
88
+
89
+ if (err) {
90
+ return pledge.reject(err);
91
+ }
92
+
93
+ pledge.resolve(data);
94
+ });
95
+
96
+ return pledge;
97
+ });
98
+
99
+ /**
100
+ * Read and return the contents as a string
101
+ *
102
+ * @author Jelle De Loecker <jelle@develry.be>
103
+ * @since 1.1.0
104
+ * @version 1.1.0
105
+ */
106
+ File.setMethod(function readString(encoding) {
107
+
108
+ if (!encoding) {
109
+ encoding = 'utf8';
110
+ }
111
+
112
+ return this.read({encoding: encoding});
112
113
  });
@@ -0,0 +1,138 @@
1
+ const libpath = alchemy.use('path');
2
+
3
+ /**
4
+ * Migrations
5
+ *
6
+ * @constructor
7
+ *
8
+ * @author Jelle De Loecker <jelle@elevenways.be>
9
+ * @since 1.2.0
10
+ * @version 1.2.0
11
+ */
12
+ const Migration = Function.inherits('Alchemy.Base', function Migration(document) {
13
+ this.document = document;
14
+ });
15
+
16
+ /**
17
+ * Start the necesary migrations
18
+ *
19
+ * @constructor
20
+ *
21
+ * @author Jelle De Loecker <jelle@elevenways.be>
22
+ * @since 1.2.0
23
+ * @version 1.2.0
24
+ */
25
+ Migration.setStatic(async function start() {
26
+
27
+ const AlchemyMigration = Model.get('AlchemyMigration');
28
+
29
+ console.log('Starting migration task...');
30
+
31
+ let dir = new Classes.Alchemy.Inode.Directory(libpath.resolve(PATH_APP, 'migrations'));
32
+
33
+ await dir.loadContents();
34
+
35
+ for (let entry of dir) {
36
+
37
+ let name = entry.name.beforeLast('.js');
38
+
39
+ if (!name) {
40
+ continue;
41
+ }
42
+
43
+ let crit = AlchemyMigration.find();
44
+ crit.where('name').equals(name);
45
+
46
+ let record = await AlchemyMigration.find('first', crit);
47
+
48
+ if (record) {
49
+ if (record.ended) {
50
+ console.log(' »» Migration "' + name + '" already finished on ' + record.ended.format('Y-m-d H:i'));
51
+ } else if (record.error) {
52
+ console.log(' »» Migration "' + name + '" failed with error: ' + record.error);
53
+ console.log('Migrations stopped');
54
+ return;
55
+ } else {
56
+ console.log(' »» Migration "' + name + '" has not yet finished!');
57
+ console.log('Migrations stopped');
58
+ return;
59
+ }
60
+
61
+ continue;
62
+ }
63
+
64
+ record = AlchemyMigration.createDocument();
65
+ record.name = name;
66
+ record.path = entry.path;
67
+
68
+ await record.save();
69
+
70
+ try {
71
+
72
+ console.log(' »» Starting migration "' + name + '"');
73
+
74
+ let migration = new Migration(record);
75
+
76
+ await Blast.require(entry.path, {
77
+ client: false,
78
+ async : true,
79
+ arguments: {
80
+ names : [
81
+ 'Blast',
82
+ 'Collection',
83
+ 'Bound',
84
+ 'Obj',
85
+ 'Fn',
86
+ 'migration'
87
+ ],
88
+ values : [
89
+ Blast,
90
+ Blast.Collection,
91
+ Blast.Bound,
92
+ Blast.Bound.Object,
93
+ Blast.Collection.Function,
94
+ migration,
95
+ ]
96
+ }
97
+ });
98
+
99
+ record.ended = new Date();
100
+ await record.save();
101
+
102
+ } catch (err) {
103
+ console.log(' »» Migration error:', err);
104
+ record.error = err.message + '\n' + err.stack;
105
+ await record.save();
106
+ console.log('Migrations stopped');
107
+ return;
108
+ }
109
+ }
110
+
111
+ console.log('Executed all migrations');
112
+ });
113
+
114
+ /**
115
+ * Migrate methods
116
+ *
117
+ * @constructor
118
+ *
119
+ * @author Jelle De Loecker <jelle@elevenways.be>
120
+ * @since 1.2.0
121
+ * @version 1.2.0
122
+ */
123
+ Migration.setMethod(function processRecords(model_name, fnc) {
124
+
125
+ const model = Model.get(model_name);
126
+
127
+ let options = {
128
+ document : false,
129
+ parallel_limit : 1,
130
+ };
131
+
132
+ return model.eachRecord(options, async (record, index, next) => {
133
+ await fnc(model, record[model_name], index);
134
+ next();
135
+ }, (err) => {
136
+ // Done!
137
+ });
138
+ });