@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.
@@ -89,16 +89,16 @@ export const j = {
89
89
  const isValueOptional = schema.getSchema().optionalField
90
90
  const builtSchema = schema.build()
91
91
  const finalValueSchema: JsonSchema = isValueOptional
92
- ? { anyOf: [{ isUndefined: true }, builtSchema] }
92
+ ? { ifDefined: builtSchema }
93
93
  : builtSchema
94
94
 
95
+ // `additionalProperties` instead of `patternProperties: { '^.+$': ... }`, which made Ajv
96
+ // regex-test every key twice (once more for `additionalProperties: false`)
95
97
  return new JObject<StringMap<SchemaOut<S>>>(
96
98
  {},
97
99
  {
98
100
  hasIsOfTypeCheck: false,
99
- patternProperties: {
100
- '^.+$': finalValueSchema,
101
- },
101
+ additionalProperties: finalValueSchema,
102
102
  },
103
103
  )
104
104
  },
@@ -917,31 +917,30 @@ export class JIsoDate<Opt extends boolean = false> extends JBuilder<IsoDate, Opt
917
917
  }
918
918
 
919
919
  before(date: string): this {
920
- return this.cloneAndUpdateSchema({ IsoDate: { before: date } })
920
+ return this.addRules({ before: date })
921
921
  }
922
922
 
923
923
  sameOrBefore(date: string): this {
924
- return this.cloneAndUpdateSchema({ IsoDate: { sameOrBefore: date } })
924
+ return this.addRules({ sameOrBefore: date })
925
925
  }
926
926
 
927
927
  after(date: string): this {
928
- return this.cloneAndUpdateSchema({ IsoDate: { after: date } })
928
+ return this.addRules({ after: date })
929
929
  }
930
930
 
931
931
  sameOrAfter(date: string): this {
932
- return this.cloneAndUpdateSchema({ IsoDate: { sameOrAfter: date } })
932
+ return this.addRules({ sameOrAfter: date })
933
933
  }
934
934
 
935
935
  between(fromDate: string, toDate: string, incl: Inclusiveness): this {
936
- let schemaPatch: Partial<JsonSchema> = {}
937
-
938
936
  if (incl === '[)') {
939
- schemaPatch = { IsoDate: { sameOrAfter: fromDate, before: toDate } }
940
- } else if (incl === '[]') {
941
- schemaPatch = { IsoDate: { sameOrAfter: fromDate, sameOrBefore: toDate } }
937
+ return this.addRules({ sameOrAfter: fromDate, before: toDate })
942
938
  }
939
+ return this.addRules({ sameOrAfter: fromDate, sameOrBefore: toDate })
940
+ }
943
941
 
944
- return this.cloneAndUpdateSchema(schemaPatch)
942
+ private addRules(rules: JsonSchemaIsoDateOptions): this {
943
+ return this.cloneAndUpdateSchema({ IsoDate: { ...this.schema.IsoDate, ...rules } })
945
944
  }
946
945
  }
947
946
 
@@ -1108,7 +1107,7 @@ export class JObject<OUT extends AnyObject, Opt extends boolean = false> extends
1108
1107
  type: 'object',
1109
1108
  properties: {},
1110
1109
  required: [],
1111
- additionalProperties: false,
1110
+ additionalProperties: opt?.additionalProperties ?? false,
1112
1111
  hasIsOfTypeCheck: opt?.hasIsOfTypeCheck ?? true,
1113
1112
  patternProperties: opt?.patternProperties ?? undefined,
1114
1113
  keySchema: opt?.keySchema ?? undefined,
@@ -1122,6 +1121,8 @@ export class JObject<OUT extends AnyObject, Opt extends boolean = false> extends
1122
1121
  */
1123
1122
 
1124
1123
  allowAdditionalProperties(): this {
1124
+ // A string map already accepts every property, validated by its value schema
1125
+ if (typeof this.schema.additionalProperties === 'object') return this.clone()
1125
1126
  return this.cloneAndUpdateSchema({ additionalProperties: true })
1126
1127
  }
1127
1128
 
@@ -1207,6 +1208,10 @@ export class JObject<OUT extends AnyObject, Opt extends boolean = false> extends
1207
1208
 
1208
1209
  interface JObjectOpts {
1209
1210
  hasIsOfTypeCheck?: false
1211
+ /**
1212
+ * Schema for every property not listed in `properties`, e.g the values of a string map.
1213
+ */
1214
+ additionalProperties?: JsonSchema<any>
1210
1215
  patternProperties?: StringMap<JsonSchema<any>>
1211
1216
  keySchema?: JsonSchema
1212
1217
  }
@@ -1477,17 +1482,14 @@ function record<
1477
1482
  const isValueOptional = (valueSchema as JSchema<any, any>).getSchema().optionalField
1478
1483
  const valueJsonSchema = valueSchema.build()
1479
1484
 
1480
- // When value schema is optional, wrap in anyOf to allow undefined values
1481
1485
  const finalValueSchema: JsonSchema = isValueOptional
1482
- ? { anyOf: [{ isUndefined: true }, valueJsonSchema] }
1486
+ ? { ifDefined: valueJsonSchema }
1483
1487
  : valueJsonSchema
1484
1488
 
1485
1489
  return new JObject([], {
1486
1490
  hasIsOfTypeCheck: false,
1487
1491
  keySchema: keyJsonSchema,
1488
- patternProperties: {
1489
- ['^.*$']: finalValueSchema,
1490
- },
1492
+ additionalProperties: finalValueSchema,
1491
1493
  }) as any
1492
1494
  }
1493
1495
 
@@ -2086,7 +2088,7 @@ export interface JsonSchema<OUT = unknown> {
2086
2088
  }
2087
2089
  patternProperties?: StringMap<JsonSchema<any>>
2088
2090
  required?: string[]
2089
- additionalProperties?: boolean
2091
+ additionalProperties?: boolean | JsonSchema<any>
2090
2092
  minProperties?: number
2091
2093
  maxProperties?: number
2092
2094
 
@@ -2146,6 +2148,10 @@ export interface JsonSchema<OUT = unknown> {
2146
2148
  errorMessages?: StringMap<string>
2147
2149
  optionalValues?: (string | number | boolean | null)[]
2148
2150
  keySchema?: JsonSchema
2151
+ /**
2152
+ * Validates the value against the given schema only when it's defined.
2153
+ */
2154
+ ifDefined?: JsonSchema
2149
2155
  isUndefined?: true
2150
2156
  minProperties2?: number
2151
2157
  exclusiveProperties?: (readonly string[])[]
@@ -62,11 +62,12 @@ export function mergeJsonSchemaObjects<T1 extends AnyObject, T2 extends AnyObjec
62
62
  })
63
63
 
64
64
  // Merge `patternProperties`
65
- Object.entries(s2.patternProperties || {}).forEach(([k, v]) => {
66
- s1.patternProperties[k] = v
67
- })
65
+ if (s2.patternProperties) {
66
+ s1.patternProperties = { ...s1.patternProperties, ...s2.patternProperties }
67
+ }
68
68
 
69
69
  s1.propertyNames = s2.propertyNames || s1.propertyNames
70
+ s1.keySchema = s2.keySchema || s1.keySchema
70
71
  s1.minProperties = s2.minProperties ?? s1.minProperties
71
72
  s1.maxProperties = s2.maxProperties ?? s1.maxProperties
72
73
 
@@ -74,7 +75,10 @@ export function mergeJsonSchemaObjects<T1 extends AnyObject, T2 extends AnyObjec
74
75
  s1.required.push(...s2.required)
75
76
  s1.required = _uniq(s1.required).sort()
76
77
 
77
- // `additionalProperties` remains the same
78
+ // `additionalProperties` remains the same, unless s2 is a string map (schema-valued)
79
+ if (typeof s2.additionalProperties === 'object') {
80
+ s1.additionalProperties = s2.additionalProperties
81
+ }
78
82
 
79
83
  return _filterNullishValues(s1, { mutate: true })
80
84
  }
@@ -11,6 +11,8 @@ entry-name normalization/validation.
11
11
 
12
12
  import type { UnixTimestamp } from '@naturalcycles/js-lib/types'
13
13
 
14
+ // oxlint-disable no-restricted-globals -- DOS date/time fields are local-time components, converted via the native Date
15
+
14
16
  // oxlint-disable no-bitwise -- the ZIP format packs DOS date/time into bit fields
15
17
 
16
18
  // Compression methods.
@@ -31,6 +31,7 @@ import { finished, pipeline } from 'node:stream/promises'
31
31
  import { promisify } from 'node:util'
32
32
  import zlib from 'node:zlib'
33
33
  import { comparators } from '@naturalcycles/js-lib/array/sort.js'
34
+ import { nowUnix } from '@naturalcycles/js-lib/datetime/localTime.js'
34
35
  import { _assert } from '@naturalcycles/js-lib/error/assert.js'
35
36
  import type { UnixTimestamp } from '@naturalcycles/js-lib/types'
36
37
  import { glob } from 'tinyglobby'
@@ -297,7 +298,7 @@ export class ZipWriter implements AsyncDisposable {
297
298
  if (nameBuf.length > MAX_COMMENT_SIZE) {
298
299
  throw new Error(`zip entry name is too long: ${nameBuf.length} > ${MAX_COMMENT_SIZE} bytes`)
299
300
  }
300
- const mtime = opt.mtime ?? (Math.floor(Date.now() / 1000) as UnixTimestamp)
301
+ const mtime = opt.mtime ?? nowUnix()
301
302
  const { date, time } = unixToDosDateTime(mtime)
302
303
  const mode = opt.mode ?? (isDirectory ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE)
303
304
  if ((mode & 0xffff) !== mode) {