@contember/schema-utils
Advanced tools
Comparing version 1.3.0-rc.1 to 1.3.0-rc.2
@@ -31,3 +31,3 @@ "use strict"; | ||
path: Typesafe.array(Typesafe.string), | ||
direction: Typesafe.enumeration(schema_1.Model.OrderDirection.asc, schema_1.Model.OrderDirection.desc), | ||
direction: Typesafe.enumeration(schema_1.Model.OrderDirection.asc, schema_1.Model.OrderDirection.desc, schema_1.Model.OrderDirection.ascNullsFirst, schema_1.Model.OrderDirection.descNullsLast), | ||
})); | ||
@@ -34,0 +34,0 @@ const joiningColumnSchema = Typesafe.object({ |
@@ -5,7 +5,18 @@ import * as Typesafe from '@contember/typesafe'; | ||
readonly useExistsInHasManyFilter?: boolean | undefined; | ||
readonly tenant?: { | ||
readonly inviteExpirationMinutes?: number | undefined; | ||
} | undefined; | ||
}; | ||
inner: { | ||
useExistsInHasManyFilter: Typesafe.Type<boolean>; | ||
tenant: { | ||
(input: unknown, path?: PropertyKey[] | undefined): { | ||
readonly inviteExpirationMinutes?: number | undefined; | ||
}; | ||
inner: { | ||
inviteExpirationMinutes: Typesafe.Type<number>; | ||
}; | ||
}; | ||
}; | ||
}; | ||
//# sourceMappingURL=settings.d.ts.map |
@@ -30,4 +30,7 @@ "use strict"; | ||
useExistsInHasManyFilter: Typesafe.boolean, | ||
tenant: Typesafe.partial({ | ||
inviteExpirationMinutes: Typesafe.integer, | ||
}), | ||
}); | ||
const settingSchemaCheck = true; | ||
//# sourceMappingURL=settings.js.map |
@@ -1,2 +0,2 @@ | ||
export type ValidationErrorCode = 'MODEL_NAME_MISMATCH' | 'MODEL_UNDEFINED_FIELD' | 'MODEL_UNDEFINED_ENTITY' | 'MODEL_RELATION_REQUIRED' | 'MODEL_INVALID_RELATION_DEFINITION' | 'MODEL_INVALID_COLUMN_DEFINITION' | 'MODEL_INVALID_VIEW_USAGE' | 'MODEL_INVALID_IDENTIFIER' | 'MODEL_NAME_COLLISION' | 'ACL_INVALID_CONDITION' | 'ACL_UNDEFINED_VARIABLE' | 'ACL_UNDEFINED_FIELD' | 'ACL_UNDEFINED_PREDICATE' | 'ACL_UNDEFINED_ROLE' | 'ACL_UNDEFINED_ENTITY' | 'ACTIONS_NAME_MISMATCH' | 'ACTIONS_INVALID_CONDITION' | 'ACTIONS_UNDEFINED_FIELD' | 'ACTIONS_UNDEFINED_ENTITY' | 'ACTIONS_UNDEFINED_TRIGGER_TARGET' | 'ACTIONS_INVALID_SELECTION' | 'VALIDATION_UNDEFINED_ENTITY' | 'VALIDATION_UNDEFINED_FIELD' | 'VALIDATION_NOT_IMPLEMENTED'; | ||
export type ValidationErrorCode = 'MODEL_NAME_MISMATCH' | 'MODEL_UNDEFINED_FIELD' | 'MODEL_UNDEFINED_ENTITY' | 'MODEL_RELATION_REQUIRED' | 'MODEL_INVALID_RELATION_DEFINITION' | 'MODEL_INVALID_COLUMN_DEFINITION' | 'MODEL_INVALID_VIEW_USAGE' | 'MODEL_INVALID_IDENTIFIER' | 'MODEL_INVALID_ENTITY_NAME' | 'MODEL_NAME_COLLISION' | 'ACL_INVALID_CONDITION' | 'ACL_UNDEFINED_VARIABLE' | 'ACL_UNDEFINED_FIELD' | 'ACL_UNDEFINED_PREDICATE' | 'ACL_UNDEFINED_ROLE' | 'ACL_UNDEFINED_ENTITY' | 'ACTIONS_NAME_MISMATCH' | 'ACTIONS_INVALID_CONDITION' | 'ACTIONS_UNDEFINED_FIELD' | 'ACTIONS_UNDEFINED_ENTITY' | 'ACTIONS_UNDEFINED_TRIGGER_TARGET' | 'ACTIONS_INVALID_SELECTION' | 'VALIDATION_UNDEFINED_ENTITY' | 'VALIDATION_UNDEFINED_FIELD' | 'VALIDATION_NOT_IMPLEMENTED'; | ||
export interface ValidationError { | ||
@@ -3,0 +3,0 @@ path: (string | number)[]; |
@@ -11,7 +11,11 @@ import { Model } from '@contember/schema'; | ||
private validateUniqueConstraints; | ||
private validateColumnNamesCollision; | ||
private validateField; | ||
private validateRelation; | ||
private validateIdentifier; | ||
private validateCollisions; | ||
private validateEntityName; | ||
private validateMetaSuffixCollisions; | ||
private validateTableNameCollisions; | ||
private validateAliasedTypesCollision; | ||
} | ||
//# sourceMappingURL=ModelValidator.d.ts.map |
@@ -17,3 +17,2 @@ "use strict"; | ||
this.validateEntities(this.model.entities, errorBuilder.for('entities')); | ||
this.validateCollisions(Object.values(this.model.entities), errorBuilder.for('entities')); | ||
return errorBuilder.errors; | ||
@@ -37,5 +36,10 @@ } | ||
} | ||
const entitiesArr = Object.values(this.model.entities); | ||
this.validateMetaSuffixCollisions(entitiesArr, errors); | ||
this.validateTableNameCollisions(entitiesArr, errors); | ||
this.validateAliasedTypesCollision(entitiesArr, errors); | ||
} | ||
validateEntity(entity, errors) { | ||
this.validateIdentifier(entity.name, errors); | ||
this.validateEntityName(entity.name, errors); | ||
for (const [fieldName, field] of Object.entries(entity.fields)) { | ||
@@ -49,2 +53,3 @@ const fieldErrors = errors.for(fieldName); | ||
this.validateUniqueConstraints(entity.unique, new Set(Object.keys(entity.fields)), errors.for('unique')); | ||
this.validateColumnNamesCollision(entity, errors); | ||
} | ||
@@ -60,2 +65,29 @@ validateUniqueConstraints(uniqueConstraints, fields, errors) { | ||
} | ||
validateColumnNamesCollision(entity, errors) { | ||
const existingColumnNames = new Map(); | ||
const addColumnName = (fieldName, columnName) => { | ||
if (existingColumnNames.has(columnName)) { | ||
const exitingName = existingColumnNames.get(columnName); | ||
errors | ||
.for(fieldName) | ||
.add('MODEL_NAME_COLLISION', `Column name "${columnName}" on field "${fieldName}" collides with a column name on field "${exitingName}".`); | ||
} | ||
existingColumnNames.set(columnName, fieldName); | ||
}; | ||
(0, model_1.acceptEveryFieldVisitor)(this.model, entity, { | ||
visitColumn: ({ column }) => { | ||
addColumnName(column.name, column.columnName); | ||
}, | ||
visitOneHasOneOwning: ({ relation }) => { | ||
addColumnName(relation.name, relation.joiningColumn.columnName); | ||
}, | ||
visitManyHasOne: ({ relation }) => { | ||
addColumnName(relation.name, relation.joiningColumn.columnName); | ||
}, | ||
visitOneHasOneInverse: () => { }, | ||
visitOneHasMany: () => { }, | ||
visitManyHasManyInverse: () => { }, | ||
visitManyHasManyOwning: () => { }, | ||
}); | ||
} | ||
validateField(partialEntity, field, errors) { | ||
@@ -196,7 +228,22 @@ this.validateIdentifier(field.name, errors); | ||
} | ||
validateCollisions(entities, errorBuilder) { | ||
const relationNames = {}; | ||
const aliasedTypes = new Map(); | ||
validateEntityName(value, errorBuilder) { | ||
if (['Query', 'Mutation'].includes(value)) { | ||
errorBuilder.add('MODEL_INVALID_ENTITY_NAME', `${value} is reserved word`); | ||
} | ||
} | ||
validateMetaSuffixCollisions(entities, errorBuilder) { | ||
const entityNames = new Set(entities.map(it => it.name)); | ||
for (const entity of entities) { | ||
if (entity.name.endsWith('Meta')) { | ||
const baseName = entity.name.substring(0, entity.name.length - 4); | ||
if (entityNames.has(baseName)) { | ||
errorBuilder.for(entity.name) | ||
.add('MODEL_NAME_COLLISION', `entity ${entity.name} collides with entity ${baseName}, because a GraphQL type with "Meta" suffix is created for every entity`); | ||
} | ||
} | ||
} | ||
} | ||
validateTableNameCollisions(entities, errorBuilder) { | ||
const relationNames = {}; | ||
for (const entity of entities) { | ||
const description = `table name ${entity.tableName} of entity ${entity.name}`; | ||
@@ -211,9 +258,2 @@ if (relationNames[entity.tableName]) { | ||
} | ||
if (entity.name.endsWith('Meta')) { | ||
const baseName = entity.name.substring(0, entity.name.length - 4); | ||
if (entityNames.has(baseName)) { | ||
errorBuilder.for(entity.name) | ||
.add('MODEL_NAME_COLLISION', `entity ${entity.name} collides with entity ${baseName}, because a GraphQL type with "Meta" suffix is created for every entity`); | ||
} | ||
} | ||
} | ||
@@ -236,3 +276,16 @@ for (const entity of entities) { | ||
}, | ||
visitColumn: ({ entity, column }) => { | ||
visitColumn: () => { }, | ||
visitManyHasManyInverse: () => { }, | ||
visitOneHasMany: () => { }, | ||
visitOneHasOneInverse: () => { }, | ||
visitOneHasOneOwning: () => { }, | ||
visitManyHasOne: () => { }, | ||
}); | ||
} | ||
} | ||
validateAliasedTypesCollision(entities, errorBuilder) { | ||
const aliasedTypes = new Map(); | ||
for (const entity of entities) { | ||
(0, model_1.acceptEveryFieldVisitor)(this.model, entity, { | ||
visitColumn: ({ column }) => { | ||
if (!column.typeAlias) { | ||
@@ -248,2 +301,3 @@ return; | ||
}, | ||
visitManyHasManyOwning: () => { }, | ||
visitManyHasManyInverse: () => { }, | ||
@@ -250,0 +304,0 @@ visitOneHasMany: () => { }, |
@@ -6,2 +6,3 @@ "use strict"; | ||
const src_1 = require("../../../src"); | ||
const schema_definition_1 = require("@contember/schema-definition"); | ||
(0, vitest_1.test)('"meta" collision', () => { | ||
@@ -76,2 +77,23 @@ const model = { | ||
}); | ||
var ColumnNameCollision; | ||
(function (ColumnNameCollision) { | ||
class Bar { | ||
constructor() { | ||
this.rel = schema_definition_1.c.oneHasOne(Bar); | ||
this.relId = schema_definition_1.c.intColumn(); | ||
} | ||
} | ||
ColumnNameCollision.Bar = Bar; | ||
})(ColumnNameCollision || (ColumnNameCollision = {})); | ||
(0, vitest_1.test)('column name collision', () => { | ||
const schema = (0, schema_definition_1.createSchema)(ColumnNameCollision); | ||
const validator = new src_1.ModelValidator(schema.model); | ||
vitest_1.assert.deepStrictEqual(validator.validate(), [ | ||
{ | ||
code: 'MODEL_NAME_COLLISION', | ||
message: 'Column name "rel_id" on field "relId" collides with a column name on field "rel".', | ||
path: ['entities', 'Bar', 'relId'], | ||
}, | ||
]); | ||
}); | ||
//# sourceMappingURL=modelValidator.test.js.map |
{ | ||
"name": "@contember/schema-utils", | ||
"version": "1.3.0-rc.1", | ||
"version": "1.3.0-rc.2", | ||
"license": "Apache-2.0", | ||
@@ -15,9 +15,9 @@ "main": "dist/src/index.js", | ||
"dependencies": { | ||
"@contember/schema": "1.3.0-rc.1", | ||
"@contember/typesafe": "1.3.0-rc.1" | ||
"@contember/schema": "1.3.0-rc.2", | ||
"@contember/typesafe": "1.3.0-rc.2" | ||
}, | ||
"devDependencies": { | ||
"@contember/schema-definition": "1.3.0-rc.1", | ||
"@contember/schema-definition": "1.3.0-rc.2", | ||
"@types/node": "^18" | ||
} | ||
} |
@@ -6,3 +6,8 @@ import * as Typesafe from '@contember/typesafe' | ||
path: Typesafe.array(Typesafe.string), | ||
direction: Typesafe.enumeration<Model.OrderDirection>(Model.OrderDirection.asc, Model.OrderDirection.desc), | ||
direction: Typesafe.enumeration<Model.OrderDirection>( | ||
Model.OrderDirection.asc, | ||
Model.OrderDirection.desc, | ||
Model.OrderDirection.ascNullsFirst, | ||
Model.OrderDirection.descNullsLast, | ||
), | ||
})) | ||
@@ -9,0 +14,0 @@ const joiningColumnSchema = Typesafe.object({ |
@@ -6,4 +6,7 @@ import * as Typesafe from '@contember/typesafe' | ||
useExistsInHasManyFilter: Typesafe.boolean, | ||
tenant: Typesafe.partial({ | ||
inviteExpirationMinutes: Typesafe.integer, | ||
}), | ||
}) | ||
const settingSchemaCheck: Typesafe.Equals<Settings.Schema, ReturnType<typeof settingsSchema>> = true |
@@ -10,2 +10,3 @@ export type ValidationErrorCode = | ||
| 'MODEL_INVALID_IDENTIFIER' | ||
| 'MODEL_INVALID_ENTITY_NAME' | ||
| 'MODEL_NAME_COLLISION' | ||
@@ -12,0 +13,0 @@ |
@@ -16,3 +16,3 @@ import { Model } from '@contember/schema' | ||
this.validateEntities(this.model.entities, errorBuilder.for('entities')) | ||
this.validateCollisions(Object.values(this.model.entities), errorBuilder.for('entities')) | ||
return errorBuilder.errors | ||
@@ -38,2 +38,6 @@ } | ||
} | ||
const entitiesArr = Object.values(this.model.entities) | ||
this.validateMetaSuffixCollisions(entitiesArr, errors) | ||
this.validateTableNameCollisions(entitiesArr, errors) | ||
this.validateAliasedTypesCollision(entitiesArr, errors) | ||
} | ||
@@ -43,2 +47,3 @@ | ||
this.validateIdentifier(entity.name, errors) | ||
this.validateEntityName(entity.name, errors) | ||
@@ -57,2 +62,3 @@ for (const [fieldName, field] of Object.entries(entity.fields)) { | ||
) | ||
this.validateColumnNamesCollision(entity, errors) | ||
} | ||
@@ -70,2 +76,30 @@ | ||
private validateColumnNamesCollision(entity: Model.Entity, errors: ErrorBuilder): void { | ||
const existingColumnNames = new Map<string, string>() | ||
const addColumnName = (fieldName: string, columnName: string) => { | ||
if (existingColumnNames.has(columnName)) { | ||
const exitingName = existingColumnNames.get(columnName) | ||
errors | ||
.for(fieldName) | ||
.add('MODEL_NAME_COLLISION', `Column name "${columnName}" on field "${fieldName}" collides with a column name on field "${exitingName}".`) | ||
} | ||
existingColumnNames.set(columnName, fieldName) | ||
} | ||
acceptEveryFieldVisitor(this.model, entity, { | ||
visitColumn: ({ column }) => { | ||
addColumnName(column.name, column.columnName) | ||
}, | ||
visitOneHasOneOwning: ({ relation }) => { | ||
addColumnName(relation.name, relation.joiningColumn.columnName) | ||
}, | ||
visitManyHasOne: ({ relation }) => { | ||
addColumnName(relation.name, relation.joiningColumn.columnName) | ||
}, | ||
visitOneHasOneInverse: () => {}, | ||
visitOneHasMany: () => {}, | ||
visitManyHasManyInverse: () => {}, | ||
visitManyHasManyOwning: () => {}, | ||
}) | ||
} | ||
private validateField(partialEntity: Model.Entity, field: Model.AnyField, errors: ErrorBuilder): void { | ||
@@ -212,7 +246,26 @@ this.validateIdentifier(field.name, errors) | ||
private validateCollisions(entities: Model.Entity[], errorBuilder: ErrorBuilder) { | ||
const relationNames: Record<string, string> = {} | ||
const aliasedTypes = new Map<string, Model.ColumnType>() | ||
private validateEntityName(value: string, errorBuilder: ErrorBuilder) { | ||
if (['Query', 'Mutation'].includes(value)) { | ||
errorBuilder.add('MODEL_INVALID_ENTITY_NAME', `${value} is reserved word`) | ||
} | ||
} | ||
private validateMetaSuffixCollisions(entities: Model.Entity[], errorBuilder: ErrorBuilder) { | ||
const entityNames = new Set(entities.map(it => it.name)) | ||
for (const entity of entities) { | ||
if (entity.name.endsWith('Meta')) { | ||
const baseName = entity.name.substring(0, entity.name.length - 4) | ||
if (entityNames.has(baseName)) { | ||
errorBuilder.for(entity.name) | ||
.add('MODEL_NAME_COLLISION', `entity ${entity.name} collides with entity ${baseName}, because a GraphQL type with "Meta" suffix is created for every entity`) | ||
} | ||
} | ||
} | ||
} | ||
private validateTableNameCollisions(entities: Model.Entity[], errorBuilder: ErrorBuilder) { | ||
const relationNames: Record<string, string> = {} | ||
for (const entity of entities) { | ||
const description = `table name ${entity.tableName} of entity ${entity.name}` | ||
@@ -226,10 +279,2 @@ if (relationNames[entity.tableName]) { | ||
} | ||
if (entity.name.endsWith('Meta')) { | ||
const baseName = entity.name.substring(0, entity.name.length - 4) | ||
if (entityNames.has(baseName)) { | ||
errorBuilder.for(entity.name) | ||
.add('MODEL_NAME_COLLISION', `entity ${entity.name} collides with entity ${baseName}, because a GraphQL type with "Meta" suffix is created for every entity`) | ||
} | ||
} | ||
} | ||
@@ -253,3 +298,18 @@ for (const entity of entities) { | ||
}, | ||
visitColumn: ({ entity, column }) => { | ||
visitColumn: () => {}, | ||
visitManyHasManyInverse: () => { }, | ||
visitOneHasMany: () => { }, | ||
visitOneHasOneInverse: () => { }, | ||
visitOneHasOneOwning: () => { }, | ||
visitManyHasOne: () => { }, | ||
}) | ||
} | ||
} | ||
private validateAliasedTypesCollision(entities: Model.Entity[], errorBuilder: ErrorBuilder) { | ||
const aliasedTypes = new Map<string, Model.ColumnType>() | ||
for (const entity of entities) { | ||
acceptEveryFieldVisitor(this.model, entity, { | ||
visitColumn: ({ column }) => { | ||
if (!column.typeAlias) { | ||
@@ -265,2 +325,3 @@ return | ||
}, | ||
visitManyHasManyOwning: () => { }, | ||
visitManyHasManyInverse: () => { }, | ||
@@ -267,0 +328,0 @@ visitOneHasMany: () => { }, |
import { assert, test } from 'vitest' | ||
import { Model } from '@contember/schema' | ||
import { ModelValidator } from '../../../src' | ||
import { c, createSchema } from '@contember/schema-definition' | ||
test('"meta" collision', () => { | ||
@@ -77,1 +75,20 @@ const model: Model.Schema = { | ||
}) | ||
namespace ColumnNameCollision { | ||
export class Bar { | ||
rel = c.oneHasOne(Bar) | ||
relId = c.intColumn() | ||
} | ||
} | ||
test('column name collision', () => { | ||
const schema = createSchema(ColumnNameCollision) | ||
const validator = new ModelValidator(schema.model) | ||
assert.deepStrictEqual(validator.validate(), [ | ||
{ | ||
code: 'MODEL_NAME_COLLISION', | ||
message: 'Column name "rel_id" on field "relId" collides with a column name on field "rel".', | ||
path: ['entities', 'Bar', 'relId'], | ||
}, | ||
]) | ||
}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
610783
8538
+ Added@contember/schema@1.3.0-rc.2(transitive)
+ Added@contember/typesafe@1.3.0-rc.2(transitive)
- Removed@contember/schema@1.3.0-rc.1(transitive)
- Removed@contember/typesafe@1.3.0-rc.1(transitive)
Updated@contember/schema@1.3.0-rc.2