Skip to content

Commit

Permalink
feat(cli): Add typed client to a generated app (#2669)
Browse files Browse the repository at this point in the history
  • Loading branch information
daffl authored Jun 20, 2022
1 parent 24e4bc0 commit 5b801b5
Show file tree
Hide file tree
Showing 15 changed files with 117 additions and 50 deletions.
18 changes: 7 additions & 11 deletions packages/authentication-oauth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,14 @@ export const setup = (options: OauthSetupSettings) => (app: Application) => {
}
}

const grant = defaultsDeep(
{},
omit(oauth, ['redirect', 'origins']),
{
defaults: {
prefix: '/oauth',
origin: `${protocol}://${host}`,
transport: 'session',
response: ['tokens', 'raw', 'profile']
}
const grant = defaultsDeep({}, omit(oauth, ['redirect', 'origins']), {
defaults: {
prefix: '/oauth',
origin: `${protocol}://${host}`,
transport: 'session',
response: ['tokens', 'raw', 'profile']
}
)
})

const getUrl = (url: string) => {
const { defaults } = grant
Expand Down
27 changes: 27 additions & 0 deletions packages/cli/src/app/templates/client.tpl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { generator, toFile } from '@feathershq/pinion'
import { renderSource } from '../../commons'
import { AppGeneratorContext } from '../index'

const template = ({}: AppGeneratorContext) =>
`import { feathers, type Service, type TransportConnection } from '@feathersjs/feathers'
// A mapping of client side services
export interface ServiceTypes {
}
export const createClient = <Configuration = any> (connection: TransportConnection<ServiceTypes>) => {
const client = feathers<ServiceTypes, Configuration>()
client.configure(connection)
return client
}
`

export const generate = async (ctx: AppGeneratorContext) =>
generator(ctx).then(
renderSource(
template,
toFile<AppGeneratorContext>(({ lib }) => lib, 'client')
)
)
5 changes: 3 additions & 2 deletions packages/cli/src/app/templates/package.json.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ const jsPackageJson = (lib: string) => ({
const tsPackageJson = (lib: string) => ({
scripts: {
dev: `nodemon -x ts-node ${lib}/index.ts`,
compile: 'shx rm -rf lib/ && tsc',
start: 'npm run compile && node lib/',
compile: 'shx rm -rf dist/ && tsc',
start: 'npm run compile && node dist/',
test: 'mocha test/ --require ts-node/register --recursive --extension .ts --exit'
}
})
Expand Down Expand Up @@ -54,6 +54,7 @@ const packageJson = ({
test
},
main: `${lib}/`,
browser: language === 'ts' ? 'dist/client' : `${lib}/client`,
...(language === 'ts' ? tsPackageJson(lib) : jsPackageJson(lib))
})

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/app/templates/tsconfig.json.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const generate = (ctx: AppGeneratorContext) =>
compilerOptions: {
target: 'es2020',
module: 'commonjs',
outDir: './lib',
outDir: './dist',
rootDir: `./${lib}`,
strict: true,
esModuleInterop: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { generator, inject, before, toFile, when, append } from '@feathershq/pin
import { AuthenticationGeneratorContext } from '../index'

const importTemplate = ({ upperName, schemaPath }: AuthenticationGeneratorContext) =>
`import { ${upperName}Result } from './schemas/${schemaPath}'
`import { ${upperName}Result } from './${schemaPath}'
`

const paramsTemplate = ({
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/authentication/templates/user.schema.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const ${camelName}ResultSchema = schema({
$id: '${upperName}Result',
type: 'object',
additionalProperties: false,
required: [ ...${camelName}DataSchema.required, '${type === 'mongodb' ? '_id' : 'id'}' ],
required: [ '${type === 'mongodb' ? '_id' : 'id'}' ],
properties: {
...${camelName}DataSchema.properties,
${type === 'mongodb' ? '_id' : 'id'}: {
Expand All @@ -62,14 +62,16 @@ export const ${camelName}ResultSchema = schema({
export type ${upperName}Result = Infer<typeof ${camelName}ResultSchema>
// Queries shouldn't allow doing anything with the password
const { password, ...${camelName}QueryProperties } = ${camelName}ResultSchema.properties
// Schema for allowed query properties
export const ${camelName}QuerySchema = schema({
$id: '${upperName}Query',
type: 'object',
additionalProperties: false,
properties: {
...querySyntax(${camelName}ResultSchema.properties)
...querySyntax(${camelName}QueryProperties)
}
} as const)
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/commons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const initializeBaseContext =
...ctx,
lib: ctx.pkg?.directories?.lib || 'src',
test: ctx.pkg?.directories?.test || 'test',
language: ctx.pkg?.feathers?.language || 'ts',
feathers: ctx.pkg?.feathers
}))

Expand Down
27 changes: 27 additions & 0 deletions packages/cli/src/service/templates/client.tpl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { generator, inject, toFile, when, after } from '@feathershq/pinion'
import { ServiceGeneratorContext } from '../index'

const schemaImports = ({ upperName, schemaPath }: ServiceGeneratorContext) => `import type {
${upperName}Data,
${upperName}Result,
${upperName}Query,
} from './${schemaPath}'`
const declarationTemplate = ({ path, upperName }: ServiceGeneratorContext) =>
` '${path}': Service<${upperName}Data, ${upperName}Result, Params<${upperName}Query>>`

const toClientFile = toFile<ServiceGeneratorContext>(({ lib, language }) => [lib, `client.${language}`])

export const generate = async (ctx: ServiceGeneratorContext) =>
generator(ctx)
.then(
when(
(ctx) => ctx.language === 'ts',
inject(schemaImports, after("from '@feathersjs/feathers'"), toClientFile)
)
)
.then(
when(
(ctx) => ctx.language === 'ts',
inject(declarationTemplate, after('export interface ServiceTypes'), toClientFile)
)
)
21 changes: 21 additions & 0 deletions packages/feathers/src/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ type OptionalPick<T, K extends PropertyKey> = Pick<T, Extract<keyof T, K>>

export type { NextFunction }

/**
* The object returned from `.find` call by standard database adapters
*/
export interface Paginated<T> {
total: number
limit: number
skip: number
data: T[]
}

/**
* Options that can be passed when registering a service via `app.use(name, service, options)`
*/
export interface ServiceOptions {
events?: string[]
methods?: string[]
Expand Down Expand Up @@ -89,6 +95,21 @@ export type CustomMethods<T extends { [key: string]: [any, any] }> = {
[K in keyof T]: (data: T[K][0], params?: Params) => Promise<T[K][1]>
}

/**
* An interface usually use by transport clients that represents a e.g. HTTP or websocket
* connection that can be configured on the application.
*/
export type TransportConnection<Services = any> = {
(app: Application<Services>): void
Service: any
service: <L extends keyof Services & string>(
name: L
) => keyof any extends keyof Services ? ServiceInterface : Services[L]
}

/**
* The interface for a custom service method. Can e.g. be used to type client side services.
*/
export type CustomMethod<T = any, R = T, P extends Params = Params> = (data: T, params?: P) => Promise<R>

export type ServiceMixin<A> = (service: FeathersService<A>, path: string, options: ServiceOptions) => void
Expand Down
37 changes: 12 additions & 25 deletions packages/rest-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Application, defaultServiceMethods } from '@feathersjs/feathers'
import { Application, TransportConnection, defaultServiceMethods } from '@feathersjs/feathers'

import { Base } from './base'
import { AxiosClient } from './axios'
Expand All @@ -13,34 +13,21 @@ const transports = {
axios: AxiosClient
}

interface HandlerResult extends Function {
/**
* initialize service
*/
(): void

/**
* Transport Service
*/
Service: any

/**
* default Service
*/
service: any
}

export type Handler = (connection: any, options?: any, Service?: any) => HandlerResult
export type Handler<ServiceTypes> = (
connection: any,
options?: any,
Service?: any
) => TransportConnection<ServiceTypes>

export interface Transport {
superagent: Handler
fetch: Handler
axios: Handler
export interface Transport<ServiceTypes> {
superagent: Handler<ServiceTypes>
fetch: Handler<ServiceTypes>
axios: Handler<ServiceTypes>
}

export type RestService<T = any, D = Partial<any>> = Base<T, D>

export default function restClient(base = '') {
export default function restClient<ServiceTypes = any>(base = '') {
const result: any = { Base }

Object.keys(transports).forEach((key) => {
Expand Down Expand Up @@ -81,7 +68,7 @@ export default function restClient(base = '') {
}
})

return result as Transport
return result as Transport<ServiceTypes>
}

if (typeof module !== 'undefined') {
Expand Down
2 changes: 1 addition & 1 deletion packages/rest-client/test/axios.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ServiceTypes } from './declarations'

describe('Axios REST connector', function () {
const url = 'http://localhost:8889'
const connection = rest(url).axios(axios)
const connection = rest<ServiceTypes>(url).axios(axios)
const app = feathers<ServiceTypes>()
.configure(connection)
.use('todos', connection.service('todos'), {
Expand Down
2 changes: 1 addition & 1 deletion packages/rest-client/test/fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ServiceTypes } from './declarations'

describe('fetch REST connector', function () {
const url = 'http://localhost:8889'
const connection = rest(url).fetch(fetch)
const connection = rest<ServiceTypes>(url).fetch(fetch)
const app = feathers<ServiceTypes>()
.configure(connection)
.use('todos', connection.service('todos'), {
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const queryProperties = <T extends { [key: string]: JSONSchema }>(definit
return result
}, {} as { [K in keyof T]: PropertyQuery<T[K]> })

export const querySyntax = <T extends { [key: string]: JSONSchema }>(definition: T) =>
export const querySyntax = <T extends { [key: string]: any }>(definition: T) =>
({
$limit: {
type: 'number',
Expand Down
13 changes: 9 additions & 4 deletions packages/socketio-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Service, SocketService } from '@feathersjs/transport-commons/client'
import { Socket } from 'socket.io-client'
import { Application, defaultEventMap, defaultServiceMethods } from '@feathersjs/feathers'
import {
Application,
TransportConnection,
defaultEventMap,
defaultServiceMethods
} from '@feathersjs/feathers'

export { SocketService }

Expand All @@ -14,7 +19,7 @@ declare module '@feathersjs/feathers/lib/declarations' {
}
}

export default function socketioClient(connection: Socket, options?: any) {
export default function socketioClient<Services = any>(connection: Socket, options?: any) {
if (!connection) {
throw new Error('Socket.io connection needs to be provided')
}
Expand All @@ -31,7 +36,7 @@ export default function socketioClient(connection: Socket, options?: any) {
return new Service(settings) as any
}

const initialize = function (app: Application) {
const initialize = function (app: Application<Services>) {
if (app.io !== undefined) {
throw new Error('Only one default client provider can be configured')
}
Expand All @@ -50,7 +55,7 @@ export default function socketioClient(connection: Socket, options?: any) {
initialize.Service = Service
initialize.service = defaultService

return initialize
return initialize as TransportConnection<Services>
}

if (typeof module !== 'undefined') {
Expand Down
2 changes: 1 addition & 1 deletion packages/socketio-client/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('@feathersjs/socketio-client', () => {
server = await createServer().listen(9988)
socket = io('http://localhost:9988')

const connection = socketio(socket)
const connection = socketio<ServiceTypes>(socket)

app.configure(connection)
app.use('todos', connection.service('todos'), {
Expand Down

0 comments on commit 5b801b5

Please # to comment.