schema-shield 0.0.6 → 1.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 (39) hide show
  1. package/README.md +219 -65
  2. package/dist/formats.d.ts.map +1 -1
  3. package/dist/index.d.ts +25 -6
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1837 -484
  6. package/dist/index.min.js +1 -1
  7. package/dist/index.min.js.map +1 -1
  8. package/dist/index.mjs +1837 -484
  9. package/dist/keywords/array-keywords.d.ts.map +1 -1
  10. package/dist/keywords/object-keywords.d.ts.map +1 -1
  11. package/dist/keywords/other-keywords.d.ts.map +1 -1
  12. package/dist/keywords/string-keywords.d.ts.map +1 -1
  13. package/dist/types.d.ts.map +1 -1
  14. package/dist/utils/deep-freeze.d.ts +5 -0
  15. package/dist/utils/deep-freeze.d.ts.map +1 -0
  16. package/dist/utils/has-changed.d.ts +2 -0
  17. package/dist/utils/has-changed.d.ts.map +1 -0
  18. package/dist/utils/index.d.ts +5 -0
  19. package/dist/utils/index.d.ts.map +1 -0
  20. package/dist/{utils.d.ts → utils/main-utils.d.ts} +7 -9
  21. package/dist/utils/main-utils.d.ts.map +1 -0
  22. package/dist/utils/pattern-matcher.d.ts +3 -0
  23. package/dist/utils/pattern-matcher.d.ts.map +1 -0
  24. package/lib/formats.ts +468 -155
  25. package/lib/index.ts +702 -107
  26. package/lib/keywords/array-keywords.ts +260 -52
  27. package/lib/keywords/number-keywords.ts +1 -1
  28. package/lib/keywords/object-keywords.ts +295 -88
  29. package/lib/keywords/other-keywords.ts +263 -70
  30. package/lib/keywords/string-keywords.ts +123 -7
  31. package/lib/types.ts +5 -18
  32. package/lib/utils/deep-freeze.ts +208 -0
  33. package/lib/utils/has-changed.ts +51 -0
  34. package/lib/utils/index.ts +4 -0
  35. package/lib/{utils.ts → utils/main-utils.ts} +63 -77
  36. package/lib/utils/pattern-matcher.ts +66 -0
  37. package/package.json +2 -2
  38. package/tsconfig.json +4 -4
  39. package/dist/utils.d.ts.map +0 -1
@@ -0,0 +1,208 @@
1
+ export function deepFreeze(
2
+ obj: any,
3
+ freezeClassInstances: boolean = false,
4
+ seen = new WeakSet()
5
+ ): any {
6
+ if (
7
+ obj === null ||
8
+ typeof obj !== "object" ||
9
+ seen.has(obj) ||
10
+ Object.isFrozen(obj)
11
+ ) {
12
+ return obj;
13
+ }
14
+
15
+ seen.add(obj);
16
+
17
+ if (Array.isArray(obj)) {
18
+ for (let i = 0, l = obj.length; i < l; i++) {
19
+ deepFreeze(obj[i], freezeClassInstances, seen);
20
+ }
21
+ } else {
22
+ const props = Reflect.ownKeys(obj);
23
+ for (let i = 0, l = props.length; i < l; i++) {
24
+ deepFreeze(obj[props[i]], freezeClassInstances, seen);
25
+ }
26
+
27
+ // If the object is an instance of a class (not a plain object or array) we need to freeze the prototype
28
+ if (freezeClassInstances) {
29
+ const proto = Object.getPrototypeOf(obj);
30
+ if (proto && proto !== Object.prototype) {
31
+ deepFreeze(proto, freezeClassInstances, seen);
32
+ }
33
+ }
34
+ }
35
+
36
+ Object.freeze(obj);
37
+
38
+ return obj;
39
+ }
40
+
41
+ function isPlainObject(value: any): boolean {
42
+ if (!value || typeof value !== "object") {
43
+ return false;
44
+ }
45
+
46
+ const proto = Object.getPrototypeOf(value);
47
+ return proto === Object.prototype || proto === null;
48
+ }
49
+
50
+ export { isPlainObject };
51
+
52
+ function canUseStructuredClone(value: any): boolean {
53
+ if (typeof structuredClone !== "function") {
54
+ return false;
55
+ }
56
+
57
+ if (typeof Buffer !== "undefined" && value instanceof Buffer) {
58
+ return false;
59
+ }
60
+
61
+ return (
62
+ Array.isArray(value) ||
63
+ isPlainObject(value) ||
64
+ value instanceof Date ||
65
+ value instanceof RegExp ||
66
+ value instanceof Map ||
67
+ value instanceof Set ||
68
+ value instanceof ArrayBuffer ||
69
+ ArrayBuffer.isView(value)
70
+ );
71
+ }
72
+
73
+ export function deepCloneUnfreeze<T>(
74
+ obj: T,
75
+ cloneClassInstances = false,
76
+ seen = new WeakMap()
77
+ ): T {
78
+ if (typeof obj === "undefined" || obj === null || typeof obj !== "object") {
79
+ return obj;
80
+ }
81
+
82
+ const source = obj as any;
83
+
84
+ if (seen.has(source)) {
85
+ return seen.get(source);
86
+ }
87
+
88
+ if (canUseStructuredClone(source)) {
89
+ const cloned = structuredClone(source);
90
+ seen.set(source, cloned);
91
+ return cloned;
92
+ }
93
+
94
+ let clone: any;
95
+
96
+ switch (true) {
97
+ case Array.isArray(source): {
98
+ clone = [];
99
+ seen.set(source, clone);
100
+ for (let i = 0, l = source.length; i < l; i++) {
101
+ clone[i] = deepCloneUnfreeze(source[i], cloneClassInstances, seen);
102
+ }
103
+ return clone;
104
+ }
105
+ case source instanceof Date: {
106
+ clone = new Date(source.getTime());
107
+ seen.set(source, clone);
108
+ return clone;
109
+ }
110
+ case source instanceof RegExp: {
111
+ clone = new RegExp(source.source, source.flags);
112
+ seen.set(source, clone);
113
+ return clone;
114
+ }
115
+ case source instanceof Map: {
116
+ clone = new Map();
117
+ seen.set(source, clone);
118
+ for (const [key, value] of source.entries()) {
119
+ clone.set(
120
+ deepCloneUnfreeze(key, cloneClassInstances, seen),
121
+ deepCloneUnfreeze(value, cloneClassInstances, seen)
122
+ );
123
+ }
124
+ return clone;
125
+ }
126
+ case source instanceof Set: {
127
+ clone = new Set();
128
+ seen.set(source, clone);
129
+ for (const value of source.values()) {
130
+ clone.add(deepCloneUnfreeze(value, cloneClassInstances, seen));
131
+ }
132
+ return clone;
133
+ }
134
+ case source instanceof ArrayBuffer: {
135
+ clone = source.slice(0);
136
+ seen.set(source, clone);
137
+ return clone;
138
+ }
139
+ // TypedArrays and DataView
140
+ case ArrayBuffer.isView(source): {
141
+ clone = new source.constructor(source.buffer.slice(0));
142
+ seen.set(source, clone);
143
+ return clone;
144
+ }
145
+ // Node.js Buffer
146
+ case typeof Buffer !== "undefined" && source instanceof Buffer: {
147
+ clone = Buffer.from(source);
148
+ seen.set(source, clone);
149
+ return clone;
150
+ }
151
+ case source instanceof Error: {
152
+ clone = new source.constructor(source.message);
153
+ seen.set(source, clone);
154
+ break;
155
+ }
156
+ // Non clonable objects
157
+ case source instanceof Promise ||
158
+ source instanceof WeakMap ||
159
+ source instanceof WeakSet: {
160
+ clone = source;
161
+ seen.set(source, clone);
162
+ return clone;
163
+ }
164
+ // Instance of a class
165
+ case source.constructor && source.constructor !== Object: {
166
+ if (!cloneClassInstances) {
167
+ clone = source;
168
+ seen.set(source, clone);
169
+ return clone;
170
+ }
171
+ clone = Object.create(Object.getPrototypeOf(source));
172
+ seen.set(source, clone);
173
+ break;
174
+ }
175
+
176
+ // Plain objects
177
+ default: {
178
+ clone = {};
179
+ seen.set(source, clone);
180
+
181
+ const keys = Reflect.ownKeys(source);
182
+ for (let i = 0, l = keys.length; i < l; i++) {
183
+ const key = keys[i];
184
+ clone[key as string] = deepCloneUnfreeze(
185
+ source[key as string],
186
+ cloneClassInstances,
187
+ seen
188
+ );
189
+ }
190
+ return clone;
191
+ }
192
+ }
193
+
194
+ const descriptors = Object.getOwnPropertyDescriptors(source);
195
+ for (const key of Reflect.ownKeys(descriptors)) {
196
+ const descriptor = descriptors[key as string];
197
+ if ("value" in descriptor) {
198
+ descriptor.value = deepCloneUnfreeze(
199
+ descriptor.value,
200
+ cloneClassInstances,
201
+ seen
202
+ );
203
+ }
204
+ Object.defineProperty(clone, key, descriptor);
205
+ }
206
+
207
+ return clone;
208
+ }
@@ -0,0 +1,51 @@
1
+ // Utility function to check if dependencies have changed recursively
2
+ // eslint-disable-next-line sonarjs/cognitive-complexity
3
+ export function hasChanged(prev: any, current: any) {
4
+ if (Object.is(prev, current)) {
5
+ return false;
6
+ }
7
+
8
+ if (Array.isArray(prev)) {
9
+ if (Array.isArray(current) === false) {
10
+ return true;
11
+ }
12
+
13
+ if (prev.length !== current.length) {
14
+ return true;
15
+ }
16
+
17
+ for (let i = 0; i < current.length; i++) {
18
+ if (hasChanged(prev[i], current[i])) {
19
+ return true;
20
+ }
21
+ }
22
+
23
+ return false;
24
+ }
25
+
26
+ if (typeof prev === "object" && prev !== null) {
27
+ if (typeof current !== "object" || current === null) {
28
+ return true;
29
+ }
30
+
31
+ for (const key in current) {
32
+ if (hasChanged(prev[key], current[key])) {
33
+ return true;
34
+ }
35
+ }
36
+
37
+ for (const key in prev) {
38
+ if (key in current) {
39
+ continue;
40
+ }
41
+
42
+ if (hasChanged(prev[key], undefined)) {
43
+ return true;
44
+ }
45
+ }
46
+
47
+ return false;
48
+ }
49
+
50
+ return true;
51
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./main-utils";
2
+ export * from "./has-changed";
3
+ export * from "./deep-freeze";
4
+ export { deepCloneUnfreeze as deepClone } from "./deep-freeze";
@@ -1,4 +1,4 @@
1
- import { CompiledSchema } from "./index";
1
+ import { CompiledSchema } from "../index";
2
2
 
3
3
  interface ErrorTree {
4
4
  message: string;
@@ -78,18 +78,27 @@ export class ValidationError extends Error {
78
78
 
79
79
  export interface DefineErrorOptions {
80
80
  item?: any; // Final item in the schemaPath
81
- cause?: ValidationError; // Cause of the error
81
+ cause?: ValidationError | true; // Cause of the error
82
82
  data?: any; // Data that caused the error
83
83
  }
84
84
 
85
85
  export interface DefineErrorFunction {
86
- (message: string, options?: DefineErrorOptions): ValidationError;
86
+ (
87
+ message: string,
88
+ options?: DefineErrorOptions
89
+ ): ValidationError | void | true;
87
90
  }
91
+ const FAIL_FAST_DEFINE_ERROR: DefineErrorFunction = () => true;
88
92
 
89
93
  export function getDefinedErrorFunctionForKey(
90
94
  key: string,
91
- schema: CompiledSchema
95
+ schema: CompiledSchema,
96
+ failFast: boolean
92
97
  ) {
98
+ if (failFast) {
99
+ return FAIL_FAST_DEFINE_ERROR;
100
+ }
101
+
93
102
  const KeywordError = new ValidationError(`Invalid ${key}`);
94
103
  KeywordError.keyword = key;
95
104
  KeywordError.schema = schema;
@@ -97,7 +106,8 @@ export function getDefinedErrorFunctionForKey(
97
106
  const defineError: DefineErrorFunction = (message, options = {}) => {
98
107
  KeywordError.message = message;
99
108
  KeywordError.item = options.item;
100
- KeywordError.cause = options.cause;
109
+ KeywordError.cause =
110
+ options.cause && options.cause !== true ? options.cause : undefined;
101
111
  KeywordError.data = options.data;
102
112
  return KeywordError;
103
113
  };
@@ -108,54 +118,6 @@ export function getDefinedErrorFunctionForKey(
108
118
  );
109
119
  }
110
120
 
111
- export function deepEqual(
112
- obj: Array<any> | Record<string, any>,
113
- other: Array<any> | Record<string, any>
114
- ) {
115
- if (Array.isArray(obj) && Array.isArray(other)) {
116
- if (obj.length !== other.length) {
117
- return false;
118
- }
119
-
120
- for (let i = 0; i < obj.length; i++) {
121
- if (!deepEqual(obj[i], other[i])) {
122
- return false;
123
- }
124
- }
125
-
126
- return true;
127
- }
128
-
129
- if (typeof obj === "object" && typeof other === "object") {
130
- if (obj === null || other === null) {
131
- return obj === other;
132
- }
133
-
134
- const keys = Object.keys(obj);
135
- if (keys.length !== Object.keys(other).length) {
136
- return false;
137
- }
138
-
139
- for (const key of keys) {
140
- if (!deepEqual(obj[key], other[key])) {
141
- return false;
142
- }
143
- }
144
-
145
- return true;
146
- }
147
-
148
- return obj === other;
149
- }
150
-
151
- export function isObject(data) {
152
- return typeof data === "object" && data !== null && !Array.isArray(data);
153
- }
154
-
155
- export function areCloseEnough(a, b, epsilon = 1e-15) {
156
- return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b));
157
- }
158
-
159
121
  export function getUTF16Length(str) {
160
122
  let length = 0;
161
123
  for (let i = 0; i < str.length; i++) {
@@ -168,37 +130,61 @@ export function getUTF16Length(str) {
168
130
  return length;
169
131
  }
170
132
 
171
- export function deepClone(obj: any): any {
172
- if (Array.isArray(obj)) {
173
- const result = [];
174
- for (let i = 0; i < obj.length; i++) {
175
- result[i] = deepClone(obj[i]);
176
- }
177
- return result;
178
- }
133
+ export function isCompiledSchema(subSchema: any): subSchema is CompiledSchema {
134
+ return (
135
+ !!subSchema &&
136
+ typeof subSchema === "object" &&
137
+ !Array.isArray(subSchema) &&
138
+ "$validate" in subSchema
139
+ );
140
+ }
179
141
 
180
- // Is class instance of any kind
181
- if (obj && obj.constructor && obj.constructor.name !== "Object") {
182
- return obj;
142
+ export function getNamedFunction<T>(name: string, fn: T): T {
143
+ return Object.defineProperty(fn, "name", { value: name });
144
+ }
145
+
146
+ export function resolvePath(root: any, path: string): any {
147
+ if (!path || path === "#") {
148
+ return root;
183
149
  }
184
150
 
185
- if (isObject(obj)) {
186
- const result = {
187
- ...obj
188
- };
189
- for (const key in obj) {
190
- result[key] = deepClone(obj[key]);
151
+ // JSON Pointer
152
+ if (path.startsWith("#/")) {
153
+ const parts = path.split("/").slice(1);
154
+ let current = root;
155
+
156
+ for (const part of parts) {
157
+ const decodedUriPart = decodeURIComponent(part);
158
+ const key = decodedUriPart.replace(/~1/g, "/").replace(/~0/g, "~");
159
+
160
+ if (current && typeof current === "object" && key in current) {
161
+ current = current[key];
162
+ } else {
163
+ return;
164
+ }
191
165
  }
192
- return result;
166
+ return current;
193
167
  }
194
168
 
195
- return obj;
196
- }
169
+ // Simple lookup by definition name (non-standard, but useful)
170
+ if (!path.includes("#")) {
171
+ if (root.definitions && root.definitions[path]) {
172
+ return root.definitions[path];
173
+ }
174
+ if (root.defs && root.defs[path]) {
175
+ return root.defs[path];
176
+ }
197
177
 
198
- export function isCompiledSchema(subSchema: any): subSchema is CompiledSchema {
199
- return isObject(subSchema) && "$validate" in subSchema;
178
+ if (root.$id && typeof root.$id === "string") {
179
+ if (root.$id === path || root.$id.endsWith("/" + path)) {
180
+ return root;
181
+ }
182
+ }
183
+ }
184
+
185
+ return;
200
186
  }
201
187
 
202
- export function getNamedFunction<T>(name: string, fn: T): T {
203
- return Object.defineProperty(fn, "name", { value: name });
188
+ export function areCloseEnough(a: number, b: number, epsilon = 1e-15): boolean {
189
+ return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b));
204
190
  }
@@ -0,0 +1,66 @@
1
+ const REGEX_META_CHARS = /[\\.^$*+?()[\]{}|]/;
2
+
3
+ function hasRegexMeta(value: string) {
4
+ return REGEX_META_CHARS.test(value);
5
+ }
6
+
7
+ const PATTERN_CACHE = new Map<string, CompiledPatternMatcher>();
8
+
9
+ export type CompiledPatternMatcher = RegExp | ((value: string) => boolean);
10
+
11
+ export function compilePatternMatcher(pattern: string): CompiledPatternMatcher {
12
+ const cached = PATTERN_CACHE.get(pattern);
13
+ if (cached) {
14
+ return cached;
15
+ }
16
+
17
+ let compiled: CompiledPatternMatcher;
18
+
19
+ if (pattern.length === 0) {
20
+ compiled = (_value: string) => true;
21
+ } else if (!hasRegexMeta(pattern)) {
22
+ compiled = (value: string) => value.includes(pattern);
23
+ } else {
24
+ const patternLength = pattern.length;
25
+
26
+ if (patternLength >= 2 && pattern[0] === "^" && pattern[patternLength - 1] === "$") {
27
+ const inner = pattern.slice(1, -1);
28
+ if (!hasRegexMeta(inner)) {
29
+ if (inner.length === 0) {
30
+ compiled = (value: string) => value.length === 0;
31
+ } else {
32
+ compiled = (value: string) => value === inner;
33
+ }
34
+ } else {
35
+ compiled = new RegExp(pattern, "u");
36
+ }
37
+ } else if (pattern[0] === "^") {
38
+ const inner = pattern.slice(1);
39
+ if (!hasRegexMeta(inner)) {
40
+ if (inner.length === 0) {
41
+ compiled = (_value: string) => true;
42
+ } else {
43
+ compiled = (value: string) => value.startsWith(inner);
44
+ }
45
+ } else {
46
+ compiled = new RegExp(pattern, "u");
47
+ }
48
+ } else if (pattern[patternLength - 1] === "$") {
49
+ const inner = pattern.slice(0, -1);
50
+ if (!hasRegexMeta(inner)) {
51
+ if (inner.length === 0) {
52
+ compiled = (_value: string) => true;
53
+ } else {
54
+ compiled = (value: string) => value.endsWith(inner);
55
+ }
56
+ } else {
57
+ compiled = new RegExp(pattern, "u");
58
+ }
59
+ } else {
60
+ compiled = new RegExp(pattern, "u");
61
+ }
62
+ }
63
+
64
+ PATTERN_CACHE.set(pattern, compiled);
65
+ return compiled;
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "schema-shield",
3
- "version": "0.0.6",
3
+ "version": "1.0.1",
4
4
  "description": "A fast library that protects your JSON schema from invalid data.",
5
5
  "repository": "git@github.com:Masquerade-Circus/schema-shield.git",
6
6
  "author": "Masquerade <christian@masquerade-circus.net>",
@@ -74,7 +74,7 @@
74
74
  "dev:source": "NODE_ENV=development nodemon --enable-source-maps -e tsx,ts,json,css -w ./lib -w ./www source.js",
75
75
  "build": "node source.js",
76
76
  "coverage": "nyc report --reporter=lcov",
77
- "commit": "git add . && git-cz",
77
+ "commit": "git add . && git-cz && git push",
78
78
  "release": "release-it --verbose",
79
79
  "release-test": "release-it --dry-run --verbose"
80
80
  },
package/tsconfig.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "jsx": "preserve",
4
- "target": "ES2021",
5
- "moduleResolution": "nodenext",
6
- "resolveJsonModule": true,
4
+ "target": "esnext",
7
5
  "allowSyntheticDefaultImports": true,
8
6
  "allowJs": true,
9
- "esModuleInterop": true
7
+ "esModuleInterop": true,
8
+ "noEmit": false,
9
+ "emitDeclarationOnly": false
10
10
  },
11
11
  "include": ["lib/**/*"],
12
12
  "exclude": ["**/*.spec.ts", "node_modules/**/*", "docs/**/*"],
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AAED,qBAAa,eAAgB,SAAQ,KAAK;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,MAAM,CAAM;IACxB,YAAY,EAAE,MAAM,CAAM;IAC1B,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,cAAc,CAAC;IAExB,OAAO,CAAC,SAAS;IAqBjB,QAAQ,IAAI,eAAe;IAI3B,OAAO,CAAC,QAAQ;IAiBhB,OAAO,IAAI,SAAS;IAKpB,OAAO;;;;CAOR;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED,MAAM,WAAW,mBAAmB;IAClC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,eAAe,CAAC;CAClE;AAED,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,uBAkBvB;AAED,wBAAgB,SAAS,CACvB,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACrC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,WAoCxC;AAED,wBAAgB,QAAQ,CAAC,IAAI,KAAA,WAE5B;AAED,wBAAgB,cAAc,CAAC,CAAC,KAAA,EAAE,CAAC,KAAA,EAAE,OAAO,SAAQ,WAEnD;AAED,wBAAgB,cAAc,CAAC,GAAG,KAAA,UAUjC;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CAyBvC;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,GAAG,GAAG,SAAS,IAAI,cAAc,CAE5E;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAE1D"}