@naturalcycles/nodejs-lib 15.139.0 → 15.141.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.
- package/dist/exec2/exec2.js +5 -4
- package/dist/fs/kpy.js +3 -3
- package/dist/jwt/jwt.service.js +1 -0
- package/dist/stream/progressLogger.js +3 -3
- package/dist/stream/transform/transformMap.js +3 -3
- package/dist/stream/transform/transformMapSync.js +2 -1
- package/dist/stream/transform/transformThrottle.js +1 -1
- package/dist/stream/transform/transformThrottleByRSS.js +2 -2
- package/dist/validation/ajv/getAjv.js +113 -121
- package/dist/validation/ajv/jSchema.d.ts +10 -1
- package/dist/validation/ajv/jSchema.js +19 -20
- package/dist/validation/ajv/jsonSchemaBuilder.util.js +8 -4
- package/dist/zip/zipInternal.js +1 -0
- package/dist/zip/zipWriter.js +2 -1
- package/package.json +2 -2
- package/src/exec2/exec2.ts +5 -4
- package/src/fs/kpy.ts +3 -3
- package/src/jwt/jwt.service.ts +1 -0
- package/src/stream/progressLogger.ts +3 -3
- package/src/stream/transform/transformMap.ts +3 -3
- package/src/stream/transform/transformMapSync.ts +3 -2
- package/src/stream/transform/transformThrottle.ts +1 -1
- package/src/stream/transform/transformThrottleByRSS.ts +2 -2
- package/src/stream/transform/worker/workerClassProxy.js +2 -0
- package/src/validation/ajv/getAjv.ts +128 -131
- package/src/validation/ajv/jSchema.ts +27 -21
- package/src/validation/ajv/jsonSchemaBuilder.util.ts +8 -4
- package/src/zip/zipInternal.ts +2 -0
- package/src/zip/zipWriter.ts +2 -1
package/dist/exec2/exec2.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execSync, spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { nowUnixMillis } from '@naturalcycles/js-lib/datetime/localTime.js';
|
|
2
3
|
import { _since } from '@naturalcycles/js-lib/datetime/time.util.js';
|
|
3
4
|
import { AppError } from '@naturalcycles/js-lib/error/error.util.js';
|
|
4
5
|
import { _substringAfterLast } from '@naturalcycles/js-lib/string/string.util.js';
|
|
@@ -53,7 +54,7 @@ class Exec2 {
|
|
|
53
54
|
const log = opt.log ?? true; // by default log should be true, as we are printing the output
|
|
54
55
|
// Copy, to not mutate the input options
|
|
55
56
|
opt = { ...opt, log, logStart: opt.logStart ?? log, logFinish: opt.logFinish ?? log };
|
|
56
|
-
const started =
|
|
57
|
+
const started = nowUnixMillis();
|
|
57
58
|
this.logStart(cmd, opt);
|
|
58
59
|
const r = spawnSync(cmd, opt.args, {
|
|
59
60
|
encoding: 'utf8',
|
|
@@ -98,7 +99,7 @@ class Exec2 {
|
|
|
98
99
|
logStart: opt.logStart ?? opt.log ?? false,
|
|
99
100
|
logFinish: opt.logFinish ?? opt.log ?? false,
|
|
100
101
|
};
|
|
101
|
-
const started =
|
|
102
|
+
const started = nowUnixMillis();
|
|
102
103
|
this.logStart(cmd, opt);
|
|
103
104
|
try {
|
|
104
105
|
const s = execSync(cmd, {
|
|
@@ -148,7 +149,7 @@ class Exec2 {
|
|
|
148
149
|
const log = opt.log ?? true; // by default log should be true, as we are printing the output
|
|
149
150
|
// Copy, to not mutate the input options
|
|
150
151
|
opt = { ...opt, log, logStart: opt.logStart ?? log, logFinish: opt.logFinish ?? log };
|
|
151
|
-
const started =
|
|
152
|
+
const started = nowUnixMillis();
|
|
152
153
|
this.logStart(cmd, opt);
|
|
153
154
|
await new Promise((resolve, reject) => {
|
|
154
155
|
const p = spawn(cmd, opt.args || [], {
|
|
@@ -203,7 +204,7 @@ class Exec2 {
|
|
|
203
204
|
const log = opt.log ?? printWhileRunning; // by default log should be true, as we are printing the output
|
|
204
205
|
// Copy, to not mutate the input options
|
|
205
206
|
opt = { ...opt, log, logStart: opt.logStart ?? log, logFinish: opt.logFinish ?? log };
|
|
206
|
-
const started =
|
|
207
|
+
const started = nowUnixMillis();
|
|
207
208
|
this.logStart(cmd, opt);
|
|
208
209
|
let stdout = '';
|
|
209
210
|
let stderr = '';
|
package/dist/fs/kpy.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { _since,
|
|
2
|
+
import { _since, nowUnixMillis } from '@naturalcycles/js-lib/datetime';
|
|
3
3
|
import { glob, globSync } from 'tinyglobby';
|
|
4
4
|
import { boldWhite, dimGrey, grey, yellow } from '../colors/colors.js';
|
|
5
5
|
import { fs2 } from './fs2.js';
|
|
6
6
|
export async function kpy(opt) {
|
|
7
|
-
const started =
|
|
7
|
+
const started = nowUnixMillis();
|
|
8
8
|
opt = kpyPrepare(opt);
|
|
9
9
|
const filenames = await glob(opt.inputPatterns, {
|
|
10
10
|
cwd: opt.baseDir,
|
|
@@ -33,7 +33,7 @@ export async function kpy(opt) {
|
|
|
33
33
|
kpyLogResult(opt, filenames, started);
|
|
34
34
|
}
|
|
35
35
|
export function kpySync(opt) {
|
|
36
|
-
const started =
|
|
36
|
+
const started = nowUnixMillis();
|
|
37
37
|
opt = kpyPrepare(opt);
|
|
38
38
|
const filenames = globSync(opt.inputPatterns, {
|
|
39
39
|
cwd: opt.baseDir,
|
package/dist/jwt/jwt.service.js
CHANGED
|
@@ -78,6 +78,7 @@ export class JWTService {
|
|
|
78
78
|
const { payload } = await jwtVerify(token, key, {
|
|
79
79
|
algorithms: this.cfg.verifyAlgorithms || [this.cfg.algorithm],
|
|
80
80
|
...joseOpt,
|
|
81
|
+
// oxlint-disable-next-line no-restricted-globals -- jose expects a Date
|
|
81
82
|
currentDate: now === undefined ? undefined : new Date(now * 1000),
|
|
82
83
|
});
|
|
83
84
|
data = payload;
|
|
@@ -35,8 +35,8 @@ export class ProgressLogger {
|
|
|
35
35
|
progress;
|
|
36
36
|
peakRSS;
|
|
37
37
|
start() {
|
|
38
|
-
this.started =
|
|
39
|
-
this.lastSecondStarted =
|
|
38
|
+
this.started = localTime.nowUnixMillis();
|
|
39
|
+
this.lastSecondStarted = localTime.nowUnixMillis();
|
|
40
40
|
this.sma = new SimpleMovingAverage(10);
|
|
41
41
|
this.processedLastSecond = 0;
|
|
42
42
|
this.progress = 0;
|
|
@@ -60,7 +60,7 @@ export class ProgressLogger {
|
|
|
60
60
|
return;
|
|
61
61
|
const { metric, extra, chunkSize, heapUsed: logHeapUsed, heapTotal: logHeapTotal, rss: logRss, peakRSS: logPeakRss, rssMinusHeap, external, arrayBuffers, logRPS, logger, } = this.cfg;
|
|
62
62
|
const mem = process.memoryUsage();
|
|
63
|
-
const now =
|
|
63
|
+
const now = localTime.nowUnixMillis();
|
|
64
64
|
const batchedProgress = this.progress * chunkSize;
|
|
65
65
|
const lastRPS = (this.processedLastSecond * chunkSize) / ((now - this.lastSecondStarted) / 1000) || 0;
|
|
66
66
|
const rpsTotal = Math.round(batchedProgress / ((now - this.started) / 1000)) || 0;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Transform } from 'node:stream';
|
|
2
2
|
import { _hc } from '@naturalcycles/js-lib';
|
|
3
|
-
import { _since } from '@naturalcycles/js-lib/datetime';
|
|
3
|
+
import { _since, nowUnixMillis } from '@naturalcycles/js-lib/datetime';
|
|
4
4
|
import { _anyToError, _assert, ErrorMode } from '@naturalcycles/js-lib/error';
|
|
5
5
|
import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log';
|
|
6
6
|
import { pDefer } from '@naturalcycles/js-lib/promise/pDefer.js';
|
|
@@ -43,7 +43,7 @@ export function transformMap(mapper, opt = {}) {
|
|
|
43
43
|
async transform(chunk, _, cb) {
|
|
44
44
|
// Initialize start time on first item
|
|
45
45
|
if (started === 0) {
|
|
46
|
-
started =
|
|
46
|
+
started = nowUnixMillis();
|
|
47
47
|
lastWarmupCheck = started;
|
|
48
48
|
}
|
|
49
49
|
if (isSettled)
|
|
@@ -150,7 +150,7 @@ export function transformMap(mapper, opt = {}) {
|
|
|
150
150
|
},
|
|
151
151
|
});
|
|
152
152
|
function updateConcurrency() {
|
|
153
|
-
const now =
|
|
153
|
+
const now = nowUnixMillis();
|
|
154
154
|
if (now - lastWarmupCheck < WARMUP_CHECK_INTERVAL_MS)
|
|
155
155
|
return;
|
|
156
156
|
lastWarmupCheck = now;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Transform } from 'node:stream';
|
|
2
|
+
import { nowUnixMillis } from '@naturalcycles/js-lib/datetime/localTime.js';
|
|
2
3
|
import { _anyToError, _assert, ErrorMode } from '@naturalcycles/js-lib/error';
|
|
3
4
|
import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log';
|
|
4
5
|
import { END, SKIP } from '@naturalcycles/js-lib/types';
|
|
@@ -11,7 +12,7 @@ import { PIPELINE_GRACEFUL_ABORT } from '../stream.util.js';
|
|
|
11
12
|
export function transformMapSync(mapper, opt = {}) {
|
|
12
13
|
const { predicate, // defaults to "no predicate" (pass everything)
|
|
13
14
|
errorMode = ErrorMode.THROW_IMMEDIATELY, onError, onDone, metric = 'stream', objectMode = true, signal, } = opt;
|
|
14
|
-
const started =
|
|
15
|
+
const started = nowUnixMillis();
|
|
15
16
|
let index = -1;
|
|
16
17
|
let countOut = 0;
|
|
17
18
|
let isSettled = false;
|
|
@@ -48,7 +48,7 @@ export function transformThrottle(opt) {
|
|
|
48
48
|
if (++count >= throughput) {
|
|
49
49
|
// console.log('pausing now after', {item, count})
|
|
50
50
|
lock = pDefer();
|
|
51
|
-
logger.log(`${localTime.now().toPretty()} transformThrottle activated: ${count} items passed in ${_since(start)}, will pause for ${_ms(interval * 1000 - (
|
|
51
|
+
logger.log(`${localTime.now().toPretty()} transformThrottle activated: ${count} items passed in ${_since(start)}, will pause for ${_ms(interval * 1000 - (localTime.nowUnixMillis() - start))}`);
|
|
52
52
|
}
|
|
53
53
|
cb(null, item); // pass the item through
|
|
54
54
|
},
|
|
@@ -39,7 +39,7 @@ export function transformThrottleByRSS(opt) {
|
|
|
39
39
|
}
|
|
40
40
|
if (!disabled && lastRSS > maxRSSBytes && !lock) {
|
|
41
41
|
lock = pDefer();
|
|
42
|
-
pausedSince =
|
|
42
|
+
pausedSince = localTime.nowUnixMillis();
|
|
43
43
|
logger.log(`${localTime.now().toPretty()} transformThrottleByRSS paused: RSS ${_mb(lastRSS)} > ${maxRSS} MB`);
|
|
44
44
|
pollTimer = setTimeout(() => pollRSS(), pollInterval);
|
|
45
45
|
}
|
|
@@ -67,7 +67,7 @@ export function transformThrottleByRSS(opt) {
|
|
|
67
67
|
lock.resolve();
|
|
68
68
|
lock = undefined;
|
|
69
69
|
}
|
|
70
|
-
else if (pollTimeout &&
|
|
70
|
+
else if (pollTimeout && localTime.nowUnixMillis() - pausedSince >= pollTimeout) {
|
|
71
71
|
clearTimeout(rssCheckTimer);
|
|
72
72
|
if (onPollTimeout === 'throw') {
|
|
73
73
|
lock.reject(new Error(`transformThrottleByRSS pollTimeout of ${_ms(pollTimeout)} reached, RSS ${_mb(rss)} still > ${maxRSS} MB`));
|
|
@@ -2,7 +2,7 @@ import { _isBetween, _lazyValue, _round } from '@naturalcycles/js-lib';
|
|
|
2
2
|
import { _assert } from '@naturalcycles/js-lib/error';
|
|
3
3
|
import { _deepCopy, _mapObject, Set2 } from '@naturalcycles/js-lib/object';
|
|
4
4
|
import { _substringAfterLast } from '@naturalcycles/js-lib/string';
|
|
5
|
-
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
5
|
+
import { _, Ajv2020, str } from 'ajv/dist/2020.js';
|
|
6
6
|
import { validTLDs } from '../tlds.js';
|
|
7
7
|
import { jFormats } from './jsonSchemaBuilder.util.js';
|
|
8
8
|
// oxlint-disable unicorn/prefer-code-point
|
|
@@ -265,130 +265,70 @@ export function createAjv(opt) {
|
|
|
265
265
|
return true;
|
|
266
266
|
},
|
|
267
267
|
});
|
|
268
|
+
// The date/time keywords are `code` keywords, which Ajv inlines into the compiled validator.
|
|
269
|
+
// A `validate` keyword is a function call with a context object, whose instancePath Ajv builds
|
|
270
|
+
// on every call (escaping the key of every map entry), even for valid data.
|
|
268
271
|
ajv.addKeyword({
|
|
269
272
|
keyword: 'IsoDate',
|
|
270
273
|
type: 'string',
|
|
271
|
-
modifying: false,
|
|
272
|
-
errors: true,
|
|
273
274
|
schemaType: 'object',
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
instancePath: ctx?.instancePath ?? '',
|
|
297
|
-
message: `is not before ${before}`,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
if (sameOrBefore) {
|
|
302
|
-
const isParamValid = isIsoDateValid(sameOrBefore);
|
|
303
|
-
const isRuleValid = isParamValid && sameOrBefore >= data;
|
|
304
|
-
if (!isRuleValid) {
|
|
305
|
-
errors.push({
|
|
306
|
-
instancePath: ctx?.instancePath ?? '',
|
|
307
|
-
message: `is not the same or before ${sameOrBefore}`,
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
if (after) {
|
|
312
|
-
const isParamValid = isIsoDateValid(after);
|
|
313
|
-
const isRuleValid = isParamValid && after < data;
|
|
314
|
-
if (!isRuleValid) {
|
|
315
|
-
errors.push({
|
|
316
|
-
instancePath: ctx?.instancePath ?? '',
|
|
317
|
-
message: `is not after ${after}`,
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
if (sameOrAfter) {
|
|
322
|
-
const isParamValid = isIsoDateValid(sameOrAfter);
|
|
323
|
-
const isRuleValid = isParamValid && sameOrAfter <= data;
|
|
324
|
-
if (!isRuleValid) {
|
|
325
|
-
errors.push({
|
|
326
|
-
instancePath: ctx?.instancePath ?? '',
|
|
327
|
-
message: `is not the same or after ${sameOrAfter}`,
|
|
328
|
-
});
|
|
275
|
+
error: {
|
|
276
|
+
message: ({ params }) => params['rule']
|
|
277
|
+
? str `is not ${isoDateRuleLabels[params['rule']]} ${params['limit']}`
|
|
278
|
+
: str `is an invalid IsoDate`,
|
|
279
|
+
params: ({ params }) => (params['rule'] ? _ `{${params['rule']}: ${params['limit']}}` : _ `{}`),
|
|
280
|
+
},
|
|
281
|
+
code(cxt) {
|
|
282
|
+
const { gen, data } = cxt;
|
|
283
|
+
const opt = cxt.schema;
|
|
284
|
+
const isValid = gen.scopeValue('func', { ref: isIsoDateValid });
|
|
285
|
+
gen.if(_ `!${isValid}(${data})`, () => cxt.error(), () => {
|
|
286
|
+
for (const rule of isoDateRules) {
|
|
287
|
+
const limit = opt[rule];
|
|
288
|
+
if (!limit)
|
|
289
|
+
continue;
|
|
290
|
+
const params = { rule, limit };
|
|
291
|
+
if (!isIsoDateValid(limit)) {
|
|
292
|
+
// An invalid limit rejects every date
|
|
293
|
+
cxt.error(false, params);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
gen.if(isoDateRuleFails(rule, data, limit), () => cxt.error(false, params));
|
|
329
297
|
}
|
|
330
|
-
}
|
|
331
|
-
if (errors.length === 0)
|
|
332
|
-
return true;
|
|
333
|
-
validate.errors = errors;
|
|
334
|
-
return false;
|
|
298
|
+
});
|
|
335
299
|
},
|
|
336
300
|
});
|
|
337
301
|
ajv.addKeyword({
|
|
338
302
|
keyword: 'IsoDateTime',
|
|
339
303
|
type: 'string',
|
|
340
|
-
modifying: false,
|
|
341
|
-
errors: true,
|
|
342
304
|
schemaType: 'boolean',
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
validate.errors = [
|
|
348
|
-
{
|
|
349
|
-
instancePath: ctx?.instancePath ?? '',
|
|
350
|
-
message: `is an invalid IsoDateTime`,
|
|
351
|
-
},
|
|
352
|
-
];
|
|
353
|
-
return false;
|
|
305
|
+
error: { message: 'is an invalid IsoDateTime' },
|
|
306
|
+
code(cxt) {
|
|
307
|
+
const isValid = cxt.gen.scopeValue('func', { ref: isIsoDateTimeValid });
|
|
308
|
+
cxt.fail(_ `!${isValid}(${cxt.data})`);
|
|
354
309
|
},
|
|
355
310
|
});
|
|
356
311
|
ajv.addKeyword({
|
|
357
312
|
keyword: 'IsoMonth',
|
|
358
313
|
type: 'string',
|
|
359
|
-
modifying: false,
|
|
360
|
-
errors: true,
|
|
361
314
|
schemaType: 'object',
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
validate.errors = [
|
|
367
|
-
{
|
|
368
|
-
instancePath: ctx?.instancePath ?? '',
|
|
369
|
-
message: `is an invalid IsoMonth`,
|
|
370
|
-
},
|
|
371
|
-
];
|
|
372
|
-
return false;
|
|
315
|
+
error: { message: 'is an invalid IsoMonth' },
|
|
316
|
+
code(cxt) {
|
|
317
|
+
const isValid = cxt.gen.scopeValue('func', { ref: isIsoMonthValid });
|
|
318
|
+
cxt.fail(_ `!${isValid}(${cxt.data})`);
|
|
373
319
|
},
|
|
374
320
|
});
|
|
375
321
|
ajv.addKeyword({
|
|
376
322
|
keyword: 'UtcOffset',
|
|
377
323
|
type: 'string',
|
|
378
|
-
modifying: false,
|
|
379
|
-
errors: true,
|
|
380
324
|
schemaType: 'object',
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
message: `is an invalid UtcOffset`,
|
|
389
|
-
},
|
|
390
|
-
];
|
|
391
|
-
return false;
|
|
325
|
+
error: { message: 'is an invalid UtcOffset' },
|
|
326
|
+
code(cxt) {
|
|
327
|
+
const opt = cxt.schema;
|
|
328
|
+
const isValid = cxt.gen.scopeValue('func', {
|
|
329
|
+
ref: opt.allowZ ? isUtcOffsetOrZValid : isUtcOffsetValid,
|
|
330
|
+
});
|
|
331
|
+
cxt.fail(_ `!${isValid}(${cxt.data})`);
|
|
392
332
|
},
|
|
393
333
|
});
|
|
394
334
|
// Annotation only (the values are validated by `enum`), registered so strict mode doesn't reject it
|
|
@@ -425,35 +365,62 @@ export function createAjv(opt) {
|
|
|
425
365
|
return true;
|
|
426
366
|
},
|
|
427
367
|
});
|
|
368
|
+
// Strips the keys that don't match the key schema; the values are validated by `additionalProperties`.
|
|
369
|
+
// The key schema is inlined per key with error reporting off, and the error count is reset after
|
|
370
|
+
// a mismatch, so a stripped key never surfaces as an error. Keys listed in `properties` are skipped.
|
|
428
371
|
ajv.addKeyword({
|
|
429
372
|
keyword: 'keySchema',
|
|
430
373
|
type: 'object',
|
|
431
|
-
modifying: true,
|
|
432
|
-
errors: false,
|
|
433
374
|
schemaType: 'object',
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
375
|
+
trackErrors: true,
|
|
376
|
+
code(cxt) {
|
|
377
|
+
const { gen, data, parentSchema } = cxt;
|
|
378
|
+
const propertyNames = Object.keys(parentSchema['properties'] || {});
|
|
379
|
+
const valid = gen.name('valid');
|
|
380
|
+
gen.forIn('key', data, key => {
|
|
381
|
+
const checkKey = () => {
|
|
382
|
+
cxt.subschema({
|
|
383
|
+
keyword: 'keySchema',
|
|
384
|
+
data: key,
|
|
385
|
+
dataTypes: ['string'],
|
|
386
|
+
compositeRule: true,
|
|
387
|
+
createErrors: false,
|
|
388
|
+
allErrors: false,
|
|
389
|
+
}, valid);
|
|
390
|
+
gen.if(_ `!${valid}`, () => {
|
|
391
|
+
cxt.reset();
|
|
392
|
+
gen.code(_ `delete ${data}[${key}]`);
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
if (!propertyNames.length)
|
|
396
|
+
return checkKey();
|
|
397
|
+
let isNotProperty = _ `${key} !== ${propertyNames[0]}`;
|
|
398
|
+
for (const name of propertyNames.slice(1)) {
|
|
399
|
+
isNotProperty = _ `${isNotProperty} && ${key} !== ${name}`;
|
|
443
400
|
}
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
return validate;
|
|
401
|
+
gen.if(isNotProperty, checkKey);
|
|
402
|
+
});
|
|
447
403
|
},
|
|
448
404
|
});
|
|
449
|
-
// Validates
|
|
450
|
-
//
|
|
405
|
+
// Validates the inner schema only when the value is defined. Used for the optional values of
|
|
406
|
+
// `stringMap`/`record`, whose `additionalProperties` loop visits the keys with undefined values too.
|
|
407
|
+
ajv.addKeyword({
|
|
408
|
+
keyword: 'ifDefined',
|
|
409
|
+
schemaType: ['object', 'boolean'],
|
|
410
|
+
code(cxt) {
|
|
411
|
+
const { gen, data } = cxt;
|
|
412
|
+
gen.if(_ `${data} !== undefined`, () => {
|
|
413
|
+
cxt.subschema({ keyword: 'ifDefined' }, gen.name('valid'));
|
|
414
|
+
});
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
// Validates that the value is undefined. Superseded by `ifDefined`, kept for raw JSON schemas.
|
|
451
418
|
ajv.addKeyword({
|
|
452
419
|
keyword: 'isUndefined',
|
|
453
|
-
modifying: false,
|
|
454
|
-
errors: false,
|
|
455
420
|
schemaType: 'boolean',
|
|
456
|
-
|
|
421
|
+
code(cxt) {
|
|
422
|
+
cxt.fail(_ `${cxt.data} !== undefined`);
|
|
423
|
+
},
|
|
457
424
|
});
|
|
458
425
|
// This is added because Ajv validates the `min/maxProperties` before validating the properties.
|
|
459
426
|
// So, in case of `minProperties(1)` and `{ foo: 'bar' }` Ajv will let it pass, even
|
|
@@ -660,6 +627,25 @@ export function createAjv(opt) {
|
|
|
660
627
|
});
|
|
661
628
|
return ajv;
|
|
662
629
|
}
|
|
630
|
+
const isoDateRules = ['before', 'sameOrBefore', 'after', 'sameOrAfter'];
|
|
631
|
+
const isoDateRuleLabels = {
|
|
632
|
+
before: 'before',
|
|
633
|
+
sameOrBefore: 'the same or before',
|
|
634
|
+
after: 'after',
|
|
635
|
+
sameOrAfter: 'the same or after',
|
|
636
|
+
};
|
|
637
|
+
/**
|
|
638
|
+
* IsoDate strings compare chronologically as plain strings.
|
|
639
|
+
*/
|
|
640
|
+
function isoDateRuleFails(rule, data, limit) {
|
|
641
|
+
if (rule === 'before')
|
|
642
|
+
return _ `${data} >= ${limit}`;
|
|
643
|
+
if (rule === 'sameOrBefore')
|
|
644
|
+
return _ `${data} > ${limit}`;
|
|
645
|
+
if (rule === 'after')
|
|
646
|
+
return _ `${data} <= ${limit}`;
|
|
647
|
+
return _ `${data} < ${limit}`;
|
|
648
|
+
}
|
|
663
649
|
const monthLengths = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
664
650
|
const DASH_CODE = '-'.charCodeAt(0);
|
|
665
651
|
const ZERO_CODE = '0'.charCodeAt(0);
|
|
@@ -788,6 +774,12 @@ function isIsoTimezoneValid(s) {
|
|
|
788
774
|
return false; // min is -12:00
|
|
789
775
|
return true;
|
|
790
776
|
}
|
|
777
|
+
function isUtcOffsetValid(s) {
|
|
778
|
+
return s !== 'Z' && s !== '' && isIsoTimezoneValid(s);
|
|
779
|
+
}
|
|
780
|
+
function isUtcOffsetOrZValid(s) {
|
|
781
|
+
return s === 'Z' || (s !== '' && isIsoTimezoneValid(s));
|
|
782
|
+
}
|
|
791
783
|
/**
|
|
792
784
|
* This is a performance optimized correct validation
|
|
793
785
|
* for ISO month formatted as "YYYY-MM".
|
|
@@ -307,6 +307,7 @@ export declare class JIsoDate<Opt extends boolean = false> extends JBuilder<IsoD
|
|
|
307
307
|
after(date: string): this;
|
|
308
308
|
sameOrAfter(date: string): this;
|
|
309
309
|
between(fromDate: string, toDate: string, incl: Inclusiveness): this;
|
|
310
|
+
private addRules;
|
|
310
311
|
}
|
|
311
312
|
export interface JsonSchemaIsoDateOptions {
|
|
312
313
|
before?: string;
|
|
@@ -387,6 +388,10 @@ export declare class JObject<OUT extends AnyObject, Opt extends boolean = false>
|
|
|
387
388
|
}
|
|
388
389
|
interface JObjectOpts {
|
|
389
390
|
hasIsOfTypeCheck?: false;
|
|
391
|
+
/**
|
|
392
|
+
* Schema for every property not listed in `properties`, e.g the values of a string map.
|
|
393
|
+
*/
|
|
394
|
+
additionalProperties?: JsonSchema<any>;
|
|
390
395
|
patternProperties?: StringMap<JsonSchema<any>>;
|
|
391
396
|
keySchema?: JsonSchema;
|
|
392
397
|
}
|
|
@@ -574,7 +579,7 @@ export interface JsonSchema<OUT = unknown> {
|
|
|
574
579
|
};
|
|
575
580
|
patternProperties?: StringMap<JsonSchema<any>>;
|
|
576
581
|
required?: string[];
|
|
577
|
-
additionalProperties?: boolean
|
|
582
|
+
additionalProperties?: boolean | JsonSchema<any>;
|
|
578
583
|
minProperties?: number;
|
|
579
584
|
maxProperties?: number;
|
|
580
585
|
default?: OUT;
|
|
@@ -626,6 +631,10 @@ export interface JsonSchema<OUT = unknown> {
|
|
|
626
631
|
errorMessages?: StringMap<string>;
|
|
627
632
|
optionalValues?: (string | number | boolean | null)[];
|
|
628
633
|
keySchema?: JsonSchema;
|
|
634
|
+
/**
|
|
635
|
+
* Validates the value against the given schema only when it's defined.
|
|
636
|
+
*/
|
|
637
|
+
ifDefined?: JsonSchema;
|
|
629
638
|
isUndefined?: true;
|
|
630
639
|
minProperties2?: number;
|
|
631
640
|
exclusiveProperties?: (readonly string[])[];
|
|
@@ -38,13 +38,13 @@ export const j = {
|
|
|
38
38
|
const isValueOptional = schema.getSchema().optionalField;
|
|
39
39
|
const builtSchema = schema.build();
|
|
40
40
|
const finalValueSchema = isValueOptional
|
|
41
|
-
? {
|
|
41
|
+
? { ifDefined: builtSchema }
|
|
42
42
|
: builtSchema;
|
|
43
|
+
// `additionalProperties` instead of `patternProperties: { '^.+$': ... }`, which made Ajv
|
|
44
|
+
// regex-test every key twice (once more for `additionalProperties: false`)
|
|
43
45
|
return new JObject({}, {
|
|
44
46
|
hasIsOfTypeCheck: false,
|
|
45
|
-
|
|
46
|
-
'^.+$': finalValueSchema,
|
|
47
|
-
},
|
|
47
|
+
additionalProperties: finalValueSchema,
|
|
48
48
|
});
|
|
49
49
|
},
|
|
50
50
|
/**
|
|
@@ -695,26 +695,25 @@ export class JIsoDate extends JBuilder {
|
|
|
695
695
|
});
|
|
696
696
|
}
|
|
697
697
|
before(date) {
|
|
698
|
-
return this.
|
|
698
|
+
return this.addRules({ before: date });
|
|
699
699
|
}
|
|
700
700
|
sameOrBefore(date) {
|
|
701
|
-
return this.
|
|
701
|
+
return this.addRules({ sameOrBefore: date });
|
|
702
702
|
}
|
|
703
703
|
after(date) {
|
|
704
|
-
return this.
|
|
704
|
+
return this.addRules({ after: date });
|
|
705
705
|
}
|
|
706
706
|
sameOrAfter(date) {
|
|
707
|
-
return this.
|
|
707
|
+
return this.addRules({ sameOrAfter: date });
|
|
708
708
|
}
|
|
709
709
|
between(fromDate, toDate, incl) {
|
|
710
|
-
let schemaPatch = {};
|
|
711
710
|
if (incl === '[)') {
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
else if (incl === '[]') {
|
|
715
|
-
schemaPatch = { IsoDate: { sameOrAfter: fromDate, sameOrBefore: toDate } };
|
|
711
|
+
return this.addRules({ sameOrAfter: fromDate, before: toDate });
|
|
716
712
|
}
|
|
717
|
-
return this.
|
|
713
|
+
return this.addRules({ sameOrAfter: fromDate, sameOrBefore: toDate });
|
|
714
|
+
}
|
|
715
|
+
addRules(rules) {
|
|
716
|
+
return this.cloneAndUpdateSchema({ IsoDate: { ...this.schema.IsoDate, ...rules } });
|
|
718
717
|
}
|
|
719
718
|
}
|
|
720
719
|
export class JNumber extends JBuilder {
|
|
@@ -832,7 +831,7 @@ export class JObject extends JBuilder {
|
|
|
832
831
|
type: 'object',
|
|
833
832
|
properties: {},
|
|
834
833
|
required: [],
|
|
835
|
-
additionalProperties: false,
|
|
834
|
+
additionalProperties: opt?.additionalProperties ?? false,
|
|
836
835
|
hasIsOfTypeCheck: opt?.hasIsOfTypeCheck ?? true,
|
|
837
836
|
patternProperties: opt?.patternProperties ?? undefined,
|
|
838
837
|
keySchema: opt?.keySchema ?? undefined,
|
|
@@ -844,6 +843,9 @@ export class JObject extends JBuilder {
|
|
|
844
843
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
845
844
|
*/
|
|
846
845
|
allowAdditionalProperties() {
|
|
846
|
+
// A string map already accepts every property, validated by its value schema
|
|
847
|
+
if (typeof this.schema.additionalProperties === 'object')
|
|
848
|
+
return this.clone();
|
|
847
849
|
return this.cloneAndUpdateSchema({ additionalProperties: true });
|
|
848
850
|
}
|
|
849
851
|
extend(props) {
|
|
@@ -1024,16 +1026,13 @@ function record(keySchema, valueSchema) {
|
|
|
1024
1026
|
// Check if value schema is optional before build() strips the optionalField flag
|
|
1025
1027
|
const isValueOptional = valueSchema.getSchema().optionalField;
|
|
1026
1028
|
const valueJsonSchema = valueSchema.build();
|
|
1027
|
-
// When value schema is optional, wrap in anyOf to allow undefined values
|
|
1028
1029
|
const finalValueSchema = isValueOptional
|
|
1029
|
-
? {
|
|
1030
|
+
? { ifDefined: valueJsonSchema }
|
|
1030
1031
|
: valueJsonSchema;
|
|
1031
1032
|
return new JObject([], {
|
|
1032
1033
|
hasIsOfTypeCheck: false,
|
|
1033
1034
|
keySchema: keyJsonSchema,
|
|
1034
|
-
|
|
1035
|
-
['^.*$']: finalValueSchema,
|
|
1036
|
-
},
|
|
1035
|
+
additionalProperties: finalValueSchema,
|
|
1037
1036
|
});
|
|
1038
1037
|
}
|
|
1039
1038
|
function withRegexKeys(keyRegex, schema) {
|
|
@@ -53,16 +53,20 @@ export function mergeJsonSchemaObjects(schema1, schema2) {
|
|
|
53
53
|
s1.properties[k] = v;
|
|
54
54
|
});
|
|
55
55
|
// Merge `patternProperties`
|
|
56
|
-
|
|
57
|
-
s1.patternProperties
|
|
58
|
-
}
|
|
56
|
+
if (s2.patternProperties) {
|
|
57
|
+
s1.patternProperties = { ...s1.patternProperties, ...s2.patternProperties };
|
|
58
|
+
}
|
|
59
59
|
s1.propertyNames = s2.propertyNames || s1.propertyNames;
|
|
60
|
+
s1.keySchema = s2.keySchema || s1.keySchema;
|
|
60
61
|
s1.minProperties = s2.minProperties ?? s1.minProperties;
|
|
61
62
|
s1.maxProperties = s2.maxProperties ?? s1.maxProperties;
|
|
62
63
|
// Merge `required`
|
|
63
64
|
s1.required.push(...s2.required);
|
|
64
65
|
s1.required = _uniq(s1.required).sort();
|
|
65
|
-
// `additionalProperties` remains the same
|
|
66
|
+
// `additionalProperties` remains the same, unless s2 is a string map (schema-valued)
|
|
67
|
+
if (typeof s2.additionalProperties === 'object') {
|
|
68
|
+
s1.additionalProperties = s2.additionalProperties;
|
|
69
|
+
}
|
|
66
70
|
return _filterNullishValues(s1, { mutate: true });
|
|
67
71
|
}
|
|
68
72
|
export function isEveryItemString(arr) {
|
package/dist/zip/zipInternal.js
CHANGED
|
@@ -8,6 +8,7 @@ integer read/write helpers, DOS date/time conversion (both directions) and
|
|
|
8
8
|
entry-name normalization/validation.
|
|
9
9
|
|
|
10
10
|
*/
|
|
11
|
+
// oxlint-disable no-restricted-globals -- DOS date/time fields are local-time components, converted via the native Date
|
|
11
12
|
// oxlint-disable no-bitwise -- the ZIP format packs DOS date/time into bit fields
|
|
12
13
|
// Compression methods.
|
|
13
14
|
export const STORED = 0;
|
package/dist/zip/zipWriter.js
CHANGED
|
@@ -29,6 +29,7 @@ import { finished, pipeline } from 'node:stream/promises';
|
|
|
29
29
|
import { promisify } from 'node:util';
|
|
30
30
|
import zlib from 'node:zlib';
|
|
31
31
|
import { comparators } from '@naturalcycles/js-lib/array/sort.js';
|
|
32
|
+
import { nowUnix } from '@naturalcycles/js-lib/datetime/localTime.js';
|
|
32
33
|
import { _assert } from '@naturalcycles/js-lib/error/assert.js';
|
|
33
34
|
import { glob } from 'tinyglobby';
|
|
34
35
|
import { assertSafeZipEntryName, CDFH_SIG, CDFH_SIZE, DATA_DESCRIPTOR_SIG, DEFLATE, EOCDR_SIG, EOCDR_SIZE, LOCAL_FILE_HEADER_SIG, MAX_COMMENT_SIZE, normalizeZipEntryName, STORED, unixToDosDateTime, writeUInt64LE, ZIP64_EOCDL_SIG, ZIP64_EOCDL_SIZE, ZIP64_EOCDR_SIG, ZIP64_EOCDR_SIZE, } from './zipInternal.js';
|
|
@@ -222,7 +223,7 @@ export class ZipWriter {
|
|
|
222
223
|
if (nameBuf.length > MAX_COMMENT_SIZE) {
|
|
223
224
|
throw new Error(`zip entry name is too long: ${nameBuf.length} > ${MAX_COMMENT_SIZE} bytes`);
|
|
224
225
|
}
|
|
225
|
-
const mtime = opt.mtime ??
|
|
226
|
+
const mtime = opt.mtime ?? nowUnix();
|
|
226
227
|
const { date, time } = unixToDosDateTime(mtime);
|
|
227
228
|
const mode = opt.mode ?? (isDirectory ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE);
|
|
228
229
|
if ((mode & 0xffff) !== mode) {
|