Navigation Menu

Skip to content

Commit

Permalink
feat(cli): Improve generated schema definitions (#2783)
Browse files Browse the repository at this point in the history
  • Loading branch information
daffl committed Oct 8, 2022
1 parent d886f33 commit 474a9fd
Show file tree
Hide file tree
Showing 17 changed files with 193 additions and 146 deletions.
10 changes: 8 additions & 2 deletions packages/cli/src/app/templates/logger.tpl.ts
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
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
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
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
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
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
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

0 comments on commit 474a9fd

Please sign in to comment.