Skip to content

Commit

Permalink
Merge pull request #35 from smartive/feat/improve-model
Browse files Browse the repository at this point in the history
break: Improve models
  • Loading branch information
nicola-smartive authored Aug 21, 2023
2 parents bf6d55a + 717af13 commit af9605b
Show file tree
Hide file tree
Showing 14 changed files with 460 additions and 384 deletions.
26 changes: 16 additions & 10 deletions src/client/queries.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import upperFirst from 'lodash/upperFirst';
import { Field } from '..';
import { Model, Models, Relation, ReverseRelation } from '../models';
import {
actionableRelations,
and,
getModelPlural,
getModelPluralField,
isQueriableBy,
isRelation,
isSimpleField,
isToOneRelation,
isUpdatableBy,
isVisibleRelation,
Model,
Models,
not,
Relation,
ReverseRelation,
VisibleRelationsByRole,
} from '../models';
import { getModelPlural, getModelPluralField, summonByName, typeToField } from '../utils';
summonByName,
typeToField,
} from '../utils';

export const getUpdateEntityQuery = (
model: Model,
Expand Down Expand Up @@ -54,8 +53,8 @@ export const getEditEntityRelationsQuery = (
!!relations.length &&
`query ${upperFirst(action)}${model.name}Relations {
${relations
.map(({ name, type }) => {
const model = summonByName(models, type);
.map(({ name, typeName }) => {
const model = summonByName(models, typeName);
let filters = '';
if (model.displayField) {
Expand Down Expand Up @@ -206,6 +205,13 @@ export const getEntityListQuery = (
${root ? '}' : ''}
}`;

export type VisibleRelationsByRole = Record<string, Record<string, string[]>>;

export const isVisibleRelation = (visibleRelationsByRole: VisibleRelationsByRole, modelName: string, role: string) => {
const whitelist = visibleRelationsByRole[role]?.[modelName];
return ({ name }: Field) => (whitelist ? whitelist.includes(name) : true);
};

export const getEntityQuery = (
models: Models,
model: Model,
Expand Down
37 changes: 22 additions & 15 deletions src/db/generate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import CodeBlockWriter from 'code-block-writer';
import { ModelField, RawModels, getModels, isEnumModel } from '..';
import { ModelField, RawModels, get, getModels, isEnumModel, isRaw, not } from '..';

const PRIMITIVE_TYPES = {
ID: 'string',
Expand Down Expand Up @@ -29,14 +29,14 @@ export const generateDBModels = (rawModels: RawModels) => {

for (const model of models) {
// TODO: deprecate allowing to define foreignKey
const fields = model.fields.some((field) => field.foreignKey === 'id')
const fields = model.fields.some((field) => field.type === 'relation' && field.foreignKey === 'id')
? model.fields.filter((field) => field.name !== 'id')
: model.fields;

writer
.write(`export type ${model.name} = `)
.inlineBlock(() => {
for (const field of fields.filter(({ raw }) => !raw)) {
for (const field of fields.filter(not(isRaw))) {
writer.write(`'${getFieldName(field)}': ${getFieldOutputType(field)}${field.nonNull ? '' : ' | null'},`).newLine();
}
})
Expand All @@ -45,7 +45,7 @@ export const generateDBModels = (rawModels: RawModels) => {
writer
.write(`export type ${model.name}Initializer = `)
.inlineBlock(() => {
for (const field of fields.filter(({ raw }) => !raw)) {
for (const field of fields.filter(not(isRaw))) {
writer
.write(
`'${getFieldName(field)}'${field.nonNull && field.default === undefined ? '' : '?'}: ${getFieldInputType(
Expand All @@ -60,7 +60,7 @@ export const generateDBModels = (rawModels: RawModels) => {
writer
.write(`export type ${model.name}Mutator = `)
.inlineBlock(() => {
for (const field of fields.filter(({ raw }) => !raw)) {
for (const field of fields.filter(not(isRaw))) {
writer.write(`'${getFieldName(field)}'?: ${getFieldInputType(field)}${field.nonNull ? '' : ' | null'},`).newLine();
}
})
Expand All @@ -69,7 +69,7 @@ export const generateDBModels = (rawModels: RawModels) => {
writer
.write(`export type ${model.name}Seed = `)
.inlineBlock(() => {
for (const field of fields.filter(({ raw }) => !raw)) {
for (const field of fields.filter(not(isRaw))) {
const fieldName = getFieldName(field);
writer
.write(
Expand All @@ -95,16 +95,23 @@ export const generateDBModels = (rawModels: RawModels) => {
return writer.toString();
};

const getFieldName = ({ relation, name, foreignKey }: ModelField) => {
return foreignKey || `${name}${relation ? 'Id' : ''}`;
};

const getFieldOutputType = ({ relation, type, list, json }: ModelField) => {
if (json || relation) {
return 'string';
const getFieldName = (field: ModelField) => (field.type === 'relation' ? field.foreignKey || `${field.name}Id` : field.name);

const getFieldOutputType = (field: ModelField) => {
switch (field.type) {
case 'json':
// JSON data is stored as string
return 'string';
case 'relation':
// Relations are stored as ids
return 'string';
case 'enum':
return field.typeName + field.list ? '[]' : '';
case 'raw':
throw new Error(`Raw fields are not in the db.`);
default:
return get(PRIMITIVE_TYPES, field.type) + (field.list ? '[]' : '');
}

return (PRIMITIVE_TYPES[type] || type) + (list ? '[]' : '');
};

const getFieldInputType = (field: ModelField, stringTypes: string[] = []) => {
Expand Down
60 changes: 26 additions & 34 deletions src/generate/generate.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { buildASTSchema, DefinitionNode, DocumentNode, GraphQLSchema, print } from 'graphql';
import flatMap from 'lodash/flatMap';
import { RawModels } from '../models';
import {
Field,
getModelPluralField,
getModels,
isEnumModel,
isJsonObjectModel,
isQueriableField,
isRawEnumModel,
isRawObjectModel,
isRelation,
isScalarModel,
RawModels,
} from '../models';
import { getModelPluralField, getModels, typeToField } from '../utils';
import { document, enm, input, object, scalar } from './utils';
typeToField,
} from '../utils';
import { document, enm, Field, input, object, scalar } from './utils';

export const generateDefinitions = (rawModels: RawModels): DefinitionNode[] => {
const models = getModels(rawModels);
Expand All @@ -26,17 +27,6 @@ export const generateDefinitions = (rawModels: RawModels): DefinitionNode[] => {
...rawModels.filter(isRawEnumModel).map((model) => enm(model.name, model.values)),
...rawModels.filter(isScalarModel).map((model) => scalar(model.name)),
...rawModels.filter(isRawObjectModel).map((model) => object(model.name, model.fields)),
...rawModels.filter(isJsonObjectModel).map((model) => object(model.name, model.fields)),
...rawModels
.filter(isRawObjectModel)
.filter(({ rawFilters }) => rawFilters)
.map((model) =>
input(
`${model.name}Where`,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- array gets filtered above to only include models with rawFilters
model.rawFilters!.map(({ name, type, list = false, nonNull = false }) => ({ name, type, list, nonNull }))
)
),

...flatMap(
models.map((model) => {
Expand All @@ -46,10 +36,9 @@ export const generateDefinitions = (rawModels: RawModels): DefinitionNode[] => {
[
...model.fields.filter(isQueriableField).map((field) => ({
...field,
args: [
...(field.args || []),
...(hasRawFilters(rawModels, field.type) ? [{ name: 'where', type: `${field.type}Where` }] : []),
],
type:
field.type === 'relation' || field.type === 'enum' || field.type === 'raw' ? field.typeName : field.type,
args: [...(field.args || [])],
directives: field.directives,
})),
...model.reverseRelations.map(({ name, field, model }) => ({
Expand All @@ -72,8 +61,13 @@ export const generateDefinitions = (rawModels: RawModels): DefinitionNode[] => {
),
input(`${model.name}Where`, [
...model.fields
.filter(({ unique, filterable, relation }) => (unique || filterable) && !relation)
.map(({ name, type, defaultFilter }) => ({ name, type, list: true, default: defaultFilter })),
.filter(({ type, unique, filterable }) => (unique || filterable) && type !== 'relation')
.map(({ type, name, filterable }) => ({
name,
type,
list: true,
default: typeof filterable === 'object' ? filterable.default : undefined,
})),
...flatMap(
model.fields.filter(({ comparable }) => comparable),
({ name, type }) => [
Expand All @@ -84,10 +78,11 @@ export const generateDefinitions = (rawModels: RawModels): DefinitionNode[] => {
]
),
...model.fields
.filter(({ filterable, relation }) => filterable && relation)
.map(({ name, type }) => ({
.filter(isRelation)
.filter(({ filterable }) => filterable)
.map(({ name, typeName }) => ({
name,
type: `${type}Where`,
type: `${typeName}Where`,
})),
]),
input(
Expand All @@ -110,10 +105,10 @@ export const generateDefinitions = (rawModels: RawModels): DefinitionNode[] => {
`Create${model.name}`,
model.fields
.filter(({ creatable }) => creatable)
.map(({ name, relation, type, nonNull, list, default: defaultValue }) =>
relation
.map(({ name, nonNull, list, default: defaultValue, ...field }) =>
field.type === 'relation'
? { name: `${name}Id`, type: 'ID', nonNull }
: { name, type, list, nonNull: nonNull && defaultValue === undefined }
: { name, type: field.type, list, nonNull: nonNull && defaultValue === undefined }
)
)
);
Expand All @@ -125,8 +120,8 @@ export const generateDefinitions = (rawModels: RawModels): DefinitionNode[] => {
`Update${model.name}`,
model.fields
.filter(({ updatable }) => updatable)
.map(({ name, relation, type, list }) =>
relation ? { name: `${name}Id`, type: 'ID' } : { name, type, list }
.map(({ name, type, list }) =>
type === 'relation' ? { name: `${name}Id`, type: 'ID' } : { name, type, list }
)
)
);
Expand Down Expand Up @@ -265,9 +260,6 @@ export const printSchema = (schema: GraphQLSchema): string =>
.map((s) => `${s}\n`)
.join('\n');

const hasRawFilters = (models: RawModels, type: string) =>
models.filter(isRawObjectModel).some(({ name, rawFilters }) => name === type && !!rawFilters);

export const printSchemaFromDocument = (document: DocumentNode) => printSchema(buildASTSchema(document));

export const printSchemaFromModels = (models: RawModels) => printSchema(buildASTSchema(generate(models)));
12 changes: 11 additions & 1 deletion src/generate/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,19 @@ import {
ValueNode,
} from 'graphql';
import { DateTime } from 'luxon';
import { Field } from '../models';
import { Directive, Enum, Value, Values } from '../values';

export type Field = {
name: string;
type: string;
description?: string;
list?: boolean;
nonNull?: boolean;
default?: Value;
args?: Field[];
directives?: Directive[];
};

export type DirectiveLocation =
| 'ARGUMENT_DEFINITION'
| 'INPUT_FIELD_DEFINITION'
Expand Down
Loading

0 comments on commit af9605b

Please sign in to comment.