Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@contember/schema-utils

Package Overview
Dependencies
Maintainers
5
Versions
259
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@contember/schema-utils - npm Package Compare versions

Comparing version 1.3.0-rc.1 to 1.3.0-rc.2

2

dist/src/type-schema/model.js

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc