Skip to content

Commit 474a9fd

Browse files
authored
feat(cli): Improve generated schema definitions (#2783)
1 parent d886f33 commit 474a9fd

File tree

17 files changed

+193
-146
lines changed

17 files changed

+193
-146
lines changed

packages/cli/src/app/templates/logger.tpl.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ export const logger = createLogger({
2222
export const logErrorHook = async (context: HookContext, next: NextFunction) => {
2323
try {
2424
await next()
25-
} catch (error) {
26-
logger.error(error)
25+
} catch (error: any) {
26+
logger.error(error.stack)
27+
28+
// Log validation errors
29+
if (error.errors) {
30+
logger.error(error.errors)
31+
}
32+
2733
throw error
2834
}
2935
}

packages/cli/src/app/templates/schemas.tpl.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const queryValidator = addFormats(new Ajv({
3030
`
3131

3232
const configurationJsonTemplate =
33-
({}: AppGeneratorContext) => /* ts */ `import { defaultAppSettings, jsonSchema } from '@feathersjs/schema'
33+
({}: AppGeneratorContext) => /* ts */ `import { defaultAppSettings, getValidator } from '@feathersjs/schema'
3434
import type { FromSchema } from '@feathersjs/schema'
3535
3636
import { dataValidator } from './validators'
@@ -47,14 +47,13 @@ export const configurationSchema = {
4747
}
4848
} as const
4949
50-
export const configurationValidator = jsonSchema.getValidator(configurationSchema, dataValidator)
50+
export const configurationValidator = getValidator(configurationSchema, dataValidator)
5151
5252
export type ApplicationConfiguration = FromSchema<typeof configurationSchema>
5353
`
5454

5555
const configurationTypeboxTemplate =
56-
({}: AppGeneratorContext) => /* ts */ `import { jsonSchema } from '@feathersjs/schema'
57-
import { Type, defaultAppConfiguration } from '@feathersjs/typebox'
56+
({}: AppGeneratorContext) => /* ts */ `import { Type, getValidator, defaultAppConfiguration } from '@feathersjs/typebox'
5857
import type { Static } from '@feathersjs/typebox'
5958
6059
import { dataValidator } from './validators'
@@ -70,7 +69,7 @@ export const configurationSchema = Type.Intersect([
7069
7170
export type ApplicationConfiguration = Static<typeof configurationSchema>
7271
73-
export const configurationValidator = jsonSchema.getValidator(configurationSchema, dataValidator)
72+
export const configurationValidator = getValidator(configurationSchema, dataValidator)
7473
`
7574

7675
export const generate = (ctx: AppGeneratorContext) =>

packages/cli/src/authentication/templates/knex.tpl.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { getDatabaseAdapter, renderSource } from '../../commons'
33
import { AuthenticationGeneratorContext } from '../index'
44

55
const migrationTemplate = ({
6-
kebabName,
6+
kebabPath,
77
authStrategies
88
}: AuthenticationGeneratorContext) => /* ts */ `import type { Knex } from 'knex'
99
1010
export async function up(knex: Knex): Promise<void> {
11-
await knex.schema.alterTable('${kebabName}', function (table) {
11+
await knex.schema.alterTable('${kebabPath}', function (table) {
1212
table.dropColumn('text')${authStrategies
1313
.map((name) =>
1414
name === 'local'
@@ -23,7 +23,7 @@ export async function up(knex: Knex): Promise<void> {
2323
}
2424
2525
export async function down(knex: Knex): Promise<void> {
26-
await knex.schema.alterTable('${kebabName}', function (table) {
26+
await knex.schema.alterTable('${kebabPath}', function (table) {
2727
table.string('text')${authStrategies
2828
.map((name) =>
2929
name === 'local'

packages/cli/src/authentication/templates/schema.json.tpl.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,23 @@ const template = ({
88
authStrategies,
99
type,
1010
relative
11-
}: AuthenticationGeneratorContext) => /* ts */ `import { resolve, jsonSchema } from '@feathersjs/schema'
11+
}: AuthenticationGeneratorContext) => /* ts */ `import { resolve, querySyntax, getValidator, getDataValidator } from '@feathersjs/schema'
1212
import type { FromSchema } from '@feathersjs/schema'
1313
${authStrategies.includes('local') ? `import { passwordHash } from '@feathersjs/authentication-local'` : ''}
1414
1515
import type { HookContext } from '${relative}/declarations'
1616
import { dataValidator, queryValidator } from '${relative}/schemas/validators'
1717
18-
// Schema for the basic data model (e.g. creating new entries)
19-
export const ${camelName}DataSchema = {
20-
$id: '${upperName}Data',
18+
// Main data model schema
19+
export const ${camelName}Schema = {
20+
$id: '${upperName}',
2121
type: 'object',
2222
additionalProperties: false,
23-
required: [ ${authStrategies.includes('local') ? "'email'" : ''} ],
23+
required: [ '${type === 'mongodb' ? '_id' : 'id'}'${authStrategies.includes('local') ? ", 'email'" : ''} ],
2424
properties: {
25+
${type === 'mongodb' ? '_id' : 'id'}: {
26+
type: '${type === 'mongodb' ? 'string' : 'number'}'
27+
},
2528
${authStrategies
2629
.map((name) =>
2730
name === 'local'
@@ -32,30 +35,27 @@ export const ${camelName}DataSchema = {
3235
.join(',\n')}
3336
}
3437
} as const
35-
export type ${upperName}Data = FromSchema<typeof ${camelName}DataSchema>
36-
export const ${camelName}DataValidator = jsonSchema.getDataValidator(${camelName}DataSchema, dataValidator)
37-
export const ${camelName}DataResolver = resolve<${upperName}Data, HookContext>({
38-
properties: {
39-
${authStrategies.includes('local') ? `password: passwordHash({ strategy: 'local' })` : ''}
40-
}
38+
export type ${upperName} = FromSchema<typeof ${camelName}Schema>
39+
export const ${camelName}Resolver = resolve<${upperName}, HookContext>({
40+
properties: {}
4141
})
4242
43-
// Schema for the data that is being returned
44-
export const ${camelName}Schema = {
45-
$id: '${upperName}',
43+
// Schema for the basic data model (e.g. creating new entries)
44+
export const ${camelName}DataSchema = {
45+
$id: '${upperName}Data',
4646
type: 'object',
4747
additionalProperties: false,
48-
required: [ ...${camelName}DataSchema.required, '${type === 'mongodb' ? '_id' : 'id'}' ],
48+
required: [ ],
4949
properties: {
50-
...${camelName}DataSchema.properties,
51-
${type === 'mongodb' ? '_id' : 'id'}: {
52-
type: '${type === 'mongodb' ? 'string' : 'number'}'
53-
}
50+
...${camelName}Schema.properties
5451
}
5552
} as const
56-
export type ${upperName} = FromSchema<typeof ${camelName}Schema>
57-
export const ${camelName}Resolver = resolve<${upperName}, HookContext>({
58-
properties: {}
53+
export type ${upperName}Data = FromSchema<typeof ${camelName}DataSchema>
54+
export const ${camelName}DataValidator = getDataValidator(${camelName}DataSchema, dataValidator)
55+
export const ${camelName}DataResolver = resolve<${upperName}Data, HookContext>({
56+
properties: {
57+
${authStrategies.includes('local') ? `password: passwordHash({ strategy: 'local' })` : ''}
58+
}
5959
})
6060
6161
export const ${camelName}ExternalResolver = resolve<${upperName}, HookContext>({
@@ -71,11 +71,11 @@ export const ${camelName}QuerySchema = {
7171
type: 'object',
7272
additionalProperties: false,
7373
properties: {
74-
...jsonSchema.querySyntax(${camelName}Schema.properties)
74+
...querySyntax(${camelName}Schema.properties)
7575
}
7676
} as const
7777
export type ${upperName}Query = FromSchema<typeof ${camelName}QuerySchema>
78-
export const ${camelName}QueryValidator = jsonSchema.getValidator(${camelName}QuerySchema, queryValidator)
78+
export const ${camelName}QueryValidator = getValidator(${camelName}QuerySchema, queryValidator)
7979
export const ${camelName}QueryResolver = resolve<${upperName}Query, HookContext>({
8080
properties: {
8181
// If there is a user (e.g. with authentication), they are only allowed to see their own data

packages/cli/src/authentication/templates/schema.typebox.tpl.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,26 @@ export const template = ({
88
authStrategies,
99
type,
1010
relative
11-
}: AuthenticationGeneratorContext) => /* ts */ `import { jsonSchema, resolve } from '@feathersjs/schema'
12-
import { Type, querySyntax } from '@feathersjs/typebox'
11+
}: AuthenticationGeneratorContext) => /* ts */ `import { resolve } from '@feathersjs/schema'
12+
import { Type, getDataValidator, getValidator, querySyntax } from '@feathersjs/typebox'
1313
import type { Static } from '@feathersjs/typebox'
1414
${authStrategies.includes('local') ? `import { passwordHash } from '@feathersjs/authentication-local'` : ''}
1515
1616
import type { HookContext } from '${relative}/declarations'
1717
import { dataValidator, queryValidator } from '${relative}/schemas/validators'
1818
19-
// Schema for the basic data model (e.g. creating new entries)
20-
export const ${camelName}DataSchema = Type.Object({
19+
// Main data model schema
20+
export const ${camelName}Schema = Type.Object({
21+
${type === 'mongodb' ? '_id: Type.String()' : 'id: Type.Number()'},
2122
${authStrategies
2223
.map((name) =>
2324
name === 'local'
2425
? ` email: Type.String(),
25-
password: Type.String()`
26+
password: Type.Optional(Type.String())`
2627
: ` ${name}Id: Type.Optional(Type.String())`
2728
)
2829
.join(',\n')}
29-
}, { $id: '${upperName}Data', additionalProperties: false })
30-
export type ${upperName}Data = Static<typeof ${camelName}DataSchema>
31-
export const ${camelName}DataValidator = jsonSchema.getDataValidator(${camelName}DataSchema, dataValidator)
32-
export const ${camelName}DataResolver = resolve<${upperName}Data, HookContext>({
33-
properties: {
34-
${authStrategies.includes('local') ? `password: passwordHash({ strategy: 'local' })` : ''}
35-
}
36-
})
37-
38-
// Schema for the data that is being returned
39-
export const ${camelName}Schema = Type.Intersect([
40-
${camelName}DataSchema,
41-
Type.Object({
42-
${type === 'mongodb' ? '_id: Type.String()' : 'id: Type.Number()'}
43-
})
44-
], { $id: '${upperName}' })
30+
},{ $id: '${upperName}', additionalProperties: false })
4531
export type ${upperName} = Static<typeof ${camelName}Schema>
4632
export const ${camelName}Resolver = resolve<${upperName}, HookContext>({
4733
properties: {}
@@ -54,14 +40,28 @@ export const ${camelName}ExternalResolver = resolve<${upperName}, HookContext>({
5440
}
5541
})
5642
43+
// Schema for the basic data model (e.g. creating new entries)
44+
export const ${camelName}DataSchema = Type.Pick(${camelName}Schema, [
45+
${authStrategies.map((name) => (name === 'local' ? `'email', 'password'` : `'${name}Id'`)).join(', ')}
46+
],
47+
{ $id: '${upperName}Data', additionalProperties: false }
48+
)
49+
export type ${upperName}Data = Static<typeof ${camelName}DataSchema>
50+
export const ${camelName}DataValidator = getDataValidator(${camelName}DataSchema, dataValidator)
51+
export const ${camelName}DataResolver = resolve<${upperName}, HookContext>({
52+
properties: {
53+
${authStrategies.includes('local') ? `password: passwordHash({ strategy: 'local' })` : ''}
54+
}
55+
})
56+
5757
// Schema for allowed query properties
58-
export const ${camelName}QuerySchema = Type.Intersect([
59-
querySyntax(${camelName}Schema),
60-
// Add additional query properties here
61-
Type.Object({})
58+
export const ${camelName}QueryProperties = Type.Pick(${camelName}Schema, ['${
59+
type === 'mongodb' ? '_id' : 'id'
60+
}', ${authStrategies.map((name) => (name === 'local' ? `'email'` : `'${name}Id'`)).join(', ')}
6261
])
62+
export const ${camelName}QuerySchema = querySyntax(${camelName}QueryProperties)
6363
export type ${upperName}Query = Static<typeof ${camelName}QuerySchema>
64-
export const ${camelName}QueryValidator = jsonSchema.getValidator(${camelName}QuerySchema, queryValidator)
64+
export const ${camelName}QueryValidator = getValidator(${camelName}QuerySchema, queryValidator)
6565
export const ${camelName}QueryResolver = resolve<${upperName}Query, HookContext>({
6666
properties: {
6767
// If there is a user (e.g. with authentication), they are only allowed to see their own data

packages/cli/src/service/index.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ export interface ServiceGeneratorContext extends FeathersBaseContext {
4141
* The actual filename (the last element of the path)
4242
*/
4343
fileName: string
44+
/**
45+
* The kebab-cased name of the path. Will be used for e.g. database names
46+
*/
47+
kebabPath: string
4448
/**
4549
* Indicates how many file paths we should go up to import other things (e.g. `../../`)
4650
*/
@@ -77,7 +81,7 @@ export const generate = (ctx: ServiceGeneratorArguments) =>
7781
.then(checkPreconditions())
7882
.then(
7983
prompt<ServiceGeneratorArguments, ServiceGeneratorContext>(
80-
({ name, path, type, schema, authentication, isEntityService }) => [
84+
({ name, path, type, schema, authentication, isEntityService, feathers }) => [
8185
{
8286
name: 'name',
8387
type: 'input',
@@ -116,7 +120,7 @@ export const generate = (ctx: ServiceGeneratorArguments) =>
116120
type: 'list',
117121
when: !type,
118122
message: 'What kind of service is it?',
119-
default: getDatabaseAdapter(ctx.feathers?.database),
123+
default: getDatabaseAdapter(feathers?.database),
120124
choices: [
121125
{
122126
value: 'knex',
@@ -137,7 +141,7 @@ export const generate = (ctx: ServiceGeneratorArguments) =>
137141
type: 'list',
138142
when: schema === undefined,
139143
message: 'Which schema definition format do you want to use?',
140-
default: ctx.feathers?.schema || 'json',
144+
default: feathers?.schema,
141145
choices: [
142146
{
143147
value: 'typebox',
@@ -156,7 +160,7 @@ export const generate = (ctx: ServiceGeneratorArguments) =>
156160
]
157161
)
158162
)
159-
.then(async (ctx) => {
163+
.then(async (ctx): Promise<ServiceGeneratorContext> => {
160164
const { name, path, type } = ctx
161165
const kebabName = _.kebabCase(name)
162166
const camelName = _.camelCase(name)
@@ -166,6 +170,7 @@ export const generate = (ctx: ServiceGeneratorArguments) =>
166170
const folder = path.split('/').filter((el) => el !== '')
167171
const relative = ['', ...folder].map(() => '..').join('/')
168172
const fileName = _.last(folder)
173+
const kebabPath = _.kebabCase(path)
169174

170175
return {
171176
name,
@@ -177,6 +182,7 @@ export const generate = (ctx: ServiceGeneratorArguments) =>
177182
className,
178183
kebabName,
179184
camelName,
185+
kebabPath,
180186
relative,
181187
...ctx
182188
}

packages/cli/src/service/templates/schema.json.tpl.ts

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,48 +7,50 @@ const template = ({
77
upperName,
88
relative,
99
type
10-
}: ServiceGeneratorContext) => /* ts */ `import { jsonSchema, resolve } from '@feathersjs/schema'
10+
}: ServiceGeneratorContext) => /* ts */ `import { resolve, getDataValidator, getValidator, querySyntax } from '@feathersjs/schema'
1111
import type { FromSchema } from '@feathersjs/schema'
1212
1313
import type { HookContext } from '${relative}/declarations'
1414
import { dataValidator, queryValidator } from '${relative}/schemas/validators'
1515
16-
// Schema for the basic data model (e.g. creating new entries)
17-
export const ${camelName}DataSchema = {
18-
$id: '${upperName}Data',
16+
// Main data model schema
17+
export const ${camelName}Schema = {
18+
$id: '${upperName}',
1919
type: 'object',
2020
additionalProperties: false,
21-
required: [ 'text' ],
21+
required: [ '${type === 'mongodb' ? '_id' : 'id'}', 'text' ],
2222
properties: {
23+
${type === 'mongodb' ? '_id' : 'id'}: {
24+
type: '${type === 'mongodb' ? 'string' : 'number'}'
25+
},
2326
text: {
2427
type: 'string'
2528
}
2629
}
2730
} as const
28-
export type ${upperName}Data = FromSchema<typeof ${camelName}DataSchema>
29-
export const ${camelName}DataValidator = jsonSchema.getDataValidator(${camelName}DataSchema, dataValidator)
30-
export const ${camelName}DataResolver = resolve<${upperName}Data, HookContext>({
31+
export type ${upperName} = FromSchema<typeof ${camelName}Schema>
32+
export const ${camelName}Resolver = resolve<${upperName}, HookContext>({
33+
properties: {}
34+
})
35+
export const ${camelName}ExternalResolver = resolve<${upperName}, HookContext>({
3136
properties: {}
3237
})
3338
34-
// Schema for the data that is being returned
35-
export const ${camelName}Schema = {
36-
$id: '${upperName}',
39+
// Schema for creating new data
40+
export const ${camelName}DataSchema = {
41+
$id: '${upperName}Data',
3742
type: 'object',
3843
additionalProperties: false,
39-
required: [ ...${camelName}DataSchema.required, '${type === 'mongodb' ? '_id' : 'id'}' ],
44+
required: [ 'text' ],
4045
properties: {
41-
...${camelName}DataSchema.properties,
42-
${type === 'mongodb' ? '_id' : 'id'}: {
43-
type: '${type === 'mongodb' ? 'string' : 'number'}'
46+
text: {
47+
type: 'string'
4448
}
4549
}
4650
} as const
47-
export type ${upperName} = FromSchema<typeof ${camelName}Schema>
48-
export const ${camelName}Resolver = resolve<${upperName}, HookContext>({
49-
properties: {}
50-
})
51-
export const ${camelName}ExternalResolver = resolve<${upperName}, HookContext>({
51+
export type ${upperName}Data = FromSchema<typeof ${camelName}DataSchema>
52+
export const ${camelName}DataValidator = getDataValidator(${camelName}DataSchema, dataValidator)
53+
export const ${camelName}DataResolver = resolve<${upperName}Data, HookContext>({
5254
properties: {}
5355
})
5456
@@ -58,11 +60,11 @@ export const ${camelName}QuerySchema = {
5860
type: 'object',
5961
additionalProperties: false,
6062
properties: {
61-
...jsonSchema.querySyntax(${camelName}Schema.properties)
63+
...querySyntax(${camelName}Schema.properties)
6264
}
6365
} as const
6466
export type ${upperName}Query = FromSchema<typeof ${camelName}QuerySchema>
65-
export const ${camelName}QueryValidator = jsonSchema.getValidator(${camelName}QuerySchema, queryValidator)
67+
export const ${camelName}QueryValidator = getValidator(${camelName}QuerySchema, queryValidator)
6668
export const ${camelName}QueryResolver = resolve<${upperName}Query, HookContext>({
6769
properties: {}
6870
})

0 commit comments

Comments
 (0)