@seedcord/core 0.10.0 → 0.11.0

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 (63) hide show
  1. package/README.md +27 -9
  2. package/dist/CoordinatedShutdown-H5YjZn8A.d.mts +78 -0
  3. package/dist/CoordinatedStartup-CGnJrqcl.mjs +168 -0
  4. package/dist/CoordinatedStartup-CGnJrqcl.mjs.map +1 -0
  5. package/dist/{HmrModuleHandler-DOAtah3j.mjs → HmrModuleHandler-8ZC-UuZh.mjs} +9 -22
  6. package/dist/HmrModuleHandler-8ZC-UuZh.mjs.map +1 -0
  7. package/dist/PluginHost-B8kxLOeb.mjs +245 -0
  8. package/dist/PluginHost-B8kxLOeb.mjs.map +1 -0
  9. package/dist/{Plugin-Dhx_ivqj.d.mts → PluginHost-DB4j8unu.d.mts} +110 -17
  10. package/dist/PluginLifecycle-AhQ46NIr.mjs +158 -0
  11. package/dist/PluginLifecycle-AhQ46NIr.mjs.map +1 -0
  12. package/dist/ServerHost-CtmAhEXn.mjs +168 -0
  13. package/dist/ServerHost-CtmAhEXn.mjs.map +1 -0
  14. package/dist/ServerHost-DYeh0KTk.d.mts +40 -0
  15. package/dist/bindViteEnv-BgFewlZ0.d.mts +11 -0
  16. package/dist/bindViteEnv-DWq5rftJ.mjs +88 -0
  17. package/dist/bindViteEnv-DWq5rftJ.mjs.map +1 -0
  18. package/dist/env-internal.index.d.mts +2 -0
  19. package/dist/env-internal.index.mjs +3 -0
  20. package/dist/hmr.index.d.mts +0 -1
  21. package/dist/hmr.index.mjs +1 -1
  22. package/dist/{cursor-BTU5bGoo.d.mts → index-Du71Xx69.d.mts} +253 -6
  23. package/dist/index.d.mts +19 -84
  24. package/dist/index.mjs +17 -37
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/internal.index.d.mts +186 -22
  27. package/dist/internal.index.mjs +714 -19
  28. package/dist/internal.index.mjs.map +1 -1
  29. package/dist/node-internal.index.d.mts +25 -70
  30. package/dist/node-internal.index.mjs +83 -297
  31. package/dist/node-internal.index.mjs.map +1 -1
  32. package/dist/node.index.d.mts +3 -2
  33. package/dist/node.index.mjs +53 -200
  34. package/dist/node.index.mjs.map +1 -1
  35. package/dist/{phases-D_zQBXga.d.mts → phases-FTM69J3a.d.mts} +4 -4
  36. package/dist/plugin.index.d.mts +2 -2
  37. package/dist/plugin.index.mjs +2 -2
  38. package/dist/publishDefault-D3dAyzZ9.mjs +7 -0
  39. package/dist/publishDefault-D3dAyzZ9.mjs.map +1 -0
  40. package/dist/subscribers-Ce3SWy23.mjs +1913 -0
  41. package/dist/subscribers-Ce3SWy23.mjs.map +1 -0
  42. package/package.json +24 -17
  43. package/dist/Bus-BxPa-Uvp.mjs +0 -866
  44. package/dist/Bus-BxPa-Uvp.mjs.map +0 -1
  45. package/dist/HmrModuleHandler-DOAtah3j.mjs.map +0 -1
  46. package/dist/Pluggable-CwKbLui3.d.mts +0 -162
  47. package/dist/Pluggable-afy2H0Dr.mjs +0 -339
  48. package/dist/Pluggable-afy2H0Dr.mjs.map +0 -1
  49. package/dist/Plugin-CmNW5n6a.mjs +0 -122
  50. package/dist/Plugin-CmNW5n6a.mjs.map +0 -1
  51. package/dist/contextMenuLeaves-COzDYa6c.d.mts +0 -17
  52. package/dist/cursor-CgNBnJfu.mjs +0 -802
  53. package/dist/cursor-CgNBnJfu.mjs.map +0 -1
  54. package/dist/devChannel-CQU7hq7Q.mjs +0 -14
  55. package/dist/devChannel-CQU7hq7Q.mjs.map +0 -1
  56. package/dist/index-B_-EjyvS.d.mts +0 -135
  57. package/dist/publishDefault-BuSZPbjT.mjs +0 -14
  58. package/dist/publishDefault-BuSZPbjT.mjs.map +0 -1
  59. package/dist/slashRouteLeaves-k1hJnXDO.mjs +0 -31
  60. package/dist/slashRouteLeaves-k1hJnXDO.mjs.map +0 -1
  61. package/dist/subscribers-xGy7a7xR.mjs +0 -167
  62. package/dist/subscribers-xGy7a7xR.mjs.map +0 -1
  63. package/dist/types-BG0BimVr.d.mts +0 -13
@@ -0,0 +1,245 @@
1
+ import { n as StartupPhase, t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
2
+ import { SeedcordErrorCode } from "@seedcord/errors";
3
+ import { SeedcordError, SeedcordRangeError, SeedcordTypeError } from "@seedcord/errors/internal";
4
+ import { FRAMEWORK_CHANNELS, Logger } from "@seedcord/logger";
5
+ import { HostPluginKeys } from "@seedcord/types/internal";
6
+
7
+ //#region src/hmr/devChannel.ts
8
+ let channel;
9
+ /** @internal */
10
+ function setDevChannel(next) {
11
+ channel = next;
12
+ }
13
+ /** @internal */
14
+ function getDevChannel() {
15
+ return channel;
16
+ }
17
+
18
+ //#endregion
19
+ //#region src/plugin/lifecycle.ts
20
+ const DEFAULT_INIT_TIMEOUT_MS = 15e3;
21
+ const DEFAULT_READY_TIMEOUT_MS = 15e3;
22
+ const DEFAULT_DISPOSE_TIMEOUT_MS = 1e4;
23
+ function timeoutOf(field, declared, fallback, pluginName) {
24
+ if (declared === void 0) return fallback;
25
+ if (!Number.isFinite(declared) || declared <= 0) throw new SeedcordRangeError(SeedcordErrorCode.LifecycleInvalidTimeout, [`${pluginName}'s lifecycle ${field}.timeout`, declared]);
26
+ return declared;
27
+ }
28
+ /** @internal */
29
+ function resolveLifecycleSpec(spec, pluginName) {
30
+ return {
31
+ init: {
32
+ phase: spec?.init?.phase ?? 1,
33
+ timeout: timeoutOf("init", spec?.init?.timeout, DEFAULT_INIT_TIMEOUT_MS, pluginName)
34
+ },
35
+ ready: { timeout: timeoutOf("ready", spec?.ready?.timeout, DEFAULT_READY_TIMEOUT_MS, pluginName) },
36
+ dispose: {
37
+ phase: spec?.dispose?.phase ?? 3,
38
+ timeout: timeoutOf("dispose", spec?.dispose?.timeout, DEFAULT_DISPOSE_TIMEOUT_MS, pluginName)
39
+ }
40
+ };
41
+ }
42
+
43
+ //#endregion
44
+ //#region src/plugin/Plugin.ts
45
+ /** @internal */
46
+ const resolvedSpecSlot = Symbol("seedcord:plugin:spec");
47
+ /** @internal */
48
+ const loggerSlot = Symbol("seedcord:plugin:logger");
49
+ /** @internal */
50
+ const scopeSlot = Symbol("seedcord:plugin:scope");
51
+ /** @internal */
52
+ const FixedScope = Symbol("seedcord:plugin:fixed-scope");
53
+ /**
54
+ * Base class for a seedcord plugin. Extend it and implement `init()`. `this.core` is the running
55
+ * bot.
56
+ *
57
+ * Your constructor takes the host first and passes it to `super()`. Add your own parameters after
58
+ * it. `attach()` types them at the call site.
59
+ *
60
+ * A plugin that narrows `transport` or `runtime` in `Opts` passes the same values to `super()`.
61
+ * `attach()` throws when either one differs from the bot's.
62
+ *
63
+ * @typeParam Opts - Where this plugin may attach.
64
+ * @typeParam TCore - The `Core` each transport binds `this.core` to.
65
+ * @typeParam Fixed - The `transport` and `runtime` values a transport's own `Plugin` base sets. Leave it out.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * class Analytics extends Plugin<{ transport: 'gateway' }> {
70
+ * constructor(
71
+ * host: CoreBase,
72
+ * private readonly apiKey: string
73
+ * ) {
74
+ * super(host, { transport: 'gateway', init: { phase: StartupPhase.Login } });
75
+ * }
76
+ *
77
+ * public async init(): Promise<void> {
78
+ * await this.connect(this.apiKey);
79
+ * }
80
+ * }
81
+ *
82
+ * seedcord.attach('analytics', Analytics, apiKey);
83
+ * ```
84
+ */
85
+ var Plugin = class {
86
+ host;
87
+ /** @internal */
88
+ static [FixedScope] = {};
89
+ /** @internal */
90
+ [resolvedSpecSlot];
91
+ /** @internal */
92
+ [loggerSlot];
93
+ /** @internal */
94
+ [scopeSlot];
95
+ /** Logs under the plugin's class name, on the channel its attach key sets. */
96
+ logger;
97
+ constructor(host, ...[spec]) {
98
+ this.host = host;
99
+ this.logger = new Logger(this.constructor.name);
100
+ this[loggerSlot] = this.logger;
101
+ this[resolvedSpecSlot] = resolveLifecycleSpec(spec, this.constructor.name);
102
+ const passed = spec;
103
+ const fixed = new.target[FixedScope];
104
+ this[scopeSlot] = {
105
+ transport: fixed.transport ?? passed?.transport ?? "any",
106
+ runtime: fixed.runtime ?? passed?.runtime ?? "any"
107
+ };
108
+ }
109
+ /** The host, typed to the transport whose `Plugin` base this class extends. */
110
+ get core() {
111
+ return this.host;
112
+ }
113
+ /**
114
+ * Rejects the options this plugin got.
115
+ *
116
+ * @param reason - Why the options are invalid.
117
+ * @throws A **SeedcordTypeError** every time, with the plugin class name and `reason` in its message.
118
+ */
119
+ rejectOptions(reason) {
120
+ throw new SeedcordTypeError(SeedcordErrorCode.PluginOptionsRejected, [this.constructor.name, reason]);
121
+ }
122
+ /** Override to reload plugin state on an HMR update. */
123
+ onHmr(_event) {
124
+ return Promise.resolve();
125
+ }
126
+ /**
127
+ * Registers critical file patterns that should trigger a full restart when changed in Dev HMR.
128
+ * @param patterns - Glob patterns. A relative one resolves against `root` from `seedcord.config.ts`.
129
+ */
130
+ registerCriticalFiles(patterns) {
131
+ getDevChannel()?.send("seedcord:register-critical-files", { patterns });
132
+ }
133
+ };
134
+ /** @internal */
135
+ function resolvedLifecycleSpecOf(plugin) {
136
+ return plugin[resolvedSpecSlot];
137
+ }
138
+ /** @internal */
139
+ function extendsThisCorePlugin(ctor) {
140
+ return ctor.prototype instanceof Plugin;
141
+ }
142
+ /** @internal */
143
+ function pluginLoggerOf(plugin) {
144
+ return plugin[loggerSlot];
145
+ }
146
+ /** @internal */
147
+ function declaredScopeOf(plugin) {
148
+ return plugin[scopeSlot];
149
+ }
150
+
151
+ //#endregion
152
+ //#region src/plugin/PluginHost.ts
153
+ const RESERVED_KEYS = new Set(FRAMEWORK_CHANNELS);
154
+ const attachmentsSlot = Symbol("seedcord:host:attachments");
155
+ const sealSlot = Symbol("seedcord:host:seal");
156
+ /** Base class for a plugin host. */
157
+ var PluginHost = class {
158
+ #sealed = false;
159
+ #attachments = [];
160
+ #groups = /* @__PURE__ */ new Map();
161
+ #scope;
162
+ constructor(transport, runtime) {
163
+ this.#scope = {
164
+ transport,
165
+ runtime
166
+ };
167
+ }
168
+ /** @internal */
169
+ get [attachmentsSlot]() {
170
+ return this.#attachments;
171
+ }
172
+ /** @internal */
173
+ [sealSlot]() {
174
+ this.#sealed = true;
175
+ }
176
+ /** @internal codegen reads this to emit the `Core` augmentation */
177
+ get [HostPluginKeys]() {
178
+ return this.#attachments.map((attachment) => attachment.key);
179
+ }
180
+ attach(key, Plugin, ...args) {
181
+ if (this.#sealed) throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);
182
+ const dot = key.indexOf(".");
183
+ const head = dot === -1 ? key : key.slice(0, dot);
184
+ if (RESERVED_KEYS.has(head)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginReservedChannel, [head]);
185
+ const leaf = dot === -1 ? void 0 : key.slice(dot + 1);
186
+ if (head === "" || leaf === "") throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyMalformed, [key, "has an empty part."]);
187
+ if (leaf?.includes(".")) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyMalformed, [key, "has more than one dot."]);
188
+ this.#assertFree(head, leaf, key);
189
+ if (!extendsThisCorePlugin(Plugin)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginFromOtherCore, [Plugin.name]);
190
+ const instance = new Plugin(this, ...args);
191
+ this.#assertScope(Plugin.name, declaredScopeOf(instance));
192
+ pluginLoggerOf(instance).setChannel(key);
193
+ this.#attachments.push({
194
+ key,
195
+ instance,
196
+ spec: resolvedLifecycleSpecOf(instance)
197
+ });
198
+ if (leaf === void 0) return Object.assign(this, { [key]: instance });
199
+ this.#groupFor(head)[leaf] = instance;
200
+ return this;
201
+ }
202
+ #assertFree(head, leaf, key) {
203
+ if (leaf === void 0) {
204
+ if (this.#groups.has(key)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyHoldsGroup, [key]);
205
+ if (key in this) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyExists, [key]);
206
+ return;
207
+ }
208
+ const group = this.#groups.get(head);
209
+ if (!group) {
210
+ if (head in this) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginGroupTaken, [head, key]);
211
+ return;
212
+ }
213
+ if (Object.hasOwn(group, leaf)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyExists, [key]);
214
+ }
215
+ #assertScope(pluginName, declared) {
216
+ for (const axis of ["transport", "runtime"]) {
217
+ const host = this.#scope[axis];
218
+ if (declared[axis] === "any" || declared[axis] === host) continue;
219
+ throw new SeedcordTypeError(SeedcordErrorCode.CorePluginScopeMismatch, [
220
+ pluginName,
221
+ axis,
222
+ declared[axis],
223
+ host
224
+ ]);
225
+ }
226
+ }
227
+ #groupFor(head) {
228
+ const existing = this.#groups.get(head);
229
+ if (existing) return existing;
230
+ const group = Object.create(null);
231
+ this.#groups.set(head, group);
232
+ Object.assign(this, { [head]: group });
233
+ return group;
234
+ }
235
+ };
236
+ function attachmentsOf(host) {
237
+ return host[attachmentsSlot];
238
+ }
239
+ function sealAttachments(host) {
240
+ host[sealSlot]();
241
+ }
242
+
243
+ //#endregion
244
+ export { Plugin as a, setDevChannel as c, FixedScope as i, attachmentsOf as n, pluginLoggerOf as o, sealAttachments as r, getDevChannel as s, PluginHost as t };
245
+ //# sourceMappingURL=PluginHost-B8kxLOeb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PluginHost-B8kxLOeb.mjs","names":[],"sources":["../src/hmr/devChannel.ts","../src/plugin/lifecycle.ts","../src/plugin/Plugin.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\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.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 { 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';\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/** @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 * 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 * 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 // 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 this.logger = new Logger(this.constructor.name);\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 * 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 /**\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?(): Promise<void>;\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): 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. 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\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 } from '@seedcord/errors';\nimport { SeedcordError, SeedcordTypeError } from '@seedcord/errors/internal';\nimport { FRAMEWORK_CHANNELS } from '@seedcord/logger';\nimport { HostPluginKeys } from '@seedcord/types/internal';\n\nimport { declaredScopeOf, extendsThisCorePlugin, pluginLoggerOf, resolvedLifecycleSpecOf } from './Plugin';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { Bus } from '#subscribers/Bus';\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 public abstract readonly rateLimiter: IRateLimiter;\n public abstract readonly bus: Bus;\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) {\n this.#scope = { transport, runtime };\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 * 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, 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 dot = key.indexOf('.');\n const head = dot === -1 ? key : key.slice(0, dot);\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 const leaf = dot === -1 ? undefined : key.slice(dot + 1);\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 instance = new Plugin(this, ...args);\n this.#assertScope(Plugin.name, declaredScopeOf(instance));\n pluginLoggerOf(instance).setChannel(key);\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,9 +1,12 @@
1
- import { n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-D_zQBXga.mjs";
1
+ import { i as Bus, n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-FTM69J3a.mjs";
2
2
  import { Logger } from "@seedcord/logger";
3
- import { FrameworkChannel, HmrAware, HmrUpdateEvent, Tail, TypedExclude } from "@seedcord/types";
3
+ import { REST } from "@discordjs/rest";
4
+ import { HostPluginKeys } from "@seedcord/types/internal";
5
+ import { Config, FrameworkChannel, HmrAware, HmrUpdateEvent, IRateLimiter, Tail, TypedExclude } from "@seedcord/types";
4
6
  //#region src/plugin/brands.d.ts
5
7
  declare const TransportBrand: unique symbol;
6
8
  declare const RuntimeBrand: unique symbol;
9
+ declare const ScopedSpec: unique symbol;
7
10
  //#endregion
8
11
  //#region src/plugin/lifecycle.d.ts
9
12
  /**
@@ -53,13 +56,17 @@ interface ResolvedPluginLifecycleSpec {
53
56
  //#endregion
54
57
  //#region src/plugin/options.d.ts
55
58
  /**
56
- * Declares where a plugin may attach. Pass it as the `Plugin<Opts>` type argument. `attach()` fails
57
- * to compile when the host does not match.
59
+ * Declares where a plugin may attach. Pass it as the `Plugin<Opts>` type argument. Every field you
60
+ * narrow goes to `super()` as well. `attach()` fails to compile when the host does not match.
58
61
  *
59
62
  * @example
60
63
  * ```ts
61
64
  * // attaching this to an http bot fails to compile
62
65
  * class Voice extends Plugin<{ transport: 'gateway' }> {
66
+ * constructor(host: CoreBase) {
67
+ * super(host, { transport: 'gateway' });
68
+ * }
69
+ *
63
70
  * public async init(): Promise<void> {}
64
71
  * }
65
72
  * ```
@@ -84,9 +91,26 @@ type RuntimeOf<Opts extends PluginOptions> = undefined extends Opts['runtime'] ?
84
91
  type Transport = TypedExclude<NonNullable<PluginOptions['transport']>, 'any'>;
85
92
  /** @internal */
86
93
  type Runtime = TypedExclude<NonNullable<PluginOptions['runtime']>, 'any'>;
94
+ /** @internal */
95
+ interface DeclaredScope {
96
+ readonly transport: Transport | 'any';
97
+ readonly runtime: Runtime | 'any';
98
+ }
99
+ /** @internal */
100
+ type ScopeAxis = keyof DeclaredScope;
101
+ type NarrowedTransport<Opts extends PluginOptions> = TransportOf<Opts> extends 'any' ? unknown : {
102
+ transport: TransportOf<Opts>;
103
+ };
104
+ type NarrowedRuntime<Opts extends PluginOptions> = RuntimeOf<Opts> extends 'any' ? unknown : {
105
+ runtime: RuntimeOf<Opts>;
106
+ };
107
+ type ScopeToPass<Opts extends PluginOptions, Fixed extends ScopeAxis> = ('transport' extends Fixed ? unknown : NarrowedTransport<Opts>) & ('runtime' extends Fixed ? unknown : NarrowedRuntime<Opts>);
108
+ /** @internal */
109
+ type PluginSpecArgs<Opts extends PluginOptions, Fixed extends ScopeAxis> = unknown extends ScopeToPass<Opts, Fixed> ? [spec?: PluginLifecycleSpec] : [spec: PluginLifecycleSpec & ScopeToPass<Opts, Fixed> & {
110
+ readonly [ScopedSpec]?: never;
111
+ }];
87
112
  type TransportMismatch<PluginT extends string, BotT extends string> = Record<`this plugin declares transport '${PluginT}' but this bot runs '${BotT}'`, never>;
88
113
  type RuntimeMismatch<PluginRt extends string, BotRt extends string> = Record<`this plugin declares runtime '${PluginRt}' but this bot runs '${BotRt}'`, never>;
89
- type EdgePluginsUnsupported = Record<'an edge bot takes no plugins until edge support ships after v1', never>;
90
114
  type BrandTransport<Plug> = Plug extends {
91
115
  readonly [TransportBrand]?: infer T extends string;
92
116
  } ? T : 'any';
@@ -96,7 +120,7 @@ type BrandRuntime<Plug> = Plug extends {
96
120
  /** @internal */
97
121
  type TransportAssert<Plug, BotT extends Transport> = BrandTransport<Plug> extends 'any' | BotT ? unknown : TransportMismatch<BrandTransport<Plug>, BotT>;
98
122
  /** @internal */
99
- type RuntimeAssert<Plug, BotRt extends Runtime> = 'edge' extends BotRt ? EdgePluginsUnsupported : BrandRuntime<Plug> extends 'any' | BotRt ? unknown : RuntimeMismatch<BrandRuntime<Plug>, BotRt>;
123
+ type RuntimeAssert<Plug, BotRt extends Runtime> = BrandRuntime<Plug> extends 'any' | BotRt ? unknown : RuntimeMismatch<BrandRuntime<Plug>, BotRt>;
100
124
  //#endregion
101
125
  //#region src/plugin/Plugin.d.ts
102
126
  interface Initializeable {
@@ -106,6 +130,10 @@ interface Initializeable {
106
130
  declare const resolvedSpecSlot: unique symbol;
107
131
  /** @internal */
108
132
  declare const loggerSlot: unique symbol;
133
+ /** @internal */
134
+ declare const scopeSlot: unique symbol;
135
+ /** @internal */
136
+ declare const FixedScope: unique symbol;
109
137
  /**
110
138
  * Base class for a seedcord plugin. Extend it and implement `init()`. `this.core` is the running
111
139
  * bot.
@@ -113,8 +141,12 @@ declare const loggerSlot: unique symbol;
113
141
  * Your constructor takes the host first and passes it to `super()`. Add your own parameters after
114
142
  * it. `attach()` types them at the call site.
115
143
  *
116
- * @typeParam Opts - Where this plugin may attach, checked only at compile time.
144
+ * A plugin that narrows `transport` or `runtime` in `Opts` passes the same values to `super()`.
145
+ * `attach()` throws when either one differs from the bot's.
146
+ *
147
+ * @typeParam Opts - Where this plugin may attach.
117
148
  * @typeParam TCore - The `Core` each transport binds `this.core` to.
149
+ * @typeParam Fixed - The `transport` and `runtime` values a transport's own `Plugin` base sets. Leave it out.
118
150
  *
119
151
  * @example
120
152
  * ```ts
@@ -123,7 +155,7 @@ declare const loggerSlot: unique symbol;
123
155
  * host: CoreBase,
124
156
  * private readonly apiKey: string
125
157
  * ) {
126
- * super(host, { init: { phase: StartupPhase.Login } });
158
+ * super(host, { transport: 'gateway', init: { phase: StartupPhase.Login } });
127
159
  * }
128
160
  *
129
161
  * public async init(): Promise<void> {
@@ -134,9 +166,11 @@ declare const loggerSlot: unique symbol;
134
166
  * seedcord.attach('analytics', Analytics, apiKey);
135
167
  * ```
136
168
  */
137
- declare abstract class Plugin<Opts extends PluginOptions = {}, TCore extends CoreBase = CoreBase> implements Initializeable, HmrAware {
169
+ declare abstract class Plugin<Opts extends PluginOptions = {}, TCore extends CoreBase = CoreBase, Fixed extends ScopeAxis = never> implements Initializeable, HmrAware {
138
170
  private readonly host;
139
171
  /** @internal */
172
+ protected static readonly [FixedScope]: Partial<DeclaredScope>;
173
+ /** @internal */
140
174
  readonly [TransportBrand]?: TransportOf<Opts>;
141
175
  /** @internal */
142
176
  readonly [RuntimeBrand]?: RuntimeOf<Opts>;
@@ -144,9 +178,11 @@ declare abstract class Plugin<Opts extends PluginOptions = {}, TCore extends Cor
144
178
  readonly [resolvedSpecSlot]: ResolvedPluginLifecycleSpec;
145
179
  /** @internal */
146
180
  readonly [loggerSlot]: Logger;
181
+ /** @internal */
182
+ readonly [scopeSlot]: DeclaredScope;
147
183
  /** Logs under the plugin's class name, on the channel its attach key sets. */
148
184
  protected readonly logger: Logger;
149
- constructor(host: CoreBase, spec?: PluginLifecycleSpec);
185
+ constructor(host: CoreBase, ...[spec]: PluginSpecArgs<Opts, Fixed>);
150
186
  /** The host, typed to the transport whose `Plugin` base this class extends. */
151
187
  protected get core(): TCore;
152
188
  /**
@@ -170,25 +206,27 @@ declare abstract class Plugin<Opts extends PluginOptions = {}, TCore extends Cor
170
206
  */
171
207
  dispose?(): Promise<void>;
172
208
  /**
173
- * Throws a `SeedcordError` that contains the plugin class name and the reason.
209
+ * Rejects the options this plugin got.
174
210
  *
175
211
  * @param reason - Why the options are invalid.
212
+ * @throws A **SeedcordTypeError** every time, with the plugin class name and `reason` in its message.
176
213
  */
177
214
  protected rejectOptions(reason: string): never;
178
215
  /** Override to reload plugin state on an HMR update. */
179
216
  onHmr(_event: HmrUpdateEvent): Promise<void>;
180
217
  /**
181
218
  * Registers critical file patterns that should trigger a full restart when changed in Dev HMR.
182
- * @param patterns - Glob patterns relative to the project root
219
+ * @param patterns - Glob patterns. A relative one resolves against `root` from `seedcord.config.ts`.
183
220
  */
184
221
  protected registerCriticalFiles(patterns: string[]): void;
185
222
  }
186
223
  /** @internal */
187
- type PluginLike = Pick<Plugin, 'init' | 'ready' | 'dispose' | 'onHmr' | typeof resolvedSpecSlot | typeof loggerSlot>;
224
+ type PluginLike = Pick<Plugin, 'init' | 'ready' | 'dispose' | 'onHmr' | typeof resolvedSpecSlot | typeof loggerSlot | typeof scopeSlot>;
188
225
  /** @internal */
189
226
  type PluginCtor<TPlugin extends PluginLike = PluginLike> = new (...args: any[]) => TPlugin;
190
227
  /** @internal */
191
- type PluginArgs<Ctor extends PluginCtor> = Tail<ConstructorParameters<Ctor>>;
228
+ type PluginArgs<Ctor extends PluginCtor> = InheritsScopedConstructor<Ctor> extends true ? [] : Tail<ConstructorParameters<Ctor>>;
229
+ type InheritsScopedConstructor<Ctor extends PluginCtor> = Tail<ConstructorParameters<Ctor>> extends [infer Spec, ...unknown[]] ? typeof ScopedSpec extends keyof Spec ? true : false : false;
192
230
  /** @internal */
193
231
  type Attached<Key extends string, Instance> = Key extends `${infer Group}.${infer Leaf}` ? Record<Group, Record<Leaf, Instance>> : Record<Key, Instance>;
194
232
  type HostMember<Host> = keyof Host | keyof Object;
@@ -196,9 +234,64 @@ type HostMember<Host> = keyof Host | keyof Object;
196
234
  type AttachKeyAssert<Key extends string, Host> = Key extends '' ? 'A plugin key needs a name.' : Key extends `${infer Group}.${infer Leaf}` ? Leaf extends `${string}.${string}` ? `'${Key}' has more than one dot. A plugin key takes one dot at most.` : '' extends Group | Leaf ? `'${Key}' has an empty part. Write a group and a plugin name around the dot, like 'services.users'.` : Group extends FrameworkChannel ? `'${Group}' is a channel the framework logs on. Pick another group name.` : Group extends keyof Host ? Host[Group] extends PluginLike ? `'${Group}' already holds a plugin, so '${Leaf}' cannot nest inside it.` : Host[Group] extends Record<string, PluginLike> ? Leaf extends keyof Host[Group] ? `'${Key}' is already attached.` : Key : `'${Group}' is already a member on the bot. Pick another group name.` : Group extends HostMember<Host> ? `'${Group}' is already a member on the bot. Pick another group name.` : Key : Key extends FrameworkChannel ? `'${Key}' is a channel the framework logs on. Pick another plugin key.` : Key extends keyof Host ? Host[Key] extends PluginLike ? `'${Key}' is already attached.` : Host[Key] extends Record<string, PluginLike> ? `'${Key}' already holds a group of plugins. Attach this one under a name of its own.` : `'${Key}' is already a member on the bot. Pick another plugin key.` : Key extends HostMember<Host> ? `'${Key}' is already a member on the bot. Pick another plugin key.` : Key;
197
235
  type CoreParamTooNarrow = Record<'this plugin constructor must take CoreBase as its first parameter and read the transport Core off this.core', never>;
198
236
  type CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0] ? unknown : CoreParamTooNarrow;
199
- type AttachAsserts<Ctor extends PluginCtor, BotT extends Transport, BotRt extends Runtime> = TransportAssert<InstanceType<Ctor>, BotT> & RuntimeAssert<InstanceType<Ctor>, BotRt> & CoreParamAssert<Ctor>;
237
+ type ConstructorMissing = Record<"this plugin narrows its transport or runtime. Give it a constructor that passes them to super(), like super(host, { runtime: 'server' })", never>;
238
+ type OwnConstructorAssert<Ctor extends PluginCtor> = InheritsScopedConstructor<Ctor> extends true ? ConstructorMissing : unknown;
239
+ type AttachAsserts<Ctor extends PluginCtor, BotT extends Transport, BotRt extends Runtime> = TransportAssert<InstanceType<Ctor>, BotT> & RuntimeAssert<InstanceType<Ctor>, BotRt> & CoreParamAssert<Ctor> & OwnConstructorAssert<Ctor>;
200
240
  /** @internal */
201
241
  type AttachableCtor<Ctor extends PluginCtor, BotT extends Transport, BotRt extends Runtime> = unknown extends AttachAsserts<Ctor, BotT, BotRt> ? Ctor : AttachAsserts<Ctor, BotT, BotRt>;
202
242
  //#endregion
203
- export { Plugin as a, PluginLike as c, Transport as d, PluginLifecycleSpec as f, Initializeable as i, PluginOptions as l, AttachableCtor as n, PluginArgs as o, Attached as r, PluginCtor as s, AttachKeyAssert as t, Runtime as u };
204
- //# sourceMappingURL=Plugin-Dhx_ivqj.d.mts.map
243
+ //#region src/plugin/PluginHost.d.ts
244
+ interface Attachment {
245
+ readonly key: string;
246
+ readonly instance: PluginLike;
247
+ readonly spec: ResolvedPluginLifecycleSpec;
248
+ }
249
+ declare const attachmentsSlot: unique symbol;
250
+ declare const sealSlot: unique symbol;
251
+ /** Base class for a plugin host. */
252
+ declare abstract class PluginHost<BotT extends Transport, BotRt extends Runtime> implements CoreBase {
253
+ #private;
254
+ abstract readonly config: Config;
255
+ abstract readonly rest: REST;
256
+ abstract readonly applicationId: string;
257
+ abstract readonly rateLimiter: IRateLimiter;
258
+ abstract readonly bus: Bus;
259
+ constructor(transport: BotT, runtime: BotRt);
260
+ /** @internal */
261
+ get [attachmentsSlot](): readonly Attachment[];
262
+ /** @internal */
263
+ [sealSlot](): void;
264
+ /** @internal codegen reads this to emit the `Core` augmentation */
265
+ get [HostPluginKeys](): readonly string[];
266
+ /**
267
+ * Attaches a plugin under `key`. Read the instance back as `core[key]`. `seedcord codegen`
268
+ * writes the `Core` augmentation that types it there.
269
+ *
270
+ * Put one dot in the key to nest the plugin under a group. `'services.users'` reads back as
271
+ * `core.services.users`. Each name holds one plugin or one group.
272
+ *
273
+ * Startup runs each plugin's `init()` in attach order within its phase. A node bot starts in
274
+ * `start()`. An edge bot starts on its first request.
275
+ *
276
+ * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.
277
+ * Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).
278
+ * A narrower one fails to compile here.
279
+ *
280
+ * @param key - Also the channel the plugin logs on, dots included.
281
+ * @param args - Whatever your constructor takes after the host.
282
+ * @throws A **SeedcordError** when you attach after the bot has started.
283
+ * @throws A **SeedcordTypeError** when `key` is taken, reserved, or malformed.
284
+ * @throws A **SeedcordTypeError** when the plugin extends `Plugin` from another copy of `@seedcord/core`.
285
+ * @throws A **SeedcordTypeError** when the plugin's `transport` or `runtime` differs from this host and the types were bypassed.
286
+ * @example
287
+ * ```ts
288
+ * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... });
289
+ * ```
290
+ */
291
+ attach<Key extends string, Ctor extends PluginCtor>(this: this, key: AttachKeyAssert<Key, this>, Plugin: AttachableCtor<Ctor, BotT, BotRt>, ...args: PluginArgs<Ctor>): this & Attached<Key, InstanceType<Ctor>>;
292
+ }
293
+ declare function attachmentsOf(host: Pick<PluginHost<Transport, Runtime>, typeof attachmentsSlot>): readonly Attachment[];
294
+ declare function sealAttachments(host: Pick<PluginHost<Transport, Runtime>, typeof sealSlot>): void;
295
+ //#endregion
296
+ export { FixedScope as a, PluginArgs as c, Transport as d, PluginLifecycleSpec as f, sealAttachments as i, PluginCtor as l, PluginHost as n, Initializeable as o, attachmentsOf as r, Plugin as s, Attachment as t, PluginOptions as u };
297
+ //# sourceMappingURL=PluginHost-DB4j8unu.d.mts.map