@es-joy/jsoe 0.0.1

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.
Files changed (43) hide show
  1. package/.editorconfig +15 -0
  2. package/.eslintignore +7 -0
  3. package/.eslintrc.cjs +120 -0
  4. package/CHANGES.md +5 -0
  5. package/LICENSE-MIT.txt +22 -0
  6. package/README.md +82 -0
  7. package/package.json +101 -0
  8. package/rollup.config.js +29 -0
  9. package/server.js +25 -0
  10. package/src/deepEqual.js +72 -0
  11. package/src/formats/indexedDBKey.js +66 -0
  12. package/src/formats/json.js +64 -0
  13. package/src/formats/schemaAndArbitrary.js +20 -0
  14. package/src/formats/schemaOnly.js +529 -0
  15. package/src/formats/structuredCloning.js +372 -0
  16. package/src/formats.js +229 -0
  17. package/src/fundamentalTypes/BooleanObjectType.js +55 -0
  18. package/src/fundamentalTypes/NumberObjectType.js +41 -0
  19. package/src/fundamentalTypes/StringObjectType.js +37 -0
  20. package/src/fundamentalTypes/arrayReferenceType.js +203 -0
  21. package/src/fundamentalTypes/arrayType.js +898 -0
  22. package/src/fundamentalTypes/bigintType.js +53 -0
  23. package/src/fundamentalTypes/dateType.js +129 -0
  24. package/src/fundamentalTypes/nullType.js +33 -0
  25. package/src/fundamentalTypes/numberType.js +56 -0
  26. package/src/fundamentalTypes/objectReferenceType.js +51 -0
  27. package/src/fundamentalTypes/objectType.js +30 -0
  28. package/src/fundamentalTypes/regexpType.js +95 -0
  29. package/src/fundamentalTypes/sparseUndefinedType.js +43 -0
  30. package/src/fundamentalTypes/stringType.js +31 -0
  31. package/src/fundamentalTypes/undefinedType.js +37 -0
  32. package/src/index.js +7 -0
  33. package/src/subTypes/blobHTMLType.js +160 -0
  34. package/src/subTypes/falseType.js +42 -0
  35. package/src/subTypes/trueType.js +42 -0
  36. package/src/superTypes/InfinitiesSuperType.js +49 -0
  37. package/src/superTypes/SpecialNumberSuperType.js +62 -0
  38. package/src/typeChoices.js +125 -0
  39. package/src/types.js +665 -0
  40. package/src/utils/dialogs.js +115 -0
  41. package/src/utils/jsonPointer.js +89 -0
  42. package/src/utils/templateUtils.js +106 -0
  43. package/src/utils/types.js +6 -0
@@ -0,0 +1,529 @@
1
+ /* eslint-disable
2
+ no-constant-condition,
3
+ sonarjs/no-empty-collection,
4
+ sonarjs/no-identical-conditions,
5
+ no-constant-binary-expression -- Just debugging */
6
+ import Formats from '../formats.js';
7
+ import * as structuredCloning from './structuredCloning.js';
8
+ import deepEqual from '../deepEqual.js';
9
+
10
+ /**
11
+ * @type {FormatIterator}
12
+ */
13
+ export const iterate = (records, stateObj) => {
14
+ console.log('records', records, stateObj);
15
+ const {schemaContent: schema} = stateObj;
16
+ if (!schema || typeof schema !== 'object') {
17
+ throw new TypeError(`Malformed schema provided`);
18
+ }
19
+ stateObj.format = 'schemaOnly';
20
+ /*
21
+ Before labeling ANY (unless only one match), we need to
22
+ confirm the whole is a match
23
+ */
24
+ stateObj[''] = schema;
25
+
26
+ const possibleSchemaObjects = [];
27
+ possibleSchemaObjects.map((possibleSchemaObject) => {
28
+ return flattenSchemas({possibleSchemaObject});
29
+ });
30
+
31
+ const resolveRef = (/* {schema, possibleSchemaObject} */) => {
32
+ // Todo?
33
+ };
34
+ const valueInEnumList = ({list, value}) => {
35
+ return list.some((en) => deepEqual(en, value));
36
+ };
37
+ const getTypesForValues = ({enumList}) => enumList.map(
38
+ (value) => Formats.getTypeForFormatStateAndValue({
39
+ value,
40
+ format: 'structuredCloning',
41
+ state: 'arrayNonindexKeys'
42
+ // state: 'sparseArrays'
43
+ })
44
+ );
45
+ /**
46
+ * @typedef {PlainObject} SchemaObject
47
+ * @property {string} type
48
+ */
49
+ /**
50
+ * Avoids duplicates for same type but additive for `enum`
51
+ * @param {Array<any>} anyOf
52
+ * @returns {SchemaObject[]}
53
+ */
54
+ const reduceAnyOf = (anyOf) => {
55
+ return anyOf.reduce((arr, schemaObj, _i) => {
56
+ const earlierSchemaObject = arr.find((schemaObject) => {
57
+ return schemaObject.type === schemaObj.type;
58
+ });
59
+ if (!earlierSchemaObject) {
60
+ arr.push(schemaObj);
61
+ } else if (earlierSchemaObject.enum) {
62
+ schemaObj.enum.forEach((value) => {
63
+ if (!valueInEnumList({
64
+ value,
65
+ list: earlierSchemaObject.enum
66
+ })) {
67
+ earlierSchemaObject.enum.push(value);
68
+ }
69
+ });
70
+ } else if ('enum' in schemaObj) {
71
+ earlierSchemaObject.enum = schemaObj.enum;
72
+ }
73
+ return arr;
74
+ }, []);
75
+ };
76
+ const reduceEnumList = ({enumList}) => {
77
+ const newEnumTypes = getTypesForValues({enumList});
78
+ return reduceAnyOf(newEnumTypes.map((type, i) => {
79
+ return {
80
+ type,
81
+ enum: enumList[i]
82
+ };
83
+ }));
84
+ };
85
+ const addEnumConstraint = ({schema, enumList}) => {
86
+ if (!enumList.length) {
87
+ throw new TypeError(`Not possible to match empty enum`);
88
+ }
89
+
90
+ enumList = reduceEnumList({enumList});
91
+
92
+ // eslint-disable-next-line unicorn/prefer-ternary -- Refactoring
93
+ if (!schema.enum) { // Enforce new constraint allowances
94
+ schema.enum = enumList;
95
+ } else { // Remove any old allowances not part of new ones
96
+ schema.enum = schema.enum.filter((value) => {
97
+ return valueInEnumList({
98
+ value, list: enumList
99
+ });
100
+ });
101
+ }
102
+ };
103
+ /**
104
+ * Assumes confining further--not expanding allowances.
105
+ * @param {PlainObject} args
106
+ * @param {SchemaObject} args.schema
107
+ * @param {Array<any>} args.enumList
108
+ * @returns {undefined}
109
+ */
110
+ const addEnumAnyOfConstraint = ({schema, enumList}) => {
111
+ if (!schema.anyOf.length) {
112
+ // Reduction is necessary for duplicates within new items
113
+ schema.anyOf = reduceEnumList({enumList});
114
+ return;
115
+ }
116
+ const newEnumTypes = getTypesForValues({enumList});
117
+ schema.anyOf = schema.anyOf.filter((subschema) => {
118
+ const matchingNewEnumListItems = newEnumTypes.reduce(
119
+ (arr, enumType, i) => {
120
+ // Filter out different types
121
+ if (enumType === subschema.type &&
122
+ // Filter out enum values duplicated within new list
123
+ !valueInEnumList({
124
+ value: enumList[i],
125
+ list: arr
126
+ })
127
+ ) {
128
+ arr.push(enumList[i]);
129
+ }
130
+ return arr;
131
+ },
132
+ []
133
+ );
134
+ const hasMatchingType = matchingNewEnumListItems.length;
135
+ if (hasMatchingType) {
136
+ addEnumConstraint({
137
+ schema: subschema, enumList: matchingNewEnumListItems
138
+ });
139
+ }
140
+ return hasMatchingType;
141
+ });
142
+ };
143
+ /**
144
+ * Assumes confining further--not expanding allowances.
145
+ * @param {PlainObject} args
146
+ * @param {SchemaObject} args.schema
147
+ * @param {Array<any>} args.newAnyOf
148
+ * -- param {boolean} args.oneOf
149
+ * @returns {undefined}
150
+ */
151
+ const addTypeConstraints = ({
152
+ schema, newAnyOf // , oneOf = false
153
+ }) => {
154
+ newAnyOf = reduceAnyOf(newAnyOf);
155
+ if (!schema.anyOf.length) {
156
+ // Reduction is necessary for duplicates within new items
157
+ schema.anyOf = newAnyOf;
158
+ return;
159
+ }
160
+ schema.anyOf = schema.anyOf
161
+ ? schema.anyOf.filter((preexistingSchema) => {
162
+ const {type} = preexistingSchema;
163
+ return newAnyOf.some((schemaObj) => {
164
+ const addOrFilterEnumList = ({enumList}) => {
165
+ // eslint-disable-next-line unicorn/prefer-ternary
166
+ if (!preexistingSchema.enum) {
167
+ preexistingSchema.enum = reduceEnumList({enumList});
168
+ } else {
169
+ preexistingSchema.enum = preexistingSchema.enum.filter(
170
+ (value) => {
171
+ return valueInEnumList({
172
+ value, list: enumList
173
+ });
174
+ }
175
+ );
176
+ }
177
+ };
178
+ const {
179
+ type: newSchemaObjectType,
180
+ enum: enumList,
181
+ const: cnst
182
+ } = schemaObj;
183
+ const hasEnumList = Boolean(enumList);
184
+ const hasConst = 'const' in schemaObj;
185
+ const hasMatchingEnumType = hasEnumList &&
186
+ getTypesForValues({enumList}).includes(type);
187
+ const hasMatchingConstType = hasConst &&
188
+ getTypesForValues({enumList: [cnst]}).includes(type);
189
+ if (hasMatchingEnumType) {
190
+ addOrFilterEnumList({enumList});
191
+ }
192
+ if (hasMatchingConstType) {
193
+ addOrFilterEnumList({enumList: [cnst]});
194
+ }
195
+ return (!newSchemaObjectType || newSchemaObjectType === type) &&
196
+ (!hasEnumList || hasMatchingEnumType) &&
197
+ (!hasConst || hasMatchingConstType);
198
+ // Todo (readme): Handle type-specific conditions
199
+ // Todo (readme): Recurse on `anyOf`, etc. children
200
+ // Todo (readme): Use `oneOf` argument (to add `not`?)
201
+ });
202
+ })
203
+ : newAnyOf;
204
+ };
205
+ const confineSchemaType = ({targetSchema, newSchema}) => {
206
+ if (newSchema.type) {
207
+ if (targetSchema.type) {
208
+ if (Array.isArray(targetSchema.type)) {
209
+ if (Array.isArray(newSchema.type)) {
210
+ targetSchema.type = targetSchema.type.filter((type) => {
211
+ return newSchema.type.includes(type);
212
+ });
213
+ } else if (targetSchema.type.includes(newSchema.type)) {
214
+ targetSchema.type = newSchema.type;
215
+ } else {
216
+ throw new TypeError(
217
+ `The constraints for ${newSchema.type} and ` +
218
+ `${targetSchema.type} cannot be met.`
219
+ );
220
+ }
221
+ // Todo: This does nothing; why was this ocndition here?
222
+ // eslint-disable-next-line no-dupe-else-if
223
+ } else if (0 && Array.isArray(targetSchema.type)) {
224
+ if (targetSchema.type.includes(newSchema.type)) {
225
+ targetSchema.type = newSchema.type;
226
+ } else {
227
+ throw new TypeError(
228
+ `The constraints for ${newSchema.type} and ` +
229
+ `${targetSchema.type} cannot be met.`
230
+ );
231
+ }
232
+ } else if (targetSchema.type !== newSchema.type) {
233
+ throw new TypeError(
234
+ `The constraints for ${newSchema.type} and ` +
235
+ `${targetSchema.type} cannot be met.`
236
+ );
237
+ }
238
+ } else {
239
+ targetSchema.type = newSchema.type;
240
+ }
241
+ }
242
+ };
243
+ const consolidateSchemas = (schemas) => {
244
+ const retSchema = {};
245
+ schemas.forEach((schema) => {
246
+ confineSchemaType({targetSchema: retSchema, newSchema: schema});
247
+ if (schema.enum) {
248
+ addEnumConstraint({schema: retSchema, enumList: schema.enum});
249
+ }
250
+ if ('const' in schema) {
251
+ addEnumConstraint({schema: retSchema, enumList: [schema.const]});
252
+ }
253
+ if ('multipleOf' in schema) {
254
+ if ('multipleOf' in retSchema) {
255
+ if (retSchema.multipleOf % schema.multipleOf &&
256
+ schema.multipleOf % retSchema.multipleOf
257
+ ) {
258
+ retSchema.multipleOf *= schema.multipleOf;
259
+ }
260
+ } else {
261
+ retSchema.multipleOf = schema.multipleOf;
262
+ }
263
+ }
264
+ if ('maximum' in schema) {
265
+ if ('maximum' in retSchema) {
266
+ if (schema.maximum < retSchema.maximum) {
267
+ retSchema.maximum = schema.maximum;
268
+ }
269
+ } else {
270
+ retSchema.maximum = schema.maximum;
271
+ }
272
+ }
273
+ if ('minimum' in schema) {
274
+ if ('minimum' in retSchema) {
275
+ if (schema.minimum > retSchema.minimum) {
276
+ retSchema.minimum = schema.minimum;
277
+ }
278
+ } else {
279
+ retSchema.minimum = schema.minimum;
280
+ }
281
+ }
282
+ if ('maxLength' in schema) {
283
+ if ('maxLength' in retSchema) {
284
+ if (schema.maxLength < retSchema.maxLength) {
285
+ retSchema.maxLength = schema.maxLength;
286
+ }
287
+ } else {
288
+ retSchema.maxLength = schema.maxLength;
289
+ }
290
+ }
291
+ if ('minLength' in schema) {
292
+ if ('minLength' in retSchema) {
293
+ if (schema.minLength > retSchema.minLength) {
294
+ retSchema.minLength = schema.minLength;
295
+ }
296
+ } else {
297
+ retSchema.minLength = schema.minLength;
298
+ }
299
+ }
300
+ if ('pattern' in schema) {
301
+ if ('allOf' in retSchema) {
302
+ if (!retSchema.allOf.some(
303
+ (item) => item.pattern === schema.pattern
304
+ )) {
305
+ retSchema.allOf.push({pattern: schema.pattern});
306
+ }
307
+ } else { /* if ('pattern' in retSchema) {
308
+ retSchema.allOf = [
309
+ {pattern: retSchema.pattern},
310
+ {pattern: schema.pattern}
311
+ ];
312
+ delete retSchema.pattern;
313
+ } else { */
314
+ // retSchema.pattern = schema.pattern;
315
+ retSchema.allOf = [{pattern: schema.pattern}];
316
+ }
317
+ }
318
+ if ('maxItems' in schema) {
319
+ if ('maxItems' in retSchema) {
320
+ if (schema.maxItems < retSchema.maxItems) {
321
+ retSchema.maxItems = schema.maxItems;
322
+ }
323
+ } else {
324
+ retSchema.maxItems = schema.maxItems;
325
+ }
326
+ }
327
+ if ('minItems' in schema) {
328
+ if ('minItems' in retSchema) {
329
+ if (schema.minItems > retSchema.minItems) {
330
+ retSchema.minItems = schema.minItems;
331
+ }
332
+ } else {
333
+ retSchema.minItems = schema.minItems;
334
+ }
335
+ }
336
+ // Todo (low): Add `format` to `allOf`?
337
+ /*
338
+ Todo (readme): Merge other properties
339
+ 6.4. Validation Keywords for Arrays
340
+ 6.4.1. items
341
+ 6.4.2. additionalItems
342
+ 6.4.5. uniqueItems
343
+ 6.4.6. contains
344
+ 6.5. Validation Keywords for Objects
345
+ 6.5.3. required
346
+ 6.5.4. properties
347
+ 6.5.5. patternProperties
348
+ 6.5.6. additionalProperties
349
+ 6.5.7. dependencies
350
+ 6.5.8. propertyNames
351
+ 6.6. Keywords for Applying Subschemas Conditionally
352
+ 6.6.1. if
353
+ 6.6.2. then
354
+ 6.6.3. else
355
+ 6.7. Keywords for Applying Subschemas With Boolean Logic
356
+ 6.7.1. allOf
357
+ 6.7.2. anyOf
358
+ 6.7.3. oneOf
359
+ 6.7.4. not
360
+ */
361
+ if ('maxProperties' in schema) {
362
+ if ('maxProperties' in retSchema) {
363
+ if (schema.maxProperties < retSchema.maxProperties) {
364
+ retSchema.maxProperties = schema.maxProperties;
365
+ }
366
+ } else {
367
+ retSchema.maxProperties = schema.maxProperties;
368
+ }
369
+ }
370
+ if ('minProperties' in schema) {
371
+ if ('minProperties' in retSchema) {
372
+ if (schema.minProperties > retSchema.minProperties) {
373
+ retSchema.minProperties = schema.minProperties;
374
+ }
375
+ } else {
376
+ retSchema.minProperties = schema.minProperties;
377
+ }
378
+ }
379
+ });
380
+ // Todo (low): We could remove properties if type doesn't support
381
+ if ('maximum' in retSchema && `exclusiveMaximum` in retSchema) {
382
+ if (retSchema.maximum >= retSchema.exclusiveMaximum) {
383
+ delete retSchema.maximum;
384
+ } else {
385
+ delete retSchema.exclusiveMaximum;
386
+ }
387
+ }
388
+ if ('minimum' in retSchema && `exclusiveMinimum` in retSchema) {
389
+ if (retSchema.minimum <= retSchema.exclusiveMinimum) {
390
+ delete retSchema.minimum;
391
+ } else {
392
+ delete retSchema.exclusiveMinimum;
393
+ }
394
+ }
395
+ if ('maximum' in retSchema && `minimum` in retSchema &&
396
+ retSchema.maximum < retSchema.minimum
397
+ ) {
398
+ throw new TypeError(`Max under minimum`);
399
+ }
400
+ if ('maximum' in retSchema && `exclusiveMinimum` in retSchema &&
401
+ retSchema.maximum <= retSchema.exclusiveMinimum
402
+ ) {
403
+ throw new TypeError(`Max under or equal to exclusive minimum`);
404
+ }
405
+ if ('exclusiveMaximum' in retSchema &&
406
+ `exclusiveMinimum` in retSchema &&
407
+ retSchema.exclusiveMaximum < retSchema.minimum
408
+ ) {
409
+ throw new TypeError(`Exclusive max under exclusive minimum`);
410
+ }
411
+ if ('exclusiveMaximum' in retSchema && `minimum` in retSchema &&
412
+ retSchema.exclusiveMaximum <= retSchema.minimum
413
+ ) {
414
+ throw new TypeError(`Exclusive max under or equal to minimum`);
415
+ }
416
+ if ('maxLength' in retSchema && `minLength` in retSchema &&
417
+ retSchema.maxLength < retSchema.minLength
418
+ ) {
419
+ throw new TypeError('Max length under min length');
420
+ }
421
+ // Todo (low): We might parse `pattern` to see if it necessitates
422
+ // a `minLength` (or a `maxLength` if `^` and `$` are used?)
423
+ return retSchema;
424
+ };
425
+ const flattenSchemas = ({schema, possibleSchemaObject: pso}) => {
426
+ if (!schema) {
427
+ schema = {};
428
+ }
429
+ // Todo (readme): resolve refs
430
+ resolveRef({schema, pso});
431
+
432
+ // type -> [type] -> anyOf
433
+ const newAnyOf = 'type' in pso
434
+ ? (Array.isArray(pso.type) ? pso.type : [pso.type]).map(
435
+ (type) => ({type})
436
+ )
437
+ : [];
438
+ addTypeConstraints({schema, newAnyOf});
439
+
440
+ // enum -> enum
441
+ // const -> enum
442
+ if ('const' in pso) {
443
+ addEnumAnyOfConstraint({schema, enumList: [pso.const]});
444
+ }
445
+ if ('enum' in pso) {
446
+ addEnumAnyOfConstraint({schema, enumList: pso.enum});
447
+ }
448
+
449
+ if (pso.anyOf) {
450
+ addTypeConstraints({schema, newAnyOf: pso.anyOf});
451
+ }
452
+ if (pso.oneOf) {
453
+ addTypeConstraints({schema, newAnyOf: pso.oneOf, oneOf: true});
454
+ }
455
+ if (pso.allOf) {
456
+ addTypeConstraints({schema, newAnyOf: [
457
+ consolidateSchemas(pso.allOf)
458
+ ]});
459
+ }
460
+ if (pso.not) {
461
+ // Todo
462
+ }
463
+ if (pso.if) {
464
+ // pso.then
465
+ // pso.else
466
+ }
467
+ // flattenSchemas(pso.anyOf))
468
+ if (!schema.anyOf.length) {
469
+ // Todo: Should allow anything
470
+ }
471
+
472
+ // Todo: Handle `$ref` and aggregate oneOf/allOf/anyOf
473
+ // (including `type: []`)/not; if/then/else
474
+ // to `anyOf` with each constrained with any `allOf` for
475
+ // required (and any `not` (including `oneOf`) for prohibited if
476
+ // they can't be expressed as allOf),
477
+ // and any `if` conditions applied?
478
+ // Todo: Keep going until find all `type` (object/array/etc.)
479
+ // and extract out
480
+ return []; // Todo: Add possible schema objects
481
+ };
482
+ // Todo: Descend into arrays/objects (changing path)
483
+ const resolvePropertyInSchemas = (/* {
484
+ possibleSchemaObjects, arrayOrObjectPropertyName
485
+ } */) => {
486
+ // Todo?
487
+ };
488
+ stateObj.getPossibleSchemasForPathAndType = function ({
489
+ keypath, parentPath, valueType, arrayOrObjectPropertyName
490
+ }) {
491
+ const possibleSchemaObjects = flattenSchemas({
492
+ possibleSchemaObject: stateObj[parentPath]
493
+ });
494
+
495
+ // Todo: We'll probably need a separate encapsulateObserver
496
+ // run-through to ensure we exclude all non-viable
497
+ // schemas along the way
498
+ // Todo: Check type/enum
499
+ if (parentPath) {
500
+ resolvePropertyInSchemas({
501
+ possibleSchemaObjects, arrayOrObjectPropertyName
502
+ });
503
+ }
504
+
505
+ if (!stateObj[keypath]) {
506
+ stateObj[keypath] = [];
507
+ }
508
+ stateObj[keypath].push(
509
+ ...possibleSchemaObjects.filter((possibleSchemaObject) => {
510
+ return 'type' in possibleSchemaObject &&
511
+ possibleSchemaObject.type === valueType;
512
+ })
513
+ );
514
+
515
+ // apparently need this or else no return type to assign
516
+ return stateObj;
517
+ };
518
+ return structuredCloning.iterate(records, stateObj);
519
+ };
520
+
521
+ /**
522
+ * @param {string} state
523
+ * @returns {string[]}
524
+ */
525
+ export const getTypesForState = (state) => {
526
+ return structuredCloning.getTypesForState.call(
527
+ this, state
528
+ );
529
+ };