ascertain 3.0.0 → 3.1.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/build/index.cjs CHANGED
@@ -30,6 +30,9 @@ _export(exports, {
30
30
  get ascertain () {
31
31
  return ascertain;
32
32
  },
33
+ get check () {
34
+ return check;
35
+ },
33
36
  get compile () {
34
37
  return compile;
35
38
  },
@@ -39,9 +42,36 @@ _export(exports, {
39
42
  get discriminated () {
40
43
  return discriminated;
41
44
  },
45
+ get format () {
46
+ return format;
47
+ },
42
48
  get fromBase64 () {
43
49
  return fromBase64;
44
50
  },
51
+ get gt () {
52
+ return gt;
53
+ },
54
+ get integer () {
55
+ return integer;
56
+ },
57
+ get lt () {
58
+ return lt;
59
+ },
60
+ get max () {
61
+ return max;
62
+ },
63
+ get maxLength () {
64
+ return maxLength;
65
+ },
66
+ get min () {
67
+ return min;
68
+ },
69
+ get minLength () {
70
+ return minLength;
71
+ },
72
+ get multipleOf () {
73
+ return multipleOf;
74
+ },
45
75
  get optional () {
46
76
  return optional;
47
77
  },
@@ -53,6 +83,9 @@ _export(exports, {
53
83
  },
54
84
  get tuple () {
55
85
  return tuple;
86
+ },
87
+ get uniqueItems () {
88
+ return uniqueItems;
56
89
  }
57
90
  });
58
91
  const $keys = Symbol.for('@@keys');
@@ -64,6 +97,7 @@ const AND = Symbol.for('@@and');
64
97
  const OPTIONAL = Symbol.for('@@optional');
65
98
  const TUPLE = Symbol.for('@@tuple');
66
99
  const DISCRIMINATED = Symbol.for('@@discriminated');
100
+ const CHECK = Symbol.for('@@check');
67
101
  const OrCtor = function(schemas) {
68
102
  this.schemas = schemas;
69
103
  };
@@ -87,6 +121,10 @@ const DiscriminatedCtor = function(schemas, key) {
87
121
  this.key = key;
88
122
  };
89
123
  DiscriminatedCtor.prototype[$op] = DISCRIMINATED;
124
+ const CheckCtor = function(compileFn) {
125
+ this.compile = compileFn;
126
+ };
127
+ CheckCtor.prototype[$op] = CHECK;
90
128
  const or = (...schemas)=>{
91
129
  if (schemas.length === 0) throw new TypeError('Operator requires at least one schema');
92
130
  return new OrCtor(schemas);
@@ -104,6 +142,54 @@ const discriminated = (schemas, key)=>{
104
142
  if (schemas.length === 0) throw new TypeError('discriminated requires at least one schema');
105
143
  return new DiscriminatedCtor(schemas, key);
106
144
  };
145
+ const check = (fnOrOpts, message)=>{
146
+ if (typeof fnOrOpts === 'function') {
147
+ return new CheckCtor((v, ctx)=>{
148
+ const fnRef = ctx.ref(fnOrOpts);
149
+ return {
150
+ check: `!${fnRef}(${v})`,
151
+ message: message ? JSON.stringify(message) : `\`check failed for value \${${v}}\``
152
+ };
153
+ });
154
+ }
155
+ return new CheckCtor(fnOrOpts.compile);
156
+ };
157
+ const min = (n, message)=>new CheckCtor((v)=>({
158
+ check: `${v} < ${n}`,
159
+ message: message ? JSON.stringify(message) : `\`must be >= ${n}, got \${${v}}\``
160
+ }));
161
+ const max = (n, message)=>new CheckCtor((v)=>({
162
+ check: `${v} > ${n}`,
163
+ message: message ? JSON.stringify(message) : `\`must be <= ${n}, got \${${v}}\``
164
+ }));
165
+ const integer = (message)=>new CheckCtor((v)=>({
166
+ check: `!Number.isInteger(${v})`,
167
+ message: message ? JSON.stringify(message) : `\`must be an integer, got \${${v}}\``
168
+ }));
169
+ const minLength = (n, message)=>new CheckCtor((v)=>({
170
+ check: `${v}.length < ${n}`,
171
+ message: message ? JSON.stringify(message) : `\`length must be >= ${n}, got \${${v}.length}\``
172
+ }));
173
+ const maxLength = (n, message)=>new CheckCtor((v)=>({
174
+ check: `${v}.length > ${n}`,
175
+ message: message ? JSON.stringify(message) : `\`length must be <= ${n}, got \${${v}.length}\``
176
+ }));
177
+ const gt = (n, message)=>new CheckCtor((v)=>({
178
+ check: `${v} <= ${n}`,
179
+ message: message ? JSON.stringify(message) : `\`must be > ${n}, got \${${v}}\``
180
+ }));
181
+ const lt = (n, message)=>new CheckCtor((v)=>({
182
+ check: `${v} >= ${n}`,
183
+ message: message ? JSON.stringify(message) : `\`must be < ${n}, got \${${v}}\``
184
+ }));
185
+ const multipleOf = (n, message)=>new CheckCtor((v)=>({
186
+ check: `${v} % ${n} !== 0`,
187
+ message: message ? JSON.stringify(message) : `\`must be a multiple of ${n}, got \${${v}}\``
188
+ }));
189
+ const uniqueItems = (message)=>new CheckCtor((v)=>({
190
+ check: `new Set(${v}).size !== ${v}.length`,
191
+ message: message ? JSON.stringify(message) : `\`must have unique items\``
192
+ }));
107
193
  const fromBase64 = typeof Buffer === 'undefined' ? (value)=>atob(value) : (value)=>Buffer.from(value, 'base64').toString('utf-8');
108
194
  const MULTIPLIERS = {
109
195
  ms: 1,
@@ -168,6 +254,86 @@ const as = {
168
254
  }
169
255
  }
170
256
  };
257
+ const DATETIME_RE = /^\d{4}-[01]\d-[0-3]\d[t\s](?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d{2}(?::?\d{2})?)$/i;
258
+ const TIME_FMT_RE = /^(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d{2}(?::?\d{2})?)$/i;
259
+ const DURATION_RE = /^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?$/;
260
+ const EMAIL_RE = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
261
+ const IDN_EMAIL_RE = /^[a-z0-9!#$%&'*+/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]+)*@(?:[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF](?:[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]*[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?\.)+[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF](?:[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]*[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?$/i;
262
+ const HOSTNAME_RE = /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*\.?$/i;
263
+ const IDN_HOSTNAME_RE = /^(?=.{1,253}\.?$)[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF](?:[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]{0,61}[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?(?:\.[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF](?:[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]{0,61}[a-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?)*\.?$/i;
264
+ const IPV4_RE = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;
265
+ const IPV6_RE = /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?$/i;
266
+ const URI_RE = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
267
+ const isUriRef = (s)=>URI_RE.test(s) || /^[a-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]*$/i.test(s);
268
+ const IRI_RE = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|%[0-9a-f]{2})*)?$/i;
269
+ const isIriRef = (s)=>IRI_RE.test(s) || /^[a-z0-9\-._~:/?#\[\]@!$&'()*+,;=\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF%]*$/i.test(s);
270
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
271
+ const URI_TEMPLATE_RE = /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i;
272
+ const JSON_POINTER_RE = /^(?:\/(?:[^~/]|~0|~1)*)*$/;
273
+ const REL_JSON_POINTER_RE = /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/;
274
+ const DAYS = [
275
+ 0,
276
+ 31,
277
+ 28,
278
+ 31,
279
+ 30,
280
+ 31,
281
+ 30,
282
+ 31,
283
+ 31,
284
+ 30,
285
+ 31,
286
+ 30,
287
+ 31
288
+ ];
289
+ const isValidDate = (s)=>{
290
+ const m = /^\d{4}-(\d{2})-(\d{2})$/.exec(s);
291
+ if (!m) return false;
292
+ const month = +m[1], day = +m[2];
293
+ if (month < 1 || month > 12 || day < 1) return false;
294
+ if (month === 2) {
295
+ const y = +s.slice(0, 4);
296
+ return day <= (y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0) ? 29 : 28);
297
+ }
298
+ return day <= DAYS[month];
299
+ };
300
+ const isValidRegex = (s)=>{
301
+ try {
302
+ new RegExp(s);
303
+ return true;
304
+ } catch {
305
+ return false;
306
+ }
307
+ };
308
+ const regexFormat = (re, name, message)=>new CheckCtor((v, ctx)=>({
309
+ check: `!${ctx.ref(re)}.test(${v})`,
310
+ message: message ? JSON.stringify(message) : `\`must be a valid ${name}, got \${${v}}\``
311
+ }));
312
+ const fnFormat = (fn, name, message)=>new CheckCtor((v, ctx)=>({
313
+ check: `!${ctx.ref(fn)}(${v})`,
314
+ message: message ? JSON.stringify(message) : `\`must be a valid ${name}, got \${${v}}\``
315
+ }));
316
+ const format = {
317
+ dateTime: (message)=>regexFormat(DATETIME_RE, 'date-time', message),
318
+ date: (message)=>fnFormat(isValidDate, 'date', message),
319
+ time: (message)=>regexFormat(TIME_FMT_RE, 'time', message),
320
+ duration: (message)=>regexFormat(DURATION_RE, 'duration', message),
321
+ email: (message)=>regexFormat(EMAIL_RE, 'email', message),
322
+ idnEmail: (message)=>regexFormat(IDN_EMAIL_RE, 'idn-email', message),
323
+ hostname: (message)=>regexFormat(HOSTNAME_RE, 'hostname', message),
324
+ idnHostname: (message)=>regexFormat(IDN_HOSTNAME_RE, 'idn-hostname', message),
325
+ ipv4: (message)=>regexFormat(IPV4_RE, 'ipv4', message),
326
+ ipv6: (message)=>regexFormat(IPV6_RE, 'ipv6', message),
327
+ uri: (message)=>regexFormat(URI_RE, 'uri', message),
328
+ uriReference: (message)=>fnFormat(isUriRef, 'uri-reference', message),
329
+ iri: (message)=>regexFormat(IRI_RE, 'iri', message),
330
+ iriReference: (message)=>fnFormat(isIriRef, 'iri-reference', message),
331
+ uuid: (message)=>regexFormat(UUID_RE, 'uuid', message),
332
+ uriTemplate: (message)=>regexFormat(URI_TEMPLATE_RE, 'uri-template', message),
333
+ jsonPointer: (message)=>regexFormat(JSON_POINTER_RE, 'json-pointer', message),
334
+ relativeJsonPointer: (message)=>regexFormat(REL_JSON_POINTER_RE, 'relative-json-pointer', message),
335
+ regex: (message)=>fnFormat(isValidRegex, 'regex', message)
336
+ };
171
337
  class Context {
172
338
  registry = [];
173
339
  lookupMap = new Map();
@@ -282,6 +448,16 @@ const codeGen = (schema, context, valuePath, mode)=>{
282
448
  `else { ${schema.schemas.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx))).join('\n')} }`
283
449
  ].join('\n');
284
450
  }
451
+ } else if (tag === CHECK) {
452
+ const valueAlias = context.unique('v');
453
+ const ref = (v)=>`ctx.registry[${context.register(v)}]`;
454
+ const { check: cond, message } = schema.compile(valueAlias, {
455
+ ref
456
+ });
457
+ if (mode.fast) {
458
+ return `const ${valueAlias} = ${valuePath};\nif (${cond}) { ${fail} }`;
459
+ }
460
+ return `const ${valueAlias} = ${valuePath};\nif (${cond}) { ${emit(message)} }`;
285
461
  } else {
286
462
  const { key, schemas } = schema;
287
463
  const valueAlias = context.unique('v');