Skip to content

Commit

Permalink
refactor: get rid of drivers collection and use config providers
Browse files Browse the repository at this point in the history
Config providers simplifies the way we register and lazy load drivers
A detailed post will be shared on the same soon
  • Loading branch information
thetutlage committed Oct 17, 2023
1 parent cb9b434 commit 819b5bf
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 287 deletions.
5 changes: 2 additions & 3 deletions factories/core/ignitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import { Ignitor } from '../../src/ignitor/main.js'
import { drivers } from '../../modules/hash/define_config.js'
import { defineConfig as defineHttpConfig } from '../../modules/http/main.js'
import type { ApplicationService, IgnitorOptions } from '../../src/types.js'
import { defineConfig as defineLoggerConfig } from '../../modules/logger.js'
Expand Down Expand Up @@ -93,9 +94,7 @@ export class IgnitorFactory {
hash: defineHashConfig({
default: 'scrypt',
list: {
scrypt: {
driver: 'scrypt',
},
scrypt: drivers.scrypt({}),
},
}),
logger: defineLoggerConfig({
Expand Down
10 changes: 9 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,16 @@ import { errors as httpServerErrors } from '@adonisjs/http-server'
export { stubsRoot } from './stubs/main.js'
export { inject } from './modules/container.js'
export { Ignitor } from './src/ignitor/main.js'
export { configProvider } from './src/config_provider.js'

export const errors = {
/**
* Aggregated errors from all modules.
*/
export const errors: typeof encryptionErrors &
typeof httpServerErrors &
typeof appErrors &
typeof aceErrors &
typeof envErrors = {
...encryptionErrors,
...httpServerErrors,
...appErrors,
Expand Down
113 changes: 83 additions & 30 deletions modules/hash/define_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,41 @@

import { InvalidArgumentsException } from '@poppinss/utils'

import driversCollection from './drivers_collection.js'
import type { HashDriversList } from '../../src/types.js'
import type { ManagerDriverFactory } from '../../types/hash.js'
import debug from '../../src/debug.js'
import type { Argon } from './drivers/argon.js'
import type { Scrypt } from './drivers/scrypt.js'
import type { Bcrypt } from './drivers/bcrypt.js'
import type { ConfigProvider } from '../../src/types.js'
import { configProvider } from '../../src/config_provider.js'
import type {
ArgonConfig,
BcryptConfig,
ScryptConfig,
ManagerDriverFactory,
} from '../../types/hash.js'

/**
* Resolved config from the config provider will be
* the config accepted by the hash manager
*/
type ResolvedConfig<
KnownHashers extends Record<string, ManagerDriverFactory | ConfigProvider<ManagerDriverFactory>>,
> = {
default?: keyof KnownHashers
list: {
[K in keyof KnownHashers]: KnownHashers[K] extends ConfigProvider<infer A> ? A : KnownHashers[K]
}
}

/**
* Define config for the hash service.
*/
export function defineConfig<
KnownHashers extends Record<
string,
{
[K in keyof HashDriversList]: { driver: K } & Parameters<HashDriversList[K]>[0]
}[keyof HashDriversList]
>,
KnownHashers extends Record<string, ManagerDriverFactory | ConfigProvider<ManagerDriverFactory>>,
>(config: {
default?: keyof KnownHashers
list: KnownHashers
}): {
default?: keyof KnownHashers
driversInUse: Set<keyof HashDriversList>
list: { [K in keyof KnownHashers]: ManagerDriverFactory }
} {
}): ConfigProvider<ResolvedConfig<KnownHashers>> {
/**
* Hashers list should always be provided
*/
Expand All @@ -50,22 +63,62 @@ export function defineConfig<
}

/**
* Converting list config to a collection that hash manager can use
* Config provider to lazily import drivers as they are used inside
* the user application
*/
const driversInUse: Set<keyof HashDriversList> = new Set()
const managerHashers = Object.keys(config.list).reduce(
(result, disk: keyof KnownHashers) => {
const hasherConfig = config.list[disk]
driversInUse.add(hasherConfig.driver)
result[disk] = () => driversCollection.create(hasherConfig.driver, hasherConfig)
return result
},
{} as { [K in keyof KnownHashers]: ManagerDriverFactory }
)
return configProvider.create<ResolvedConfig<KnownHashers>>(async (app) => {
debug('resolving hash config')

return {
driversInUse,
default: config.default,
list: managerHashers,
}
const hashersList = Object.keys(config.list)
const hashers = {} as Record<
string,
ManagerDriverFactory | ConfigProvider<ManagerDriverFactory>
>

for (let hasherName of hashersList) {
const hasher = config.list[hasherName]
if (typeof hasher === 'function') {
hashers[hasherName] = hasher
} else {
hashers[hasherName] = await hasher.resolver(app)
}
}

return {
default: config.default,
list: hashers as ResolvedConfig<KnownHashers>['list'],
}
})
}

/**
* Helpers to configure drivers inside the config file. The
* drivers will be imported and constructed lazily.
*
* - Import happens when you first use the hash module
* - Construction of drivers happens when you first use a driver
*/
export const drivers: {
argon2: (config: ArgonConfig) => ConfigProvider<() => Argon>
bcrypt: (config: BcryptConfig) => ConfigProvider<() => Bcrypt>
scrypt: (config: ScryptConfig) => ConfigProvider<() => Scrypt>
} = {
argon2: (config) => {
return configProvider.create(async () => {
const { Argon } = await import('./drivers/argon.js')
return () => new Argon(config)
})
},
bcrypt: (config) => {
return configProvider.create(async () => {
const { Bcrypt } = await import('./drivers/bcrypt.js')
return () => new Bcrypt(config)
})
},
scrypt: (config) => {
return configProvider.create(async () => {
const { Scrypt } = await import('./drivers/scrypt.js')
return () => new Scrypt(config)
})
},
}
61 changes: 0 additions & 61 deletions modules/hash/drivers_collection.ts

This file was deleted.

1 change: 0 additions & 1 deletion modules/hash/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@

export * from '@adonisjs/hash'
export { defineConfig } from './define_config.js'
export { default as driversList } from './drivers_collection.js'
50 changes: 17 additions & 33 deletions providers/hash_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,18 @@
* file that was distributed with this source code.
*/

import { Hash, driversList } from '../modules/hash/main.js'
import type { ApplicationService, HashDriversList } from '../src/types.js'
import { RuntimeException } from '@poppinss/utils'

import { Hash } from '../modules/hash/main.js'
import { configProvider } from '../src/config_provider.js'
import type { ApplicationService } from '../src/types.js'

/**
* Registers the passwords hasher with the container
*/
export default class HashServiceProvider {
constructor(protected app: ApplicationService) {}

/**
* Lazily registers a hash driver with the driversList collection
*/
protected async registerHashDrivers(driversInUse: Set<keyof HashDriversList>) {
if (driversInUse.has('bcrypt')) {
const { Bcrypt } = await import('../modules/hash/drivers/bcrypt.js')
driversList.extend('bcrypt', (config) => new Bcrypt(config))
}

if (driversInUse.has('scrypt')) {
const { Scrypt } = await import('../modules/hash/drivers/scrypt.js')
driversList.extend('scrypt', (config) => new Scrypt(config))
}

if (driversInUse.has('argon2')) {
const { Argon } = await import('../modules/hash/drivers/argon.js')
driversList.extend('argon2', (config) => new Argon(config))
}
}

/**
* Registering the hash class to resolve an instance with the
* default hasher.
Expand All @@ -52,7 +35,18 @@ export default class HashServiceProvider {
*/
protected registerHashManager() {
this.app.container.singleton('hash', async () => {
const config = this.app.config.get<any>('hash')
const hashConfigProvider = this.app.config.get('hash')

/**
* Resolve config from the provider
*/
const config = await configProvider.resolve<any>(this.app, hashConfigProvider)
if (!config) {
throw new RuntimeException(
'Invalid "config/hash.ts" file. Make sure you using the "defineConfig" method'
)
}

const { HashManager } = await import('../modules/hash/main.js')
return new HashManager(config)
})
Expand All @@ -65,14 +59,4 @@ export default class HashServiceProvider {
this.registerHashManager()
this.registerHash()
}

/**
* Register drivers based upon hash config
*/
boot() {
this.app.container.resolving('hash', async () => {
const config = this.app.config.get<any>('hash')
await this.registerHashDrivers(config.driversInUse)
})
}
}
31 changes: 31 additions & 0 deletions src/config_provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { ApplicationService, ConfigProvider } from './types.js'

/**
* Helper to create config provider and resolve config from
* them
*/
export const configProvider = {
create<T>(resolver: ConfigProvider<T>['resolver']): ConfigProvider<T> {
return {
type: 'provider',
resolver,
}
},

async resolve<T>(app: ApplicationService, provider: unknown): Promise<T | null> {
if (provider && typeof provider === 'object' && 'type' in provider) {
return (provider as ConfigProvider<T>).resolver(app)
}

return null
},
}

0 comments on commit 819b5bf

Please sign in to comment.