ascertain 3.0.0 → 3.2.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,39 @@ _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
+ },
75
+ get oneOf () {
76
+ return oneOf;
77
+ },
45
78
  get optional () {
46
79
  return optional;
47
80
  },
@@ -53,6 +86,9 @@ _export(exports, {
53
86
  },
54
87
  get tuple () {
55
88
  return tuple;
89
+ },
90
+ get uniqueItems () {
91
+ return uniqueItems;
56
92
  }
57
93
  });
58
94
  const $keys = Symbol.for('@@keys');
@@ -64,6 +100,7 @@ const AND = Symbol.for('@@and');
64
100
  const OPTIONAL = Symbol.for('@@optional');
65
101
  const TUPLE = Symbol.for('@@tuple');
66
102
  const DISCRIMINATED = Symbol.for('@@discriminated');
103
+ const CHECK = Symbol.for('@@check');
67
104
  const OrCtor = function(schemas) {
68
105
  this.schemas = schemas;
69
106
  };
@@ -87,6 +124,10 @@ const DiscriminatedCtor = function(schemas, key) {
87
124
  this.key = key;
88
125
  };
89
126
  DiscriminatedCtor.prototype[$op] = DISCRIMINATED;
127
+ const CheckCtor = function(compileFn) {
128
+ this.compile = compileFn;
129
+ };
130
+ CheckCtor.prototype[$op] = CHECK;
90
131
  const or = (...schemas)=>{
91
132
  if (schemas.length === 0) throw new TypeError('Operator requires at least one schema');
92
133
  return new OrCtor(schemas);
@@ -104,6 +145,63 @@ const discriminated = (schemas, key)=>{
104
145
  if (schemas.length === 0) throw new TypeError('discriminated requires at least one schema');
105
146
  return new DiscriminatedCtor(schemas, key);
106
147
  };
148
+ const check = (fnOrOpts, message)=>{
149
+ if (typeof fnOrOpts === 'function') {
150
+ return new CheckCtor((v, ctx)=>{
151
+ const fnRef = ctx.ref(fnOrOpts);
152
+ return {
153
+ check: `!${fnRef}(${v})`,
154
+ message: message ? JSON.stringify(message) : `\`check failed for value \${${v}}\``
155
+ };
156
+ });
157
+ }
158
+ return new CheckCtor(fnOrOpts.compile);
159
+ };
160
+ const min = (n, message)=>new CheckCtor((v)=>({
161
+ check: `${v} < ${n}`,
162
+ message: message ? JSON.stringify(message) : `\`must be >= ${n}, got \${${v}}\``
163
+ }));
164
+ const max = (n, message)=>new CheckCtor((v)=>({
165
+ check: `${v} > ${n}`,
166
+ message: message ? JSON.stringify(message) : `\`must be <= ${n}, got \${${v}}\``
167
+ }));
168
+ const integer = (message)=>new CheckCtor((v)=>({
169
+ check: `!Number.isInteger(${v})`,
170
+ message: message ? JSON.stringify(message) : `\`must be an integer, got \${${v}}\``
171
+ }));
172
+ const minLength = (n, message)=>new CheckCtor((v)=>({
173
+ check: `${v}.length < ${n}`,
174
+ message: message ? JSON.stringify(message) : `\`length must be >= ${n}, got \${${v}.length}\``
175
+ }));
176
+ const maxLength = (n, message)=>new CheckCtor((v)=>({
177
+ check: `${v}.length > ${n}`,
178
+ message: message ? JSON.stringify(message) : `\`length must be <= ${n}, got \${${v}.length}\``
179
+ }));
180
+ const gt = (n, message)=>new CheckCtor((v)=>({
181
+ check: `${v} <= ${n}`,
182
+ message: message ? JSON.stringify(message) : `\`must be > ${n}, got \${${v}}\``
183
+ }));
184
+ const lt = (n, message)=>new CheckCtor((v)=>({
185
+ check: `${v} >= ${n}`,
186
+ message: message ? JSON.stringify(message) : `\`must be < ${n}, got \${${v}}\``
187
+ }));
188
+ const multipleOf = (n, message)=>new CheckCtor((v)=>({
189
+ check: `${v} % ${n} !== 0`,
190
+ message: message ? JSON.stringify(message) : `\`must be a multiple of ${n}, got \${${v}}\``
191
+ }));
192
+ const uniqueItems = (message)=>new CheckCtor((v)=>({
193
+ check: `new Set(${v}).size !== ${v}.length`,
194
+ message: message ? JSON.stringify(message) : `\`must have unique items\``
195
+ }));
196
+ const oneOf = (values, message)=>{
197
+ const set = new Set(Array.isArray(values) ? values : Object.values(values));
198
+ return new CheckCtor((v, ctx)=>({
199
+ check: `!${ctx.ref(set)}.has(${v})`,
200
+ message: message ? JSON.stringify(message) : `\`must be one of [${[
201
+ ...set
202
+ ].map(toLiteral).join(', ')}], got \${${v}}\``
203
+ }));
204
+ };
107
205
  const fromBase64 = typeof Buffer === 'undefined' ? (value)=>atob(value) : (value)=>Buffer.from(value, 'base64').toString('utf-8');
108
206
  const MULTIPLIERS = {
109
207
  ms: 1,
@@ -168,6 +266,86 @@ const as = {
168
266
  }
169
267
  }
170
268
  };
269
+ 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;
270
+ 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;
271
+ const DURATION_RE = /^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?$/;
272
+ 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;
273
+ 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;
274
+ 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;
275
+ 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;
276
+ 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)$/;
277
+ 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;
278
+ 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;
279
+ const isUriRef = (s)=>URI_RE.test(s) || /^[a-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]*$/i.test(s);
280
+ 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;
281
+ const isIriRef = (s)=>IRI_RE.test(s) || /^[a-z0-9\-._~:/?#\[\]@!$&'()*+,;=\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF%]*$/i.test(s);
282
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
283
+ 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;
284
+ const JSON_POINTER_RE = /^(?:\/(?:[^~/]|~0|~1)*)*$/;
285
+ const REL_JSON_POINTER_RE = /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/;
286
+ const DAYS = [
287
+ 0,
288
+ 31,
289
+ 28,
290
+ 31,
291
+ 30,
292
+ 31,
293
+ 30,
294
+ 31,
295
+ 31,
296
+ 30,
297
+ 31,
298
+ 30,
299
+ 31
300
+ ];
301
+ const isValidDate = (s)=>{
302
+ const m = /^\d{4}-(\d{2})-(\d{2})$/.exec(s);
303
+ if (!m) return false;
304
+ const month = +m[1], day = +m[2];
305
+ if (month < 1 || month > 12 || day < 1) return false;
306
+ if (month === 2) {
307
+ const y = +s.slice(0, 4);
308
+ return day <= (y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0) ? 29 : 28);
309
+ }
310
+ return day <= DAYS[month];
311
+ };
312
+ const isValidRegex = (s)=>{
313
+ try {
314
+ new RegExp(s);
315
+ return true;
316
+ } catch {
317
+ return false;
318
+ }
319
+ };
320
+ const regexFormat = (re, name, message)=>new CheckCtor((v, ctx)=>({
321
+ check: `!${ctx.ref(re)}.test(${v})`,
322
+ message: message ? JSON.stringify(message) : `\`must be a valid ${name}, got \${${v}}\``
323
+ }));
324
+ const fnFormat = (fn, name, message)=>new CheckCtor((v, ctx)=>({
325
+ check: `!${ctx.ref(fn)}(${v})`,
326
+ message: message ? JSON.stringify(message) : `\`must be a valid ${name}, got \${${v}}\``
327
+ }));
328
+ const format = {
329
+ dateTime: (message)=>regexFormat(DATETIME_RE, 'date-time', message),
330
+ date: (message)=>fnFormat(isValidDate, 'date', message),
331
+ time: (message)=>regexFormat(TIME_FMT_RE, 'time', message),
332
+ duration: (message)=>regexFormat(DURATION_RE, 'duration', message),
333
+ email: (message)=>regexFormat(EMAIL_RE, 'email', message),
334
+ idnEmail: (message)=>regexFormat(IDN_EMAIL_RE, 'idn-email', message),
335
+ hostname: (message)=>regexFormat(HOSTNAME_RE, 'hostname', message),
336
+ idnHostname: (message)=>regexFormat(IDN_HOSTNAME_RE, 'idn-hostname', message),
337
+ ipv4: (message)=>regexFormat(IPV4_RE, 'ipv4', message),
338
+ ipv6: (message)=>regexFormat(IPV6_RE, 'ipv6', message),
339
+ uri: (message)=>regexFormat(URI_RE, 'uri', message),
340
+ uriReference: (message)=>fnFormat(isUriRef, 'uri-reference', message),
341
+ iri: (message)=>regexFormat(IRI_RE, 'iri', message),
342
+ iriReference: (message)=>fnFormat(isIriRef, 'iri-reference', message),
343
+ uuid: (message)=>regexFormat(UUID_RE, 'uuid', message),
344
+ uriTemplate: (message)=>regexFormat(URI_TEMPLATE_RE, 'uri-template', message),
345
+ jsonPointer: (message)=>regexFormat(JSON_POINTER_RE, 'json-pointer', message),
346
+ relativeJsonPointer: (message)=>regexFormat(REL_JSON_POINTER_RE, 'relative-json-pointer', message),
347
+ regex: (message)=>fnFormat(isValidRegex, 'regex', message)
348
+ };
171
349
  class Context {
172
350
  registry = [];
173
351
  lookupMap = new Map();
@@ -282,6 +460,16 @@ const codeGen = (schema, context, valuePath, mode)=>{
282
460
  `else { ${schema.schemas.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx))).join('\n')} }`
283
461
  ].join('\n');
284
462
  }
463
+ } else if (tag === CHECK) {
464
+ const valueAlias = context.unique('v');
465
+ const ref = (v)=>`ctx.registry[${context.register(v)}]`;
466
+ const { check: cond, message } = schema.compile(valueAlias, {
467
+ ref
468
+ });
469
+ if (mode.fast) {
470
+ return `const ${valueAlias} = ${valuePath};\nif (${cond}) { ${fail} }`;
471
+ }
472
+ return `const ${valueAlias} = ${valuePath};\nif (${cond}) { ${emit(message)} }`;
285
473
  } else {
286
474
  const { key, schemas } = schema;
287
475
  const valueAlias = context.unique('v');