@basictech/schema 0.5.0 → 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.
@@ -1,6 +1,6 @@
1
1
 
2
2
  
3
- > @basictech/schema@0.1.0-beta.1 build
3
+ > @basictech/schema@0.5.0 build
4
4
  > tsup
5
5
 
6
6
  CLI Building entry: ./index.ts
@@ -11,13 +11,13 @@
11
11
  CLI Cleaning output folder
12
12
  CJS Build start
13
13
  ESM Build start
14
- ESM dist/index.mjs 8.36 KB
15
- ESM dist/index.mjs.map 19.00 KB
16
- ESM ⚡️ Build success in 8ms
17
- CJS dist/index.js 10.11 KB
18
- CJS dist/index.js.map 19.08 KB
19
- CJS ⚡️ Build success in 8ms
14
+ CJS dist/index.js 13.75 KB
15
+ CJS dist/index.js.map 26.36 KB
16
+ CJS ⚡️ Build success in 9ms
17
+ ESM dist/index.mjs 11.93 KB
18
+ ESM dist/index.mjs.map 26.28 KB
19
+ ESM ⚡️ Build success in 9ms
20
20
  DTS Build start
21
- DTS ⚡️ Build success in 751ms
22
- DTS dist/index.d.ts 2.67 KB
23
- DTS dist/index.d.mts 2.67 KB
21
+ DTS ⚡️ Build success in 1166ms
22
+ DTS dist/index.d.ts 7.04 KB
23
+ DTS dist/index.d.mts 7.04 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @basictech/schema
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - update schema rules
8
+
3
9
  ## 0.5.0
4
10
 
5
11
  ### 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: validator.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
- for (const fieldName in newTable.fields) {
253
- const newField = newTable.fields[fieldName];
254
- const oldField = oldTable.fields[fieldName];
255
- if (!oldField) {
256
- changes.push({
257
- type: "field_added",
258
- table: tableName,
259
- field: fieldName
260
- });
261
- continue;
262
- }
263
- if (newField.type !== oldField.type) {
264
- changes.push({
265
- type: "field_type_changed",
266
- table: tableName,
267
- field: fieldName,
268
- old: oldField.type,
269
- new: newField.type
270
- });
271
- }
272
- if (newField.required !== oldField.required) {
273
- changes.push({
274
- type: "field_required_changed",
275
- table: tableName,
276
- field: fieldName,
277
- old: oldField.required,
278
- new: newField.required
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
- for (const fieldName in oldTable.fields) {
283
- if (!newTable.fields[fieldName]) {
284
- changes.push({
285
- type: "field_removed",
286
- table: tableName,
287
- field: fieldName
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 is invalid" };
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: validator.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
- for (const fieldName in newTable.fields) {
214
- const newField = newTable.fields[fieldName];
215
- const oldField = oldTable.fields[fieldName];
216
- if (!oldField) {
217
- changes.push({
218
- type: "field_added",
219
- table: tableName,
220
- field: fieldName
221
- });
222
- continue;
223
- }
224
- if (newField.type !== oldField.type) {
225
- changes.push({
226
- type: "field_type_changed",
227
- table: tableName,
228
- field: fieldName,
229
- old: oldField.type,
230
- new: newField.type
231
- });
232
- }
233
- if (newField.required !== oldField.required) {
234
- changes.push({
235
- type: "field_required_changed",
236
- table: tableName,
237
- field: fieldName,
238
- old: oldField.required,
239
- new: newField.required
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
- for (const fieldName in oldTable.fields) {
244
- if (!newTable.fields[fieldName]) {
245
- changes.push({
246
- type: "field_removed",
247
- table: tableName,
248
- field: fieldName
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 is invalid" };
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,
@@ -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: validator.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
- for (const fieldName in newTable.fields) {
303
- const newField = newTable.fields[fieldName]
304
- const oldField = oldTable.fields[fieldName]
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
- if (!oldField) {
307
- changes.push({
308
- type: 'field_added',
309
- table: tableName,
310
- field: fieldName
311
- })
312
- continue
313
- }
363
+ if (!oldField) {
364
+ changes.push({
365
+ type: 'field_added',
366
+ table: tableName,
367
+ field: fieldName
368
+ })
369
+ continue
370
+ }
314
371
 
315
- // Check for field type changes
316
- if (newField.type !== oldField.type) {
317
- changes.push({
318
- type: 'field_type_changed',
319
- table: tableName,
320
- field: fieldName,
321
- old: oldField.type,
322
- new: newField.type
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
- // Check for required flag changes
327
- if (newField.required !== oldField.required) {
328
- changes.push({
329
- type: 'field_required_changed',
330
- table: tableName,
331
- field: fieldName,
332
- old: oldField.required,
333
- new: newField.required
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
- // Check for removed fields
339
- for (const fieldName in oldTable.fields) {
340
- if (!newTable.fields[fieldName]) {
341
- changes.push({
342
- type: 'field_removed',
343
- table: tableName,
344
- field: fieldName
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 is invalid" }
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.5.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
- "peerDependencies": {}
28
- }
27
+ }
28
+ }