@sleekcms/json-zen 1.0.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/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2022, SleekSky LLC, California, USA
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,292 @@
1
+ # JSON Zen
2
+
3
+ A compact, string-based schema syntax for validating and shaping JSON objects — with far less boilerplate than JSON Schema.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @sleekcms/json-zen
9
+ ```
10
+
11
+ ```js
12
+ // CommonJS
13
+ const { verify, check, shape, toSchema, extendTypes } = require('@sleekcms/json-zen');
14
+
15
+ // ESM / TypeScript
16
+ import { verify, check, shape, toSchema, extendTypes } from '@sleekcms/json-zen';
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```js
22
+ import { verify, check, shape, toSchema } from '@sleekcms/json-zen';
23
+
24
+ const data = { name: 'Alice', age: 30, tags: ['admin', 'user'] };
25
+
26
+ // Validate (throws on failure)
27
+ verify(data, '{name:s, age:n, tags:[s]}'); // true
28
+ // using full type names is equivalent:
29
+ verify(data, '{name:string, age:number, tags:[string]}'); // true
30
+
31
+ // Safe boolean check
32
+ check(data, '{name:s, age:s}'); // false
33
+
34
+ // Generate a schema from an existing object
35
+ toSchema(data); // "{name:string,age:number,tags:[string]}"
36
+
37
+ // Fill in missing fields with typed defaults
38
+ shape({ name: 'Bob' }, '{name:s, age:n, active:b}');
39
+ // { name: 'Bob', age: 0, active: true }
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Schema Syntax
45
+
46
+ Alt schemas are plain strings. Whitespace is ignored.
47
+
48
+ ### Built-in Types
49
+
50
+ | Shorthand | Full name | Validates | Default (shape) |
51
+ |-----------|-----------|----------------------|-----------------|
52
+ | `s` | `string` | string | `""` |
53
+ | `n` | `number` | number (incl. floats)| `0` |
54
+ | `b` | `boolean` | boolean | `true` |
55
+ | — | `null` | strictly `null` | `null` |
56
+ | `?` | — | any value / nullable | `null` |
57
+
58
+ Shorthands and full names are interchangeable: `{a:s}` and `{a:string}` behave identically.
59
+
60
+ ### Objects
61
+
62
+ ```
63
+ {key:type, key:type, ...}
64
+ ```
65
+
66
+ ```js
67
+ verify({ a: 'hello', b: 42 }, '{a:s, b:n}'); // true — shorthands
68
+ verify({ a: 'hello', b: 42 }, '{a:string, b:number}'); // true — full names
69
+ ```
70
+
71
+ ### Arrays
72
+
73
+ A single type applies to every element. Multiple comma-separated types cycle by index.
74
+
75
+ ```js
76
+ verify([1, 2, 3], '[n]'); // true — all numbers
77
+ verify([1, 'x', 2, 'y'], '[n,s]'); // true — alternating number/string
78
+ ```
79
+
80
+ ### Optional fields (`?`)
81
+
82
+ Prefix a type with `?` to make it optional. A missing or `null` value passes validation.
83
+
84
+ ```js
85
+ verify({ a: 1 }, '{a:n, b:?s}'); // true — b is missing but optional
86
+ verify({ a: 1, b: null }, '{a:n, b:?s}'); // true
87
+ ```
88
+
89
+ ### Presence-only validation
90
+
91
+ Omit the type to require only that a key exists and is not `null`/`undefined`.
92
+
93
+ ```js
94
+ verify({ a: 1, b: false }, '{a, b}'); // true
95
+ verify({ a: 1, b: null }, '{a, b}'); // throws — b is null
96
+ ```
97
+
98
+ ### Default values (`type:default`)
99
+
100
+ Attach a default after the type, used by `shape` to fill missing values.
101
+
102
+ ```js
103
+ shape({}, '{name:s:anonymous, count:n:0}');
104
+ // { name: 'anonymous', count: 0 }
105
+ ```
106
+
107
+ Use quotes for defaults containing spaces:
108
+
109
+ ```js
110
+ shape({}, '{title:s:"Hello World"}');
111
+ // { title: 'Hello World' }
112
+ ```
113
+
114
+ ### Wildcard key (`*`)
115
+
116
+ `*:type` matches any key not already listed in the schema.
117
+
118
+ ```js
119
+ verify({ a: 1, b: 2, c: 3 }, '{*:n}'); // true
120
+ verify({ a: 1, b: '2' }, '{a:n, *:s}'); // true — b matched by wildcard
121
+ shape({ x: 1, y: 2, z: 3 }, '{*:n}'); // { x: 1, y: 2, z: 3 }
122
+ ```
123
+
124
+ ### Nested objects and arrays
125
+
126
+ ```js
127
+ const schema = '{user:{name:s, scores:[n]}, active:b}';
128
+
129
+ verify({ user: { name: 'Alice', scores: [10, 20] }, active: true }, schema); // true
130
+ ```
131
+
132
+ ---
133
+
134
+ ## API
135
+
136
+ ### `verify(json, schema, options?)`
137
+
138
+ Validates `json` against `schema`. Returns `true` on success, throws an `ZenError` on failure.
139
+
140
+ ```js
141
+ verify({ a: 1 }, '{a:s}');
142
+ // throws: "json.a: validation failed"
143
+ ```
144
+
145
+ Access all failures at once via `error.errors`:
146
+
147
+ ```js
148
+ try {
149
+ verify({}, '{a:n, b:n}');
150
+ } catch (e) {
151
+ console.log(e.message); // "Validation failed: 2 errors found"
152
+ console.log(e.errors); // [['json.a', 'is required'], ['json.b', 'is required']]
153
+ }
154
+ ```
155
+
156
+ **Options:**
157
+
158
+ | Option | Type | Description |
159
+ |---------|----------|---------------------------------------------------|
160
+ | `_path` | `string` | Override the root label in error messages |
161
+ | _(key)_ | validator | Inline custom type — see [Custom Validators](#custom-validators) |
162
+
163
+ ```js
164
+ verify({ a: 'x' }, '{a:s}', { _path: 'payload' });
165
+ // throws: "payload.a: validation failed"
166
+ ```
167
+
168
+ ---
169
+
170
+ ### `check(json, schema, options?)`
171
+
172
+ Same as `verify` but returns `false` instead of throwing. Schema errors still throw.
173
+
174
+ ```js
175
+ check({ a: 1, b: 'x' }, '{a:n, b:n}'); // false
176
+ check({ a: 1, b: 2 }, '{a:n, b:n}'); // true
177
+ ```
178
+
179
+ ---
180
+
181
+ ### `shape(json, schema, options?)`
182
+
183
+ Returns a new object shaped to the schema. Values from `json` are used when valid; otherwise, typed defaults fill in the gaps. Extra keys in `json` not in the schema are omitted.
184
+
185
+ ```js
186
+ shape({ a: 1, extra: 99 }, '{a:n, b:s, c:b}');
187
+ // { a: 1, b: '', c: true }
188
+ ```
189
+
190
+ Nested structures are shaped recursively:
191
+
192
+ ```js
193
+ shape({ a: {} }, '{a:{x:n, y:s}}');
194
+ // { a: { x: 0, y: '' } }
195
+ ```
196
+
197
+ **Options:**
198
+
199
+ | Option | Type | Description |
200
+ |-------------|-----------|--------------------------------------------------------------|
201
+ | `_optional` | `boolean` | When `true`, fill optional (`?`) fields with defaults instead of `null` |
202
+ | _(key)_ | validator | Inline custom type |
203
+
204
+ ```js
205
+ shape({}, '{a:?n}'); // { a: null }
206
+ shape({}, '{a:?n}', { _optional: true }); // { a: 0 }
207
+ ```
208
+
209
+ ---
210
+
211
+ ### `toSchema(json)`
212
+
213
+ Generates a schema string from a JSON object. Useful for bootstrapping a schema from a known-good example.
214
+
215
+ ```js
216
+ toSchema({ a: 'foo', b: 1, c: [1, 2], d: { e: null } });
217
+ // "{a:string,b:number,c:[number],d:{e:?}}"
218
+ ```
219
+
220
+ ---
221
+
222
+ ### `extendTypes(validators)`
223
+
224
+ Registers one or more custom types globally. Once registered, the type name can be used in any schema.
225
+
226
+ The validator function receives the value when checking, and `undefined` when `shape` needs a default — return the default value in that case.
227
+
228
+ ```js
229
+ import { extendTypes, shape, verify } from '@sleekcms/json-zen';
230
+
231
+ extendTypes({
232
+ url: (value) => {
233
+ if (value === undefined) return 'https://example.com'; // default for shape
234
+ return typeof value === 'string' && value.startsWith('http');
235
+ }
236
+ });
237
+
238
+ verify({ img: 'https://example.com/pic.jpg' }, '{img:url}'); // true
239
+ shape({}, '{img:url}'); // { img: 'https://example.com' }
240
+ ```
241
+
242
+ ---
243
+
244
+ ## Custom Validators
245
+
246
+ Custom validators can be passed inline via `options`, registered globally via `extendTypes`, or provided as enum arrays or RegExp patterns.
247
+
248
+ ### Inline function
249
+
250
+ ```js
251
+ verify({ status: 'active' }, '{status:myType}', {
252
+ myType: (value) => value === 'active' || value === 'inactive'
253
+ });
254
+ ```
255
+
256
+ ### Enum (array)
257
+
258
+ ```js
259
+ verify({ role: 'admin' }, '{role:r}', { r: ['admin', 'user', 'guest'] }); // true
260
+ verify({ role: 'root' }, '{role:r}', { r: ['admin', 'user', 'guest'] }); // throws
261
+ ```
262
+
263
+ ### Regex
264
+
265
+ ```js
266
+ verify({ slug: 'hello-world' }, '{slug:sl}', { sl: /^[a-z-]+$/ }); // true
267
+ ```
268
+
269
+ ### Context-aware validator
270
+
271
+ Validators receive `{ path, json, parent }` as a second argument, enabling cross-field validation:
272
+
273
+ ```js
274
+ extendTypes({
275
+ matchesType: (value, { parent }) => {
276
+ if (parent.type === 'number') return typeof value === 'number';
277
+ if (parent.type === 'string') return typeof value === 'string';
278
+ return false;
279
+ }
280
+ });
281
+
282
+ verify(
283
+ [{ type: 'number', value: 42 }, { type: 'string', value: 'hi' }],
284
+ '[{type:s, value:matchesType}]'
285
+ ); // true
286
+ ```
287
+
288
+ ---
289
+
290
+ ## License
291
+
292
+ MIT © Yusuf Bhabhrawala, SleekSky LLC
package/lib/index.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Author: Yusuf Bhabhrawala
3
+ */
4
+ export declare const setEnv: (e: string | null) => void;
5
+ type FlattenResult = [string, string[], string[]];
6
+ export declare const _flatten: (schema: string) => FlattenResult;
7
+ export type TypeShape = string | TypeShape[] | {
8
+ [key: string]: TypeShape;
9
+ };
10
+ export declare const typeShape: (schema: string) => TypeShape;
11
+ export declare const toSchema: (json: unknown) => string;
12
+ export interface ShapeOptions {
13
+ _optional?: boolean;
14
+ [key: string]: unknown;
15
+ }
16
+ export declare const shape: <T = unknown>(json: unknown, schema: string, options?: ShapeOptions) => T;
17
+ export interface VerifyOptions {
18
+ _path?: string;
19
+ [key: string]: unknown;
20
+ }
21
+ export declare const verify: (json: unknown, schema: string, options?: VerifyOptions) => boolean | undefined;
22
+ export declare const check: (json: unknown, schema: string, options?: VerifyOptions) => boolean;
23
+ export declare const extendTypes: (types: Record<string, unknown>) => void;
24
+ export {};
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAiBH,eAAO,MAAM,MAAM,GAAI,GAAG,MAAM,GAAG,IAAI,KAAG,IAEzC,CAAC;AAEF,KAAK,aAAa,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AAElD,eAAO,MAAM,QAAQ,GAAI,QAAQ,MAAM,KAAG,aAqCzC,CAAC;AAQF,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAG5E,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,SAqC1C,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,MAAM,OAAO,KAAG,MAiBxC,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,KAAK,GAAI,CAAC,GAAG,OAAO,EAAE,MAAM,OAAO,EAAE,QAAQ,MAAM,EAAE,UAAS,YAAiB,KAAG,CAsF9F,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,MAAM,GAAI,MAAM,OAAO,EAAE,QAAQ,MAAM,EAAE,UAAS,aAAkB,KAAG,OAAO,GAAG,SA6F7F,CAAC;AAGF,eAAO,MAAM,KAAK,GAAI,MAAM,OAAO,EAAE,QAAQ,MAAM,EAAE,UAAU,aAAa,KAAG,OAQ9E,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,IAE5D,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,326 @@
1
+ "use strict";
2
+ /**
3
+ * Author: Yusuf Bhabhrawala
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.extendTypes = exports.check = exports.verify = exports.shape = exports.toSchema = exports.typeShape = exports._flatten = exports.setEnv = void 0;
10
+ const types_1 = __importDefault(require("./types"));
11
+ const zen_error_1 = __importDefault(require("./zen-error"));
12
+ const RX = {
13
+ FLAT_ARRAY: /(\[([^\[\]\{\}]*)\])/,
14
+ FLAT_OBJECT: /(\{([^\{\}\[\]]*)\})/,
15
+ MALFORMED: /[\[\]\{\}]/,
16
+ FLAT_SCALAR: /^[^\[\]\{\}]*$/,
17
+ OPTIONAL: /^[\?]/,
18
+ LOOKUP: /^[0-9]+$/,
19
+ DEFAULTS: /^\$([0-9]+)$/
20
+ };
21
+ let env = null;
22
+ const setEnv = (e) => {
23
+ env = e;
24
+ };
25
+ exports.setEnv = setEnv;
26
+ const _flatten = (schema) => {
27
+ const lookups = [];
28
+ const default_strings = [];
29
+ let m;
30
+ // convert "hello world" to $0. remote " (quotes)
31
+ while ((m = schema.match(/"([^"]+)"/))) {
32
+ schema = schema.substr(0, m.index) + `$${default_strings.length}` + schema.substr(m.index + m[0].length);
33
+ default_strings.push(m[1]);
34
+ }
35
+ while ((m = schema.match(/'([^']+)'/))) {
36
+ schema = schema.substr(0, m.index) + `$${default_strings.length}` + schema.substr(m.index + m[0].length);
37
+ default_strings.push(m[1]);
38
+ }
39
+ if (schema.match(/"/))
40
+ throw new Error("Missing closing quote");
41
+ // strip all white spaces
42
+ schema = schema.replace(/\s/g, "");
43
+ function reduce(schema) {
44
+ let m;
45
+ let found = false;
46
+ while ((m = schema.match(RX.FLAT_ARRAY)) || (m = schema.match(RX.FLAT_OBJECT))) {
47
+ schema = schema.substr(0, m.index) + lookups.length + schema.substr(m.index + m[0].length);
48
+ lookups.push(m[0]);
49
+ found = true;
50
+ }
51
+ if (found)
52
+ return reduce(schema);
53
+ if (!found && schema.match(RX.MALFORMED)) {
54
+ throw new Error("Schema error: probably invalid braces or brackets");
55
+ }
56
+ return schema;
57
+ }
58
+ schema = reduce(schema);
59
+ return [schema, lookups, default_strings];
60
+ };
61
+ exports._flatten = _flatten;
62
+ function splitOnce(str, delim) {
63
+ const parts = str.split(delim);
64
+ const first = parts.shift();
65
+ return [first, parts.join(delim)];
66
+ }
67
+ // Set leaf types to format "optional:type:default_value" tuple. Eg. "?:boolean:false" or ":integer:"
68
+ const typeShape = (schema) => {
69
+ let [sch, lookups, default_strings] = (0, exports._flatten)(schema);
70
+ function traverse(sch) {
71
+ if (!sch)
72
+ return "";
73
+ let m;
74
+ if (sch.match(RX.LOOKUP))
75
+ return traverse(lookups[Number(sch)]);
76
+ if ((m = sch.match(RX.FLAT_ARRAY))) {
77
+ sch = m[2];
78
+ const schema_parts = sch.split(",");
79
+ return schema_parts.length > 0 ? [traverse(schema_parts[0])] : [traverse("")];
80
+ }
81
+ if ((m = sch.match(RX.FLAT_OBJECT))) {
82
+ sch = m[2];
83
+ return sch.split(",").reduce((acc, name) => {
84
+ let [k, v] = splitOnce(name, ":");
85
+ if (k.match(RX.DEFAULTS))
86
+ k = default_strings[Number(k.substr(1))];
87
+ acc[k] = traverse(v);
88
+ return acc;
89
+ }, {});
90
+ }
91
+ if (sch.match(RX.FLAT_SCALAR)) {
92
+ let [type, def] = sch.split(":");
93
+ if (!def)
94
+ def = "";
95
+ if (def && def.match(RX.DEFAULTS))
96
+ def = default_strings[Number(def.substr(1))];
97
+ let optional = '';
98
+ if (type.match(RX.OPTIONAL)) {
99
+ optional = '?';
100
+ type = type.substr(1);
101
+ }
102
+ const type_lookup = { i: "integer", n: "number", b: "boolean", s: "string" };
103
+ if (type_lookup[type])
104
+ type = type_lookup[type];
105
+ return [optional, type, def].join(':');
106
+ }
107
+ return "";
108
+ }
109
+ return traverse(sch);
110
+ };
111
+ exports.typeShape = typeShape;
112
+ const toSchema = (json) => {
113
+ function traverse(obj) {
114
+ const type = types_1.default.toSchema(obj);
115
+ if (type === "array") {
116
+ const arr = obj;
117
+ const sch = arr.length > 0 ? traverse(arr[0]) : "";
118
+ return `[${sch}]`;
119
+ }
120
+ if (type === "object") {
121
+ const objRecord = obj;
122
+ return '{' + Object.keys(objRecord).map((k) => {
123
+ return `${k}:${traverse(objRecord[k])}`;
124
+ }).join(",") + '}';
125
+ }
126
+ return type;
127
+ }
128
+ return traverse(json);
129
+ };
130
+ exports.toSchema = toSchema;
131
+ const shape = (json, schema, options = {}) => {
132
+ const types = new types_1.default(options);
133
+ options._optional = options._optional || false;
134
+ let lookups;
135
+ let default_strings;
136
+ [schema, lookups, default_strings] = (0, exports._flatten)(schema);
137
+ const getValue = (type, value, def) => {
138
+ let m;
139
+ if (def && (m = def.match(RX.DEFAULTS)))
140
+ def = default_strings[Number(m[1])];
141
+ if (types.has(type)) {
142
+ return (value && types.check(type, value)) ? value : (def !== undefined) ? types.cast(type, def) : types.sample(type);
143
+ }
144
+ return value || def || "Not defined!";
145
+ };
146
+ // flat validator value = {k:t:d,..} [t:d,..] t:d
147
+ function traverse({ value, schema }) {
148
+ if (!schema)
149
+ schema = "";
150
+ let m;
151
+ let optional = false;
152
+ if (schema.match(RX.OPTIONAL)) {
153
+ schema = schema.substr(1);
154
+ optional = true;
155
+ }
156
+ // if lookup, validate further
157
+ if (schema.match(RX.LOOKUP))
158
+ return traverse({ value, schema: `${optional ? '?' : ''}${lookups[Number(schema)]}` });
159
+ if ((value === null || value === undefined) && optional && !options._optional)
160
+ return null;
161
+ if (schema.match(RX.FLAT_SCALAR)) {
162
+ // if scalar
163
+ let type;
164
+ let def;
165
+ [type, def] = schema.split(":");
166
+ return getValue(type, value, def);
167
+ }
168
+ else if ((m = schema.match(RX.FLAT_ARRAY))) {
169
+ // if array
170
+ schema = m[2];
171
+ const schema_parts = schema.split(",");
172
+ let arr = value;
173
+ if (!Array.isArray(value))
174
+ arr = schema_parts.map(() => null);
175
+ if (arr.length < schema_parts.length) {
176
+ arr = arr.concat(schema_parts.slice(arr.length).map(() => null));
177
+ }
178
+ return arr.map((v, i) => traverse({ value: v, schema: schema_parts[i % schema_parts.length] }));
179
+ }
180
+ else if ((m = schema.match(RX.FLAT_OBJECT))) {
181
+ // if object
182
+ let obj = value;
183
+ if (typeof value !== 'object' || Array.isArray(value)) {
184
+ obj = {};
185
+ }
186
+ schema = m[2];
187
+ if (schema === "")
188
+ return obj;
189
+ let wildcard = false;
190
+ const shp = schema.split(",").reduce((acc, name) => {
191
+ let [k, t, d] = name.split(":");
192
+ if (!t)
193
+ t = "";
194
+ if (d)
195
+ t += ":" + d;
196
+ if (k === "*")
197
+ wildcard = t;
198
+ else
199
+ acc[k] = traverse({ value: obj[k], schema: t });
200
+ return acc;
201
+ }, {});
202
+ if (wildcard !== false) {
203
+ for (const k in obj)
204
+ if (shp[k] === undefined)
205
+ shp[k] = traverse({ value: obj[k], schema: wildcard });
206
+ }
207
+ return shp;
208
+ }
209
+ return value;
210
+ }
211
+ const result = traverse({ value: json, schema });
212
+ return result;
213
+ };
214
+ exports.shape = shape;
215
+ const verify = (json, schema, options = {}) => {
216
+ if (env && env !== "development")
217
+ return undefined;
218
+ const types = new types_1.default(options);
219
+ const errors = [];
220
+ const jsonPath = options._path || 'json';
221
+ let lookups;
222
+ [schema, lookups] = (0, exports._flatten)(schema);
223
+ // flat validator value = {k:t:d,..} [t:d,..] t:d
224
+ function validate({ path, value, schema, parent = null }) {
225
+ let m;
226
+ let optional = false;
227
+ if (schema.match(RX.OPTIONAL)) {
228
+ schema = schema.substr(1);
229
+ if (value === undefined || value === null)
230
+ return true;
231
+ optional = true;
232
+ }
233
+ // if lookup, validate further
234
+ if (schema.match(RX.LOOKUP))
235
+ return validate({ path: `${path}`, value, schema: `${optional ? '?' : ''}${lookups[Number(schema)]}`, parent: parent });
236
+ if (schema.match(RX.FLAT_SCALAR)) {
237
+ // if scalar
238
+ let def;
239
+ [schema, def] = schema.split(":");
240
+ if (value === undefined || value === null) {
241
+ errors.push([path, 'is required']);
242
+ return false;
243
+ }
244
+ if (schema === "")
245
+ return true; // no validation needed
246
+ if (!types.has(schema))
247
+ throw new Error(`Schema error: Validator - ${schema} - not found`);
248
+ else if (types.check(schema, value, { path, json, parent }))
249
+ return true;
250
+ else {
251
+ errors.push([path, 'validation failed']);
252
+ return false;
253
+ }
254
+ }
255
+ else if ((m = schema.match(RX.FLAT_ARRAY))) {
256
+ // if array
257
+ if (!Array.isArray(value)) {
258
+ errors.push([path, 'should be array']);
259
+ return false;
260
+ }
261
+ schema = m[2];
262
+ const schema_parts = schema.split(",");
263
+ for (const i in value) {
264
+ validate({ path: `${path}.${i}`, value: value[i], schema: schema_parts[Number(i) % schema_parts.length], parent: value });
265
+ }
266
+ return true;
267
+ }
268
+ else if ((m = schema.match(RX.FLAT_OBJECT))) {
269
+ // if object
270
+ if (typeof value !== 'object' || Array.isArray(value)) {
271
+ errors.push([path, 'should be object']);
272
+ return false;
273
+ }
274
+ schema = m[2];
275
+ let wildcard = false;
276
+ if (schema !== "") {
277
+ const obj = value;
278
+ const keys = schema.split(",").reduce((acc, name) => {
279
+ let [k, t] = name.split(":");
280
+ if (!t)
281
+ t = "";
282
+ if (k === '*')
283
+ wildcard = t;
284
+ else
285
+ acc[k] = t;
286
+ return acc;
287
+ }, {});
288
+ if (wildcard !== false) {
289
+ for (const k in obj)
290
+ if (keys[k] === undefined)
291
+ keys[k] = wildcard;
292
+ }
293
+ // if object, validate for all k-v
294
+ for (const k in keys)
295
+ validate({ path: `${path}.${k}`, value: obj[k], schema: keys[k], parent: value });
296
+ }
297
+ return true;
298
+ }
299
+ return true;
300
+ }
301
+ validate({ path: jsonPath, value: json, schema });
302
+ if (errors.length > 0)
303
+ throw new zen_error_1.default(errors);
304
+ return true;
305
+ };
306
+ exports.verify = verify;
307
+ // same as verify. returns boolean. won't throw.
308
+ const check = (json, schema, options) => {
309
+ try {
310
+ (0, exports.verify)(json, schema, options);
311
+ return true;
312
+ }
313
+ catch (error) {
314
+ if (error instanceof Error && error.message.match(/^Schema error/))
315
+ throw error;
316
+ return false;
317
+ }
318
+ };
319
+ exports.check = check;
320
+ const extendTypes = (types) => {
321
+ types_1.default.extend(types);
322
+ };
323
+ exports.extendTypes = extendTypes;
324
+ // CommonJS compatibility
325
+ module.exports = { verify: exports.verify, check: exports.check, shape: exports.shape, toSchema: exports.toSchema, _flatten: exports._flatten, typeShape: exports.typeShape, extendTypes: exports.extendTypes, setEnv: exports.setEnv };
326
+ //# sourceMappingURL=index.js.map