@seedcord/core 0.4.0 → 0.4.1

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.
Files changed (30) hide show
  1. package/dist/{Bus-BVwmKc-A.mjs → Bus-yJGE6Fs2.mjs} +78 -78
  2. package/dist/{Bus-BVwmKc-A.mjs.map → Bus-yJGE6Fs2.mjs.map} +1 -1
  3. package/dist/{Pluggable-Cf5-WWmW.mjs → Pluggable-BI_LQuPm.mjs} +3 -3
  4. package/dist/{Pluggable-Cf5-WWmW.mjs.map → Pluggable-BI_LQuPm.mjs.map} +1 -1
  5. package/dist/{Pluggable-DFNUIlJM.d.mts → Pluggable-bFCmT4oB.d.mts} +3 -3
  6. package/dist/{Plugin-C6oMqWf8.mjs → Plugin-B0SRHzMX.mjs} +2 -2
  7. package/dist/{Plugin-C6oMqWf8.mjs.map → Plugin-B0SRHzMX.mjs.map} +1 -1
  8. package/dist/{Plugin-CG9pWbPF.d.mts → Plugin-BW2JH2_h.d.mts} +2 -2
  9. package/dist/{cursor-BcRfmTSo.d.mts → cursor-CO_MTgdZ.d.mts} +5 -203
  10. package/dist/{cursor-GBUYz3jk.mjs → cursor-CaT051ii.mjs} +6 -472
  11. package/dist/cursor-CaT051ii.mjs.map +1 -0
  12. package/dist/{index-ZzdQafFv.d.mts → index-CvneCObW.d.mts} +2 -2
  13. package/dist/index.d.mts +6 -5
  14. package/dist/index.mjs +42 -7
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/internal.index.d.mts +6 -5
  17. package/dist/internal.index.mjs +4 -4
  18. package/dist/node-internal.index.d.mts +4 -4
  19. package/dist/node-internal.index.mjs +6 -6
  20. package/dist/node.index.d.mts +1 -1
  21. package/dist/node.index.mjs +2 -2
  22. package/dist/{phases-F8yvPd60.d.mts → phases-C5Steaa2.d.mts} +56 -56
  23. package/dist/plugin.index.d.mts +1 -1
  24. package/dist/plugin.index.mjs +1 -1
  25. package/dist/{slashRouteLeaves-CbYRwqMY.mjs → slashRouteLeaves-BQmbFm-S.mjs} +2 -2
  26. package/dist/{slashRouteLeaves-CbYRwqMY.mjs.map → slashRouteLeaves-BQmbFm-S.mjs.map} +1 -1
  27. package/dist/{subscribers-CGGQkUfM.mjs → subscribers-DVa-Pr45.mjs} +3 -3
  28. package/dist/{subscribers-CGGQkUfM.mjs.map → subscribers-DVa-Pr45.mjs.map} +1 -1
  29. package/package.json +8 -7
  30. package/dist/cursor-GBUYz3jk.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Pluggable-Cf5-WWmW.mjs","names":[],"sources":["../src/node/Lifecycle/withTimeout.ts","../src/node/assertNodeVersion.ts","../src/node/processErrors.ts","../src/node/Pluggable.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nasync function raceTimer(\n work: Promise<unknown>,\n timeoutMs: number,\n onTimeout: (resolve: () => void, reject: (error: unknown) => void) => void\n): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n try {\n await Promise.race([\n work,\n new Promise<void>((resolve, reject) => {\n timer = setTimeout(() => onTimeout(resolve, reject), timeoutMs);\n })\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n// rejects with a LifecycleTaskTimeout when run outlasts the bound\nexport function withTimeout(name: string, run: () => Promise<void>, timeoutMs: number): Promise<void> {\n return raceTimer(run(), timeoutMs, (_, reject) => {\n reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [name, timeoutMs]));\n });\n}\n\n// resolves when work settles or the bound elapses, whichever is first\nexport function settleWithin(work: Promise<unknown>, timeoutMs: number): Promise<void> {\n return raceTimer(work, timeoutMs, (resolve) => resolve());\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\n// every seedcord package declares engines.node as `>=major` or `>=major.minor`\nconst RANGE = new RegExp(String.raw`^>=(\\d+)(?:\\.(\\d+))?`);\nconst VERSION = /^v?(\\d+)\\.(\\d+)/;\nconst SPACES = /\\s/g;\n\n/** @internal */\nexport function assertNodeVersion(range: string, running: string): void {\n const required = RANGE.exec(range.replaceAll(SPACES, ''));\n const current = VERSION.exec(running);\n if (!required || !current) return;\n\n const [, requiredMajor, requiredMinor = '0'] = required;\n const [, currentMajor, currentMinor] = current;\n\n const meetsRange =\n Number(currentMajor) > Number(requiredMajor) ||\n (Number(currentMajor) === Number(requiredMajor) && Number(currentMinor) >= Number(requiredMinor));\n if (meetsRange) return;\n\n throw new SeedcordError(SeedcordErrorCode.UnsupportedNodeVersion, [range, running]);\n}\n","import crypto from 'node:crypto';\n\nimport { Logger } from '@seedcord/logger';\n\nimport { asError } from '#stops/asError';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\n\nconst logger = new Logger('Faults', { channel: 'errors' });\n\n/** @internal */\nexport function registerProcessErrors(core: CoreBase, shutdown: CoordinatedShutdown): () => void {\n function report(caught: unknown, routeId: string): void {\n const error = asError(caught);\n const uuid = crypto.randomUUID();\n\n if (core.config.errors?.errorStack ?? false) logger.error(uuid, error);\n else logger.error(`${uuid} | ${error.message}`);\n\n core.bus[PublishDefault]('unknownException', { uuid, error, routeId });\n }\n\n const onRejection = (reason: unknown): void => {\n report(reason, 'process:unhandledRejection');\n };\n\n const onException = (caught: unknown): void => {\n report(caught, 'process:uncaughtException');\n // node does not exit on its own once a listener is registered\n void shutdown.run(1);\n };\n\n process.on('unhandledRejection', onRejection);\n process.on('uncaughtException', onException);\n\n return () => {\n process.off('unhandledRejection', onRejection);\n process.off('uncaughtException', onException);\n };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordAggregateError, SeedcordError, SeedcordTypeError } from '@seedcord/errors/internal';\nimport { FRAMEWORK_CHANNELS, Logger } from '@seedcord/logger';\nimport { HostPluginKeys, HostShutdown, HostStartup } from '@seedcord/types/internal';\n\nimport { assertNodeVersion } from '#node/assertNodeVersion';\nimport { StartupPhase } from '#src/lifecycle/phases';\nimport { pluginLoggerOf, resolvedLifecycleSpecOf } from '#src/plugin/Plugin';\n\nimport { withTimeout } from './Lifecycle/withTimeout';\nimport { registerProcessErrors } from './processErrors';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\nimport type { CoordinatedStartup } from '#node/Lifecycle/CoordinatedStartup';\nimport type { ShutdownPhase } from '#src/lifecycle/phases';\nimport type { ChannelKeyAssert, Runtime, RuntimeAssert, Transport, TransportAssert } from '#src/plugin/options';\nimport type { CoreParamAssert, PluginArgs, PluginCtor, PluginLike } from '#src/plugin/Plugin';\nimport type { Bus } from '#subscribers/Bus';\nimport type { REST } from '@discordjs/rest';\nimport type { Config, IRateLimiter } from '@seedcord/types';\n\ninterface Attachment {\n readonly key: string;\n readonly instance: PluginLike;\n}\n\nconst RESERVED_KEYS: ReadonlySet<string> = new Set(FRAMEWORK_CHANNELS);\n\n/**\n * Base class for a plugin host, a transport `Seedcord` class.\n *\n * You attach plugins while configuring the bot. Within one startup phase, their `init()` calls run\n * one after another in attach order.\n */\n// no defaults, a default runtime of the full union contains 'edge' and RuntimeAssert rejects every plugin on that\nexport abstract class Pluggable<BotT extends Transport, BotRt extends Runtime> implements CoreBase {\n public abstract readonly config: Config;\n public abstract readonly rest: REST;\n public abstract readonly applicationId: string;\n public abstract readonly rateLimiter: IRateLimiter;\n public abstract readonly bus: Bus;\n\n /** @internal */\n readonly [HostShutdown]: CoordinatedShutdown;\n /** @internal */\n readonly [HostStartup]: CoordinatedStartup;\n\n /** Add a task that runs while the bot shuts down. */\n public readonly shutdown: Pick<CoordinatedShutdown, 'addTask'>;\n\n /** Add a task that runs while the bot starts. */\n public readonly startup: Pick<CoordinatedStartup, 'addTask'>;\n\n protected isInitialized = false;\n protected startFailed = false;\n protected readonly plugins: PluginLike[] = [];\n\n private readonly pluginLogger = new Logger('Plugins', { channel: 'plugins' });\n\n private readonly attachments: Attachment[] = [];\n private readonly completedInits = new Set<Attachment>();\n private readonly disposePhases = new Set<ShutdownPhase>();\n private pluginTasksRegistered = false;\n private initPromise?: Promise<this> | undefined;\n\n private static isInstantiated = false;\n private static liveHost?: object | undefined;\n private static liveShutdown?: CoordinatedShutdown | undefined;\n private static liveProcessErrors?: (() => void) | undefined;\n\n constructor(shutdown: CoordinatedShutdown, startup: CoordinatedStartup) {\n // a `sideEffects: false` build would drop the same call in the node entry\n assertNodeVersion(process.env.PACKAGE_NODE_RANGE ?? '', process.version);\n\n if (Pluggable.isInstantiated) {\n // signal handlers from the caller's shutdown stay on the process unless dropped here\n shutdown.removeSignalHandlers();\n throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);\n }\n\n Pluggable.isInstantiated = true;\n Pluggable.liveHost = this;\n Pluggable.liveShutdown = shutdown;\n this[HostShutdown] = shutdown;\n this[HostStartup] = startup;\n // a getter returning the slot would hand back run() and the signal handlers with it\n this.shutdown = { addTask: shutdown.addTask.bind(shutdown) };\n this.startup = { addTask: startup.addTask.bind(startup) };\n }\n\n /** @internal */\n protected init(): Promise<this> {\n // clearing the slot on a rejection lets a later retry throw its own error\n this.initPromise ??= this.runInit().catch((caught: unknown) => {\n this.initPromise = undefined;\n throw caught;\n });\n return this.initPromise;\n }\n\n private async runInit(): Promise<this> {\n if (this.isInitialized) return this;\n // a rerun after a failed startup would re-init the rolled-back plugins\n if (this.startFailed) throw new SeedcordError(SeedcordErrorCode.LifecycleRestartAfterFailure);\n\n this.registerPluginTasks();\n\n if (this.config.errors?.catchProcessErrors ?? true) {\n Pluggable.liveProcessErrors = registerProcessErrors(this, this[HostShutdown]);\n }\n\n const startupSettled: PromiseWithResolvers<void> = Promise.withResolvers();\n this[HostShutdown].gateOnStartup(startupSettled.promise);\n\n try {\n await this[HostStartup].run();\n } catch (caught) {\n await this.rollback();\n throw caught;\n } finally {\n startupSettled.resolve();\n }\n\n this.isInitialized = true;\n return this;\n }\n\n /** @internal codegen reads this to emit the `Core` augmentation */\n public get [HostPluginKeys](): readonly string[] {\n return this.attachments.map((attachment) => attachment.key);\n }\n\n /** @internal */\n protected static reset(host?: object): boolean {\n if (host !== undefined && Pluggable.liveHost !== host) return false;\n\n Pluggable.liveShutdown?.removeSignalHandlers();\n Pluggable.liveShutdown = undefined;\n Pluggable.liveProcessErrors?.();\n Pluggable.liveProcessErrors = undefined;\n Pluggable.liveHost = undefined;\n Pluggable.isInstantiated = false;\n return true;\n }\n\n /**\n * Attaches a plugin under `key`. Read the instance back as `core[key]`. `seedcord codegen`\n * writes the `Core` augmentation that types it there.\n *\n * Startup runs each plugin's `init()` in attach order within its phase.\n *\n * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.\n * Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).\n * A narrower one fails to compile here.\n *\n * @param key - Also the channel the plugin logs on. Reserved channel names throw.\n * @param args - Whatever your constructor takes after the host.\n * @throws A **SeedcordError** if you attach after the bot has started. A taken or reserved key throws too.\n * @example\n * ```ts\n * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... });\n * ```\n */\n public attach<Key extends string, Ctor extends PluginCtor>(\n this: this,\n key: ChannelKeyAssert<Key>,\n Plugin: Ctor &\n TransportAssert<InstanceType<Ctor>, BotT> &\n RuntimeAssert<InstanceType<Ctor>, BotRt> &\n CoreParamAssert<Ctor>,\n ...args: PluginArgs<Ctor>\n ): this & Record<Key, InstanceType<Ctor>> {\n if (this.isInitialized) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);\n }\n // several reserved channels are also members on a host, which the next check would report first\n if (RESERVED_KEYS.has(key)) {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginReservedChannel, [key]);\n }\n if (key in this) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [key]);\n }\n\n const instance = new Plugin(this, ...args);\n pluginLoggerOf(instance).setChannel(key);\n this.plugins.push(instance);\n this.attachments.push({ key, instance });\n\n return Object.assign(this, { [key]: instance } as Record<Key, InstanceType<Ctor>>);\n }\n\n // one combined task per phase keeps plugin inits sequential while the phase's other tasks run concurrently\n private registerPluginTasks(): void {\n if (this.pluginTasksRegistered) return;\n this.pluginTasksRegistered = true;\n\n const groups = new Map<StartupPhase, Attachment[]>();\n for (const attachment of this.attachments) {\n const phase = resolvedLifecycleSpecOf(attachment.instance).init.phase;\n const group = groups.get(phase) ?? [];\n group.push(attachment);\n groups.set(phase, group);\n }\n\n for (const [phase, group] of groups) {\n // Ready inits run in the combined Ready task, before the ready hooks\n if (phase === StartupPhase.Ready) continue;\n const budget = group.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0);\n this.startup.addTask(phase, 'plugins-init', () => this.runInits(group), budget);\n }\n\n this.registerReadyTask(groups.get(StartupPhase.Ready) ?? []);\n }\n\n private async runInits(group: readonly Attachment[]): Promise<void> {\n for (const attachment of group) {\n const { key, instance } = attachment;\n const spec = resolvedLifecycleSpecOf(instance);\n\n pluginLoggerOf(instance).utils.initialization(key, 'start');\n await withTimeout(`Plugin (${key})`, () => instance.init(), spec.init.timeout);\n pluginLoggerOf(instance).utils.initialization(key, 'end');\n\n this.completedInits.add(attachment);\n if (instance.dispose) this.registerDisposeTask(spec.dispose.phase);\n }\n }\n\n private registerReadyTask(readyInits: readonly Attachment[]): void {\n const steps = this.attachments.reduce<{ key: string; run: () => Promise<void>; timeout: number }[]>(\n (acc, a) => {\n const ready = a.instance.ready?.bind(a.instance);\n if (ready) {\n acc.push({\n key: a.key,\n run: ready,\n timeout: resolvedLifecycleSpecOf(a.instance).ready.timeout\n });\n }\n return acc;\n },\n []\n );\n if (readyInits.length === 0 && steps.length === 0) return;\n\n const budget =\n readyInits.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0) +\n steps.reduce((sum, step) => sum + step.timeout, 0);\n this.startup.addTask(\n StartupPhase.Ready,\n 'plugins-ready',\n async () => {\n await this.runInits(readyInits);\n for (const step of steps) {\n await withTimeout(`Plugin:${step.key}:ready`, step.run, step.timeout);\n }\n },\n budget\n );\n }\n\n private registerDisposeTask(phase: ShutdownPhase): void {\n if (this.disposePhases.has(phase)) return;\n this.disposePhases.add(phase);\n\n // the budget covers every dispose in this phase because they all run in this one task\n const budget = this.attachments.reduce((sum, a) => {\n const spec = resolvedLifecycleSpecOf(a.instance);\n return a.instance.dispose && spec.dispose.phase === phase ? sum + spec.dispose.timeout : sum;\n }, 0);\n this.shutdown.addTask(phase, 'plugins-dispose', () => this.runDisposals(phase), budget);\n }\n\n private async runDisposals(phase: ShutdownPhase): Promise<void> {\n const failures: unknown[] = [];\n // keep disposing the rest even when one fails\n await this.disposeCompleted(phase, (caught) => failures.push(caught));\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) {\n throw new SeedcordAggregateError(SeedcordErrorCode.PluginDisposeFailures, failures, [failures.length]);\n }\n }\n\n private async disposeCompleted(\n phase: ShutdownPhase | undefined,\n onError: (caught: unknown) => void\n ): Promise<void> {\n for (const attachment of [...this.attachments].reverse()) {\n if (!this.completedInits.has(attachment)) continue;\n const dispose = attachment.instance.dispose?.bind(attachment.instance);\n if (!dispose) continue;\n const spec = resolvedLifecycleSpecOf(attachment.instance);\n if (phase !== undefined && spec.dispose.phase !== phase) continue;\n\n try {\n await withTimeout(`Plugin:${attachment.key}:dispose`, dispose, spec.dispose.timeout);\n } catch (caught) {\n onError(caught);\n }\n }\n }\n\n private async rollback(): Promise<void> {\n this.startFailed = true;\n // log rollback dispose failures so they do not hide the startup error\n await this.disposeCompleted(undefined, (caught) => this.pluginLogger.warn('rollback dispose failed', caught));\n // prevents re-dispose when shutdown runs after rollback\n this.completedInits.clear();\n }\n}\n\n// the public `shutdown` field carries addTask alone\nexport function shutdownOf(host: Pick<Pluggable<Transport, Runtime>, typeof HostShutdown>): CoordinatedShutdown {\n return host[HostShutdown];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Pluggable-BI_LQuPm.mjs","names":[],"sources":["../src/node/Lifecycle/withTimeout.ts","../src/node/assertNodeVersion.ts","../src/node/processErrors.ts","../src/node/Pluggable.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nasync function raceTimer(\n work: Promise<unknown>,\n timeoutMs: number,\n onTimeout: (resolve: () => void, reject: (error: unknown) => void) => void\n): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n try {\n await Promise.race([\n work,\n new Promise<void>((resolve, reject) => {\n timer = setTimeout(() => onTimeout(resolve, reject), timeoutMs);\n })\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n// rejects with a LifecycleTaskTimeout when run outlasts the bound\nexport function withTimeout(name: string, run: () => Promise<void>, timeoutMs: number): Promise<void> {\n return raceTimer(run(), timeoutMs, (_, reject) => {\n reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [name, timeoutMs]));\n });\n}\n\n// resolves when work settles or the bound elapses, whichever is first\nexport function settleWithin(work: Promise<unknown>, timeoutMs: number): Promise<void> {\n return raceTimer(work, timeoutMs, (resolve) => resolve());\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\n// every seedcord package declares engines.node as `>=major` or `>=major.minor`\nconst RANGE = new RegExp(String.raw`^>=(\\d+)(?:\\.(\\d+))?`);\nconst VERSION = /^v?(\\d+)\\.(\\d+)/;\nconst SPACES = /\\s/g;\n\n/** @internal */\nexport function assertNodeVersion(range: string, running: string): void {\n const required = RANGE.exec(range.replaceAll(SPACES, ''));\n const current = VERSION.exec(running);\n if (!required || !current) return;\n\n const [, requiredMajor, requiredMinor = '0'] = required;\n const [, currentMajor, currentMinor] = current;\n\n const meetsRange =\n Number(currentMajor) > Number(requiredMajor) ||\n (Number(currentMajor) === Number(requiredMajor) && Number(currentMinor) >= Number(requiredMinor));\n if (meetsRange) return;\n\n throw new SeedcordError(SeedcordErrorCode.UnsupportedNodeVersion, [range, running]);\n}\n","import crypto from 'node:crypto';\n\nimport { Logger } from '@seedcord/logger';\n\nimport { asError } from '#stops/asError';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\n\nconst logger = new Logger('Faults', { channel: 'errors' });\n\n/** @internal */\nexport function registerProcessErrors(core: CoreBase, shutdown: CoordinatedShutdown): () => void {\n function report(caught: unknown, routeId: string): void {\n const error = asError(caught);\n const uuid = crypto.randomUUID();\n\n if (core.config.errors?.errorStack ?? false) logger.error(uuid, error);\n else logger.error(`${uuid} | ${error.message}`);\n\n core.bus[PublishDefault]('unknownException', { uuid, error, routeId });\n }\n\n const onRejection = (reason: unknown): void => {\n report(reason, 'process:unhandledRejection');\n };\n\n const onException = (caught: unknown): void => {\n report(caught, 'process:uncaughtException');\n // node does not exit on its own once a listener is registered\n void shutdown.run(1);\n };\n\n process.on('unhandledRejection', onRejection);\n process.on('uncaughtException', onException);\n\n return () => {\n process.off('unhandledRejection', onRejection);\n process.off('uncaughtException', onException);\n };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordAggregateError, SeedcordError, SeedcordTypeError } from '@seedcord/errors/internal';\nimport { FRAMEWORK_CHANNELS, Logger } from '@seedcord/logger';\nimport { HostPluginKeys, HostShutdown, HostStartup } from '@seedcord/types/internal';\n\nimport { assertNodeVersion } from '#node/assertNodeVersion';\nimport { StartupPhase } from '#src/lifecycle/phases';\nimport { pluginLoggerOf, resolvedLifecycleSpecOf } from '#src/plugin/Plugin';\n\nimport { withTimeout } from './Lifecycle/withTimeout';\nimport { registerProcessErrors } from './processErrors';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\nimport type { CoordinatedStartup } from '#node/Lifecycle/CoordinatedStartup';\nimport type { ShutdownPhase } from '#src/lifecycle/phases';\nimport type { ChannelKeyAssert, Runtime, RuntimeAssert, Transport, TransportAssert } from '#src/plugin/options';\nimport type { CoreParamAssert, PluginArgs, PluginCtor, PluginLike } from '#src/plugin/Plugin';\nimport type { Bus } from '#subscribers/Bus';\nimport type { REST } from '@discordjs/rest';\nimport type { Config, IRateLimiter } from '@seedcord/types';\n\ninterface Attachment {\n readonly key: string;\n readonly instance: PluginLike;\n}\n\nconst RESERVED_KEYS: ReadonlySet<string> = new Set(FRAMEWORK_CHANNELS);\n\n/**\n * Base class for a plugin host, a transport `Seedcord` class.\n *\n * You attach plugins while configuring the bot. Within one startup phase, their `init()` calls run\n * one after another in attach order.\n */\n// no defaults, a default runtime of the full union contains 'edge' and RuntimeAssert rejects every plugin on that\nexport abstract class Pluggable<BotT extends Transport, BotRt extends Runtime> implements CoreBase {\n public abstract readonly config: Config;\n public abstract readonly rest: REST;\n public abstract readonly applicationId: string;\n public abstract readonly rateLimiter: IRateLimiter;\n public abstract readonly bus: Bus;\n\n /** @internal */\n readonly [HostShutdown]: CoordinatedShutdown;\n /** @internal */\n readonly [HostStartup]: CoordinatedStartup;\n\n /** Add a task that runs while the bot shuts down. */\n public readonly shutdown: Pick<CoordinatedShutdown, 'addTask'>;\n\n /** Add a task that runs while the bot starts. */\n public readonly startup: Pick<CoordinatedStartup, 'addTask'>;\n\n protected isInitialized = false;\n protected startFailed = false;\n protected readonly plugins: PluginLike[] = [];\n\n private readonly pluginLogger = new Logger('Plugins', { channel: 'plugins' });\n\n private readonly attachments: Attachment[] = [];\n private readonly completedInits = new Set<Attachment>();\n private readonly disposePhases = new Set<ShutdownPhase>();\n private pluginTasksRegistered = false;\n private initPromise?: Promise<this> | undefined;\n\n private static isInstantiated = false;\n private static liveHost?: object | undefined;\n private static liveShutdown?: CoordinatedShutdown | undefined;\n private static liveProcessErrors?: (() => void) | undefined;\n\n constructor(shutdown: CoordinatedShutdown, startup: CoordinatedStartup) {\n // a `sideEffects: false` build would drop the same call in the node entry\n assertNodeVersion(process.env.PACKAGE_NODE_RANGE ?? '', process.version);\n\n if (Pluggable.isInstantiated) {\n // signal handlers from the caller's shutdown stay on the process unless dropped here\n shutdown.removeSignalHandlers();\n throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);\n }\n\n Pluggable.isInstantiated = true;\n Pluggable.liveHost = this;\n Pluggable.liveShutdown = shutdown;\n this[HostShutdown] = shutdown;\n this[HostStartup] = startup;\n // a getter returning the slot would hand back run() and the signal handlers with it\n this.shutdown = { addTask: shutdown.addTask.bind(shutdown) };\n this.startup = { addTask: startup.addTask.bind(startup) };\n }\n\n /** @internal */\n protected init(): Promise<this> {\n // clearing the slot on a rejection lets a later retry throw its own error\n this.initPromise ??= this.runInit().catch((caught: unknown) => {\n this.initPromise = undefined;\n throw caught;\n });\n return this.initPromise;\n }\n\n private async runInit(): Promise<this> {\n if (this.isInitialized) return this;\n // a rerun after a failed startup would re-init the rolled-back plugins\n if (this.startFailed) throw new SeedcordError(SeedcordErrorCode.LifecycleRestartAfterFailure);\n\n this.registerPluginTasks();\n\n if (this.config.errors?.catchProcessErrors ?? true) {\n Pluggable.liveProcessErrors = registerProcessErrors(this, this[HostShutdown]);\n }\n\n const startupSettled: PromiseWithResolvers<void> = Promise.withResolvers();\n this[HostShutdown].gateOnStartup(startupSettled.promise);\n\n try {\n await this[HostStartup].run();\n } catch (caught) {\n await this.rollback();\n throw caught;\n } finally {\n startupSettled.resolve();\n }\n\n this.isInitialized = true;\n return this;\n }\n\n /** @internal codegen reads this to emit the `Core` augmentation */\n public get [HostPluginKeys](): readonly string[] {\n return this.attachments.map((attachment) => attachment.key);\n }\n\n /** @internal */\n protected static reset(host?: object): boolean {\n if (host !== undefined && Pluggable.liveHost !== host) return false;\n\n Pluggable.liveShutdown?.removeSignalHandlers();\n Pluggable.liveShutdown = undefined;\n Pluggable.liveProcessErrors?.();\n Pluggable.liveProcessErrors = undefined;\n Pluggable.liveHost = undefined;\n Pluggable.isInstantiated = false;\n return true;\n }\n\n /**\n * Attaches a plugin under `key`. Read the instance back as `core[key]`. `seedcord codegen`\n * writes the `Core` augmentation that types it there.\n *\n * Startup runs each plugin's `init()` in attach order within its phase.\n *\n * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.\n * Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).\n * A narrower one fails to compile here.\n *\n * @param key - Also the channel the plugin logs on. Reserved channel names throw.\n * @param args - Whatever your constructor takes after the host.\n * @throws A **SeedcordError** if you attach after the bot has started. A taken or reserved key throws too.\n * @example\n * ```ts\n * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... });\n * ```\n */\n public attach<Key extends string, Ctor extends PluginCtor>(\n this: this,\n key: ChannelKeyAssert<Key>,\n Plugin: Ctor &\n TransportAssert<InstanceType<Ctor>, BotT> &\n RuntimeAssert<InstanceType<Ctor>, BotRt> &\n CoreParamAssert<Ctor>,\n ...args: PluginArgs<Ctor>\n ): this & Record<Key, InstanceType<Ctor>> {\n if (this.isInitialized) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);\n }\n // several reserved channels are also members on a host, which the next check would report first\n if (RESERVED_KEYS.has(key)) {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginReservedChannel, [key]);\n }\n if (key in this) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [key]);\n }\n\n const instance = new Plugin(this, ...args);\n pluginLoggerOf(instance).setChannel(key);\n this.plugins.push(instance);\n this.attachments.push({ key, instance });\n\n return Object.assign(this, { [key]: instance } as Record<Key, InstanceType<Ctor>>);\n }\n\n // one combined task per phase keeps plugin inits sequential while the phase's other tasks run concurrently\n private registerPluginTasks(): void {\n if (this.pluginTasksRegistered) return;\n this.pluginTasksRegistered = true;\n\n const groups = new Map<StartupPhase, Attachment[]>();\n for (const attachment of this.attachments) {\n const phase = resolvedLifecycleSpecOf(attachment.instance).init.phase;\n const group = groups.get(phase) ?? [];\n group.push(attachment);\n groups.set(phase, group);\n }\n\n for (const [phase, group] of groups) {\n // Ready inits run in the combined Ready task, before the ready hooks\n if (phase === StartupPhase.Ready) continue;\n const budget = group.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0);\n this.startup.addTask(phase, 'plugins-init', () => this.runInits(group), budget);\n }\n\n this.registerReadyTask(groups.get(StartupPhase.Ready) ?? []);\n }\n\n private async runInits(group: readonly Attachment[]): Promise<void> {\n for (const attachment of group) {\n const { key, instance } = attachment;\n const spec = resolvedLifecycleSpecOf(instance);\n\n pluginLoggerOf(instance).utils.initialization(key, 'start');\n await withTimeout(`Plugin (${key})`, () => instance.init(), spec.init.timeout);\n pluginLoggerOf(instance).utils.initialization(key, 'end');\n\n this.completedInits.add(attachment);\n if (instance.dispose) this.registerDisposeTask(spec.dispose.phase);\n }\n }\n\n private registerReadyTask(readyInits: readonly Attachment[]): void {\n const steps = this.attachments.reduce<{ key: string; run: () => Promise<void>; timeout: number }[]>(\n (acc, a) => {\n const ready = a.instance.ready?.bind(a.instance);\n if (ready) {\n acc.push({\n key: a.key,\n run: ready,\n timeout: resolvedLifecycleSpecOf(a.instance).ready.timeout\n });\n }\n return acc;\n },\n []\n );\n if (readyInits.length === 0 && steps.length === 0) return;\n\n const budget =\n readyInits.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0) +\n steps.reduce((sum, step) => sum + step.timeout, 0);\n this.startup.addTask(\n StartupPhase.Ready,\n 'plugins-ready',\n async () => {\n await this.runInits(readyInits);\n for (const step of steps) {\n await withTimeout(`Plugin:${step.key}:ready`, step.run, step.timeout);\n }\n },\n budget\n );\n }\n\n private registerDisposeTask(phase: ShutdownPhase): void {\n if (this.disposePhases.has(phase)) return;\n this.disposePhases.add(phase);\n\n // the budget covers every dispose in this phase because they all run in this one task\n const budget = this.attachments.reduce((sum, a) => {\n const spec = resolvedLifecycleSpecOf(a.instance);\n return a.instance.dispose && spec.dispose.phase === phase ? sum + spec.dispose.timeout : sum;\n }, 0);\n this.shutdown.addTask(phase, 'plugins-dispose', () => this.runDisposals(phase), budget);\n }\n\n private async runDisposals(phase: ShutdownPhase): Promise<void> {\n const failures: unknown[] = [];\n // keep disposing the rest even when one fails\n await this.disposeCompleted(phase, (caught) => failures.push(caught));\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) {\n throw new SeedcordAggregateError(SeedcordErrorCode.PluginDisposeFailures, failures, [failures.length]);\n }\n }\n\n private async disposeCompleted(\n phase: ShutdownPhase | undefined,\n onError: (caught: unknown) => void\n ): Promise<void> {\n for (const attachment of [...this.attachments].reverse()) {\n if (!this.completedInits.has(attachment)) continue;\n const dispose = attachment.instance.dispose?.bind(attachment.instance);\n if (!dispose) continue;\n const spec = resolvedLifecycleSpecOf(attachment.instance);\n if (phase !== undefined && spec.dispose.phase !== phase) continue;\n\n try {\n await withTimeout(`Plugin:${attachment.key}:dispose`, dispose, spec.dispose.timeout);\n } catch (caught) {\n onError(caught);\n }\n }\n }\n\n private async rollback(): Promise<void> {\n this.startFailed = true;\n // log rollback dispose failures so they do not hide the startup error\n await this.disposeCompleted(undefined, (caught) => this.pluginLogger.warn('rollback dispose failed', caught));\n // prevents re-dispose when shutdown runs after rollback\n this.completedInits.clear();\n }\n}\n\n// the public `shutdown` field carries addTask alone\nexport function shutdownOf(host: Pick<Pluggable<Transport, Runtime>, typeof HostShutdown>): CoordinatedShutdown {\n return host[HostShutdown];\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { i as Bus, n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-F8yvPd60.mjs";
2
- import { a as PluginCtor, d as Transport, f as TransportAssert, i as PluginArgs, l as Runtime, o as PluginLike, s as ChannelKeyAssert, t as CoreParamAssert, u as RuntimeAssert } from "./Plugin-CG9pWbPF.mjs";
1
+ import { i as Bus, n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-C5Steaa2.mjs";
2
+ import { a as PluginCtor, d as Transport, f as TransportAssert, i as PluginArgs, l as Runtime, o as PluginLike, s as ChannelKeyAssert, t as CoreParamAssert, u as RuntimeAssert } from "./Plugin-BW2JH2_h.mjs";
3
3
  import { Logger } from "@seedcord/logger";
4
4
  import { REST } from "@discordjs/rest";
5
5
  import { HostPluginKeys, HostShutdown, HostStartup } from "@seedcord/types/internal";
@@ -145,4 +145,4 @@ declare abstract class Pluggable<BotT extends Transport, BotRt extends Runtime>
145
145
  declare function shutdownOf(host: Pick<Pluggable<Transport, Runtime>, typeof HostShutdown>): CoordinatedShutdown;
146
146
  //#endregion
147
147
  export { LifecycleTask as a, CoordinatedShutdown as i, shutdownOf as n, CoordinatedStartup as r, Pluggable as t };
148
- //# sourceMappingURL=Pluggable-DFNUIlJM.d.mts.map
148
+ //# sourceMappingURL=Pluggable-bFCmT4oB.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { n as StartupPhase, t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
2
2
  import { t as getDevChannel } from "./devChannel-CQU7hq7Q.mjs";
3
3
  import { SeedcordErrorCode } from "@seedcord/errors";
4
- import { Logger } from "@seedcord/logger";
5
4
  import { SeedcordError, SeedcordRangeError } from "@seedcord/errors/internal";
5
+ import { Logger } from "@seedcord/logger";
6
6
 
7
7
  //#region src/plugin/lifecycle.ts
8
8
  const DEFAULT_INIT_TIMEOUT_MS = 15e3;
@@ -115,4 +115,4 @@ function pluginLoggerOf(plugin) {
115
115
 
116
116
  //#endregion
117
117
  export { pluginLoggerOf as n, resolvedLifecycleSpecOf as r, Plugin as t };
118
- //# sourceMappingURL=Plugin-C6oMqWf8.mjs.map
118
+ //# sourceMappingURL=Plugin-B0SRHzMX.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Plugin-C6oMqWf8.mjs","names":[],"sources":["../src/plugin/lifecycle.ts","../src/plugin/Plugin.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordRangeError } from '@seedcord/errors/internal';\n\nimport { ShutdownPhase, StartupPhase } from '#src/lifecycle/phases';\n\n/**\n * Sets when a plugin's lifecycle methods run. Pass it as the second argument to `super()`. Omit a\n * field to keep its default.\n *\n * @see {@link StartupPhase}\n * @see {@link ShutdownPhase}\n * @example\n * ```ts\n * super(host, { init: { phase: StartupPhase.Login }, dispose: { timeout: 30_000 } });\n * ```\n */\nexport interface PluginLifecycleSpec {\n /**\n * Defaults to `StartupPhase.Configuration` and 15000ms. Set the phase to `Ready` and `init()`\n * runs ahead of every plugin's `ready()`.\n */\n init?: { phase?: StartupPhase; timeout?: number };\n /** `ready()` always runs in `StartupPhase.Ready`. Defaults to a 15000ms timeout. */\n ready?: { timeout?: number };\n /** Defaults to `ShutdownPhase.Disconnect` and 10000ms. */\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 SeedcordRangeError(SeedcordErrorCode.PluginInvalidLifecycleTimeout, [pluginName, field, declared]);\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 { RuntimeBrand, TransportBrand } from './brands';\nimport { resolveLifecycleSpec } from './lifecycle';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { ResolvedPluginLifecycleSpec, PluginLifecycleSpec } from './lifecycle';\nimport type { TransportOf, PluginOptions, RuntimeOf } from './options';\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/** @internal */\nconst loggerSlot = Symbol('seedcord.plugin.logger');\n\n/**\n * Base class for a seedcord plugin. Extend it and implement `init()`. `this.core` is the running\n * bot.\n *\n * Your constructor takes the host first and passes it to `super()`. Add your own parameters after\n * it. `attach()` types them at the call site.\n *\n * @typeParam Opts - Where this plugin may attach, checked only at compile time.\n * @typeParam TCore - The `Core` each transport binds `this.core` to.\n *\n * @example\n * ```ts\n * class Analytics extends Plugin<{ transport: 'gateway' }> {\n * constructor(\n * host: CoreBase,\n * private readonly apiKey: string\n * ) {\n * super(host, { init: { phase: StartupPhase.Login } });\n * }\n *\n * public async init(): Promise<void> {\n * await this.connect(this.apiKey);\n * }\n * }\n *\n * seedcord.attach('analytics', Analytics, apiKey);\n * ```\n */\nexport abstract class Plugin<Opts extends PluginOptions = {}, TCore extends CoreBase = CoreBase>\n implements Initializeable, HmrAware\n{\n // phantom, never set at runtime.\n /** @internal */\n declare readonly [TransportBrand]?: TransportOf<Opts>;\n /** @internal */\n declare readonly [RuntimeBrand]?: RuntimeOf<Opts>;\n\n /** @internal */\n readonly [resolvedSpecSlot]: ResolvedPluginLifecycleSpec;\n /** @internal */\n readonly [loggerSlot]: Logger;\n\n /** Logs under the plugin's class name, on the channel its attach key sets. */\n protected 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[loggerSlot] = this.logger;\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 /**\n * Runs in `StartupPhase.Configuration` by default.\n * Move it to a different phase with the {@link PluginLifecycleSpec}.\n */\n abstract init(): Promise<void>;\n\n /**\n * Runs in the Ready phase, after every attached plugin's `init()` has resolved. The other Ready\n * tasks run alongside it, including the http server binding its port.\n *\n * Override it when your work needs a logged-in client or a resolved application id.\n */\n ready?(): Promise<void>;\n\n /** Runs during teardown, in `ShutdownPhase.Disconnect` by default. It never runs when `init()` has thrown. */\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\n/** @internal */\nexport function pluginLoggerOf(plugin: PluginLike): Logger {\n return plugin[loggerSlot];\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<\n Plugin,\n 'init' | 'ready' | 'dispose' | 'onHmr' | typeof resolvedSpecSlot | typeof loggerSlot\n>;\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":";;;;;;;AAmCA,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,mBAAmB,kBAAkB,+BAA+B;EAAC;EAAY;EAAO;CAAQ,CAAC;CAE/G,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;;;;;ACnDA,MAAM,mBAAmB,OAAO,sBAAsB;;AAEtD,MAAM,aAAa,OAAO,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BlD,IAAsB,SAAtB,MAEA;CAiByB;;CATrB,CAAU;;CAEV,CAAU;;CAGV,AAAmB;CAGnB,YACI,AAAiB,MACjB,MACF;EAFmB;EAGjB,KAAK,SAAS,IAAI,OAAO,KAAK,YAAY,IAAI;EAC9C,KAAK,cAAc,KAAK;EACxB,KAAK,oBAAoB,qBAAqB,MAAM,KAAK,YAAY,IAAI;CAC7E;;CAGA,IAAc,OAAc;EAExB,OAAO,KAAK;CAChB;;;;;;CAwBA,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;;AAGA,SAAgB,eAAe,QAA4B;CACvD,OAAO,OAAO;AAClB"}
1
+ {"version":3,"file":"Plugin-B0SRHzMX.mjs","names":[],"sources":["../src/plugin/lifecycle.ts","../src/plugin/Plugin.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordRangeError } from '@seedcord/errors/internal';\n\nimport { ShutdownPhase, StartupPhase } from '#src/lifecycle/phases';\n\n/**\n * Sets when a plugin's lifecycle methods run. Pass it as the second argument to `super()`. Omit a\n * field to keep its default.\n *\n * @see {@link StartupPhase}\n * @see {@link ShutdownPhase}\n * @example\n * ```ts\n * super(host, { init: { phase: StartupPhase.Login }, dispose: { timeout: 30_000 } });\n * ```\n */\nexport interface PluginLifecycleSpec {\n /**\n * Defaults to `StartupPhase.Configuration` and 15000ms. Set the phase to `Ready` and `init()`\n * runs ahead of every plugin's `ready()`.\n */\n init?: { phase?: StartupPhase; timeout?: number };\n /** `ready()` always runs in `StartupPhase.Ready`. Defaults to a 15000ms timeout. */\n ready?: { timeout?: number };\n /** Defaults to `ShutdownPhase.Disconnect` and 10000ms. */\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 SeedcordRangeError(SeedcordErrorCode.PluginInvalidLifecycleTimeout, [pluginName, field, declared]);\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 { RuntimeBrand, TransportBrand } from './brands';\nimport { resolveLifecycleSpec } from './lifecycle';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { ResolvedPluginLifecycleSpec, PluginLifecycleSpec } from './lifecycle';\nimport type { TransportOf, PluginOptions, RuntimeOf } from './options';\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/** @internal */\nconst loggerSlot = Symbol('seedcord.plugin.logger');\n\n/**\n * Base class for a seedcord plugin. Extend it and implement `init()`. `this.core` is the running\n * bot.\n *\n * Your constructor takes the host first and passes it to `super()`. Add your own parameters after\n * it. `attach()` types them at the call site.\n *\n * @typeParam Opts - Where this plugin may attach, checked only at compile time.\n * @typeParam TCore - The `Core` each transport binds `this.core` to.\n *\n * @example\n * ```ts\n * class Analytics extends Plugin<{ transport: 'gateway' }> {\n * constructor(\n * host: CoreBase,\n * private readonly apiKey: string\n * ) {\n * super(host, { init: { phase: StartupPhase.Login } });\n * }\n *\n * public async init(): Promise<void> {\n * await this.connect(this.apiKey);\n * }\n * }\n *\n * seedcord.attach('analytics', Analytics, apiKey);\n * ```\n */\nexport abstract class Plugin<Opts extends PluginOptions = {}, TCore extends CoreBase = CoreBase>\n implements Initializeable, HmrAware\n{\n // phantom, never set at runtime.\n /** @internal */\n declare readonly [TransportBrand]?: TransportOf<Opts>;\n /** @internal */\n declare readonly [RuntimeBrand]?: RuntimeOf<Opts>;\n\n /** @internal */\n readonly [resolvedSpecSlot]: ResolvedPluginLifecycleSpec;\n /** @internal */\n readonly [loggerSlot]: Logger;\n\n /** Logs under the plugin's class name, on the channel its attach key sets. */\n protected 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[loggerSlot] = this.logger;\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 /**\n * Runs in `StartupPhase.Configuration` by default.\n * Move it to a different phase with the {@link PluginLifecycleSpec}.\n */\n abstract init(): Promise<void>;\n\n /**\n * Runs in the Ready phase, after every attached plugin's `init()` has resolved. The other Ready\n * tasks run alongside it, including the http server binding its port.\n *\n * Override it when your work needs a logged-in client or a resolved application id.\n */\n ready?(): Promise<void>;\n\n /** Runs during teardown, in `ShutdownPhase.Disconnect` by default. It never runs when `init()` has thrown. */\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\n/** @internal */\nexport function pluginLoggerOf(plugin: PluginLike): Logger {\n return plugin[loggerSlot];\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<\n Plugin,\n 'init' | 'ready' | 'dispose' | 'onHmr' | typeof resolvedSpecSlot | typeof loggerSlot\n>;\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":";;;;;;;AAmCA,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,mBAAmB,kBAAkB,+BAA+B;EAAC;EAAY;EAAO;CAAQ,CAAC;CAE/G,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;;;;;ACnDA,MAAM,mBAAmB,OAAO,sBAAsB;;AAEtD,MAAM,aAAa,OAAO,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BlD,IAAsB,SAAtB,MAEA;CAiByB;;CATrB,CAAU;;CAEV,CAAU;;CAGV,AAAmB;CAGnB,YACI,AAAiB,MACjB,MACF;EAFmB;EAGjB,KAAK,SAAS,IAAI,OAAO,KAAK,YAAY,IAAI;EAC9C,KAAK,cAAc,KAAK;EACxB,KAAK,oBAAoB,qBAAqB,MAAM,KAAK,YAAY,IAAI;CAC7E;;CAGA,IAAc,OAAc;EAExB,OAAO,KAAK;CAChB;;;;;;CAwBA,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;;AAGA,SAAgB,eAAe,QAA4B;CACvD,OAAO,OAAO;AAClB"}
@@ -1,4 +1,4 @@
1
- import { n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-F8yvPd60.mjs";
1
+ import { n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-C5Steaa2.mjs";
2
2
  import { Logger } from "@seedcord/logger";
3
3
  import { FrameworkChannel, HmrAware, HmrUpdateEvent, Tail, TypedExclude } from "@seedcord/types";
4
4
  //#region src/plugin/brands.d.ts
@@ -190,4 +190,4 @@ type CoreParamTooNarrow = Record<'this plugin constructor must take CoreBase as
190
190
  type CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0] ? unknown : CoreParamTooNarrow;
191
191
  //#endregion
192
192
  export { PluginCtor as a, PluginOptions as c, Transport as d, TransportAssert as f, PluginArgs as i, Runtime as l, Initializeable as n, PluginLike as o, PluginLifecycleSpec as p, Plugin as r, ChannelKeyAssert as s, CoreParamAssert as t, RuntimeAssert as u };
193
- //# sourceMappingURL=Plugin-CG9pWbPF.d.mts.map
193
+ //# sourceMappingURL=Plugin-BW2JH2_h.d.mts.map
@@ -1,8 +1,9 @@
1
+ import { H as Notice, R as InteractionRoutes, r as CoreBase } from "./phases-C5Steaa2.mjs";
1
2
  import { t as Constructor } from "./basic-Dng__Ena.mjs";
2
- import { b as Notice, r as CoreBase, z as InteractionRoutes } from "./phases-F8yvPd60.mjs";
3
3
  import "reflect-metadata";
4
+ import { AnyCustomId, CustomId, CustomIdField } from "@seedcord/custom-id";
4
5
  import { ActionRowBuilder, ButtonBuilder, ChannelSelectMenuBuilder, CheckboxBuilder, CheckboxGroupBuilder, CheckboxGroupOptionBuilder, ContainerBuilder, ContextMenuCommandBuilder, EmbedBuilder, FileBuilder, FileUploadBuilder, LabelBuilder, MediaGalleryBuilder, MentionableSelectMenuBuilder, ModalBuilder, RadioGroupBuilder, RadioGroupOptionBuilder, RoleSelectMenuBuilder, SectionBuilder, SeparatorBuilder, SlashCommandBuilder, SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextDisplayBuilder, TextInputBuilder, UserSelectMenuBuilder } from "@discordjs/builders";
5
- import { APIMessageTopLevelComponent, ApplicationCommandType, ChannelType, Snowflake } from "discord-api-types/v10";
6
+ import { APIMessageTopLevelComponent, ApplicationCommandType, ChannelType } from "discord-api-types/v10";
6
7
  import { ReplyResponse } from "@seedcord/types";
7
8
  //#region src/registries/ContextMenuRegistry.d.ts
8
9
  /** The two context-menu command kinds Discord defines. */
@@ -263,30 +264,6 @@ type B = IsTrueFixed<never>;
263
264
  */
264
265
  type IsNever<T> = [T] extends [never] ? true : false;
265
266
  //#endregion
266
- //#region ../../node_modules/.pnpm/type-fest@5.8.0/node_modules/type-fest/source/non-empty-tuple.d.ts
267
- /**
268
- Matches any non-empty tuple.
269
-
270
- @example
271
- ```
272
- import type {NonEmptyTuple} from 'type-fest';
273
-
274
- const sum = (...numbers: NonEmptyTuple<number>) => numbers.reduce((total, value) => total + value, 0);
275
-
276
- sum(1, 2, 3);
277
- // Ok
278
-
279
- // @ts-expect-error
280
- sum();
281
- // Error: Expected at least 1 arguments, but got 0.
282
- ```
283
-
284
- @see {@link RequireAtLeastOne} for objects
285
-
286
- @category Array
287
- */
288
- type NonEmptyTuple<T = unknown> = readonly [T, ...T[]];
289
- //#endregion
290
267
  //#region ../../node_modules/.pnpm/type-fest@5.8.0/node_modules/type-fest/source/promisable.d.ts
291
268
  /**
292
269
  Create a type that represents either the value or the value wrapped in `PromiseLike`.
@@ -348,181 +325,6 @@ declare function RegisterCommand(scope: 'global'): (ctor: CommandCtor) => void;
348
325
  */
349
326
  declare function RegisterCommand(scope: 'guild', guilds: string[]): (ctor: CommandCtor) => void;
350
327
  //#endregion
351
- //#region src/customId/Field.d.ts
352
- /**
353
- * One field in a customId shape, its wire kind plus the type it decodes to.
354
- *
355
- * @internal
356
- */
357
- interface CustomIdField<Decoded> {
358
- /** Which wire encoding this field uses. */
359
- readonly kind: 'snowflake' | 'uuid' | 'int' | 'bool' | 'oneOf' | 'string';
360
- /** Lower bound, for a bounded int field. */
361
- readonly min?: number;
362
- /** Upper bound, for a bounded int field. */
363
- readonly max?: number;
364
- /** The allowed values, for a oneOf field. */
365
- readonly choices?: readonly string[];
366
- /** Whether this field also carries null. */
367
- readonly nullable?: boolean;
368
- /** Phantom only, carries the decoded type and is never set at runtime. */
369
- readonly decoded?: Decoded;
370
- }
371
- /**
372
- * The set of fields a customId carries, keyed by name.
373
- *
374
- * @internal
375
- */
376
- type CustomIdShape = Record<string, CustomIdField<unknown>>;
377
- /**
378
- * The decoded result, each field name mapped to its decoded type.
379
- *
380
- * @internal
381
- */
382
- type DecodedParams<Shape extends CustomIdShape> = { [Name in keyof Shape]: Shape[Name] extends CustomIdField<infer Decoded> ? Decoded : never; };
383
- //#endregion
384
- //#region src/customId/CustomId.d.ts
385
- /** Per-field options every {@link CustomId} chain method takes. */
386
- interface FieldOptions<Nullable extends boolean = boolean> {
387
- /**
388
- * Whether the field also carries null. A nullable field costs one extra slot on the wire.
389
- *
390
- * @defaultValue `false`
391
- */
392
- nullable?: Nullable;
393
- }
394
- type Nullish<Decoded, Nullable extends boolean> = Nullable extends true ? Decoded | null : Decoded;
395
- /** Strip the layout hash off the routeKey to recover the stable prefix the controller routes by. @internal */
396
- declare function prefixOf(wire: string): string;
397
- /**
398
- * A typed customId. The single source of truth shared by the component that mints it and the handler
399
- * that reads it. This gives you typed reads on the `.customId` field in components. Values are packed
400
- * into a compact wire string, which fits more of them inside Discord's 100-char limit.
401
- *
402
- * @typeParam Prefix - The stable route prefix, e.g. 'approve'.
403
- * @typeParam Shape - The accumulated fields, filled in by the chain.
404
- *
405
- * @example
406
- * ```ts
407
- * const ApproveId = new CustomId('approve')
408
- * .snowflake('userId')
409
- * .oneOf('action', ['approve', 'deny']);
410
- *
411
- * // Set the custom id on a button when creating it.
412
- * new ButtonBuilder().setCustomId(ApproveId.encode({ userId: '123', action: 'approve' }));
413
- *
414
- * // in the handler, userId comes back a string
415
- * const { userId, action } = this.params; // userId: string, action: 'approve' | 'deny'
416
- * await this.event.guild?.members.fetch(userId);
417
- * ```
418
- */
419
- declare class CustomId<Prefix extends string, Shape extends CustomIdShape = {}> {
420
- /** The stable route prefix, e.g. 'approve'. */
421
- readonly prefix: Prefix;
422
- /** The field definitions accumulated by the chain, keyed by name. */
423
- readonly shape: Shape;
424
- /** The prefix plus a short hash of the shape, the part of the wire before the colon. */
425
- readonly routeKey: string;
426
- constructor(prefix: Prefix, shape?: Shape);
427
- private add;
428
- /**
429
- * Add a Discord ID field, decoded as a string (the `Snowflake` type from discord-api-types).
430
- *
431
- * @example
432
- * ```ts
433
- * new CustomId('ban').snowflake('userId');
434
- * ```
435
- */
436
- snowflake<Name extends string, const Nullable extends boolean = false>(name: Name, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<Snowflake, Nullable>>>>;
437
- /**
438
- * Add a UUID field, decoded as a lowercase uuid string.
439
- *
440
- * @example
441
- * ```ts
442
- * new CustomId('ticket').uuid('ticketId');
443
- * ```
444
- */
445
- uuid<Name extends string, const Nullable extends boolean = false>(name: Name, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<string, Nullable>>>>;
446
- /**
447
- * Add an integer field with no bounds, for a value up to 2^53.
448
- *
449
- * @example
450
- * ```ts
451
- * new CustomId('shop').int('amount');
452
- * ```
453
- */
454
- int<Name extends string, const Nullable extends boolean = false>(name: Name, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<number, Nullable>>>>;
455
- /**
456
- * Add an integer field bounded by min and max, so it packs into fewer characters on the wire.
457
- *
458
- * @example
459
- * ```ts
460
- * new CustomId('paginate').int('page', 1, 50);
461
- * ```
462
- */
463
- int<Name extends string, const Nullable extends boolean = false>(name: Name, min: number, max: number, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<number, Nullable>>>>;
464
- /**
465
- * Add a boolean flag.
466
- *
467
- * @example
468
- * ```ts
469
- * new CustomId('settings').bool('silent');
470
- * ```
471
- */
472
- bool<Name extends string, const Nullable extends boolean = false>(name: Name, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<boolean, Nullable>>>>;
473
- /**
474
- * Add a field that is one value from a fixed list, decoded as the literal union. No `as const` needed.
475
- *
476
- * @example
477
- * ```ts
478
- * new CustomId('poll').oneOf('choice', ['yes', 'no', 'abstain']);
479
- * ```
480
- */
481
- oneOf<Name extends string, const Choices extends NonEmptyTuple<string>, const Nullable extends boolean = false>(name: Name, choices: Choices, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<Choices[number], Nullable>>>>;
482
- /**
483
- * Add a free short text field. Avoid it where possible. It cannot be packed, which makes it the most
484
- * expensive field on the wire.
485
- *
486
- * @example
487
- * ```ts
488
- * new CustomId('note').str('message');
489
- * ```
490
- */
491
- str<Name extends string, const Nullable extends boolean = false>(name: Name, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<string, Nullable>>>>;
492
- /**
493
- * Mint a wire string from values. Throws if a value is out of its field's range or the wire is over 100 chars.
494
- *
495
- * @param values - One value per field, typed by the chain.
496
- * @returns The wire string to put on the component's customId.
497
- */
498
- encode(values: DecodedParams<Shape>): string;
499
- /**
500
- * Read a wire string back into values.
501
- *
502
- * - This will refuse with a default Notice when the shape changed since the wire was minted.
503
- * - Or refuse with a different default Notice on a corrupt or foreign wire.
504
- *
505
- * @param wire - The customId string from the interaction.
506
- * @returns The decoded values, typed by the chain.
507
- */
508
- decode(wire: string): DecodedParams<Shape>;
509
- /** True if this wire was minted from this customId's prefix, ignoring the shape hash. */
510
- owns(wire: string): boolean;
511
- }
512
- /** @internal */
513
- type AnyCustomId = CustomId<string, CustomIdShape>;
514
- /**
515
- * The outcome of decoding a wire against several customIds, the matched prefix paired with its values.
516
- *
517
- * @internal
518
- */
519
- type DecodedRoute<Defs extends readonly AnyCustomId[]> = { [Index in keyof Defs]: Defs[Index] extends AnyCustomId ? {
520
- readonly prefix: Defs[Index]['prefix'];
521
- readonly params: DecodedParams<Defs[Index]['shape']>;
522
- } : never; }[number];
523
- /** @internal */
524
- declare function decodeFor<Defs extends readonly AnyCustomId[]>(defs: Defs, wire: string): DecodedRoute<Defs>;
525
- //#endregion
526
328
  //#region src/decorators/interactionRoutes.d.ts
527
329
  /** The select menu kinds the select route decorators take. */
528
330
  declare enum SelectMenuKind {
@@ -983,5 +785,5 @@ type PageCursor<Prefix extends string> = CustomId<Prefix, {
983
785
  /** Build the page cursor for a paginator. */
984
786
  declare function pageCursor<Prefix extends string>(prefix: Prefix): PageCursor<Prefix>;
985
787
  //#endregion
986
- export { Promisable as $, serializeReply as A, contextMenuRouteOf as B, Gate as C, defineEffectGate as D, RequiredOf as E, ContextMenuNamesBrand as F, AnyCustomId as G, selectMenuRouteOf as H, SlashRouteBrand as I, decodeFor as J, CustomId as K, RoutableConstructor as L, ComponentDefsBrand as M, ComponentKindBrand as N, defineGate as O, ContextMenuKindBrand as P, RegisterCommand as Q, SelectMenuKind as R, EffectGate as S, GuildPermissionsContext as T, storeComponentRoute as U, interactionRoutesOf as V, storeInteractionRoute as W, DecodedParams as X, prefixOf as Y, CommandMeta as Z, pickNotice as _, AutocompleteOptions as a, CacheFor as at, JoinNames as b, FocusedField as c, SlashOption as ct, RequirePermissionsOptions as d, ContextMenuKind as dt, IsNever as et, RequireRole as f, MenuCacheFor as ft, GateNoticeOptions as g, PermissionScope as h, UserContextMenuRegistry as ht, AutocompletableNames as i, RowType as it, AutocompleteRouteBrand as j, SerializedReply as k, RequireBotPermissions as l, SlashRegistry as lt, PermissionNames as m, NamesFor as mt, PageCursor as n, RowComponent as nt, ChoiceValueOf as o, OptionKind as ot, RequireRoleOptions as p, MessageContextMenuRegistry as pt, FieldOptions as q, pageCursor as r, BuilderType as rt, EntryFor as s, RouteCache as st, PAGE_MAX as t, BuilderComponent as tt, RequirePermissions as u, SlashRouteEntry as ut, GateFitsWith as v, GateContextBase as w, TwoOrMore as x, IntersectRequired as y, areRoutes as z };
987
- //# sourceMappingURL=cursor-BcRfmTSo.d.mts.map
788
+ export { CacheFor as $, serializeReply as A, contextMenuRouteOf as B, Gate as C, defineEffectGate as D, RequiredOf as E, ContextMenuNamesBrand as F, CommandMeta as G, selectMenuRouteOf as H, SlashRouteBrand as I, IsNever as J, RegisterCommand as K, RoutableConstructor as L, ComponentDefsBrand as M, ComponentKindBrand as N, defineGate as O, ContextMenuKindBrand as P, RowType as Q, SelectMenuKind as R, EffectGate as S, GuildPermissionsContext as T, storeComponentRoute as U, interactionRoutesOf as V, storeInteractionRoute as W, RowComponent as X, BuilderComponent as Y, BuilderType as Z, pickNotice as _, AutocompleteOptions as a, ContextMenuKind as at, JoinNames as b, FocusedField as c, NamesFor as ct, RequirePermissionsOptions as d, OptionKind as et, RequireRole as f, GateNoticeOptions as g, PermissionScope as h, AutocompletableNames as i, SlashRouteEntry as it, AutocompleteRouteBrand as j, SerializedReply as k, RequireBotPermissions as l, UserContextMenuRegistry as lt, PermissionNames as m, PageCursor as n, SlashOption as nt, ChoiceValueOf as o, MenuCacheFor as ot, RequireRoleOptions as p, Promisable as q, pageCursor as r, SlashRegistry as rt, EntryFor as s, MessageContextMenuRegistry as st, PAGE_MAX as t, RouteCache as tt, RequirePermissions as u, GateFitsWith as v, GateContextBase as w, TwoOrMore as x, IntersectRequired as y, areRoutes as z };
789
+ //# sourceMappingURL=cursor-CO_MTgdZ.d.mts.map