Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): Improve generated schema definitions #2783

Merged
merged 2 commits into from
Oct 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/cli/src/app/templates/logger.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ export const logger = createLogger({
export const logErrorHook = async (context: HookContext, next: NextFunction) => {
try {
await next()
} catch (error) {
logger.error(error)
} catch (error: any) {
logger.error(error.stack)

// Log validation errors
if (error.errors) {
logger.error(error.errors)
}

throw error
}
}
Expand Down
9 changes: 4 additions & 5 deletions packages/cli/src/app/templates/schemas.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const queryValidator = addFormats(new Ajv({
`

const configurationJsonTemplate =
({}: AppGeneratorContext) => /* ts */ `import { defaultAppSettings, jsonSchema } from '@feathersjs/schema'
({}: AppGeneratorContext) => /* ts */ `import { defaultAppSettings, getValidator } from '@feathersjs/schema'
import type { FromSchema } from '@feathersjs/schema'

import { dataValidator } from './validators'
Expand All @@ -47,14 +47,13 @@ export const configurationSchema = {
}
} as const

export const configurationValidator = jsonSchema.getValidator(configurationSchema, dataValidator)
export const configurationValidator = getValidator(configurationSchema, dataValidator)

export type ApplicationConfiguration = FromSchema<typeof configurationSchema>
`

const configurationTypeboxTemplate =
({}: AppGeneratorContext) => /* ts */ `import { jsonSchema } from '@feathersjs/schema'
import { Type, defaultAppConfiguration } from '@feathersjs/typebox'
({}: AppGeneratorContext) => /* ts */ `import { Type, getValidator, defaultAppConfiguration } from '@feathersjs/typebox'
import type { Static } from '@feathersjs/typebox'

import { dataValidator } from './validators'
Expand All @@ -70,7 +69,7 @@ export const configurationSchema = Type.Intersect([

export type ApplicationConfiguration = Static<typeof configurationSchema>

export const configurationValidator = jsonSchema.getValidator(configurationSchema, dataValidator)
export const configurationValidator = getValidator(configurationSchema, dataValidator)
`

export const generate = (ctx: AppGeneratorContext) =>
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/authentication/templates/knex.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { getDatabaseAdapter, renderSource } from '../../commons'
import { AuthenticationGeneratorContext } from '../index'

const migrationTemplate = ({
kebabName,
kebabPath,
authStrategies
}: AuthenticationGeneratorContext) => /* ts */ `import type { Knex } from 'knex'

export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable('${kebabName}', function (table) {
await knex.schema.alterTable('${kebabPath}', function (table) {
table.dropColumn('text')${authStrategies
.map((name) =>
name === 'local'
Expand All @@ -23,7 +23,7 @@ export async function up(knex: Knex): Promise<void> {
}

export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable('${kebabName}', function (table) {
await knex.schema.alterTable('${kebabPath}', function (table) {
table.string('text')${authStrategies
.map((name) =>
name === 'local'
Expand Down
48 changes: 24 additions & 24 deletions packages/cli/src/authentication/templates/schema.json.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@ const template = ({
authStrategies,
type,
relative
}: AuthenticationGeneratorContext) => /* ts */ `import { resolve, jsonSchema } from '@feathersjs/schema'
}: AuthenticationGeneratorContext) => /* ts */ `import { resolve, querySyntax, getValidator, getDataValidator } from '@feathersjs/schema'
import type { FromSchema } from '@feathersjs/schema'
${authStrategies.includes('local') ? `import { passwordHash } from '@feathersjs/authentication-local'` : ''}

import type { HookContext } from '${relative}/declarations'
import { dataValidator, queryValidator } from '${relative}/schemas/validators'

// Schema for the basic data model (e.g. creating new entries)
export const ${camelName}DataSchema = {
$id: '${upperName}Data',
// Main data model schema
export const ${camelName}Schema = {
$id: '${upperName}',
type: 'object',
additionalProperties: false,
required: [ ${authStrategies.includes('local') ? "'email'" : ''} ],
required: [ '${type === 'mongodb' ? '_id' : 'id'}'${authStrategies.includes('local') ? ", 'email'" : ''} ],
properties: {
${type === 'mongodb' ? '_id' : 'id'}: {
type: '${type === 'mongodb' ? 'string' : 'number'}'
},
${authStrategies
.map((name) =>
name === 'local'
Expand All @@ -32,30 +35,27 @@ export const ${camelName}DataSchema = {
.join(',\n')}
}
} as const
export type ${upperName}Data = FromSchema<typeof ${camelName}DataSchema>
export const ${camelName}DataValidator = jsonSchema.getDataValidator(${camelName}DataSchema, dataValidator)
export const ${camelName}DataResolver = resolve<${upperName}Data, HookContext>({
properties: {
${authStrategies.includes('local') ? `password: passwordHash({ strategy: 'local' })` : ''}
}
export type ${upperName} = FromSchema<typeof ${camelName}Schema>
export const ${camelName}Resolver = resolve<${upperName}, HookContext>({
properties: {}
})

// Schema for the data that is being returned
export const ${camelName}Schema = {
$id: '${upperName}',
// Schema for the basic data model (e.g. creating new entries)
export const ${camelName}DataSchema = {
$id: '${upperName}Data',
type: 'object',
additionalProperties: false,
required: [ ...${camelName}DataSchema.required, '${type === 'mongodb' ? '_id' : 'id'}' ],
required: [ ],
properties: {
...${camelName}DataSchema.properties,
${type === 'mongodb' ? '_id' : 'id'}: {
type: '${type === 'mongodb' ? 'string' : 'number'}'
}
...${camelName}Schema.properties
}
} as const
export type ${upperName} = FromSchema<typeof ${camelName}Schema>
export const ${camelName}Resolver = resolve<${upperName}, HookContext>({
properties: {}
export type ${upperName}Data = FromSchema<typeof ${camelName}DataSchema>
export const ${camelName}DataValidator = getDataValidator(${camelName}DataSchema, dataValidator)
export const ${camelName}DataResolver = resolve<${upperName}Data, HookContext>({
properties: {
${authStrategies.includes('local') ? `password: passwordHash({ strategy: 'local' })` : ''}
}
})

export const ${camelName}ExternalResolver = resolve<${upperName}, HookContext>({
Expand All @@ -71,11 +71,11 @@ export const ${camelName}QuerySchema = {
type: 'object',
additionalProperties: false,
properties: {
...jsonSchema.querySyntax(${camelName}Schema.properties)
...querySyntax(${camelName}Schema.properties)
}
} as const
export type ${upperName}Query = FromSchema<typeof ${camelName}QuerySchema>
export const ${camelName}QueryValidator = jsonSchema.getValidator(${camelName}QuerySchema, queryValidator)
export const ${camelName}QueryValidator = getValidator(${camelName}QuerySchema, queryValidator)
export const ${camelName}QueryResolver = resolve<${upperName}Query, HookContext>({
properties: {
// If there is a user (e.g. with authentication), they are only allowed to see their own data
Expand Down
52 changes: 26 additions & 26 deletions packages/cli/src/authentication/templates/schema.typebox.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,26 @@ export const template = ({
authStrategies,
type,
relative
}: AuthenticationGeneratorContext) => /* ts */ `import { jsonSchema, resolve } from '@feathersjs/schema'
import { Type, querySyntax } from '@feathersjs/typebox'
}: AuthenticationGeneratorContext) => /* ts */ `import { resolve } from '@feathersjs/schema'
import { Type, getDataValidator, getValidator, querySyntax } from '@feathersjs/typebox'
import type { Static } from '@feathersjs/typebox'
${authStrategies.includes('local') ? `import { passwordHash } from '@feathersjs/authentication-local'` : ''}

import type { HookContext } from '${relative}/declarations'
import { dataValidator, queryValidator } from '${relative}/schemas/validators'

// Schema for the basic data model (e.g. creating new entries)
export const ${camelName}DataSchema = Type.Object({
// Main data model schema
export const ${camelName}Schema = Type.Object({
${type === 'mongodb' ? '_id: Type.String()' : 'id: Type.Number()'},
${authStrategies
.map((name) =>
name === 'local'
? ` email: Type.String(),
password: Type.String()`
password: Type.Optional(Type.String())`
: ` ${name}Id: Type.Optional(Type.String())`
)
.join(',\n')}
}, { $id: '${upperName}Data', additionalProperties: false })
export type ${upperName}Data = Static<typeof ${camelName}DataSchema>
export const ${camelName}DataValidator = jsonSchema.getDataValidator(${camelName}DataSchema, dataValidator)
export const ${camelName}DataResolver = resolve<${upperName}Data, HookContext>({
properties: {
${authStrategies.includes('local') ? `password: passwordHash({ strategy: 'local' })` : ''}
}
})

// Schema for the data that is being returned
export const ${camelName}Schema = Type.Intersect([
${camelName}DataSchema,
Type.Object({
${type === 'mongodb' ? '_id: Type.String()' : 'id: Type.Number()'}
})
], { $id: '${upperName}' })
},{ $id: '${upperName}', additionalProperties: false })
export type ${upperName} = Static<typeof ${camelName}Schema>
export const ${camelName}Resolver = resolve<${upperName}, HookContext>({
properties: {}
Expand All @@ -54,14 +40,28 @@ export const ${camelName}ExternalResolver = resolve<${upperName}, HookContext>({
}
})

// Schema for the basic data model (e.g. creating new entries)
export const ${camelName}DataSchema = Type.Pick(${camelName}Schema, [
${authStrategies.map((name) => (name === 'local' ? `'email', 'password'` : `'${name}Id'`)).join(', ')}
],
{ $id: '${upperName}Data', additionalProperties: false }
)
export type ${upperName}Data = Static<typeof ${camelName}DataSchema>
export const ${camelName}DataValidator = getDataValidator(${camelName}DataSchema, dataValidator)
export const ${camelName}DataResolver = resolve<${upperName}, HookContext>({
properties: {
${authStrategies.includes('local') ? `password: passwordHash({ strategy: 'local' })` : ''}
}
})

// Schema for allowed query properties
export const ${camelName}QuerySchema = Type.Intersect([
querySyntax(${camelName}Schema),
// Add additional query properties here
Type.Object({})
export const ${camelName}QueryProperties = Type.Pick(${camelName}Schema, ['${
type === 'mongodb' ? '_id' : 'id'
}', ${authStrategies.map((name) => (name === 'local' ? `'email'` : `'${name}Id'`)).join(', ')}
])
export const ${camelName}QuerySchema = querySyntax(${camelName}QueryProperties)
export type ${upperName}Query = Static<typeof ${camelName}QuerySchema>
export const ${camelName}QueryValidator = jsonSchema.getValidator(${camelName}QuerySchema, queryValidator)
export const ${camelName}QueryValidator = getValidator(${camelName}QuerySchema, queryValidator)
export const ${camelName}QueryResolver = resolve<${upperName}Query, HookContext>({
properties: {
// If there is a user (e.g. with authentication), they are only allowed to see their own data
Expand Down
14 changes: 10 additions & 4 deletions packages/cli/src/service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export interface ServiceGeneratorContext extends FeathersBaseContext {
* The actual filename (the last element of the path)
*/
fileName: string
/**
* The kebab-cased name of the path. Will be used for e.g. database names
*/
kebabPath: string
/**
* Indicates how many file paths we should go up to import other things (e.g. `../../`)
*/
Expand Down Expand Up @@ -77,7 +81,7 @@ export const generate = (ctx: ServiceGeneratorArguments) =>
.then(checkPreconditions())
.then(
prompt<ServiceGeneratorArguments, ServiceGeneratorContext>(
({ name, path, type, schema, authentication, isEntityService }) => [
({ name, path, type, schema, authentication, isEntityService, feathers }) => [
{
name: 'name',
type: 'input',
Expand Down Expand Up @@ -116,7 +120,7 @@ export const generate = (ctx: ServiceGeneratorArguments) =>
type: 'list',
when: !type,
message: 'What kind of service is it?',
default: getDatabaseAdapter(ctx.feathers?.database),
default: getDatabaseAdapter(feathers?.database),
choices: [
{
value: 'knex',
Expand All @@ -137,7 +141,7 @@ export const generate = (ctx: ServiceGeneratorArguments) =>
type: 'list',
when: schema === undefined,
message: 'Which schema definition format do you want to use?',
default: ctx.feathers?.schema || 'json',
default: feathers?.schema,
choices: [
{
value: 'typebox',
Expand All @@ -156,7 +160,7 @@ export const generate = (ctx: ServiceGeneratorArguments) =>
]
)
)
.then(async (ctx) => {
.then(async (ctx): Promise<ServiceGeneratorContext> => {
const { name, path, type } = ctx
const kebabName = _.kebabCase(name)
const camelName = _.camelCase(name)
Expand All @@ -166,6 +170,7 @@ export const generate = (ctx: ServiceGeneratorArguments) =>
const folder = path.split('/').filter((el) => el !== '')
const relative = ['', ...folder].map(() => '..').join('/')
const fileName = _.last(folder)
const kebabPath = _.kebabCase(path)

return {
name,
Expand All @@ -177,6 +182,7 @@ export const generate = (ctx: ServiceGeneratorArguments) =>
className,
kebabName,
camelName,
kebabPath,
relative,
...ctx
}
Expand Down
46 changes: 24 additions & 22 deletions packages/cli/src/service/templates/schema.json.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,50 @@ const template = ({
upperName,
relative,
type
}: ServiceGeneratorContext) => /* ts */ `import { jsonSchema, resolve } from '@feathersjs/schema'
}: ServiceGeneratorContext) => /* ts */ `import { resolve, getDataValidator, getValidator, querySyntax } from '@feathersjs/schema'
import type { FromSchema } from '@feathersjs/schema'

import type { HookContext } from '${relative}/declarations'
import { dataValidator, queryValidator } from '${relative}/schemas/validators'

// Schema for the basic data model (e.g. creating new entries)
export const ${camelName}DataSchema = {
$id: '${upperName}Data',
// Main data model schema
export const ${camelName}Schema = {
$id: '${upperName}',
type: 'object',
additionalProperties: false,
required: [ 'text' ],
required: [ '${type === 'mongodb' ? '_id' : 'id'}', 'text' ],
properties: {
${type === 'mongodb' ? '_id' : 'id'}: {
type: '${type === 'mongodb' ? 'string' : 'number'}'
},
text: {
type: 'string'
}
}
} as const
export type ${upperName}Data = FromSchema<typeof ${camelName}DataSchema>
export const ${camelName}DataValidator = jsonSchema.getDataValidator(${camelName}DataSchema, dataValidator)
export const ${camelName}DataResolver = resolve<${upperName}Data, HookContext>({
export type ${upperName} = FromSchema<typeof ${camelName}Schema>
export const ${camelName}Resolver = resolve<${upperName}, HookContext>({
properties: {}
})
export const ${camelName}ExternalResolver = resolve<${upperName}, HookContext>({
properties: {}
})

// Schema for the data that is being returned
export const ${camelName}Schema = {
$id: '${upperName}',
// Schema for creating new data
export const ${camelName}DataSchema = {
$id: '${upperName}Data',
type: 'object',
additionalProperties: false,
required: [ ...${camelName}DataSchema.required, '${type === 'mongodb' ? '_id' : 'id'}' ],
required: [ 'text' ],
properties: {
...${camelName}DataSchema.properties,
${type === 'mongodb' ? '_id' : 'id'}: {
type: '${type === 'mongodb' ? 'string' : 'number'}'
text: {
type: 'string'
}
}
} as const
export type ${upperName} = FromSchema<typeof ${camelName}Schema>
export const ${camelName}Resolver = resolve<${upperName}, HookContext>({
properties: {}
})
export const ${camelName}ExternalResolver = resolve<${upperName}, HookContext>({
export type ${upperName}Data = FromSchema<typeof ${camelName}DataSchema>
export const ${camelName}DataValidator = getDataValidator(${camelName}DataSchema, dataValidator)
export const ${camelName}DataResolver = resolve<${upperName}Data, HookContext>({
properties: {}
})

Expand All @@ -58,11 +60,11 @@ export const ${camelName}QuerySchema = {
type: 'object',
additionalProperties: false,
properties: {
...jsonSchema.querySyntax(${camelName}Schema.properties)
...querySyntax(${camelName}Schema.properties)
}
} as const
export type ${upperName}Query = FromSchema<typeof ${camelName}QuerySchema>
export const ${camelName}QueryValidator = jsonSchema.getValidator(${camelName}QuerySchema, queryValidator)
export const ${camelName}QueryValidator = getValidator(${camelName}QuerySchema, queryValidator)
export const ${camelName}QueryResolver = resolve<${upperName}Query, HookContext>({
properties: {}
})
Expand Down
Loading