@basictech/schema 0.5.0-beta.1 → 0.6.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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +14 -0
- package/dist/index.d.mts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +106 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +106 -54
- package/dist/index.mjs.map +1 -1
- package/index.ts +125 -60
- package/package.json +5 -5
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @basictech/schema@0.
|
|
3
|
+
> @basictech/schema@0.5.0 build
|
|
4
4
|
> tsup
|
|
5
5
|
|
|
6
6
|
[34mCLI[39m Building entry: ./index.ts
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
[34mCLI[39m Cleaning output folder
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
13
|
[34mESM[39m Build start
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
19
|
-
[
|
|
14
|
+
[32mCJS[39m [1mdist/index.js [22m[32m13.75 KB[39m
|
|
15
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m26.36 KB[39m
|
|
16
|
+
[32mCJS[39m ⚡️ Build success in 9ms
|
|
17
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m11.93 KB[39m
|
|
18
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m26.28 KB[39m
|
|
19
|
+
[32mESM[39m ⚡️ Build success in 9ms
|
|
20
20
|
[34mDTS[39m Build start
|
|
21
|
-
[32mDTS[39m ⚡️ Build success in
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
23
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[
|
|
21
|
+
[32mDTS[39m ⚡️ Build success in 1166ms
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m7.04 KB[39m
|
|
23
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m7.04 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @basictech/schema
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- update schema rules
|
|
8
|
+
|
|
9
|
+
## 0.5.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- publish with origin
|
|
14
|
+
- a41eb20: schema validation to react client
|
|
15
|
+
- c76951e: add origin
|
|
16
|
+
|
|
3
17
|
## 0.5.0-beta.1
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -82,6 +82,19 @@ declare function validateUpdateSchema(oldSchema: any, newSchema: any): {
|
|
|
82
82
|
errors: ErrorObject[];
|
|
83
83
|
message: string;
|
|
84
84
|
changes?: undefined;
|
|
85
|
+
} | {
|
|
86
|
+
valid: boolean;
|
|
87
|
+
errors: {
|
|
88
|
+
change: {
|
|
89
|
+
type: string;
|
|
90
|
+
property: string;
|
|
91
|
+
old: any;
|
|
92
|
+
new: any;
|
|
93
|
+
};
|
|
94
|
+
message: string;
|
|
95
|
+
}[];
|
|
96
|
+
message: string;
|
|
97
|
+
changes?: undefined;
|
|
85
98
|
} | {
|
|
86
99
|
valid: boolean;
|
|
87
100
|
changes: SchemaChange[];
|
|
@@ -115,7 +128,7 @@ declare function getJsonSchema(): {
|
|
|
115
128
|
type: string;
|
|
116
129
|
};
|
|
117
130
|
patternProperties: {
|
|
118
|
-
"^[a-zA-Z0-9_]
|
|
131
|
+
"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$": {
|
|
119
132
|
type: string;
|
|
120
133
|
propertyNames: {
|
|
121
134
|
pattern: string;
|
|
@@ -168,7 +181,7 @@ declare function getJsonSchema(): {
|
|
|
168
181
|
type: string;
|
|
169
182
|
};
|
|
170
183
|
patternProperties: {
|
|
171
|
-
"^[a-zA-Z0-9_]
|
|
184
|
+
"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$": {
|
|
172
185
|
type: string;
|
|
173
186
|
properties: {
|
|
174
187
|
type: {
|
package/dist/index.d.ts
CHANGED
|
@@ -82,6 +82,19 @@ declare function validateUpdateSchema(oldSchema: any, newSchema: any): {
|
|
|
82
82
|
errors: ErrorObject[];
|
|
83
83
|
message: string;
|
|
84
84
|
changes?: undefined;
|
|
85
|
+
} | {
|
|
86
|
+
valid: boolean;
|
|
87
|
+
errors: {
|
|
88
|
+
change: {
|
|
89
|
+
type: string;
|
|
90
|
+
property: string;
|
|
91
|
+
old: any;
|
|
92
|
+
new: any;
|
|
93
|
+
};
|
|
94
|
+
message: string;
|
|
95
|
+
}[];
|
|
96
|
+
message: string;
|
|
97
|
+
changes?: undefined;
|
|
85
98
|
} | {
|
|
86
99
|
valid: boolean;
|
|
87
100
|
changes: SchemaChange[];
|
|
@@ -115,7 +128,7 @@ declare function getJsonSchema(): {
|
|
|
115
128
|
type: string;
|
|
116
129
|
};
|
|
117
130
|
patternProperties: {
|
|
118
|
-
"^[a-zA-Z0-9_]
|
|
131
|
+
"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$": {
|
|
119
132
|
type: string;
|
|
120
133
|
propertyNames: {
|
|
121
134
|
pattern: string;
|
|
@@ -168,7 +181,7 @@ declare function getJsonSchema(): {
|
|
|
168
181
|
type: string;
|
|
169
182
|
};
|
|
170
183
|
patternProperties: {
|
|
171
|
-
"^[a-zA-Z0-9_]
|
|
184
|
+
"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$": {
|
|
172
185
|
type: string;
|
|
173
186
|
properties: {
|
|
174
187
|
type: {
|
package/dist/index.js
CHANGED
|
@@ -56,16 +56,16 @@ var basicJsonSchema = {
|
|
|
56
56
|
"tables": {
|
|
57
57
|
"type": "object",
|
|
58
58
|
"propertyNames": {
|
|
59
|
-
"pattern": "^[a-zA-Z0-9_]
|
|
59
|
+
"pattern": "^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$",
|
|
60
60
|
"minLength": 1,
|
|
61
61
|
"maxLength": 50,
|
|
62
62
|
"type": "string"
|
|
63
63
|
},
|
|
64
64
|
"patternProperties": {
|
|
65
|
-
"^[a-zA-Z0-9_]
|
|
65
|
+
"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$": {
|
|
66
66
|
"type": "object",
|
|
67
67
|
"propertyNames": {
|
|
68
|
-
"pattern": "^[a-zA-Z0-9_]
|
|
68
|
+
"pattern": "^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$",
|
|
69
69
|
"minLength": 1,
|
|
70
70
|
"maxLength": 50,
|
|
71
71
|
"type": "string"
|
|
@@ -105,13 +105,13 @@ var basicJsonSchema = {
|
|
|
105
105
|
"fields": {
|
|
106
106
|
"type": "object",
|
|
107
107
|
"propertyNames": {
|
|
108
|
-
"pattern": "^[a-zA-Z0-9_]
|
|
108
|
+
"pattern": "^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$",
|
|
109
109
|
"minLength": 1,
|
|
110
110
|
"maxLength": 50,
|
|
111
111
|
"type": "string"
|
|
112
112
|
},
|
|
113
113
|
"patternProperties": {
|
|
114
|
-
"^[a-zA-Z0-9_]
|
|
114
|
+
"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$": {
|
|
115
115
|
"type": "object",
|
|
116
116
|
"properties": {
|
|
117
117
|
"type": {
|
|
@@ -139,7 +139,7 @@ var basicJsonSchema = {
|
|
|
139
139
|
},
|
|
140
140
|
"required": ["project_id", "version", "tables"]
|
|
141
141
|
};
|
|
142
|
-
var ajv = new import_ajv.default();
|
|
142
|
+
var ajv = new import_ajv.default({ allErrors: true });
|
|
143
143
|
var validator = ajv.compile(basicJsonSchema);
|
|
144
144
|
function generateEmptySchema(project_id = "", version = 0) {
|
|
145
145
|
return {
|
|
@@ -166,11 +166,54 @@ function compareSchemas(oldSchema, newSchema) {
|
|
|
166
166
|
}
|
|
167
167
|
function validateSchema(schema) {
|
|
168
168
|
const v = validator(schema);
|
|
169
|
+
const ajvErrors = validator.errors || [];
|
|
170
|
+
const customErrors = validateCaseInsensitiveNames(schema);
|
|
169
171
|
return {
|
|
170
|
-
valid: v,
|
|
171
|
-
errors:
|
|
172
|
+
valid: v && customErrors.length === 0,
|
|
173
|
+
errors: [...ajvErrors, ...customErrors]
|
|
172
174
|
};
|
|
173
175
|
}
|
|
176
|
+
function validateCaseInsensitiveNames(schema) {
|
|
177
|
+
const errors = [];
|
|
178
|
+
if (!schema.tables)
|
|
179
|
+
return errors;
|
|
180
|
+
const tableNames = /* @__PURE__ */ new Set();
|
|
181
|
+
for (const tableName in schema.tables) {
|
|
182
|
+
const lowerTableName = tableName.toLowerCase();
|
|
183
|
+
if (tableNames.has(lowerTableName)) {
|
|
184
|
+
errors.push({
|
|
185
|
+
keyword: "caseInsensitiveDuplicate",
|
|
186
|
+
instancePath: `/tables/${tableName}`,
|
|
187
|
+
schemaPath: "#/properties/tables/propertyNames",
|
|
188
|
+
params: { propertyName: tableName },
|
|
189
|
+
message: `Table name "${tableName}" conflicts with another table name (case-insensitive)`
|
|
190
|
+
});
|
|
191
|
+
} else {
|
|
192
|
+
tableNames.add(lowerTableName);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const tableName in schema.tables) {
|
|
196
|
+
const table = schema.tables[tableName];
|
|
197
|
+
if (!table.fields)
|
|
198
|
+
continue;
|
|
199
|
+
const fieldNames = /* @__PURE__ */ new Set();
|
|
200
|
+
for (const fieldName in table.fields) {
|
|
201
|
+
const lowerFieldName = fieldName.toLowerCase();
|
|
202
|
+
if (fieldNames.has(lowerFieldName)) {
|
|
203
|
+
errors.push({
|
|
204
|
+
keyword: "caseInsensitiveDuplicate",
|
|
205
|
+
instancePath: `/tables/${tableName}/fields/${fieldName}`,
|
|
206
|
+
schemaPath: "#/properties/tables/patternProperties/^%5E%28%3F%21id%24%7CID%24%7CId%24%7CiD%24%29%5Ba-zA-Z0-9%5D%5Ba-zA-Z0-9_%5D*%24/properties/fields/propertyNames",
|
|
207
|
+
params: { propertyName: fieldName },
|
|
208
|
+
message: `Field name "${fieldName}" conflicts with another field name in table "${tableName}" (case-insensitive)`
|
|
209
|
+
});
|
|
210
|
+
} else {
|
|
211
|
+
fieldNames.add(lowerFieldName);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return errors;
|
|
216
|
+
}
|
|
174
217
|
function validateData(schema, table, data, checkRequired = true) {
|
|
175
218
|
const valid = validateSchema(schema);
|
|
176
219
|
if (!valid.valid) {
|
|
@@ -249,50 +292,52 @@ function _getSchemaChanges(oldSchema, newSchema) {
|
|
|
249
292
|
});
|
|
250
293
|
continue;
|
|
251
294
|
}
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
295
|
+
if (newTable.fields && oldTable.fields) {
|
|
296
|
+
for (const fieldName in newTable.fields) {
|
|
297
|
+
const newField = newTable.fields[fieldName];
|
|
298
|
+
const oldField = oldTable.fields[fieldName];
|
|
299
|
+
if (!oldField) {
|
|
300
|
+
changes.push({
|
|
301
|
+
type: "field_added",
|
|
302
|
+
table: tableName,
|
|
303
|
+
field: fieldName
|
|
304
|
+
});
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (newField.type !== oldField.type) {
|
|
308
|
+
changes.push({
|
|
309
|
+
type: "field_type_changed",
|
|
310
|
+
table: tableName,
|
|
311
|
+
field: fieldName,
|
|
312
|
+
old: oldField.type,
|
|
313
|
+
new: newField.type
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
if (newField.required !== oldField.required) {
|
|
317
|
+
changes.push({
|
|
318
|
+
type: "field_required_changed",
|
|
319
|
+
table: tableName,
|
|
320
|
+
field: fieldName,
|
|
321
|
+
old: oldField.required,
|
|
322
|
+
new: newField.required
|
|
323
|
+
});
|
|
324
|
+
}
|
|
280
325
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
326
|
+
for (const fieldName in oldTable.fields) {
|
|
327
|
+
if (!newTable.fields[fieldName]) {
|
|
328
|
+
changes.push({
|
|
329
|
+
type: "field_removed",
|
|
330
|
+
table: tableName,
|
|
331
|
+
field: fieldName
|
|
332
|
+
});
|
|
333
|
+
}
|
|
289
334
|
}
|
|
290
335
|
}
|
|
291
336
|
}
|
|
292
337
|
for (const tableName in newSchema.tables) {
|
|
293
338
|
const newTable = newSchema.tables[tableName];
|
|
294
339
|
const oldTable = oldSchema.tables[tableName];
|
|
295
|
-
if (!oldTable)
|
|
340
|
+
if (!oldTable || !newTable.fields || !oldTable.fields)
|
|
296
341
|
continue;
|
|
297
342
|
for (const fieldName in newTable.fields) {
|
|
298
343
|
const newField = newTable.fields[fieldName];
|
|
@@ -342,7 +387,22 @@ function validateUpdateSchema(oldSchema, newSchema) {
|
|
|
342
387
|
const oldValid = validateSchema(oldSchema);
|
|
343
388
|
const newValid = validateSchema(newSchema);
|
|
344
389
|
if (!oldValid.valid || !newValid.valid) {
|
|
345
|
-
return { valid: false, errors: oldValid.errors.concat(newValid.errors), message: "schemas are
|
|
390
|
+
return { valid: false, errors: oldValid.errors.concat(newValid.errors), message: "schemas are invalid" };
|
|
391
|
+
}
|
|
392
|
+
if (newSchema.version !== oldSchema.version + 1) {
|
|
393
|
+
return {
|
|
394
|
+
valid: false,
|
|
395
|
+
errors: [{
|
|
396
|
+
change: {
|
|
397
|
+
type: "property_changed",
|
|
398
|
+
property: "version",
|
|
399
|
+
old: oldSchema.version,
|
|
400
|
+
new: newSchema.version
|
|
401
|
+
},
|
|
402
|
+
message: `Version must be incremented by 1. Expected version:${oldSchema.version + 1}, got version:${newSchema.version}`
|
|
403
|
+
}],
|
|
404
|
+
message: "Version must be incremented by 1"
|
|
405
|
+
};
|
|
346
406
|
}
|
|
347
407
|
const changes = _getSchemaChanges(oldSchema, newSchema);
|
|
348
408
|
const changeErrors = [];
|
|
@@ -353,14 +413,6 @@ function validateUpdateSchema(oldSchema, newSchema) {
|
|
|
353
413
|
message: "Cannot modify project_id property"
|
|
354
414
|
});
|
|
355
415
|
}
|
|
356
|
-
if (change.type === "property_changed" && change.property === "version") {
|
|
357
|
-
if (change.new !== change.old + 1) {
|
|
358
|
-
changeErrors.push({
|
|
359
|
-
change,
|
|
360
|
-
message: `Version must be incremented by 1. Expected version:${change.old + 1}, got version:${change.new}`
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
416
|
if (change.type === "field_type_changed") {
|
|
365
417
|
changeErrors.push({
|
|
366
418
|
change,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.ts"],"sourcesContent":["// Basic Schema Library\n// utils for validating and interacting with Basic schemas\nimport Ajv 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 \"propertyNames\": {\n \"pattern\": \"^[a-zA-Z0-9_]+$\",\n \"minLength\": 1,\n \"maxLength\": 50, \n \"type\": \"string\"\n },\n \"patternProperties\": {\n \"^[a-zA-Z0-9_]+$\": {\n \"type\": \"object\",\n \"propertyNames\": {\n \"pattern\": \"^[a-zA-Z0-9_]+$\",\n \"minLength\": 1,\n \"maxLength\": 50, \n \"type\": \"string\"\n },\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\",\n \"enum\": [\"collection\"]\n },\n \"origin\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"enum\": [\"reference\"]\n },\n \"project_id\": {\n \"type\": \"string\"\n },\n \"table\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"integer\"\n }\n },\n \"if\": {\n \"properties\": { \"type\": { \"const\": \"reference\" } }\n },\n \"then\": {\n \"required\": [\"project_id\", \"table\"]\n }\n },\n \"fields\": {\n \"type\": \"object\",\n \"propertyNames\": {\n \"pattern\": \"^[a-zA-Z0-9_]+$\",\n \"minLength\": 1,\n \"maxLength\": 50, \n \"type\": \"string\"\n },\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 foo: { \n name: \"foo\",\n type: \"collection\",\n fields: {\n bar: {\n type: \"string\",\n required: true,\n }\n }\n }\n }\n }\n}\n\ntype Schema = {\n project_id: string,\n version: number,\n tables: any\n}\n\n\n/**\n * Compare two schemas and detect any differences between them\n * @param oldSchema - The original schema to compare against\n * @param newSchema - The new schema to compare with the original\n * @returns {Object} Comparison result containing:\n * - valid: boolean indicating if schemas are identical\n * - changes: Array of detected changes between schemas\n */\nfunction compareSchemas(oldSchema: any, newSchema: any) {\n const changes = _getSchemaChanges(oldSchema, newSchema)\n const valid = changes.length === 0 ? true : false\n return { valid, changes }\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: Schema): { valid: boolean, errors: ErrorObject[] } {\n const v = validator(schema)\n return {\n valid: v,\n errors: validator.errors || []\n }\n}\n\ntype 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 */\nfunction validateData(schema: Schema, table: string, data: Record<string, any>, checkRequired: boolean = true) : { valid: boolean, errors?: ErrorObject[], message?: string } {\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\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\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, errors: [] }\n}\n\n\n/**\n * Get the JSON schema definition for the Basic Schema\n * @returns {Object} The JSON schema\n */\nfunction getJsonSchema() {\n return basicJsonSchema\n}\n\nexport {\n validateSchema,\n validateData,\n generateEmptySchema,\n validateUpdateSchema,\n compareSchemas,\n getJsonSchema\n}"],"mappings":"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","names":["Ajv"]}
|
|
1
|
+
{"version":3,"sources":["../index.ts"],"sourcesContent":["// Basic Schema Library\n// utils for validating and interacting with Basic schemas\nimport Ajv 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 \"propertyNames\": {\n \"pattern\": \"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$\",\n \"minLength\": 1,\n \"maxLength\": 50, \n \"type\": \"string\"\n },\n \"patternProperties\": {\n \"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$\": {\n \"type\": \"object\",\n \"propertyNames\": {\n \"pattern\": \"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$\",\n \"minLength\": 1,\n \"maxLength\": 50, \n \"type\": \"string\"\n },\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\",\n \"enum\": [\"collection\"]\n },\n \"origin\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"enum\": [\"reference\"]\n },\n \"project_id\": {\n \"type\": \"string\"\n },\n \"table\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"integer\"\n }\n },\n \"if\": {\n \"properties\": { \"type\": { \"const\": \"reference\" } }\n },\n \"then\": {\n \"required\": [\"project_id\", \"table\"]\n }\n },\n \"fields\": {\n \"type\": \"object\",\n \"propertyNames\": {\n \"pattern\": \"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$\",\n \"minLength\": 1,\n \"maxLength\": 50, \n \"type\": \"string\"\n },\n \"patternProperties\": {\n \"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][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({ allErrors: true })\nconst validator = ajv.compile(basicJsonSchema)\n\nfunction generateEmptySchema(project_id: string = \"\", version: number = 0) {\n return {\n project_id: project_id,\n version: version,\n tables: {\n foo: { \n name: \"foo\",\n type: \"collection\",\n fields: {\n bar: {\n type: \"string\",\n required: true,\n }\n }\n }\n }\n }\n}\n\ntype Schema = {\n project_id: string,\n version: number,\n tables: any\n}\n\n\n/**\n * Compare two schemas and detect any differences between them\n * @param oldSchema - The original schema to compare against\n * @param newSchema - The new schema to compare with the original\n * @returns {Object} Comparison result containing:\n * - valid: boolean indicating if schemas are identical\n * - changes: Array of detected changes between schemas\n */\nfunction compareSchemas(oldSchema: any, newSchema: any) {\n const changes = _getSchemaChanges(oldSchema, newSchema)\n const valid = changes.length === 0 ? true : false\n return { valid, changes }\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: Schema): { valid: boolean, errors: ErrorObject[] } {\n const v = validator(schema)\n const ajvErrors = validator.errors || []\n \n // Add custom validation for case-insensitive duplicates\n const customErrors = validateCaseInsensitiveNames(schema)\n \n return {\n valid: v && customErrors.length === 0,\n errors: [...ajvErrors, ...customErrors]\n }\n}\n\n/**\n * Validate that table and field names are case-insensitive unique\n * @param schema - The schema to validate\n * @returns Array of validation errors\n */\nfunction validateCaseInsensitiveNames(schema: Schema): ErrorObject[] {\n const errors: ErrorObject[] = []\n \n if (!schema.tables) return errors\n \n // Check for case-insensitive duplicate table names\n const tableNames = new Set<string>()\n for (const tableName in schema.tables) {\n const lowerTableName = tableName.toLowerCase()\n if (tableNames.has(lowerTableName)) {\n errors.push({\n keyword: 'caseInsensitiveDuplicate',\n instancePath: `/tables/${tableName}`,\n schemaPath: '#/properties/tables/propertyNames',\n params: { propertyName: tableName },\n message: `Table name \"${tableName}\" conflicts with another table name (case-insensitive)`\n })\n } else {\n tableNames.add(lowerTableName)\n }\n }\n \n // Check for case-insensitive duplicate field names within each table\n for (const tableName in schema.tables) {\n const table = schema.tables[tableName]\n if (!table.fields) continue\n \n const fieldNames = new Set<string>()\n for (const fieldName in table.fields) {\n const lowerFieldName = fieldName.toLowerCase()\n if (fieldNames.has(lowerFieldName)) {\n errors.push({\n keyword: 'caseInsensitiveDuplicate',\n instancePath: `/tables/${tableName}/fields/${fieldName}`,\n schemaPath: '#/properties/tables/patternProperties/^%5E%28%3F%21id%24%7CID%24%7CId%24%7CiD%24%29%5Ba-zA-Z0-9%5D%5Ba-zA-Z0-9_%5D*%24/properties/fields/propertyNames',\n params: { propertyName: fieldName },\n message: `Field name \"${fieldName}\" conflicts with another field name in table \"${tableName}\" (case-insensitive)`\n })\n } else {\n fieldNames.add(lowerFieldName)\n }\n }\n }\n \n return errors\n}\n\ntype 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 */\nfunction validateData(schema: Schema, table: string, data: Record<string, any>, checkRequired: boolean = true) : { valid: boolean, errors?: ErrorObject[], message?: string } {\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\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 - only if both tables have fields\n if (newTable.fields && oldTable.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\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 || !newTable.fields || !oldTable.fields) 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\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 invalid\" }\n }\n\n // Always check that version is incremented by 1\n if (newSchema.version !== oldSchema.version + 1) {\n return {\n valid: false,\n errors: [{\n change: {\n type: 'property_changed',\n property: 'version',\n old: oldSchema.version,\n new: newSchema.version\n },\n message: `Version must be incremented by 1. Expected version:${oldSchema.version + 1}, got version:${newSchema.version}`\n }],\n message: \"Version must be incremented by 1\"\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 === '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, errors: [] }\n}\n\n\n/**\n * Get the JSON schema definition for the Basic Schema\n * @returns {Object} The JSON schema\n */\nfunction getJsonSchema() {\n return basicJsonSchema\n}\n\nexport {\n validateSchema,\n validateData,\n generateEmptySchema,\n validateUpdateSchema,\n compareSchemas,\n getJsonSchema\n}"],"mappings":"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","names":["Ajv"]}
|
package/dist/index.mjs
CHANGED
|
@@ -17,16 +17,16 @@ var basicJsonSchema = {
|
|
|
17
17
|
"tables": {
|
|
18
18
|
"type": "object",
|
|
19
19
|
"propertyNames": {
|
|
20
|
-
"pattern": "^[a-zA-Z0-9_]
|
|
20
|
+
"pattern": "^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$",
|
|
21
21
|
"minLength": 1,
|
|
22
22
|
"maxLength": 50,
|
|
23
23
|
"type": "string"
|
|
24
24
|
},
|
|
25
25
|
"patternProperties": {
|
|
26
|
-
"^[a-zA-Z0-9_]
|
|
26
|
+
"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$": {
|
|
27
27
|
"type": "object",
|
|
28
28
|
"propertyNames": {
|
|
29
|
-
"pattern": "^[a-zA-Z0-9_]
|
|
29
|
+
"pattern": "^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$",
|
|
30
30
|
"minLength": 1,
|
|
31
31
|
"maxLength": 50,
|
|
32
32
|
"type": "string"
|
|
@@ -66,13 +66,13 @@ var basicJsonSchema = {
|
|
|
66
66
|
"fields": {
|
|
67
67
|
"type": "object",
|
|
68
68
|
"propertyNames": {
|
|
69
|
-
"pattern": "^[a-zA-Z0-9_]
|
|
69
|
+
"pattern": "^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$",
|
|
70
70
|
"minLength": 1,
|
|
71
71
|
"maxLength": 50,
|
|
72
72
|
"type": "string"
|
|
73
73
|
},
|
|
74
74
|
"patternProperties": {
|
|
75
|
-
"^[a-zA-Z0-9_]
|
|
75
|
+
"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$": {
|
|
76
76
|
"type": "object",
|
|
77
77
|
"properties": {
|
|
78
78
|
"type": {
|
|
@@ -100,7 +100,7 @@ var basicJsonSchema = {
|
|
|
100
100
|
},
|
|
101
101
|
"required": ["project_id", "version", "tables"]
|
|
102
102
|
};
|
|
103
|
-
var ajv = new Ajv();
|
|
103
|
+
var ajv = new Ajv({ allErrors: true });
|
|
104
104
|
var validator = ajv.compile(basicJsonSchema);
|
|
105
105
|
function generateEmptySchema(project_id = "", version = 0) {
|
|
106
106
|
return {
|
|
@@ -127,11 +127,54 @@ function compareSchemas(oldSchema, newSchema) {
|
|
|
127
127
|
}
|
|
128
128
|
function validateSchema(schema) {
|
|
129
129
|
const v = validator(schema);
|
|
130
|
+
const ajvErrors = validator.errors || [];
|
|
131
|
+
const customErrors = validateCaseInsensitiveNames(schema);
|
|
130
132
|
return {
|
|
131
|
-
valid: v,
|
|
132
|
-
errors:
|
|
133
|
+
valid: v && customErrors.length === 0,
|
|
134
|
+
errors: [...ajvErrors, ...customErrors]
|
|
133
135
|
};
|
|
134
136
|
}
|
|
137
|
+
function validateCaseInsensitiveNames(schema) {
|
|
138
|
+
const errors = [];
|
|
139
|
+
if (!schema.tables)
|
|
140
|
+
return errors;
|
|
141
|
+
const tableNames = /* @__PURE__ */ new Set();
|
|
142
|
+
for (const tableName in schema.tables) {
|
|
143
|
+
const lowerTableName = tableName.toLowerCase();
|
|
144
|
+
if (tableNames.has(lowerTableName)) {
|
|
145
|
+
errors.push({
|
|
146
|
+
keyword: "caseInsensitiveDuplicate",
|
|
147
|
+
instancePath: `/tables/${tableName}`,
|
|
148
|
+
schemaPath: "#/properties/tables/propertyNames",
|
|
149
|
+
params: { propertyName: tableName },
|
|
150
|
+
message: `Table name "${tableName}" conflicts with another table name (case-insensitive)`
|
|
151
|
+
});
|
|
152
|
+
} else {
|
|
153
|
+
tableNames.add(lowerTableName);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const tableName in schema.tables) {
|
|
157
|
+
const table = schema.tables[tableName];
|
|
158
|
+
if (!table.fields)
|
|
159
|
+
continue;
|
|
160
|
+
const fieldNames = /* @__PURE__ */ new Set();
|
|
161
|
+
for (const fieldName in table.fields) {
|
|
162
|
+
const lowerFieldName = fieldName.toLowerCase();
|
|
163
|
+
if (fieldNames.has(lowerFieldName)) {
|
|
164
|
+
errors.push({
|
|
165
|
+
keyword: "caseInsensitiveDuplicate",
|
|
166
|
+
instancePath: `/tables/${tableName}/fields/${fieldName}`,
|
|
167
|
+
schemaPath: "#/properties/tables/patternProperties/^%5E%28%3F%21id%24%7CID%24%7CId%24%7CiD%24%29%5Ba-zA-Z0-9%5D%5Ba-zA-Z0-9_%5D*%24/properties/fields/propertyNames",
|
|
168
|
+
params: { propertyName: fieldName },
|
|
169
|
+
message: `Field name "${fieldName}" conflicts with another field name in table "${tableName}" (case-insensitive)`
|
|
170
|
+
});
|
|
171
|
+
} else {
|
|
172
|
+
fieldNames.add(lowerFieldName);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return errors;
|
|
177
|
+
}
|
|
135
178
|
function validateData(schema, table, data, checkRequired = true) {
|
|
136
179
|
const valid = validateSchema(schema);
|
|
137
180
|
if (!valid.valid) {
|
|
@@ -210,50 +253,52 @@ function _getSchemaChanges(oldSchema, newSchema) {
|
|
|
210
253
|
});
|
|
211
254
|
continue;
|
|
212
255
|
}
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
256
|
+
if (newTable.fields && oldTable.fields) {
|
|
257
|
+
for (const fieldName in newTable.fields) {
|
|
258
|
+
const newField = newTable.fields[fieldName];
|
|
259
|
+
const oldField = oldTable.fields[fieldName];
|
|
260
|
+
if (!oldField) {
|
|
261
|
+
changes.push({
|
|
262
|
+
type: "field_added",
|
|
263
|
+
table: tableName,
|
|
264
|
+
field: fieldName
|
|
265
|
+
});
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (newField.type !== oldField.type) {
|
|
269
|
+
changes.push({
|
|
270
|
+
type: "field_type_changed",
|
|
271
|
+
table: tableName,
|
|
272
|
+
field: fieldName,
|
|
273
|
+
old: oldField.type,
|
|
274
|
+
new: newField.type
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
if (newField.required !== oldField.required) {
|
|
278
|
+
changes.push({
|
|
279
|
+
type: "field_required_changed",
|
|
280
|
+
table: tableName,
|
|
281
|
+
field: fieldName,
|
|
282
|
+
old: oldField.required,
|
|
283
|
+
new: newField.required
|
|
284
|
+
});
|
|
285
|
+
}
|
|
241
286
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
287
|
+
for (const fieldName in oldTable.fields) {
|
|
288
|
+
if (!newTable.fields[fieldName]) {
|
|
289
|
+
changes.push({
|
|
290
|
+
type: "field_removed",
|
|
291
|
+
table: tableName,
|
|
292
|
+
field: fieldName
|
|
293
|
+
});
|
|
294
|
+
}
|
|
250
295
|
}
|
|
251
296
|
}
|
|
252
297
|
}
|
|
253
298
|
for (const tableName in newSchema.tables) {
|
|
254
299
|
const newTable = newSchema.tables[tableName];
|
|
255
300
|
const oldTable = oldSchema.tables[tableName];
|
|
256
|
-
if (!oldTable)
|
|
301
|
+
if (!oldTable || !newTable.fields || !oldTable.fields)
|
|
257
302
|
continue;
|
|
258
303
|
for (const fieldName in newTable.fields) {
|
|
259
304
|
const newField = newTable.fields[fieldName];
|
|
@@ -303,7 +348,22 @@ function validateUpdateSchema(oldSchema, newSchema) {
|
|
|
303
348
|
const oldValid = validateSchema(oldSchema);
|
|
304
349
|
const newValid = validateSchema(newSchema);
|
|
305
350
|
if (!oldValid.valid || !newValid.valid) {
|
|
306
|
-
return { valid: false, errors: oldValid.errors.concat(newValid.errors), message: "schemas are
|
|
351
|
+
return { valid: false, errors: oldValid.errors.concat(newValid.errors), message: "schemas are invalid" };
|
|
352
|
+
}
|
|
353
|
+
if (newSchema.version !== oldSchema.version + 1) {
|
|
354
|
+
return {
|
|
355
|
+
valid: false,
|
|
356
|
+
errors: [{
|
|
357
|
+
change: {
|
|
358
|
+
type: "property_changed",
|
|
359
|
+
property: "version",
|
|
360
|
+
old: oldSchema.version,
|
|
361
|
+
new: newSchema.version
|
|
362
|
+
},
|
|
363
|
+
message: `Version must be incremented by 1. Expected version:${oldSchema.version + 1}, got version:${newSchema.version}`
|
|
364
|
+
}],
|
|
365
|
+
message: "Version must be incremented by 1"
|
|
366
|
+
};
|
|
307
367
|
}
|
|
308
368
|
const changes = _getSchemaChanges(oldSchema, newSchema);
|
|
309
369
|
const changeErrors = [];
|
|
@@ -314,14 +374,6 @@ function validateUpdateSchema(oldSchema, newSchema) {
|
|
|
314
374
|
message: "Cannot modify project_id property"
|
|
315
375
|
});
|
|
316
376
|
}
|
|
317
|
-
if (change.type === "property_changed" && change.property === "version") {
|
|
318
|
-
if (change.new !== change.old + 1) {
|
|
319
|
-
changeErrors.push({
|
|
320
|
-
change,
|
|
321
|
-
message: `Version must be incremented by 1. Expected version:${change.old + 1}, got version:${change.new}`
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
377
|
if (change.type === "field_type_changed") {
|
|
326
378
|
changeErrors.push({
|
|
327
379
|
change,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.ts"],"sourcesContent":["// Basic Schema Library\n// utils for validating and interacting with Basic schemas\nimport Ajv 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 \"propertyNames\": {\n \"pattern\": \"^[a-zA-Z0-9_]+$\",\n \"minLength\": 1,\n \"maxLength\": 50, \n \"type\": \"string\"\n },\n \"patternProperties\": {\n \"^[a-zA-Z0-9_]+$\": {\n \"type\": \"object\",\n \"propertyNames\": {\n \"pattern\": \"^[a-zA-Z0-9_]+$\",\n \"minLength\": 1,\n \"maxLength\": 50, \n \"type\": \"string\"\n },\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\",\n \"enum\": [\"collection\"]\n },\n \"origin\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"enum\": [\"reference\"]\n },\n \"project_id\": {\n \"type\": \"string\"\n },\n \"table\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"integer\"\n }\n },\n \"if\": {\n \"properties\": { \"type\": { \"const\": \"reference\" } }\n },\n \"then\": {\n \"required\": [\"project_id\", \"table\"]\n }\n },\n \"fields\": {\n \"type\": \"object\",\n \"propertyNames\": {\n \"pattern\": \"^[a-zA-Z0-9_]+$\",\n \"minLength\": 1,\n \"maxLength\": 50, \n \"type\": \"string\"\n },\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 foo: { \n name: \"foo\",\n type: \"collection\",\n fields: {\n bar: {\n type: \"string\",\n required: true,\n }\n }\n }\n }\n }\n}\n\ntype Schema = {\n project_id: string,\n version: number,\n tables: any\n}\n\n\n/**\n * Compare two schemas and detect any differences between them\n * @param oldSchema - The original schema to compare against\n * @param newSchema - The new schema to compare with the original\n * @returns {Object} Comparison result containing:\n * - valid: boolean indicating if schemas are identical\n * - changes: Array of detected changes between schemas\n */\nfunction compareSchemas(oldSchema: any, newSchema: any) {\n const changes = _getSchemaChanges(oldSchema, newSchema)\n const valid = changes.length === 0 ? true : false\n return { valid, changes }\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: Schema): { valid: boolean, errors: ErrorObject[] } {\n const v = validator(schema)\n return {\n valid: v,\n errors: validator.errors || []\n }\n}\n\ntype 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 */\nfunction validateData(schema: Schema, table: string, data: Record<string, any>, checkRequired: boolean = true) : { valid: boolean, errors?: ErrorObject[], message?: string } {\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\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\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, errors: [] }\n}\n\n\n/**\n * Get the JSON schema definition for the Basic Schema\n * @returns {Object} The JSON schema\n */\nfunction getJsonSchema() {\n return basicJsonSchema\n}\n\nexport {\n validateSchema,\n validateData,\n generateEmptySchema,\n validateUpdateSchema,\n compareSchemas,\n getJsonSchema\n}"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../index.ts"],"sourcesContent":["// Basic Schema Library\n// utils for validating and interacting with Basic schemas\nimport Ajv 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 \"propertyNames\": {\n \"pattern\": \"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$\",\n \"minLength\": 1,\n \"maxLength\": 50, \n \"type\": \"string\"\n },\n \"patternProperties\": {\n \"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$\": {\n \"type\": \"object\",\n \"propertyNames\": {\n \"pattern\": \"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$\",\n \"minLength\": 1,\n \"maxLength\": 50, \n \"type\": \"string\"\n },\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\",\n \"enum\": [\"collection\"]\n },\n \"origin\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"enum\": [\"reference\"]\n },\n \"project_id\": {\n \"type\": \"string\"\n },\n \"table\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"integer\"\n }\n },\n \"if\": {\n \"properties\": { \"type\": { \"const\": \"reference\" } }\n },\n \"then\": {\n \"required\": [\"project_id\", \"table\"]\n }\n },\n \"fields\": {\n \"type\": \"object\",\n \"propertyNames\": {\n \"pattern\": \"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$\",\n \"minLength\": 1,\n \"maxLength\": 50, \n \"type\": \"string\"\n },\n \"patternProperties\": {\n \"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][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({ allErrors: true })\nconst validator = ajv.compile(basicJsonSchema)\n\nfunction generateEmptySchema(project_id: string = \"\", version: number = 0) {\n return {\n project_id: project_id,\n version: version,\n tables: {\n foo: { \n name: \"foo\",\n type: \"collection\",\n fields: {\n bar: {\n type: \"string\",\n required: true,\n }\n }\n }\n }\n }\n}\n\ntype Schema = {\n project_id: string,\n version: number,\n tables: any\n}\n\n\n/**\n * Compare two schemas and detect any differences between them\n * @param oldSchema - The original schema to compare against\n * @param newSchema - The new schema to compare with the original\n * @returns {Object} Comparison result containing:\n * - valid: boolean indicating if schemas are identical\n * - changes: Array of detected changes between schemas\n */\nfunction compareSchemas(oldSchema: any, newSchema: any) {\n const changes = _getSchemaChanges(oldSchema, newSchema)\n const valid = changes.length === 0 ? true : false\n return { valid, changes }\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: Schema): { valid: boolean, errors: ErrorObject[] } {\n const v = validator(schema)\n const ajvErrors = validator.errors || []\n \n // Add custom validation for case-insensitive duplicates\n const customErrors = validateCaseInsensitiveNames(schema)\n \n return {\n valid: v && customErrors.length === 0,\n errors: [...ajvErrors, ...customErrors]\n }\n}\n\n/**\n * Validate that table and field names are case-insensitive unique\n * @param schema - The schema to validate\n * @returns Array of validation errors\n */\nfunction validateCaseInsensitiveNames(schema: Schema): ErrorObject[] {\n const errors: ErrorObject[] = []\n \n if (!schema.tables) return errors\n \n // Check for case-insensitive duplicate table names\n const tableNames = new Set<string>()\n for (const tableName in schema.tables) {\n const lowerTableName = tableName.toLowerCase()\n if (tableNames.has(lowerTableName)) {\n errors.push({\n keyword: 'caseInsensitiveDuplicate',\n instancePath: `/tables/${tableName}`,\n schemaPath: '#/properties/tables/propertyNames',\n params: { propertyName: tableName },\n message: `Table name \"${tableName}\" conflicts with another table name (case-insensitive)`\n })\n } else {\n tableNames.add(lowerTableName)\n }\n }\n \n // Check for case-insensitive duplicate field names within each table\n for (const tableName in schema.tables) {\n const table = schema.tables[tableName]\n if (!table.fields) continue\n \n const fieldNames = new Set<string>()\n for (const fieldName in table.fields) {\n const lowerFieldName = fieldName.toLowerCase()\n if (fieldNames.has(lowerFieldName)) {\n errors.push({\n keyword: 'caseInsensitiveDuplicate',\n instancePath: `/tables/${tableName}/fields/${fieldName}`,\n schemaPath: '#/properties/tables/patternProperties/^%5E%28%3F%21id%24%7CID%24%7CId%24%7CiD%24%29%5Ba-zA-Z0-9%5D%5Ba-zA-Z0-9_%5D*%24/properties/fields/propertyNames',\n params: { propertyName: fieldName },\n message: `Field name \"${fieldName}\" conflicts with another field name in table \"${tableName}\" (case-insensitive)`\n })\n } else {\n fieldNames.add(lowerFieldName)\n }\n }\n }\n \n return errors\n}\n\ntype 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 */\nfunction validateData(schema: Schema, table: string, data: Record<string, any>, checkRequired: boolean = true) : { valid: boolean, errors?: ErrorObject[], message?: string } {\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\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 - only if both tables have fields\n if (newTable.fields && oldTable.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\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 || !newTable.fields || !oldTable.fields) 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\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 invalid\" }\n }\n\n // Always check that version is incremented by 1\n if (newSchema.version !== oldSchema.version + 1) {\n return {\n valid: false,\n errors: [{\n change: {\n type: 'property_changed',\n property: 'version',\n old: oldSchema.version,\n new: newSchema.version\n },\n message: `Version must be incremented by 1. Expected version:${oldSchema.version + 1}, got version:${newSchema.version}`\n }],\n message: \"Version must be incremented by 1\"\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 === '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, errors: [] }\n}\n\n\n/**\n * Get the JSON schema definition for the Basic Schema\n * @returns {Object} The JSON schema\n */\nfunction getJsonSchema() {\n return basicJsonSchema\n}\n\nexport {\n validateSchema,\n validateData,\n generateEmptySchema,\n validateUpdateSchema,\n compareSchemas,\n getJsonSchema\n}"],"mappings":"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","names":[]}
|
package/index.ts
CHANGED
|
@@ -19,16 +19,16 @@ const basicJsonSchema = {
|
|
|
19
19
|
"tables": {
|
|
20
20
|
"type": "object",
|
|
21
21
|
"propertyNames": {
|
|
22
|
-
"pattern": "^[a-zA-Z0-9_]
|
|
22
|
+
"pattern": "^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$",
|
|
23
23
|
"minLength": 1,
|
|
24
24
|
"maxLength": 50,
|
|
25
25
|
"type": "string"
|
|
26
26
|
},
|
|
27
27
|
"patternProperties": {
|
|
28
|
-
"^[a-zA-Z0-9_]
|
|
28
|
+
"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$": {
|
|
29
29
|
"type": "object",
|
|
30
30
|
"propertyNames": {
|
|
31
|
-
"pattern": "^[a-zA-Z0-9_]
|
|
31
|
+
"pattern": "^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$",
|
|
32
32
|
"minLength": 1,
|
|
33
33
|
"maxLength": 50,
|
|
34
34
|
"type": "string"
|
|
@@ -68,13 +68,13 @@ const basicJsonSchema = {
|
|
|
68
68
|
"fields": {
|
|
69
69
|
"type": "object",
|
|
70
70
|
"propertyNames": {
|
|
71
|
-
"pattern": "^[a-zA-Z0-9_]
|
|
71
|
+
"pattern": "^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$",
|
|
72
72
|
"minLength": 1,
|
|
73
73
|
"maxLength": 50,
|
|
74
74
|
"type": "string"
|
|
75
75
|
},
|
|
76
76
|
"patternProperties": {
|
|
77
|
-
"^[a-zA-Z0-9_]
|
|
77
|
+
"^(?!id$|ID$|Id$|iD$)[a-zA-Z0-9][a-zA-Z0-9_]*$": {
|
|
78
78
|
"type": "object",
|
|
79
79
|
"properties": {
|
|
80
80
|
"type": {
|
|
@@ -103,10 +103,9 @@ const basicJsonSchema = {
|
|
|
103
103
|
"required": ["project_id", "version", "tables"]
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
const ajv = new Ajv()
|
|
106
|
+
const ajv = new Ajv({ allErrors: true })
|
|
107
107
|
const validator = ajv.compile(basicJsonSchema)
|
|
108
108
|
|
|
109
|
-
|
|
110
109
|
function generateEmptySchema(project_id: string = "", version: number = 0) {
|
|
111
110
|
return {
|
|
112
111
|
project_id: project_id,
|
|
@@ -155,10 +154,67 @@ function compareSchemas(oldSchema: any, newSchema: any) {
|
|
|
155
154
|
*/
|
|
156
155
|
function validateSchema(schema: Schema): { valid: boolean, errors: ErrorObject[] } {
|
|
157
156
|
const v = validator(schema)
|
|
157
|
+
const ajvErrors = validator.errors || []
|
|
158
|
+
|
|
159
|
+
// Add custom validation for case-insensitive duplicates
|
|
160
|
+
const customErrors = validateCaseInsensitiveNames(schema)
|
|
161
|
+
|
|
158
162
|
return {
|
|
159
|
-
valid: v,
|
|
160
|
-
errors:
|
|
163
|
+
valid: v && customErrors.length === 0,
|
|
164
|
+
errors: [...ajvErrors, ...customErrors]
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Validate that table and field names are case-insensitive unique
|
|
170
|
+
* @param schema - The schema to validate
|
|
171
|
+
* @returns Array of validation errors
|
|
172
|
+
*/
|
|
173
|
+
function validateCaseInsensitiveNames(schema: Schema): ErrorObject[] {
|
|
174
|
+
const errors: ErrorObject[] = []
|
|
175
|
+
|
|
176
|
+
if (!schema.tables) return errors
|
|
177
|
+
|
|
178
|
+
// Check for case-insensitive duplicate table names
|
|
179
|
+
const tableNames = new Set<string>()
|
|
180
|
+
for (const tableName in schema.tables) {
|
|
181
|
+
const lowerTableName = tableName.toLowerCase()
|
|
182
|
+
if (tableNames.has(lowerTableName)) {
|
|
183
|
+
errors.push({
|
|
184
|
+
keyword: 'caseInsensitiveDuplicate',
|
|
185
|
+
instancePath: `/tables/${tableName}`,
|
|
186
|
+
schemaPath: '#/properties/tables/propertyNames',
|
|
187
|
+
params: { propertyName: tableName },
|
|
188
|
+
message: `Table name "${tableName}" conflicts with another table name (case-insensitive)`
|
|
189
|
+
})
|
|
190
|
+
} else {
|
|
191
|
+
tableNames.add(lowerTableName)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check for case-insensitive duplicate field names within each table
|
|
196
|
+
for (const tableName in schema.tables) {
|
|
197
|
+
const table = schema.tables[tableName]
|
|
198
|
+
if (!table.fields) continue
|
|
199
|
+
|
|
200
|
+
const fieldNames = new Set<string>()
|
|
201
|
+
for (const fieldName in table.fields) {
|
|
202
|
+
const lowerFieldName = fieldName.toLowerCase()
|
|
203
|
+
if (fieldNames.has(lowerFieldName)) {
|
|
204
|
+
errors.push({
|
|
205
|
+
keyword: 'caseInsensitiveDuplicate',
|
|
206
|
+
instancePath: `/tables/${tableName}/fields/${fieldName}`,
|
|
207
|
+
schemaPath: '#/properties/tables/patternProperties/^%5E%28%3F%21id%24%7CID%24%7CId%24%7CiD%24%29%5Ba-zA-Z0-9%5D%5Ba-zA-Z0-9_%5D*%24/properties/fields/propertyNames',
|
|
208
|
+
params: { propertyName: fieldName },
|
|
209
|
+
message: `Field name "${fieldName}" conflicts with another field name in table "${tableName}" (case-insensitive)`
|
|
210
|
+
})
|
|
211
|
+
} else {
|
|
212
|
+
fieldNames.add(lowerFieldName)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
161
215
|
}
|
|
216
|
+
|
|
217
|
+
return errors
|
|
162
218
|
}
|
|
163
219
|
|
|
164
220
|
type ErrorObject = {
|
|
@@ -298,51 +354,53 @@ function _getSchemaChanges(oldSchema: any, newSchema: any): SchemaChange[] {
|
|
|
298
354
|
continue
|
|
299
355
|
}
|
|
300
356
|
|
|
301
|
-
// Compare fields
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
|
|
357
|
+
// Compare fields - only if both tables have fields
|
|
358
|
+
if (newTable.fields && oldTable.fields) {
|
|
359
|
+
for (const fieldName in newTable.fields) {
|
|
360
|
+
const newField = newTable.fields[fieldName]
|
|
361
|
+
const oldField = oldTable.fields[fieldName]
|
|
305
362
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
363
|
+
if (!oldField) {
|
|
364
|
+
changes.push({
|
|
365
|
+
type: 'field_added',
|
|
366
|
+
table: tableName,
|
|
367
|
+
field: fieldName
|
|
368
|
+
})
|
|
369
|
+
continue
|
|
370
|
+
}
|
|
314
371
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
372
|
+
// Check for field type changes
|
|
373
|
+
if (newField.type !== oldField.type) {
|
|
374
|
+
changes.push({
|
|
375
|
+
type: 'field_type_changed',
|
|
376
|
+
table: tableName,
|
|
377
|
+
field: fieldName,
|
|
378
|
+
old: oldField.type,
|
|
379
|
+
new: newField.type
|
|
380
|
+
})
|
|
381
|
+
}
|
|
325
382
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
383
|
+
// Check for required flag changes
|
|
384
|
+
if (newField.required !== oldField.required) {
|
|
385
|
+
changes.push({
|
|
386
|
+
type: 'field_required_changed',
|
|
387
|
+
table: tableName,
|
|
388
|
+
field: fieldName,
|
|
389
|
+
old: oldField.required,
|
|
390
|
+
new: newField.required
|
|
391
|
+
})
|
|
392
|
+
}
|
|
335
393
|
}
|
|
336
|
-
}
|
|
337
394
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
395
|
+
// Check for removed fields
|
|
396
|
+
for (const fieldName in oldTable.fields) {
|
|
397
|
+
if (!newTable.fields[fieldName]) {
|
|
398
|
+
changes.push({
|
|
399
|
+
type: 'field_removed',
|
|
400
|
+
table: tableName,
|
|
401
|
+
field: fieldName
|
|
402
|
+
})
|
|
403
|
+
}
|
|
346
404
|
}
|
|
347
405
|
}
|
|
348
406
|
}
|
|
@@ -352,7 +410,7 @@ function _getSchemaChanges(oldSchema: any, newSchema: any): SchemaChange[] {
|
|
|
352
410
|
const newTable = newSchema.tables[tableName]
|
|
353
411
|
const oldTable = oldSchema.tables[tableName]
|
|
354
412
|
|
|
355
|
-
if (!oldTable) continue
|
|
413
|
+
if (!oldTable || !newTable.fields || !oldTable.fields) continue
|
|
356
414
|
|
|
357
415
|
for (const fieldName in newTable.fields) {
|
|
358
416
|
const newField = newTable.fields[fieldName]
|
|
@@ -409,9 +467,25 @@ function validateUpdateSchema(oldSchema: any, newSchema: any) {
|
|
|
409
467
|
const newValid = validateSchema(newSchema)
|
|
410
468
|
|
|
411
469
|
if (!oldValid.valid || !newValid.valid) {
|
|
412
|
-
return { valid: false, errors: oldValid.errors.concat(newValid.errors), message: "schemas are
|
|
470
|
+
return { valid: false, errors: oldValid.errors.concat(newValid.errors), message: "schemas are invalid" }
|
|
413
471
|
}
|
|
414
472
|
|
|
473
|
+
// Always check that version is incremented by 1
|
|
474
|
+
if (newSchema.version !== oldSchema.version + 1) {
|
|
475
|
+
return {
|
|
476
|
+
valid: false,
|
|
477
|
+
errors: [{
|
|
478
|
+
change: {
|
|
479
|
+
type: 'property_changed',
|
|
480
|
+
property: 'version',
|
|
481
|
+
old: oldSchema.version,
|
|
482
|
+
new: newSchema.version
|
|
483
|
+
},
|
|
484
|
+
message: `Version must be incremented by 1. Expected version:${oldSchema.version + 1}, got version:${newSchema.version}`
|
|
485
|
+
}],
|
|
486
|
+
message: "Version must be incremented by 1"
|
|
487
|
+
}
|
|
488
|
+
}
|
|
415
489
|
|
|
416
490
|
const changes = _getSchemaChanges(oldSchema, newSchema)
|
|
417
491
|
|
|
@@ -424,15 +498,6 @@ function validateUpdateSchema(oldSchema: any, newSchema: any) {
|
|
|
424
498
|
})
|
|
425
499
|
}
|
|
426
500
|
|
|
427
|
-
if (change.type === 'property_changed' && change.property === 'version') {
|
|
428
|
-
if (change.new !== change.old + 1) {
|
|
429
|
-
changeErrors.push({
|
|
430
|
-
change: change,
|
|
431
|
-
message: `Version must be incremented by 1. Expected version:${change.old + 1}, got version:${change.new}`
|
|
432
|
-
})
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
501
|
if (change.type === 'field_type_changed') {
|
|
437
502
|
changeErrors.push({
|
|
438
503
|
change: change,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basictech/schema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "utils for Basic Schema",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
"author": "",
|
|
18
18
|
"license": "ISC",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"ajv": "^8.17.1"
|
|
20
|
+
"ajv": "^8.17.1",
|
|
21
|
+
"ajv-errors": "^3.0.0"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@repo/typescript-config": "*",
|
|
24
25
|
"tsup": "^7.2.0",
|
|
25
26
|
"typescript": "^5.0.0"
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
}
|
|
27
|
+
}
|
|
28
|
+
}
|