@based/schema 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 (63) hide show
  1. package/README.md +21 -0
  2. package/dist/deepPartial.d.ts +0 -0
  3. package/dist/deepPartial.js +3 -0
  4. package/dist/deepPartial.js.map +1 -0
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.js +20 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/languages.d.ts +187 -0
  9. package/dist/languages.js +190 -0
  10. package/dist/languages.js.map +1 -0
  11. package/dist/schema.d.ts +1 -0
  12. package/dist/schema.js +3 -0
  13. package/dist/schema.js.map +1 -0
  14. package/dist/set/enum.d.ts +2 -0
  15. package/dist/set/enum.js +15 -0
  16. package/dist/set/enum.js.map +1 -0
  17. package/dist/set/fieldValidator.d.ts +6 -0
  18. package/dist/set/fieldValidator.js +144 -0
  19. package/dist/set/fieldValidator.js.map +1 -0
  20. package/dist/set/handleError.d.ts +1 -0
  21. package/dist/set/handleError.js +9 -0
  22. package/dist/set/handleError.js.map +1 -0
  23. package/dist/set/index.d.ts +5 -0
  24. package/dist/set/index.js +93 -0
  25. package/dist/set/index.js.map +1 -0
  26. package/dist/set/parsers.d.ts +6 -0
  27. package/dist/set/parsers.js +287 -0
  28. package/dist/set/parsers.js.map +1 -0
  29. package/dist/setWalker.d.ts +11 -0
  30. package/dist/setWalker.js +189 -0
  31. package/dist/setWalker.js.map +1 -0
  32. package/dist/transformers.d.ts +3 -0
  33. package/dist/transformers.js +18 -0
  34. package/dist/transformers.js.map +1 -0
  35. package/dist/typeWalker.d.ts +3 -0
  36. package/dist/typeWalker.js +18 -0
  37. package/dist/typeWalker.js.map +1 -0
  38. package/dist/types.d.ts +163 -0
  39. package/dist/types.js +8 -0
  40. package/dist/types.js.map +1 -0
  41. package/dist/validate.d.ts +4 -0
  42. package/dist/validate.js +34 -0
  43. package/dist/validate.js.map +1 -0
  44. package/dist/validateFields.d.ts +4 -0
  45. package/dist/validateFields.js +34 -0
  46. package/dist/validateFields.js.map +1 -0
  47. package/dist/validateSchema copy.d.ts +4 -0
  48. package/dist/validateSchema copy.js +34 -0
  49. package/dist/validateSchema copy.js.map +1 -0
  50. package/dist/validateSchema.d.ts +4 -0
  51. package/dist/validateSchema.js +34 -0
  52. package/dist/validateSchema.js.map +1 -0
  53. package/package.json +35 -0
  54. package/src/index.ts +5 -0
  55. package/src/languages.ts +188 -0
  56. package/src/set/handleError.ts +15 -0
  57. package/src/set/index.ts +135 -0
  58. package/src/set/parsers.ts +448 -0
  59. package/src/types.ts +277 -0
  60. package/src/validateSchema.ts +56 -0
  61. package/test/setWalker.ts +197 -0
  62. package/test/validateSchema.ts +42 -0
  63. package/tsconfig.json +9 -0
package/src/types.ts ADDED
@@ -0,0 +1,277 @@
1
+ import type { Language } from './languages'
2
+ import type { PartialDeep } from 'type-fest'
3
+
4
+ // Schema type
5
+ // inspiration from https://json-schema.org/understanding-json-schema/index.html
6
+ // but added a few extra types
7
+ // reference
8
+ // references
9
+ // set
10
+ // record
11
+
12
+ // https://json-schema.org/learn/examples/geographical-location.schema.json
13
+
14
+ // contentSchema can be used for JSON types as well
15
+ // contentSchema can be used for reference / refrences
16
+
17
+ // TODO parser / validator / parseOut / parseIn (parsIn after validator)
18
+
19
+ // for refs etc check https://json-schema.org/understanding-json-schema/structuring.html#defs
20
+
21
+ export type AllowedTypes = (string | { type?: string; $filter: any | any[] })[]
22
+
23
+ export type BasedSchemaFieldType =
24
+ | 'array'
25
+ | 'object'
26
+ | 'record'
27
+ | 'set'
28
+ | 'string'
29
+ | 'number'
30
+ | 'float'
31
+ | 'json'
32
+ | 'integer'
33
+ | 'timestamp'
34
+ | 'reference'
35
+ | 'references'
36
+ | 'text'
37
+
38
+ export const isCollection = (type: string): boolean => {
39
+ return type === 'array' || type === 'object' || type === 'record'
40
+ }
41
+
42
+ export type BasedSchemaPattern = string // RE ^[A-Za-z_][A-Za-z0-9_]*$
43
+
44
+ export type BasedSchemaLanguage = Language // fix
45
+
46
+ export type BasedSchemaTypePrefix = string // fix
47
+
48
+ // Some examples
49
+ export type BasedSchemaContentMediaType =
50
+ | 'text/html'
51
+ | 'text/plain'
52
+ | 'text/markdown'
53
+ | 'image/png'
54
+ | 'image/jpeg'
55
+ | 'video/mp4'
56
+ | string
57
+
58
+ export type BasedSchemaFieldShared = {
59
+ $id?: string
60
+ $schema?: string
61
+ isRequired?: boolean
62
+ title?: string
63
+ description?: string
64
+ readOnly?: boolean
65
+ writeOnly?: boolean
66
+ $comment?: string
67
+ examples?: any[] // <--- make this generic
68
+ default?: any // <-- make this generic
69
+ // extra thing by us allow users to overwrite entire validations
70
+ customValidator?: (
71
+ value: any,
72
+ path: (number | string)[],
73
+ target: BasedSetTarget
74
+ ) => Promise<boolean>
75
+ $defs?: { [key: string]: BasedSchemaField }
76
+ }
77
+
78
+ // -------------- Primitive ---------------
79
+ export type BasedSchemaFieldString = {
80
+ type: 'string'
81
+ minLength?: number
82
+ maxLength?: number
83
+ contentMediaEncoding?: string // base64
84
+ contentMediaType?: BasedSchemaContentMediaType
85
+ pattern?: BasedSchemaPattern
86
+ format?: 'email' | 'hostname' | 'ipv4' | 'ipv6' | 'uuid' | 'uri'
87
+ // maybe add some more? e.g. phone
88
+ } & BasedSchemaFieldShared
89
+
90
+ export type BasedSchemaFieldEnum = {
91
+ enum: any[] // this changes behaviour pretty extreme
92
+ // important to type as well because we want to enum based on the type e.g. for references
93
+ } & BasedSchemaFieldShared
94
+
95
+ export type BasedSchemaFieldConst = {
96
+ const: any
97
+ } & BasedSchemaFieldShared
98
+
99
+ type NumberDefaults = {
100
+ multipleOf?: number
101
+ minimum?: number
102
+ maximum?: number
103
+ exclusiveMaximum?: boolean
104
+ exclusiveMinimum?: boolean
105
+ }
106
+
107
+ export type BasedSchemaFieldNumber = NumberDefaults & {
108
+ type: 'number'
109
+ }
110
+
111
+ export type BasedSchemaFieldHyperLogLog = {
112
+ type: 'hyperloglog'
113
+ // allow any (objects become hashes)
114
+ }
115
+
116
+ export type BasedSchemaFieldInteger = NumberDefaults & {
117
+ type: 'integer'
118
+ } & BasedSchemaFieldShared
119
+
120
+ export type BasedSchemaFieldTimeStamp = NumberDefaults & {
121
+ type: 'timestamp'
122
+ } & BasedSchemaFieldShared
123
+
124
+ export type BasedSchemaFieldBoolean = {
125
+ type: 'boolean'
126
+ } & BasedSchemaFieldShared
127
+
128
+ // Can support full json SCHEMA validation for this
129
+ export type BasedSchemaFieldJSON = {
130
+ type: 'json'
131
+ } & BasedSchemaFieldShared
132
+
133
+ export type BasedSchemaFieldPrimitive =
134
+ | BasedSchemaFieldString
135
+ | BasedSchemaFieldNumber
136
+ | BasedSchemaFieldInteger
137
+ | BasedSchemaFieldTimeStamp
138
+ | BasedSchemaFieldJSON
139
+ | BasedSchemaFieldBoolean
140
+ | BasedSchemaFieldEnum
141
+ | BasedSchemaFieldShared
142
+
143
+ // -------------- Enumerable ---------------
144
+ export type BasedSchemaFieldText = {
145
+ type: 'text'
146
+ required?: BasedSchemaLanguage[]
147
+ contentMediaType?: BasedSchemaContentMediaType
148
+ minLength?: number
149
+ maxLength?: number
150
+ contentMediaEncoding?: string // base64
151
+ pattern?: BasedSchemaPattern
152
+ } & BasedSchemaFieldShared
153
+
154
+ export type BasedSchemaFieldObject = {
155
+ type: 'object'
156
+ properties: {
157
+ [name: string]: BasedSchemaField
158
+ }
159
+ required?: string[]
160
+ } & BasedSchemaFieldShared
161
+
162
+ export type BasedSchemaFieldRecord = {
163
+ type: 'record'
164
+ values: BasedSchemaField
165
+ } & BasedSchemaFieldShared
166
+
167
+ export type BasedSchemaFieldArray = {
168
+ type: 'array'
169
+ values: BasedSchemaField
170
+ } & BasedSchemaFieldShared
171
+
172
+ export type BasedSchemaFieldSet = {
173
+ type: 'set'
174
+ items: BasedSchemaFieldPrimitive
175
+ } & BasedSchemaFieldShared
176
+
177
+ export type BasedSchemaFieldEnumerable =
178
+ | BasedSchemaFieldText
179
+ | BasedSchemaFieldObject
180
+ | BasedSchemaFieldRecord
181
+ | BasedSchemaFieldArray
182
+ | BasedSchemaFieldSet
183
+
184
+ // -------------- Reference ---------------
185
+ export type BasedSchemaFieldReference = {
186
+ type: 'reference'
187
+ bidirectional?: {
188
+ fromField: string
189
+ }
190
+ // TODO: selva filters { $operator: 'includes', $value: 'image/', $field: 'mimeType' }
191
+ allowedTypes?: AllowedTypes
192
+ } & BasedSchemaFieldShared
193
+
194
+ // make extra package called based db - query (maybe in based-db)
195
+ export type BasedSchemaFieldReferences = {
196
+ type: 'references'
197
+ bidirectional?: {
198
+ fromField: string
199
+ }
200
+ allowedTypes?: AllowedTypes
201
+ } & BasedSchemaFieldShared
202
+
203
+ // return type can be typed - sort of
204
+ export type BasedSchemaField =
205
+ | BasedSchemaFieldEnumerable
206
+ | BasedSchemaFieldPrimitive
207
+ | BasedSchemaFieldReference
208
+ | BasedSchemaFieldReferences
209
+ | BasedSchemaFieldHyperLogLog
210
+ | {
211
+ isRequired?: boolean // our own
212
+ $ref: string // to mimic json schema will just load it in place (so only for setting)
213
+ }
214
+
215
+ export type BasedSchemaType = {
216
+ fields: {
217
+ [name: string]: BasedSchemaField
218
+ }
219
+ title?: string
220
+ description?: string
221
+ prefix?: BasedSchemaTypePrefix
222
+ examples?: any[]
223
+ required?: string[]
224
+ $defs?: { [key: string]: BasedSchemaField }
225
+ }
226
+
227
+ // this is the return value,, optional for insert
228
+ export type BasedSchema = {
229
+ languages: BasedSchemaLanguage[]
230
+ root: BasedSchemaType
231
+ // in our setup this is used as top level /$defs/[name]/
232
+ // in our setup this is used as top level /types/[name]/$defs/[name]
233
+ // #/$defs/name
234
+ $defs: { [name: string]: BasedSchemaField }
235
+ types: {
236
+ [type: string]: BasedSchemaType
237
+ }
238
+ prefixToTypeMapping: {
239
+ [prefix: string]: string
240
+ }
241
+ }
242
+
243
+ export type BasedSchemaTypePartial = PartialDeep<BasedSchemaType>
244
+
245
+ export type BasedSchemaFieldPartial = PartialDeep<BasedSchemaField>
246
+
247
+ export type BasedSchemaPartial = PartialDeep<BasedSchema>
248
+
249
+ export type BasedSetTarget = {
250
+ type: string
251
+ $alias?: string
252
+ $id?: string
253
+ schema: BasedSchema
254
+ $language?: BasedSchemaLanguage
255
+ }
256
+
257
+ export type BasedSetHandlers = {
258
+ collect: (props: {
259
+ path: (string | number)[]
260
+ value: any
261
+ typeSchema: BasedSchemaType
262
+ fieldSchema: BasedSchemaField
263
+ target: BasedSetTarget
264
+ }) => void
265
+
266
+ // has to be fixed / decided upon
267
+ // ['bla[0].xxx', '']
268
+ // [bla, [0]', xxx]
269
+ // (number|string)[]
270
+ // typeschema, target) => Promise<boolean>
271
+
272
+ // $filter
273
+ referenceFilterCondition: (
274
+ referenceId: string,
275
+ $filter: any
276
+ ) => Promise<boolean>
277
+ }
@@ -0,0 +1,56 @@
1
+ import {
2
+ BasedSchemaPartial,
3
+ BasedSchemaFieldPartial,
4
+ BasedSchemaTypePartial,
5
+ BasedSchemaField,
6
+ } from './types'
7
+
8
+ export const validateType = (
9
+ fromSchema: BasedSchemaPartial,
10
+ typeName: string,
11
+ type: BasedSchemaTypePartial
12
+ ) => {
13
+ if (
14
+ type.prefix &&
15
+ (typeof type.prefix !== 'string' || type.prefix.length !== 2)
16
+ ) {
17
+ throw new Error(
18
+ `Incorrect prefix "${type.prefix}" for type "${typeName}" has to be a string of 2 alphanumerical characters e.g. "Az", "ab", "cc", "10"`
19
+ )
20
+ }
21
+ }
22
+
23
+ export const validateField = (
24
+ fromSchema: BasedSchemaPartial,
25
+ path: string[],
26
+ field: BasedSchemaFieldPartial
27
+ ) => {}
28
+
29
+ export const validateSchema = (
30
+ schema: BasedSchemaPartial
31
+ ): BasedSchemaPartial => {
32
+ // rewrite schema things like required / required: []
33
+
34
+ if (typeof schema !== 'object') {
35
+ throw new Error('Schema is not an object')
36
+ }
37
+
38
+ if (schema.languages && !Array.isArray(schema.languages)) {
39
+ throw new Error('Languages needs to be an array')
40
+ }
41
+
42
+ if (schema.$defs) {
43
+ // first defs ofc
44
+ }
45
+
46
+ if (schema.root) {
47
+ }
48
+
49
+ if (schema.types) {
50
+ for (const type in schema.types) {
51
+ validateType(schema, type, schema.types[type])
52
+ }
53
+ }
54
+
55
+ return schema
56
+ }
@@ -0,0 +1,197 @@
1
+ import test from 'ava'
2
+ import { BasedSchema, setWalker } from '../src/index'
3
+
4
+ const schema: BasedSchema = {
5
+ types: {
6
+ bla: {
7
+ prefix: 'bl',
8
+ fields: {
9
+ snurp: {
10
+ type: 'array',
11
+ values: {
12
+ type: 'object',
13
+ properties: {
14
+ x: {
15
+ type: 'array',
16
+ values: {
17
+ type: 'number',
18
+ },
19
+ },
20
+ },
21
+ },
22
+ },
23
+ specialArray: {
24
+ type: 'array',
25
+ values: {
26
+ type: 'string',
27
+ },
28
+ },
29
+ snurpArray: {
30
+ type: 'array',
31
+ values: {
32
+ type: 'number',
33
+ },
34
+ },
35
+ powerLevel: {
36
+ type: 'integer',
37
+ },
38
+ form: {
39
+ title: 'A registration form',
40
+ description: 'A simple form example.',
41
+ type: 'object',
42
+ required: ['firstName', 'lastName'],
43
+ properties: {
44
+ bla: {
45
+ type: 'references',
46
+ },
47
+ blab: {
48
+ type: 'references',
49
+ },
50
+ snurp: {
51
+ type: 'reference',
52
+ },
53
+ things: {
54
+ enum: ['yuzi', 'jux', 'mr tony', 9000],
55
+ },
56
+ blub: {
57
+ type: 'set',
58
+ items: { type: 'string' },
59
+ },
60
+ json: {
61
+ type: 'json',
62
+ },
63
+ firstName: {
64
+ type: 'string',
65
+ title: 'First name',
66
+ default: 'Chuck',
67
+ },
68
+ lastName: {
69
+ type: 'string',
70
+ title: 'Last name',
71
+ },
72
+ age: {
73
+ type: 'integer',
74
+ title: 'Age',
75
+ },
76
+ bio: {
77
+ type: 'string',
78
+ title: 'Bio',
79
+ },
80
+ password: {
81
+ type: 'string',
82
+ title: 'Password',
83
+ minLength: 3,
84
+ },
85
+ telephone: {
86
+ type: 'string',
87
+ title: 'Telephone',
88
+ minLength: 10,
89
+ },
90
+ },
91
+ },
92
+ },
93
+ },
94
+ },
95
+ $defs: {},
96
+ languages: ['en'],
97
+ root: {
98
+ fields: {},
99
+ },
100
+ prefixToTypeMapping: {
101
+ bl: 'bla',
102
+ },
103
+ }
104
+
105
+ // $value
106
+ // $default
107
+
108
+ // $noRoot
109
+
110
+ // $delete -> change for set / references
111
+
112
+ // $merge: false,
113
+
114
+ // $increment
115
+ // $decrement
116
+
117
+ // $assign
118
+ // $insert
119
+ // $remove
120
+ // $push: 7,
121
+ // $unshift ( $unshift: {$value: 123,$maxLen: 10,},)
122
+ // $alias: 'maTestWithAlias',
123
+ // aliases (set
124
+
125
+ // parse formats for string e.g. uri / email etc
126
+
127
+ test.serial('collect correctly', async (t) => {
128
+ const results: { path: (string | number)[]; value: any }[] = []
129
+ await setWalker(
130
+ schema,
131
+ {
132
+ $id: 'bl1',
133
+ form: {
134
+ lastName: 'de beer',
135
+ bla: ['bl123', 'bl234'],
136
+ blab: { $add: ['bl456'] },
137
+ blub: ['x'],
138
+ json: { bla: 1, x: 2, y: 3 },
139
+ snurp: 'blx12',
140
+ things: 'mr tony',
141
+ password: 'mypassword!',
142
+ },
143
+ snurpArray: {
144
+ $assign: {
145
+ $idx: 0,
146
+ $value: 100,
147
+ },
148
+ },
149
+ specialArray: {
150
+ $insert: {
151
+ $value: ['a', 'b', 'c'],
152
+ $idx: 0,
153
+ },
154
+ },
155
+ snurp: [
156
+ {
157
+ x: [1, 2, 3],
158
+ },
159
+ ],
160
+ },
161
+ {
162
+ collect: ({ path, value, typeSchema, fieldSchema, target }) => {
163
+ results.push({
164
+ path,
165
+ value,
166
+ })
167
+ },
168
+ referenceFilterCondition: async (id, filter) => {
169
+ return true
170
+ },
171
+ }
172
+ )
173
+
174
+ const result = [
175
+ { path: ['form', 'lastName'], value: 'de beer' },
176
+ { path: ['form', 'json'], value: '{"bla":1,"x":2,"y":3}' },
177
+ { path: ['form', 'snurp'], value: 'blx12' },
178
+ { path: ['form', 'things'], value: 2 },
179
+ { path: ['form', 'password'], value: 'mypassword!' },
180
+ {
181
+ path: ['snurpArray'],
182
+ value: { $assign: { $idx: 0, $value: 100 } },
183
+ },
184
+ {
185
+ path: ['specialArray'],
186
+ value: { $insert: { $value: ['a', 'b', 'c'], $idx: 0 } },
187
+ },
188
+ { path: ['snurp', 0, 'x', 0], value: 1 },
189
+ { path: ['snurp', 0, 'x', 1], value: 2 },
190
+ { path: ['snurp', 0, 'x', 2], value: 3 },
191
+ { path: ['form', 'bla'], value: ['bl123', 'bl234'] },
192
+ { path: ['form', 'blab'], value: { $add: ['bl456'] } },
193
+ { path: ['form', 'blub'], value: ['x'] },
194
+ ]
195
+
196
+ t.deepEqual(results, result)
197
+ })
@@ -0,0 +1,42 @@
1
+ import test from 'ava'
2
+ import { validateSchema } from '../src/index'
3
+
4
+ test.serial('throw on invalid schema', async (t) => {
5
+ const prefixError = t.throws(() => {
6
+ validateSchema({
7
+ $defs: {
8
+ yuzi: {
9
+ type: 'string',
10
+ title: 'BLA',
11
+ description: 'SNURP',
12
+ },
13
+ },
14
+ types: {
15
+ bla: {
16
+ prefix: 'fix',
17
+ fields: {
18
+ yuzi: {
19
+ type: 'object',
20
+ customValidator: async (value, path, target) => {
21
+ return true
22
+ },
23
+ properties: {
24
+ gurt: {
25
+ $ref: '/$defs/yuzi',
26
+ },
27
+ flap: {
28
+ enum: ['bla', 'blap', 'flip'],
29
+ },
30
+ },
31
+ },
32
+ },
33
+ },
34
+ },
35
+ })
36
+ })
37
+
38
+ t.is(
39
+ prefixError.message,
40
+ 'Incorrect prefix "fix" for type "bla" has to be a string of 2 alphanumerical characters e.g. "Az", "ab", "cc", "10"'
41
+ )
42
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src/**/*", "src/**/*.json"],
8
+ "exclude": ["node_modules", "test", "dist", "tmp", "examples"]
9
+ }