Models unit; session model; generalize session classes/interfaces

This commit is contained in:
garrettmills
2020-07-27 09:41:04 -05:00
parent 878de025d8
commit 25a37cf1a2
27 changed files with 331 additions and 37 deletions

View File

@@ -1,6 +1,7 @@
import Session, { SessionData } from './Session.ts'
import Session from './Session.ts'
import SessionInterface, { SessionData } from './SessionInterface.ts'
export default class MemorySession extends Session {
export default class MemorySession extends Session implements SessionInterface {
private _key!: string
private _data: SessionData = {}
@@ -29,4 +30,5 @@ export default class MemorySession extends Session {
}
public async persist() {}
public async init_session(): Promise<void> {}
}

View File

@@ -0,0 +1,9 @@
import SessionFactory from './SessionFactory.ts'
import MemorySession from './MemorySession.ts'
import SessionInterface from './SessionInterface.ts'
export default class MemorySessionFactory extends SessionFactory {
produce(dependencies: any[], parameters: any[]): SessionInterface {
return new MemorySession()
}
}

View File

@@ -3,8 +3,9 @@ import {Collection} from '../../collection/Collection.ts'
import Session from './Session.ts'
import SessionManager, {InvalidSessionKeyError} from './SessionManager.ts'
import Utility from '../../service/utility/Utility.ts'
import SessionInterface from './SessionInterface.ts'
export type SessionRegistrant = { key: string, session: Session }
export type SessionRegistrant = { key: string, session: SessionInterface }
@Service()
export default class MemorySessionManager extends SessionManager {
@@ -14,7 +15,7 @@ export default class MemorySessionManager extends SessionManager {
return !!this._sessions.firstWhere('key', '=', key)
}
public async get_session(key?: string): Promise<Session> {
public async get_session(key?: string): Promise<SessionInterface> {
if ( !key ) {
const utility: Utility = this.make(Utility)
const session_key: string = key || utility.uuid()

View File

@@ -0,0 +1,8 @@
import SessionManagerFactory from "./SessionManagerFactory.ts";
import MemorySessionManager from "./MemorySessionManager.ts";
export default class MemorySessionManagerFactory extends SessionManagerFactory {
produce(dependencies: any[], parameters: any[]): any {
return new MemorySessionManager()
}
}

View File

@@ -0,0 +1,20 @@
import SessionFactory from './SessionFactory.ts'
import SessionInterface from './SessionInterface.ts'
import {Model} from '../../../../orm/src/model/Model.ts'
import {StaticClass} from '../../../../di/src/type/StaticClass.ts'
import {isInstantiable} from '../../../../di/src/type/Instantiable.ts'
export default class ModelSessionFactory extends SessionFactory {
constructor(
protected readonly ModelClass: StaticClass<SessionInterface, typeof Model>,
) {
super()
}
produce(dependencies: any[], parameters: any[]): SessionInterface {
if ( isInstantiable<SessionInterface>(this.ModelClass) )
return new this.ModelClass() as SessionInterface
else
throw new TypeError(`Session model class ${this.ModelClass} is not instantiable.`)
}
}

View File

@@ -0,0 +1,52 @@
import SessionManager, {InvalidSessionKeyError} from './SessionManager.ts'
import {Model} from '../../../../orm/src/model/Model.ts'
import SessionInterface, {isSessionInterface} from './SessionInterface.ts'
import {StaticClass} from '../../../../di/src/type/StaticClass.ts'
export default class ModelSessionManager extends SessionManager {
constructor(
protected readonly ModelClass: StaticClass<SessionInterface, typeof Model>,
) {
super()
}
public async get_session(key?: string): Promise<SessionInterface> {
const ModelClass: typeof Model = this.ModelClass as typeof Model
if ( !key ) {
const session = this.make(ModelClass)
await session.init_session()
if ( isSessionInterface(session) )
return session as SessionInterface
throw new TypeError(`Session model improperly implements the required SessionInterface.`)
}
const session = await ModelClass.find_by_key(key)
if ( !session ) throw new InvalidSessionKeyError(key)
if ( isSessionInterface(session) )
return session as SessionInterface
throw new TypeError(`Session model improperly implements the required SessionInterface.`)
}
public async has_session(key: string): Promise<boolean> {
const ModelClass: typeof Model = this.ModelClass as typeof Model
return ModelClass.select(ModelClass.qualified_key_name())
.where(ModelClass.qualified_key_name(), '=', key)
.exists()
}
public async purge(key?: string): Promise<void> {
const ModelClass: typeof Model = this.ModelClass as typeof Model
const mutable = ModelClass.delete()
if ( key ) {
mutable.where(ModelClass.qualified_key_name(), '=', key)
}
await mutable.execute()
}
}

View File

@@ -0,0 +1,17 @@
import SessionManagerFactory from './SessionManagerFactory.ts'
import ModelSessionManager from './ModelSessionManager.ts'
import {Model} from '../../../../orm/src/model/Model.ts'
import {StaticClass} from '../../../../di/src/type/StaticClass.ts'
import SessionInterface from './SessionInterface.ts'
export default class MemorySessionManagerFactory extends SessionManagerFactory {
constructor(
protected readonly ModelClass: StaticClass<SessionInterface, typeof Model>,
) {
super()
}
produce(dependencies: any[], parameters: any[]): any {
return new ModelSessionManager(this.ModelClass)
}
}

View File

@@ -1,8 +1,7 @@
import AppClass from '../../lifecycle/AppClass.ts'
import SessionInterface, {SessionData} from './SessionInterface.ts'
export type SessionData = { [key: string]: any }
export default abstract class Session extends AppClass {
export default abstract class Session extends AppClass implements SessionInterface {
public abstract get_key(): string
public abstract set_key(key: string): void
public abstract async persist(): Promise<void>
@@ -10,4 +9,5 @@ export default abstract class Session extends AppClass {
public abstract set_data(data: SessionData): void
public abstract get_attribute(key: string): any
public abstract set_attribute(key: string, value: any): void
public abstract async init_session(): Promise<void>
}

View File

@@ -3,6 +3,7 @@ import MemorySession from './MemorySession.ts'
import Session from './Session.ts'
import {DependencyRequirement} from '../../../../di/src/type/DependencyRequirement.ts'
import {Collection} from '../../collection/Collection.ts'
import SessionInterface from './SessionInterface.ts'
// TODO support configurable session backends
@@ -11,7 +12,7 @@ export default class SessionFactory extends AbstractFactory {
super({})
}
produce(dependencies: any[], parameters: any[]): any {
produce(dependencies: any[], parameters: any[]): SessionInterface {
return new MemorySession()
}

View File

@@ -0,0 +1,42 @@
import {logger} from "../../service/logging/global.ts";
export type SessionData = { [key: string]: any }
export default interface SessionInterface {
get_key(): string
set_key(key: string): void
persist(): Promise<void>
get_data(): SessionData
set_data(data: SessionData): void
get_attribute(key: string): any
set_attribute(key: string, value: any): void
init_session(): Promise<void>
}
export function isSessionInterface(what: any): what is SessionInterface {
const name_length_checks = [
{ name: 'get_key', length: 0 },
{ name: 'set_key', length: 1 },
{ name: 'persist', length: 0 },
{ name: 'get_data', length: 0 },
{ name: 'set_data', length: 1 },
{ name: 'get_attribute', length: 1 },
{ name: 'set_attribute', length: 2 },
{ name: 'init_session', length: 0 },
]
for ( const check of name_length_checks ) {
const { name, length } = check
if ( !(typeof what[name] === 'function') ) {
logger.debug(`Invalid session interface: typeof ${name} is not a function.`)
return false
}
if ( what[name].length !== length ) {
logger.debug(`Invalid session interface: method ${name} should expect ${length} arguments, ${what[name].length} actual.`)
return false
}
}
return true
}

View File

@@ -1,5 +1,5 @@
import AppClass from '../../lifecycle/AppClass.ts'
import Session from './Session.ts'
import SessionInterface from './SessionInterface.ts'
export class InvalidSessionKeyError extends Error {
constructor(key: any) {
@@ -9,7 +9,7 @@ export class InvalidSessionKeyError extends Error {
export default abstract class SessionManager extends AppClass {
public abstract async get_session(key?: string): Promise<Session>
public abstract async get_session(key?: string): Promise<SessionInterface>
public abstract async has_session(key: string): Promise<boolean>
public abstract async purge(key?: string): Promise<void>

View File

@@ -0,0 +1,47 @@
import {Model} from '../../../../orm/src/model/Model.ts'
import SessionInterface, {SessionData} from './SessionInterface.ts'
import {Field} from '../../../../orm/src/model/Field.ts'
import {Type} from '../../../../orm/src/db/types.ts'
export default class SessionModel extends Model<SessionModel> implements SessionInterface {
@Field(Type.json)
protected data?: string
public get_key(): string {
return String(this.key())
}
public set_key(key: string) {
// @ts-ignore
this[this.key_name()] = parseInt(key)
}
public async persist(): Promise<void> {
await this.save()
}
public get_data(): SessionData {
return this.data ? JSON.parse(this.data) : undefined
}
public set_data(data: SessionData) {
this.data = JSON.stringify(data)
}
public get_attribute(key: string): any {
const data = this.get_data()
if ( data ) return data[key]
}
public set_attribute(key: string, value: any) {
const data = this.get_data()
data[key] = value
this.set_data(data)
}
public async init_session(): Promise<void> {
this.data = JSON.stringify({})
await this.save()
}
}