Initial commit

This commit is contained in:
2025-03-07 19:22:02 +01:00
commit 4a98255d83
55743 changed files with 5280367 additions and 0 deletions
+213
View File
@@ -0,0 +1,213 @@
import { Middleware } from './compose';
import { copyToSelf, copyProperties } from './utils';
export const HOOKS: string = Symbol('@feathersjs/hooks') as any;
export type HookContextData = { [key: string]: any };
/**
* The base hook context.
*/
export class BaseHookContext<C = any> {
self?: C;
[key: string]: any;
constructor (data: HookContextData = {}) {
Object.assign(this, data);
}
}
export interface HookContext<T = any, C = any> extends BaseHookContext<C> {
result?: T;
method?: string;
arguments: any[];
}
export type HookContextConstructor = new (data?: { [key: string]: any }) => BaseHookContext;
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|null) {
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|null {
const previous = this._parent?.getProps();
if (previous && this._props) {
return copyProperties({}, previous, this._props);
}
return previous || this._props || null;
}
params (...params: string[]) {
this._params = params;
return this;
}
getParams (): string[]|null {
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|null {
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 = BaseHookContext): 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);
}
+47
View File
@@ -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|undefined = 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);
}
}
};
}
+115
View File
@@ -0,0 +1,115 @@
import { compose, Middleware } from './compose';
import {
HookContext, setManager, HookContextData, HookOptions, convertOptions, setMiddleware
} from './base';
import { copyFnProperties, copyProperties } from './utils';
export function getOriginal (fn: any): any {
return typeof fn.original === 'function' ? getOriginal(fn.original) : fn;
}
export function functionHooks <F> (fn: F, managerOrMiddleware: HookOptions) {
if (typeof fn !== 'function') {
throw new Error('Can not apply hooks to non-function');
}
const manager = convertOptions(managerOrMiddleware);
const wrapper: any = function (this: any, ...args: any[]) {
const { Context, original } = wrapper;
// If we got passed an existing HookContext instance, we want to return it as well
const returnContext = args[args.length - 1] instanceof Context;
// Use existing context or default
const base = returnContext ? (args.pop() as HookContext) : new Context();
// Initialize the context
const context = manager.initializeContext(this, args, base);
// Assemble the hook chain
const hookChain: Middleware[] = [
// Return `ctx.result` or the context
(ctx, next) => next().then(() => returnContext ? ctx : ctx.result)
];
// Create the hook chain by calling the `collectMiddleware function
const mw = manager.collectMiddleware(this, args);
if (mw) {
Array.prototype.push.apply(hookChain, mw);
}
// Runs the actual original method if `ctx.result` is not already set
hookChain.push((ctx, next) => {
if (!Object.prototype.hasOwnProperty.call(context, 'result')) {
return Promise.resolve(original.apply(this, ctx.arguments)).then(result => {
ctx.result = result;
return next();
});
}
return next();
});
return compose(hookChain).call(this, context);
};
copyFnProperties(wrapper, fn);
copyProperties(wrapper, fn);
setManager(wrapper, manager);
return Object.assign(wrapper, {
original: getOriginal(fn),
Context: manager.getContextClass(),
createContext: (data: HookContextData = {}) => {
return new wrapper.Context(data);
}
});
};
export type HookMap<O = any> = {
[L in keyof O]?: HookOptions;
}
export function objectHooks (_obj: any, hooks: HookMap|Middleware[]) {
const obj = typeof _obj === 'function' ? _obj.prototype : _obj;
if (Array.isArray(hooks)) {
return setMiddleware(obj, hooks);
}
return Object.keys(hooks).reduce((result, method) => {
const fn = obj[method];
if (typeof fn !== 'function') {
throw new Error(`Can not apply hooks. '${method}' is not a function`);
}
const manager = convertOptions(hooks[method]);
result[method] = functionHooks(fn, manager.props({ method }));
return result;
}, obj);
};
export const hookDecorator = (managerOrMiddleware?: HookOptions) => {
const wrapper: any = (_target: any, method: string, descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> => {
const manager = convertOptions(managerOrMiddleware);
if (!descriptor) {
setManager(_target.prototype, manager);
return _target;
}
const fn = descriptor.value;
if (typeof fn !== 'function') {
throw new Error(`Can not apply hooks. '${method}' is not a function`);
}
descriptor.value = functionHooks(fn, manager.props({ method }));
return descriptor;
};
return wrapper;
};
+92
View File
@@ -0,0 +1,92 @@
import { Middleware } from './compose';
import {
HookManager, HookContextData, HookContext, HookContextConstructor, HookOptions
} from './base';
import { functionHooks, hookDecorator, objectHooks, HookMap } from './hooks';
export * from './hooks';
export * from './compose';
export * from './base';
export interface WrapperAddon<F> {
original: F;
Context: HookContextConstructor;
createContext: (data?: HookContextData) => HookContext;
}
export type WrappedFunction<F, T> = F&((...rest: any[]) => Promise<T>|Promise<HookContext>)&WrapperAddon<F>;
export type MiddlewareOptions = {
params?: any;
defaults?: any;
props?: any;
};
/**
* Initializes a hook settings object with the given middleware.
* @param mw The list of middleware
* @param options Middleware options (params, default, props)
*/
export function middleware (mw?: Middleware[], options?: MiddlewareOptions) {
const manager = new HookManager().middleware(mw);
if (options) {
if (options.params) {
manager.params(...options.params);
}
if (options.defaults) {
manager.defaults(options.defaults);
}
if (options.props) {
manager.props(options.props);
}
}
return manager;
}
/**
* Returns a new function that wraps an existing async function
* with hooks.
*
* @param fn The async function to add hooks to.
* @param manager An array of middleware or hook settings
* (`middleware([]).params()` etc.)
*/
export function hooks<F, T = any> (
fn: F&(() => void),
manager?: HookManager
): WrappedFunction<F, T>;
/**
* Add hooks to one or more methods on an object or class.
* @param obj The object to add hooks to
* @param hookMap A map of middleware settings where the
* key is the method name.
*/
export function hooks<O> (obj: O|(new (...args: any[]) => O), hookMap: HookMap<O>|Middleware[]): O;
/**
* Decorate a class method with hooks.
* @param manager The hooks settings
*/
export function hooks<T = any> (
manager?: HookOptions
): any;
// Fallthrough to actual implementation
export function hooks (...args: any[]) {
const [ target, _hooks ] = args;
if (typeof target === 'function' && (_hooks instanceof HookManager || Array.isArray(_hooks) || args.length === 1)) {
return functionHooks(target, _hooks);
}
if (args.length === 2) {
return objectHooks(target, _hooks);
}
return hookDecorator(target);
}
+63
View File
@@ -0,0 +1,63 @@
const proto = Object.prototype as any;
// These are non-standard but offer a more reliable prototype based
// lookup for properties
const hasProtoDefinitions = typeof proto.__lookupGetter__ === 'function' &&
typeof proto.__defineGetter__ === 'function' &&
typeof proto.__defineSetter__ === 'function';
export function copyToSelf (target: any) {
// tslint:disable-next-line
for (const key in target) {
if (!target.hasOwnProperty(key)) {
const getter = hasProtoDefinitions ? target.constructor.prototype.__lookupGetter__(key)
: Object.getOwnPropertyDescriptor(target, key);
if (hasProtoDefinitions && getter) {
target.__defineGetter__(key, getter);
const setter = target.constructor.prototype.__lookupSetter__(key);
if (setter) {
target.__defineSetter__(key, setter);
}
} else if (getter) {
Object.defineProperty(target, key, getter);
} else {
target[key] = target[key];
}
}
}
}
export function copyProperties <F> (target: F, ...originals: any[]) {
for (const original of originals) {
const originalProps = (Object.keys(original) as any)
.concat(Object.getOwnPropertySymbols(original));
for (const prop of originalProps) {
const propDescriptor = Object.getOwnPropertyDescriptor(original, prop);
if (propDescriptor && !Object.prototype.hasOwnProperty.call(target, prop)) {
Object.defineProperty(target, prop, propDescriptor);
}
}
}
return target;
}
export function copyFnProperties <F> (target: F, original : any) {
const internalProps = ['name', 'length'];
try {
for (const prop of internalProps) {
const value = original[prop];
Object.defineProperty(target, prop, { value });
}
} catch (e) {
// Avoid IE error
}
return target;
}