@basictech/schema 0.1.0-beta.0 → 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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../index.ts"],"sourcesContent":["function hello() : string { \n return 'hello'\n}\n\nexport default hello"],"mappings":";AAAA,SAAS,QAAiB;AACtB,SAAO;AACX;AAEA,IAAO,iBAAQ;","names":[]}
1
+ {"version":3,"sources":["../index.ts"],"sourcesContent":["// Basic Schema Library\n// utils for validating and interacting with Basic schemas\nimport Ajv, { ErrorObject } from 'ajv'\n\nconst basicJsonSchema = {\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"properties\": {\n \"project_id\": {\n \"type\": \"string\"\n },\n \"namespace\": {\n \"type\": \"string\",\n },\n \"version\": {\n \"type\": \"integer\",\n \"minimum\": 0\n },\n \"tables\": {\n \"type\": \"object\",\n \"patternProperties\": {\n \"^[a-zA-Z0-9_]+$\": {\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\",\n \"enum\": [\"collection\"]\n },\n \"fields\": {\n \"type\": \"object\",\n \"patternProperties\": {\n \"^[a-zA-Z0-9_]+$\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"enum\": [\"string\", \"boolean\", \"number\", \"json\"]\n },\n \"indexed\": {\n \"type\": \"boolean\"\n }, \n \"required\": {\n \"type\": \"boolean\"\n }\n },\n \"required\": [\"type\"]\n }\n },\n \"additionalProperties\": true\n }\n },\n \"required\": [\"fields\"]\n }\n },\n \"additionalProperties\": true\n }\n },\n \"required\": [\"project_id\", \"version\", \"tables\"]\n }\n\nconst ajv = new Ajv()\nconst validator = ajv.compile(basicJsonSchema)\n\n\nfunction generateEmptySchema(project_id: string = \"\", version: number = 0) {\n return {\n project_id: project_id,\n version: version,\n tables: {\n fields: {\n example: { \n type: \"string\",\n }\n }\n }\n }\n}\n\ntype Schema = { \n project_id : string,\n version : number,\n tables : any\n}\n\n\n/**\n * Validate a schema\n * only checks if the schema is formatted correctly, not if can be published\n * @param schema - The schema to validate\n * @returns {valid: boolean, errors: any[]} - The validation result\n */\nfunction validateSchema(schema: any) : {valid: boolean, errors: ErrorObject[]} {\n const v = validator(schema)\n return { \n valid: v,\n errors: validator.errors || []\n }\n}\n\n// type ErrorObject = {\n// keyword: string;\n// instancePath: string; \n// schemaPath: string;\n// params: Record<string, any>;\n// propertyName?: string;\n// message?: string;\n// schema?: any;\n// parentSchema?: any;\n// data?: any;\n// }\n\n\n/**\n * Validate data against a schema's table definition. Only checks against provided schema.\n * @param schema - The schema to validate against\n * @param table - The table name in the schema to validate against\n * @param data - The data object to validate\n * @param checkRequired - Whether to check if required fields are present (default: true)\n * @returns {Object} Validation result containing:\n * - valid: boolean indicating if validation passed\n * - errors: Array of validation error objects\n * - message: Error message if validation failed\n */\n\nfunction validateData(schema: any, table: string, data: Record<string, any>, checkRequired: boolean = true) {\n const valid = validateSchema(schema)\n if (!valid.valid) {\n return { valid: false, errors: valid.errors, message: \"Schema is invalid\" }\n }\n\n const tableSchema = schema.tables[table]\n\n if (!tableSchema) {\n return { valid: false, errors: [{ message: `Table ${table} not found in schema` }], message: \"Table not found\" }\n }\n\n for (const [fieldName, fieldValue] of Object.entries(data)) {\n const fieldSchema = tableSchema.fields[fieldName]\n \n if (!fieldSchema) {\n return { \n valid: false, \n errors: [{ message: `Field ${fieldName} not found in schema` }],\n message: \"Invalid field\"\n }\n }\n\n const schemaType = fieldSchema.type\n const valueType = typeof fieldValue\n\n if ( \n (schemaType === 'string' && valueType !== 'string') ||\n (schemaType === 'number' && valueType !== 'number') ||\n (schemaType === 'boolean' && valueType !== 'boolean') ||\n (schemaType === 'json' && valueType !== 'object')\n ) {\n return {\n valid: false,\n errors: [{ \n message: `Field ${fieldName} should be type ${schemaType}, got ${valueType}` \n }],\n message: \"invalid type\"\n }\n }\n }\n\n if (checkRequired) {\n for (const [fieldName, fieldSchema] of Object.entries(tableSchema.fields)) {\n if ((fieldSchema as { required?: boolean }).required && !data[fieldName]) {\n return { valid: false, errors: [{ message: `Field ${fieldName} is required` }], message: \"Required field missing\" }\n }\n }\n }\n\n return { valid: true, errors: [] }\n}\n\ntype SchemaChangeType = \"property_changed\" | \"property_removed\" | \"table_added\" | \"table_removed\" | \"field_added\" | \"field_removed\" | \"field_type_changed\" | \"field_required_changed\" | \"field_property_added\" | \"field_property_changed\" | \"field_property_removed\"\n\n\ntype SchemaChange = {\n type: SchemaChangeType,\n property?: string,\n table?: string,\n field?: string,\n old?: any,\n new?: any\n}\n\nfunction _getSchemaChanges(oldSchema: any, newSchema: any) : SchemaChange[] {\n // Compare tables between schemas\n const changes : SchemaChange[] = []\n\n // Check for top level property changes\n for (const key in newSchema) {\n if (key !== 'tables' && newSchema[key] !== oldSchema[key]) {\n changes.push({\n type: 'property_changed',\n property: key,\n old: oldSchema[key],\n new: newSchema[key]\n })\n }\n }\n\n for (const key in oldSchema) {\n if (key !== 'tables' && !newSchema.hasOwnProperty(key)) {\n changes.push({\n type: 'property_removed',\n property: key,\n old: oldSchema[key]\n })\n }\n }\n\n // Check for removed tables\n for (const tableName in oldSchema.tables) {\n if (!newSchema.tables[tableName]) {\n changes.push({\n type: 'table_removed',\n table: tableName\n })\n }\n }\n\n // Check for added tables and field changes\n for (const tableName in newSchema.tables) {\n const newTable = newSchema.tables[tableName]\n const oldTable = oldSchema.tables[tableName]\n\n if (!oldTable) {\n changes.push({\n type: 'table_added', \n table: tableName\n })\n continue\n }\n\n // Compare fields\n for (const fieldName in newTable.fields) {\n const newField = newTable.fields[fieldName]\n const oldField = oldTable.fields[fieldName]\n\n if (!oldField) {\n changes.push({\n type: 'field_added',\n table: tableName,\n field: fieldName\n })\n continue\n }\n\n // Check for field type changes\n if (newField.type !== oldField.type) {\n changes.push({\n type: 'field_type_changed',\n table: tableName,\n field: fieldName,\n old: oldField.type,\n new: newField.type\n })\n }\n\n // Check for required flag changes\n if (newField.required !== oldField.required) {\n changes.push({\n type: 'field_required_changed',\n table: tableName,\n field: fieldName,\n old: oldField.required,\n new: newField.required\n })\n }\n }\n\n // Check for removed fields\n for (const fieldName in oldTable.fields) {\n if (!newTable.fields[fieldName]) {\n changes.push({\n type: 'field_removed',\n table: tableName,\n field: fieldName\n })\n }\n }\n }\n\n // Check for field property changes (excluding type which is already checked)\n for (const tableName in newSchema.tables) {\n const newTable = newSchema.tables[tableName]\n const oldTable = oldSchema.tables[tableName]\n\n if (!oldTable) continue\n\n for (const fieldName in newTable.fields) {\n const newField = newTable.fields[fieldName]\n const oldField = oldTable.fields[fieldName]\n\n if (!oldField) continue\n\n // Compare all properties except type\n for (const prop in newField) {\n if (prop === 'type') continue\n \n if (!(prop in oldField)) {\n changes.push({\n type: 'field_property_added',\n table: tableName,\n field: fieldName,\n property: prop,\n new: newField[prop]\n })\n } else if (JSON.stringify(newField[prop]) !== JSON.stringify(oldField[prop])) {\n changes.push({\n type: 'field_property_changed',\n table: tableName,\n field: fieldName,\n property: prop,\n old: oldField[prop],\n new: newField[prop]\n })\n }\n }\n\n // Check for removed properties\n for (const prop in oldField) {\n if (prop === 'type') continue\n if (!(prop in newField)) {\n changes.push({\n type: 'field_property_removed',\n table: tableName,\n field: fieldName,\n property: prop,\n old: oldField[prop]\n })\n }\n }\n }\n }\n\n return changes\n}\n\n\n// function verifyScehma(schema : any ) { \n// const valid = validateSchema(schema)\n// if (!valid.valid) {\n// return { valid: false, errors: valid.errors, message: \"Schema is invalid\" }\n// }\n\n\n\n\n// return { valid: true, errors: [] }\n// }\n\n\nfunction validateUpdateSchema(oldSchema: any, newSchema: any) {\n const oldValid = validateSchema(oldSchema)\n const newValid = validateSchema(newSchema)\n\n if (!oldValid.valid || !newValid.valid) {\n return { valid: false, errors: oldValid.errors.concat(newValid.errors), message: \"schemas are is invalid\" }\n }\n\n \n const changes = _getSchemaChanges(oldSchema, newSchema)\n\n const changeErrors = []\n for (const change of changes) {\n if (change.type === 'property_changed' && change.property === 'project_id') {\n changeErrors.push({ \n change: change,\n message: \"Cannot modify project_id property\"\n })\n }\n\n if (change.type === 'property_changed' && change.property === 'version') {\n if (change.new !== change.old + 1) {\n changeErrors.push({\n change: change,\n message: `Version must be incremented by 1. Expected version:${change.old + 1}, got version:${change.new}`\n })\n }\n }\n\n if (change.type === 'field_type_changed') {\n changeErrors.push({\n change: change,\n message: `Cannot change type of field \"${change.field}\" from \"${change.old}\" to \"${change.new}\"`\n })\n }\n }\n\n if (changeErrors.length > 0) {\n return {\n valid: false,\n errors: changeErrors,\n message: \"Invalid schema changes detected\"\n }\n }\n\n return { valid: true, changes: changes }\n}\n\nexport {\n validateSchema,\n validateData,\n generateEmptySchema,\n validateUpdateSchema\n}\n\nconst schema = {\n validateSchema,\n validateData,\n generateEmptySchema,\n validateUpdateSchema\n}\n\nexport default schema"],"mappings":"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","names":["schema"]}
package/index.ts CHANGED
@@ -1,5 +1,423 @@
1
- function hello() : string {
2
- return 'hello'
1
+ // Basic Schema Library
2
+ // utils for validating and interacting with Basic schemas
3
+ import Ajv, { ErrorObject } from 'ajv'
4
+
5
+ const basicJsonSchema = {
6
+ "$schema": "http://json-schema.org/draft-07/schema#",
7
+ "type": "object",
8
+ "properties": {
9
+ "project_id": {
10
+ "type": "string"
11
+ },
12
+ "namespace": {
13
+ "type": "string",
14
+ },
15
+ "version": {
16
+ "type": "integer",
17
+ "minimum": 0
18
+ },
19
+ "tables": {
20
+ "type": "object",
21
+ "patternProperties": {
22
+ "^[a-zA-Z0-9_]+$": {
23
+ "type": "object",
24
+ "properties": {
25
+ "name": {
26
+ "type": "string"
27
+ },
28
+ "type": {
29
+ "type": "string",
30
+ "enum": ["collection"]
31
+ },
32
+ "fields": {
33
+ "type": "object",
34
+ "patternProperties": {
35
+ "^[a-zA-Z0-9_]+$": {
36
+ "type": "object",
37
+ "properties": {
38
+ "type": {
39
+ "type": "string",
40
+ "enum": ["string", "boolean", "number", "json"]
41
+ },
42
+ "indexed": {
43
+ "type": "boolean"
44
+ },
45
+ "required": {
46
+ "type": "boolean"
47
+ }
48
+ },
49
+ "required": ["type"]
50
+ }
51
+ },
52
+ "additionalProperties": true
53
+ }
54
+ },
55
+ "required": ["fields"]
56
+ }
57
+ },
58
+ "additionalProperties": true
59
+ }
60
+ },
61
+ "required": ["project_id", "version", "tables"]
62
+ }
63
+
64
+ const ajv = new Ajv()
65
+ const validator = ajv.compile(basicJsonSchema)
66
+
67
+
68
+ function generateEmptySchema(project_id: string = "", version: number = 0) {
69
+ return {
70
+ project_id: project_id,
71
+ version: version,
72
+ tables: {
73
+ fields: {
74
+ example: {
75
+ type: "string",
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ type Schema = {
83
+ project_id : string,
84
+ version : number,
85
+ tables : any
86
+ }
87
+
88
+
89
+ /**
90
+ * Validate a schema
91
+ * only checks if the schema is formatted correctly, not if can be published
92
+ * @param schema - The schema to validate
93
+ * @returns {valid: boolean, errors: any[]} - The validation result
94
+ */
95
+ function validateSchema(schema: any) : {valid: boolean, errors: ErrorObject[]} {
96
+ const v = validator(schema)
97
+ return {
98
+ valid: v,
99
+ errors: validator.errors || []
100
+ }
101
+ }
102
+
103
+ // type ErrorObject = {
104
+ // keyword: string;
105
+ // instancePath: string;
106
+ // schemaPath: string;
107
+ // params: Record<string, any>;
108
+ // propertyName?: string;
109
+ // message?: string;
110
+ // schema?: any;
111
+ // parentSchema?: any;
112
+ // data?: any;
113
+ // }
114
+
115
+
116
+ /**
117
+ * Validate data against a schema's table definition. Only checks against provided schema.
118
+ * @param schema - The schema to validate against
119
+ * @param table - The table name in the schema to validate against
120
+ * @param data - The data object to validate
121
+ * @param checkRequired - Whether to check if required fields are present (default: true)
122
+ * @returns {Object} Validation result containing:
123
+ * - valid: boolean indicating if validation passed
124
+ * - errors: Array of validation error objects
125
+ * - message: Error message if validation failed
126
+ */
127
+
128
+ function validateData(schema: any, table: string, data: Record<string, any>, checkRequired: boolean = true) {
129
+ const valid = validateSchema(schema)
130
+ if (!valid.valid) {
131
+ return { valid: false, errors: valid.errors, message: "Schema is invalid" }
132
+ }
133
+
134
+ const tableSchema = schema.tables[table]
135
+
136
+ if (!tableSchema) {
137
+ return { valid: false, errors: [{ message: `Table ${table} not found in schema` }], message: "Table not found" }
138
+ }
139
+
140
+ for (const [fieldName, fieldValue] of Object.entries(data)) {
141
+ const fieldSchema = tableSchema.fields[fieldName]
142
+
143
+ if (!fieldSchema) {
144
+ return {
145
+ valid: false,
146
+ errors: [{ message: `Field ${fieldName} not found in schema` }],
147
+ message: "Invalid field"
148
+ }
149
+ }
150
+
151
+ const schemaType = fieldSchema.type
152
+ const valueType = typeof fieldValue
153
+
154
+ if (
155
+ (schemaType === 'string' && valueType !== 'string') ||
156
+ (schemaType === 'number' && valueType !== 'number') ||
157
+ (schemaType === 'boolean' && valueType !== 'boolean') ||
158
+ (schemaType === 'json' && valueType !== 'object')
159
+ ) {
160
+ return {
161
+ valid: false,
162
+ errors: [{
163
+ message: `Field ${fieldName} should be type ${schemaType}, got ${valueType}`
164
+ }],
165
+ message: "invalid type"
166
+ }
167
+ }
168
+ }
169
+
170
+ if (checkRequired) {
171
+ for (const [fieldName, fieldSchema] of Object.entries(tableSchema.fields)) {
172
+ if ((fieldSchema as { required?: boolean }).required && !data[fieldName]) {
173
+ return { valid: false, errors: [{ message: `Field ${fieldName} is required` }], message: "Required field missing" }
174
+ }
175
+ }
176
+ }
177
+
178
+ return { valid: true, errors: [] }
179
+ }
180
+
181
+ type SchemaChangeType = "property_changed" | "property_removed" | "table_added" | "table_removed" | "field_added" | "field_removed" | "field_type_changed" | "field_required_changed" | "field_property_added" | "field_property_changed" | "field_property_removed"
182
+
183
+
184
+ type SchemaChange = {
185
+ type: SchemaChangeType,
186
+ property?: string,
187
+ table?: string,
188
+ field?: string,
189
+ old?: any,
190
+ new?: any
191
+ }
192
+
193
+ function _getSchemaChanges(oldSchema: any, newSchema: any) : SchemaChange[] {
194
+ // Compare tables between schemas
195
+ const changes : SchemaChange[] = []
196
+
197
+ // Check for top level property changes
198
+ for (const key in newSchema) {
199
+ if (key !== 'tables' && newSchema[key] !== oldSchema[key]) {
200
+ changes.push({
201
+ type: 'property_changed',
202
+ property: key,
203
+ old: oldSchema[key],
204
+ new: newSchema[key]
205
+ })
206
+ }
207
+ }
208
+
209
+ for (const key in oldSchema) {
210
+ if (key !== 'tables' && !newSchema.hasOwnProperty(key)) {
211
+ changes.push({
212
+ type: 'property_removed',
213
+ property: key,
214
+ old: oldSchema[key]
215
+ })
216
+ }
217
+ }
218
+
219
+ // Check for removed tables
220
+ for (const tableName in oldSchema.tables) {
221
+ if (!newSchema.tables[tableName]) {
222
+ changes.push({
223
+ type: 'table_removed',
224
+ table: tableName
225
+ })
226
+ }
227
+ }
228
+
229
+ // Check for added tables and field changes
230
+ for (const tableName in newSchema.tables) {
231
+ const newTable = newSchema.tables[tableName]
232
+ const oldTable = oldSchema.tables[tableName]
233
+
234
+ if (!oldTable) {
235
+ changes.push({
236
+ type: 'table_added',
237
+ table: tableName
238
+ })
239
+ continue
240
+ }
241
+
242
+ // Compare fields
243
+ for (const fieldName in newTable.fields) {
244
+ const newField = newTable.fields[fieldName]
245
+ const oldField = oldTable.fields[fieldName]
246
+
247
+ if (!oldField) {
248
+ changes.push({
249
+ type: 'field_added',
250
+ table: tableName,
251
+ field: fieldName
252
+ })
253
+ continue
254
+ }
255
+
256
+ // Check for field type changes
257
+ if (newField.type !== oldField.type) {
258
+ changes.push({
259
+ type: 'field_type_changed',
260
+ table: tableName,
261
+ field: fieldName,
262
+ old: oldField.type,
263
+ new: newField.type
264
+ })
265
+ }
266
+
267
+ // Check for required flag changes
268
+ if (newField.required !== oldField.required) {
269
+ changes.push({
270
+ type: 'field_required_changed',
271
+ table: tableName,
272
+ field: fieldName,
273
+ old: oldField.required,
274
+ new: newField.required
275
+ })
276
+ }
277
+ }
278
+
279
+ // Check for removed fields
280
+ for (const fieldName in oldTable.fields) {
281
+ if (!newTable.fields[fieldName]) {
282
+ changes.push({
283
+ type: 'field_removed',
284
+ table: tableName,
285
+ field: fieldName
286
+ })
287
+ }
288
+ }
289
+ }
290
+
291
+ // Check for field property changes (excluding type which is already checked)
292
+ for (const tableName in newSchema.tables) {
293
+ const newTable = newSchema.tables[tableName]
294
+ const oldTable = oldSchema.tables[tableName]
295
+
296
+ if (!oldTable) continue
297
+
298
+ for (const fieldName in newTable.fields) {
299
+ const newField = newTable.fields[fieldName]
300
+ const oldField = oldTable.fields[fieldName]
301
+
302
+ if (!oldField) continue
303
+
304
+ // Compare all properties except type
305
+ for (const prop in newField) {
306
+ if (prop === 'type') continue
307
+
308
+ if (!(prop in oldField)) {
309
+ changes.push({
310
+ type: 'field_property_added',
311
+ table: tableName,
312
+ field: fieldName,
313
+ property: prop,
314
+ new: newField[prop]
315
+ })
316
+ } else if (JSON.stringify(newField[prop]) !== JSON.stringify(oldField[prop])) {
317
+ changes.push({
318
+ type: 'field_property_changed',
319
+ table: tableName,
320
+ field: fieldName,
321
+ property: prop,
322
+ old: oldField[prop],
323
+ new: newField[prop]
324
+ })
325
+ }
326
+ }
327
+
328
+ // Check for removed properties
329
+ for (const prop in oldField) {
330
+ if (prop === 'type') continue
331
+ if (!(prop in newField)) {
332
+ changes.push({
333
+ type: 'field_property_removed',
334
+ table: tableName,
335
+ field: fieldName,
336
+ property: prop,
337
+ old: oldField[prop]
338
+ })
339
+ }
340
+ }
341
+ }
342
+ }
343
+
344
+ return changes
345
+ }
346
+
347
+
348
+ // function verifyScehma(schema : any ) {
349
+ // const valid = validateSchema(schema)
350
+ // if (!valid.valid) {
351
+ // return { valid: false, errors: valid.errors, message: "Schema is invalid" }
352
+ // }
353
+
354
+
355
+
356
+
357
+ // return { valid: true, errors: [] }
358
+ // }
359
+
360
+
361
+ function validateUpdateSchema(oldSchema: any, newSchema: any) {
362
+ const oldValid = validateSchema(oldSchema)
363
+ const newValid = validateSchema(newSchema)
364
+
365
+ if (!oldValid.valid || !newValid.valid) {
366
+ return { valid: false, errors: oldValid.errors.concat(newValid.errors), message: "schemas are is invalid" }
367
+ }
368
+
369
+
370
+ const changes = _getSchemaChanges(oldSchema, newSchema)
371
+
372
+ const changeErrors = []
373
+ for (const change of changes) {
374
+ if (change.type === 'property_changed' && change.property === 'project_id') {
375
+ changeErrors.push({
376
+ change: change,
377
+ message: "Cannot modify project_id property"
378
+ })
379
+ }
380
+
381
+ if (change.type === 'property_changed' && change.property === 'version') {
382
+ if (change.new !== change.old + 1) {
383
+ changeErrors.push({
384
+ change: change,
385
+ message: `Version must be incremented by 1. Expected version:${change.old + 1}, got version:${change.new}`
386
+ })
387
+ }
388
+ }
389
+
390
+ if (change.type === 'field_type_changed') {
391
+ changeErrors.push({
392
+ change: change,
393
+ message: `Cannot change type of field "${change.field}" from "${change.old}" to "${change.new}"`
394
+ })
395
+ }
396
+ }
397
+
398
+ if (changeErrors.length > 0) {
399
+ return {
400
+ valid: false,
401
+ errors: changeErrors,
402
+ message: "Invalid schema changes detected"
403
+ }
404
+ }
405
+
406
+ return { valid: true, changes: changes }
407
+ }
408
+
409
+ export {
410
+ validateSchema,
411
+ validateData,
412
+ generateEmptySchema,
413
+ validateUpdateSchema
414
+ }
415
+
416
+ const schema = {
417
+ validateSchema,
418
+ validateData,
419
+ generateEmptySchema,
420
+ validateUpdateSchema
3
421
  }
4
422
 
5
- export default hello
423
+ export default schema
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basictech/schema",
3
- "version": "0.1.0-beta.0",
3
+ "version": "0.1.0",
4
4
  "description": "utils for Basic Schema",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@repo/typescript-config/base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist"
5
+ },
6
+ "include": ["index.ts"],
7
+ "exclude": ["node_modules", "dist"]
8
+ }
9
+