Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(hooks): Add Deno tests and build, CI and fix build (#73)
- Loading branch information
Showing
14 changed files
with
621 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
name: Deno CI | ||
|
||
on: [push, pull_request] | ||
|
||
jobs: | ||
test: | ||
runs-on: "ubuntu-latest" | ||
steps: | ||
- name: Setup repo | ||
uses: actions/checkout@v2 | ||
|
||
- name: Setup Deno | ||
uses: denolib/setup-deno@v2 | ||
with: | ||
deno-version: v1.x | ||
|
||
- name: Install Node.js 14 | ||
uses: actions/setup-node@v1 | ||
with: | ||
node-version: 14 | ||
|
||
- name: Install build tooling | ||
run: npm install | ||
|
||
- name: Run Tests | ||
run: npm run test:deno |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,6 +62,5 @@ typings/ | |
|
||
# Build folders | ||
lib/ | ||
deno/ | ||
dist/ | ||
*.sqlite |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
import { Middleware } from './compose.ts'; | ||
import { copyToSelf, copyProperties } from './utils.ts'; | ||
|
||
export const HOOKS: string = Symbol('@feathersjs/hooks') as any; | ||
|
||
export type HookContextData = { [key: string]: any }; | ||
|
||
/** | ||
* The base hook context. | ||
*/ | ||
export class HookContext<T = any, C = any> { | ||
result?: T; | ||
method?: string; | ||
self: C; | ||
arguments: any[]; | ||
[key: string]: any; | ||
|
||
constructor (data: HookContextData = {}) { | ||
Object.assign(this, data); | ||
} | ||
} | ||
|
||
export type HookContextConstructor = new (data?: { [key: string]: any }) => HookContext; | ||
|
||
export type HookDefaultsInitializer = (self?: any, args?: any[], context?: HookContext) => HookContextData; | ||
|
||
export class HookManager { | ||
_parent?: this|null = null; | ||
_params: string[]|null = null; | ||
_middleware: Middleware[]|null = null; | ||
_props: HookContextData|null = null; | ||
_defaults: HookDefaultsInitializer; | ||
|
||
parent (parent: this) { | ||
this._parent = parent; | ||
|
||
return this; | ||
} | ||
|
||
middleware (middleware?: Middleware[]) { | ||
this._middleware = middleware?.length ? middleware : null; | ||
|
||
return this; | ||
} | ||
|
||
getMiddleware (): Middleware[]|null { | ||
const previous = this._parent?.getMiddleware(); | ||
|
||
if (previous && this._middleware) { | ||
return previous.concat(this._middleware); | ||
} | ||
|
||
return previous || this._middleware; | ||
} | ||
|
||
collectMiddleware (self: any, _args: any[]): Middleware[] { | ||
const otherMiddleware = getMiddleware(self); | ||
const middleware = this.getMiddleware(); | ||
|
||
if (otherMiddleware && middleware) { | ||
return otherMiddleware.concat(middleware); | ||
} | ||
|
||
return otherMiddleware || middleware; | ||
} | ||
|
||
props (props: HookContextData) { | ||
if (!this._props) { | ||
this._props = {}; | ||
} | ||
|
||
copyProperties(this._props, props); | ||
|
||
return this; | ||
} | ||
|
||
getProps (): HookContextData { | ||
const previous = this._parent?.getProps(); | ||
|
||
if (previous && this._props) { | ||
return copyProperties({}, previous, this._props); | ||
} | ||
|
||
return previous || this._props; | ||
} | ||
|
||
params (...params: string[]) { | ||
this._params = params; | ||
|
||
return this; | ||
} | ||
|
||
getParams (): string[] { | ||
const previous = this._parent?.getParams(); | ||
|
||
if (previous && this._params) { | ||
return previous.concat(this._params); | ||
} | ||
|
||
return previous || this._params; | ||
} | ||
|
||
defaults (defaults: HookDefaultsInitializer) { | ||
this._defaults = defaults; | ||
|
||
return this; | ||
} | ||
|
||
getDefaults (self: any, args: any[], context: HookContext): HookContextData { | ||
const defaults = typeof this._defaults === 'function' ? this._defaults(self, args, context) : null; | ||
const previous = this._parent?.getDefaults(self, args, context); | ||
|
||
if (previous && defaults) { | ||
return Object.assign({}, previous, defaults); | ||
} | ||
|
||
return previous || defaults; | ||
} | ||
|
||
getContextClass (Base: HookContextConstructor = HookContext): HookContextConstructor { | ||
const ContextClass = class ContextClass extends Base { | ||
constructor (data: any) { | ||
super(data); | ||
|
||
copyToSelf(this); | ||
} | ||
}; | ||
const params = this.getParams(); | ||
const props = this.getProps(); | ||
|
||
if (params) { | ||
params.forEach((name, index) => { | ||
if (props?.[name] !== undefined) { | ||
throw new Error(`Hooks can not have a property and param named '${name}'. Use .defaults instead.`); | ||
} | ||
|
||
Object.defineProperty(ContextClass.prototype, name, { | ||
enumerable: true, | ||
get () { | ||
return this?.arguments[index]; | ||
}, | ||
set (value: any) { | ||
this.arguments[index] = value; | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
if (props) { | ||
copyProperties(ContextClass.prototype, props); | ||
} | ||
|
||
return ContextClass; | ||
} | ||
|
||
initializeContext (self: any, args: any[], context: HookContext): HookContext { | ||
const ctx = this._parent ? this._parent.initializeContext(self, args, context) : context; | ||
const defaults = this.getDefaults(self, args, ctx); | ||
|
||
if (self) { | ||
ctx.self = self; | ||
} | ||
|
||
ctx.arguments = args; | ||
|
||
if (defaults) { | ||
for (const name of Object.keys(defaults)) { | ||
if (ctx[name] === undefined) { | ||
ctx[name] = defaults[name]; | ||
} | ||
} | ||
} | ||
|
||
return ctx; | ||
} | ||
} | ||
|
||
export type HookOptions = HookManager|Middleware[]|null; | ||
|
||
export function convertOptions (options: HookOptions = null) { | ||
if (!options) { | ||
return new HookManager() | ||
} | ||
|
||
return Array.isArray(options) ? new HookManager().middleware(options) : options; | ||
} | ||
|
||
export function getManager (target: any): HookManager|null { | ||
return (target && target[HOOKS]) || null; | ||
} | ||
|
||
export function setManager<T> (target: T, manager: HookManager) { | ||
const parent = getManager(target); | ||
|
||
(target as any)[HOOKS] = manager.parent(parent); | ||
|
||
return target; | ||
} | ||
|
||
export function getMiddleware (target: any): Middleware[]|null { | ||
const manager = getManager(target); | ||
|
||
return manager ? manager.getMiddleware() : null; | ||
} | ||
|
||
export function setMiddleware<T> (target: T, middleware: Middleware[]) { | ||
const manager = new HookManager().middleware(middleware); | ||
|
||
return setManager(target, manager); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// TypeScript port of koa-compose (https://github.com/koajs/compose) | ||
export type NextFunction = () => Promise<any>; | ||
|
||
export type Middleware<T = any> = (context: T, next: NextFunction) => Promise<any>; | ||
|
||
export function compose<T = any> (middleware: Middleware<T>[]) { | ||
if (!Array.isArray(middleware)) { | ||
throw new TypeError('Middleware stack must be an array!'); | ||
} | ||
|
||
for (const fn of middleware) { | ||
if (typeof fn !== 'function') { | ||
throw new TypeError('Middleware must be composed of functions!'); | ||
} | ||
} | ||
|
||
return function (this: any, context: T, next?: Middleware<T>) { | ||
// last called middleware # | ||
let index: number = -1; | ||
|
||
return dispatch.call(this, 0); | ||
|
||
function dispatch (this: any, i: number): Promise<any> { | ||
if (i <= index) { | ||
return Promise.reject(new Error('next() called multiple times')); | ||
} | ||
|
||
index = i; | ||
|
||
let fn = middleware[i]; | ||
|
||
if (i === middleware.length) { | ||
fn = next; | ||
} | ||
|
||
if (!fn) { | ||
return Promise.resolve(); | ||
} | ||
|
||
try { | ||
return Promise.resolve(fn.call(this, context, dispatch.bind(this, i + 1))); | ||
} catch (err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
}; | ||
} |
Oops, something went wrong.