@naturalcycles/nodejs-lib 13.23.0 → 13.24.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/fs/fs2.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  import type { RmOptions } from 'node:fs';
5
5
  import fs from 'node:fs';
6
6
  import { DumpOptions } from 'js-yaml';
7
- import { ReadableTyped, WritableTyped } from '../stream/stream.model';
7
+ import { ReadableTyped, TransformTyped } from '../stream/stream.model';
8
8
  /**
9
9
  * fs2 conveniently groups filesystem functions together.
10
10
  * Supposed to be almost a drop-in replacement for these things together:
@@ -77,7 +77,7 @@ declare class FS2 {
77
77
  createWriteStream: typeof fs.createWriteStream;
78
78
  createReadStream: typeof fs.createReadStream;
79
79
  createReadStreamAsNDJSON<ROW = any>(inputPath: string): ReadableTyped<ROW>;
80
- createWriteStreamAsNDJSON(outputPath: string): WritableTyped<any>;
80
+ createWriteStreamAsNDJSON(outputPath: string): TransformTyped<any, any>[];
81
81
  }
82
82
  export declare const fs2: FS2;
83
83
  export interface JsonOptions {
package/dist/fs/fs2.js CHANGED
@@ -311,7 +311,8 @@ class FS2 {
311
311
  // .pipe(transformJsonParse<ROW>())
312
312
  }
313
313
  /*
314
- Returns a Writable.
314
+ Returns an array of Transforms, so that you can ...destructure them at
315
+ the end of the _pipeline.
315
316
 
316
317
  Replaces a list of operations:
317
318
  - transformToNDJson
@@ -320,17 +321,17 @@ class FS2 {
320
321
  */
321
322
  createWriteStreamAsNDJSON(outputPath) {
322
323
  this.ensureFile(outputPath);
323
- const transform1 = (0, transformToNDJson_1.transformToNDJson)();
324
- let transform = transform1;
325
- if (outputPath.endsWith('.gz')) {
326
- transform = transform.pipe((0, node_zlib_1.createGzip)({
327
- // chunkSize: 64 * 1024, // no observed speedup
328
- }));
329
- }
330
- transform.pipe(node_fs_1.default.createWriteStream(outputPath, {
331
- // highWaterMark: 64 * 1024, // no observed speedup
332
- }));
333
- return transform1;
324
+ return [
325
+ (0, transformToNDJson_1.transformToNDJson)(),
326
+ outputPath.endsWith('.gz')
327
+ ? (0, node_zlib_1.createGzip)({
328
+ // chunkSize: 64 * 1024, // no observed speedup
329
+ })
330
+ : undefined,
331
+ node_fs_1.default.createWriteStream(outputPath, {
332
+ // highWaterMark: 64 * 1024, // no observed speedup
333
+ }),
334
+ ].filter(js_lib_1._isTruthy);
334
335
  }
335
336
  }
336
337
  exports.fs2 = new FS2();
@@ -26,7 +26,7 @@ async function ndjsonMap(mapper, opt) {
26
26
  }),
27
27
  (0, __1.transformLimit)({ limit: limitOutput, sourceReadable: readable }),
28
28
  (0, __1.transformLogProgress)({ metric: 'saved', logEvery: logEveryOutput }),
29
- __1.fs2.createWriteStreamAsNDJSON(outputFilePath),
29
+ ...__1.fs2.createWriteStreamAsNDJSON(outputFilePath),
30
30
  ]);
31
31
  }
32
32
  exports.ndjsonMap = ndjsonMap;
@@ -41,14 +41,14 @@ function stringExtensions(joi) {
41
41
  let { min, max } = args;
42
42
  // Today allows +-14 hours gap to account for different timezones
43
43
  if (max === 'today') {
44
- max = (0, js_lib_1.localTimeNow)().plus(14, 'hour').toISODate();
44
+ max = getTodayStrPlus15();
45
45
  }
46
46
  if (min === 'today') {
47
- min = (0, js_lib_1.localTimeNow)().minus(14, 'hour').toISODate();
47
+ min = getTodayStrMinus15();
48
48
  }
49
49
  // console.log('min/max', min, max)
50
- const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
51
- if (!m || m.length <= 1) {
50
+ const parts = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
51
+ if (!parts || parts.length < 4) {
52
52
  err = 'string.dateString';
53
53
  }
54
54
  else if (min && v < min) {
@@ -57,8 +57,7 @@ function stringExtensions(joi) {
57
57
  else if (max && v > max) {
58
58
  err = 'string.dateStringMax';
59
59
  }
60
- else if (!js_lib_1.LocalDate.isValid(v)) {
61
- // todo: replace with another regex (from ajv-validators) for speed
60
+ else if (!isValidDate(parts)) {
62
61
  err = 'string.dateStringCalendarAccuracy';
63
62
  }
64
63
  if (err) {
@@ -71,3 +70,39 @@ function stringExtensions(joi) {
71
70
  };
72
71
  }
73
72
  exports.stringExtensions = stringExtensions;
73
+ const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
74
+ // Based on: https://github.com/ajv-validator
75
+ function isValidDate(parts) {
76
+ const year = Number(parts[1]);
77
+ const month = Number(parts[2]);
78
+ const day = Number(parts[3]);
79
+ return (month >= 1 &&
80
+ month <= 12 &&
81
+ day >= 1 &&
82
+ day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month]));
83
+ }
84
+ function isLeapYear(year) {
85
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
86
+ }
87
+ let lastCheckedPlus = 0;
88
+ let todayStrPlusCached;
89
+ let lastCheckedMinus = 0;
90
+ let todayStrMinusCached;
91
+ function getTodayStrPlus15() {
92
+ const now = Date.now();
93
+ if (now - lastCheckedPlus < 3_600_000) {
94
+ // cached for 1 hour
95
+ return todayStrPlusCached;
96
+ }
97
+ lastCheckedPlus = now;
98
+ return (todayStrPlusCached = (0, js_lib_1.localTimeNow)().plus(15, 'hour').toISODate());
99
+ }
100
+ function getTodayStrMinus15() {
101
+ const now = Date.now();
102
+ if (now - lastCheckedMinus < 3_600_000) {
103
+ // cached for 1 hour
104
+ return todayStrMinusCached;
105
+ }
106
+ lastCheckedMinus = now;
107
+ return (todayStrMinusCached = (0, js_lib_1.localTimeNow)().plus(-15, 'hour').toISODate());
108
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
- "version": "13.23.0",
3
+ "version": "13.24.0",
4
4
  "scripts": {
5
5
  "prepare": "husky",
6
6
  "docs-serve": "vuepress dev docs",
package/src/fs/fs2.ts CHANGED
@@ -19,10 +19,10 @@ import fs from 'node:fs'
19
19
  import fsp from 'node:fs/promises'
20
20
  import path from 'node:path'
21
21
  import { createGzip, createUnzip } from 'node:zlib'
22
- import { _jsonParse } from '@naturalcycles/js-lib'
22
+ import { _isTruthy, _jsonParse } from '@naturalcycles/js-lib'
23
23
  import yaml, { DumpOptions } from 'js-yaml'
24
24
  import { transformToNDJson } from '../stream/ndjson/transformToNDJson'
25
- import { ReadableTyped, WritableTyped } from '../stream/stream.model'
25
+ import { ReadableTyped, TransformTyped } from '../stream/stream.model'
26
26
  import { transformSplitOnNewline } from '../stream/transform/transformSplit'
27
27
  import { requireFileToExist } from '../util/env.util'
28
28
 
@@ -353,31 +353,28 @@ class FS2 {
353
353
  }
354
354
 
355
355
  /*
356
- Returns a Writable.
356
+ Returns an array of Transforms, so that you can ...destructure them at
357
+ the end of the _pipeline.
357
358
 
358
359
  Replaces a list of operations:
359
360
  - transformToNDJson
360
361
  - createGzip (only if path ends with '.gz')
361
362
  - fs.createWriteStream
362
363
  */
363
- createWriteStreamAsNDJSON(outputPath: string): WritableTyped<any> {
364
+ createWriteStreamAsNDJSON(outputPath: string): TransformTyped<any, any>[] {
364
365
  this.ensureFile(outputPath)
365
366
 
366
- const transform1 = transformToNDJson()
367
- let transform = transform1
368
- if (outputPath.endsWith('.gz')) {
369
- transform = transform.pipe(
370
- createGzip({
371
- // chunkSize: 64 * 1024, // no observed speedup
372
- }),
373
- )
374
- }
375
- transform.pipe(
367
+ return [
368
+ transformToNDJson(),
369
+ outputPath.endsWith('.gz')
370
+ ? createGzip({
371
+ // chunkSize: 64 * 1024, // no observed speedup
372
+ })
373
+ : undefined,
376
374
  fs.createWriteStream(outputPath, {
377
375
  // highWaterMark: 64 * 1024, // no observed speedup
378
376
  }),
379
- )
380
- return transform1
377
+ ].filter(_isTruthy) as TransformTyped<any, any>[]
381
378
  }
382
379
  }
383
380
 
@@ -60,6 +60,6 @@ export async function ndjsonMap<IN = any, OUT = any>(
60
60
  }),
61
61
  transformLimit({ limit: limitOutput, sourceReadable: readable }),
62
62
  transformLogProgress({ metric: 'saved', logEvery: logEveryOutput }),
63
- fs2.createWriteStreamAsNDJSON(outputFilePath),
63
+ ...fs2.createWriteStreamAsNDJSON(outputFilePath),
64
64
  ])
65
65
  }
@@ -1,4 +1,4 @@
1
- import { LocalDate, localTimeNow } from '@naturalcycles/js-lib'
1
+ import { localTimeNow } from '@naturalcycles/js-lib'
2
2
  import Joi, { Extension, StringSchema as JoiStringSchema } from 'joi'
3
3
 
4
4
  export interface StringSchema<TSchema = string> extends JoiStringSchema<TSchema> {
@@ -51,22 +51,21 @@ export function stringExtensions(joi: typeof Joi): Extension {
51
51
 
52
52
  // Today allows +-14 hours gap to account for different timezones
53
53
  if (max === 'today') {
54
- max = localTimeNow().plus(14, 'hour').toISODate()
54
+ max = getTodayStrPlus15()
55
55
  }
56
56
  if (min === 'today') {
57
- min = localTimeNow().minus(14, 'hour').toISODate()
57
+ min = getTodayStrMinus15()
58
58
  }
59
59
  // console.log('min/max', min, max)
60
60
 
61
- const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v)
62
- if (!m || m.length <= 1) {
61
+ const parts = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v)
62
+ if (!parts || parts.length < 4) {
63
63
  err = 'string.dateString'
64
64
  } else if (min && v < min) {
65
65
  err = 'string.dateStringMin'
66
66
  } else if (max && v > max) {
67
67
  err = 'string.dateStringMax'
68
- } else if (!LocalDate.isValid(v)) {
69
- // todo: replace with another regex (from ajv-validators) for speed
68
+ } else if (!isValidDate(parts)) {
70
69
  err = 'string.dateStringCalendarAccuracy'
71
70
  }
72
71
 
@@ -80,3 +79,48 @@ export function stringExtensions(joi: typeof Joi): Extension {
80
79
  },
81
80
  }
82
81
  }
82
+
83
+ const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
84
+ // Based on: https://github.com/ajv-validator
85
+ function isValidDate(parts: string[]): boolean {
86
+ const year = Number(parts[1])
87
+ const month = Number(parts[2])
88
+ const day = Number(parts[3])
89
+ return (
90
+ month >= 1 &&
91
+ month <= 12 &&
92
+ day >= 1 &&
93
+ day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month]!)
94
+ )
95
+ }
96
+
97
+ function isLeapYear(year: number): boolean {
98
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
99
+ }
100
+
101
+ let lastCheckedPlus = 0
102
+ let todayStrPlusCached: string
103
+ let lastCheckedMinus = 0
104
+ let todayStrMinusCached: string
105
+
106
+ function getTodayStrPlus15(): string {
107
+ const now = Date.now()
108
+ if (now - lastCheckedPlus < 3_600_000) {
109
+ // cached for 1 hour
110
+ return todayStrPlusCached
111
+ }
112
+
113
+ lastCheckedPlus = now
114
+ return (todayStrPlusCached = localTimeNow().plus(15, 'hour').toISODate())
115
+ }
116
+
117
+ function getTodayStrMinus15(): string {
118
+ const now = Date.now()
119
+ if (now - lastCheckedMinus < 3_600_000) {
120
+ // cached for 1 hour
121
+ return todayStrMinusCached
122
+ }
123
+
124
+ lastCheckedMinus = now
125
+ return (todayStrMinusCached = localTimeNow().plus(-15, 'hour').toISODate())
126
+ }