@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,53 @@
1
+ import {$e} from '../utils/templateUtils.js';
2
+
3
+ /**
4
+ * @type {TypeObject}
5
+ */
6
+ const bigintType = {
7
+ option: ['BigInt'],
8
+ stringRegex: new RegExp(
9
+ '^' + // No leading content.
10
+ '-?' + // Optional negative sign.
11
+ // How many digits?
12
+ '[0-9]{1,}' +
13
+ 'n' +
14
+ '$', // No trailing content.
15
+ 'u'
16
+ ),
17
+ toValue (s) {
18
+ return {value: BigInt(s.slice(0, -1))};
19
+ },
20
+ getInput ({root}) {
21
+ return $e(root, 'input');
22
+ },
23
+ setValue ({root, value}) {
24
+ this.getInput({root}).value = String(value);
25
+ },
26
+ validate ({root}) {
27
+ const val = this.getInput({root}).value;
28
+ return {
29
+ message: 'Not a valid BigInt',
30
+ valid: val && val.match(/^-?(\d+)$/u)
31
+ };
32
+ },
33
+ getValue ({root}) {
34
+ return BigInt(this.getInput({root}).value);
35
+ },
36
+ /* schema:
37
+ viewSchemaUI () {
38
+ // Todo?
39
+ },
40
+ */
41
+ viewUI ({value}) {
42
+ return ['i', {dataset: {type: 'bigint'}}, [`${String(value)}n`]];
43
+ },
44
+ editUI ({typeNamespace, value = ''}) {
45
+ return ['div', {dataset: {type: 'bigint'}}, [
46
+ ['input', {
47
+ name: `${typeNamespace}-bigint`, type: 'number', step: 'any', value
48
+ }]
49
+ ]];
50
+ }
51
+ };
52
+
53
+ export default bigintType;
@@ -0,0 +1,129 @@
1
+ import {$e} from '../utils/templateUtils.js';
2
+ import {jml} from '../../node_modules/jamilih/dist/jml-es.js';
3
+ import Types from '../types.js';
4
+
5
+ /**
6
+ * @type {TypeObject}
7
+ */
8
+ const dateType = {
9
+ option: ['Date'],
10
+ // ISO Date string
11
+ dateRegex: /^(?:\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}\.\d{3}Z)?|(?:\+|-)\d{6}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)$/u, // eslint-disable-line unicorn/no-unsafe-regex
12
+ stringRegex () {
13
+ const regex = this.dateRegex;
14
+ if (!this.valid) {
15
+ const {source} = regex;
16
+ return new RegExp('^' + '(?:InvalidDate)|' + source.slice(1), 'u');
17
+ }
18
+ return new RegExp(regex, 'u');
19
+ },
20
+ toValue (s) {
21
+ return {value: new Date(s)};
22
+ },
23
+ getInput ({root}) {
24
+ return $e(root, 'input[type="date"]');
25
+ },
26
+ setValue ({root, value}) {
27
+ const notANum = value && Number.isNaN(value.getTime());
28
+ if (notANum) {
29
+ $e(root, '.invalidDate').$setValidity(true);
30
+ return;
31
+ }
32
+ const dateStr = new Date(Date.parse(value)).toISOString();
33
+ this.getInput({root}).value = dateStr.length === 24
34
+ ? dateStr.slice(0, 10)
35
+ // eslint-disable-next-line max-len -- Long
36
+ /* istanbul ignore next -- 6 digits year not reliable through `Date.parse` */
37
+ : dateStr.slice(3, 13); // Will cut off ten/hundred thousand years
38
+ },
39
+ validate ({root}) {
40
+ if (this.isInvalid({root})) {
41
+ return {
42
+ valid: true
43
+ };
44
+ }
45
+ const val = this.getInput({root}).value;
46
+ if (!val) {
47
+ return {
48
+ valid: false,
49
+ message: 'Must not be empty`'
50
+ };
51
+ }
52
+ return {
53
+ valid: val.match(new RegExp(this.dateRegex, 'u')),
54
+ message: 'Must match a valid date'
55
+ }; // Input shouldn't allow anyways
56
+ },
57
+ isInvalid ({root}) {
58
+ return !this.valid && $e(root, '.invalidDate').checked;
59
+ },
60
+ getValue ({root}) {
61
+ if (this.isInvalid({root})) {
62
+ return this.toValue('NaN').value;
63
+ }
64
+ return this.toValue(this.getInput({root}).value).value;
65
+ },
66
+ isValueInvalid (value) {
67
+ return value && Number.isNaN(value.getTime());
68
+ },
69
+ viewUI ({value}) {
70
+ return !this.valid && this.isValueInvalid(value)
71
+ ? ['i', {
72
+ dataset: {type: 'date'}, class: 'InvalidDate'
73
+ }, ['InvalidDate']]
74
+ : ['i', {dataset: {type: 'date'}, class: 'ValidDate'}, [
75
+ value.toISOString().slice(0, 10)
76
+ ]];
77
+ },
78
+ // Change to default to `new Date()` when can't be `NaN`
79
+ // value (keys)?
80
+ editUI ({topRoot, typeNamespace, value = ''}) {
81
+ const notANum = this.isValueInvalid(value);
82
+ const invalid = this.valid
83
+ ? ''
84
+ : jml('div', [
85
+ ['label', [
86
+ 'Invalid date',
87
+ ['input', {
88
+ type: 'checkbox',
89
+ class: 'invalidDate',
90
+ checked: notANum,
91
+ name: `${typeNamespace}-invalidDate`,
92
+ $custom: {
93
+ $setValidity (legitimateInvalid) {
94
+ console.log('legitimateInvalid', legitimateInvalid);
95
+ if (legitimateInvalid === true) {
96
+ this.checked = true;
97
+ }
98
+ const label = invalid.previousElementSibling;
99
+ label.hidden = Boolean(legitimateInvalid);
100
+ const root = label.parentElement;
101
+ Types.validate({type: 'date', root, topRoot});
102
+ }
103
+ },
104
+ $on: {
105
+ click (e) {
106
+ this.$setValidity();
107
+ }
108
+ }
109
+ }]
110
+ ]]
111
+ ]);
112
+ return ['div', {dataset: {type: this.valid ? 'ValidDate' : 'date'}}, [
113
+ ['label', {
114
+ hidden: notANum
115
+ }, [
116
+ 'Date: ',
117
+ ['input', {
118
+ name: `${typeNamespace}-date`,
119
+ type: 'date',
120
+ // Required yyyy-MM-dd format
121
+ value: !value || notANum ? '' : value.toISOString().slice(0, 10)
122
+ }]
123
+ ]],
124
+ invalid
125
+ ]];
126
+ }
127
+ };
128
+
129
+ export default dateType;
@@ -0,0 +1,33 @@
1
+ import {$e} from '../utils/templateUtils.js';
2
+
3
+ /**
4
+ * @type {TypeObject}
5
+ */
6
+ const nullType = {
7
+ option: ['Null'],
8
+ stringRegex: /^null$/u,
9
+ toValue: () => ({value: null}),
10
+ getValue: () => null,
11
+ viewUI () {
12
+ return ['i', {dataset: {type: 'null'}}, ['null']];
13
+ },
14
+ /* istanbul ignore next -- No dupe keys, array refs, or validation */
15
+ getInput ({root}) {
16
+ return $e(root, 'input');
17
+ },
18
+ editUI ({typeNamespace}) {
19
+ return ['div', {dataset: {type: 'null'}}, [
20
+ ['label', [
21
+ 'Null',
22
+ ['input', {
23
+ type: 'checkbox',
24
+ name: `${typeNamespace}-null`,
25
+ checked: true,
26
+ disabled: true
27
+ }]
28
+ ]]
29
+ ]];
30
+ }
31
+ };
32
+
33
+ export default nullType;
@@ -0,0 +1,56 @@
1
+ import {$e} from '../utils/templateUtils.js';
2
+
3
+ /**
4
+ * @type {TypeObject}
5
+ */
6
+ const numberType = {
7
+ option: ['Number'],
8
+ stringRegex: new RegExp(
9
+ '^' + // No leading content.
10
+ '[-+]?' + // Optional sign.
11
+ // Optionally 0-30 decimal digits of mantissa.
12
+ '(?:[0-9]{0,30}\\.)?' +
13
+ // 1-30 decimal digits of integer or fraction.
14
+ '[0-9]{1,30}' +
15
+ // Optional exponent 0-29 for scientific notation.
16
+ '(?:[Ee][-+]?[1-2]?[0-9])?' +
17
+ '$', // No trailing content.
18
+ 'u'
19
+ ),
20
+ toValue (s) {
21
+ return {value: Number(s)};
22
+ },
23
+ getInput ({root}) {
24
+ return $e(root, 'input');
25
+ },
26
+ setValue ({root, value}) {
27
+ this.getInput({root}).value = String(value);
28
+ },
29
+ validate ({root}) {
30
+ const val = this.getInput({root}).value;
31
+ return {
32
+ message: 'Not a valid (finite) number',
33
+ valid: val && val.match(/^-?(\d+|\d*\.\d+)$/u)
34
+ };
35
+ },
36
+ getValue ({root}) {
37
+ return Number.parseFloat(this.getInput({root}).value);
38
+ },
39
+ /* schema
40
+ viewSchemaUI () {
41
+ // Todo?
42
+ },
43
+ */
44
+ viewUI ({value}) {
45
+ return ['i', {dataset: {type: 'number'}}, [String(value)]];
46
+ },
47
+ editUI ({typeNamespace, value = ''}) {
48
+ return ['div', {dataset: {type: 'number'}}, [
49
+ ['input', {
50
+ name: `${typeNamespace}-number`, type: 'number', step: 'any', value
51
+ }]
52
+ ]];
53
+ }
54
+ };
55
+
56
+ export default numberType;
@@ -0,0 +1,51 @@
1
+ import Types from '../types.js';
2
+
3
+ /**
4
+ * @type {TypeObject}
5
+ */
6
+ const objectReferenceType = {
7
+ option: ['Object reference'],
8
+ type: 'object',
9
+ stringRegex: /^objectRef\((?:|\/[^)]*)\)$/u,
10
+ toValue (...args) {
11
+ return Types.availableTypes.arrayReference.toValue.apply(this, args);
12
+ },
13
+ resolveReference (...args) {
14
+ return Types.availableTypes.arrayReference.resolveReference.apply(
15
+ this, args
16
+ );
17
+ },
18
+ stateDependent: {
19
+ structuredCloning: {
20
+ after: 'object',
21
+ contexts: [
22
+ 'arrayNonindexKeys',
23
+ // 'sparseArrays',
24
+ 'object'
25
+ ]
26
+ }
27
+ },
28
+ getInput (...args) {
29
+ return Types.availableTypes.arrayReference.getInput.apply(this, args);
30
+ },
31
+ setValue (...args) {
32
+ return Types.availableTypes.arrayReference.setValue.apply(this, args);
33
+ },
34
+ getValue (...args) {
35
+ return Types.availableTypes.arrayReference.getValue.apply(this, args);
36
+ },
37
+ validate (...args) {
38
+ return Types.availableTypes.arrayReference.validate.apply(this, args);
39
+ },
40
+ validateAll (...args) {
41
+ return Types.availableTypes.arrayReference.validateAll.apply(this, args);
42
+ },
43
+ viewUI (...args) {
44
+ return Types.availableTypes.arrayReference.viewUI.apply(this, args);
45
+ },
46
+ editUI (...args) {
47
+ return Types.availableTypes.arrayReference.editUI.apply(this, args);
48
+ }
49
+ };
50
+
51
+ export default objectReferenceType;
@@ -0,0 +1,30 @@
1
+ import Types from '../types.js';
2
+
3
+ /**
4
+ * @type {TypeObject}
5
+ */
6
+ const objectType = {
7
+ option: ['Object'],
8
+ regexEndings: [',', '}'],
9
+ stringRegexBegin: /^\{/u,
10
+ stringRegexEnd: /^\}/u,
11
+ toValue (...args) {
12
+ return Types.availableTypes.array.toValue.apply(this, args);
13
+ },
14
+ getValue (...args) {
15
+ return Types.availableTypes.array.getValue.apply(this, args);
16
+ },
17
+ getInput (...args) {
18
+ return Types.availableTypes.array.getInput.apply(this, args);
19
+ },
20
+ viewUI (...args) {
21
+ return Types.availableTypes.array.viewUI.apply(this, args);
22
+ },
23
+ editUI ({...args}) {
24
+ return Types.availableTypes.array.editUI.call(this, {
25
+ ...args, type: 'object'
26
+ });
27
+ }
28
+ };
29
+
30
+ export default objectType;
@@ -0,0 +1,95 @@
1
+ import {$e} from '../utils/templateUtils.js';
2
+ import {jml} from '../../node_modules/jamilih/dist/jml-es.js';
3
+ import Types from '../types.js';
4
+
5
+ /**
6
+ * @type {TypeObject}
7
+ */
8
+ const regexpType = {
9
+ option: ['RegExp'],
10
+ stringRegex (nonGrouping) {
11
+ const parenth = nonGrouping ? '(?:' : '(';
12
+ return new RegExp(`^/${parenth}.*)/${parenth}[${
13
+ this.allowedFlags.join('')
14
+ }]{0,${
15
+ this.allowedFlags.length
16
+ }})$`, 'u');
17
+ },
18
+ toValue (s) {
19
+ const [, str, flags] = s.match(this.stringRegex());
20
+ return {value: new RegExp(str, flags)};
21
+ },
22
+ getInput ({root}) {
23
+ return $e(root, 'input');
24
+ },
25
+ setValue ({root, value}) {
26
+ this.getInput({root}).value = value.source;
27
+ this.getSelect({root}).$set([...value.flags]);
28
+ },
29
+ getSelect ({root}) {
30
+ return $e(root, 'select');
31
+ },
32
+ validate ({root}) {
33
+ try {
34
+ this.getValue({root});
35
+ return {valid: true};
36
+ } catch (err) {
37
+ return {
38
+ valid: false,
39
+ message: err.message
40
+ };
41
+ }
42
+ },
43
+ getValue ({root}) {
44
+ return new RegExp(
45
+ this.getInput({root}).value, // .replace(/\\/g, '\\\\'),
46
+ [...this.getSelect({root}).selectedOptions].reduce((s, opt) => {
47
+ return s + opt.value;
48
+ }, '')
49
+ );
50
+ },
51
+ allowedFlags: ['g', 'i', 'm', 'u', 'y', 's'],
52
+ viewUI ({value}) {
53
+ return ['i', {dataset: {type: 'regexp'}}, [String(value)]];
54
+ },
55
+ editUI ({typeNamespace, value = {source: '', flags: ''}}) {
56
+ // Todo (low): Add RegExp syntax highlighter
57
+ const select = jml(
58
+ 'select',
59
+ {multiple: true, size: 5, $custom: {
60
+ $set (valArr) { // A useful reusable method for multiple selects
61
+ [...this.options].forEach((opt) => {
62
+ opt.selected = valArr.includes(opt.value);
63
+ });
64
+ }
65
+ }},
66
+ this.allowedFlags.map((flag) => {
67
+ return ['option', {
68
+ selected: value.flags.includes(flag)
69
+ }, [flag]];
70
+ })
71
+ );
72
+ const root = jml('div', {dataset: {type: 'regexp'}}, [
73
+ ['label', [
74
+ 'Source ',
75
+ ['input', {
76
+ name: `${typeNamespace}-regexp`, type: 'text',
77
+ value: value.source
78
+ }]
79
+ ]],
80
+ ['br'],
81
+ ['label', [
82
+ 'Flags ',
83
+ select
84
+ ]]
85
+ ]);
86
+ // Could be disallowed flags; we might instead try in
87
+ // advance which will work
88
+ select.addEventListener('change', () => {
89
+ Types.validate({type: 'regexp', root});
90
+ });
91
+ return [root];
92
+ }
93
+ };
94
+
95
+ export default regexpType;
@@ -0,0 +1,43 @@
1
+ import {$e} from '../utils/templateUtils.js';
2
+
3
+ /**
4
+ * @type {TypeObject}
5
+ */
6
+ const sparseUndefinedType = {
7
+ option: ['Sparse undefined'],
8
+ stateDependent: {
9
+ structuredCloning: {
10
+ after: 'undef',
11
+ contexts: [
12
+ 'arrayNonindexKeys'
13
+ // 'sparseArrays'
14
+ ]
15
+ }
16
+ },
17
+ /* istanbul ignore next -- Catching instead of this placeholder */
18
+ viewUI (/* {value} */) {
19
+ return ['i', {
20
+ dataset: {type: 'sparseUndefined'}
21
+ }, [`undefined (sparse)`]];
22
+ },
23
+ /* istanbul ignore next -- Catching instead of this placeholder */
24
+ getInput ({root}) {
25
+ return $e(root, 'input');
26
+ },
27
+ /* istanbul ignore next -- Catching instead of this placeholder */
28
+ editUI ({typeNamespace}) {
29
+ return ['div', [
30
+ ['label', {dataset: {type: 'sparseUndefined'}}, [
31
+ 'Sparse undefined',
32
+ ['input', {
33
+ type: 'checkbox',
34
+ name: `${typeNamespace}-sparseUndefined`,
35
+ checked: true,
36
+ disabled: true
37
+ }]
38
+ ]]
39
+ ]];
40
+ }
41
+ };
42
+
43
+ export default sparseUndefinedType;
@@ -0,0 +1,31 @@
1
+ import {$e} from '../utils/templateUtils.js';
2
+
3
+ /**
4
+ * @type {TypeObject}
5
+ */
6
+ const stringType = {
7
+ option: ['String'],
8
+ stringRegex: /^"(?:[^\\"]|\\\\|\\")*"$/u,
9
+ toValue (s) {
10
+ return {value: s.slice(1, -1)};
11
+ },
12
+ getInput ({root}) {
13
+ return $e(root, 'textarea');
14
+ },
15
+ setValue ({root, value}) {
16
+ this.getInput({root}).value = value;
17
+ },
18
+ getValue ({root}) {
19
+ return this.getInput({root}).value;
20
+ },
21
+ viewUI ({value}) {
22
+ return ['span', {dataset: {type: 'string'}}, [value]];
23
+ },
24
+ editUI ({typeNamespace, value = ''}) {
25
+ return ['div', {dataset: {type: 'string'}}, [
26
+ ['textarea', {name: `${typeNamespace}-string`}, [value]]
27
+ ]];
28
+ }
29
+ };
30
+
31
+ export default stringType;
@@ -0,0 +1,37 @@
1
+ import {$e} from '../utils/templateUtils.js';
2
+
3
+ /**
4
+ * @type {TypeObject}
5
+ */
6
+ const undefinedType = {
7
+ stringRegex: /^undefined$/u,
8
+ option: ['Explicit undefined'], // Explicit undefined only
9
+ toValue (_s) {
10
+ return {value: undefined};
11
+ },
12
+ getValue () {
13
+ return this.toValue().value;
14
+ },
15
+ viewUI (/* {value} */) {
16
+ return ['i', {dataset: {type: 'undef'}}, ['undefined']];
17
+ },
18
+ /* istanbul ignore next -- No dupe keys, array refs, or validation */
19
+ getInput ({root}) {
20
+ return $e(root, 'input');
21
+ },
22
+ editUI ({typeNamespace}) {
23
+ return ['div', {dataset: {type: 'undef'}}, [
24
+ ['label', [
25
+ 'Undefined',
26
+ ['input', {
27
+ type: 'checkbox',
28
+ name: `${typeNamespace}-undef`,
29
+ checked: true,
30
+ disabled: true
31
+ }]
32
+ ]]
33
+ ]];
34
+ }
35
+ };
36
+
37
+ export default undefinedType;
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export {default as Types} from './types.js';
2
+
3
+ export {default as Formats} from './formats.js';
4
+
5
+ export {
6
+ default as typeChoices, getFormatAndSchemaChoices
7
+ } from './typeChoices.js';