alchemymvc 1.4.1 → 1.4.2
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/helper_datasource/05-fallback_datasource.js +8 -13
- package/lib/app/helper_field/time_field.js +1 -1
- package/lib/app/model/system_task_history_model.js +134 -0
- package/lib/class/datasource.js +14 -2
- package/lib/class/document.js +1 -1
- package/lib/class/inode_file.js +2 -0
- package/lib/class/model.js +2 -2
- package/lib/class/postponement.js +1 -1
- package/lib/class/router.js +2 -1
- package/lib/class/schema_client.js +1 -1
- package/lib/class/task.js +42 -24
- package/lib/core/prefix.js +1 -1
- package/lib/testing/harness.js +85 -3
- package/package.json +4 -4
|
@@ -158,7 +158,7 @@ Fallback.setMethod(function storeInUpperDatasource(model, data, options) {
|
|
|
158
158
|
*
|
|
159
159
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
160
160
|
* @since 1.1.0
|
|
161
|
-
* @version 1.4.
|
|
161
|
+
* @version 1.4.2
|
|
162
162
|
*
|
|
163
163
|
* @param {Alchemy.OperationalContext.ReadDocumentFromDatasource} context
|
|
164
164
|
*
|
|
@@ -176,7 +176,7 @@ Fallback.setMethod(function read(context) {
|
|
|
176
176
|
let lower_context = context.createChild();
|
|
177
177
|
lower_context.setCriteria(lower_criteria);
|
|
178
178
|
|
|
179
|
-
tasks.push(() => this.lower.read(
|
|
179
|
+
tasks.push(() => this.lower.read(lower_context));
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
let upper_criteria = criteria.clone();
|
|
@@ -195,7 +195,7 @@ Fallback.setMethod(function read(context) {
|
|
|
195
195
|
*
|
|
196
196
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
197
197
|
* @since 1.1.0
|
|
198
|
-
* @version 1.
|
|
198
|
+
* @version 1.4.2
|
|
199
199
|
*
|
|
200
200
|
* @param {Model} model
|
|
201
201
|
*
|
|
@@ -204,21 +204,16 @@ Fallback.setMethod(function read(context) {
|
|
|
204
204
|
Fallback.setMethod(function getRecordsToSync(model) {
|
|
205
205
|
|
|
206
206
|
var that = this,
|
|
207
|
-
pledge = new Pledge,
|
|
208
207
|
criteria = model.find();
|
|
209
208
|
|
|
210
209
|
criteria.where('_$needs_remote_save').equals(1);
|
|
211
210
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
pledge.resolve(records);
|
|
219
|
-
});
|
|
211
|
+
let context = new Classes.Alchemy.OperationalContext.ReadDocumentFromDatasource();
|
|
212
|
+
context.setDatasource(this.upper);
|
|
213
|
+
context.setModel(model);
|
|
214
|
+
context.setCriteria(criteria);
|
|
220
215
|
|
|
221
|
-
return
|
|
216
|
+
return this.upper.read(context);
|
|
222
217
|
});
|
|
223
218
|
|
|
224
219
|
/**
|
|
@@ -116,4 +116,138 @@ SystemTaskHistory.constitute(function chimeraConfig() {
|
|
|
116
116
|
route : 'Chimera.Editor#taskMonitor',
|
|
117
117
|
route_params : {task_history_id: '$pk'},
|
|
118
118
|
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Derive a single-word status from the recorded fields.
|
|
123
|
+
*
|
|
124
|
+
* - 'running': is_running is still true
|
|
125
|
+
* - 'failed': had_error is true
|
|
126
|
+
* - 'done': started AND ended cleanly
|
|
127
|
+
* - 'aborted': started but never ended (pre-1.4.2 zombies, or a process
|
|
128
|
+
* that died mid-task without unwinding the try/finally)
|
|
129
|
+
*
|
|
130
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
131
|
+
* @since 1.4.2
|
|
132
|
+
*
|
|
133
|
+
* @return {string}
|
|
134
|
+
*/
|
|
135
|
+
SystemTaskHistory.setDocumentMethod(function getStatus() {
|
|
136
|
+
|
|
137
|
+
if (this.had_error) {
|
|
138
|
+
return 'failed';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (this.is_running) {
|
|
142
|
+
return 'running';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (this.ended_at) {
|
|
146
|
+
return 'done';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (this.started_at) {
|
|
150
|
+
return 'aborted';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return 'scheduled';
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Return how long this run took (or has been running so far), in
|
|
158
|
+
* milliseconds. Null when nothing has started yet.
|
|
159
|
+
*
|
|
160
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
161
|
+
* @since 1.4.2
|
|
162
|
+
*
|
|
163
|
+
* @return {number|null}
|
|
164
|
+
*/
|
|
165
|
+
SystemTaskHistory.setDocumentMethod(function getDuration() {
|
|
166
|
+
|
|
167
|
+
if (!this.started_at) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let end = this.ended_at || (this.is_running ? new Date() : null);
|
|
172
|
+
|
|
173
|
+
if (!end) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return end.getTime() - this.started_at.getTime();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Query the most recent task runs with optional filters. Centralized
|
|
182
|
+
* here so that both Chimera-side dashboards and MCP / API consumers can
|
|
183
|
+
* use the same paginated query - avoids forking the criteria.
|
|
184
|
+
*
|
|
185
|
+
* Options:
|
|
186
|
+
* type {string} Filter by task type_path (e.g. 'arcana.task.sync_harvest_clients')
|
|
187
|
+
* status {string} 'running' | 'done' | 'failed' | 'aborted'
|
|
188
|
+
* since {Date} Earliest started_at to include
|
|
189
|
+
* limit {number} Page size (1-100, default 25)
|
|
190
|
+
* offset {number} Skip count
|
|
191
|
+
*
|
|
192
|
+
* Returns `{rows, total}` where `total` is the unpaginated count.
|
|
193
|
+
*
|
|
194
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
195
|
+
* @since 1.4.2
|
|
196
|
+
*
|
|
197
|
+
* @param {Object} [options]
|
|
198
|
+
*
|
|
199
|
+
* @return {Promise<{rows: DocumentList, total: number}>}
|
|
200
|
+
*/
|
|
201
|
+
SystemTaskHistory.setMethod(async function findRecent(options) {
|
|
202
|
+
|
|
203
|
+
options = options || {};
|
|
204
|
+
|
|
205
|
+
let crit = this.find();
|
|
206
|
+
|
|
207
|
+
if (options.type) {
|
|
208
|
+
crit.where('type').equals(options.type);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (options.status) {
|
|
212
|
+
switch (options.status) {
|
|
213
|
+
case 'running':
|
|
214
|
+
crit.where('is_running').equals(true);
|
|
215
|
+
break;
|
|
216
|
+
|
|
217
|
+
case 'failed':
|
|
218
|
+
crit.where('had_error').equals(true);
|
|
219
|
+
break;
|
|
220
|
+
|
|
221
|
+
case 'done':
|
|
222
|
+
crit.where('is_running').equals(false);
|
|
223
|
+
crit.where('had_error').not().equals(true);
|
|
224
|
+
crit.where('ended_at').exists(true);
|
|
225
|
+
break;
|
|
226
|
+
|
|
227
|
+
case 'aborted':
|
|
228
|
+
crit.where('is_running').equals(false);
|
|
229
|
+
crit.where('had_error').not().equals(true);
|
|
230
|
+
crit.where('ended_at').isEmpty();
|
|
231
|
+
crit.where('started_at').exists(true);
|
|
232
|
+
break;
|
|
233
|
+
|
|
234
|
+
default:
|
|
235
|
+
throw new Error('Unknown task-run status filter: ' + options.status);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (options.since) {
|
|
240
|
+
crit.where('started_at').gte(options.since);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let limit = Math.min(Math.max(options.limit || 25, 1), 100);
|
|
244
|
+
let offset = Math.max(options.offset || 0, 0);
|
|
245
|
+
|
|
246
|
+
crit.sort({started_at: -1});
|
|
247
|
+
crit.setOption('available', true);
|
|
248
|
+
crit.limit(limit);
|
|
249
|
+
if (offset > 0) crit.skip(offset);
|
|
250
|
+
|
|
251
|
+
let rows = await this.find('all', crit);
|
|
252
|
+
return {rows, total: rows.available || 0};
|
|
119
253
|
});
|
package/lib/class/datasource.js
CHANGED
|
@@ -508,7 +508,7 @@ Datasource.setMethod(function read(context) {
|
|
|
508
508
|
|
|
509
509
|
model.emit('reading_datasource', criteria);
|
|
510
510
|
|
|
511
|
-
|
|
511
|
+
let read_pledge = Pledge.Swift.waterfall(
|
|
512
512
|
that._read(context),
|
|
513
513
|
_result => {
|
|
514
514
|
|
|
@@ -548,6 +548,18 @@ Datasource.setMethod(function read(context) {
|
|
|
548
548
|
}
|
|
549
549
|
);
|
|
550
550
|
});
|
|
551
|
+
|
|
552
|
+
// If a cache_pledge was created, make sure it gets rejected on error
|
|
553
|
+
// so that subsequent queries with the same hash don't hang forever
|
|
554
|
+
if (cache_pledge) {
|
|
555
|
+
Pledge.Swift.done(read_pledge, (err) => {
|
|
556
|
+
if (err) {
|
|
557
|
+
cache_pledge.reject(err);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return read_pledge;
|
|
551
563
|
});
|
|
552
564
|
|
|
553
565
|
/**
|
|
@@ -603,7 +615,7 @@ Datasource.setMethod(function update(context) {
|
|
|
603
615
|
|
|
604
616
|
let result = Swift.waterfall(
|
|
605
617
|
// Convert the data into something the datasource will understand
|
|
606
|
-
this.toDatasource(context),
|
|
618
|
+
() => this.toDatasource(context),
|
|
607
619
|
|
|
608
620
|
// Actually create the data
|
|
609
621
|
converted_data => this._update(context.setConvertedData(converted_data)),
|
package/lib/class/document.js
CHANGED
package/lib/class/inode_file.js
CHANGED
package/lib/class/model.js
CHANGED
|
@@ -390,7 +390,7 @@ Model.setStatic(async function checkPathValue(value, name, field_name, conduit)
|
|
|
390
390
|
if (result) {
|
|
391
391
|
let found_value = result[field_name];
|
|
392
392
|
|
|
393
|
-
if (found_value != value && !Object.alike(value, found_value)) {
|
|
393
|
+
if (conduit && found_value != value && !Object.alike(value, found_value)) {
|
|
394
394
|
conduit.rewriteRequestRouteParam(name, found_value);
|
|
395
395
|
}
|
|
396
396
|
}
|
|
@@ -534,7 +534,7 @@ Model.setStatic(function getField(name) {
|
|
|
534
534
|
if (name.indexOf('.') > -1) {
|
|
535
535
|
split = name.split('.');
|
|
536
536
|
|
|
537
|
-
alias =
|
|
537
|
+
alias = split[0];
|
|
538
538
|
|
|
539
539
|
if (this.schema.associations[alias] == null) {
|
|
540
540
|
model = this;
|
package/lib/class/router.js
CHANGED
|
@@ -1184,7 +1184,7 @@ Schema.setMethod(function addIndex(_field_or_name, _options) {
|
|
|
1184
1184
|
}
|
|
1185
1185
|
};
|
|
1186
1186
|
|
|
1187
|
-
if (typeof options.order == '
|
|
1187
|
+
if (typeof options.order == 'string') {
|
|
1188
1188
|
if (options.order == 'asc') {
|
|
1189
1189
|
options.order = 1;
|
|
1190
1190
|
} else {
|
package/lib/class/task.js
CHANGED
|
@@ -371,33 +371,51 @@ Task.setMethod(async function start(payload) {
|
|
|
371
371
|
let result;
|
|
372
372
|
|
|
373
373
|
try {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
374
|
+
try {
|
|
375
|
+
result = await this.executor();
|
|
376
|
+
} catch (err) {
|
|
377
|
+
if (err == 'stopped') {
|
|
378
|
+
this.report('stopped', 'Stopped');
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Set the error
|
|
383
|
+
this.error = err;
|
|
384
|
+
|
|
385
|
+
// Report failed
|
|
386
|
+
let report = this.report('failed');
|
|
387
|
+
report.error = err;
|
|
388
|
+
|
|
389
|
+
// Surface the failure on the history document - without
|
|
390
|
+
// this, the row is left with `is_running: true` and no
|
|
391
|
+
// error info, looking identical to a still-running task.
|
|
392
|
+
document.had_error = true;
|
|
393
|
+
document.error_message = String(err && err.message || err);
|
|
394
|
+
document.error_stack = err && err.stack || null;
|
|
395
|
+
|
|
396
|
+
throw err;
|
|
379
397
|
}
|
|
380
398
|
|
|
381
|
-
//
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
399
|
+
// If the command hasn't been manually stopped,
|
|
400
|
+
// report it as done
|
|
401
|
+
if (!this.manual_stop_end) {
|
|
402
|
+
this.report('done');
|
|
403
|
+
}
|
|
404
|
+
} finally {
|
|
405
|
+
// Always close out the history document so it doesn't get
|
|
406
|
+
// stuck as a zombie "running forever" row.
|
|
407
|
+
document.ended_at = new Date();
|
|
408
|
+
document.is_running = false;
|
|
409
|
+
|
|
410
|
+
try {
|
|
411
|
+
await document.save();
|
|
412
|
+
} catch (save_err) {
|
|
413
|
+
// Don't let a history-save failure swallow the original
|
|
414
|
+
// executor error on its way out of the catch above.
|
|
415
|
+
alchemy.registerError(save_err);
|
|
416
|
+
}
|
|
395
417
|
}
|
|
396
418
|
|
|
397
|
-
document.ended_at = new Date();
|
|
398
|
-
document.is_running = false;
|
|
399
|
-
await document.save();
|
|
400
|
-
|
|
401
419
|
this[RUNNING_PLEDGE].resolve(result);
|
|
402
420
|
|
|
403
421
|
return result;
|
|
@@ -567,7 +585,7 @@ async function doAsyncLoopUntilNotBusy(max_tries) {
|
|
|
567
585
|
}
|
|
568
586
|
|
|
569
587
|
do {
|
|
570
|
-
|
|
588
|
+
log.info('Waiting for system to be less busy, try', tries);
|
|
571
589
|
await Pledge.after(500);
|
|
572
590
|
tries++;
|
|
573
591
|
} while (tries < max_tries && alchemy.isTooBusy());
|
package/lib/core/prefix.js
CHANGED
|
@@ -242,7 +242,7 @@ Connection.url = function url(connectionName, options) {
|
|
|
242
242
|
Connection.fill = function fill(url, params) {
|
|
243
243
|
|
|
244
244
|
if (params) {
|
|
245
|
-
for (paramName in params) {
|
|
245
|
+
for (let paramName in params) {
|
|
246
246
|
url = url.replace(':'+paramName, params[paramName]);
|
|
247
247
|
}
|
|
248
248
|
}
|
package/lib/testing/harness.js
CHANGED
|
@@ -55,6 +55,12 @@ const TestHarness = Function.inherits('Informer', 'Alchemy.Testing', function Te
|
|
|
55
55
|
// Use mongo-unit for in-memory MongoDB (default: true)
|
|
56
56
|
use_mongo_unit: true,
|
|
57
57
|
|
|
58
|
+
// Extra options forwarded to mongo-unit's start() (e.g. `version`
|
|
59
|
+
// to pin the mongod binary, or `dbpath` to point at a tmpfs dir).
|
|
60
|
+
// `storageEngine` defaults to 'wiredTiger' in startMongo - see why
|
|
61
|
+
// there.
|
|
62
|
+
mongo_unit_options: {},
|
|
63
|
+
|
|
58
64
|
// Server port (default: random between 3470-3570)
|
|
59
65
|
port: 3470 + Math.floor(Math.random() * 100),
|
|
60
66
|
|
|
@@ -243,10 +249,21 @@ TestHarness.setMethod(async function startMongo() {
|
|
|
243
249
|
}
|
|
244
250
|
|
|
245
251
|
let MongoUnit = this.getMongoUnit();
|
|
246
|
-
|
|
252
|
+
|
|
253
|
+
// mongo-unit defaults its standalone storage engine to
|
|
254
|
+
// `ephemeralForTest`, which was removed in MongoDB 6.0 (and
|
|
255
|
+
// `inMemory` is Enterprise-only). Force `wiredTiger` so a modern
|
|
256
|
+
// mongod binary actually boots. Callers can still override it (or
|
|
257
|
+
// pin `version` / set `dbpath`) via `mongo_unit_options`.
|
|
258
|
+
let mongo_opts = Object.assign({
|
|
259
|
+
verbose : false,
|
|
260
|
+
storageEngine : 'wiredTiger',
|
|
261
|
+
}, this.options.mongo_unit_options);
|
|
262
|
+
|
|
263
|
+
this._mongo_uri = await MongoUnit.start(mongo_opts);
|
|
247
264
|
|
|
248
265
|
if (!this._mongo_uri) {
|
|
249
|
-
throw new Error('Failed to start mongo-unit');
|
|
266
|
+
throw new Error('Failed to start mongo-unit (no URI returned)');
|
|
250
267
|
}
|
|
251
268
|
|
|
252
269
|
return this._mongo_uri;
|
|
@@ -278,6 +295,15 @@ TestHarness.setMethod(function startServer() {
|
|
|
278
295
|
STAGES.getStage('datasource').addPostTask(() => {
|
|
279
296
|
Datasource.create('mongo', 'default', { uri: this._mongo_uri });
|
|
280
297
|
});
|
|
298
|
+
} else if (this.options.use_mongo_unit) {
|
|
299
|
+
// mongo-unit was requested but never produced a URI (startMongo
|
|
300
|
+
// failed or wasn't called). Refuse to start - otherwise the app
|
|
301
|
+
// silently falls back to whatever default datasource is
|
|
302
|
+
// configured, which can be a REAL database. That fallback is how
|
|
303
|
+
// a broken in-memory mongo ends up polluting (and reading stale
|
|
304
|
+
// data from) a live dev DB. Fail loud instead.
|
|
305
|
+
return reject(new Error('TestHarness: use_mongo_unit is enabled but no mongo URI is available '
|
|
306
|
+
+ '(did startMongo() fail?). Refusing to start the server to avoid connecting to a non-test database.'));
|
|
281
307
|
}
|
|
282
308
|
|
|
283
309
|
// Register additional module search paths
|
|
@@ -392,7 +418,15 @@ TestHarness.setMethod(async function stop() {
|
|
|
392
418
|
|
|
393
419
|
// Stop mongo-unit
|
|
394
420
|
if (this._mongo_unit && this._mongo_uri) {
|
|
395
|
-
|
|
421
|
+
|
|
422
|
+
// mongodb-memory-server kills a standalone mongod with SIGINT (then
|
|
423
|
+
// SIGKILL) rather than a clean admin shutdown. Under some mongod
|
|
424
|
+
// builds that triggers a coredump on the way down. Ask mongod to
|
|
425
|
+
// shut down cleanly first, so the subsequent signal hits an already
|
|
426
|
+
// gone process and short-circuits.
|
|
427
|
+
await this._gracefullyShutdownMongo(this._mongo_uri);
|
|
428
|
+
|
|
429
|
+
await this._mongo_unit.stop();
|
|
396
430
|
this._mongo_uri = null;
|
|
397
431
|
}
|
|
398
432
|
|
|
@@ -403,6 +437,54 @@ TestHarness.setMethod(async function stop() {
|
|
|
403
437
|
}
|
|
404
438
|
});
|
|
405
439
|
|
|
440
|
+
/**
|
|
441
|
+
* Ask a mongod to shut down cleanly via the admin command, so the
|
|
442
|
+
* subsequent signal-based kill from mongodb-memory-server hits an
|
|
443
|
+
* already-gone process. Best-effort: any failure is swallowed and we
|
|
444
|
+
* fall through to the normal stop path.
|
|
445
|
+
*
|
|
446
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
447
|
+
* @since 1.4.2
|
|
448
|
+
*
|
|
449
|
+
* @param {string} uri
|
|
450
|
+
*
|
|
451
|
+
* @return {Promise}
|
|
452
|
+
*/
|
|
453
|
+
TestHarness.setMethod(async function _gracefullyShutdownMongo(uri) {
|
|
454
|
+
|
|
455
|
+
if (!uri) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
let MongoClient;
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
MongoClient = require('mongodb').MongoClient;
|
|
463
|
+
} catch (err) {
|
|
464
|
+
// No mongodb driver available - nothing we can do, let the
|
|
465
|
+
// normal stop path handle it.
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
let client;
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
client = await MongoClient.connect(uri, { directConnection: true });
|
|
473
|
+
try {
|
|
474
|
+
await client.db('admin').command({ shutdown: 1, force: true, timeoutSecs: 1 });
|
|
475
|
+
} catch (err) {
|
|
476
|
+
// mongod closes the connection mid-command while shutting down,
|
|
477
|
+
// surfacing as a network error. That means it worked - swallow.
|
|
478
|
+
}
|
|
479
|
+
} catch (err) {
|
|
480
|
+
// Couldn't reach mongod - fall through to the signal-based stop.
|
|
481
|
+
} finally {
|
|
482
|
+
if (client) {
|
|
483
|
+
try { await client.close(true); } catch (_) {}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
|
|
406
488
|
/**
|
|
407
489
|
* Get a full URL for a path
|
|
408
490
|
*
|
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.4.
|
|
4
|
+
"version": "1.4.2",
|
|
5
5
|
"author": "Jelle De Loecker <jelle@elevenways.be>",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"alchemy",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"codecov" : "~3.8.1",
|
|
65
65
|
"istanbul-lib-instrument" : "~6.0.1",
|
|
66
|
-
"mocha" : "
|
|
67
|
-
"mongo-unit" : "
|
|
66
|
+
"mocha" : "^11.7.5",
|
|
67
|
+
"mongo-unit" : "^3.4.0",
|
|
68
68
|
"nyc" : "^15.1.0",
|
|
69
69
|
"puppeteer" : "~21.3.6",
|
|
70
70
|
"source-map" : "~0.7.3"
|
|
@@ -79,4 +79,4 @@
|
|
|
79
79
|
"engines": {
|
|
80
80
|
"node" : ">=16.20.1"
|
|
81
81
|
}
|
|
82
|
-
}
|
|
82
|
+
}
|