alchemymvc 1.3.21 → 1.4.0-alpha.1

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 (155) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +3 -3
  3. package/lib/app/behaviour/publishable_behaviour.js +5 -5
  4. package/lib/app/behaviour/revision_behaviour.js +10 -10
  5. package/lib/app/behaviour/sluggable_behaviour.js +14 -14
  6. package/lib/app/conduit/electron_conduit.js +9 -9
  7. package/lib/app/conduit/http_conduit.js +13 -13
  8. package/lib/app/conduit/loopback_conduit.js +15 -15
  9. package/lib/app/conduit/socket_conduit.js +43 -43
  10. package/lib/app/config/routes.js +26 -0
  11. package/lib/app/controller/00-default_app_controller.js +21 -0
  12. package/lib/app/controller/alchemy_info_controller.js +12 -12
  13. package/lib/app/datasource/mongo_datasource.js +16 -16
  14. package/lib/app/element/00-default_app_element.js +19 -0
  15. package/lib/app/element/time_ago.js +5 -5
  16. package/lib/app/helper/00-default_app_helper.js +11 -0
  17. package/lib/app/helper/alchemy_helper.js +22 -22
  18. package/lib/app/helper/backed_map.js +1 -1
  19. package/lib/app/helper/breadcrumb.js +10 -10
  20. package/lib/app/helper/client_collection.js +3 -3
  21. package/lib/app/helper/cron.js +29 -29
  22. package/lib/app/helper/enum_values.js +6 -6
  23. package/lib/app/helper/pagination_helper.js +36 -36
  24. package/lib/app/helper/router_helper.js +35 -35
  25. package/lib/app/helper/socket_helper.js +57 -57
  26. package/lib/app/helper/syncable.js +84 -59
  27. package/lib/app/helper_component/paginate_component.js +9 -9
  28. package/lib/app/helper_controller/component.js +1 -1
  29. package/lib/app/helper_controller/conduit.js +31 -31
  30. package/lib/app/helper_controller/controller.js +54 -39
  31. package/lib/app/helper_datasource/00-nosql_datasource.js +624 -70
  32. package/lib/app/helper_datasource/05-fallback_datasource.js +10 -10
  33. package/lib/app/helper_datasource/idb_datasource.js +6 -6
  34. package/lib/app/helper_datasource/indexed_db.js +22 -22
  35. package/lib/app/helper_datasource/remote_datasource.js +5 -5
  36. package/lib/app/helper_error/http_error.js +4 -4
  37. package/lib/app/helper_error/model_error.js +2 -2
  38. package/lib/app/helper_error/validation_error.js +12 -12
  39. package/lib/app/helper_field/00-objectid_field.js +7 -7
  40. package/lib/app/helper_field/05-string_field.js +16 -12
  41. package/lib/app/helper_field/06-text_field.js +2 -4
  42. package/lib/app/helper_field/10-number_field.js +9 -12
  43. package/lib/app/helper_field/11-date_field.js +15 -15
  44. package/lib/app/helper_field/15-local_temporal_field.js +10 -10
  45. package/lib/app/helper_field/20-decimal_field.js +8 -9
  46. package/lib/app/helper_field/belongsto_field.js +1 -1
  47. package/lib/app/helper_field/big_int_field.js +8 -8
  48. package/lib/app/helper_field/boolean_field.js +9 -11
  49. package/lib/app/helper_field/datetime_field.js +3 -3
  50. package/lib/app/helper_field/enum_field.js +13 -8
  51. package/lib/app/helper_field/fixed_decimal_field.js +6 -7
  52. package/lib/app/helper_field/geopoint_field.js +9 -10
  53. package/lib/app/helper_field/habtm_field.js +3 -3
  54. package/lib/app/helper_field/hasoneparent_field.js +1 -1
  55. package/lib/app/helper_field/html_field.js +2 -4
  56. package/lib/app/helper_field/integer_field.js +8 -11
  57. package/lib/app/helper_field/local_date_field.js +5 -5
  58. package/lib/app/helper_field/local_date_time_field.js +5 -5
  59. package/lib/app/helper_field/local_time_field.js +5 -5
  60. package/lib/app/helper_field/mixed_field.js +5 -5
  61. package/lib/app/helper_field/object_field.js +8 -8
  62. package/lib/app/helper_field/password_field.js +3 -3
  63. package/lib/app/helper_field/regexp_field.js +7 -9
  64. package/lib/app/helper_field/schema_field.js +91 -88
  65. package/lib/app/helper_field/settings_field.js +92 -0
  66. package/lib/app/helper_field/time_field.js +6 -6
  67. package/lib/app/helper_field/url_field.js +2 -4
  68. package/lib/app/helper_model/00-base_criteria.js +662 -0
  69. package/lib/app/helper_model/05-criteria_expressions.js +605 -0
  70. package/lib/app/helper_model/10-model_criteria.js +1182 -0
  71. package/lib/app/helper_model/data_provider.js +2 -2
  72. package/lib/app/helper_model/document.js +103 -92
  73. package/lib/app/helper_model/document_list.js +14 -14
  74. package/lib/app/helper_model/field_config.js +11 -11
  75. package/lib/app/helper_model/field_set.js +17 -17
  76. package/lib/app/helper_model/model.js +203 -124
  77. package/lib/app/helper_model/remote_data_provider.js +2 -2
  78. package/lib/app/helper_validator/00_validator.js +16 -16
  79. package/lib/app/helper_validator/not_empty_validator.js +9 -9
  80. package/lib/app/model/00-default_app_model.js +18 -0
  81. package/lib/app/model/05-system_model.js +27 -0
  82. package/lib/app/model/{alchemy_migration_model.js → system_migration_model.js} +4 -4
  83. package/lib/app/model/system_setting_model.js +154 -0
  84. package/lib/app/model/{alchemy_task_history_model.js → system_task_history_model.js} +7 -7
  85. package/lib/app/model/{alchemy_task_model.js → system_task_model.js} +11 -11
  86. package/lib/bootstrap.js +22 -312
  87. package/lib/class/accumulator.js +5 -5
  88. package/lib/class/behaviour.js +5 -5
  89. package/lib/class/component.js +3 -3
  90. package/lib/class/conduit.js +203 -163
  91. package/lib/class/controller.js +42 -42
  92. package/lib/class/datasource.js +74 -79
  93. package/lib/class/document.js +74 -95
  94. package/lib/class/document_list.js +5 -5
  95. package/lib/class/element.js +17 -17
  96. package/lib/class/error.js +3 -3
  97. package/lib/class/field.js +169 -91
  98. package/lib/class/field_value.js +6 -6
  99. package/lib/class/helper.js +3 -3
  100. package/lib/class/inode.js +17 -17
  101. package/lib/class/inode_dir.js +12 -12
  102. package/lib/class/inode_file.js +50 -25
  103. package/lib/class/inode_list.js +4 -4
  104. package/lib/class/migration.js +4 -4
  105. package/lib/class/model.js +182 -168
  106. package/lib/class/path_definition.js +22 -22
  107. package/lib/class/path_evaluator.js +5 -5
  108. package/lib/class/path_param_definition.js +7 -7
  109. package/lib/class/plugin.js +312 -0
  110. package/lib/class/postponement.js +29 -29
  111. package/lib/class/reciprocal.js +8 -8
  112. package/lib/class/route.js +33 -33
  113. package/lib/class/router.js +73 -73
  114. package/lib/class/schema.js +21 -21
  115. package/lib/class/schema_client.js +73 -67
  116. package/lib/class/session.js +63 -29
  117. package/lib/class/session_scene.js +4 -4
  118. package/lib/class/sitemap.js +16 -16
  119. package/lib/class/task.js +39 -39
  120. package/lib/class/task_service.js +43 -47
  121. package/lib/{init → core}/alchemy.js +413 -374
  122. package/lib/{init/functions.js → core/alchemy_functions.js} +171 -108
  123. package/lib/core/alchemy_load_functions.js +715 -0
  124. package/lib/core/base.js +50 -62
  125. package/lib/core/client_alchemy.js +144 -152
  126. package/lib/core/client_base.js +39 -52
  127. package/lib/core/discovery.js +16 -18
  128. package/lib/core/middleware.js +54 -43
  129. package/lib/core/{routing.js → prefix.js} +14 -16
  130. package/lib/core/setting.js +1684 -0
  131. package/lib/core/stage.js +758 -0
  132. package/lib/scripts/create_constants.js +119 -0
  133. package/lib/{init/languages.js → scripts/create_languages.js} +5 -5
  134. package/lib/scripts/create_settings.js +449 -0
  135. package/lib/scripts/create_shared_constants.js +95 -0
  136. package/lib/scripts/create_stages.js +55 -0
  137. package/lib/scripts/init_alchemy.js +51 -0
  138. package/lib/{init/requirements.js → scripts/preload_modules.js} +15 -2
  139. package/lib/scripts/setup_devwatch.js +238 -0
  140. package/lib/stages/00-load_core.js +342 -0
  141. package/lib/stages/05-load_app.js +57 -0
  142. package/lib/stages/10-datasource.js +61 -0
  143. package/lib/stages/15-tasks.js +27 -0
  144. package/lib/stages/20-settings.js +68 -0
  145. package/lib/stages/50-routes.js +218 -0
  146. package/lib/stages/90-server.js +347 -0
  147. package/package.json +5 -7
  148. package/lib/app/helper_model/criteria.js +0 -2294
  149. package/lib/app/helper_model/db_query.js +0 -1488
  150. package/lib/app/routes.js +0 -11
  151. package/lib/core/socket.js +0 -171
  152. package/lib/init/constants.js +0 -158
  153. package/lib/init/devwatch.js +0 -238
  154. package/lib/init/load_functions.js +0 -973
  155. package/lib/stages.js +0 -513
@@ -0,0 +1,347 @@
1
+ const libfs = require('fs');
2
+
3
+ /**
4
+ * The "server" stage
5
+ *
6
+ * @author Jelle De Loecker <jelle@elevenways.be>
7
+ * @since 1.4.0
8
+ * @version 1.4.0
9
+ *
10
+ * @type {Alchemy.Stages.Stage}
11
+ */
12
+ const server = STAGES.createStage('server');
13
+
14
+ /**
15
+ * The "server.create_http" stage:
16
+ * Create the server instance
17
+ *
18
+ * @author Jelle De Loecker <jelle@elevenways.be>
19
+ * @since 1.4.0
20
+ * @version 1.4.0
21
+ *
22
+ * @type {Alchemy.Stages.Stage}
23
+ */
24
+ const create_http = server.createStage('create_http', () => {
25
+
26
+ // Create the server
27
+ alchemy.server = alchemy.modules.http.createServer();
28
+
29
+ // Listen for requests
30
+ alchemy.server.on('request', (request, response) => Router.resolve(request, response));
31
+ });
32
+
33
+ /**
34
+ * The "server.websocket" stage:
35
+ * Setup the websocket system
36
+ *
37
+ * @author Jelle De Loecker <jelle@elevenways.be>
38
+ * @since 1.4.0
39
+ * @version 1.4.0
40
+ *
41
+ * @type {Alchemy.Stages.Stage}
42
+ */
43
+ const websocket = server.createStage('websocket', () => {
44
+
45
+ let msgpack_parser,
46
+ iostream,
47
+ types = alchemy.shared('Socket.types'),
48
+ path = alchemy.use('path'),
49
+ fs = alchemy.use('fs');
50
+
51
+ const websockets = alchemy.settings.network.use_websockets;
52
+
53
+ if (!websockets || websockets === 'never') {
54
+ log.info('Websockets have been disabled');
55
+ return;
56
+ } else {
57
+ if (websockets == 'optional') {
58
+ log.info('Websockets have been enabled optionally');
59
+ } else {
60
+ log.info('Websockets have been enabled, clients will automatically connect');
61
+ }
62
+ }
63
+
64
+ const socket_io = alchemy.use('socket.io');
65
+
66
+ if (!socket_io) {
67
+ return log.error('Could not load socket.io!');
68
+ }
69
+
70
+ iostream = alchemy.use('socket.io-stream');
71
+ msgpack_parser = alchemy.use('socket.io-msgpack-parser');
72
+
73
+ let socket_io_options = {
74
+ serveClient : false,
75
+ };
76
+
77
+ if (msgpack_parser) {
78
+ socket_io_options.parser = msgpack_parser;
79
+ }
80
+
81
+ if (!alchemy.server) {
82
+ throw new Error('No server has been created yet, unable to start socket.io');
83
+ }
84
+
85
+ // Create the Socket.io listener
86
+ alchemy.io = socket_io(alchemy.server, socket_io_options);
87
+
88
+ // Get the core client path
89
+ let client_path = alchemy.findModule('socket.io-client').module_dir;
90
+
91
+ if (msgpack_parser) {
92
+ client_path = path.join(client_path, 'dist', 'socket.io.msgpack.min.js');
93
+ } else {
94
+ client_path = path.join(client_path, 'dist', 'socket.io.min.js');
95
+ }
96
+
97
+ // Get the stream client path
98
+ let stream_path = path.dirname(alchemy.findModule('@11ways/socket.io-stream').module_path);
99
+ stream_path = path.resolve(stream_path, 'socket.io-stream.js');
100
+
101
+ // Serve the socket io core file
102
+ Router.use('/scripts/socket.io.js', function getSocketIo(req, res, next) {
103
+ alchemy.minifyScript(client_path, function gotMinifiedPath(err, mpath) {
104
+ req.conduit.serveFile(mpath || client_path);
105
+ });
106
+ });
107
+
108
+ // Serve the socket io stream file
109
+ Router.use('/scripts/socket.io-stream.js', function getSocketStream(req, res, next) {
110
+ alchemy.minifyScript(stream_path, function gotMinifiedPath(err, mpath) {
111
+ req.conduit.serveFile(mpath || stream_path);
112
+ });
113
+ });
114
+
115
+ /**
116
+ * Handle connections
117
+ *
118
+ * @author Jelle De Loecker <jelle@elevenways.be>
119
+ * @since 0.0.1
120
+ * @version 0.2.0
121
+ */
122
+ alchemy.io.sockets.on('connection', function onConnect(socket){
123
+
124
+ var syncs = {},
125
+ latencies = [],
126
+ latency_avg = 2,
127
+ offset = 0;
128
+
129
+ socket.on('timesync', function gotTimesyncRequest(data) {
130
+
131
+ var received = Date.now(),
132
+ latency;
133
+
134
+ // This is the initial request
135
+ if (data.count == null) {
136
+ data.count = 0;
137
+ data.latency_trip = 0;
138
+
139
+ // Reset the latencies array
140
+ latencies.length = 0;
141
+ }
142
+
143
+ // Do 8 round trips to determine latency
144
+ if (data.latency_trip <= 8) {
145
+
146
+ if (data.last_sent) {
147
+
148
+ // Latency is the timestamp when we received the response
149
+ // minus the timestamp when we sent the request
150
+ latency = received - data.last_sent;
151
+ latencies.push(latency);
152
+ }
153
+
154
+ // Wait before responding
155
+ setTimeout(function waitForLatency() {
156
+ data.last_sent = Date.now();
157
+ socket.emit('timesync', data);
158
+ }, 100 + (data.latency_trip * 150));
159
+
160
+ data.latency_trip++;
161
+ } else if (data.latency_trip) {
162
+
163
+ latency_avg = ~~(Math.median(latencies) / 2);
164
+ offset = (received - latency_avg) - data.client_time;
165
+
166
+ socket.emit('timesync', {offset: offset, latency: latency_avg});
167
+ }
168
+ });
169
+
170
+ // Wait for the announcement
171
+ socket.once('announce', function gotAnnouncement(data) {
172
+
173
+ var SocketClass,
174
+ class_name;
175
+
176
+ // Try getting the socket class of this type
177
+ if (typeof data.type == 'string') {
178
+ class_name = data.type.classify();
179
+ SocketClass = Classes.Alchemy.Conduit[class_name + 'Socket'];
180
+ }
181
+
182
+ // If no socket class was found get the regular class
183
+ if (!SocketClass) {
184
+ SocketClass = Classes.Alchemy.Conduit.Socket;
185
+ }
186
+
187
+ new SocketClass(socket, data);
188
+ });
189
+ });
190
+
191
+ });
192
+
193
+ /**
194
+ * The "server.warn_debug" stage:
195
+ * Start the server
196
+ *
197
+ * @author Jelle De Loecker <jelle@elevenways.be>
198
+ * @since 1.4.0
199
+ * @version 1.4.0
200
+ *
201
+ * @type {Alchemy.Stages.Stage}
202
+ */
203
+ const warn_debug = server.createStage('warn_debug', () => {
204
+ // See if we want to enable debugging
205
+ if (alchemy.getSetting('debugging.debug')) {
206
+ log.info('Hawkejs debugging has been ENABLED');
207
+ alchemy.hawkejs._debug = true;
208
+ }
209
+ });
210
+
211
+ /**
212
+ * The "server.start" stage:
213
+ * Start the server
214
+ *
215
+ * @author Jelle De Loecker <jelle@elevenways.be>
216
+ * @since 1.4.0
217
+ * @version 1.4.0
218
+ *
219
+ * @type {Alchemy.Stages.Stage}
220
+ */
221
+ const start = server.createStage('start', () => {
222
+
223
+ if (process.send) {
224
+ // Create a connection to the hohenheim parent
225
+ alchemy.hohenheim = new Classes.Alchemy.Reciprocal(process, 'hohenheim');
226
+ }
227
+
228
+ if (alchemy.getSetting('client_mode')) {
229
+ return server.launch(true);
230
+ }
231
+
232
+ alchemy.exposeDefaultStaticVariables();
233
+
234
+ let port = alchemy.getSetting('network.port'),
235
+ socket = alchemy.getSetting('network.socket');
236
+
237
+ // If a falsy (non-null) port is given (and no socket file), do nothing
238
+ if (!port && port !== null && !socket) {
239
+ return;
240
+ }
241
+
242
+ let listen_target;
243
+
244
+ // Are we using a socket file?
245
+ if (typeof socket == 'string') {
246
+ let stat;
247
+
248
+ try {
249
+ stat = libfs.statSync(socket);
250
+ } catch (err) {
251
+ // File not found, so it's safe to use
252
+ console.log(err);
253
+ }
254
+
255
+ if (stat) {
256
+ log.info('Found existing socketfile at', socket, ', need to remove it');
257
+ libfs.unlinkSync(socket);
258
+ }
259
+
260
+ listen_target = socket;
261
+ }
262
+
263
+ if (!listen_target && port) {
264
+ listen_target = port;
265
+ }
266
+
267
+ // Start listening on the given port
268
+ // The actual `requests` listener is defined in the 'http' stage
269
+ alchemy.server.listen(listen_target, function areListening(){
270
+
271
+ let address = alchemy.server.address();
272
+ let url = alchemy.getSetting('network.main_url');
273
+
274
+ if (typeof address == 'string') {
275
+ alchemy.setSetting('network.socket', address);
276
+ log.info('HTTP server listening on socket file', address);
277
+
278
+ alchemy.executeSetting('network.socketfile_chmod');
279
+ } else {
280
+ // Get the actual server port
281
+ alchemy.setSetting('network.port', address.port);
282
+ log.info('HTTP server listening on port', address.port);
283
+
284
+ if (!url) {
285
+ url = 'http://localhost:' + address.port;
286
+ }
287
+ }
288
+
289
+ if (url) {
290
+ let pretty_url = alchemy.colors.bg.getRgb(1, 0, 1) + alchemy.colors.fg.getRgb(5, 3, 0) + ' ' + url + ' ' + alchemy.colors.reset;
291
+ log.info('Served at »»', pretty_url, '««');
292
+ }
293
+
294
+ // If this process is a child, tell the parent we're ready
295
+ if (process.send) {
296
+ log.info('Letting the parent know we\'re ready!');
297
+ process.send({alchemy: {ready: true}});
298
+
299
+ process.on('disconnect', function onParentExit() {
300
+ log.info('Parent exited, closing down');
301
+ process.exit();
302
+ });
303
+ }
304
+
305
+ server.launch(true);
306
+ });
307
+
308
+ // Listen for errors (like EADDRINUSE)
309
+ alchemy.server.on('error', function onError(err) {
310
+
311
+ if (process.send) {
312
+ process.send({alchemy: {error: err}});
313
+ return process.exit();
314
+ }
315
+
316
+ throw err;
317
+ });
318
+ });
319
+
320
+ /**
321
+ * The "server.listening" stage:
322
+ * At this point the server is listening for incoming requests
323
+ *
324
+ * @author Jelle De Loecker <jelle@elevenways.be>
325
+ * @since 1.4.0
326
+ * @version 1.4.0
327
+ *
328
+ * @type {Alchemy.Stages.Stage}
329
+ */
330
+ const listening = server.createStage('listening', () => {
331
+
332
+ });
333
+
334
+ STAGES.afterStages(['datasource', 'server.websocket'], () => {
335
+
336
+ // Need to wait for all classes to load
337
+ Blast.loaded(function hasLoaded() {
338
+ server.launch([
339
+ 'create_http',
340
+ 'warn_debug',
341
+ 'start'
342
+ ]);
343
+
344
+ // Indicate the server has started
345
+ alchemy.started = true;
346
+ });
347
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alchemymvc",
3
3
  "description": "MVC framework for Node.js",
4
- "version": "1.3.21",
4
+ "version": "1.4.0-alpha.1",
5
5
  "author": "Jelle De Loecker <jelle@elevenways.be>",
6
6
  "keywords": [
7
7
  "alchemy",
@@ -22,20 +22,19 @@
22
22
  "chokidar" : "~3.5.3",
23
23
  "formidable" : "~3.5.1",
24
24
  "graceful-fs" : "~4.2.11",
25
- "hawkejs" : "~2.3.15",
25
+ "hawkejs" : "~2.3.16",
26
26
  "jsondiffpatch" : "~0.5.0",
27
27
  "mime" : "~3.0.0",
28
28
  "minimist" : "~1.2.5",
29
29
  "mkdirp" : "~3.0.1",
30
- "mmmagic" : "~0.5.3",
30
+ "@picturae/mmmagic": "0.5.3",
31
31
  "mongodb" : "~6.1.0",
32
32
  "ncp" : "~2.0.0",
33
33
  "postcss" : "~8.4.31",
34
- "protoblast" : "~0.8.15",
34
+ "protoblast" : "~0.9.0",
35
35
  "semver" : "~7.5.4",
36
36
  "socket.io" : "~4.7.2",
37
37
  "@11ways/socket.io-stream" : "~0.9.2",
38
- "sputnik" : "~0.1.0",
39
38
  "terser" : "~5.21.0",
40
39
  "toobusy-js" : "~0.5.1",
41
40
  "useragent" : "~2.3.0"
@@ -45,7 +44,7 @@
45
44
  "index.js"
46
45
  ],
47
46
  "optionalDependencies": {
48
- "janeway" : "~0.4.2",
47
+ "janeway" : "~0.4.4",
49
48
  "less" : "~4.2.0",
50
49
  "sass" : "~1.68.0",
51
50
  "sass-embedded" : "~1.67.0",
@@ -63,7 +62,6 @@
63
62
  "source-map" : "~0.7.3"
64
63
  },
65
64
  "scripts": {
66
- "appveyor" : "mocha --exit --reporter spec --bail --timeout 20000 --file test/00-init.js",
67
65
  "coverage" : "nyc --reporter=text --reporter=lcov mocha --exit --timeout 200000 --bail --file test/00-init.js",
68
66
  "report-coverage" : "nyc report --reporter=lcov && cat ./coverage/lcov.info | codecov",
69
67
  "test" : "mocha --exit --reporter spec --bail --timeout 50000 --file test/00-init.js"