@naturalcycles/nodejs-lib 15.109.0 → 15.111.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/zip/index.d.ts +3 -0
- package/dist/zip/index.js +3 -0
- package/dist/zip/zip2.d.ts +37 -0
- package/dist/zip/zip2.js +51 -0
- package/dist/zip/zipInternal.d.ts +41 -0
- package/dist/zip/zipInternal.js +88 -0
- package/dist/zip/zipReader.d.ts +142 -0
- package/dist/zip/zipReader.js +475 -0
- package/dist/zip/zipWriter.d.ts +168 -0
- package/dist/zip/zipWriter.js +579 -0
- package/package.json +3 -3
- package/src/jwt/jwt.service.ts +1 -0
- package/src/stream/transform/worker/workerClassProxy.js +1 -0
- package/src/validation/ajv/jSchema.ts +10 -10
- package/src/zip/index.ts +3 -0
- package/src/zip/zip2.ts +56 -0
- package/src/zip/zipInternal.ts +111 -0
- package/src/zip/zipReader.ts +620 -0
- package/src/zip/zipWriter.ts +792 -0
|
@@ -414,7 +414,7 @@ export class JSchema<OUT, Opt>
|
|
|
414
414
|
* The usage of this function is discouraged as it defeats the purpose of having type-safe validation.
|
|
415
415
|
*/
|
|
416
416
|
castAs<T>(): JSchema<T, Opt> {
|
|
417
|
-
return this as
|
|
417
|
+
return this as any as JSchema<T, Opt>
|
|
418
418
|
}
|
|
419
419
|
|
|
420
420
|
/**
|
|
@@ -529,7 +529,7 @@ export class JSchema<OUT, Opt>
|
|
|
529
529
|
const clone = this.cloneAndUpdateSchema({
|
|
530
530
|
postValidation: fn,
|
|
531
531
|
})
|
|
532
|
-
return clone as
|
|
532
|
+
return clone as any as JSchema<OUT2, Opt>
|
|
533
533
|
}
|
|
534
534
|
|
|
535
535
|
/**
|
|
@@ -557,7 +557,7 @@ export class JBuilder<OUT, Opt> extends JSchema<OUT, Opt> {
|
|
|
557
557
|
* The usage of this function is discouraged as it defeats the purpose of having type-safe validation.
|
|
558
558
|
*/
|
|
559
559
|
override castAs<T>(): JBuilder<T, Opt> {
|
|
560
|
-
return this as
|
|
560
|
+
return this as any as JBuilder<T, Opt>
|
|
561
561
|
}
|
|
562
562
|
|
|
563
563
|
$schema($schema: string): this {
|
|
@@ -683,7 +683,7 @@ export class JBuilder<OUT, Opt> extends JSchema<OUT, Opt> {
|
|
|
683
683
|
const { customValidations = [] } = this.schema
|
|
684
684
|
return this.cloneAndUpdateSchema({
|
|
685
685
|
customValidations: [...customValidations, validator],
|
|
686
|
-
}) as
|
|
686
|
+
}) as any as JBuilder<OUT2, Opt>
|
|
687
687
|
}
|
|
688
688
|
|
|
689
689
|
/**
|
|
@@ -700,7 +700,7 @@ export class JBuilder<OUT, Opt> extends JSchema<OUT, Opt> {
|
|
|
700
700
|
const { customConversions = [] } = this.schema
|
|
701
701
|
return this.cloneAndUpdateSchema({
|
|
702
702
|
customConversions: [...customConversions, converter],
|
|
703
|
-
}) as
|
|
703
|
+
}) as any as JBuilder<OUT2, Opt>
|
|
704
704
|
}
|
|
705
705
|
}
|
|
706
706
|
|
|
@@ -783,7 +783,7 @@ export class JString<
|
|
|
783
783
|
}
|
|
784
784
|
|
|
785
785
|
branded<B extends string>(): JString<B, Opt> {
|
|
786
|
-
return this as
|
|
786
|
+
return this as any as JString<B, Opt>
|
|
787
787
|
}
|
|
788
788
|
|
|
789
789
|
/**
|
|
@@ -934,7 +934,7 @@ export class JNumber<
|
|
|
934
934
|
}
|
|
935
935
|
|
|
936
936
|
branded<B extends number>(): JNumber<B, Opt> {
|
|
937
|
-
return this as
|
|
937
|
+
return this as any as JNumber<B, Opt>
|
|
938
938
|
}
|
|
939
939
|
|
|
940
940
|
multipleOf(multipleOf: number): this {
|
|
@@ -1131,7 +1131,7 @@ export class JObject<OUT extends AnyObject, Opt extends boolean = false> extends
|
|
|
1131
1131
|
const clone = this.clone()
|
|
1132
1132
|
mergeJsonSchemaObjects(clone.schema as any, other.schema as any)
|
|
1133
1133
|
_objectAssign(clone.schema, { hasIsOfTypeCheck: false })
|
|
1134
|
-
return clone as
|
|
1134
|
+
return clone as JObject<OUT & OUT2, false>
|
|
1135
1135
|
}
|
|
1136
1136
|
|
|
1137
1137
|
/**
|
|
@@ -1229,7 +1229,7 @@ export class JObjectInfer<
|
|
|
1229
1229
|
// the new schema loses that quality.
|
|
1230
1230
|
_objectAssign(newBuilder.schema, { hasIsOfTypeCheck: false })
|
|
1231
1231
|
|
|
1232
|
-
return newBuilder as
|
|
1232
|
+
return newBuilder as any as JObjectInfer<
|
|
1233
1233
|
{
|
|
1234
1234
|
[K in keyof PROPS | keyof NEW_PROPS]: K extends keyof NEW_PROPS
|
|
1235
1235
|
? NEW_PROPS[K]
|
|
@@ -1321,7 +1321,7 @@ export class JEnum<
|
|
|
1321
1321
|
}
|
|
1322
1322
|
|
|
1323
1323
|
branded<B extends OUT>(): JEnum<B, Opt> {
|
|
1324
|
-
return this as
|
|
1324
|
+
return this as any as JEnum<B, Opt>
|
|
1325
1325
|
}
|
|
1326
1326
|
}
|
|
1327
1327
|
|
package/src/zip/index.ts
ADDED
package/src/zip/zip2.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { createWriteStream } from 'node:fs'
|
|
1
2
|
import { promisify } from 'node:util'
|
|
2
3
|
import type { ZlibOptions, ZstdOptions } from 'node:zlib'
|
|
3
4
|
import zlib from 'node:zlib'
|
|
4
5
|
import type { Integer } from '@naturalcycles/js-lib/types'
|
|
6
|
+
import { openZip } from './zipReader.js'
|
|
7
|
+
import type { ZipEntry } from './zipReader.js'
|
|
8
|
+
import { createZipBuffer, zipPaths, ZipWriter } from './zipWriter.js'
|
|
9
|
+
import type { ZipFileEntry, ZipPathsOptions } from './zipWriter.js'
|
|
5
10
|
|
|
6
11
|
const deflateAsync = promisify(zlib.deflate.bind(zlib))
|
|
7
12
|
const inflateAsync = promisify(zlib.inflate.bind(zlib))
|
|
@@ -158,6 +163,57 @@ class Zip2 {
|
|
|
158
163
|
isGzipBuffer(input: Buffer): boolean {
|
|
159
164
|
return input[0] === 0x1f && input[1] === 0x8b
|
|
160
165
|
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Open a zip file and extract all of its entries into `destDir`, streaming each
|
|
169
|
+
* file to disk. Directories are created as needed.
|
|
170
|
+
*
|
|
171
|
+
* Path-traversal attempts (absolute paths, `..` segments, or paths escaping
|
|
172
|
+
* `destDir`) are rejected.
|
|
173
|
+
*
|
|
174
|
+
* Returns the list of extracted entries.
|
|
175
|
+
*/
|
|
176
|
+
async extractZipFileToDirectory(zipFilePath: string, destDir: string): Promise<ZipEntry[]> {
|
|
177
|
+
const zip = await openZip(zipFilePath)
|
|
178
|
+
try {
|
|
179
|
+
return await zip.extractAll(destDir)
|
|
180
|
+
} finally {
|
|
181
|
+
await zip.close()
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Create a zip archive on disk from a list of files and/or directories.
|
|
187
|
+
*/
|
|
188
|
+
async zipPaths(paths: string[], outputZipFilePath: string, opt?: ZipPathsOptions): Promise<void> {
|
|
189
|
+
return await zipPaths(paths, outputZipFilePath, opt)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Create a zip archive on disk, returning a {@link ZipWriter} that streams to it.
|
|
194
|
+
*
|
|
195
|
+
* Add entries with {@link ZipWriter.addFile}/{@link ZipWriter.addBuffer}/etc.,
|
|
196
|
+
* then call {@link ZipWriter.finalize} (or use `await using`):
|
|
197
|
+
*
|
|
198
|
+
* ```ts
|
|
199
|
+
* const zip = createZip('archive.zip')
|
|
200
|
+
* await zip.addFile('./photo.jpg')
|
|
201
|
+
* await zip.addBuffer(Buffer.from('{"a":1}'), 'data.json')
|
|
202
|
+
* await zip.finalize()
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
createZip(filePath: string): ZipWriter {
|
|
206
|
+
return new ZipWriter(createWriteStream(filePath))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Build a zip archive entirely in memory and return it as a Buffer.
|
|
211
|
+
*
|
|
212
|
+
* For large archives or streamed inputs prefer {@link createZip} / {@link ZipWriter}.
|
|
213
|
+
*/
|
|
214
|
+
async createZipBuffer(entries: ZipFileEntry[]): Promise<Buffer> {
|
|
215
|
+
return await createZipBuffer(entries)
|
|
216
|
+
}
|
|
161
217
|
}
|
|
162
218
|
|
|
163
219
|
export const zip2 = new Zip2()
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
Shared low-level helpers and constants for the zip reader and writer.
|
|
4
|
+
|
|
5
|
+
These are the parts of the ZIP file format that both reading (`zipReader.ts`) and
|
|
6
|
+
writing (`zipWriter.ts`) need in common: record signatures and fixed sizes, 64-bit
|
|
7
|
+
integer read/write helpers, DOS date/time conversion (both directions) and
|
|
8
|
+
entry-name normalization/validation.
|
|
9
|
+
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { UnixTimestamp } from '@naturalcycles/js-lib/types'
|
|
13
|
+
|
|
14
|
+
// oxlint-disable no-bitwise -- the ZIP format packs DOS date/time into bit fields
|
|
15
|
+
|
|
16
|
+
// Compression methods.
|
|
17
|
+
export const STORED = 0
|
|
18
|
+
export const DEFLATE = 8
|
|
19
|
+
|
|
20
|
+
// Record signatures (little-endian uint32) and fixed sizes (in bytes).
|
|
21
|
+
export const LOCAL_FILE_HEADER_SIG = 0x04034b50
|
|
22
|
+
export const DATA_DESCRIPTOR_SIG = 0x08074b50
|
|
23
|
+
export const CDFH_SIG = 0x02014b50
|
|
24
|
+
export const CDFH_SIZE = 46
|
|
25
|
+
export const EOCDR_SIG = 0x06054b50
|
|
26
|
+
export const EOCDR_SIZE = 22
|
|
27
|
+
export const ZIP64_EOCDL_SIG = 0x07064b50
|
|
28
|
+
export const ZIP64_EOCDL_SIZE = 20
|
|
29
|
+
export const ZIP64_EOCDR_SIG = 0x06064b50
|
|
30
|
+
export const ZIP64_EOCDR_SIZE = 56
|
|
31
|
+
|
|
32
|
+
/** The .zip comment and per-entry name/comment length fields are all 16-bit. */
|
|
33
|
+
export const MAX_COMMENT_SIZE = 0xffff
|
|
34
|
+
|
|
35
|
+
const MAX_SAFE_INTEGER_BIG = BigInt(Number.MAX_SAFE_INTEGER)
|
|
36
|
+
|
|
37
|
+
/** Read a 64-bit little-endian unsigned integer, rejecting values above `Number.MAX_SAFE_INTEGER`. */
|
|
38
|
+
export function readUInt64LE(buf: Buffer, offset: number): number {
|
|
39
|
+
const value = buf.readBigUInt64LE(offset)
|
|
40
|
+
if (value > MAX_SAFE_INTEGER_BIG) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
'zip file too large: 64-bit values above Number.MAX_SAFE_INTEGER are not supported',
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
return Number(value)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Write a 64-bit little-endian unsigned integer. */
|
|
49
|
+
export function writeUInt64LE(buf: Buffer, value: number, offset: number): void {
|
|
50
|
+
buf.writeBigUInt64LE(BigInt(value), offset)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Decode a packed DOS date + time pair into a {@link UnixTimestamp} (seconds).
|
|
55
|
+
* The DOS fields are local-time, so they are interpreted in the local timezone.
|
|
56
|
+
* Used when no Info-ZIP extended timestamp is present.
|
|
57
|
+
*/
|
|
58
|
+
export function dosDateTimeToUnix(date: number, time: number): UnixTimestamp {
|
|
59
|
+
const day = date & 0x1f // 1-31
|
|
60
|
+
const month = ((date >> 5) & 0x0f) - 1 // 1-12 -> 0-11
|
|
61
|
+
const year = ((date >> 9) & 0x7f) + 1980 // 0-127 -> 1980-2107
|
|
62
|
+
const second = (time & 0x1f) * 2 // 0-29 -> 0-58
|
|
63
|
+
const minute = (time >> 5) & 0x3f // 0-59
|
|
64
|
+
const hour = (time >> 11) & 0x1f // 0-23
|
|
65
|
+
return Math.floor(
|
|
66
|
+
new Date(year, month, day, hour, minute, second).getTime() / 1000,
|
|
67
|
+
) as UnixTimestamp
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const MIN_DOS_DATE = new Date(1980, 0, 1)
|
|
71
|
+
const MAX_DOS_DATE = new Date(2107, 11, 31, 23, 59, 58)
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Encode a {@link UnixTimestamp} (seconds) into the packed DOS date + time pair stored
|
|
75
|
+
* in local file and central directory headers. The DOS fields are local-time, so the
|
|
76
|
+
* timestamp is rendered in the local timezone. Out-of-range dates are clamped to 1980-2107.
|
|
77
|
+
*/
|
|
78
|
+
export function unixToDosDateTime(ts: UnixTimestamp): { date: number; time: number } {
|
|
79
|
+
const jsDate = new Date(ts * 1000)
|
|
80
|
+
const d = jsDate < MIN_DOS_DATE ? MIN_DOS_DATE : jsDate > MAX_DOS_DATE ? MAX_DOS_DATE : jsDate
|
|
81
|
+
|
|
82
|
+
const date =
|
|
83
|
+
(d.getDate() & 0x1f) | // 1-31
|
|
84
|
+
(((d.getMonth() + 1) & 0x0f) << 5) | // 1-12
|
|
85
|
+
(((d.getFullYear() - 1980) & 0x7f) << 9) // 1980-2107
|
|
86
|
+
|
|
87
|
+
const time =
|
|
88
|
+
Math.floor(d.getSeconds() / 2) | // 0-29
|
|
89
|
+
((d.getMinutes() & 0x3f) << 5) | // 0-59
|
|
90
|
+
((d.getHours() & 0x1f) << 11) // 0-23
|
|
91
|
+
|
|
92
|
+
return { date, time }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Normalize Windows-style separators, like yauzl in non-strict mode. */
|
|
96
|
+
export function normalizeZipEntryName(name: string): string {
|
|
97
|
+
return name.replaceAll('\\', '/')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Reject entry names that would escape the extraction directory: absolute paths
|
|
102
|
+
* (drive letters or a leading `/`) and any `..` path segment.
|
|
103
|
+
*/
|
|
104
|
+
export function assertSafeZipEntryName(name: string): void {
|
|
105
|
+
if (/^[a-z]:/i.test(name) || name.startsWith('/')) {
|
|
106
|
+
throw new Error(`absolute path in zip entry: ${name}`)
|
|
107
|
+
}
|
|
108
|
+
if (name.split('/').includes('..')) {
|
|
109
|
+
throw new Error(`invalid relative path in zip entry: ${name}`)
|
|
110
|
+
}
|
|
111
|
+
}
|