Skip to content

Commit

Permalink
refactor: use session stores and get rid of drivers collection
Browse files Browse the repository at this point in the history
BREAKING CHANGE: In pursuit of using config providers, we have refactored
the configuration to use stores for storing session data
  • Loading branch information
thetutlage committed Oct 19, 2023
1 parent 3a0a592 commit 3e967ac
Show file tree
Hide file tree
Showing 36 changed files with 687 additions and 822 deletions.
25 changes: 18 additions & 7 deletions factories/session_middleware_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,26 @@
*/

import { Emitter } from '@adonisjs/core/events'
import { ApplicationService, EventsList } from '@adonisjs/core/types'
import { AppFactory } from '@adonisjs/core/factories/app'
import type { ApplicationService, EventsList } from '@adonisjs/core/types'

import { defineConfig } from '../index.js'
import { SessionConfig } from '../src/types/main.js'
import { registerSessionDriver } from '../src/helpers.js'
import SessionMiddleware from '../src/session_middleware.js'
import type { SessionConfig, SessionStoreFactory } from '../src/types.js'

/**
* Exposes the API to create an instance of the session middleware
* without additional plumbing
*/
export class SessionMiddlewareFactory {
#config: Partial<SessionConfig> = { driver: 'memory' }
#config: Partial<SessionConfig> & {
store: string
stores: Record<string, SessionStoreFactory>
} = {
store: 'memory',
stores: {},
}

#emitter?: Emitter<EventsList>

#getApp() {
Expand All @@ -35,7 +41,13 @@ export class SessionMiddlewareFactory {
/**
* Merge custom options
*/
merge(options: { config?: Partial<SessionConfig>; emitter?: Emitter<EventsList> }) {
merge(options: {
config?: Partial<SessionConfig> & {
store: string
stores: Record<string, SessionStoreFactory>
}
emitter?: Emitter<EventsList>
}) {
if (options.config) {
this.#config = options.config
}
Expand All @@ -51,8 +63,7 @@ export class SessionMiddlewareFactory {
* Creates an instance of the session middleware
*/
async create() {
const config = defineConfig(this.#config)
await registerSessionDriver(this.#getApp(), config.driver)
const config = await defineConfig(this.#config).resolver(this.#getApp())
return new SessionMiddleware(config, this.#getEmitter())
}
}
5 changes: 1 addition & 4 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
* file that was distributed with this source code.
*/

import './src/types/extended.js'

export * as errors from './src/errors.js'
export { configure } from './configure.js'
export { stubsRoot } from './stubs/main.js'
export { defineConfig } from './src/define_config.js'
export { default as driversList } from './src/drivers_collection.js'
export { defineConfig, stores } from './src/define_config.js'
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"@japa/browser-client": "^2.0.0",
"@japa/file-system": "^2.0.0",
"@japa/plugin-adonisjs": "^2.0.0-3",
"@japa/runner": "^3.0.3",
"@japa/runner": "^3.0.4",
"@japa/snapshot": "^2.0.0",
"@swc/core": "1.3.82",
"@types/node": "^20.8.7",
Expand Down
60 changes: 38 additions & 22 deletions providers/session_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,24 @@
*/

import type { Edge } from 'edge.js'
import { configProvider } from '@adonisjs/core'
import { RuntimeException } from '@poppinss/utils'
import type { ApplicationService } from '@adonisjs/core/types'

import debug from '../src/debug.js'
import { registerSessionDriver } from '../src/helpers.js'
import type { Session } from '../src/session.js'
import SessionMiddleware from '../src/session_middleware.js'

/**
* Events emitted by the session class
*/
declare module '@adonisjs/core/types' {
interface EventsList {
'session:initiated': { session: Session }
'session:committed': { session: Session }
'session:migrated': { fromSessionId: string; toSessionId: string; session: Session }
}
}

/**
* Session provider configures the session management inside an
* AdonisJS application
Expand All @@ -22,15 +34,19 @@ export default class SessionProvider {
constructor(protected app: ApplicationService) {}

/**
* Returns edge when it's installed
* Registers edge plugin when edge is installed
* in the user application.
*/
protected async getEdge(): Promise<Edge | null> {
protected async registerEdgePlugin() {
let edge: Edge | null = null
try {
const { default: edge } = await import('edge.js')
debug('Detected edge.js package. Adding session primitives to it')
return edge
} catch {
return null
const edgeExports = await import('edge.js')
edge = edgeExports.default
} catch {}

if (edge) {
const { edgePluginSession } = await import('../src/plugins/edge.js')
edge.use(edgePluginSession)
}
}

Expand All @@ -39,27 +55,27 @@ export default class SessionProvider {
*/
register() {
this.app.container.singleton(SessionMiddleware, async (resolver) => {
const config = this.app.config.get<any>('session', {})
const sessionConfigProvider = this.app.config.get('session', {})

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

const emitter = await resolver.make('emitter')
return new SessionMiddleware(config, emitter)
})
}

/**
* Registering the active driver when middleware is used
* +
* Adding edge tags (if edge is installed)
*/
async boot() {
this.app.container.resolving(SessionMiddleware, async () => {
const config = this.app.config.get<any>('session')
await registerSessionDriver(this.app, config.driver)
})

const edge = await this.getEdge()
if (edge) {
const { edgePluginSession } = await import('../src/plugins/edge.js')
edge.use(edgePluginSession)
}
await this.registerEdgePlugin()
}
}
30 changes: 15 additions & 15 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import { cuid } from '@adonisjs/core/helpers'

import debug from './debug.js'
import { Store } from './store.js'
import type { SessionData, SessionDriverContract } from './types/main.js'
import { ValuesStore } from './values_store.js'
import type { SessionData, SessionStoreContract } from './types.js'

/**
* Session client exposes the API to set session data as a client
Expand All @@ -20,17 +20,17 @@ export class SessionClient {
/**
* Data store
*/
#store = new Store({})
#valuesStore = new ValuesStore({})

/**
* Flash messages store
*/
#flashMessagesStore = new Store({})
#flashMessagesStore = new ValuesStore({})

/**
* The session driver to use for reading and writing session data
* The session store to use for reading and writing session data
*/
#driver: SessionDriverContract
#store: SessionStoreContract

/**
* Session key for setting flash messages
Expand All @@ -43,15 +43,15 @@ export class SessionClient {
*/
sessionId = cuid()

constructor(driver: SessionDriverContract) {
this.#driver = driver
constructor(store: SessionStoreContract) {
this.#store = store
}

/**
* Merge session data
*/
merge(values: SessionData) {
this.#store.merge(values)
this.#valuesStore.merge(values)
return this
}

Expand All @@ -68,12 +68,12 @@ export class SessionClient {
*/
async commit() {
if (!this.#flashMessagesStore.isEmpty) {
this.#store.set(this.flashKey, this.#flashMessagesStore.toJSON())
this.#valuesStore.set(this.flashKey, this.#flashMessagesStore.toJSON())
}

debug('committing session data during api request')
if (!this.#store.isEmpty) {
this.#driver.write(this.sessionId, this.#store.toJSON())
if (!this.#valuesStore.isEmpty) {
this.#store.write(this.sessionId, this.#valuesStore.toJSON())
}
}

Expand All @@ -82,15 +82,15 @@ export class SessionClient {
*/
async destroy(sessionId?: string) {
debug('destroying session data during api request')
this.#driver.destroy(sessionId || this.sessionId)
this.#store.destroy(sessionId || this.sessionId)
}

/**
* Loads session data from the session store
*/
async load(sessionId?: string) {
const contents = await this.#driver.read(sessionId || this.sessionId)
const store = new Store(contents)
const contents = await this.#store.read(sessionId || this.sessionId)
const store = new ValuesStore(contents)
const flashMessages = store.pull(this.flashKey, {})

return {
Expand Down
Loading

0 comments on commit 3e967ac

Please sign in to comment.