alchemymvc 1.4.0 → 1.4.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.
- package/lib/app/behaviour/revision_behaviour.js +1 -1
- package/lib/app/behaviour/sluggable_behaviour.js +2 -2
- package/lib/app/datasource/mongo_datasource.js +19 -3
- package/lib/app/helper/cron.js +2 -2
- package/lib/app/helper_datasource/00-nosql_datasource.js +9 -3
- package/lib/app/helper_datasource/05-fallback_datasource.js +2 -0
- package/lib/app/helper_datasource/idb_datasource.js +7 -5
- package/lib/app/helper_datasource/remote_datasource.js +1 -1
- package/lib/app/helper_field/password_field.js +4 -2
- package/lib/app/helper_field/schema_field.js +3 -2
- package/lib/app/helper_model/00-base_criteria.js +14 -0
- package/lib/app/helper_model/05-criteria_expressions.js +30 -7
- package/lib/app/helper_model/10-model_criteria.js +47 -8
- package/lib/app/helper_model/document.js +11 -2
- package/lib/app/helper_model/model.js +6 -3
- package/lib/class/conduit.js +5 -2
- package/lib/class/controller.js +1 -0
- package/lib/class/document.js +39 -11
- package/lib/class/import_stream_parser.js +299 -0
- package/lib/class/migration.js +5 -2
- package/lib/class/model.js +10 -140
- package/lib/class/plugin.js +32 -3
- package/lib/class/router.js +24 -27
- package/lib/class/schema_client.js +38 -7
- package/lib/class/sitemap.js +2 -2
- package/lib/core/alchemy.js +110 -162
- package/lib/core/alchemy_load_functions.js +64 -5
- package/lib/core/base.js +2 -2
- package/lib/core/middleware.js +31 -5
- package/lib/core/setting.js +12 -9
- package/lib/scripts/create_constants.js +5 -1
- package/lib/stages/00-load_core.js +8 -2
- package/lib/testing/browser.js +1164 -0
- package/lib/testing/harness.js +840 -0
- package/package.json +10 -3
- package/testing/browser.js +27 -0
- package/testing.js +37 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
const libstream = require('stream');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The ImportStreamParser class:
|
|
5
|
+
* Handles parsing of Alchemy's binary import stream format.
|
|
6
|
+
*
|
|
7
|
+
* Stream format:
|
|
8
|
+
* - 0x01 [1-byte size] [model name]: Model header
|
|
9
|
+
* - 0x02 [4-byte size BE] [document data]: Document data
|
|
10
|
+
* - 0xFF [4-byte size BE] [extra data]: Extra import data for current document
|
|
11
|
+
*
|
|
12
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
13
|
+
* @since 1.4.1
|
|
14
|
+
* @version 1.4.1
|
|
15
|
+
*/
|
|
16
|
+
const ImportStreamParser = Function.inherits('Alchemy.Base', function ImportStreamParser(input, options) {
|
|
17
|
+
|
|
18
|
+
// The input stream to parse
|
|
19
|
+
this.input = input;
|
|
20
|
+
|
|
21
|
+
// Options
|
|
22
|
+
this.options = options || {};
|
|
23
|
+
|
|
24
|
+
// The model resolver function - receives model name, returns Model instance
|
|
25
|
+
// Can throw an error to abort parsing
|
|
26
|
+
this.model_resolver = null;
|
|
27
|
+
|
|
28
|
+
// State machine variables
|
|
29
|
+
this.current_type = null;
|
|
30
|
+
this.extra_stream = null;
|
|
31
|
+
this.stopped = false;
|
|
32
|
+
this.paused = false;
|
|
33
|
+
this.buffer = null;
|
|
34
|
+
this.model = null;
|
|
35
|
+
this.value = null;
|
|
36
|
+
this.seen = 0;
|
|
37
|
+
this.left = 0;
|
|
38
|
+
this.size = 0;
|
|
39
|
+
this.doc = null;
|
|
40
|
+
|
|
41
|
+
// Track stream end and pending imports
|
|
42
|
+
this.stream_ended = false;
|
|
43
|
+
this.pending_import = false;
|
|
44
|
+
|
|
45
|
+
// The pledge that resolves when parsing is complete
|
|
46
|
+
this.pledge = new Pledge();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Set the model resolver function.
|
|
51
|
+
* Called when a 0x01 model header is encountered.
|
|
52
|
+
*
|
|
53
|
+
* The resolver receives (model_name, current_model) and should:
|
|
54
|
+
* - Return a Model instance to use for subsequent documents
|
|
55
|
+
* - Throw an error to abort parsing
|
|
56
|
+
*
|
|
57
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
58
|
+
* @since 1.4.1
|
|
59
|
+
* @version 1.4.1
|
|
60
|
+
*
|
|
61
|
+
* @param {Function} resolver (model_name, current_model) => Model
|
|
62
|
+
*/
|
|
63
|
+
ImportStreamParser.setMethod(function setModelResolver(resolver) {
|
|
64
|
+
this.model_resolver = resolver;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Start parsing the input stream.
|
|
69
|
+
* Returns a Pledge that resolves when parsing is complete.
|
|
70
|
+
*
|
|
71
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
72
|
+
* @since 1.4.1
|
|
73
|
+
* @version 1.4.1
|
|
74
|
+
*
|
|
75
|
+
* @return {Pledge}
|
|
76
|
+
*/
|
|
77
|
+
ImportStreamParser.setMethod(function parse() {
|
|
78
|
+
|
|
79
|
+
let that = this;
|
|
80
|
+
|
|
81
|
+
if (!this.model_resolver) {
|
|
82
|
+
return Pledge.reject(new Error('No model resolver has been set'));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.input.on('data', function onData(data) {
|
|
86
|
+
|
|
87
|
+
if (that.stopped) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (that.buffer) {
|
|
92
|
+
that.buffer = Buffer.concat([that.buffer, data]);
|
|
93
|
+
} else {
|
|
94
|
+
that.buffer = data;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
that.handleBuffer();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
this.input.on('end', function onEnd() {
|
|
101
|
+
that.stream_ended = true;
|
|
102
|
+
|
|
103
|
+
// Only resolve if we're not in the middle of importing a document
|
|
104
|
+
if (!that.stopped && !that.pending_import) {
|
|
105
|
+
that.pledge.resolve();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this.input.on('error', function onError(err) {
|
|
110
|
+
that.stopped = true;
|
|
111
|
+
that.pledge.reject(err);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return this.pledge;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Handle the current buffer data.
|
|
119
|
+
* Implements the state machine for parsing packet headers.
|
|
120
|
+
*
|
|
121
|
+
* State machine:
|
|
122
|
+
* - current_type = null: waiting for a new packet header
|
|
123
|
+
* - current_type = 0x01/0x02/0xFF: header parsed, processing payload
|
|
124
|
+
*
|
|
125
|
+
* We must NOT set current_type until we have the FULL header,
|
|
126
|
+
* otherwise a TCP chunk boundary could leave us in an invalid state.
|
|
127
|
+
*
|
|
128
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
129
|
+
* @since 1.4.1
|
|
130
|
+
* @version 1.4.1
|
|
131
|
+
*/
|
|
132
|
+
ImportStreamParser.setMethod(function handleBuffer() {
|
|
133
|
+
|
|
134
|
+
if (this.paused) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!this.current_type) {
|
|
139
|
+
// Need at least 1 byte to peek at the marker type
|
|
140
|
+
if (this.buffer.length < 1) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let marker = this.buffer.readUInt8(0);
|
|
145
|
+
|
|
146
|
+
if (marker == 0x01) {
|
|
147
|
+
// Type 0x01: 1-byte marker + 1-byte size = 2 bytes header
|
|
148
|
+
if (this.buffer.length < 2) {
|
|
149
|
+
return; // Wait for more data (don't consume the marker yet)
|
|
150
|
+
}
|
|
151
|
+
this.current_type = marker;
|
|
152
|
+
this.size = this.buffer.readUInt8(1);
|
|
153
|
+
this.buffer = this.buffer.slice(2);
|
|
154
|
+
} else if (marker == 0x02) {
|
|
155
|
+
// Type 0x02: 1-byte marker + 4-byte size = 5 bytes header
|
|
156
|
+
if (this.buffer.length < 5) {
|
|
157
|
+
return; // Wait for more data (don't consume the marker yet)
|
|
158
|
+
}
|
|
159
|
+
this.current_type = marker;
|
|
160
|
+
this.size = this.buffer.readUInt32BE(1);
|
|
161
|
+
this.buffer = this.buffer.slice(5);
|
|
162
|
+
} else if (marker == 0xFF) {
|
|
163
|
+
// Type 0xFF: 1-byte marker + 4-byte size = 5 bytes header
|
|
164
|
+
if (this.buffer.length < 5) {
|
|
165
|
+
return; // Wait for more data (don't consume the marker yet)
|
|
166
|
+
}
|
|
167
|
+
this.current_type = marker;
|
|
168
|
+
this.size = this.buffer.readUInt32BE(1);
|
|
169
|
+
this.buffer = this.buffer.slice(5);
|
|
170
|
+
this.seen = 0;
|
|
171
|
+
|
|
172
|
+
if (!this.doc) {
|
|
173
|
+
this.stopped = true;
|
|
174
|
+
this.pledge.reject(new Error('Found extra import data, but no active document'));
|
|
175
|
+
} else {
|
|
176
|
+
this.extra_stream = new libstream.PassThrough();
|
|
177
|
+
this.doc.extraImportFromStream(this.extra_stream);
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
// Unknown marker - this shouldn't happen in valid data
|
|
181
|
+
this.stopped = true;
|
|
182
|
+
this.pledge.reject(new Error('Unknown marker byte: 0x' + marker.toString(16)));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.handlePayload();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Handle the payload data after a header has been parsed.
|
|
192
|
+
*
|
|
193
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
194
|
+
* @since 1.4.1
|
|
195
|
+
* @version 1.4.1
|
|
196
|
+
*/
|
|
197
|
+
ImportStreamParser.setMethod(function handlePayload() {
|
|
198
|
+
|
|
199
|
+
let that = this;
|
|
200
|
+
|
|
201
|
+
// Handle extra data streaming (0xFF)
|
|
202
|
+
if (this.current_type == 0xFF) {
|
|
203
|
+
this.left = this.size - this.seen;
|
|
204
|
+
this.value = this.buffer.slice(0, this.left);
|
|
205
|
+
|
|
206
|
+
this.seen += this.value.length;
|
|
207
|
+
|
|
208
|
+
if (this.value.length == this.buffer.length) {
|
|
209
|
+
this.buffer = null;
|
|
210
|
+
} else if (this.value.length < this.buffer.length) {
|
|
211
|
+
this.buffer = this.buffer.slice(this.left);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
this.extra_stream.write(this.value);
|
|
215
|
+
|
|
216
|
+
if (this.value.length == this.left) {
|
|
217
|
+
this.extra_stream.end();
|
|
218
|
+
this.current_type = null;
|
|
219
|
+
|
|
220
|
+
if (this.buffer) {
|
|
221
|
+
this.handleBuffer();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Wait for full payload
|
|
229
|
+
if (this.buffer.length >= this.size) {
|
|
230
|
+
this.value = this.buffer.slice(0, this.size);
|
|
231
|
+
this.buffer = this.buffer.slice(this.size);
|
|
232
|
+
} else {
|
|
233
|
+
// Wait for next call
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Handle model header (0x01)
|
|
238
|
+
if (this.current_type == 0x01) {
|
|
239
|
+
let model_name = this.value.toString();
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
this.model = this.model_resolver(model_name, this.model);
|
|
243
|
+
this.doc = null;
|
|
244
|
+
} catch (err) {
|
|
245
|
+
this.stopped = true;
|
|
246
|
+
return this.pledge.reject(err);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!this.model) {
|
|
250
|
+
this.stopped = true;
|
|
251
|
+
return this.pledge.reject(new Error('Model resolver returned no model for "' + model_name + '"'));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
this.current_type = null;
|
|
255
|
+
this.size = 0;
|
|
256
|
+
}
|
|
257
|
+
// Handle document data (0x02)
|
|
258
|
+
else if (this.current_type == 0x02) {
|
|
259
|
+
this.doc = this.model.createDocument();
|
|
260
|
+
this.input.pause();
|
|
261
|
+
this.paused = true;
|
|
262
|
+
this.pending_import = true;
|
|
263
|
+
|
|
264
|
+
this.doc.importFromBuffer(this.value, this.options).done(function done(err, result) {
|
|
265
|
+
|
|
266
|
+
that.pending_import = false;
|
|
267
|
+
|
|
268
|
+
if (err) {
|
|
269
|
+
that.stopped = true;
|
|
270
|
+
return that.pledge.reject(err);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
that.current_type = null;
|
|
274
|
+
that.paused = false;
|
|
275
|
+
|
|
276
|
+
// Check if there's more data to process
|
|
277
|
+
if (that.buffer && that.buffer.length > 0) {
|
|
278
|
+
that.input.resume();
|
|
279
|
+
that.handleBuffer();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// No more data in buffer
|
|
284
|
+
that.input.resume();
|
|
285
|
+
|
|
286
|
+
// If the stream has ended and there's no more data, resolve
|
|
287
|
+
if (that.stream_ended && !that.stopped) {
|
|
288
|
+
that.pledge.resolve();
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Continue processing remaining buffer
|
|
296
|
+
if (this.buffer && this.buffer.length) {
|
|
297
|
+
this.handleBuffer();
|
|
298
|
+
}
|
|
299
|
+
});
|
package/lib/class/migration.js
CHANGED
|
@@ -20,7 +20,7 @@ const Migration = Function.inherits('Alchemy.Base', function Migration(document)
|
|
|
20
20
|
*
|
|
21
21
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
22
22
|
* @since 1.2.0
|
|
23
|
-
* @version 1.
|
|
23
|
+
* @version 1.4.1
|
|
24
24
|
*/
|
|
25
25
|
Migration.setStatic(async function start() {
|
|
26
26
|
|
|
@@ -32,7 +32,10 @@ Migration.setStatic(async function start() {
|
|
|
32
32
|
|
|
33
33
|
await dir.loadContents();
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// Sort entries alphabetically so numbered prefixes (001_, 002_) run in order
|
|
36
|
+
let entries = [...dir].sort((a, b) => a.name.localeCompare(b.name));
|
|
37
|
+
|
|
38
|
+
for (let entry of entries) {
|
|
36
39
|
|
|
37
40
|
let name = entry.name.beforeLast('.js');
|
|
38
41
|
|
package/lib/class/model.js
CHANGED
|
@@ -1590,7 +1590,7 @@ Model.setMethod(function exportToStream(output, options) {
|
|
|
1590
1590
|
*
|
|
1591
1591
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1592
1592
|
* @since 1.0.5
|
|
1593
|
-
* @version 1.
|
|
1593
|
+
* @version 1.4.1
|
|
1594
1594
|
*
|
|
1595
1595
|
* @param {Stream} input
|
|
1596
1596
|
* @param {Object} options
|
|
@@ -1612,150 +1612,20 @@ Model.setMethod(function importFromStream(input, options) {
|
|
|
1612
1612
|
return Pledge.reject(new Error('No source input stream has been given'));
|
|
1613
1613
|
}
|
|
1614
1614
|
|
|
1615
|
-
let that = this
|
|
1616
|
-
|
|
1617
|
-
extra_stream,
|
|
1618
|
-
pledge = new Pledge(),
|
|
1619
|
-
stopped,
|
|
1620
|
-
paused,
|
|
1621
|
-
buffer,
|
|
1622
|
-
value,
|
|
1623
|
-
seen = 0,
|
|
1624
|
-
left,
|
|
1625
|
-
size,
|
|
1626
|
-
doc;
|
|
1627
|
-
|
|
1628
|
-
input.on('data', function onData(data) {
|
|
1629
|
-
|
|
1630
|
-
if (stopped) {
|
|
1631
|
-
return;
|
|
1632
|
-
}
|
|
1615
|
+
let that = this;
|
|
1616
|
+
let parser = new Classes.Alchemy.ImportStreamParser(input, options);
|
|
1633
1617
|
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1618
|
+
// Model resolver for single-model import:
|
|
1619
|
+
// Validates that the model name matches, returns this model instance
|
|
1620
|
+
parser.setModelResolver(function resolveModel(model_name, current_model) {
|
|
1621
|
+
if (model_name == that.model_name) {
|
|
1622
|
+
return that;
|
|
1638
1623
|
}
|
|
1639
1624
|
|
|
1640
|
-
|
|
1625
|
+
throw new Error('Model names do not match: expected "' + that.model_name + '", got "' + model_name + '"');
|
|
1641
1626
|
});
|
|
1642
1627
|
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
if (paused) {
|
|
1646
|
-
return;
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
if (!current_type && buffer.length < 2) {
|
|
1650
|
-
return;
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
if (!current_type) {
|
|
1654
|
-
current_type = buffer.readUInt8(0);
|
|
1655
|
-
|
|
1656
|
-
if (current_type == 0x01) {
|
|
1657
|
-
size = buffer.readUInt8(1);
|
|
1658
|
-
buffer = buffer.slice(2);
|
|
1659
|
-
} else if (current_type == 0x02 && buffer.length >= 5) {
|
|
1660
|
-
size = buffer.readUInt32BE(1);
|
|
1661
|
-
buffer = buffer.slice(5);
|
|
1662
|
-
} else if (current_type == 0xFF) {
|
|
1663
|
-
size = buffer.readUInt32BE(1);
|
|
1664
|
-
buffer = buffer.slice(5);
|
|
1665
|
-
seen = 0;
|
|
1666
|
-
|
|
1667
|
-
if (!doc) {
|
|
1668
|
-
stopped = true;
|
|
1669
|
-
pledge.reject(new Error('Found extra import data, but no active document'));
|
|
1670
|
-
} else {
|
|
1671
|
-
extra_stream = new require('stream').PassThrough();
|
|
1672
|
-
doc.extraImportFromStream(extra_stream);
|
|
1673
|
-
}
|
|
1674
|
-
} else {
|
|
1675
|
-
// Not enough data? Wait
|
|
1676
|
-
current_type = null;
|
|
1677
|
-
return;
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
handleRest();
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
function handleRest() {
|
|
1685
|
-
|
|
1686
|
-
if (current_type == 0xFF) {
|
|
1687
|
-
left = size - seen;
|
|
1688
|
-
value = buffer.slice(0, left);
|
|
1689
|
-
|
|
1690
|
-
seen += value.length;
|
|
1691
|
-
|
|
1692
|
-
if (value.length == buffer.length) {
|
|
1693
|
-
buffer = null;
|
|
1694
|
-
} else if (value.length < buffer.length) {
|
|
1695
|
-
buffer = buffer.slice(left);
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
extra_stream.write(value);
|
|
1699
|
-
|
|
1700
|
-
if (value.length == left) {
|
|
1701
|
-
extra_stream.end();
|
|
1702
|
-
current_type = null;
|
|
1703
|
-
|
|
1704
|
-
if (buffer) {
|
|
1705
|
-
handleBuffer();
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
return;
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
if (buffer.length >= size) {
|
|
1713
|
-
value = buffer.slice(0, size);
|
|
1714
|
-
buffer = buffer.slice(size);
|
|
1715
|
-
} else {
|
|
1716
|
-
// Wait for next call
|
|
1717
|
-
return;
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
if (current_type == 0x01) {
|
|
1721
|
-
value = value.toString();
|
|
1722
|
-
|
|
1723
|
-
if (value == that.model_name) {
|
|
1724
|
-
// Found name!
|
|
1725
|
-
current_type = null;
|
|
1726
|
-
size = 0;
|
|
1727
|
-
} else {
|
|
1728
|
-
stopped = true;
|
|
1729
|
-
return pledge.reject(new Error('Model names do not match'));
|
|
1730
|
-
}
|
|
1731
|
-
} else if (current_type == 0x02) {
|
|
1732
|
-
doc = that.createDocument();
|
|
1733
|
-
input.pause();
|
|
1734
|
-
paused = true;
|
|
1735
|
-
|
|
1736
|
-
doc.importFromBuffer(value).done(function done(err, result) {
|
|
1737
|
-
|
|
1738
|
-
if (err) {
|
|
1739
|
-
stopped = true;
|
|
1740
|
-
return pledge.reject(err);
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
current_type = null;
|
|
1744
|
-
paused = false;
|
|
1745
|
-
input.resume();
|
|
1746
|
-
|
|
1747
|
-
handleBuffer();
|
|
1748
|
-
});
|
|
1749
|
-
|
|
1750
|
-
return;
|
|
1751
|
-
}
|
|
1752
|
-
|
|
1753
|
-
if (buffer && buffer.length) {
|
|
1754
|
-
handleBuffer();
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
return pledge;
|
|
1628
|
+
return parser.parse();
|
|
1759
1629
|
});
|
|
1760
1630
|
|
|
1761
1631
|
/**
|
package/lib/class/plugin.js
CHANGED
|
@@ -41,7 +41,7 @@ const Plugin = Function.inherits('Alchemy.Base', function Plugin(name, path, def
|
|
|
41
41
|
*
|
|
42
42
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
43
43
|
* @since 1.4.0
|
|
44
|
-
* @version 1.4.
|
|
44
|
+
* @version 1.4.1
|
|
45
45
|
*
|
|
46
46
|
* @return {boolean}
|
|
47
47
|
*/
|
|
@@ -52,16 +52,45 @@ Plugin.setMethod(function doPreload() {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// Create settings from the `config/settings.js` file
|
|
55
|
-
|
|
55
|
+
// This can fail if the config is invalid
|
|
56
|
+
try {
|
|
57
|
+
this.loadSettingDefinitions();
|
|
58
|
+
} catch (err) {
|
|
59
|
+
this._handleLoadError('settings', err);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
56
62
|
|
|
57
63
|
// Load the bootstrap file
|
|
58
|
-
|
|
64
|
+
// This can fail due to syntax errors or runtime errors
|
|
65
|
+
try {
|
|
66
|
+
this.loadBootstrap();
|
|
67
|
+
} catch (err) {
|
|
68
|
+
this._handleLoadError('bootstrap', err);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this[FLAGS].preloaded = true;
|
|
59
73
|
|
|
60
74
|
PLUGINS_STAGE.addPostTask(() => {
|
|
61
75
|
return this.startPlugin();
|
|
62
76
|
});
|
|
63
77
|
});
|
|
64
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Handle a plugin loading error
|
|
81
|
+
* Plugin errors are fatal - the server cannot start with a broken plugin
|
|
82
|
+
*
|
|
83
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
84
|
+
* @since 1.4.1
|
|
85
|
+
* @version 1.4.1
|
|
86
|
+
*
|
|
87
|
+
* @param {string} phase Which phase failed (settings, bootstrap, start)
|
|
88
|
+
* @param {Error} err The error that occurred
|
|
89
|
+
*/
|
|
90
|
+
Plugin.setMethod(function _handleLoadError(phase, err) {
|
|
91
|
+
alchemy.handlePluginError(this.name, phase, err);
|
|
92
|
+
});
|
|
93
|
+
|
|
65
94
|
/**
|
|
66
95
|
* Do the rest of the plugin loading
|
|
67
96
|
*
|
package/lib/class/router.js
CHANGED
|
@@ -200,7 +200,7 @@ RouterClass.setMethod(function headerBypass(prefix) {
|
|
|
200
200
|
*
|
|
201
201
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
202
202
|
* @since 0.3.0
|
|
203
|
-
* @version
|
|
203
|
+
* @version 1.4.1
|
|
204
204
|
*/
|
|
205
205
|
RouterClass.setMethod(function getFullMount() {
|
|
206
206
|
|
|
@@ -215,6 +215,11 @@ RouterClass.setMethod(function getFullMount() {
|
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// Remove trailing slash (but keep single "/" for root)
|
|
219
|
+
if (result.length > 1 && result[result.length - 1] == '/') {
|
|
220
|
+
result = result.slice(0, -1);
|
|
221
|
+
}
|
|
222
|
+
|
|
218
223
|
return result;
|
|
219
224
|
});
|
|
220
225
|
|
|
@@ -1053,34 +1058,12 @@ RouterClass.setMethod('delete', function _delete(name, paths, fnc, options) {
|
|
|
1053
1058
|
return this.add(['delete'], name, paths, fnc, options);
|
|
1054
1059
|
});
|
|
1055
1060
|
|
|
1056
|
-
/**
|
|
1057
|
-
* Get an object of all the routes in this router and its children
|
|
1058
|
-
*
|
|
1059
|
-
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1060
|
-
* @since 0.2.0
|
|
1061
|
-
* @version 0.2.0
|
|
1062
|
-
*/
|
|
1063
|
-
RouterClass.setMethod(function getFullMount() {
|
|
1064
|
-
|
|
1065
|
-
var result = this.mount;
|
|
1066
|
-
|
|
1067
|
-
if (this.parent != null && this.parent.mount != '/') {
|
|
1068
|
-
result = this.parent.mount + result;
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
if (result[result.length-1] == '/') {
|
|
1072
|
-
result = result.slice(0, -1);
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
return result;
|
|
1076
|
-
});
|
|
1077
|
-
|
|
1078
1061
|
/**
|
|
1079
1062
|
* Get the full route object, for internal use
|
|
1080
1063
|
*
|
|
1081
1064
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1082
1065
|
* @since 0.5.0
|
|
1083
|
-
* @version
|
|
1066
|
+
* @version 1.4.1
|
|
1084
1067
|
*
|
|
1085
1068
|
* @param {Object} result Optional object to store sectioned results in
|
|
1086
1069
|
*
|
|
@@ -1111,7 +1094,14 @@ RouterClass.setMethod(function getFullRoutes(result) {
|
|
|
1111
1094
|
temp = {};
|
|
1112
1095
|
|
|
1113
1096
|
for (prefix in route.paths) {
|
|
1114
|
-
|
|
1097
|
+
let source = route.paths[prefix].source;
|
|
1098
|
+
|
|
1099
|
+
// Avoid double slashes when mount is '/' and path starts with '/'
|
|
1100
|
+
if (mount == '/' && source[0] == '/') {
|
|
1101
|
+
temp[prefix] = source;
|
|
1102
|
+
} else {
|
|
1103
|
+
temp[prefix] = mount + source;
|
|
1104
|
+
}
|
|
1115
1105
|
}
|
|
1116
1106
|
|
|
1117
1107
|
section[route.name] = route;
|
|
@@ -1175,7 +1165,7 @@ RouterClass.setMethod(function getOptions(result) {
|
|
|
1175
1165
|
*
|
|
1176
1166
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1177
1167
|
* @since 0.2.0
|
|
1178
|
-
* @version 1.
|
|
1168
|
+
* @version 1.4.1
|
|
1179
1169
|
*
|
|
1180
1170
|
* @param {Object} result Optional object to store sectioned results in
|
|
1181
1171
|
*
|
|
@@ -1206,7 +1196,14 @@ RouterClass.setMethod(function getRoutes(result) {
|
|
|
1206
1196
|
temp = {};
|
|
1207
1197
|
|
|
1208
1198
|
for (prefix in route.paths) {
|
|
1209
|
-
|
|
1199
|
+
let source = route.paths[prefix].source;
|
|
1200
|
+
|
|
1201
|
+
// Avoid double slashes when mount is '/' and path starts with '/'
|
|
1202
|
+
if (mount == '/' && source[0] == '/') {
|
|
1203
|
+
temp[prefix] = source;
|
|
1204
|
+
} else {
|
|
1205
|
+
temp[prefix] = mount + source;
|
|
1206
|
+
}
|
|
1210
1207
|
}
|
|
1211
1208
|
|
|
1212
1209
|
section[route.name] = {
|
|
@@ -1079,7 +1079,7 @@ Schema.setMethod(function getFieldNames() {
|
|
|
1079
1079
|
*
|
|
1080
1080
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1081
1081
|
* @since 0.2.0
|
|
1082
|
-
* @version 1.4.
|
|
1082
|
+
* @version 1.4.1
|
|
1083
1083
|
*
|
|
1084
1084
|
* @param {string|FieldType} _field_or_name Field name, or index name when using `fields` option
|
|
1085
1085
|
* @param {Object} options
|
|
@@ -1111,15 +1111,46 @@ Schema.setMethod(function addIndex(_field_or_name, _options) {
|
|
|
1111
1111
|
// When `fields` is provided, the first argument is the index name
|
|
1112
1112
|
options.name = _field_or_name;
|
|
1113
1113
|
|
|
1114
|
+
// Create the index entry
|
|
1115
|
+
if (this.indexes[options.name] == null) {
|
|
1116
|
+
this.indexes[options.name] = {
|
|
1117
|
+
fields: {},
|
|
1118
|
+
options: options
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// Add all fields to the index
|
|
1114
1123
|
for (let field_name of options.fields) {
|
|
1115
|
-
this.
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1124
|
+
let field = this.getField(field_name);
|
|
1125
|
+
|
|
1126
|
+
if (!field) {
|
|
1127
|
+
throw new Error('Could not find field "' + field_name + '" for compound index "' + options.name + '"');
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
let path = field.path;
|
|
1131
|
+
this.indexes[options.name].fields[path] = options.order || 1;
|
|
1132
|
+
this.index_fields[path] = options;
|
|
1121
1133
|
}
|
|
1122
1134
|
|
|
1135
|
+
// Now call ensureIndex once with the complete compound index
|
|
1136
|
+
const that = this;
|
|
1137
|
+
|
|
1138
|
+
that.getDatasource().done(function gotDs(err, datasource) {
|
|
1139
|
+
if (err) {
|
|
1140
|
+
throw err;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
if (datasource.supports('ensure_index') === false) {
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
datasource.ensureIndex(that.model_class, that.indexes[options.name], function ensuredIndex(err, result) {
|
|
1148
|
+
if (err) {
|
|
1149
|
+
alchemy.printLog('error', ['Error ensuring compound index', options.name, 'in model', that.model_name], {err: err});
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1123
1154
|
return;
|
|
1124
1155
|
}
|
|
1125
1156
|
|