wysiwyv 0.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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -0
  3. package/dist/core.d.ts +7 -0
  4. package/dist/core.d.ts.map +1 -0
  5. package/dist/core.js +147 -0
  6. package/dist/hooks/and.d.ts +10 -0
  7. package/dist/hooks/and.d.ts.map +1 -0
  8. package/dist/hooks/and.js +29 -0
  9. package/dist/hooks/any.d.ts +8 -0
  10. package/dist/hooks/any.d.ts.map +1 -0
  11. package/dist/hooks/any.js +11 -0
  12. package/dist/hooks/array.d.ts +18 -0
  13. package/dist/hooks/array.d.ts.map +1 -0
  14. package/dist/hooks/array.js +56 -0
  15. package/dist/hooks/bool.d.ts +8 -0
  16. package/dist/hooks/bool.d.ts.map +1 -0
  17. package/dist/hooks/bool.js +16 -0
  18. package/dist/hooks/datetime.d.ts +11 -0
  19. package/dist/hooks/datetime.d.ts.map +1 -0
  20. package/dist/hooks/datetime.js +88 -0
  21. package/dist/hooks/email.d.ts +8 -0
  22. package/dist/hooks/email.d.ts.map +1 -0
  23. package/dist/hooks/email.js +26 -0
  24. package/dist/hooks/index.d.ts +3 -0
  25. package/dist/hooks/index.d.ts.map +1 -0
  26. package/dist/hooks/index.js +28 -0
  27. package/dist/hooks/int.d.ts +13 -0
  28. package/dist/hooks/int.d.ts.map +1 -0
  29. package/dist/hooks/int.js +24 -0
  30. package/dist/hooks/number.d.ts +15 -0
  31. package/dist/hooks/number.d.ts.map +1 -0
  32. package/dist/hooks/number.js +30 -0
  33. package/dist/hooks/object.d.ts +14 -0
  34. package/dist/hooks/object.d.ts.map +1 -0
  35. package/dist/hooks/object.js +80 -0
  36. package/dist/hooks/or.d.ts +10 -0
  37. package/dist/hooks/or.d.ts.map +1 -0
  38. package/dist/hooks/or.js +26 -0
  39. package/dist/hooks/string.d.ts +8 -0
  40. package/dist/hooks/string.d.ts.map +1 -0
  41. package/dist/hooks/string.js +16 -0
  42. package/dist/hooks/uuid.d.ts +9 -0
  43. package/dist/hooks/uuid.d.ts.map +1 -0
  44. package/dist/hooks/uuid.js +78 -0
  45. package/dist/hooks/val.d.ts +10 -0
  46. package/dist/hooks/val.d.ts.map +1 -0
  47. package/dist/hooks/val.js +24 -0
  48. package/dist/type/engine.d.ts +14 -0
  49. package/dist/type/engine.d.ts.map +1 -0
  50. package/dist/type/engine.js +1 -0
  51. package/dist/type/plugin.d.ts +26 -0
  52. package/dist/type/plugin.d.ts.map +1 -0
  53. package/dist/type/plugin.js +1 -0
  54. package/dist/type/template.d.ts +6 -0
  55. package/dist/type/template.d.ts.map +1 -0
  56. package/dist/type/template.js +1 -0
  57. package/dist/util/HookAssessment.d.ts +13 -0
  58. package/dist/util/HookAssessment.d.ts.map +1 -0
  59. package/dist/util/HookAssessment.js +42 -0
  60. package/dist/util/HookError.d.ts +14 -0
  61. package/dist/util/HookError.d.ts.map +1 -0
  62. package/dist/util/HookError.js +13 -0
  63. package/dist/util/parseHook.d.ts +5 -0
  64. package/dist/util/parseHook.d.ts.map +1 -0
  65. package/dist/util/parseHook.js +21 -0
  66. package/dist/util/stringify.d.ts +2 -0
  67. package/dist/util/stringify.d.ts.map +1 -0
  68. package/dist/util/stringify.js +34 -0
  69. package/dist/util/types.d.ts +15 -0
  70. package/dist/util/types.d.ts.map +1 -0
  71. package/dist/util/types.js +24 -0
  72. package/dist/wysiwyv-core.d.ts +3 -0
  73. package/dist/wysiwyv-core.d.ts.map +1 -0
  74. package/dist/wysiwyv-core.js +11 -0
  75. package/dist/wysiwyv.d.ts +3 -0
  76. package/dist/wysiwyv.d.ts.map +1 -0
  77. package/dist/wysiwyv.js +7 -0
  78. package/dist/wysiwyv.min.js +1 -0
  79. package/package.json +52 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jeremy "Ibi-Wan" Kent "...obi"
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,150 @@
1
+ | _README_ | [USAGE](README-USAGE.md) | [INCLUDED PLUGINS](README-INCLUDED-PLUGINS.md) | [PLUGIN AUTHORING](README-PLUGIN-AUTHORING.md) | [INTEGRATION](README-INTEGRATION.md) |
2
+ | :------- | :----------------------- | :--------------------------------------------- | :--------------------------------------------- | :----------------------------------- |
3
+
4
+ # WYSIWYV
5
+
6
+ - [Why You Wyv](#why-you-wyv)
7
+ - [Included Plugins](#included-plugins)
8
+ - [Make Your Own Plugins](#make-your-own-plugins)
9
+
10
+ ## What You See is What You Validate
11
+
12
+ ### candidate data:
13
+
14
+ ```json
15
+ {
16
+ "name": "John Smythe-Do",
17
+ "id": "c522fc68-ca27-4391-ac43-494c023e24a7",
18
+ "age": 27
19
+ }
20
+ ```
21
+
22
+ ### validation template:
23
+
24
+ ```json
25
+ {
26
+ "name": "$string",
27
+ "id": "$uuid",
28
+ "age": "$number"
29
+ }
30
+ ```
31
+
32
+ ### plugins nest and compose:
33
+
34
+ ```js
35
+ {
36
+ dept: ["sales", "acquisitions"],
37
+ team: [
38
+ { name: "Joan Duet", team: "sales" },
39
+ { name: "Jonn Tray", team: "sales" },
40
+ { name: "Jane Quay", team: "sales" }
41
+ ]
42
+ }
43
+ ```
44
+
45
+ ```js
46
+ {
47
+ dept: { $array: { $minlength: 1 } },
48
+ team: {
49
+ $array: { $each: {
50
+ $object: { $partial: {
51
+ team: "sales",
52
+ }},
53
+ }},
54
+ },
55
+ }
56
+ ```
57
+
58
+ ### success is simple:
59
+
60
+ ```js
61
+ { success: true, errors: [] }
62
+ ```
63
+
64
+ ### rejection is clear:
65
+
66
+ ```js
67
+ {
68
+ success: false,
69
+ errors:[
70
+ {
71
+ message: "Type: Expected 'number', got value 'old enough'",
72
+ path: ".age"
73
+ }
74
+ ]
75
+ }
76
+ ```
77
+
78
+ ## Why You Wyv?
79
+
80
+ - Templates: JSON
81
+ - Or directly in JS
82
+ - $-prefix for plugins
83
+ - Templates are Language-Agnostic
84
+ - Other language libraries to follow
85
+ - Tiny: Core is ~ 1.5kB
86
+ - Batteries: Included
87
+ - Strict: By Default
88
+ - Flexible: Rich Plugin API
89
+ - Fast Enough: Faster than yup and joi.
90
+ _(Still pursuing zod and valibot)_
91
+ - Simple: Reuse in:
92
+ - jest assertions
93
+ - express middleware
94
+ - cli tools
95
+ - custom exceptions
96
+
97
+ ### The real reason:
98
+
99
+ - You like that the plugins are called wyverns.
100
+
101
+ [↑ top](#wysiwyv)
102
+
103
+ ## Included Plugins
104
+
105
+ Read the [Included Plugins Documentation](README-INCLUDED-PLUGINS.md)
106
+
107
+ - $and / $or
108
+ - $any
109
+ - $array / $object / $plainobject
110
+ - $bool
111
+ - $isodate / $basicisodate / $strictisodate
112
+ - $email
113
+ - $int / $number
114
+ - $string
115
+ - $uuid
116
+ - $val
117
+ - Match values known at call time
118
+ - Ensure values match within data (back-references)
119
+
120
+ [↑ top](#wysiwyv)
121
+
122
+ ## Make Your Own Plugins
123
+
124
+ Read the [Plugin Authoring Documentation](README-PLUGIN-AUTHORING.md)
125
+
126
+ - Full Type System enables clean interfacing
127
+ - See `src/type/plugin.ts` for contract
128
+ - See `src/hooks/string.ts` for a simple example
129
+ - Rich data: inject at setup time or call time, or add during validation
130
+ - All scoped to your plugin for safety
131
+ - Alternately, use a shared context to communicate across plugins
132
+ - See `src/hooks/val.ts` for context use example
133
+ - `evaluate` function provided for custom recursive descent
134
+ - See `src/hooks/object.ts` for descent example
135
+
136
+ [↑ top](#wysiwyv)
137
+
138
+ # TODO
139
+
140
+ - [x] Base Plugin Set
141
+ - [x] Test Coverage
142
+ - [x] Documentation
143
+ - [x] Basic Usage
144
+ - [x] Plugin Authoring
145
+ - [x] Included Plugins
146
+ - [x] Integration Examples
147
+ - [x] Benchmarks
148
+ - [ ] Distro Builds
149
+ - [ ] To Npm!
150
+ - [ ] Reuse Instances
package/dist/core.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { type WysiwyvEvaluatorFunction } from "./type/engine";
2
+ import { type PluginList } from "./type/plugin";
3
+ import type { MultiContext } from "./type/engine";
4
+ export declare const makeCore: (hooks: PluginList, pluginSetups: MultiContext) => {
5
+ evaluate: WysiwyvEvaluatorFunction;
6
+ };
7
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAK9D,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAyBhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,eAAO,MAAM,QAAQ,GAAI,OAAO,UAAU,EAAE,cAAc,YAAY;;CA4MrE,CAAC"}
package/dist/core.js ADDED
@@ -0,0 +1,147 @@
1
+ import {} from "./type/engine";
2
+ import {} from "./type/plugin";
3
+ import {} from "./type/template";
4
+ import {} from "./type/template";
5
+ import {} from "./type/plugin";
6
+ import {} from "./type/plugin";
7
+ import { HookAssessor } from "./util/HookAssessment";
8
+ import { errAttr, errConfig, errMissing, errType, errUnexpected, errValue, } from "./util/HookError";
9
+ import { getHookKey, getHookParams } from "./util/parseHook";
10
+ import { repr } from "./util/stringify";
11
+ import { isArray, isBoolean, isNull, isNumber, isPlainObject, isString, isUndefined, } from "./util/types";
12
+ export const makeCore = (hooks, pluginSetups) => {
13
+ const perHookSetups = Object.freeze({ ...pluginSetups });
14
+ const perHookContexts = {};
15
+ const sharedContext = {};
16
+ const handlerKeyCache = new Map();
17
+ const evaluate = (expected, candidate, path) => {
18
+ const key = getHookKey(expected);
19
+ if (!key)
20
+ return expectNormal(expected, candidate, path);
21
+ const hookSetup = perHookSetups[key] ?? {};
22
+ if (perHookContexts[key] === undefined) {
23
+ perHookContexts[key] = {};
24
+ }
25
+ const hookContext = perHookContexts[key];
26
+ let handler;
27
+ if (handlerKeyCache.has(key)) {
28
+ handler = handlerKeyCache.get(key);
29
+ }
30
+ else {
31
+ const hookPlugin = hooks.find((h) => h.handles(key));
32
+ handler = hookPlugin?.handlers[key];
33
+ if (handler) {
34
+ handlerKeyCache.set(key, handler);
35
+ }
36
+ }
37
+ if (!handler)
38
+ return expectNormal(expected, candidate, path);
39
+ const params = getHookParams(expected, key);
40
+ const result = handler(candidate, expected, {
41
+ path,
42
+ params,
43
+ setup: hookSetup,
44
+ context: hookContext,
45
+ shared: sharedContext,
46
+ evaluate: evaluate,
47
+ });
48
+ return result;
49
+ };
50
+ const expectNormal = (expected, candidate, path) => {
51
+ switch (true) {
52
+ case isString(expected):
53
+ return expectString(expected, candidate, path);
54
+ case isNumber(expected):
55
+ return expectNumber(expected, candidate, path);
56
+ case isBoolean(expected):
57
+ return expectBoolean(expected, candidate, path);
58
+ case isNull(expected):
59
+ return expectNull(candidate, path);
60
+ case isArray(expected):
61
+ return expectArray(expected, candidate, path);
62
+ case isPlainObject(expected):
63
+ return expectObject(expected, candidate, path);
64
+ default:
65
+ return HookAssessor.fault(errConfig(`Unsupported value type in expected object: '${repr(expected)}'`, path));
66
+ }
67
+ };
68
+ const expectString = (expected, candidate, path) => {
69
+ if (typeof candidate !== "string") {
70
+ return HookAssessor.fault(errType("string", candidate, path));
71
+ }
72
+ if (candidate !== expected) {
73
+ return HookAssessor.fault(errValue(expected, candidate, path));
74
+ }
75
+ return HookAssessor.SUCCESS;
76
+ };
77
+ const expectNumber = (expected, candidate, path) => {
78
+ if (typeof candidate !== "number") {
79
+ return HookAssessor.fault(errType("number", candidate, path));
80
+ }
81
+ if (candidate !== expected) {
82
+ return HookAssessor.fault(errValue(expected, candidate, path));
83
+ }
84
+ return HookAssessor.SUCCESS;
85
+ };
86
+ const expectBoolean = (expected, candidate, path) => {
87
+ if (typeof candidate !== "boolean") {
88
+ return HookAssessor.fault(errType("boolean", candidate, path));
89
+ }
90
+ if (candidate !== expected) {
91
+ return HookAssessor.fault(errValue(expected, candidate, path));
92
+ }
93
+ return HookAssessor.SUCCESS;
94
+ };
95
+ const expectNull = (candidate, path) => {
96
+ if (candidate !== null) {
97
+ return HookAssessor.fault(errType("null", candidate, path));
98
+ }
99
+ return HookAssessor.SUCCESS;
100
+ };
101
+ const expectArray = (expected, candidate, path) => {
102
+ if (!isArray(candidate)) {
103
+ return HookAssessor.fault(errType("array", candidate, path));
104
+ }
105
+ const errors = HookAssessor.start();
106
+ if (candidate.length !== expected.length) {
107
+ errors.fault(errAttr("Array length", expected.length, candidate.length, path));
108
+ }
109
+ for (let i = 0; i < expected.length; i++) {
110
+ if (isUndefined(candidate[i])) {
111
+ errors.fault(errMissing(`index ${i}`, expected[i], `${path}[${i}]`));
112
+ continue;
113
+ }
114
+ const itemErrors = evaluate(expected[i], // not undefined per earlier check
115
+ candidate[i], `${path}[${i}]`);
116
+ errors.include(itemErrors);
117
+ }
118
+ return errors;
119
+ };
120
+ const expectObject = (expected, candidate, path) => {
121
+ if (!isPlainObject(candidate)) {
122
+ return HookAssessor.fault(errType("object", candidate, path));
123
+ }
124
+ const errors = HookAssessor.start();
125
+ const expectedKeys = Object.keys(expected);
126
+ const candidateKeys = Object.keys(candidate);
127
+ const missingKeys = expectedKeys.filter((k) => !candidateKeys.includes(k));
128
+ const extraKeys = candidateKeys.filter((k) => !expectedKeys.includes(k));
129
+ missingKeys.forEach((k) => {
130
+ errors.fault(errMissing(`${k}`, expected[k], `${path}.${k}`));
131
+ });
132
+ extraKeys.forEach((k) => {
133
+ errors.fault(errUnexpected(k, candidate[k], `${path}.${k}`));
134
+ });
135
+ for (const key in expected) {
136
+ if (missingKeys.includes(key))
137
+ continue; // already reported
138
+ const keyErrors = evaluate(expected[key], // not undefined per earlier check
139
+ candidate[key], `${path}.${key}`);
140
+ errors.include(keyErrors);
141
+ }
142
+ return errors;
143
+ };
144
+ return {
145
+ evaluate,
146
+ };
147
+ };
@@ -0,0 +1,10 @@
1
+ import type { HookValue } from "../type/template";
2
+ import type { ContextObject } from "../type/plugin";
3
+ import type { WyvPlugin } from "../type/plugin";
4
+ export declare const WYV_KEY_AND = "$and";
5
+ type WyvParams = HookValue[];
6
+ type WyvSetup = unknown;
7
+ type WyvContext = ContextObject;
8
+ declare const andWyvern: WyvPlugin<WyvParams, WyvSetup, WyvContext>;
9
+ export default andWyvern;
10
+ //# sourceMappingURL=and.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"and.d.ts","sourceRoot":"","sources":["../../src/hooks/and.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAIhD,eAAO,MAAM,WAAW,SAAS,CAAC;AAElC,KAAK,SAAS,GAAG,SAAS,EAAE,CAAC;AAC7B,KAAK,QAAQ,GAAG,OAAO,CAAC;AACxB,KAAK,UAAU,GAAG,aAAa,CAAC;AAEhC,QAAA,MAAM,SAAS,EAAE,SAAS,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CAqCzD,CAAC;AAEF,eAAe,SAAS,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { HookAssessor } from "../util/HookAssessment";
2
+ import { errColl, errConfig } from "../util/HookError";
3
+ export const WYV_KEY_AND = "$and";
4
+ const andWyvern = {
5
+ handles: (value) => [WYV_KEY_AND].includes(value),
6
+ handlers: {
7
+ [WYV_KEY_AND]: (value, _expected, { path, params, evaluate }) => {
8
+ if (!Array.isArray(params)) {
9
+ return HookAssessor.fault(errConfig("$and value should be an array of templates", path));
10
+ }
11
+ if (params.length === 0) {
12
+ // AND(<no arguments>) is defined as TRUE for boolean algebra
13
+ return HookAssessor.SUCCESS;
14
+ }
15
+ const errors = HookAssessor.start();
16
+ for (const template of params) {
17
+ const templateErrors = evaluate(template, value, path);
18
+ errors.include(templateErrors);
19
+ }
20
+ if (errors.success) {
21
+ return HookAssessor.SUCCESS;
22
+ }
23
+ const a = HookAssessor.fault(errColl("$and", path));
24
+ const final = a.include(errors);
25
+ return final;
26
+ },
27
+ },
28
+ };
29
+ export default andWyvern;
@@ -0,0 +1,8 @@
1
+ import type { ContextObject, WyvPlugin } from "../type/plugin";
2
+ export declare const WYV_KEY_ANY = "$any";
3
+ type WyvParams = unknown;
4
+ type WyvSetup = unknown;
5
+ type WyvContext = ContextObject;
6
+ declare const anyWyvern: WyvPlugin<WyvParams, WyvSetup, WyvContext>;
7
+ export default anyWyvern;
8
+ //# sourceMappingURL=any.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"any.d.ts","sourceRoot":"","sources":["../../src/hooks/any.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG/D,eAAO,MAAM,WAAW,SAAS,CAAC;AAElC,KAAK,SAAS,GAAG,OAAO,CAAC;AACzB,KAAK,QAAQ,GAAG,OAAO,CAAC;AACxB,KAAK,UAAU,GAAG,aAAa,CAAC;AAEhC,QAAA,MAAM,SAAS,EAAE,SAAS,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CAOzD,CAAC;AAEF,eAAe,SAAS,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { HookAssessor } from "../util/HookAssessment";
2
+ export const WYV_KEY_ANY = "$any";
3
+ const anyWyvern = {
4
+ handles: (value) => [WYV_KEY_ANY].includes(value),
5
+ handlers: {
6
+ [WYV_KEY_ANY]: () => {
7
+ return HookAssessor.SUCCESS;
8
+ },
9
+ },
10
+ };
11
+ export default anyWyvern;
@@ -0,0 +1,18 @@
1
+ import type { ContextObject } from "../type/plugin";
2
+ import type { WyvPlugin } from "../type/plugin";
3
+ export declare const WYV_KEY_ARRAY = "$array";
4
+ export declare const WYV_ARRAY_PARAM_LENGTH = "$length";
5
+ export declare const WYV_ARRAY_PARAM_MINLENGTH = "$minlength";
6
+ export declare const WYV_ARRAY_PARAM_MAXLENGTH = "$maxlength";
7
+ export declare const WYV_ARRAY_PARAM_EACH = "$each";
8
+ type WyvParams = {
9
+ [WYV_ARRAY_PARAM_LENGTH]?: number;
10
+ [WYV_ARRAY_PARAM_MINLENGTH]?: number;
11
+ [WYV_ARRAY_PARAM_MAXLENGTH]?: number;
12
+ [WYV_ARRAY_PARAM_EACH]?: unknown;
13
+ };
14
+ type WyvSetup = unknown;
15
+ type WyvContext = ContextObject;
16
+ declare const arrayWyvern: WyvPlugin<WyvParams, WyvSetup, WyvContext>;
17
+ export default arrayWyvern;
18
+ //# sourceMappingURL=array.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"array.d.ts","sourceRoot":"","sources":["../../src/hooks/array.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAKhD,eAAO,MAAM,aAAa,WAAW,CAAC;AACtC,eAAO,MAAM,sBAAsB,YAAY,CAAC;AAChD,eAAO,MAAM,yBAAyB,eAAe,CAAC;AACtD,eAAO,MAAM,yBAAyB,eAAe,CAAC;AACtD,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C,KAAK,SAAS,GAAG;IACf,CAAC,sBAAsB,CAAC,CAAC,EAAE,MAAM,CAAC;IAClC,CAAC,yBAAyB,CAAC,CAAC,EAAE,MAAM,CAAC;IACrC,CAAC,yBAAyB,CAAC,CAAC,EAAE,MAAM,CAAC;IACrC,CAAC,oBAAoB,CAAC,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AACF,KAAK,QAAQ,GAAG,OAAO,CAAC;AACxB,KAAK,UAAU,GAAG,aAAa,CAAC;AAEhC,QAAA,MAAM,WAAW,EAAE,SAAS,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CA8F3D,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -0,0 +1,56 @@
1
+ import { HookAssessor } from "../util/HookAssessment";
2
+ import { errAttr, errConfig, errType } from "../util/HookError";
3
+ import { isArray, isDefined, isNumber, isObject, notNull } from "../util/types";
4
+ export const WYV_KEY_ARRAY = "$array";
5
+ export const WYV_ARRAY_PARAM_LENGTH = "$length";
6
+ export const WYV_ARRAY_PARAM_MINLENGTH = "$minlength";
7
+ export const WYV_ARRAY_PARAM_MAXLENGTH = "$maxlength";
8
+ export const WYV_ARRAY_PARAM_EACH = "$each";
9
+ const arrayWyvern = {
10
+ handles: (value) => [WYV_KEY_ARRAY].includes(value),
11
+ handlers: {
12
+ [WYV_KEY_ARRAY]: (value, _expected, { path, params, evaluate }) => {
13
+ if (!isArray(value)) {
14
+ return HookAssessor.fault(errType("array", value, path));
15
+ }
16
+ const errors = HookAssessor.start();
17
+ const normalizedParams = isObject(params)
18
+ ? params
19
+ : { [WYV_ARRAY_PARAM_EACH]: params };
20
+ const { [WYV_ARRAY_PARAM_LENGTH]: length, [WYV_ARRAY_PARAM_MINLENGTH]: minLength, [WYV_ARRAY_PARAM_MAXLENGTH]: maxLength, [WYV_ARRAY_PARAM_EACH]: each, ...rest } = normalizedParams;
21
+ const isParametric = isDefined(length) ||
22
+ isDefined(minLength) ||
23
+ isDefined(maxLength) ||
24
+ isDefined(each);
25
+ if (isParametric && Object.keys(rest).length > 0) {
26
+ errors.fault(errConfig(`Ignored unknown $array parameters: '${Object.keys(rest)}'`, path));
27
+ }
28
+ const useEach = isParametric
29
+ ? each
30
+ : Object.keys(rest).length > 0
31
+ ? rest
32
+ : null;
33
+ if (isDefined(length) && value.length !== length) {
34
+ errors.fault(errAttr(WYV_KEY_ARRAY + "." + WYV_ARRAY_PARAM_LENGTH, length, value.length, path));
35
+ }
36
+ if (isDefined(minLength) &&
37
+ isNumber(minLength) &&
38
+ value.length < minLength) {
39
+ errors.fault(errAttr(WYV_KEY_ARRAY + "." + WYV_ARRAY_PARAM_MINLENGTH, minLength, value.length, path));
40
+ }
41
+ if (isDefined(maxLength) &&
42
+ isNumber(maxLength) &&
43
+ value.length > maxLength) {
44
+ errors.fault(errAttr(WYV_KEY_ARRAY + "." + WYV_ARRAY_PARAM_MAXLENGTH, maxLength, value.length, path));
45
+ }
46
+ if (notNull(useEach)) {
47
+ for (const [index, element] of value.entries()) {
48
+ const elementErrors = evaluate(useEach, element, `${path}[${index}]`);
49
+ errors.include(elementErrors);
50
+ }
51
+ }
52
+ return errors;
53
+ },
54
+ },
55
+ };
56
+ export default arrayWyvern;
@@ -0,0 +1,8 @@
1
+ import type { ContextObject, WyvPlugin } from "../type/plugin";
2
+ export declare const WYV_KEY_BOOL = "$bool";
3
+ type WyvParams = unknown;
4
+ type WyvSetup = unknown;
5
+ type WyvContext = ContextObject;
6
+ declare const boolWyvern: WyvPlugin<WyvParams, WyvSetup, WyvContext>;
7
+ export default boolWyvern;
8
+ //# sourceMappingURL=bool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bool.d.ts","sourceRoot":"","sources":["../../src/hooks/bool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAM/D,eAAO,MAAM,YAAY,UAAU,CAAC;AAEpC,KAAK,SAAS,GAAG,OAAO,CAAC;AACzB,KAAK,QAAQ,GAAG,OAAO,CAAC;AACxB,KAAK,UAAU,GAAG,aAAa,CAAC;AAEhC,QAAA,MAAM,UAAU,EAAE,SAAS,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CAU1D,CAAC;AAEF,eAAe,UAAU,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { HookAssessor } from "../util/HookAssessment";
2
+ import { isBoolean } from "../util/types";
3
+ import { errType } from "../util/HookError";
4
+ export const WYV_KEY_BOOL = "$bool";
5
+ const boolWyvern = {
6
+ handles: (value) => [WYV_KEY_BOOL].includes(value),
7
+ handlers: {
8
+ [WYV_KEY_BOOL]: (value, _expected, { path }) => {
9
+ if (!isBoolean(value)) {
10
+ return HookAssessor.fault(errType("boolean", value, path));
11
+ }
12
+ return HookAssessor.SUCCESS;
13
+ },
14
+ },
15
+ };
16
+ export default boolWyvern;
@@ -0,0 +1,11 @@
1
+ import type { ContextObject, HookKey } from "../type/plugin";
2
+ import type { WyvPlugin } from "../type/plugin";
3
+ export declare const WYV_KEY_ISODATE: HookKey;
4
+ export declare const WYV_KEY_BASICISODATE: HookKey;
5
+ export declare const WYV_KEY_STRICTISODATE: HookKey;
6
+ type WyvParams = unknown;
7
+ type WyvSetup = unknown;
8
+ type WyvContext = ContextObject;
9
+ declare const datetimeWyvern: WyvPlugin<WyvParams, WyvSetup, WyvContext>;
10
+ export default datetimeWyvern;
11
+ //# sourceMappingURL=datetime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datetime.d.ts","sourceRoot":"","sources":["../../src/hooks/datetime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AA+ChD,eAAO,MAAM,eAAe,EAAE,OAAoB,CAAC;AACnD,eAAO,MAAM,oBAAoB,EAAE,OAAyB,CAAC;AAC7D,eAAO,MAAM,qBAAqB,EAAE,OAA0B,CAAC;AAgC/D,KAAK,SAAS,GAAG,OAAO,CAAC;AACzB,KAAK,QAAQ,GAAG,OAAO,CAAC;AACxB,KAAK,UAAU,GAAG,aAAa,CAAC;AAEhC,QAAA,MAAM,cAAc,EAAE,SAAS,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CAsB9D,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -0,0 +1,88 @@
1
+ import { HookAssessor } from "../util/HookAssessment";
2
+ import { errType } from "../util/HookError";
3
+ import { isString } from "../util/types";
4
+ const YEAR = "(?:\\d{4})"; // 0000-9999
5
+ const MONTH = "(?:0[1-9]|1[0-2])"; // 01-12;
6
+ const DAY = `(?:0[1-9]|[12]\\d|3[01])`; // 01-31
7
+ const HOUR = `(?:[01]\\d|2[0-3])`; // 00-23
8
+ const MINUTE = "(?:[0-5]\\d)"; // 00-59
9
+ const SECOND = "(?:[0-5]\\d)"; // 00-59
10
+ const MILLI_EXT = "(?:[,.]\\d+)"; // .123...
11
+ const MILLI_RFC = "(?:\\.\\d+)"; // .123...
12
+ // Validates numeric ranges: YYYY-MM-DD
13
+ const DATE_EXT = `(?:${YEAR}-${MONTH}-${DAY})`;
14
+ const DATE_BAS = `(?:${YEAR}${MONTH}${DAY})`;
15
+ // Validates numeric ranges: HH:mm:ss.SSS (seconds/ms optional)
16
+ const TIME_EXT = `(?:${HOUR}:${MINUTE}(?::${SECOND}(?:${MILLI_EXT})?)?)`;
17
+ const TIME_BAS = `(?:${HOUR}${MINUTE}(?:${SECOND}(?:${MILLI_EXT})?)?)`;
18
+ const TIME_RFC = `(?:${HOUR}:${MINUTE}(?::${SECOND}(?:${MILLI_RFC})?)?)`;
19
+ // Validates Z or ±HH:mm or ±HHmm
20
+ const ZONE_EXT = `(?:Z|[+-]${HOUR}:?${MINUTE})`;
21
+ const ZONE_BAS = `(?:Z|[+-]${HOUR}${MINUTE})`;
22
+ const ZONE_RFC = `(?:Z|[+-]${HOUR}:${MINUTE}|-00:00)`;
23
+ const make8601 = () => {
24
+ return new RegExp(`^${DATE_EXT}T${TIME_EXT}${ZONE_EXT}$`, "i");
25
+ };
26
+ const RE_ISO_8601 = make8601();
27
+ const make8601basic = () => {
28
+ return new RegExp(`^${DATE_BAS}T${TIME_BAS}${ZONE_BAS}$`, "i");
29
+ };
30
+ const RE_ISO_8601_BASIC = make8601basic();
31
+ const make3339 = () => {
32
+ // Full Pattern: YYYY-MM-DDTHH:mm:ss.sssZ
33
+ return new RegExp(`^${DATE_EXT}[T ]${TIME_RFC}${ZONE_RFC}$`, "i");
34
+ };
35
+ const RE_ISO_3339 = make3339();
36
+ export const WYV_KEY_ISODATE = "$isodate";
37
+ export const WYV_KEY_BASICISODATE = "$basicisodate";
38
+ export const WYV_KEY_STRICTISODATE = "$strictisodate";
39
+ const checkParsedDate = (value) => {
40
+ const date = new Date(value);
41
+ return !Number.isNaN(date.getTime());
42
+ };
43
+ const checkIsoDate = (value) => {
44
+ if (!isString(value)) {
45
+ return false;
46
+ }
47
+ if (!RE_ISO_8601.test(value))
48
+ return false;
49
+ return checkParsedDate(value);
50
+ };
51
+ const checkBasicIsoDate = (value) => {
52
+ if (!isString(value)) {
53
+ return false;
54
+ }
55
+ if (!RE_ISO_8601_BASIC.test(value))
56
+ return false;
57
+ // return checkParsedDate(value); // built-in can't parse basic.
58
+ return true;
59
+ };
60
+ const checkStrictIsoDate = (value) => {
61
+ if (!isString(value)) {
62
+ return false;
63
+ }
64
+ if (!RE_ISO_3339.test(value))
65
+ return false;
66
+ return checkParsedDate(value);
67
+ };
68
+ const datetimeWyvern = {
69
+ handles: (value) => [WYV_KEY_ISODATE, WYV_KEY_BASICISODATE, WYV_KEY_STRICTISODATE].includes(value),
70
+ handlers: {
71
+ [WYV_KEY_ISODATE]: (value, _expected, { path }) => {
72
+ if (!checkIsoDate(value))
73
+ return HookAssessor.fault(errType("ISO 8601 date", value, path));
74
+ return HookAssessor.SUCCESS;
75
+ },
76
+ [WYV_KEY_BASICISODATE]: (value, _expected, { path }) => {
77
+ if (!checkBasicIsoDate(value))
78
+ return HookAssessor.fault(errType("Basic ISO 8601 date", value, path));
79
+ return HookAssessor.SUCCESS;
80
+ },
81
+ [WYV_KEY_STRICTISODATE]: (value, _expected, { path }) => {
82
+ if (!checkStrictIsoDate(value))
83
+ return HookAssessor.fault(errType("RFC 3339 date", value, path));
84
+ return HookAssessor.SUCCESS;
85
+ },
86
+ },
87
+ };
88
+ export default datetimeWyvern;
@@ -0,0 +1,8 @@
1
+ import type { ContextObject, WyvPlugin } from "../type/plugin";
2
+ export declare const WYV_KEY_EMAIL = "$email";
3
+ type WyvParams = unknown;
4
+ type WyvSetup = unknown;
5
+ type WyvContext = ContextObject;
6
+ declare const emailWyvern: WyvPlugin<WyvParams, WyvSetup, WyvContext>;
7
+ export default emailWyvern;
8
+ //# sourceMappingURL=email.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../src/hooks/email.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAa/D,eAAO,MAAM,aAAa,WAAW,CAAC;AAEtC,KAAK,SAAS,GAAG,OAAO,CAAC;AACzB,KAAK,QAAQ,GAAG,OAAO,CAAC;AACxB,KAAK,UAAU,GAAG,aAAa,CAAC;AAEhC,QAAA,MAAM,WAAW,EAAE,SAAS,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CAc3D,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { HookAssessor } from "../util/HookAssessment";
2
+ import { isString } from "../util/types";
3
+ import { errType } from "../util/HookError";
4
+ const ncg = (s) => `(?:${s})`;
5
+ const CHARS = `[a-zA-Z0-9_%+\\-]+`;
6
+ const DOT_CHARS = ncg(`\\.${CHARS}`);
7
+ const DOTTED_CHARS_ANY = ncg(`${CHARS}${DOT_CHARS}*`);
8
+ const DOTTED_CHARS_SOME = ncg(`${CHARS}${DOT_CHARS}+`);
9
+ const EMAIL = `${DOTTED_CHARS_ANY}@${DOTTED_CHARS_SOME}`;
10
+ const RE_EMAIL_LOOSE = new RegExp(`^${EMAIL}$`);
11
+ export const WYV_KEY_EMAIL = "$email";
12
+ const emailWyvern = {
13
+ handles: (value) => [WYV_KEY_EMAIL].includes(value),
14
+ handlers: {
15
+ [WYV_KEY_EMAIL]: (value, _expected, { path }) => {
16
+ if (!isString(value)) {
17
+ return HookAssessor.fault(errType("string", value, path));
18
+ }
19
+ if (!RE_EMAIL_LOOSE.test(value)) {
20
+ return HookAssessor.fault(errType("valid email address", value, path));
21
+ }
22
+ return HookAssessor.SUCCESS;
23
+ },
24
+ },
25
+ };
26
+ export default emailWyvern;
@@ -0,0 +1,3 @@
1
+ import type { PluginList } from "../type/plugin";
2
+ export declare const defaultHooks: PluginList;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAgBjD,eAAO,MAAM,YAAY,EAAE,UAc1B,CAAC"}