@seedcord/core 0.1.1-next.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/HmrModuleHandler-CxGOo1LT.cjs.map +1 -1
- package/dist/HmrModuleHandler-KxRP8lqJ.mjs.map +1 -1
- package/dist/Plugin-CDAV7P9l.mjs.map +1 -1
- package/dist/Plugin-DVNh1kvX.cjs.map +1 -1
- package/dist/{Plugin-BPz0obbN.d.mts → Plugin-qRc7Ch4g.d.mts} +22 -43
- package/dist/{basic-CeSFvbaz.d.mts → basic-Dng__Ena.d.mts} +2 -2
- package/dist/{contextMenuLeaves-CC3-qObq.d.mts → contextMenuLeaves-COzDYa6c.d.mts} +4 -5
- package/dist/{cursor-Bk-W5MTq.d.mts → cursor-C77xH5QT.d.mts} +30 -52
- package/dist/{cursor-Y6ct-N_8.mjs → cursor-D207VrRo.mjs} +20 -44
- package/dist/cursor-D207VrRo.mjs.map +1 -0
- package/dist/{cursor-D2iP-CCS.cjs → cursor-DFgJ70_B.cjs} +20 -44
- package/dist/cursor-DFgJ70_B.cjs.map +1 -0
- package/dist/{index-Dm0WKN37.d.mts → index-C0k7lRDi.d.mts} +3 -14
- package/dist/index.cjs +17 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +21 -24
- package/dist/index.mjs +17 -22
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.cjs +10 -48
- package/dist/internal.index.cjs.map +1 -1
- package/dist/internal.index.d.mts +15 -62
- package/dist/internal.index.mjs +10 -48
- package/dist/internal.index.mjs.map +1 -1
- package/dist/node-internal.index.cjs +3 -76
- package/dist/node-internal.index.cjs.map +1 -1
- package/dist/node-internal.index.d.mts +4 -76
- package/dist/node-internal.index.mjs +3 -76
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/plugin.index.d.mts +1 -1
- package/dist/{publishDefault-CldnxcT2.mjs → publishDefault-BCrMG9T8.mjs} +5 -19
- package/dist/publishDefault-BCrMG9T8.mjs.map +1 -0
- package/dist/{publishDefault-CVlwYUS5.cjs → publishDefault-D52iT8mF.cjs} +5 -19
- package/dist/publishDefault-D52iT8mF.cjs.map +1 -0
- package/dist/{slashRouteLeaves-BANDtPFm.mjs → slashRouteLeaves-De1SxqnX.mjs} +4 -4
- package/dist/slashRouteLeaves-De1SxqnX.mjs.map +1 -0
- package/dist/{slashRouteLeaves-nfxOAM7Q.cjs → slashRouteLeaves-HVl6Ymr0.cjs} +4 -4
- package/dist/slashRouteLeaves-HVl6Ymr0.cjs.map +1 -0
- package/dist/{subscribers-CDbep0RH.cjs → subscribers-6x4xw39Z.cjs} +8 -26
- package/dist/subscribers-6x4xw39Z.cjs.map +1 -0
- package/dist/{subscribers-xO6DZ-70.mjs → subscribers-nEpbJj3Z.mjs} +8 -26
- package/dist/subscribers-nEpbJj3Z.mjs.map +1 -0
- package/package.json +9 -9
- package/dist/cursor-D2iP-CCS.cjs.map +0 -1
- package/dist/cursor-Y6ct-N_8.mjs.map +0 -1
- package/dist/publishDefault-CVlwYUS5.cjs.map +0 -1
- package/dist/publishDefault-CldnxcT2.mjs.map +0 -1
- package/dist/slashRouteLeaves-BANDtPFm.mjs.map +0 -1
- package/dist/slashRouteLeaves-nfxOAM7Q.cjs.map +0 -1
- package/dist/subscribers-CDbep0RH.cjs.map +0 -1
- package/dist/subscribers-xO6DZ-70.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HmrModuleHandler-CxGOo1LT.cjs","names":["existsSync","formatFilePath","resolve","sep","paint","pathToFileURL"],"sources":["../src/hmr/HmrModuleHandler.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { resolve, sep } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { paint } from '@seedcord/logger';\nimport { formatFilePath } from '@seedcord/utils';\n\nimport type { Logger } from '@seedcord/logger';\nimport type { HmrUpdateEvent } from '@seedcord/types';\n\ninterface HmrStore<THandler, TMiddleware, TArtifacts> {\n fileToHandlers: Map<string, Set<THandler>>;\n fileToMiddlewares: Map<string, Set<TMiddleware>>;\n handlerArtifacts: Map<THandler, TArtifacts>;\n}\n\n// justified: units are caller-supplied classes, name is read for log labels only\nconst displayName = (unit: unknown, fallback: string): string => (unit as { name?: string }).name ?? fallback;\n\ninterface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unknown> {\n handlersDir: string;\n middlewaresDir?: string;\n isHandler: (val: unknown) => val is THandler;\n isMiddleware?: (val: unknown) => val is TMiddleware;\n registerHandler: (handler: THandler, file: string) => void;\n registerMiddleware?: (middleware: TMiddleware, file: string) => void;\n unregisterHandler: (handler: THandler, artifacts?: TArtifacts) => void;\n unregisterMiddleware?: (middleware: TMiddleware) => void;\n /** Snapshotted per handler so a rollback re-registers with the same artifacts after the module re-imports. */\n getArtifacts?: (handler: THandler) => TArtifacts;\n logger: Logger;\n}\n\n/**\n * Reloads THandler and optional TMiddleware modules on HMR updates.\n *\n * Call `handle()` from your `HmrAware.onHmr()` for standard reload behavior, or write a fully custom `onHmr()` and skip this class.\n *\n * This is only useful during development.\n */\nexport class HmrModuleHandler<THandler, TMiddleware = void, TArtifacts = unknown> {\n private readonly store: HmrStore<THandler, TMiddleware, TArtifacts>;\n private readonly options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>;\n\n constructor(options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>) {\n // the caller's options object stays unmutated\n this.options = { ...options, logger: options.logger.inChannel('hmr') };\n\n // no import.meta.hot.data stash needed, a leaf reload never reconstructs the singleton that holds these Maps\n this.store = {\n fileToHandlers: new Map(),\n fileToMiddlewares: new Map(),\n handlerArtifacts: new Map()\n };\n }\n\n /** Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories. */\n public async handle(event: HmrUpdateEvent): Promise<void> {\n const { file, affectedModules, type } = event;\n const { logger } = this.options;\n\n if (type === 'delete' || type === 'deleteDir') {\n this.report('Unloaded', this.unload(file), [file]);\n return;\n }\n\n // a directory is never importable. Its files arrive as their own create events\n if (type === 'createDir') return;\n\n if (type === 'update' && !existsSync(file)) {\n if (this.isTracked(file)) {\n logger.debug(`Received update for non-existent file: ${formatFilePath(file)}, treating as delete`);\n this.report('Unloaded', this.unload(file), [file]);\n }\n return;\n }\n\n const rollbackEnabled = event.rollback ?? true;\n const filesToReload = affectedModules && affectedModules.length > 0 ? affectedModules : [file];\n\n const startedAt = Date.now();\n const names: string[] = [];\n const touched: string[] = [];\n\n for (const fileToReload of filesToReload) {\n if (!this.inScope(fileToReload)) continue;\n\n const reloaded = await this.reloadWithRollback(fileToReload, rollbackEnabled);\n if (reloaded.length === 0) continue;\n\n names.push(...reloaded);\n touched.push(fileToReload);\n }\n\n this.report(type === 'create' ? 'Registered' : 'Reloaded', names, touched, Date.now() - startedAt);\n }\n\n private inScope(file: string): boolean {\n const { handlersDir, middlewaresDir } = this.options;\n\n // the separator keeps a sibling dir sharing the prefix (commands-extra vs commands) out\n if (file.startsWith(resolve(process.cwd(), handlersDir) + sep)) return true;\n return middlewaresDir ? file.startsWith(resolve(process.cwd(), middlewaresDir) + sep) : false;\n }\n\n // one line per save, since one edit can reload every module that imports the file\n private report(verb: string, names: string[], files: string[], elapsedMs?: number): void {\n if (names.length === 0) return;\n\n const from = files.length === 1 ? formatFilePath(files[0] ?? '') : `${String(files.length)} files`;\n const timing = elapsedMs === undefined ? '' : ` ${paint.mute('in')} ${String(elapsedMs)}ms`;\n this.options.logger.debug(`${verb} ${paint.sky.bold(names.join(', '))} from ${paint.mute(from)}${timing}`);\n }\n\n private async reloadWithRollback(file: string, rollbackEnabled: boolean): Promise<string[]> {\n const { logger } = this.options;\n const snapshot = this.snapshotUnits(file);\n this.unload(file);\n\n const reloaded = await this.reloadFile(file);\n if (reloaded !== null) return reloaded;\n\n // a failed reload can leave a partial registration\n this.unload(file);\n if (!rollbackEnabled) return [];\n if (snapshot.handlers.length === 0 && snapshot.middlewares.length === 0) return [];\n\n // a typo keeps the last-good units live until the next good save\n this.restoreUnits(file, snapshot);\n logger.warn(`${paint.amber.bold('Rolled back')} ${paint.mute(formatFilePath(file))} to the last-good version`);\n return [];\n }\n\n /** Links the handler to its source file so a later update can unload it. */\n public trackHandler(file: string, handler: THandler): void {\n let set = this.store.fileToHandlers.get(file);\n if (!set) {\n set = new Set();\n this.store.fileToHandlers.set(file, set);\n }\n set.add(handler);\n\n if (this.options.getArtifacts) {\n const artifacts = this.options.getArtifacts(handler);\n this.store.handlerArtifacts.set(handler, artifacts);\n }\n }\n\n /** Links the middleware to its source file so a later update can unload it. */\n public trackMiddleware(file: string, middleware: TMiddleware): void {\n let set = this.store.fileToMiddlewares.get(file);\n if (!set) {\n set = new Set();\n this.store.fileToMiddlewares.set(file, set);\n }\n set.add(middleware);\n }\n\n private isTracked(file: string): boolean {\n return this.store.fileToHandlers.has(file) || this.store.fileToMiddlewares.has(file);\n }\n\n private unload(file: string): string[] {\n const names: string[] = [];\n const handlers = this.store.fileToHandlers.get(file);\n if (handlers) {\n for (const handler of handlers) {\n const artifacts = this.store.handlerArtifacts.get(handler);\n this.options.unregisterHandler(handler, artifacts);\n this.store.handlerArtifacts.delete(handler);\n names.push(displayName(handler, 'Handler'));\n }\n this.store.fileToHandlers.delete(file);\n }\n\n const middlewares = this.store.fileToMiddlewares.get(file);\n if (middlewares) {\n for (const middleware of middlewares) {\n this.options.unregisterMiddleware?.(middleware);\n names.push(displayName(middleware, 'Middleware'));\n }\n this.store.fileToMiddlewares.delete(file);\n }\n\n return names;\n }\n\n // only a failed reload returns null\n private async reloadFile(file: string): Promise<string[] | null> {\n const { logger, isHandler, isMiddleware, registerHandler, registerMiddleware } = this.options;\n\n if (!existsSync(file)) {\n logger.debug(`File does not exist, skipping reload: ${formatFilePath(file)}`);\n return null;\n }\n\n try {\n let fileUrl = pathToFileURL(file).href;\n // vitest has no real vite server managing the module graph, so bust the import cache manually.\n if (process.env.VITEST === 'true') fileUrl += `?update=${Date.now()}`;\n\n // justified: a dynamic import resolves to an untyped export map\n const imported = (await import(fileUrl)) as Record<string, unknown>;\n\n const names: string[] = [];\n for (const val of Object.values(imported)) {\n if (isHandler(val)) {\n registerHandler(val, file);\n this.trackHandler(file, val);\n names.push(displayName(val, 'Handler'));\n } else if (isMiddleware && registerMiddleware && isMiddleware(val)) {\n registerMiddleware(val, file);\n this.trackMiddleware(file, val);\n names.push(displayName(val, 'Middleware'));\n }\n }\n\n return names;\n } catch (error) {\n logger.error(`Failed to reload file: ${file}`, error);\n return null;\n }\n }\n\n // restoring re-registers the same class objects, so a live db connection survives rollback\n private snapshotUnits(file: string): { handlers: THandler[]; middlewares: TMiddleware[] } {\n return {\n handlers: [...(this.store.fileToHandlers.get(file) ?? [])],\n middlewares: [...(this.store.fileToMiddlewares.get(file) ?? [])]\n };\n }\n\n private restoreUnits(file: string, snapshot: { handlers: THandler[]; middlewares: TMiddleware[] }): void {\n for (const handler of snapshot.handlers) {\n this.options.registerHandler(handler, file);\n this.trackHandler(file, handler);\n }\n\n if (this.options.registerMiddleware) {\n for (const middleware of snapshot.middlewares) {\n this.options.registerMiddleware(middleware, file);\n this.trackMiddleware(file, middleware);\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HmrModuleHandler-CxGOo1LT.cjs","names":["existsSync","formatFilePath","resolve","sep","paint","pathToFileURL"],"sources":["../src/hmr/HmrModuleHandler.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { resolve, sep } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { paint } from '@seedcord/logger';\nimport { formatFilePath } from '@seedcord/utils';\n\nimport type { Logger } from '@seedcord/logger';\nimport type { HmrUpdateEvent } from '@seedcord/types';\n\ninterface HmrStore<THandler, TMiddleware, TArtifacts> {\n fileToHandlers: Map<string, Set<THandler>>;\n fileToMiddlewares: Map<string, Set<TMiddleware>>;\n handlerArtifacts: Map<THandler, TArtifacts>;\n}\n\n// justified: units are caller-supplied classes, name is read for log labels only\nconst displayName = (unit: unknown, fallback: string): string => (unit as { name?: string }).name ?? fallback;\n\ninterface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unknown> {\n handlersDir: string;\n middlewaresDir?: string;\n isHandler: (val: unknown) => val is THandler;\n isMiddleware?: (val: unknown) => val is TMiddleware;\n registerHandler: (handler: THandler, file: string) => void;\n registerMiddleware?: (middleware: TMiddleware, file: string) => void;\n unregisterHandler: (handler: THandler, artifacts?: TArtifacts) => void;\n unregisterMiddleware?: (middleware: TMiddleware) => void;\n /** Snapshotted per handler so a rollback re-registers with the same artifacts after the module re-imports. */\n getArtifacts?: (handler: THandler) => TArtifacts;\n logger: Logger;\n}\n\n/**\n * Reloads THandler and optional TMiddleware modules on HMR updates.\n *\n * Call `handle()` from your `HmrAware.onHmr()` for standard reload behavior, or write a fully custom `onHmr()` and skip this class.\n *\n * This is only useful during development.\n */\nexport class HmrModuleHandler<THandler, TMiddleware = void, TArtifacts = unknown> {\n private readonly store: HmrStore<THandler, TMiddleware, TArtifacts>;\n private readonly options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>;\n\n constructor(options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>) {\n this.options = { ...options, logger: options.logger.inChannel('hmr') };\n\n // no import.meta.hot.data stash needed because a leaf reload never reconstructs the singleton\n // holding these Maps\n this.store = {\n fileToHandlers: new Map(),\n fileToMiddlewares: new Map(),\n handlerArtifacts: new Map()\n };\n }\n\n /** Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories. */\n public async handle(event: HmrUpdateEvent): Promise<void> {\n const { file, affectedModules, type } = event;\n const { logger } = this.options;\n\n if (type === 'delete' || type === 'deleteDir') {\n this.report('Unloaded', this.unload(file), [file]);\n return;\n }\n\n // a directory is never importable. its files arrive as their own create events\n if (type === 'createDir') return;\n\n if (type === 'update' && !existsSync(file)) {\n if (this.isTracked(file)) {\n logger.debug(`Received update for non-existent file: ${formatFilePath(file)}, treating as delete`);\n this.report('Unloaded', this.unload(file), [file]);\n }\n return;\n }\n\n const rollbackEnabled = event.rollback ?? true;\n const filesToReload = affectedModules && affectedModules.length > 0 ? affectedModules : [file];\n\n const startedAt = Date.now();\n const names: string[] = [];\n const touched: string[] = [];\n\n for (const fileToReload of filesToReload) {\n if (!this.inScope(fileToReload)) continue;\n\n const reloaded = await this.reloadWithRollback(fileToReload, rollbackEnabled);\n if (reloaded.length === 0) continue;\n\n names.push(...reloaded);\n touched.push(fileToReload);\n }\n\n this.report(type === 'create' ? 'Registered' : 'Reloaded', names, touched, Date.now() - startedAt);\n }\n\n private inScope(file: string): boolean {\n const { handlersDir, middlewaresDir } = this.options;\n\n // the trailing separator keeps commands-extra out of commands\n if (file.startsWith(resolve(process.cwd(), handlersDir) + sep)) return true;\n return middlewaresDir ? file.startsWith(resolve(process.cwd(), middlewaresDir) + sep) : false;\n }\n\n // one line per save, since one edit can reload every module that imports the file\n private report(verb: string, names: string[], files: string[], elapsedMs?: number): void {\n if (names.length === 0) return;\n\n const from = files.length === 1 ? formatFilePath(files[0] ?? '') : `${String(files.length)} files`;\n const timing = elapsedMs === undefined ? '' : ` ${paint.mute('in')} ${String(elapsedMs)}ms`;\n this.options.logger.debug(`${verb} ${paint.sky.bold(names.join(', '))} from ${paint.mute(from)}${timing}`);\n }\n\n private async reloadWithRollback(file: string, rollbackEnabled: boolean): Promise<string[]> {\n const { logger } = this.options;\n const snapshot = this.snapshotUnits(file);\n this.unload(file);\n\n const reloaded = await this.reloadFile(file);\n if (reloaded !== null) return reloaded;\n\n // a failed reload can leave a partial registration\n this.unload(file);\n if (!rollbackEnabled) return [];\n if (snapshot.handlers.length === 0 && snapshot.middlewares.length === 0) return [];\n\n // a typo keeps the last-good units live until the next good save\n this.restoreUnits(file, snapshot);\n logger.warn(`${paint.amber.bold('Rolled back')} ${paint.mute(formatFilePath(file))} to the last-good version`);\n return [];\n }\n\n /** Links the handler to its source file so a later update can unload it. */\n public trackHandler(file: string, handler: THandler): void {\n let set = this.store.fileToHandlers.get(file);\n if (!set) {\n set = new Set();\n this.store.fileToHandlers.set(file, set);\n }\n set.add(handler);\n\n if (this.options.getArtifacts) {\n const artifacts = this.options.getArtifacts(handler);\n this.store.handlerArtifacts.set(handler, artifacts);\n }\n }\n\n /** Links the middleware to its source file so a later update can unload it. */\n public trackMiddleware(file: string, middleware: TMiddleware): void {\n let set = this.store.fileToMiddlewares.get(file);\n if (!set) {\n set = new Set();\n this.store.fileToMiddlewares.set(file, set);\n }\n set.add(middleware);\n }\n\n private isTracked(file: string): boolean {\n return this.store.fileToHandlers.has(file) || this.store.fileToMiddlewares.has(file);\n }\n\n private unload(file: string): string[] {\n const names: string[] = [];\n const handlers = this.store.fileToHandlers.get(file);\n if (handlers) {\n for (const handler of handlers) {\n const artifacts = this.store.handlerArtifacts.get(handler);\n this.options.unregisterHandler(handler, artifacts);\n this.store.handlerArtifacts.delete(handler);\n names.push(displayName(handler, 'Handler'));\n }\n this.store.fileToHandlers.delete(file);\n }\n\n const middlewares = this.store.fileToMiddlewares.get(file);\n if (middlewares) {\n for (const middleware of middlewares) {\n this.options.unregisterMiddleware?.(middleware);\n names.push(displayName(middleware, 'Middleware'));\n }\n this.store.fileToMiddlewares.delete(file);\n }\n\n return names;\n }\n\n private async reloadFile(file: string): Promise<string[] | null> {\n const { logger, isHandler, isMiddleware, registerHandler, registerMiddleware } = this.options;\n\n if (!existsSync(file)) {\n logger.debug(`File does not exist, skipping reload: ${formatFilePath(file)}`);\n return null;\n }\n\n try {\n let fileUrl = pathToFileURL(file).href;\n // vitest has no real vite server managing the module graph\n if (process.env.VITEST === 'true') fileUrl += `?update=${Date.now()}`;\n\n // justified: a dynamic import resolves to an untyped export map\n const imported = (await import(fileUrl)) as Record<string, unknown>;\n\n const names: string[] = [];\n for (const val of Object.values(imported)) {\n if (isHandler(val)) {\n registerHandler(val, file);\n this.trackHandler(file, val);\n names.push(displayName(val, 'Handler'));\n } else if (isMiddleware && registerMiddleware && isMiddleware(val)) {\n registerMiddleware(val, file);\n this.trackMiddleware(file, val);\n names.push(displayName(val, 'Middleware'));\n }\n }\n\n return names;\n } catch (error) {\n logger.error(`Failed to reload file: ${file}`, error);\n return null;\n }\n }\n\n // these are the same class objects, which is how a live db connection survives a rollback\n private snapshotUnits(file: string): { handlers: THandler[]; middlewares: TMiddleware[] } {\n return {\n handlers: [...(this.store.fileToHandlers.get(file) ?? [])],\n middlewares: [...(this.store.fileToMiddlewares.get(file) ?? [])]\n };\n }\n\n private restoreUnits(file: string, snapshot: { handlers: THandler[]; middlewares: TMiddleware[] }): void {\n for (const handler of snapshot.handlers) {\n this.options.registerHandler(handler, file);\n this.trackHandler(file, handler);\n }\n\n if (this.options.registerMiddleware) {\n for (const middleware of snapshot.middlewares) {\n this.options.registerMiddleware(middleware, file);\n this.trackMiddleware(file, middleware);\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HmrModuleHandler-KxRP8lqJ.mjs","names":[],"sources":["../src/hmr/HmrModuleHandler.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { resolve, sep } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { paint } from '@seedcord/logger';\nimport { formatFilePath } from '@seedcord/utils';\n\nimport type { Logger } from '@seedcord/logger';\nimport type { HmrUpdateEvent } from '@seedcord/types';\n\ninterface HmrStore<THandler, TMiddleware, TArtifacts> {\n fileToHandlers: Map<string, Set<THandler>>;\n fileToMiddlewares: Map<string, Set<TMiddleware>>;\n handlerArtifacts: Map<THandler, TArtifacts>;\n}\n\n// justified: units are caller-supplied classes, name is read for log labels only\nconst displayName = (unit: unknown, fallback: string): string => (unit as { name?: string }).name ?? fallback;\n\ninterface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unknown> {\n handlersDir: string;\n middlewaresDir?: string;\n isHandler: (val: unknown) => val is THandler;\n isMiddleware?: (val: unknown) => val is TMiddleware;\n registerHandler: (handler: THandler, file: string) => void;\n registerMiddleware?: (middleware: TMiddleware, file: string) => void;\n unregisterHandler: (handler: THandler, artifacts?: TArtifacts) => void;\n unregisterMiddleware?: (middleware: TMiddleware) => void;\n /** Snapshotted per handler so a rollback re-registers with the same artifacts after the module re-imports. */\n getArtifacts?: (handler: THandler) => TArtifacts;\n logger: Logger;\n}\n\n/**\n * Reloads THandler and optional TMiddleware modules on HMR updates.\n *\n * Call `handle()` from your `HmrAware.onHmr()` for standard reload behavior, or write a fully custom `onHmr()` and skip this class.\n *\n * This is only useful during development.\n */\nexport class HmrModuleHandler<THandler, TMiddleware = void, TArtifacts = unknown> {\n private readonly store: HmrStore<THandler, TMiddleware, TArtifacts>;\n private readonly options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>;\n\n constructor(options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>) {\n // the caller's options object stays unmutated\n this.options = { ...options, logger: options.logger.inChannel('hmr') };\n\n // no import.meta.hot.data stash needed, a leaf reload never reconstructs the singleton that holds these Maps\n this.store = {\n fileToHandlers: new Map(),\n fileToMiddlewares: new Map(),\n handlerArtifacts: new Map()\n };\n }\n\n /** Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories. */\n public async handle(event: HmrUpdateEvent): Promise<void> {\n const { file, affectedModules, type } = event;\n const { logger } = this.options;\n\n if (type === 'delete' || type === 'deleteDir') {\n this.report('Unloaded', this.unload(file), [file]);\n return;\n }\n\n // a directory is never importable. Its files arrive as their own create events\n if (type === 'createDir') return;\n\n if (type === 'update' && !existsSync(file)) {\n if (this.isTracked(file)) {\n logger.debug(`Received update for non-existent file: ${formatFilePath(file)}, treating as delete`);\n this.report('Unloaded', this.unload(file), [file]);\n }\n return;\n }\n\n const rollbackEnabled = event.rollback ?? true;\n const filesToReload = affectedModules && affectedModules.length > 0 ? affectedModules : [file];\n\n const startedAt = Date.now();\n const names: string[] = [];\n const touched: string[] = [];\n\n for (const fileToReload of filesToReload) {\n if (!this.inScope(fileToReload)) continue;\n\n const reloaded = await this.reloadWithRollback(fileToReload, rollbackEnabled);\n if (reloaded.length === 0) continue;\n\n names.push(...reloaded);\n touched.push(fileToReload);\n }\n\n this.report(type === 'create' ? 'Registered' : 'Reloaded', names, touched, Date.now() - startedAt);\n }\n\n private inScope(file: string): boolean {\n const { handlersDir, middlewaresDir } = this.options;\n\n // the separator keeps a sibling dir sharing the prefix (commands-extra vs commands) out\n if (file.startsWith(resolve(process.cwd(), handlersDir) + sep)) return true;\n return middlewaresDir ? file.startsWith(resolve(process.cwd(), middlewaresDir) + sep) : false;\n }\n\n // one line per save, since one edit can reload every module that imports the file\n private report(verb: string, names: string[], files: string[], elapsedMs?: number): void {\n if (names.length === 0) return;\n\n const from = files.length === 1 ? formatFilePath(files[0] ?? '') : `${String(files.length)} files`;\n const timing = elapsedMs === undefined ? '' : ` ${paint.mute('in')} ${String(elapsedMs)}ms`;\n this.options.logger.debug(`${verb} ${paint.sky.bold(names.join(', '))} from ${paint.mute(from)}${timing}`);\n }\n\n private async reloadWithRollback(file: string, rollbackEnabled: boolean): Promise<string[]> {\n const { logger } = this.options;\n const snapshot = this.snapshotUnits(file);\n this.unload(file);\n\n const reloaded = await this.reloadFile(file);\n if (reloaded !== null) return reloaded;\n\n // a failed reload can leave a partial registration\n this.unload(file);\n if (!rollbackEnabled) return [];\n if (snapshot.handlers.length === 0 && snapshot.middlewares.length === 0) return [];\n\n // a typo keeps the last-good units live until the next good save\n this.restoreUnits(file, snapshot);\n logger.warn(`${paint.amber.bold('Rolled back')} ${paint.mute(formatFilePath(file))} to the last-good version`);\n return [];\n }\n\n /** Links the handler to its source file so a later update can unload it. */\n public trackHandler(file: string, handler: THandler): void {\n let set = this.store.fileToHandlers.get(file);\n if (!set) {\n set = new Set();\n this.store.fileToHandlers.set(file, set);\n }\n set.add(handler);\n\n if (this.options.getArtifacts) {\n const artifacts = this.options.getArtifacts(handler);\n this.store.handlerArtifacts.set(handler, artifacts);\n }\n }\n\n /** Links the middleware to its source file so a later update can unload it. */\n public trackMiddleware(file: string, middleware: TMiddleware): void {\n let set = this.store.fileToMiddlewares.get(file);\n if (!set) {\n set = new Set();\n this.store.fileToMiddlewares.set(file, set);\n }\n set.add(middleware);\n }\n\n private isTracked(file: string): boolean {\n return this.store.fileToHandlers.has(file) || this.store.fileToMiddlewares.has(file);\n }\n\n private unload(file: string): string[] {\n const names: string[] = [];\n const handlers = this.store.fileToHandlers.get(file);\n if (handlers) {\n for (const handler of handlers) {\n const artifacts = this.store.handlerArtifacts.get(handler);\n this.options.unregisterHandler(handler, artifacts);\n this.store.handlerArtifacts.delete(handler);\n names.push(displayName(handler, 'Handler'));\n }\n this.store.fileToHandlers.delete(file);\n }\n\n const middlewares = this.store.fileToMiddlewares.get(file);\n if (middlewares) {\n for (const middleware of middlewares) {\n this.options.unregisterMiddleware?.(middleware);\n names.push(displayName(middleware, 'Middleware'));\n }\n this.store.fileToMiddlewares.delete(file);\n }\n\n return names;\n }\n\n // only a failed reload returns null\n private async reloadFile(file: string): Promise<string[] | null> {\n const { logger, isHandler, isMiddleware, registerHandler, registerMiddleware } = this.options;\n\n if (!existsSync(file)) {\n logger.debug(`File does not exist, skipping reload: ${formatFilePath(file)}`);\n return null;\n }\n\n try {\n let fileUrl = pathToFileURL(file).href;\n // vitest has no real vite server managing the module graph, so bust the import cache manually.\n if (process.env.VITEST === 'true') fileUrl += `?update=${Date.now()}`;\n\n // justified: a dynamic import resolves to an untyped export map\n const imported = (await import(fileUrl)) as Record<string, unknown>;\n\n const names: string[] = [];\n for (const val of Object.values(imported)) {\n if (isHandler(val)) {\n registerHandler(val, file);\n this.trackHandler(file, val);\n names.push(displayName(val, 'Handler'));\n } else if (isMiddleware && registerMiddleware && isMiddleware(val)) {\n registerMiddleware(val, file);\n this.trackMiddleware(file, val);\n names.push(displayName(val, 'Middleware'));\n }\n }\n\n return names;\n } catch (error) {\n logger.error(`Failed to reload file: ${file}`, error);\n return null;\n }\n }\n\n // restoring re-registers the same class objects, so a live db connection survives rollback\n private snapshotUnits(file: string): { handlers: THandler[]; middlewares: TMiddleware[] } {\n return {\n handlers: [...(this.store.fileToHandlers.get(file) ?? [])],\n middlewares: [...(this.store.fileToMiddlewares.get(file) ?? [])]\n };\n }\n\n private restoreUnits(file: string, snapshot: { handlers: THandler[]; middlewares: TMiddleware[] }): void {\n for (const handler of snapshot.handlers) {\n this.options.registerHandler(handler, file);\n this.trackHandler(file, handler);\n }\n\n if (this.options.registerMiddleware) {\n for (const middleware of snapshot.middlewares) {\n this.options.registerMiddleware(middleware, file);\n this.trackMiddleware(file, middleware);\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HmrModuleHandler-KxRP8lqJ.mjs","names":[],"sources":["../src/hmr/HmrModuleHandler.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { resolve, sep } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { paint } from '@seedcord/logger';\nimport { formatFilePath } from '@seedcord/utils';\n\nimport type { Logger } from '@seedcord/logger';\nimport type { HmrUpdateEvent } from '@seedcord/types';\n\ninterface HmrStore<THandler, TMiddleware, TArtifacts> {\n fileToHandlers: Map<string, Set<THandler>>;\n fileToMiddlewares: Map<string, Set<TMiddleware>>;\n handlerArtifacts: Map<THandler, TArtifacts>;\n}\n\n// justified: units are caller-supplied classes, name is read for log labels only\nconst displayName = (unit: unknown, fallback: string): string => (unit as { name?: string }).name ?? fallback;\n\ninterface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unknown> {\n handlersDir: string;\n middlewaresDir?: string;\n isHandler: (val: unknown) => val is THandler;\n isMiddleware?: (val: unknown) => val is TMiddleware;\n registerHandler: (handler: THandler, file: string) => void;\n registerMiddleware?: (middleware: TMiddleware, file: string) => void;\n unregisterHandler: (handler: THandler, artifacts?: TArtifacts) => void;\n unregisterMiddleware?: (middleware: TMiddleware) => void;\n /** Snapshotted per handler so a rollback re-registers with the same artifacts after the module re-imports. */\n getArtifacts?: (handler: THandler) => TArtifacts;\n logger: Logger;\n}\n\n/**\n * Reloads THandler and optional TMiddleware modules on HMR updates.\n *\n * Call `handle()` from your `HmrAware.onHmr()` for standard reload behavior, or write a fully custom `onHmr()` and skip this class.\n *\n * This is only useful during development.\n */\nexport class HmrModuleHandler<THandler, TMiddleware = void, TArtifacts = unknown> {\n private readonly store: HmrStore<THandler, TMiddleware, TArtifacts>;\n private readonly options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>;\n\n constructor(options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>) {\n this.options = { ...options, logger: options.logger.inChannel('hmr') };\n\n // no import.meta.hot.data stash needed because a leaf reload never reconstructs the singleton\n // holding these Maps\n this.store = {\n fileToHandlers: new Map(),\n fileToMiddlewares: new Map(),\n handlerArtifacts: new Map()\n };\n }\n\n /** Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories. */\n public async handle(event: HmrUpdateEvent): Promise<void> {\n const { file, affectedModules, type } = event;\n const { logger } = this.options;\n\n if (type === 'delete' || type === 'deleteDir') {\n this.report('Unloaded', this.unload(file), [file]);\n return;\n }\n\n // a directory is never importable. its files arrive as their own create events\n if (type === 'createDir') return;\n\n if (type === 'update' && !existsSync(file)) {\n if (this.isTracked(file)) {\n logger.debug(`Received update for non-existent file: ${formatFilePath(file)}, treating as delete`);\n this.report('Unloaded', this.unload(file), [file]);\n }\n return;\n }\n\n const rollbackEnabled = event.rollback ?? true;\n const filesToReload = affectedModules && affectedModules.length > 0 ? affectedModules : [file];\n\n const startedAt = Date.now();\n const names: string[] = [];\n const touched: string[] = [];\n\n for (const fileToReload of filesToReload) {\n if (!this.inScope(fileToReload)) continue;\n\n const reloaded = await this.reloadWithRollback(fileToReload, rollbackEnabled);\n if (reloaded.length === 0) continue;\n\n names.push(...reloaded);\n touched.push(fileToReload);\n }\n\n this.report(type === 'create' ? 'Registered' : 'Reloaded', names, touched, Date.now() - startedAt);\n }\n\n private inScope(file: string): boolean {\n const { handlersDir, middlewaresDir } = this.options;\n\n // the trailing separator keeps commands-extra out of commands\n if (file.startsWith(resolve(process.cwd(), handlersDir) + sep)) return true;\n return middlewaresDir ? file.startsWith(resolve(process.cwd(), middlewaresDir) + sep) : false;\n }\n\n // one line per save, since one edit can reload every module that imports the file\n private report(verb: string, names: string[], files: string[], elapsedMs?: number): void {\n if (names.length === 0) return;\n\n const from = files.length === 1 ? formatFilePath(files[0] ?? '') : `${String(files.length)} files`;\n const timing = elapsedMs === undefined ? '' : ` ${paint.mute('in')} ${String(elapsedMs)}ms`;\n this.options.logger.debug(`${verb} ${paint.sky.bold(names.join(', '))} from ${paint.mute(from)}${timing}`);\n }\n\n private async reloadWithRollback(file: string, rollbackEnabled: boolean): Promise<string[]> {\n const { logger } = this.options;\n const snapshot = this.snapshotUnits(file);\n this.unload(file);\n\n const reloaded = await this.reloadFile(file);\n if (reloaded !== null) return reloaded;\n\n // a failed reload can leave a partial registration\n this.unload(file);\n if (!rollbackEnabled) return [];\n if (snapshot.handlers.length === 0 && snapshot.middlewares.length === 0) return [];\n\n // a typo keeps the last-good units live until the next good save\n this.restoreUnits(file, snapshot);\n logger.warn(`${paint.amber.bold('Rolled back')} ${paint.mute(formatFilePath(file))} to the last-good version`);\n return [];\n }\n\n /** Links the handler to its source file so a later update can unload it. */\n public trackHandler(file: string, handler: THandler): void {\n let set = this.store.fileToHandlers.get(file);\n if (!set) {\n set = new Set();\n this.store.fileToHandlers.set(file, set);\n }\n set.add(handler);\n\n if (this.options.getArtifacts) {\n const artifacts = this.options.getArtifacts(handler);\n this.store.handlerArtifacts.set(handler, artifacts);\n }\n }\n\n /** Links the middleware to its source file so a later update can unload it. */\n public trackMiddleware(file: string, middleware: TMiddleware): void {\n let set = this.store.fileToMiddlewares.get(file);\n if (!set) {\n set = new Set();\n this.store.fileToMiddlewares.set(file, set);\n }\n set.add(middleware);\n }\n\n private isTracked(file: string): boolean {\n return this.store.fileToHandlers.has(file) || this.store.fileToMiddlewares.has(file);\n }\n\n private unload(file: string): string[] {\n const names: string[] = [];\n const handlers = this.store.fileToHandlers.get(file);\n if (handlers) {\n for (const handler of handlers) {\n const artifacts = this.store.handlerArtifacts.get(handler);\n this.options.unregisterHandler(handler, artifacts);\n this.store.handlerArtifacts.delete(handler);\n names.push(displayName(handler, 'Handler'));\n }\n this.store.fileToHandlers.delete(file);\n }\n\n const middlewares = this.store.fileToMiddlewares.get(file);\n if (middlewares) {\n for (const middleware of middlewares) {\n this.options.unregisterMiddleware?.(middleware);\n names.push(displayName(middleware, 'Middleware'));\n }\n this.store.fileToMiddlewares.delete(file);\n }\n\n return names;\n }\n\n private async reloadFile(file: string): Promise<string[] | null> {\n const { logger, isHandler, isMiddleware, registerHandler, registerMiddleware } = this.options;\n\n if (!existsSync(file)) {\n logger.debug(`File does not exist, skipping reload: ${formatFilePath(file)}`);\n return null;\n }\n\n try {\n let fileUrl = pathToFileURL(file).href;\n // vitest has no real vite server managing the module graph\n if (process.env.VITEST === 'true') fileUrl += `?update=${Date.now()}`;\n\n // justified: a dynamic import resolves to an untyped export map\n const imported = (await import(fileUrl)) as Record<string, unknown>;\n\n const names: string[] = [];\n for (const val of Object.values(imported)) {\n if (isHandler(val)) {\n registerHandler(val, file);\n this.trackHandler(file, val);\n names.push(displayName(val, 'Handler'));\n } else if (isMiddleware && registerMiddleware && isMiddleware(val)) {\n registerMiddleware(val, file);\n this.trackMiddleware(file, val);\n names.push(displayName(val, 'Middleware'));\n }\n }\n\n return names;\n } catch (error) {\n logger.error(`Failed to reload file: ${file}`, error);\n return null;\n }\n }\n\n // these are the same class objects, which is how a live db connection survives a rollback\n private snapshotUnits(file: string): { handlers: THandler[]; middlewares: TMiddleware[] } {\n return {\n handlers: [...(this.store.fileToHandlers.get(file) ?? [])],\n middlewares: [...(this.store.fileToMiddlewares.get(file) ?? [])]\n };\n }\n\n private restoreUnits(file: string, snapshot: { handlers: THandler[]; middlewares: TMiddleware[] }): void {\n for (const handler of snapshot.handlers) {\n this.options.registerHandler(handler, file);\n this.trackHandler(file, handler);\n }\n\n if (this.options.registerMiddleware) {\n for (const middleware of snapshot.middlewares) {\n this.options.registerMiddleware(middleware, file);\n this.trackMiddleware(file, middleware);\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Plugin-CDAV7P9l.mjs","names":[],"sources":["../src/plugin/lifecycle.ts","../src/plugin/Plugin.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport { ShutdownPhase, StartupPhase } from '@src/lifecycle/phases';\n\n/**\n * The lifecycle a plugin declares through `super(host, spec)`. Unset fields resolve to the base\n * defaults. `ready` runs at a fixed `StartupPhase.Ready
|
|
1
|
+
{"version":3,"file":"Plugin-CDAV7P9l.mjs","names":[],"sources":["../src/plugin/lifecycle.ts","../src/plugin/Plugin.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport { ShutdownPhase, StartupPhase } from '@src/lifecycle/phases';\n\n/**\n * The lifecycle a plugin declares through `super(host, spec)`. Unset fields resolve to the base\n * defaults. `ready` runs at a fixed `StartupPhase.Ready`.\n */\nexport interface PluginLifecycleSpec {\n init?: { phase?: StartupPhase; timeout?: number };\n ready?: { timeout?: number };\n dispose?: { phase?: ShutdownPhase; timeout?: number };\n}\n\n/** @internal */\nexport interface ResolvedPluginLifecycleSpec {\n readonly init: { readonly phase: StartupPhase; readonly timeout: number };\n readonly ready: { readonly timeout: number };\n readonly dispose: { readonly phase: ShutdownPhase; readonly timeout: number };\n}\n\nconst DEFAULT_INIT_TIMEOUT_MS = 15_000;\nconst DEFAULT_READY_TIMEOUT_MS = 15_000;\nconst DEFAULT_DISPOSE_TIMEOUT_MS = 10_000;\n\nfunction timeoutOf(\n field: 'init' | 'ready' | 'dispose',\n declared: number | undefined,\n fallback: number,\n pluginName: string\n): number {\n if (declared === undefined) return fallback;\n if (!Number.isFinite(declared) || declared <= 0) {\n throw new SeedcordError(SeedcordErrorCode.PluginOptionsRejected, [\n pluginName,\n `lifecycle ${field}.timeout must be a positive number of milliseconds`\n ]);\n }\n return declared;\n}\n\n/** @internal */\nexport function resolveLifecycleSpec(\n spec: PluginLifecycleSpec | undefined,\n pluginName: string\n): ResolvedPluginLifecycleSpec {\n return {\n init: {\n phase: spec?.init?.phase ?? StartupPhase.Configuration,\n timeout: timeoutOf('init', spec?.init?.timeout, DEFAULT_INIT_TIMEOUT_MS, pluginName)\n },\n ready: {\n timeout: timeoutOf('ready', spec?.ready?.timeout, DEFAULT_READY_TIMEOUT_MS, pluginName)\n },\n dispose: {\n phase: spec?.dispose?.phase ?? ShutdownPhase.Disconnect,\n timeout: timeoutOf('dispose', spec?.dispose?.timeout, DEFAULT_DISPOSE_TIMEOUT_MS, pluginName)\n }\n };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\n\nimport { getDevChannel } from '@hmr/devChannel';\n\nimport { resolveLifecycleSpec } from './lifecycle';\n\nimport type { ResolvedPluginLifecycleSpec, PluginLifecycleSpec } from './lifecycle';\nimport type { TransportOf, PluginOptions, RuntimeOf } from './options';\nimport type { CoreBase } from '@interfaces/CoreBase';\nimport type { Tail, HmrAware, HmrUpdateEvent } from '@seedcord/types';\n\nexport interface Initializeable {\n init(): Promise<void>;\n}\n\n/** @internal */\nconst resolvedSpecSlot = Symbol('seedcord.plugin.spec');\n\n/**\n * Base class for Seedcord plugins. A subclass declares its options as the `Opts` type argument,\n * implements `init()`, and reads the framework surface through `this.core`.\n *\n * @typeParam Opts - The plugin's declared {@link PluginOptions}.\n * @typeParam TCore - The host type `this.core` resolves to. Each transport binds it to its own `Core`.\n */\nexport abstract class Plugin<Opts extends PluginOptions = {}, TCore extends CoreBase = CoreBase>\n implements Initializeable, HmrAware\n{\n /** @internal phantom, never set at runtime */\n declare readonly __transport?: TransportOf<Opts>;\n /** @internal phantom, never set at runtime */\n declare readonly __runtime?: RuntimeOf<Opts>;\n\n /** @internal */\n readonly [resolvedSpecSlot]: ResolvedPluginLifecycleSpec;\n\n /** Logs under the plugin's class name, on the channel its attach key sets. */\n public readonly logger: Logger;\n\n // CoreBase here keeps the augmented Core out of ConstructorParameters, which attach reads\n constructor(\n private readonly host: CoreBase,\n spec?: PluginLifecycleSpec\n ) {\n this.logger = new Logger(this.constructor.name);\n this[resolvedSpecSlot] = resolveLifecycleSpec(spec, this.constructor.name);\n }\n\n /** The host, typed to the transport whose `Plugin` base this class extends. */\n protected get core(): TCore {\n // justified: each transport binds TCore to the Core its own host satisfies\n return this.host as TCore;\n }\n\n abstract init(): Promise<void>;\n\n /**\n * Runs in the startup Ready phase, after `init()`. The gateway client is logged in by this phase,\n * and the http host binds its server in the same phase.\n */\n ready?(): Promise<void>;\n\n /** Runs during teardown, only when `init()` resolved. */\n dispose?(): Promise<void>;\n\n /**\n * Throws a `SeedcordError` that contains the plugin class name and the reason.\n *\n * @param reason - Why the options are invalid.\n */\n protected rejectOptions(reason: string): never {\n throw new SeedcordError(SeedcordErrorCode.PluginOptionsRejected, [this.constructor.name, reason]);\n }\n\n /** Override to reload plugin state on an HMR update. */\n public onHmr(_event: HmrUpdateEvent): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Registers critical file patterns that should trigger a full restart when changed in Dev HMR.\n * @param patterns - Glob patterns relative to the project root\n */\n protected registerCriticalFiles(patterns: string[]): void {\n getDevChannel()?.send('seedcord:register-critical-files', { patterns });\n }\n}\n\n/** @internal */\nexport function resolvedLifecycleSpecOf(plugin: PluginLike): ResolvedPluginLifecycleSpec {\n return plugin[resolvedSpecSlot];\n}\n\nexport type { PluginLifecycleSpec } from './lifecycle';\nexport type { PluginOptions } from './options';\n\n// a bound of Plugin<{}> rejects every plugin that declares an option, since Plugin<A> and Plugin<B>\n// are mutually unassignable. every member here must stay Opts-independent.\n/** @internal */\nexport type PluginLike = Pick<Plugin, 'init' | 'ready' | 'dispose' | 'logger' | 'onHmr' | typeof resolvedSpecSlot>;\n\n// a `CoreBase` first parameter rejects a narrowing ctor on its own, and it also collapses\n// `InstanceType<Ctor>` to `PluginLike` at every attach call. CoreParamAssert checks it instead.\n/** @internal */\nexport type PluginCtor<TPlugin extends PluginLike = PluginLike> = new (...args: any[]) => TPlugin;\n\n/** @internal */\nexport type PluginArgs<Ctor extends PluginCtor> = Tail<ConstructorParameters<Ctor>>;\n\ntype CoreParamTooNarrow = Record<\n 'this plugin constructor must take CoreBase as its first parameter and read the transport Core off this.core',\n never\n>;\n\n// a narrowed first parameter fails `CoreBase extends Param`\n/** @internal */\nexport type CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0]\n ? unknown\n : CoreParamTooNarrow;\n"],"mappings":";;;;;;;AAsBA,MAAM,0BAA0B;AAChC,MAAM,2BAA2B;AACjC,MAAM,6BAA6B;AAEnC,SAAS,UACL,OACA,UACA,UACA,YACM;CACN,IAAI,aAAa,QAAW,OAAO;CACnC,IAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,YAAY,GAC1C,MAAM,IAAI,cAAc,kBAAkB,uBAAuB,CAC7D,YACA,aAAa,MAAM,mDACvB,CAAC;CAEL,OAAO;AACX;;AAGA,SAAgB,qBACZ,MACA,YAC2B;CAC3B,OAAO;EACH,MAAM;GACF,OAAO,MAAM,MAAM;GACnB,SAAS,UAAU,QAAQ,MAAM,MAAM,SAAS,yBAAyB,UAAU;EACvF;EACA,OAAO,EACH,SAAS,UAAU,SAAS,MAAM,OAAO,SAAS,0BAA0B,UAAU,EAC1F;EACA,SAAS;GACL,OAAO,MAAM,SAAS;GACtB,SAAS,UAAU,WAAW,MAAM,SAAS,SAAS,4BAA4B,UAAU;EAChG;CACJ;AACJ;;;;;AC1CA,MAAM,mBAAmB,OAAO,sBAAsB;;;;;;;;AAStD,IAAsB,SAAtB,MAEA;CAcyB;;CAPrB,CAAU;;CAGV,AAAgB;CAGhB,YACI,AAAiB,MACjB,MACF;EAFmB;EAGjB,KAAK,SAAS,IAAI,OAAO,KAAK,YAAY,IAAI;EAC9C,KAAK,oBAAoB,qBAAqB,MAAM,KAAK,YAAY,IAAI;CAC7E;;CAGA,IAAc,OAAc;EAExB,OAAO,KAAK;CAChB;;;;;;CAkBA,AAAU,cAAc,QAAuB;EAC3C,MAAM,IAAI,cAAc,kBAAkB,uBAAuB,CAAC,KAAK,YAAY,MAAM,MAAM,CAAC;CACpG;;CAGA,AAAO,MAAM,QAAuC;EAChD,OAAO,QAAQ,QAAQ;CAC3B;;;;;CAMA,AAAU,sBAAsB,UAA0B;EACtD,cAAc,CAAC,EAAE,KAAK,oCAAoC,EAAE,SAAS,CAAC;CAC1E;AACJ;;AAGA,SAAgB,wBAAwB,QAAiD;CACrF,OAAO,OAAO;AAClB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Plugin-DVNh1kvX.cjs","names":["SeedcordError","SeedcordErrorCode","Logger","SeedcordError","SeedcordErrorCode"],"sources":["../src/plugin/lifecycle.ts","../src/plugin/Plugin.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport { ShutdownPhase, StartupPhase } from '@src/lifecycle/phases';\n\n/**\n * The lifecycle a plugin declares through `super(host, spec)`. Unset fields resolve to the base\n * defaults. `ready` runs at a fixed `StartupPhase.Ready
|
|
1
|
+
{"version":3,"file":"Plugin-DVNh1kvX.cjs","names":["SeedcordError","SeedcordErrorCode","Logger","SeedcordError","SeedcordErrorCode"],"sources":["../src/plugin/lifecycle.ts","../src/plugin/Plugin.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport { ShutdownPhase, StartupPhase } from '@src/lifecycle/phases';\n\n/**\n * The lifecycle a plugin declares through `super(host, spec)`. Unset fields resolve to the base\n * defaults. `ready` runs at a fixed `StartupPhase.Ready`.\n */\nexport interface PluginLifecycleSpec {\n init?: { phase?: StartupPhase; timeout?: number };\n ready?: { timeout?: number };\n dispose?: { phase?: ShutdownPhase; timeout?: number };\n}\n\n/** @internal */\nexport interface ResolvedPluginLifecycleSpec {\n readonly init: { readonly phase: StartupPhase; readonly timeout: number };\n readonly ready: { readonly timeout: number };\n readonly dispose: { readonly phase: ShutdownPhase; readonly timeout: number };\n}\n\nconst DEFAULT_INIT_TIMEOUT_MS = 15_000;\nconst DEFAULT_READY_TIMEOUT_MS = 15_000;\nconst DEFAULT_DISPOSE_TIMEOUT_MS = 10_000;\n\nfunction timeoutOf(\n field: 'init' | 'ready' | 'dispose',\n declared: number | undefined,\n fallback: number,\n pluginName: string\n): number {\n if (declared === undefined) return fallback;\n if (!Number.isFinite(declared) || declared <= 0) {\n throw new SeedcordError(SeedcordErrorCode.PluginOptionsRejected, [\n pluginName,\n `lifecycle ${field}.timeout must be a positive number of milliseconds`\n ]);\n }\n return declared;\n}\n\n/** @internal */\nexport function resolveLifecycleSpec(\n spec: PluginLifecycleSpec | undefined,\n pluginName: string\n): ResolvedPluginLifecycleSpec {\n return {\n init: {\n phase: spec?.init?.phase ?? StartupPhase.Configuration,\n timeout: timeoutOf('init', spec?.init?.timeout, DEFAULT_INIT_TIMEOUT_MS, pluginName)\n },\n ready: {\n timeout: timeoutOf('ready', spec?.ready?.timeout, DEFAULT_READY_TIMEOUT_MS, pluginName)\n },\n dispose: {\n phase: spec?.dispose?.phase ?? ShutdownPhase.Disconnect,\n timeout: timeoutOf('dispose', spec?.dispose?.timeout, DEFAULT_DISPOSE_TIMEOUT_MS, pluginName)\n }\n };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\n\nimport { getDevChannel } from '@hmr/devChannel';\n\nimport { resolveLifecycleSpec } from './lifecycle';\n\nimport type { ResolvedPluginLifecycleSpec, PluginLifecycleSpec } from './lifecycle';\nimport type { TransportOf, PluginOptions, RuntimeOf } from './options';\nimport type { CoreBase } from '@interfaces/CoreBase';\nimport type { Tail, HmrAware, HmrUpdateEvent } from '@seedcord/types';\n\nexport interface Initializeable {\n init(): Promise<void>;\n}\n\n/** @internal */\nconst resolvedSpecSlot = Symbol('seedcord.plugin.spec');\n\n/**\n * Base class for Seedcord plugins. A subclass declares its options as the `Opts` type argument,\n * implements `init()`, and reads the framework surface through `this.core`.\n *\n * @typeParam Opts - The plugin's declared {@link PluginOptions}.\n * @typeParam TCore - The host type `this.core` resolves to. Each transport binds it to its own `Core`.\n */\nexport abstract class Plugin<Opts extends PluginOptions = {}, TCore extends CoreBase = CoreBase>\n implements Initializeable, HmrAware\n{\n /** @internal phantom, never set at runtime */\n declare readonly __transport?: TransportOf<Opts>;\n /** @internal phantom, never set at runtime */\n declare readonly __runtime?: RuntimeOf<Opts>;\n\n /** @internal */\n readonly [resolvedSpecSlot]: ResolvedPluginLifecycleSpec;\n\n /** Logs under the plugin's class name, on the channel its attach key sets. */\n public readonly logger: Logger;\n\n // CoreBase here keeps the augmented Core out of ConstructorParameters, which attach reads\n constructor(\n private readonly host: CoreBase,\n spec?: PluginLifecycleSpec\n ) {\n this.logger = new Logger(this.constructor.name);\n this[resolvedSpecSlot] = resolveLifecycleSpec(spec, this.constructor.name);\n }\n\n /** The host, typed to the transport whose `Plugin` base this class extends. */\n protected get core(): TCore {\n // justified: each transport binds TCore to the Core its own host satisfies\n return this.host as TCore;\n }\n\n abstract init(): Promise<void>;\n\n /**\n * Runs in the startup Ready phase, after `init()`. The gateway client is logged in by this phase,\n * and the http host binds its server in the same phase.\n */\n ready?(): Promise<void>;\n\n /** Runs during teardown, only when `init()` resolved. */\n dispose?(): Promise<void>;\n\n /**\n * Throws a `SeedcordError` that contains the plugin class name and the reason.\n *\n * @param reason - Why the options are invalid.\n */\n protected rejectOptions(reason: string): never {\n throw new SeedcordError(SeedcordErrorCode.PluginOptionsRejected, [this.constructor.name, reason]);\n }\n\n /** Override to reload plugin state on an HMR update. */\n public onHmr(_event: HmrUpdateEvent): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Registers critical file patterns that should trigger a full restart when changed in Dev HMR.\n * @param patterns - Glob patterns relative to the project root\n */\n protected registerCriticalFiles(patterns: string[]): void {\n getDevChannel()?.send('seedcord:register-critical-files', { patterns });\n }\n}\n\n/** @internal */\nexport function resolvedLifecycleSpecOf(plugin: PluginLike): ResolvedPluginLifecycleSpec {\n return plugin[resolvedSpecSlot];\n}\n\nexport type { PluginLifecycleSpec } from './lifecycle';\nexport type { PluginOptions } from './options';\n\n// a bound of Plugin<{}> rejects every plugin that declares an option, since Plugin<A> and Plugin<B>\n// are mutually unassignable. every member here must stay Opts-independent.\n/** @internal */\nexport type PluginLike = Pick<Plugin, 'init' | 'ready' | 'dispose' | 'logger' | 'onHmr' | typeof resolvedSpecSlot>;\n\n// a `CoreBase` first parameter rejects a narrowing ctor on its own, and it also collapses\n// `InstanceType<Ctor>` to `PluginLike` at every attach call. CoreParamAssert checks it instead.\n/** @internal */\nexport type PluginCtor<TPlugin extends PluginLike = PluginLike> = new (...args: any[]) => TPlugin;\n\n/** @internal */\nexport type PluginArgs<Ctor extends PluginCtor> = Tail<ConstructorParameters<Ctor>>;\n\ntype CoreParamTooNarrow = Record<\n 'this plugin constructor must take CoreBase as its first parameter and read the transport Core off this.core',\n never\n>;\n\n// a narrowed first parameter fails `CoreBase extends Param`\n/** @internal */\nexport type CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0]\n ? unknown\n : CoreParamTooNarrow;\n"],"mappings":";;;;;;;AAsBA,MAAM,0BAA0B;AAChC,MAAM,2BAA2B;AACjC,MAAM,6BAA6B;AAEnC,SAAS,UACL,OACA,UACA,UACA,YACM;CACN,IAAI,aAAa,QAAW,OAAO;CACnC,IAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,YAAY,GAC1C,MAAM,IAAIA,wCAAcC,mCAAkB,uBAAuB,CAC7D,YACA,aAAa,MAAM,mDACvB,CAAC;CAEL,OAAO;AACX;;AAGA,SAAgB,qBACZ,MACA,YAC2B;CAC3B,OAAO;EACH,MAAM;GACF,OAAO,MAAM,MAAM;GACnB,SAAS,UAAU,QAAQ,MAAM,MAAM,SAAS,yBAAyB,UAAU;EACvF;EACA,OAAO,EACH,SAAS,UAAU,SAAS,MAAM,OAAO,SAAS,0BAA0B,UAAU,EAC1F;EACA,SAAS;GACL,OAAO,MAAM,SAAS;GACtB,SAAS,UAAU,WAAW,MAAM,SAAS,SAAS,4BAA4B,UAAU;EAChG;CACJ;AACJ;;;;;AC1CA,MAAM,mBAAmB,OAAO,sBAAsB;;;;;;;;AAStD,IAAsB,SAAtB,MAEA;CAcyB;;CAPrB,CAAU;;CAGV,AAAgB;CAGhB,YACI,AAAiB,MACjB,MACF;EAFmB;EAGjB,KAAK,SAAS,IAAIC,wBAAO,KAAK,YAAY,IAAI;EAC9C,KAAK,oBAAoB,qBAAqB,MAAM,KAAK,YAAY,IAAI;CAC7E;;CAGA,IAAc,OAAc;EAExB,OAAO,KAAK;CAChB;;;;;;CAkBA,AAAU,cAAc,QAAuB;EAC3C,MAAM,IAAIC,wCAAcC,mCAAkB,uBAAuB,CAAC,KAAK,YAAY,MAAM,MAAM,CAAC;CACpG;;CAGA,AAAO,MAAM,QAAuC;EAChD,OAAO,QAAQ,QAAQ;CAC3B;;;;;CAMA,AAAU,sBAAsB,UAA0B;EACtD,iCAAc,CAAC,EAAE,KAAK,oCAAoC,EAAE,SAAS,CAAC;CAC1E;AACJ;;AAGA,SAAgB,wBAAwB,QAAiD;CACrF,OAAO,OAAO;AAClB"}
|
|
@@ -27,16 +27,10 @@ declare const SubscribeMetadataKey: unique symbol;
|
|
|
27
27
|
declare const WebhookUrlMetadataKey: unique symbol;
|
|
28
28
|
//#endregion
|
|
29
29
|
//#region src/subscribers/publishDefault.d.ts
|
|
30
|
-
/**
|
|
31
|
-
* The key of the Bus method that emits the framework's own subscriptions. `publish` excludes these keys
|
|
32
|
-
* through `PublishableKey`, and this symbol is exported only from the internal entry.
|
|
33
|
-
*
|
|
34
|
-
* @internal
|
|
35
|
-
*/
|
|
30
|
+
/** @internal PublishableKey excludes this key so publish() never emits it externally. */
|
|
36
31
|
declare const PublishDefault: unique symbol;
|
|
37
32
|
//#endregion
|
|
38
33
|
//#region src/reply/AckTrace.d.ts
|
|
39
|
-
/** The first ack's stack, passed as the throw cause so a double-ack points to both lines. */
|
|
40
34
|
declare class AckTrace extends Error {
|
|
41
35
|
constructor(method: ReplyMethod);
|
|
42
36
|
}
|
|
@@ -44,23 +38,15 @@ declare class AckTrace extends Error {
|
|
|
44
38
|
//#region src/reply/ackLegality.d.ts
|
|
45
39
|
type AckState = 'unacked' | 'deferred-reply' | 'deferred-update' | 'replied';
|
|
46
40
|
type ReplyMethod = 'reply' | 'defer' | 'deferUpdate' | 'update' | 'followUp' | 'edit' | 'delete' | 'send' | 'showModal';
|
|
47
|
-
/** Throws before any API call. `ackedBy` becomes the throw cause, its stack points at the first ack. */
|
|
48
41
|
declare function checkAckLegality(method: ReplyMethod, state: AckState, routeId: string, ackedBy?: AckTrace): void;
|
|
49
42
|
declare function sendTarget(state: AckState): 'reply' | 'edit' | 'followUp';
|
|
50
43
|
//#endregion
|
|
51
44
|
//#region src/reply/responseReport.d.ts
|
|
52
|
-
/** The bus a write publishes on, and the interaction it belongs to. */
|
|
53
45
|
interface ReplyTelemetry {
|
|
54
46
|
readonly bus: Bus;
|
|
55
47
|
readonly interactionId: string;
|
|
56
48
|
}
|
|
57
|
-
/** The reply verbs plus the autocomplete choices callback, which runs outside the ack state machine. */
|
|
58
49
|
type WriteMethod = ReplyMethod | 'respond';
|
|
59
|
-
/**
|
|
60
|
-
* Runs a write that returns no message and publishes whichever arm it takes.
|
|
61
|
-
*
|
|
62
|
-
* @internal
|
|
63
|
-
*/
|
|
64
50
|
declare function reportedWrite<Result>(telemetry: ReplyTelemetry, routeId: string, method: WriteMethod, write: () => Promise<Result>): Promise<Result>;
|
|
65
51
|
//#endregion
|
|
66
52
|
//#region src/stops/Notice.d.ts
|
|
@@ -110,7 +96,7 @@ declare abstract class Notice extends Error {
|
|
|
110
96
|
*/
|
|
111
97
|
report: boolean;
|
|
112
98
|
/**
|
|
113
|
-
* Whether the reply is ephemeral,
|
|
99
|
+
* Whether the reply is ephemeral, seen only by the invoking user. Set it false for a refusal the
|
|
114
100
|
* whole channel should see.
|
|
115
101
|
*
|
|
116
102
|
* @defaultValue `true`
|
|
@@ -118,13 +104,13 @@ declare abstract class Notice extends Error {
|
|
|
118
104
|
ephemeral: boolean;
|
|
119
105
|
/**
|
|
120
106
|
* A short one-line reason. When every arm of an `or` gate refuses and each refusal sets this, `or`
|
|
121
|
-
* lists them
|
|
107
|
+
* lists them as the reply.
|
|
122
108
|
*/
|
|
123
109
|
summary?: string;
|
|
124
110
|
protected constructor(message: string, options?: ErrorOptions);
|
|
125
111
|
/**
|
|
126
112
|
* Builds what the user sees. Called fresh each time the denial is shown, so the builders are new
|
|
127
|
-
* and the bot color resolves at render time
|
|
113
|
+
* and the bot color resolves at render time.
|
|
128
114
|
*/
|
|
129
115
|
abstract render(ctx: RenderContext): ReplyResponse;
|
|
130
116
|
}
|
|
@@ -163,7 +149,7 @@ interface InteractionFaultSource {
|
|
|
163
149
|
}
|
|
164
150
|
/**
|
|
165
151
|
* A fault that came from a client event, JSON-safe for a webhook payload. The actor, guild, and channel
|
|
166
|
-
* are best-effort, derived from the event args
|
|
152
|
+
* are best-effort, derived from the event args. Each is nullable. The raw args carry the full detail.
|
|
167
153
|
*/
|
|
168
154
|
interface EventFaultSource {
|
|
169
155
|
kind: 'event';
|
|
@@ -221,7 +207,7 @@ interface DefaultSubscriptions {
|
|
|
221
207
|
durationMs: number;
|
|
222
208
|
/**
|
|
223
209
|
* Discord's interaction creation until dispatch entry, read from the snowflake. This covers
|
|
224
|
-
* network transit plus any time your bot was busy before starting
|
|
210
|
+
* network transit plus any time your bot was busy before starting. A rise points at either
|
|
225
211
|
* Discord or your own backlog. It subtracts a Discord timestamp from the host clock, so a host
|
|
226
212
|
* running behind Discord reports a negative value.
|
|
227
213
|
*/
|
|
@@ -307,16 +293,16 @@ declare abstract class Subscriber<KeyOfSubscribers extends SubscriptionKey, TCor
|
|
|
307
293
|
//#endregion
|
|
308
294
|
//#region src/subscribers/Bus.d.ts
|
|
309
295
|
/**
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
296
|
+
* Both parameters are `never` because construct-signature parameters check contravariantly, and a
|
|
297
|
+
* concrete subscriber narrows each to its own payload and transport `Core`. One cast at dispatch
|
|
298
|
+
* restores both.
|
|
313
299
|
*
|
|
314
300
|
* @internal
|
|
315
301
|
*/
|
|
316
302
|
type StoredSubscriberCtor = new (data: never, core: never) => Subscriber<SubscriptionKey, CoreBase>;
|
|
317
303
|
/**
|
|
318
|
-
*
|
|
319
|
-
*
|
|
304
|
+
* `resolve` is lazy so an edge host registers from a manifest row without importing the module
|
|
305
|
+
* until that key first publishes.
|
|
320
306
|
*
|
|
321
307
|
* @internal
|
|
322
308
|
*/
|
|
@@ -324,7 +310,7 @@ interface SubscriberRegistration {
|
|
|
324
310
|
readonly keys: readonly SubscriptionKey[];
|
|
325
311
|
readonly frequency: EventFrequency;
|
|
326
312
|
readonly resolve: () => StoredSubscriberCtor | Promise<StoredSubscriberCtor>;
|
|
327
|
-
/**
|
|
313
|
+
/** A server-host registration carries it from the start. A lazy one fills it on first resolve. */
|
|
328
314
|
ctor?: StoredSubscriberCtor | undefined;
|
|
329
315
|
}
|
|
330
316
|
/**
|
|
@@ -339,12 +325,7 @@ declare class Bus extends TypedEventEmitter<SubscriptionTuples> {
|
|
|
339
325
|
private readonly executedOnce;
|
|
340
326
|
private readonly webhookProbes;
|
|
341
327
|
constructor(core: CoreBase);
|
|
342
|
-
/**
|
|
343
|
-
* Registers the two shipped webhook reporters. The host calls this during startup, keeping the
|
|
344
|
-
* url validation inside `start()` where a failure can still release the singleton guard.
|
|
345
|
-
*
|
|
346
|
-
* @internal
|
|
347
|
-
*/
|
|
328
|
+
/** @internal the host calls this during startup, keeping the url validation inside `start()` where a failure can still release the singleton guard. */
|
|
348
329
|
registerDefaults(): void;
|
|
349
330
|
/** @internal */
|
|
350
331
|
register(registration: SubscriberRegistration): void;
|
|
@@ -354,17 +335,15 @@ declare class Bus extends TypedEventEmitter<SubscriptionTuples> {
|
|
|
354
335
|
get registeredCount(): number;
|
|
355
336
|
/** @internal Only eagerly registered reporters are probed, which is every server-host one. */
|
|
356
337
|
verifyWebhooks(): Promise<void>;
|
|
357
|
-
/** Returns whether the class should register. */
|
|
358
338
|
private probeWebhook;
|
|
359
339
|
/**
|
|
360
340
|
* Publishes an event to its subscribers and native listeners.
|
|
361
341
|
*
|
|
362
|
-
* Fire-and-forget
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
* it never runs twice.
|
|
342
|
+
* Fire-and-forget. Subscriber handlers run asynchronously and this returns before they finish. A
|
|
343
|
+
* caller cannot assume a side effect has completed. Errors thrown by a subscriber or by an `on()`
|
|
344
|
+
* listener are caught and logged, never surfaced here. One throwing listener does not stop the
|
|
345
|
+
* others. Subscribers on one key run concurrently and carry no ordering guarantee. A `'once'`
|
|
346
|
+
* subscriber is marked as executed when it starts, even if it throws. It never runs twice.
|
|
368
347
|
*
|
|
369
348
|
* The framework's own keys are excluded. Subscribe to those and listen with `on`. The framework
|
|
370
349
|
* is their only publisher.
|
|
@@ -431,7 +410,7 @@ declare enum ShutdownPhase {
|
|
|
431
410
|
//#region src/plugin/lifecycle.d.ts
|
|
432
411
|
/**
|
|
433
412
|
* The lifecycle a plugin declares through `super(host, spec)`. Unset fields resolve to the base
|
|
434
|
-
* defaults. `ready` runs at a fixed `StartupPhase.Ready
|
|
413
|
+
* defaults. `ready` runs at a fixed `StartupPhase.Ready`.
|
|
435
414
|
*/
|
|
436
415
|
interface PluginLifecycleSpec {
|
|
437
416
|
init?: {
|
|
@@ -479,9 +458,9 @@ interface PluginOptions {
|
|
|
479
458
|
type TransportOf<Opts extends PluginOptions> = undefined extends Opts['transport'] ? 'any' : NonNullable<Opts['transport']>;
|
|
480
459
|
/** @internal */
|
|
481
460
|
type RuntimeOf<Opts extends PluginOptions> = undefined extends Opts['runtime'] ? 'any' : NonNullable<Opts['runtime']>;
|
|
482
|
-
/** @internal
|
|
461
|
+
/** @internal */
|
|
483
462
|
type Transport = TypedExclude<NonNullable<PluginOptions['transport']>, 'any'>;
|
|
484
|
-
/** @internal
|
|
463
|
+
/** @internal */
|
|
485
464
|
type Runtime = TypedExclude<NonNullable<PluginOptions['runtime']>, 'any'>;
|
|
486
465
|
type TransportMismatch<PluginT extends string, BotT extends string> = Record<`this plugin declares transport '${PluginT}' but this bot runs '${BotT}'`, never>;
|
|
487
466
|
type RuntimeMismatch<PluginRt extends string, BotRt extends string> = Record<`this plugin declares runtime '${PluginRt}' but this bot runs '${BotRt}'`, never>;
|
|
@@ -558,4 +537,4 @@ type CoreParamTooNarrow = Record<'this plugin constructor must take CoreBase as
|
|
|
558
537
|
type CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0] ? unknown : CoreParamTooNarrow;
|
|
559
538
|
//#endregion
|
|
560
539
|
export { AckState as A, InteractionRouteKeys as B, FaultSource as C, Notice as D, Subscriptions as E, PublishDefault as F, MiddlewareMetadataKey as H, CommandMetadataKey as I, EventMetadataKey as L, checkAckLegality as M, sendTarget as N, ReplyTelemetry as O, AckTrace as P, GatedMetadataKey as R, DispatchOutcome as S, SubscriptionKey as T, SubscribeMetadataKey as U, InteractionRoutes as V, WebhookUrlMetadataKey as W, Bus as _, PluginCtor as a, Subscriber as b, PluginOptions as c, Transport as d, TransportAssert as f, CoreBase as g, StartupPhase as h, PluginArgs as i, ReplyMethod as j, reportedWrite as k, Runtime as l, ShutdownPhase as m, Initializeable as n, PluginLike as o, PluginLifecycleSpec as p, Plugin as r, ChannelKeyAssert as s, CoreParamAssert as t, RuntimeAssert as u, StoredSubscriberCtor as v, SubscriptionData as w, DefaultSubscriptions as x, SubscriberRegistration as y, InteractionMetadataKey as z };
|
|
561
|
-
//# sourceMappingURL=Plugin-
|
|
540
|
+
//# sourceMappingURL=Plugin-qRc7Ch4g.d.mts.map
|
|
@@ -3,7 +3,7 @@ import { APIApplicationCommand } from "discord-api-types/v10";
|
|
|
3
3
|
//#region src/commands/types.d.ts
|
|
4
4
|
/** @internal */
|
|
5
5
|
type CommandBuilder = SlashCommandBuilder | ContextMenuCommandBuilder;
|
|
6
|
-
/**
|
|
6
|
+
/** discord returns these from each scope's deploy, keyed by command id. @internal */
|
|
7
7
|
interface DeployResult {
|
|
8
8
|
global: Map<string, APIApplicationCommand>;
|
|
9
9
|
guilds: Map<string, Map<string, APIApplicationCommand>>;
|
|
@@ -18,4 +18,4 @@ Matches a [`class` constructor](https://developer.mozilla.org/en-US/docs/Web/Jav
|
|
|
18
18
|
type Constructor<T, Arguments extends unknown[] = any[]> = new (...arguments_: Arguments) => T;
|
|
19
19
|
//#endregion
|
|
20
20
|
export { CommandBuilder as n, DeployResult as r, Constructor as t };
|
|
21
|
-
//# sourceMappingURL=basic-
|
|
21
|
+
//# sourceMappingURL=basic-Dng__Ena.d.mts.map
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { ContextMenuCommandBuilder, SlashCommandBuilder } from "@discordjs/builders";
|
|
2
2
|
//#region src/commands/contextMenuLeaves.d.ts
|
|
3
|
-
/**
|
|
3
|
+
/** @internal */
|
|
4
4
|
interface ContextMenuLeaves {
|
|
5
5
|
readonly user: Set<string>;
|
|
6
6
|
readonly message: Set<string>;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* kinds stay in separate sets.
|
|
9
|
+
* dedupes names across builders, since the same builder repeats once per guild.
|
|
10
|
+
* a user and a message command can share a name.
|
|
12
11
|
*
|
|
13
12
|
* @internal
|
|
14
13
|
*/
|
|
15
14
|
declare function contextMenuLeaves(commands: Iterable<SlashCommandBuilder | ContextMenuCommandBuilder>): ContextMenuLeaves;
|
|
16
15
|
//#endregion
|
|
17
16
|
export { contextMenuLeaves as n, ContextMenuLeaves as t };
|
|
18
|
-
//# sourceMappingURL=contextMenuLeaves-
|
|
17
|
+
//# sourceMappingURL=contextMenuLeaves-COzDYa6c.d.mts.map
|