@seedcord/core 0.11.0 → 0.12.0-next.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.
Files changed (54) hide show
  1. package/README.md +3 -3
  2. package/dist/Bus-VJw-QpoY.mjs +986 -0
  3. package/dist/Bus-VJw-QpoY.mjs.map +1 -0
  4. package/dist/{CoordinatedShutdown-H5YjZn8A.d.mts → CoordinatedShutdown-fPKLWjc5.d.mts} +13 -4
  5. package/dist/{CoordinatedStartup-CGnJrqcl.mjs → CoordinatedStartup-Zw_Lk7Zk.mjs} +22 -20
  6. package/dist/CoordinatedStartup-Zw_Lk7Zk.mjs.map +1 -0
  7. package/dist/Generated-ByC53_A2.d.mts +26 -0
  8. package/dist/HmrModuleHandler-Btgu8r96.mjs +165 -0
  9. package/dist/HmrModuleHandler-Btgu8r96.mjs.map +1 -0
  10. package/dist/{PluginHost-DB4j8unu.d.mts → PluginHost-DFnkIldj.d.mts} +43 -15
  11. package/dist/PluginHost-DTtxWRiL.mjs +552 -0
  12. package/dist/PluginHost-DTtxWRiL.mjs.map +1 -0
  13. package/dist/{PluginLifecycle-AhQ46NIr.mjs → PluginLifecycle-hx5zUc-P.mjs} +4 -4
  14. package/dist/PluginLifecycle-hx5zUc-P.mjs.map +1 -0
  15. package/dist/{ServerHost-DYeh0KTk.d.mts → ServerHost-B5o3ScCF.d.mts} +7 -6
  16. package/dist/{ServerHost-CtmAhEXn.mjs → ServerHost-CDaEDR4C.mjs} +17 -6
  17. package/dist/{ServerHost-CtmAhEXn.mjs.map → ServerHost-CDaEDR4C.mjs.map} +1 -1
  18. package/dist/bindViteEnv-NFzvdqlT.mjs +32 -0
  19. package/dist/{bindViteEnv-DWq5rftJ.mjs.map → bindViteEnv-NFzvdqlT.mjs.map} +1 -1
  20. package/dist/env-internal.index.mjs +1 -1
  21. package/dist/hmr.index.d.mts +20 -20
  22. package/dist/hmr.index.mjs +1 -1
  23. package/dist/{index-Du71Xx69.d.mts → index-DGHng1IN.d.mts} +115 -201
  24. package/dist/index.d.mts +321 -74
  25. package/dist/index.mjs +323 -102
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/internal.index.d.mts +223 -83
  28. package/dist/internal.index.mjs +573 -74
  29. package/dist/internal.index.mjs.map +1 -1
  30. package/dist/node-internal.index.d.mts +8 -4
  31. package/dist/node-internal.index.mjs +19 -6
  32. package/dist/node-internal.index.mjs.map +1 -1
  33. package/dist/node.index.d.mts +3 -3
  34. package/dist/node.index.mjs +22 -28
  35. package/dist/node.index.mjs.map +1 -1
  36. package/dist/{phases-FTM69J3a.d.mts → phases-C2QCSqRu.d.mts} +164 -55
  37. package/dist/plugin.index.d.mts +3 -2
  38. package/dist/plugin.index.mjs +1 -1
  39. package/dist/subscribers-CGuVjbrg.mjs +982 -0
  40. package/dist/subscribers-CGuVjbrg.mjs.map +1 -0
  41. package/dist/{bindViteEnv-DWq5rftJ.mjs → usingCtx-Cf2cZi96.mjs} +1 -31
  42. package/package.json +14 -11
  43. package/dist/CoordinatedStartup-CGnJrqcl.mjs.map +0 -1
  44. package/dist/HmrModuleHandler-8ZC-UuZh.mjs +0 -162
  45. package/dist/HmrModuleHandler-8ZC-UuZh.mjs.map +0 -1
  46. package/dist/PluginHost-B8kxLOeb.mjs +0 -245
  47. package/dist/PluginHost-B8kxLOeb.mjs.map +0 -1
  48. package/dist/PluginLifecycle-AhQ46NIr.mjs.map +0 -1
  49. package/dist/phases-BauKJOTT.mjs +0 -31
  50. package/dist/phases-BauKJOTT.mjs.map +0 -1
  51. package/dist/publishDefault-D3dAyzZ9.mjs +0 -7
  52. package/dist/publishDefault-D3dAyzZ9.mjs.map +0 -1
  53. package/dist/subscribers-Ce3SWy23.mjs +0 -1913
  54. package/dist/subscribers-Ce3SWy23.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PluginHost-DTtxWRiL.mjs","names":[],"sources":["../src/hmr/devChannel.ts","../src/plugin/lifecycle.ts","../src/plugin/Plugin.ts","../src/kv/GuardedDriver.ts","../src/kv/KeyedQueue.ts","../src/kv/KvCollection.ts","../src/kv/Kv.ts","../src/rateLimiter/KvRateLimiter.ts","../src/plugin/splitPluginKey.ts","../src/plugin/PluginHost.ts"],"sourcesContent":["import type { DevChannel, SeedcordCliEvents, SeedcordFrameworkEvents } from '@seedcord/types/internal';\n\nexport type CliChannel = DevChannel<SeedcordFrameworkEvents, SeedcordCliEvents>;\n\n// one module instance per process, the HmrManager write and every framework read share it\nlet channel: CliChannel | undefined;\n\n/** @internal */\nexport function setDevChannel(next: CliChannel | undefined): void {\n channel = next;\n}\n\n/** @internal */\nexport function getDevChannel(): CliChannel | undefined {\n return channel;\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordRangeError } from '@seedcord/errors/internal';\n\nimport { ShutdownPhase, StartupPhase } from '#src/lifecycle/phases';\n\nimport type { TaskShutdownPhase } 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?: TaskShutdownPhase; 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: TaskShutdownPhase; 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.LifecycleInvalidTimeout, [\n `${pluginName}'s lifecycle ${field}.timeout`,\n declared\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 { SeedcordTypeError } 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 { Kv } from '#src/kv/Kv';\nimport type { ScopedSpec } from './brands';\nimport type { ResolvedPluginLifecycleSpec, PluginLifecycleSpec } from './lifecycle';\nimport type {\n DeclaredScope,\n TransportOf,\n PluginOptions,\n PluginSpecArgs,\n RuntimeOf,\n ScopeAxis,\n Runtime,\n RuntimeAssert,\n Transport,\n TransportAssert\n} from './options';\nimport type { Tail, FrameworkChannel, HmrAware, HmrUpdateEvent } from '@seedcord/types';\nimport type { Promisable } from 'type-fest';\n\ninterface Attaching {\n readonly key: string;\n readonly kv: Kv;\n}\n\n// set only while attach() runs `new Plugin()`\nlet attaching: Attaching | undefined;\n\nexport interface Initializeable {\n init(): Promisable<void>;\n}\n\n/** @internal */\nconst resolvedSpecSlot = Symbol('seedcord:plugin:spec');\n/** @internal */\nconst loggerSlot = Symbol('seedcord:plugin:logger');\n/** @internal */\nconst scopeSlot = Symbol('seedcord:plugin:scope');\n/** @internal */\nexport const FixedScope = Symbol('seedcord:plugin:fixed-scope');\n\n/**\n * Base class for a seedcord plugin. Extend it and implement `init()`. `this.core` is the running\n * bot.\n *\n * Take the host as your constructor's first parameter and pass it to `super()`. Add your own parameters after\n * it. `attach()` types them at the call site.\n *\n * A plugin that narrows `transport` or `runtime` in `Opts` passes the same values to `super()`.\n * `attach()` throws when either one differs from the bot's.\n *\n * @typeParam Opts - Where this plugin may attach.\n * @typeParam TCore - The `Core` each transport binds `this.core` to.\n * @typeParam Fixed - The `transport` and `runtime` values a transport's own `Plugin` base sets. Leave it out.\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, { transport: 'gateway', 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<\n Opts extends PluginOptions = {},\n TCore extends CoreBase = CoreBase,\n Fixed extends ScopeAxis = never\n>\n implements Initializeable, HmrAware\n{\n /** @internal */\n protected static readonly [FixedScope]: Partial<DeclaredScope> = {};\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 /** @internal */\n readonly [scopeSlot]: DeclaredScope;\n\n /** Logs under the plugin's class name, on the channel its attach key sets. */\n protected readonly logger: Logger;\n\n readonly #kv: Kv | undefined;\n\n // CoreBase here keeps the augmented Core out of ConstructorParameters, which attach reads\n constructor(\n private readonly host: CoreBase,\n ...[spec]: PluginSpecArgs<Opts, Fixed>\n ) {\n // cleared here. a plugin this constructor builds by hand doesn't get the outer key\n const attachment = attaching;\n attaching = undefined;\n this.#kv = attachment?.kv;\n\n this.logger = new Logger(this.constructor.name, attachment && { channel: attachment.key });\n this[loggerSlot] = this.logger;\n this[resolvedSpecSlot] = resolveLifecycleSpec(spec, this.constructor.name);\n\n const passed: (PluginLifecycleSpec & Partial<DeclaredScope>) | undefined = spec;\n const fixed = new.target[FixedScope];\n this[scopeSlot] = {\n transport: fixed.transport ?? passed?.transport ?? 'any',\n runtime: fixed.runtime ?? passed?.runtime ?? 'any'\n };\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 * This plugin's records in the bot's `config.kv`, stored under its attach key. Two attachments of\n * one plugin class each get their own records. Renaming the attach key leaves the records under\n * the old key behind.\n *\n * @throws A **SeedcordTypeError** when something built the plugin with `new` outside `attach()`.\n * @example\n * ```ts\n * // works in a field initializer, the constructor, and every method\n * private readonly jobs = this.kv.collection<Job>('job');\n * ```\n */\n protected get kv(): Kv {\n if (!this.#kv)\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKvOutsideAttach, [this.constructor.name]);\n return this.#kv;\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(): Promisable<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?(): Promisable<void>;\n\n /**\n * Runs during teardown, in `ShutdownPhase.Disconnect` by default. seedcord skips it when `init()`\n * has thrown.\n *\n * When `init()` outlasts its timeout, seedcord calls this once that `init()` resolves, outside any\n * shutdown phase. The process must still be alive for that call to happen.\n */\n dispose?(): Promisable<void>;\n\n /**\n * Returns data that `seedcord codegen` writes into the bot's `seedcord-gen.ts` as a type.\n * Read it back with `Generated<typeof YourPlugin>`. Return plain data, or classes and objects\n * that a project file or your plugin's package exports. The class needs a `static readonly id`.\n *\n * `seedcord codegen` calls this without running `init()`. Read only what the constructor stored\n * and the bot's files.\n */\n codegen?(): Promise<unknown>;\n\n /**\n * Rejects the options this plugin got.\n *\n * @param reason - Why the options are invalid.\n * @throws A **SeedcordTypeError** every time, with the plugin class name and `reason` in its message.\n */\n protected rejectOptions(reason: string): never {\n throw new SeedcordTypeError(SeedcordErrorCode.PluginOptionsRejected, [this.constructor.name, reason]);\n }\n\n /** Override to reload plugin state on an HMR update. */\n public onHmr(_event: HmrUpdateEvent): Promisable<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. A relative one resolves against `root` from `seedcord.config.ts`.\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 extendsThisCorePlugin(ctor: PluginCtor): boolean {\n return ctor.prototype instanceof Plugin;\n}\n\n/** @internal */\nexport function pluginLoggerOf(plugin: PluginLike): Logger {\n return plugin[loggerSlot];\n}\n\n/** @internal */\nexport function declaredScopeOf(plugin: PluginLike): DeclaredScope {\n return plugin[scopeSlot];\n}\n\n/** @internal */\nexport function constructAttached<TPlugin>(key: string, kv: Kv, construct: () => TPlugin): TPlugin {\n attaching = { key, kv };\n try {\n return construct();\n } finally {\n attaching = undefined;\n }\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 | typeof scopeSlot\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> =\n InheritsScopedConstructor<Ctor> extends true ? [] : Tail<ConstructorParameters<Ctor>>;\n\n// a narrowed plugin with no constructor of its own inherits the base one, whose spec carries ScopedSpec\ntype InheritsScopedConstructor<Ctor extends PluginCtor> =\n Tail<ConstructorParameters<Ctor>> extends [infer Spec, ...unknown[]]\n ? typeof ScopedSpec extends keyof Spec\n ? true\n : false\n : false;\n\n/** @internal */\nexport type Attached<Key extends string, Instance> = Key extends `${infer Group}.${infer Leaf}`\n ? Record<Group, Record<Leaf, Instance>>\n : Record<Key, Instance>;\n\n// attach writes the key onto the host, so a key like toString would shadow the one on Object.prototype\n// eslint-disable-next-line @typescript-eslint/no-wrapper-object-types -- lint:fix swaps this for `keyof object`, which is never\ntype HostMember<Host> = keyof Host | keyof Object;\n\n// a string argument against an object type renders only as `string is not assignable`\n/** @internal */\nexport type AttachKeyAssert<Key extends string, Host> = Key extends ''\n ? 'A plugin key needs a name.'\n : Key extends `${infer Group}.${infer Leaf}`\n ? Leaf extends `${string}.${string}`\n ? `'${Key}' has more than one dot. A plugin key takes one dot at most.`\n : '' extends Group | Leaf\n ? `'${Key}' has an empty part. Write a group and a plugin name around the dot, like 'services.users'.`\n : Group extends FrameworkChannel\n ? `'${Group}' is a channel the framework logs on. Pick another group name.`\n : Group extends keyof Host\n ? Host[Group] extends PluginLike\n ? `'${Group}' already holds a plugin, so '${Leaf}' cannot nest inside it.`\n : Host[Group] extends Record<string, PluginLike>\n ? Leaf extends keyof Host[Group]\n ? `'${Key}' is already attached.`\n : Key\n : `'${Group}' is already a member on the bot. Pick another group name.`\n : Group extends HostMember<Host>\n ? `'${Group}' is already a member on the bot. Pick another group name.`\n : Key\n : Key extends FrameworkChannel\n ? `'${Key}' is a channel the framework logs on. Pick another plugin key.`\n : Key extends keyof Host\n ? Host[Key] extends PluginLike\n ? `'${Key}' is already attached.`\n : Host[Key] extends Record<string, PluginLike>\n ? `'${Key}' already holds a group of plugins. Attach this one under a name of its own.`\n : `'${Key}' is already a member on the bot. Pick another plugin key.`\n : Key extends HostMember<Host>\n ? `'${Key}' is already a member on the bot. Pick another plugin key.`\n : Key;\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`\ntype CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0]\n ? unknown\n : CoreParamTooNarrow;\n\ntype ConstructorMissing = Record<\n \"this plugin narrows its transport or runtime. Give it a constructor that passes them to super(), like super(host, { runtime: 'server' })\",\n never\n>;\n\ntype OwnConstructorAssert<Ctor extends PluginCtor> =\n InheritsScopedConstructor<Ctor> extends true ? ConstructorMissing : unknown;\n\ntype AttachAsserts<Ctor extends PluginCtor, BotT extends Transport, BotRt extends Runtime> = TransportAssert<\n InstanceType<Ctor>,\n BotT\n> &\n RuntimeAssert<InstanceType<Ctor>, BotRt> &\n CoreParamAssert<Ctor> &\n OwnConstructorAssert<Ctor>;\n\n// typescript can't infer Ctor from a generic class through `Ctor & AttachAsserts`. every assert returns unknown to pass.\n/** @internal */\nexport type AttachableCtor<Ctor extends PluginCtor, BotT extends Transport, BotRt extends Runtime> =\n unknown extends AttachAsserts<Ctor, BotT, BotRt> ? Ctor : AttachAsserts<Ctor, BotT, BotRt>;\n","import { SeedcordErrorCode, isSeedcordError } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport type { SeedcordErrorArguments } from '@seedcord/errors/internal';\nimport type { KvDriver, KvEntry } from '@seedcord/types';\nimport type { Promisable } from 'type-fest';\n\ntype Operation = SeedcordErrorArguments<SeedcordErrorCode.CoreKvDriverFailed>[0];\n\nfunction translated(operation: Operation, key: string, error: unknown): unknown {\n if (isSeedcordError(error)) return error;\n return new SeedcordError(SeedcordErrorCode.CoreKvDriverFailed, [operation, key], { cause: error });\n}\n\nasync function guard<TResult>(operation: Operation, key: string, run: () => Promisable<TResult>): Promise<TResult> {\n try {\n return await run();\n } catch (error) {\n throw translated(operation, key, error);\n }\n}\n\nexport class GuardedDriver implements KvDriver {\n constructor(private readonly driver: KvDriver) {}\n\n public async get(key: string): Promise<string | undefined> {\n return guard('read', key, () => this.driver.get(key));\n }\n\n public async set(key: string, value: string, ttlMs?: number): Promise<void> {\n await guard('write', key, () => this.driver.set(key, value, ttlMs));\n }\n\n public async delete(key: string): Promise<void> {\n await guard('delete', key, () => this.driver.delete(key));\n }\n\n public async *entries(prefix: string): AsyncIterable<KvEntry> {\n try {\n for await (const entry of this.driver.entries(prefix)) yield entry;\n } catch (error) {\n throw translated('list', prefix, error);\n }\n }\n}\n","export class KeyedQueue {\n private readonly tails = new Map<string, Promise<void>>();\n\n public run<TResult>(key: string, task: () => Promise<TResult>): Promise<TResult> {\n const run = (this.tails.get(key) ?? Promise.resolve()).then(task);\n const tail = run.then(\n () => undefined,\n () => undefined\n );\n this.tails.set(key, tail);\n void tail.then(() => {\n if (this.tails.get(key) === tail) this.tails.delete(key);\n });\n return run;\n }\n}\n","import type { KeyedQueue } from './KeyedQueue';\nimport type { KvDriver } from '@seedcord/types';\nimport type { Jsonify, Simplify } from 'type-fest';\n\n// a hover shows the record's fields in place of JsonifyObject<...>. SimplifyDeep hits TS2589 in get()\ntype Stored<TRecord> = Simplify<Jsonify<TRecord>>;\n\n// expiresAt covers backends that ignore a ttl, like unstorage's fs driver\ninterface Envelope<TValue> {\n value: TValue;\n expiresAt?: number;\n}\n\n/**\n * The records of one type, stored under one name in `config.kv`. Get one from\n * `kv.collection<T>('name')`.\n *\n * Every record goes through JSON on its way in and out. A `Date` you store reads back from `get()`\n * as an ISO string.\n *\n * @example\n * ```ts\n * interface Profile { bio: string; joinedAt: Date }\n * const profiles = this.core.kv.collection<Profile>('profile');\n *\n * await profiles.set(user.id, { bio: 'hi', joinedAt: new Date() });\n * const profile = await profiles.get(user.id); // { bio: string; joinedAt: string } | undefined\n * await profiles.delete(user.id);\n * ```\n */\nexport class KvCollection<TRecord> {\n readonly #driver: KvDriver;\n readonly #prefix: string;\n readonly #updates: KeyedQueue;\n\n constructor(driver: KvDriver, prefix: string, updates: KeyedQueue) {\n this.#driver = driver;\n this.#prefix = prefix;\n this.#updates = updates;\n }\n\n /** Reads the record stored under `id`. Resolves to `undefined` when nothing is there or the record expired. */\n public async get(id: string): Promise<Stored<TRecord> | undefined> {\n const key = this.#prefix + id;\n const raw = await this.#driver.get(key);\n const value = this.#liveValue(raw);\n if (raw !== undefined && value === undefined) await this.#deleteExpired(key);\n return value;\n }\n\n /**\n * Stores `value` under `id` and replaces whatever was there.\n *\n * @param ttlMs - How long the record lasts, in milliseconds. Leave it out to keep the record until you delete it.\n */\n public async set(id: string, value: TRecord, ttlMs?: number): Promise<void> {\n const key = this.#prefix + id;\n await this.#updates.run(key, async () => this.#write(key, value, ttlMs));\n }\n\n /** Removes the record under `id`. */\n public async delete(id: string): Promise<void> {\n const key = this.#prefix + id;\n await this.#updates.run(key, async () => this.#driver.delete(key));\n }\n\n /**\n * Stores what `next` returns for the record under `id`, then resolves to that value. `next` gets\n * the current record, or `undefined` when there is none.\n *\n * Use it over a `get()` then a `set()` when two calls can change one record at the same time.\n * `set()`, `delete()`, and `update()` calls on one id run one after the other in this process.\n * None of them loses another's change. Two processes sharing one backend can still both read\n * before either writes.\n *\n * @param ttlMs - How many milliseconds the stored record lasts. Pass a function to work it out from the record. Leave it out to keep the record until you delete it.\n * @example\n * ```ts\n * // counts a run of the command and resolves to the new count\n * const runs = await counts.update(user.id, (count) => (count ?? 0) + 1);\n * ```\n */\n public async update(\n id: string,\n next: (current: Stored<TRecord> | undefined) => TRecord,\n ttlMs?: number | ((value: TRecord) => number)\n ): Promise<TRecord> {\n const key = this.#prefix + id;\n return this.#updates.run(key, async () => {\n const value = next(this.#liveValue(await this.#driver.get(key)));\n await this.#write(key, value, typeof ttlMs === 'function' ? ttlMs(value) : ttlMs);\n return value;\n });\n }\n\n /**\n * Yields `[id, record]` for every record in this collection that hasn't expired. Use it to read\n * back what the bot saved before a restart.\n *\n * @throws A **SeedcordError** when the backend can't list its keys, like `@keyv/memcache`.\n * @example\n * ```ts\n * // schedules every saved reminder again after a restart\n * for await (const [id, reminder] of reminders.entries()) schedule(id, reminder);\n * ```\n */\n public async *entries(): AsyncIterable<[id: string, record: Stored<TRecord>]> {\n const expired: string[] = [];\n try {\n for await (const [key, raw] of this.#driver.entries(this.#prefix)) {\n const value = this.#liveValue(raw);\n if (value === undefined) expired.push(key);\n else yield [key.slice(this.#prefix.length), value];\n }\n } finally {\n // deletes wait until the listing ends. a driver may be reading from an open cursor\n await Promise.all(expired.map(async (key) => this.#deleteExpired(key)));\n }\n }\n\n async #write(key: string, value: TRecord, ttlMs: number | undefined): Promise<void> {\n const envelope: Envelope<TRecord> = ttlMs === undefined ? { value } : { value, expiresAt: Date.now() + ttlMs };\n await this.#driver.set(key, JSON.stringify(envelope), ttlMs);\n }\n\n // reads again in the queue. a set queued after the first read may have stored a live record\n async #deleteExpired(key: string): Promise<void> {\n await this.#updates.run(key, async () => {\n const raw = await this.#driver.get(key);\n if (raw !== undefined && this.#liveValue(raw) === undefined) await this.#driver.delete(key);\n });\n }\n\n #liveValue(raw: string | undefined): Stored<TRecord> | undefined {\n if (raw === undefined) return undefined;\n // justified: only this collection writes under its prefix\n const envelope = JSON.parse(raw) as Envelope<Stored<TRecord>>;\n if (envelope.expiresAt !== undefined && envelope.expiresAt <= Date.now()) return undefined;\n return envelope.value;\n }\n}\n","import { GuardedDriver } from './GuardedDriver';\nimport { KeyedQueue } from './KeyedQueue';\nimport { KvCollection } from './KvCollection';\n\nimport type { Config } from '@seedcord/types';\n\ntype ConfiguredDriver = NonNullable<Config['kv']>;\n\n// what JSON.stringify keeps as written. a Date passes through its toJSON\ntype JsonSafe<TValue> = TValue extends string | number | boolean | null | undefined\n ? TValue\n : TValue extends { toJSON(): unknown }\n ? TValue\n : TValue extends (...args: never[]) => unknown\n ? never\n : TValue extends ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>\n ? never\n : TValue extends readonly (infer Item)[]\n ? readonly JsonSafe<Item>[]\n : TValue extends object\n ? { [K in keyof TValue]: JsonSafe<TValue[K]> }\n : never;\n\n// tsc prints a failed string argument against an object type as only `string is not assignable`\ntype CollectionName<TRecord> = unknown extends TRecord\n ? 'Pass the record type, like kv.collection<Profile>(name).'\n : [TRecord] extends [JsonSafe<TRecord>]\n ? string\n : 'This record type holds a value that JSON cannot keep, like a function, Map, Set, or bigint.';\n\n// unescaped, every key under `user:settings:` would also start with `user:`\nfunction keyPrefix(path: readonly string[]): string {\n return path.map((segment) => `${encodeURIComponent(segment)}:`).join('');\n}\n\n/**\n * The bot's key-value store, or one prefixed slice of it. `core.kv` holds your handlers' records. A\n * plugin's `this.kv` holds the plugin's.\n */\nexport class Kv {\n /** What you passed as `config.kv`. Register its type in `KvTypes` to call the library's own methods on it. */\n public readonly driver: ConfiguredDriver;\n readonly #guarded: GuardedDriver;\n readonly #path: readonly string[];\n readonly #updates = new KeyedQueue();\n\n constructor(driver: ConfiguredDriver, ...path: string[]) {\n this.driver = driver;\n this.#guarded = new GuardedDriver(driver);\n this.#path = path;\n }\n\n /**\n * Records of one type under `name`. Pass the type argument. A record type holding a function,\n * `Map`, `Set`, or `bigint` fails to compile here.\n */\n public collection<TRecord>(name: CollectionName<TRecord>): KvCollection<TRecord> {\n return new KvCollection<TRecord>(this.#guarded, keyPrefix([...this.#path, name]), this.#updates);\n }\n}\n","import type { Kv } from '#src/kv/Kv';\nimport type { KvCollection } from '#src/kv/KvCollection';\nimport type { EpochMs, IRateLimiter, RateLimitResult, RateLimitWindow } from '@seedcord/types';\n\nfunction limitedResult(live: readonly number[], now: number): RateLimitResult {\n // a reduce, because spreading into Math.min throws past the argument cap\n const resetAt = live.reduce((earliest, exp) => Math.min(earliest, exp), Number.POSITIVE_INFINITY) as EpochMs;\n return { limited: true, resetAt, remaining: 0, retryAfterMs: resetAt - now };\n}\n\nfunction openResult(remaining: number): RateLimitResult {\n return { limited: false, resetAt: 0 as EpochMs, remaining, retryAfterMs: 0 };\n}\n\n// 0 or a negative limit never means \"block everything\"\nfunction limitOf(window: RateLimitWindow): number {\n return Math.max(1, window.limit ?? 1);\n}\n\nfunction liveUses(stored: readonly number[] | undefined, now: number): number[] {\n return (stored ?? []).filter((exp) => exp > now);\n}\n\nexport class KvRateLimiter implements IRateLimiter {\n private readonly uses: KvCollection<number[]>;\n\n constructor(kv: Kv) {\n this.uses = kv.collection<number[]>('rate-limit');\n }\n\n public async charge(key: string, window: RateLimitWindow): Promise<RateLimitResult> {\n const limit = limitOf(window);\n // update() calls the callback before it resolves\n let result!: RateLimitResult;\n await this.uses.update(\n key,\n (stored) => {\n // read here, since this callback can wait in the queue behind other charges\n const now = Date.now();\n const live = liveUses(stored, now);\n const limited = live.length >= limit;\n if (!limited) live.push(now + window.windowMs);\n result = limited ? limitedResult(live, now) : openResult(limit - live.length);\n return live;\n },\n (live) => live.reduce((latest, exp) => Math.max(latest, exp), 0) - Date.now()\n );\n return result;\n }\n\n public async peek(key: string, window: RateLimitWindow): Promise<RateLimitResult> {\n const stored = await this.uses.get(key);\n const now = Date.now();\n const limit = limitOf(window);\n const live = liveUses(stored, now);\n return live.length >= limit ? limitedResult(live, now) : openResult(limit - live.length);\n }\n\n public async reset(key: string): Promise<void> {\n await this.uses.delete(key);\n }\n}\n","interface PluginKeyParts {\n readonly head: string;\n readonly leaf: string | undefined;\n}\n\n// 'services.users' attaches the plugin as users inside the group services\nexport function splitPluginKey(key: string): PluginKeyParts {\n const dot = key.indexOf('.');\n return dot === -1 ? { head: key, leaf: undefined } : { head: key.slice(0, dot), leaf: key.slice(dot + 1) };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError, SeedcordTypeError } from '@seedcord/errors/internal';\nimport { FRAMEWORK_CHANNELS } from '@seedcord/logger';\nimport { HostPluginKeys } from '@seedcord/types/internal';\n\nimport { Kv } from '#src/kv/Kv';\nimport { MemoryDriver } from '#src/kv/MemoryDriver';\nimport { KvRateLimiter } from '#src/rateLimiter/KvRateLimiter';\nimport { Bus } from '#subscribers/Bus';\n\nimport { constructAttached, declaredScopeOf, extendsThisCorePlugin, resolvedLifecycleSpecOf } from './Plugin';\nimport { splitPluginKey } from './splitPluginKey';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { ResolvedPluginLifecycleSpec } from './lifecycle';\nimport type { DeclaredScope, Runtime, Transport } from './options';\nimport type { AttachableCtor, Attached, AttachKeyAssert, PluginArgs, PluginCtor, PluginLike } from './Plugin';\nimport type { REST } from '@discordjs/rest';\nimport type { Config, IRateLimiter } from '@seedcord/types';\n\nexport interface Attachment {\n readonly key: string;\n readonly instance: PluginLike;\n readonly spec: ResolvedPluginLifecycleSpec;\n}\n\nconst RESERVED_KEYS: ReadonlySet<string> = new Set(FRAMEWORK_CHANNELS);\n\nconst attachmentsSlot = Symbol('seedcord:host:attachments');\nconst sealSlot = Symbol('seedcord:host:seal');\n\n/** Base class for a plugin host. */\nexport abstract class PluginHost<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\n /** @see {@link Bus} */\n public readonly bus: Bus;\n /** @see {@link Kv} */\n public readonly kv: Kv;\n /** @see {@link IRateLimiter} */\n public readonly rateLimiter: IRateLimiter;\n\n #sealed = false;\n readonly #attachments: Attachment[] = [];\n readonly #groups = new Map<string, Record<string, PluginLike>>();\n readonly #scope: { readonly transport: BotT; readonly runtime: BotRt };\n\n constructor(transport: BotT, runtime: BotRt, driver: Config['kv']) {\n this.#scope = { transport, runtime };\n this.bus = new Bus(this);\n const kvDriver = driver ?? new MemoryDriver();\n this.kv = new Kv(kvDriver, 'app');\n this.rateLimiter = new KvRateLimiter(new Kv(kvDriver, 'seedcord'));\n }\n\n /** @internal */\n public get [attachmentsSlot](): readonly Attachment[] {\n return this.#attachments;\n }\n\n /** @internal */\n public [sealSlot](): void {\n this.#sealed = true;\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 /**\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 * Put one dot in the key to nest the plugin under a group. `'services.users'` reads back as\n * `core.services.users`. Each name holds one plugin or one group.\n *\n * Startup runs each plugin's `init()` in attach order within its phase. A node bot starts in\n * `start()`. An edge bot starts on its first request.\n *\n * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.\n * Take `CoreBase` as your constructor's first parameter. `attach()` passes it, so you don't.\n * A narrower one fails to compile here.\n *\n * @param key - Also the channel the plugin logs on, dots included.\n * @param args - Whatever your constructor takes after the host.\n * @throws A **SeedcordError** when you attach after the bot has started.\n * @throws A **SeedcordTypeError** when `key` is taken, reserved, or malformed.\n * @throws A **SeedcordTypeError** when the plugin extends `Plugin` from another copy of `@seedcord/core`.\n * @throws A **SeedcordTypeError** when the plugin's `transport` or `runtime` differs from this host and the types were bypassed.\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: AttachKeyAssert<Key, this>,\n Plugin: AttachableCtor<Ctor, BotT, BotRt>,\n ...args: PluginArgs<Ctor>\n ): this & Attached<Key, InstanceType<Ctor>>;\n public attach<Key extends string, Ctor extends PluginCtor>(\n this: this,\n key: Key,\n Plugin: Ctor,\n ...args: PluginArgs<Ctor>\n ): this & Attached<Key, InstanceType<Ctor>> {\n if (this.#sealed) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);\n }\n\n const { head, leaf } = splitPluginKey(key);\n\n // runs before assertFree because some reserved channels are also host members\n if (RESERVED_KEYS.has(head)) {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginReservedChannel, [head]);\n }\n\n if (head === '' || leaf === '') {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyMalformed, [key, 'has an empty part.']);\n }\n if (leaf?.includes('.')) {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyMalformed, [key, 'has more than one dot.']);\n }\n this.#assertFree(head, leaf, key);\n\n if (!extendsThisCorePlugin(Plugin)) {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginFromOtherCore, [Plugin.name]);\n }\n\n const kv = new Kv(this.kv.driver, 'plugins', key);\n const instance = constructAttached(key, kv, () => new Plugin(this, ...args));\n this.#assertScope(Plugin.name, declaredScopeOf(instance));\n this.#attachments.push({ key, instance, spec: resolvedLifecycleSpecOf(instance) });\n\n if (leaf === undefined) {\n return Object.assign(this, { [key]: instance }) as this & Attached<Key, InstanceType<Ctor>>;\n }\n\n this.#groupFor(head)[leaf] = instance;\n return this as this & Attached<Key, InstanceType<Ctor>>;\n }\n\n #assertFree(head: string, leaf: string | undefined, key: string): void {\n if (leaf === undefined) {\n if (this.#groups.has(key)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyHoldsGroup, [key]);\n if (key in this) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyExists, [key]);\n return;\n }\n\n const group = this.#groups.get(head);\n if (!group) {\n if (head in this) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginGroupTaken, [head, key]);\n return;\n }\n if (Object.hasOwn(group, leaf)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyExists, [key]);\n }\n\n // the types already reject a mismatch. plain JS and casts reach this.\n #assertScope(pluginName: string, declared: DeclaredScope): void {\n for (const axis of ['transport', 'runtime'] as const) {\n const host = this.#scope[axis];\n if (declared[axis] === 'any' || declared[axis] === host) continue;\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginScopeMismatch, [\n pluginName,\n axis,\n declared[axis],\n host\n ]);\n }\n }\n\n #groupFor(head: string): Record<string, PluginLike> {\n const existing = this.#groups.get(head);\n if (existing) return existing;\n\n // a null prototype makes a leaf called __proto__ or valueOf an ordinary key\n const group = Object.create(null) as Record<string, PluginLike>;\n this.#groups.set(head, group);\n Object.assign(this, { [head]: group });\n return group;\n }\n}\n\nexport function attachmentsOf(\n host: Pick<PluginHost<Transport, Runtime>, typeof attachmentsSlot>\n): readonly Attachment[] {\n return host[attachmentsSlot];\n}\n\nexport function sealAttachments(host: Pick<PluginHost<Transport, Runtime>, typeof sealSlot>): void {\n host[sealSlot]();\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { n as StartupPhase } from "./phases-BauKJOTT.mjs";
2
- import { o as pluginLoggerOf } from "./PluginHost-B8kxLOeb.mjs";
1
+ import { b as StartupPhase } from "./Bus-VJw-QpoY.mjs";
2
+ import { c as pluginLoggerOf } from "./PluginHost-DTtxWRiL.mjs";
3
3
  import { SeedcordErrorCode, isSeedcordError } from "@seedcord/errors";
4
4
  import { SeedcordError, throwSingleOrAggregate } from "@seedcord/errors/internal";
5
5
  import { Logger } from "@seedcord/logger";
@@ -92,7 +92,7 @@ var PluginLifecycle = class {
92
92
  try {
93
93
  await withTimeout(`Plugin (${key})`, () => running, spec.init.timeout);
94
94
  } catch (caught) {
95
- if (isSeedcordError(caught, void 0, SeedcordErrorCode.LifecycleTaskTimeout)) this.disposeWhenInitResolves(attachment, running);
95
+ if (isSeedcordError(caught, void 0, SeedcordErrorCode.LifecycleTaskTimeout)) this.disposeWhenInitResolves(attachment, Promise.resolve(running));
96
96
  throw caught;
97
97
  }
98
98
  pluginLoggerOf(instance).utils.initialization(key, "end");
@@ -155,4 +155,4 @@ var PluginLifecycle = class {
155
155
 
156
156
  //#endregion
157
157
  export { logUnknownFault as i, settleWithin as n, withTimeout as r, PluginLifecycle as t };
158
- //# sourceMappingURL=PluginLifecycle-AhQ46NIr.mjs.map
158
+ //# sourceMappingURL=PluginLifecycle-hx5zUc-P.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PluginLifecycle-hx5zUc-P.mjs","names":[],"sources":["../src/notices/logUnknownFault.ts","../src/lifecycle/withTimeout.ts","../src/lifecycle/PluginLifecycle.ts"],"sourcesContent":["import type { Logger } from '@seedcord/logger';\nimport type { ErrorsConfig } from '@seedcord/types';\n\nfunction causeLine(error: Error): string {\n const { cause } = error;\n if (!Error.isError(cause)) return '';\n const [first] = cause.message.split('\\n');\n return `\\ncaused by ${cause.name}: ${first ?? cause.message}`;\n}\n\nexport function logUnknownFault(logger: Logger, uuid: string, error: Error, errors: ErrorsConfig | undefined): void {\n if (errors?.errorStack ?? true) logger.error(uuid, error);\n else logger.error(`${uuid} | ${error.message}${causeLine(error)}`);\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport type { Promisable } from 'type-fest';\n\n// calling start() in here turns its synchronous throw into a rejection\nasync function raceTimer(\n start: () => Promisable<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 start(),\n new Promise<void>((resolve, reject) => {\n // node's timer can go off before performance.now() reaches timeoutMs\n const expiresAt = performance.now() + timeoutMs;\n const tick = (): void => {\n const remaining = expiresAt - performance.now();\n if (remaining > 0) {\n timer = setTimeout(tick, remaining);\n return;\n }\n onTimeout(resolve, reject);\n };\n timer = setTimeout(tick, 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: () => Promisable<void>, timeoutMs: number): Promise<void> {\n return raceTimer(run, timeoutMs, (_, reject) => {\n reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [name, timeoutMs]));\n });\n}\n\n// the caller does not get work's rejection\nexport function settleWithin(work: Promise<unknown>, timeoutMs: number): Promise<void> {\n return raceTimer(\n () =>\n work.then(\n () => undefined,\n () => undefined\n ),\n timeoutMs,\n (resolve) => resolve()\n );\n}\n","import { SeedcordErrorCode, isSeedcordError } from '@seedcord/errors';\nimport { throwSingleOrAggregate } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\n\nimport { StartupPhase } from '#src/lifecycle/phases';\nimport { pluginLoggerOf } from '#src/plugin/Plugin';\n\nimport { withTimeout } from './withTimeout';\n\nimport type { ShutdownTasks } from '#node/Lifecycle/CoordinatedShutdown';\nimport type { TaskShutdownPhase } from '#src/lifecycle/phases';\nimport type { Attachment } from '#src/plugin/PluginHost';\nimport type { StartupTasks } from './CoordinatedStartup';\nimport type { Promisable } from 'type-fest';\n\ninterface ReadyStep {\n readonly key: string;\n readonly run: () => Promisable<void>;\n readonly timeout: number;\n}\n\nexport class PluginLifecycle {\n private readonly logger = new Logger('Plugins', { channel: 'plugins' });\n private readonly completedInits = new Set<Attachment>();\n private readonly disposePhases = new Set<TaskShutdownPhase>();\n private readonly timedOutInits = new Set<Promise<void>>();\n private attachments: readonly Attachment[] = [];\n private registered = false;\n\n constructor(\n private readonly startup: StartupTasks,\n private readonly shutdown: ShutdownTasks\n ) {}\n\n // one combined task per phase keeps plugin inits sequential while the phase's other tasks run concurrently\n public register(attachments: readonly Attachment[]): void {\n if (this.registered) return;\n this.registered = true;\n this.attachments = [...attachments];\n\n const groups = Map.groupBy(this.attachments, (attachment) => attachment.spec.init.phase);\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 + a.spec.init.timeout, 0);\n this.startup.addTask(phase, 'plugins-init', () => this.runInits(group), budget);\n }\n this.registerReadyTask(groups.get(StartupPhase.Ready) ?? []);\n }\n\n public async rollback(): Promise<void> {\n // log rollback dispose failures so they do not hide the startup error\n await this.disposeCompleted(undefined, (caught) => this.logger.warn('rollback dispose failed', caught));\n // prevents re-dispose when shutdown runs after rollback\n this.completedInits.clear();\n }\n\n public afterTimedOutInits(run: () => void): void {\n if (this.timedOutInits.size === 0) {\n run();\n return;\n }\n void Promise.allSettled(this.timedOutInits).then(run);\n }\n\n private async runInits(group: readonly Attachment[]): Promise<void> {\n for (const attachment of group) {\n const { key, instance, spec } = attachment;\n\n pluginLoggerOf(instance).utils.initialization(key, 'start');\n const running = instance.init();\n try {\n await withTimeout(`Plugin (${key})`, () => running, spec.init.timeout);\n } catch (caught) {\n if (isSeedcordError(caught, undefined, SeedcordErrorCode.LifecycleTaskTimeout)) {\n this.disposeWhenInitResolves(attachment, Promise.resolve(running));\n }\n throw caught;\n }\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 // disposeCompleted skips a plugin missing from completedInits\n private disposeWhenInitResolves({ key, instance, spec }: Attachment, running: Promise<void>): void {\n const dispose = instance.dispose?.bind(instance);\n\n const settled = running.then(\n async () => {\n if (!dispose) return;\n await withTimeout(`Plugin:${key}:dispose`, dispose, spec.dispose.timeout).catch((caught: unknown) =>\n this.logger.warn(`${key} dispose failed after a timed-out init`, caught)\n );\n },\n (caught: unknown) => this.logger.warn(`${key} init failed after its timeout`, caught)\n );\n this.timedOutInits.add(settled);\n void settled.finally(() => this.timedOutInits.delete(settled));\n }\n\n private registerReadyTask(readyInits: readonly Attachment[]): void {\n const steps = this.attachments.reduce<ReadyStep[]>((acc, { key, instance, spec }) => {\n const ready = instance.ready?.bind(instance);\n\n if (ready) acc.push({ key, run: ready, timeout: spec.ready.timeout });\n return acc;\n }, []);\n if (readyInits.length === 0 && steps.length === 0) return;\n const budget =\n readyInits.reduce((sum, a) => sum + a.spec.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: TaskShutdownPhase): 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(\n (sum, a) => (a.instance.dispose && a.spec.dispose.phase === phase ? sum + a.spec.dispose.timeout : sum),\n 0\n );\n this.shutdown.addTask(phase, 'plugins-dispose', () => this.runDisposals(phase), budget);\n }\n\n private async runDisposals(phase: TaskShutdownPhase): Promise<void> {\n const failures: unknown[] = [];\n await this.disposeCompleted(phase, (caught) => failures.push(caught));\n throwSingleOrAggregate(failures, SeedcordErrorCode.PluginDisposeFailures);\n }\n\n private async disposeCompleted(\n phase: TaskShutdownPhase | 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 { key, instance, spec } = attachment;\n const dispose = instance.dispose?.bind(instance);\n if (!dispose) continue;\n if (phase !== undefined && spec.dispose.phase !== phase) continue;\n\n try {\n await withTimeout(`Plugin:${key}:dispose`, dispose, spec.dispose.timeout);\n } catch (caught) {\n onError(caught);\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
- import { d as Transport, n as PluginHost } from "./PluginHost-DB4j8unu.mjs";
2
- import { n as CoordinatedStartup, t as CoordinatedShutdown } from "./CoordinatedShutdown-H5YjZn8A.mjs";
1
+ import { f as Transport, n as PluginHost } from "./PluginHost-DFnkIldj.mjs";
2
+ import { i as StartupTasks, n as ShutdownTasks, r as CoordinatedStartup, t as CoordinatedShutdown } from "./CoordinatedShutdown-fPKLWjc5.mjs";
3
3
  import { HostShutdown, HostStartup } from "@seedcord/types/internal";
4
+ import { Config } from "@seedcord/types";
4
5
  import { Promisable } from "type-fest";
5
6
  //#region src/node/ServerHost.d.ts
6
7
  /** Base class for a transport `Seedcord` class that runs as a long-lived node or bun process. */
@@ -11,10 +12,10 @@ declare abstract class ServerHost<BotT extends Transport> extends PluginHost<Bot
11
12
  /** @internal */
12
13
  readonly [HostStartup]: CoordinatedStartup;
13
14
  /** Add a task that runs while the bot shuts down. */
14
- readonly shutdown: Pick<CoordinatedShutdown, 'addTask'>;
15
+ readonly shutdown: ShutdownTasks;
15
16
  /** Add a task that runs while the bot starts. */
16
- readonly startup: Pick<CoordinatedStartup, 'addTask'>;
17
- constructor(transport: BotT, shutdown: CoordinatedShutdown, startup: CoordinatedStartup);
17
+ readonly startup: StartupTasks;
18
+ constructor(transport: BotT, shutdown: CoordinatedShutdown, startup: CoordinatedStartup, driver: Config['kv']);
18
19
  /** @internal */
19
20
  protected init(): Promise<this>;
20
21
  /** @internal */
@@ -37,4 +38,4 @@ declare abstract class ServerHost<BotT extends Transport> extends PluginHost<Bot
37
38
  declare function shutdownOf(host: Pick<ServerHost<Transport>, typeof HostShutdown>): CoordinatedShutdown;
38
39
  //#endregion
39
40
  export { shutdownOf as n, ServerHost as t };
40
- //# sourceMappingURL=ServerHost-DYeh0KTk.d.mts.map
41
+ //# sourceMappingURL=ServerHost-B5o3ScCF.d.mts.map
@@ -1,6 +1,7 @@
1
- import { t as PublishDefault } from "./publishDefault-D3dAyzZ9.mjs";
2
- import { n as attachmentsOf, r as sealAttachments, t as PluginHost } from "./PluginHost-B8kxLOeb.mjs";
3
- import { i as logUnknownFault, t as PluginLifecycle } from "./PluginLifecycle-AhQ46NIr.mjs";
1
+ import { x as PublishDefault, y as ShutdownPhase } from "./Bus-VJw-QpoY.mjs";
2
+ import { n as attachmentsOf, r as sealAttachments, t as PluginHost } from "./PluginHost-DTtxWRiL.mjs";
3
+ import { i as logUnknownFault, t as PluginLifecycle } from "./PluginLifecycle-hx5zUc-P.mjs";
4
+ import { t as _usingCtx } from "./usingCtx-Cf2cZi96.mjs";
4
5
  import { SeedcordErrorCode } from "@seedcord/errors";
5
6
  import { SeedcordError } from "@seedcord/errors/internal";
6
7
  import { Logger, LoggerChannelRegistry } from "@seedcord/logger";
@@ -80,9 +81,9 @@ var ServerHost = class ServerHost extends PluginHost {
80
81
  static #liveHost;
81
82
  static #liveShutdown;
82
83
  static #liveProcessErrors;
83
- constructor(transport, shutdown, startup) {
84
+ constructor(transport, shutdown, startup, driver) {
84
85
  assertDeclaredRuntime();
85
- super(transport, "server");
86
+ super(transport, "server", driver);
86
87
  if (ServerHost.#isInstantiated) throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);
87
88
  installNodeConsole();
88
89
  ServerHost.#isInstantiated = true;
@@ -93,6 +94,16 @@ var ServerHost = class ServerHost extends PluginHost {
93
94
  this.shutdown = { addTask: shutdown.addTask.bind(shutdown) };
94
95
  this.startup = { addTask: startup.addTask.bind(startup) };
95
96
  this.#lifecycle = new PluginLifecycle(this.startup, this.shutdown);
97
+ shutdown.addTask(5, "close-kv", async () => {
98
+ try {
99
+ var _usingCtx$1 = _usingCtx();
100
+ _usingCtx$1.a(this.kv.driver);
101
+ } catch (_) {
102
+ _usingCtx$1.e = _;
103
+ } finally {
104
+ await _usingCtx$1.d();
105
+ }
106
+ });
96
107
  }
97
108
  /** @internal */
98
109
  init() {
@@ -165,4 +176,4 @@ function shutdownOf(host) {
165
176
 
166
177
  //#endregion
167
178
  export { shutdownOf as n, assertDeclaredRuntime as r, ServerHost as t };
168
- //# sourceMappingURL=ServerHost-CtmAhEXn.mjs.map
179
+ //# sourceMappingURL=ServerHost-CDaEDR4C.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ServerHost-CtmAhEXn.mjs","names":[],"sources":["../src/node/assertRuntimeVersion.ts","../src/node/processErrors.ts","../src/node/ServerHost.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { isValid, isValidRange, satisfies } from 'verkit';\n\nexport function assertDeclaredRuntime(): void {\n const { bun, node } = process.versions;\n // tsdown-config bakes both ranges in from this package's engines\n const runtime =\n bun === undefined\n ? { name: 'Node', range: process.env.PACKAGE_NODE_RANGE ?? '', running: node }\n : { name: 'Bun', range: process.env.PACKAGE_BUN_RANGE ?? '', running: bun };\n\n if (\n !runtime.range ||\n !isValidRange(runtime.range) ||\n !isValid(runtime.running) ||\n satisfies(runtime.running, runtime.range, { includePrerelease: true })\n )\n return;\n\n throw new SeedcordError(SeedcordErrorCode.UnsupportedRuntimeVersion, [\n runtime.name,\n runtime.range,\n runtime.running\n ]);\n}\n","import crypto from 'node:crypto';\n\nimport { Logger } from '@seedcord/logger';\nimport { asError } from '@seedcord/utils/internal';\n\nimport { logUnknownFault } from '#src/notices/logUnknownFault';\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, origin: string): void {\n const error = asError(caught);\n const uuid = crypto.randomUUID();\n\n logUnknownFault(logger, uuid, error, core.config.errors);\n\n core.bus[PublishDefault]('unknownException', { uuid, dispatchId: null, error, origin });\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 { SeedcordError } from '@seedcord/errors/internal';\nimport { LoggerChannelRegistry } from '@seedcord/logger';\nimport { installNodeConsole, installNodeDefaults } from '@seedcord/logger/node';\nimport { HostShutdown, HostStartup } from '@seedcord/types/internal';\nimport { runsUnderSeedcordDev } from '@seedcord/utils/internal';\n\nimport { assertDeclaredRuntime } from '#node/assertRuntimeVersion';\nimport { PluginLifecycle } from '#src/lifecycle/PluginLifecycle';\nimport { attachmentsOf, PluginHost, sealAttachments } from '#src/plugin/PluginHost';\n\nimport { registerProcessErrors } from './processErrors';\n\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\nimport type { CoordinatedStartup } from '#src/lifecycle/CoordinatedStartup';\nimport type { Transport } from '#src/plugin/options';\nimport type { Promisable } from 'type-fest';\n\n/** Base class for a transport `Seedcord` class that runs as a long-lived node or bun process. */\nexport abstract class ServerHost<BotT extends Transport> extends PluginHost<BotT, 'server'> {\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 readonly #lifecycle: PluginLifecycle;\n #startFailed = false;\n #initPromise?: Promise<this> | undefined;\n\n static #isInstantiated = false;\n static #liveHost?: object | undefined;\n static #liveShutdown?: CoordinatedShutdown | undefined;\n static #liveProcessErrors?: (() => void) | undefined;\n\n constructor(transport: BotT, shutdown: CoordinatedShutdown, startup: CoordinatedStartup) {\n // a `sideEffects: false` build would drop the same call in the node entry\n assertDeclaredRuntime();\n super(transport, 'server');\n\n if (ServerHost.#isInstantiated) throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);\n\n // init() opens the log file\n installNodeConsole();\n\n ServerHost.#isInstantiated = true;\n ServerHost.#liveHost = this;\n ServerHost.#liveShutdown = shutdown;\n this[HostShutdown] = shutdown;\n this[HostStartup] = startup;\n // a getter returning the slot would expose run() and the signal handlers too\n this.shutdown = { addTask: shutdown.addTask.bind(shutdown) };\n this.startup = { addTask: startup.addTask.bind(startup) };\n this.#lifecycle = new PluginLifecycle(this.startup, this.shutdown);\n }\n\n /** @internal */\n protected init(): Promise<this> {\n // a retry after a rejection runs #runInit again and hits its restart guard\n this.#initPromise ??= this.#runInit().catch(async (caught: unknown) => {\n this.#initPromise = undefined;\n await this.#release(1);\n throw caught;\n });\n return this.#initPromise;\n }\n\n async #runInit(): Promise<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 installNodeDefaults(this.config.logger);\n sealAttachments(this);\n this.#lifecycle.register(attachmentsOf(this));\n\n // codegen and the build construct the bot without starting it.\n // under seedcord dev the CLI handles SIGINT and SIGTERM\n if (!runsUnderSeedcordDev()) this[HostShutdown].registerSignalHandlers();\n if (this.config.errors?.catchProcessErrors ?? true) {\n ServerHost.#liveProcessErrors = registerProcessErrors(this, this[HostShutdown]);\n }\n\n const startupSettled: PromiseWithResolvers<void> = Promise.withResolvers();\n this[HostShutdown].gateOnStartup(startupSettled.promise);\n\n try {\n // nothing a plugin adds through startup.addTask runs after this\n await this[HostStartup].run(() => this.openHealth());\n } catch (caught) {\n this.#startFailed = true;\n await this.#lifecycle.rollback();\n throw caught;\n } finally {\n startupSettled.resolve();\n }\n\n return this;\n }\n\n /** @internal */\n protected openHealth(): Promisable<void> {}\n\n /**\n * Runs the coordinated shutdown without calling `process.exit`. A new `Seedcord` can be\n * constructed afterwards. `await using` calls this when its scope ends.\n *\n * @example\n * ```ts\n * // the bot shuts down when this block ends\n * await using bot = await new Seedcord(config).start();\n * console.log(await bot.rest.get(Routes.user('@me')));\n * ```\n */\n public async [Symbol.asyncDispose](): Promise<void> {\n await this.#release(0);\n }\n\n async #release(exitCode: number): Promise<void> {\n try {\n await this[HostShutdown].run(exitCode, false);\n } finally {\n ServerHost.reset(this);\n }\n }\n\n /** @internal */\n protected static reset(host?: object): boolean {\n if (host !== undefined && ServerHost.#liveHost !== host) return false;\n\n ServerHost.#liveShutdown?.removeSignalHandlers();\n ServerHost.#liveShutdown = undefined;\n ServerHost.#liveProcessErrors?.();\n ServerHost.#liveProcessErrors = undefined;\n ServerHost.#liveHost = undefined;\n ServerHost.#isInstantiated = false;\n // configure() keeps the dev TUI's capture sink\n LoggerChannelRegistry.instance.configure({});\n return true;\n }\n}\n\n// the public `shutdown` field carries addTask alone\nexport function shutdownOf(host: Pick<ServerHost<Transport>, typeof HostShutdown>): CoordinatedShutdown {\n return host[HostShutdown];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ServerHost-CDaEDR4C.mjs","names":[],"sources":["../src/node/assertRuntimeVersion.ts","../src/node/processErrors.ts","../src/node/ServerHost.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { isValid, isValidRange, satisfies } from 'verkit';\n\nexport function assertDeclaredRuntime(): void {\n const { bun, node } = process.versions;\n // tsdown-config bakes both ranges in from this package's engines\n const runtime =\n bun === undefined\n ? { name: 'Node', range: process.env.PACKAGE_NODE_RANGE ?? '', running: node }\n : { name: 'Bun', range: process.env.PACKAGE_BUN_RANGE ?? '', running: bun };\n\n if (\n !runtime.range ||\n !isValidRange(runtime.range) ||\n !isValid(runtime.running) ||\n satisfies(runtime.running, runtime.range, { includePrerelease: true })\n )\n return;\n\n throw new SeedcordError(SeedcordErrorCode.UnsupportedRuntimeVersion, [\n runtime.name,\n runtime.range,\n runtime.running\n ]);\n}\n","import crypto from 'node:crypto';\n\nimport { Logger } from '@seedcord/logger';\nimport { asError } from '@seedcord/utils/internal';\n\nimport { logUnknownFault } from '#src/notices/logUnknownFault';\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, origin: string): void {\n const error = asError(caught);\n const uuid = crypto.randomUUID();\n\n logUnknownFault(logger, uuid, error, core.config.errors);\n\n core.bus[PublishDefault]('unknownException', { uuid, dispatchId: null, error, origin });\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 { SeedcordError } from '@seedcord/errors/internal';\nimport { LoggerChannelRegistry } from '@seedcord/logger';\nimport { installNodeConsole, installNodeDefaults } from '@seedcord/logger/node';\nimport { HostShutdown, HostStartup } from '@seedcord/types/internal';\nimport { runsUnderSeedcordDev } from '@seedcord/utils/internal';\n\nimport { assertDeclaredRuntime } from '#node/assertRuntimeVersion';\nimport { ShutdownPhase } from '#src/lifecycle/phases';\nimport { PluginLifecycle } from '#src/lifecycle/PluginLifecycle';\nimport { attachmentsOf, PluginHost, sealAttachments } from '#src/plugin/PluginHost';\n\nimport { registerProcessErrors } from './processErrors';\n\nimport type { CoordinatedShutdown, ShutdownTasks } from '#node/Lifecycle/CoordinatedShutdown';\nimport type { CoordinatedStartup, StartupTasks } from '#src/lifecycle/CoordinatedStartup';\nimport type { Transport } from '#src/plugin/options';\nimport type { Config } from '@seedcord/types';\nimport type { Promisable } from 'type-fest';\n\n/** Base class for a transport `Seedcord` class that runs as a long-lived node or bun process. */\nexport abstract class ServerHost<BotT extends Transport> extends PluginHost<BotT, 'server'> {\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: ShutdownTasks;\n\n /** Add a task that runs while the bot starts. */\n public readonly startup: StartupTasks;\n\n readonly #lifecycle: PluginLifecycle;\n #startFailed = false;\n #initPromise?: Promise<this> | undefined;\n\n static #isInstantiated = false;\n static #liveHost?: object | undefined;\n static #liveShutdown?: CoordinatedShutdown | undefined;\n static #liveProcessErrors?: (() => void) | undefined;\n\n constructor(transport: BotT, shutdown: CoordinatedShutdown, startup: CoordinatedStartup, driver: Config['kv']) {\n // a `sideEffects: false` build would drop the same call in the node entry\n assertDeclaredRuntime();\n super(transport, 'server', driver);\n\n if (ServerHost.#isInstantiated) throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);\n\n // init() opens the log file\n installNodeConsole();\n\n ServerHost.#isInstantiated = true;\n ServerHost.#liveHost = this;\n ServerHost.#liveShutdown = shutdown;\n this[HostShutdown] = shutdown;\n this[HostStartup] = startup;\n // a getter returning the slot would expose run() and the signal handlers too\n this.shutdown = { addTask: shutdown.addTask.bind(shutdown) };\n this.startup = { addTask: startup.addTask.bind(startup) };\n this.#lifecycle = new PluginLifecycle(this.startup, this.shutdown);\n shutdown.addTask(ShutdownPhase.Release, 'close-kv', async () => {\n await using _driver = this.kv.driver;\n });\n }\n\n /** @internal */\n protected init(): Promise<this> {\n // a retry after a rejection runs #runInit again and hits its restart guard\n this.#initPromise ??= this.#runInit().catch(async (caught: unknown) => {\n this.#initPromise = undefined;\n await this.#release(1);\n throw caught;\n });\n return this.#initPromise;\n }\n\n async #runInit(): Promise<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 installNodeDefaults(this.config.logger);\n sealAttachments(this);\n this.#lifecycle.register(attachmentsOf(this));\n\n // codegen and the build construct the bot without starting it.\n // under seedcord dev the CLI handles SIGINT and SIGTERM\n if (!runsUnderSeedcordDev()) this[HostShutdown].registerSignalHandlers();\n if (this.config.errors?.catchProcessErrors ?? true) {\n ServerHost.#liveProcessErrors = registerProcessErrors(this, this[HostShutdown]);\n }\n\n const startupSettled: PromiseWithResolvers<void> = Promise.withResolvers();\n this[HostShutdown].gateOnStartup(startupSettled.promise);\n\n try {\n // nothing a plugin adds through startup.addTask runs after this\n await this[HostStartup].run(() => this.openHealth());\n } catch (caught) {\n this.#startFailed = true;\n await this.#lifecycle.rollback();\n throw caught;\n } finally {\n startupSettled.resolve();\n }\n\n return this;\n }\n\n /** @internal */\n protected openHealth(): Promisable<void> {}\n\n /**\n * Runs the coordinated shutdown without calling `process.exit`. A new `Seedcord` can be\n * constructed afterwards. `await using` calls this when its scope ends.\n *\n * @example\n * ```ts\n * // the bot shuts down when this block ends\n * await using bot = await new Seedcord(config).start();\n * console.log(await bot.rest.get(Routes.user('@me')));\n * ```\n */\n public async [Symbol.asyncDispose](): Promise<void> {\n await this.#release(0);\n }\n\n async #release(exitCode: number): Promise<void> {\n try {\n await this[HostShutdown].run(exitCode, false);\n } finally {\n ServerHost.reset(this);\n }\n }\n\n /** @internal */\n protected static reset(host?: object): boolean {\n if (host !== undefined && ServerHost.#liveHost !== host) return false;\n\n ServerHost.#liveShutdown?.removeSignalHandlers();\n ServerHost.#liveShutdown = undefined;\n ServerHost.#liveProcessErrors?.();\n ServerHost.#liveProcessErrors = undefined;\n ServerHost.#liveHost = undefined;\n ServerHost.#isInstantiated = false;\n // configure() keeps the dev TUI's capture sink\n LoggerChannelRegistry.instance.configure({});\n return true;\n }\n}\n\n// the public `shutdown` field carries addTask alone\nexport function shutdownOf(host: Pick<ServerHost<Transport>, typeof HostShutdown>): CoordinatedShutdown {\n return host[HostShutdown];\n}\n"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { t as _usingCtx } from "./usingCtx-Cf2cZi96.mjs";
2
+ import { PortableSource, merge } from "envapt";
3
+
4
+ //#region src/miscellaneous/bindViteEnv.ts
5
+ function bindEnv(envapter, source, directory) {
6
+ try {
7
+ var _usingCtx$1 = _usingCtx();
8
+ const previousSource = envapter.source;
9
+ const previousBaseDir = previousSource.supportsFiles ? envapter.baseDir : void 0;
10
+ const cleanup = _usingCtx$1.u(new DisposableStack());
11
+ cleanup.defer(() => {
12
+ envapter.useSource(previousSource);
13
+ if (previousSource.supportsFiles) envapter.baseDir = previousBaseDir;
14
+ });
15
+ if (previousSource.supportsFiles && directory !== void 0) envapter.baseDir = directory;
16
+ envapter.useSource(source);
17
+ return cleanup.move();
18
+ } catch (_) {
19
+ _usingCtx$1.e = _;
20
+ } finally {
21
+ _usingCtx$1.d();
22
+ }
23
+ }
24
+ function bindViteEnv(envapter, vite, { bindings, directory } = {}) {
25
+ const { MODE: mode, BASE_URL: _baseUrl, DEV: _dev, PROD: _prod, SSR: _ssr, ...values } = vite;
26
+ const source = bindings === void 0 ? envapter.source : merge((key) => key === "MODE" && typeof mode === "string" ? mode : void 0, new PortableSource(bindings));
27
+ return bindEnv(envapter, merge(source, new PortableSource(values)), directory);
28
+ }
29
+
30
+ //#endregion
31
+ export { bindViteEnv as n, bindEnv as t };
32
+ //# sourceMappingURL=bindViteEnv-NFzvdqlT.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bindViteEnv-DWq5rftJ.mjs","names":[],"sources":["../src/miscellaneous/bindViteEnv.ts"],"sourcesContent":["import { merge, PortableSource } from 'envapt';\n\nimport type { Envapter, Source } from 'envapt';\n\nexport function bindEnv(envapter: typeof Envapter, source: Source, directory?: string): Disposable {\n const previousSource = envapter.source;\n const previousBaseDir = previousSource.supportsFiles ? envapter.baseDir : undefined;\n using cleanup = new DisposableStack();\n cleanup.defer(() => {\n envapter.useSource(previousSource);\n if (previousSource.supportsFiles) envapter.baseDir = previousBaseDir;\n });\n if (previousSource.supportsFiles && directory !== undefined) envapter.baseDir = directory;\n envapter.useSource(source);\n return cleanup.move();\n}\n\ninterface ViteEnvOptions {\n bindings?: Record<string, unknown>;\n directory?: string | undefined;\n}\n\nexport function bindViteEnv(\n envapter: typeof Envapter,\n vite: Record<string, unknown>,\n { bindings, directory }: ViteEnvOptions = {}\n): Disposable {\n // vite has alrdy applied envPrefix to the exposed user vars\n const { MODE: mode, BASE_URL: _baseUrl, DEV: _dev, PROD: _prod, SSR: _ssr, ...values } = vite;\n const source =\n bindings === undefined\n ? envapter.source\n : merge(\n (key) => (key === 'MODE' && typeof mode === 'string' ? mode : undefined),\n new PortableSource(bindings)\n );\n return bindEnv(envapter, merge(source, new PortableSource(values)), directory);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,SAAgB,QAAQ,UAA2B,QAAgB,WAAgC;;;EAC/F,MAAM,iBAAiB,SAAS;EAChC,MAAM,kBAAkB,eAAe,gBAAgB,SAAS,UAAU;EAC1E,MAAM,wBAAU,IAAI,gBAAgB;EACpC,QAAQ,YAAY;GAChB,SAAS,UAAU,cAAc;GACjC,IAAI,eAAe,eAAe,SAAS,UAAU;EACzD,CAAC;EACD,IAAI,eAAe,iBAAiB,cAAc,QAAW,SAAS,UAAU;EAChF,SAAS,UAAU,MAAM;EACzB,OAAO,QAAQ,KAAK;;;;;;AACxB;AAOA,SAAgB,YACZ,UACA,MACA,EAAE,UAAU,cAA8B,CAAC,GACjC;CAEV,MAAM,EAAE,MAAM,MAAM,UAAU,UAAU,KAAK,MAAM,MAAM,OAAO,KAAK,MAAM,GAAG,WAAW;CACzF,MAAM,SACF,aAAa,SACP,SAAS,SACT,OACK,QAAS,QAAQ,UAAU,OAAO,SAAS,WAAW,OAAO,QAC9D,IAAI,eAAe,QAAQ,CAC/B;CACV,OAAO,QAAQ,UAAU,MAAM,QAAQ,IAAI,eAAe,MAAM,CAAC,GAAG,SAAS;AACjF"}
1
+ {"version":3,"file":"bindViteEnv-NFzvdqlT.mjs","names":[],"sources":["../src/miscellaneous/bindViteEnv.ts"],"sourcesContent":["import { merge, PortableSource } from 'envapt';\n\nimport type { Envapter, Source } from 'envapt';\n\nexport function bindEnv(envapter: typeof Envapter, source: Source, directory?: string): Disposable {\n const previousSource = envapter.source;\n const previousBaseDir = previousSource.supportsFiles ? envapter.baseDir : undefined;\n using cleanup = new DisposableStack();\n cleanup.defer(() => {\n envapter.useSource(previousSource);\n if (previousSource.supportsFiles) envapter.baseDir = previousBaseDir;\n });\n if (previousSource.supportsFiles && directory !== undefined) envapter.baseDir = directory;\n envapter.useSource(source);\n return cleanup.move();\n}\n\ninterface ViteEnvOptions {\n bindings?: Record<string, unknown>;\n directory?: string | undefined;\n}\n\nexport function bindViteEnv(\n envapter: typeof Envapter,\n vite: Record<string, unknown>,\n { bindings, directory }: ViteEnvOptions = {}\n): Disposable {\n // vite has alrdy applied envPrefix to the exposed user vars\n const { MODE: mode, BASE_URL: _baseUrl, DEV: _dev, PROD: _prod, SSR: _ssr, ...values } = vite;\n const source =\n bindings === undefined\n ? envapter.source\n : merge(\n (key) => (key === 'MODE' && typeof mode === 'string' ? mode : undefined),\n new PortableSource(bindings)\n );\n return bindEnv(envapter, merge(source, new PortableSource(values)), directory);\n}\n"],"mappings":";;;;AAIA,SAAgB,QAAQ,UAA2B,QAAgB,WAAgC;;;EAC/F,MAAM,iBAAiB,SAAS;EAChC,MAAM,kBAAkB,eAAe,gBAAgB,SAAS,UAAU;EAC1E,MAAM,wBAAU,IAAI,gBAAgB;EACpC,QAAQ,YAAY;GAChB,SAAS,UAAU,cAAc;GACjC,IAAI,eAAe,eAAe,SAAS,UAAU;EACzD,CAAC;EACD,IAAI,eAAe,iBAAiB,cAAc,QAAW,SAAS,UAAU;EAChF,SAAS,UAAU,MAAM;EACzB,OAAO,QAAQ,KAAK;;;;;;AACxB;AAOA,SAAgB,YACZ,UACA,MACA,EAAE,UAAU,cAA8B,CAAC,GACjC;CAEV,MAAM,EAAE,MAAM,MAAM,UAAU,UAAU,KAAK,MAAM,MAAM,OAAO,KAAK,MAAM,GAAG,WAAW;CACzF,MAAM,SACF,aAAa,SACP,SAAS,SACT,OACK,QAAS,QAAQ,UAAU,OAAO,SAAS,WAAW,OAAO,QAC9D,IAAI,eAAe,QAAQ,CAC/B;CACV,OAAO,QAAQ,UAAU,MAAM,QAAQ,IAAI,eAAe,MAAM,CAAC,GAAG,SAAS;AACjF"}
@@ -1,3 +1,3 @@
1
- import { n as bindViteEnv } from "./bindViteEnv-DWq5rftJ.mjs";
1
+ import { n as bindViteEnv } from "./bindViteEnv-NFzvdqlT.mjs";
2
2
 
3
3
  export { bindViteEnv };
@@ -1,17 +1,22 @@
1
1
  import { Logger } from "@seedcord/logger";
2
2
  import { HmrUpdateEvent } from "@seedcord/types";
3
3
  //#region src/hmr/HmrModuleHandler.d.ts
4
- interface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unknown> {
5
- handlersDir: string;
6
- middlewaresDir?: string;
7
- isHandler: (val: unknown) => val is THandler;
8
- isMiddleware?: (val: unknown) => val is TMiddleware;
9
- registerHandler: (handler: THandler, file: string) => void;
10
- registerMiddleware?: (middleware: TMiddleware, file: string) => void;
11
- unregisterHandler: (handler: THandler, artifacts?: TArtifacts) => void;
12
- unregisterMiddleware?: (middleware: TMiddleware) => void;
13
- /** Snapshotted per handler so a rollback re-registers with the same artifacts after the module re-imports. */
4
+ interface HmrFolderOptions<TUnit> {
5
+ dir: string;
6
+ is: (val: unknown) => val is TUnit;
7
+ register: (unit: TUnit, file: string) => void;
8
+ }
9
+ interface HmrHandlerOptions<THandler, TArtifacts> extends HmrFolderOptions<THandler> {
10
+ unregister: (handler: THandler, artifacts?: TArtifacts) => void;
11
+ /** Called each time a handler is tracked. `unregister` receives the result as its second argument. */
14
12
  getArtifacts?: (handler: THandler) => TArtifacts;
13
+ }
14
+ interface HmrMiddlewareOptions<TMiddleware> extends HmrFolderOptions<TMiddleware> {
15
+ unregister: (middleware: TMiddleware) => void;
16
+ }
17
+ interface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unknown> {
18
+ handlers: HmrHandlerOptions<THandler, TArtifacts>;
19
+ middlewares?: HmrMiddlewareOptions<TMiddleware>;
15
20
  logger: Logger;
16
21
  }
17
22
  /**
@@ -22,22 +27,17 @@ interface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unk
22
27
  * This is only useful during development.
23
28
  */
24
29
  export declare class HmrModuleHandler<THandler, TMiddleware = void, TArtifacts = unknown> {
25
- private readonly store;
26
- private readonly options;
30
+ #private;
27
31
  constructor(options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>);
28
- /** Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories. */
32
+ /**
33
+ * Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories.
34
+ * When that unloads something, it also retries every file that failed earlier on a duplicate name.
35
+ */
29
36
  handle(event: HmrUpdateEvent): Promise<void>;
30
- private inScope;
31
- private report;
32
- private reloadWithRollback;
33
37
  /** Links the handler to its source file so a later update can unload it. */
34
38
  trackHandler(file: string, handler: THandler): void;
35
39
  /** Links the middleware to its source file so a later update can unload it. */
36
40
  trackMiddleware(file: string, middleware: TMiddleware): void;
37
- private unload;
38
- private reloadFile;
39
- private snapshotUnits;
40
- private restoreUnits;
41
41
  }
42
42
  //#endregion
43
43
  //# sourceMappingURL=hmr.index.d.mts.map
@@ -1,3 +1,3 @@
1
- import { t as HmrModuleHandler } from "./HmrModuleHandler-8ZC-UuZh.mjs";
1
+ import { t as HmrModuleHandler } from "./HmrModuleHandler-Btgu8r96.mjs";
2
2
 
3
3
  export { HmrModuleHandler };