//#region src/utils.ts function flatHooks(configHooks, hooks = {}, parentName) { for (const key in configHooks) { const subHook = configHooks[key]; const name = parentName ? `${parentName}:${key}` : key; if (typeof subHook === "object" && subHook !== null) flatHooks(subHook, hooks, name); else if (typeof subHook === "function") hooks[name] = subHook; } return hooks; } function mergeHooks(...hooks) { const finalHooks = {}; for (const hook of hooks) { const flatenHook = flatHooks(hook); for (const key in flatenHook) if (finalHooks[key]) finalHooks[key].push(flatenHook[key]); else finalHooks[key] = [flatenHook[key]]; } for (const key in finalHooks) if (finalHooks[key].length > 1) { const array = finalHooks[key]; finalHooks[key] = (...arguments_) => serial(array, (function_) => function_(...arguments_)); } else finalHooks[key] = finalHooks[key][0]; return finalHooks; } function serial(tasks, function_) { return tasks.reduce((promise, task) => promise.then(() => function_(task)), Promise.resolve()); } const createTask = /* @__PURE__ */ (() => { if (console.createTask) return console.createTask; const defaultTask = { run: (fn) => fn() }; return () => defaultTask; })(); function callHooks(hooks, args, startIndex, task) { for (let i = startIndex; i < hooks.length; i += 1) try { const result = task ? task.run(() => hooks[i](...args)) : hooks[i](...args); if (result instanceof Promise) return result.then(() => callHooks(hooks, args, i + 1, task)); } catch (error) { return Promise.reject(error); } } function serialTaskCaller(hooks, args, name) { if (hooks.length > 0) return callHooks(hooks, args, 0, createTask(name)); } function parallelTaskCaller(hooks, args, name) { if (hooks.length > 0) { const task = createTask(name); return Promise.all(hooks.map((hook) => task.run(() => hook(...args)))); } } /** @deprecated */ function serialCaller(hooks, arguments_) { return hooks.reduce((promise, hookFunction) => promise.then(() => hookFunction(...arguments_ || [])), Promise.resolve()); } /** @deprecated */ function parallelCaller(hooks, args) { return Promise.all(hooks.map((hook) => hook(...args || []))); } function callEachWith(callbacks, arg0) { for (const callback of [...callbacks]) callback(arg0); } //#endregion //#region src/hookable.ts var Hookable = class { _hooks; _before; _after; _deprecatedHooks; _deprecatedMessages; constructor() { this._hooks = {}; this._before = void 0; this._after = void 0; this._deprecatedMessages = void 0; this._deprecatedHooks = {}; this.hook = this.hook.bind(this); this.callHook = this.callHook.bind(this); this.callHookWith = this.callHookWith.bind(this); } hook(name, function_, options = {}) { if (!name || typeof function_ !== "function") return () => {}; const originalName = name; let dep; while (this._deprecatedHooks[name]) { dep = this._deprecatedHooks[name]; name = dep.to; } if (dep && !options.allowDeprecated) { let message = dep.message; if (!message) message = `${originalName} hook has been deprecated` + (dep.to ? `, please use ${dep.to}` : ""); if (!this._deprecatedMessages) this._deprecatedMessages = /* @__PURE__ */ new Set(); if (!this._deprecatedMessages.has(message)) { console.warn(message); this._deprecatedMessages.add(message); } } if (!function_.name) try { Object.defineProperty(function_, "name", { get: () => "_" + name.replace(/\W+/g, "_") + "_hook_cb", configurable: true }); } catch {} this._hooks[name] = this._hooks[name] || []; this._hooks[name].push(function_); return () => { if (function_) { this.removeHook(name, function_); function_ = void 0; } }; } hookOnce(name, function_) { let _unreg; let _function = (...arguments_) => { if (typeof _unreg === "function") _unreg(); _unreg = void 0; _function = void 0; return function_(...arguments_); }; _unreg = this.hook(name, _function); return _unreg; } removeHook(name, function_) { const hooks = this._hooks[name]; if (hooks) { const index = hooks.indexOf(function_); if (index !== -1) hooks.splice(index, 1); if (hooks.length === 0) this._hooks[name] = void 0; } } deprecateHook(name, deprecated) { this._deprecatedHooks[name] = typeof deprecated === "string" ? { to: deprecated } : deprecated; const _hooks = this._hooks[name] || []; this._hooks[name] = void 0; for (const hook of _hooks) this.hook(name, hook); } deprecateHooks(deprecatedHooks) { for (const name in deprecatedHooks) this.deprecateHook(name, deprecatedHooks[name]); } addHooks(configHooks) { const hooks = flatHooks(configHooks); const removeFns = Object.keys(hooks).map((key) => this.hook(key, hooks[key])); return () => { for (const unreg of removeFns) unreg(); removeFns.length = 0; }; } removeHooks(configHooks) { const hooks = flatHooks(configHooks); for (const key in hooks) this.removeHook(key, hooks[key]); } removeAllHooks() { this._hooks = {}; } callHook(name, ...args) { return this.callHookWith(serialTaskCaller, name, args); } callHookParallel(name, ...args) { return this.callHookWith(parallelTaskCaller, name, args); } callHookWith(caller, name, args) { const event = this._before || this._after ? { name, args, context: {} } : void 0; if (this._before) callEachWith(this._before, event); const result = caller(this._hooks[name] ? [...this._hooks[name]] : [], args, name); if (result instanceof Promise) return result.finally(() => { if (this._after && event) callEachWith(this._after, event); }); if (this._after && event) callEachWith(this._after, event); return result; } beforeEach(function_) { this._before = this._before || []; this._before.push(function_); return () => { if (this._before !== void 0) { const index = this._before.indexOf(function_); if (index !== -1) this._before.splice(index, 1); } }; } afterEach(function_) { this._after = this._after || []; this._after.push(function_); return () => { if (this._after !== void 0) { const index = this._after.indexOf(function_); if (index !== -1) this._after.splice(index, 1); } }; } }; function createHooks() { return new Hookable(); } var HookableCore = class { _hooks; constructor() { this._hooks = {}; } hook(name, fn) { if (!name || typeof fn !== "function") return () => {}; this._hooks[name] = this._hooks[name] || []; this._hooks[name].push(fn); return () => { if (fn) { this.removeHook(name, fn); fn = void 0; } }; } removeHook(name, function_) { const hooks = this._hooks[name]; if (hooks) { const index = hooks.indexOf(function_); if (index !== -1) hooks.splice(index, 1); if (hooks.length === 0) this._hooks[name] = void 0; } } callHook(name, ...args) { const hooks = this._hooks[name]; if (!hooks || hooks.length === 0) return; return callHooks(hooks, args, 0); } }; //#endregion //#region src/debugger.ts const isBrowser = typeof window !== "undefined"; /** Start debugging hook names and timing in console */ function createDebugger(hooks, _options = {}) { const options = { inspect: isBrowser, group: isBrowser, filter: () => true, ..._options }; const _filter = options.filter; const filter = typeof _filter === "string" ? (name) => name.startsWith(_filter) : _filter; const _tag = options.tag ? `[${options.tag}] ` : ""; const logPrefix = (event) => _tag + event.name + "".padEnd(event._id, "\0"); const _idCtr = {}; const unsubscribeBefore = hooks.beforeEach((event) => { if (filter !== void 0 && !filter(event.name)) return; _idCtr[event.name] = _idCtr[event.name] || 0; event._id = _idCtr[event.name]++; console.time(logPrefix(event)); }); const unsubscribeAfter = hooks.afterEach((event) => { if (filter !== void 0 && !filter(event.name)) return; if (options.group) console.groupCollapsed(event.name); if (options.inspect) console.timeLog(logPrefix(event), event.args); else console.timeEnd(logPrefix(event)); if (options.group) console.groupEnd(); _idCtr[event.name]--; }); return { close: () => { unsubscribeBefore(); unsubscribeAfter(); } }; } //#endregion export { Hookable, HookableCore, createDebugger, createHooks, flatHooks, mergeHooks, parallelCaller, serial, serialCaller };