@seedcord/core 0.11.0 → 0.12.0-next.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.
- package/dist/Bus-W1K-uMeO.mjs +930 -0
- package/dist/Bus-W1K-uMeO.mjs.map +1 -0
- package/dist/{CoordinatedShutdown-H5YjZn8A.d.mts → CoordinatedShutdown-BVtNNSsF.d.mts} +13 -4
- package/dist/{CoordinatedStartup-CGnJrqcl.mjs → CoordinatedStartup-DA3gW8op.mjs} +22 -20
- package/dist/CoordinatedStartup-DA3gW8op.mjs.map +1 -0
- package/dist/Generated-ByC53_A2.d.mts +26 -0
- package/dist/{HmrModuleHandler-8ZC-UuZh.mjs → HmrModuleHandler-BMD7dg8N.mjs} +2 -2
- package/dist/HmrModuleHandler-BMD7dg8N.mjs.map +1 -0
- package/dist/PluginHost-CYe-UVpp.mjs +552 -0
- package/dist/PluginHost-CYe-UVpp.mjs.map +1 -0
- package/dist/{PluginHost-DB4j8unu.d.mts → PluginHost-DWBgBS6Q.d.mts} +35 -8
- package/dist/{PluginLifecycle-AhQ46NIr.mjs → PluginLifecycle-DdwRzcDj.mjs} +3 -3
- package/dist/PluginLifecycle-DdwRzcDj.mjs.map +1 -0
- package/dist/{ServerHost-CtmAhEXn.mjs → ServerHost-DHg-Y2_q.mjs} +17 -6
- package/dist/{ServerHost-CtmAhEXn.mjs.map → ServerHost-DHg-Y2_q.mjs.map} +1 -1
- package/dist/{ServerHost-DYeh0KTk.d.mts → ServerHost-whpI0h4d.d.mts} +7 -6
- package/dist/bindViteEnv-NFzvdqlT.mjs +32 -0
- package/dist/{bindViteEnv-DWq5rftJ.mjs.map → bindViteEnv-NFzvdqlT.mjs.map} +1 -1
- package/dist/env-internal.index.mjs +1 -1
- package/dist/hmr.index.mjs +1 -1
- package/dist/{index-Du71Xx69.d.mts → index-Boo33hh3.d.mts} +10 -11
- package/dist/index.d.mts +108 -6
- package/dist/index.mjs +118 -12
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.d.mts +25 -7
- package/dist/internal.index.mjs +8 -8
- package/dist/node-internal.index.d.mts +7 -4
- package/dist/node-internal.index.mjs +19 -6
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/node.index.d.mts +3 -3
- package/dist/node.index.mjs +22 -28
- package/dist/node.index.mjs.map +1 -1
- package/dist/{phases-FTM69J3a.d.mts → phases-Dojnr-ng.d.mts} +111 -12
- package/dist/plugin.index.d.mts +3 -2
- package/dist/plugin.index.mjs +1 -1
- package/dist/{subscribers-Ce3SWy23.mjs → subscribers-D4_HZcgw.mjs} +12 -829
- package/dist/subscribers-D4_HZcgw.mjs.map +1 -0
- package/dist/{bindViteEnv-DWq5rftJ.mjs → usingCtx-Cf2cZi96.mjs} +1 -31
- package/package.json +11 -9
- package/dist/CoordinatedStartup-CGnJrqcl.mjs.map +0 -1
- package/dist/HmrModuleHandler-8ZC-UuZh.mjs.map +0 -1
- package/dist/PluginHost-B8kxLOeb.mjs +0 -245
- package/dist/PluginHost-B8kxLOeb.mjs.map +0 -1
- package/dist/PluginLifecycle-AhQ46NIr.mjs.map +0 -1
- package/dist/phases-BauKJOTT.mjs +0 -31
- package/dist/phases-BauKJOTT.mjs.map +0 -1
- package/dist/publishDefault-D3dAyzZ9.mjs +0 -7
- package/dist/publishDefault-D3dAyzZ9.mjs.map +0 -1
- package/dist/subscribers-Ce3SWy23.mjs.map +0 -1
package/dist/internal.index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { c as
|
|
4
|
-
import { t as HmrModuleHandler } from "./HmrModuleHandler-
|
|
5
|
-
import { i as logUnknownFault, t as PluginLifecycle } from "./PluginLifecycle-
|
|
6
|
-
import { t as CoordinatedStartup } from "./CoordinatedStartup-
|
|
7
|
-
import { t as bindEnv } from "./bindViteEnv-
|
|
1
|
+
import { $ as commandKey, A as serializeReply, B as contextMenuRouteOf, C as NotAllowed, D as OnCooldown, E as NotOwner, F as AckTrace, G as decodeComponentRoute, H as storeComponentRoute, I as checkAckLegality, J as CommandInjector, K as ConfigGuilds, L as sendTarget, P as reportedWrite, Q as warnLinks, S as NeedsAny, T as NotInGuild, U as storeInteractionRoute, V as interactionRoutesOf, W as ComponentDefsKey, Z as installLinks, _ as runCommits, at as guardedAccessor, b as MissingPermissions, c as PermissionNames, ct as globalFirst, dt as contextMenuLeaves, et as commandRefOf, f as pickNotice, ft as NoticeCard, g as runCheck, i as Silence, it as clearStore, j as translateSerializationError, lt as scopeIdsOf, n as pageCursor, nt as linkCommands, ot as GLOBAL_ID, p as discardCommits, pt as Notice, r as ResolvedEmoji, rt as accessorStore, st as commandsRoute, t as PAGE_MAX, tt as indexById, ut as skippedNote, v as GateNotice, w as NotInDm, x as MissingRole, y as HasDangerousPermissions, z as interactionMiddlewareMetaOf } from "./subscribers-D4_HZcgw.mjs";
|
|
2
|
+
import { A as WebhookUrlMetadataKey, C as EventMiddlewareMetadataKey, D as InteractionMiddlewareMetadataKey, E as InteractionMetadataKey, N as isBuilderComponentClass, O as InteractionRouteKeys, P as bindBotColor, S as EventMetadataKey, a as RegisterSubscriber, b as PublishDefault, c as VerifyWebhooks, f as Subscriber, g as OnceTracker, h as sendFlags, i as RegisterDefaults, k as SubscribeMetadataKey, m as deferFlags, n as busLoggerOf, o as RegisteredCount, p as ReportThrottle, r as registrationFor, s as UnregisterSubscriber, w as GatedMetadataKey, x as CommandMetadataKey } from "./Bus-W1K-uMeO.mjs";
|
|
3
|
+
import { a as FixedScope, c as getDevChannel, i as splitPluginKey, l as setDevChannel, n as attachmentsOf, r as sealAttachments } from "./PluginHost-CYe-UVpp.mjs";
|
|
4
|
+
import { t as HmrModuleHandler } from "./HmrModuleHandler-BMD7dg8N.mjs";
|
|
5
|
+
import { i as logUnknownFault, t as PluginLifecycle } from "./PluginLifecycle-DdwRzcDj.mjs";
|
|
6
|
+
import { t as CoordinatedStartup } from "./CoordinatedStartup-DA3gW8op.mjs";
|
|
7
|
+
import { t as bindEnv } from "./bindViteEnv-NFzvdqlT.mjs";
|
|
8
8
|
import "reflect-metadata";
|
|
9
9
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
|
10
10
|
import { Routes } from "discord-api-types/v10";
|
|
@@ -969,5 +969,5 @@ async function runAfter(ran, result, logger) {
|
|
|
969
969
|
}
|
|
970
970
|
|
|
971
971
|
//#endregion
|
|
972
|
-
export { AckTrace, AutocompleteRouteBrand, BAKED_IDS_KEY, COMMAND_DEPLOY_TIMEOUT_MS, CommandMetadataKey, CommandRegistry, ComponentDefsBrand, ComponentDefsKey, ComponentKindBrand, ContextMenuKindBrand, ContextMenuNamesBrand, CoordinatedStartup, EventMetadataKey, EventMiddlewareMetadataKey, FixedScope, GLOBAL_ID, GateNotice, GatedMetadataKey, HasDangerousPermissions, HmrManager, InteractionMetadataKey, InteractionMiddlewareMetadataKey, InteractionRouteKeys, MiddlewareKindsBrand, MiddlewareRegistry, MissingPermissions, MissingRole, NeedsAny, NotAllowed, NotInDm, NotInGuild, NotOwner, NoticeCard, OnCooldown, PAGE_MAX, PermissionNames, PluginLifecycle, PublishDefault, RegisterDefaults, RegisterSubscriber, RegisteredCount, ReportThrottle, SlashRouteBrand, SubscribeMetadataKey, SubscriberLoader, UnregisterSubscriber, VerifyWebhooks, WebhookUrlMetadataKey, accessorStore, attachmentsOf, bindBotColor, bindEnv, busLoggerOf, checkAckLegality, clearStore, commandKey, commandRefOf, commandsRoute, connectDevChannel, contextMenuLeaves, contextMenuRouteOf, currentApplication, decodeComponentRoute, deferFlags, eventResultFor, forwardLogsToCli, fromDevLogLine, fromWireError, getDevChannel, globalFirst, guardedAccessor, guildSummaries, indexById, installLinks, interactionMiddleware, interactionMiddlewareMetaOf, interactionRoutesOf, isCommandClass, isEmojiTuple, linkCommands, logUnknownFault, outcomeFor, pageCursor, pickNotice, planDeploy, putCommands, queuedMsFor, readBotToken, readCommands, readCommandsAt, registrationFor, reportDispatch, reportedWrite, resolveDeployTarget, resolveEmojis, resultFor, runAfter, runGates, runHandlerGates, scopeIdsOf, sealAttachments, sendFlags, sendTarget, serializeReply, setDevChannel, skippedNote, slashRouteLeaves, slowGateMonitor, storeComponentRoute, storeInteractionRoute, toWireError, translateSerializationError, warnLinks, wrapHot };
|
|
972
|
+
export { AckTrace, AutocompleteRouteBrand, BAKED_IDS_KEY, COMMAND_DEPLOY_TIMEOUT_MS, CommandMetadataKey, CommandRegistry, ComponentDefsBrand, ComponentDefsKey, ComponentKindBrand, ContextMenuKindBrand, ContextMenuNamesBrand, CoordinatedStartup, EventMetadataKey, EventMiddlewareMetadataKey, FixedScope, GLOBAL_ID, GateNotice, GatedMetadataKey, HasDangerousPermissions, HmrManager, InteractionMetadataKey, InteractionMiddlewareMetadataKey, InteractionRouteKeys, MiddlewareKindsBrand, MiddlewareRegistry, MissingPermissions, MissingRole, NeedsAny, NotAllowed, NotInDm, NotInGuild, NotOwner, NoticeCard, OnCooldown, OnceTracker, PAGE_MAX, PermissionNames, PluginLifecycle, PublishDefault, RegisterDefaults, RegisterSubscriber, RegisteredCount, ReportThrottle, SlashRouteBrand, SubscribeMetadataKey, SubscriberLoader, UnregisterSubscriber, VerifyWebhooks, WebhookUrlMetadataKey, accessorStore, attachmentsOf, bindBotColor, bindEnv, busLoggerOf, checkAckLegality, clearStore, commandKey, commandRefOf, commandsRoute, connectDevChannel, contextMenuLeaves, contextMenuRouteOf, currentApplication, decodeComponentRoute, deferFlags, eventResultFor, forwardLogsToCli, fromDevLogLine, fromWireError, getDevChannel, globalFirst, guardedAccessor, guildSummaries, indexById, installLinks, interactionMiddleware, interactionMiddlewareMetaOf, interactionRoutesOf, isCommandClass, isEmojiTuple, linkCommands, logUnknownFault, outcomeFor, pageCursor, pickNotice, planDeploy, putCommands, queuedMsFor, readBotToken, readCommands, readCommandsAt, registrationFor, reportDispatch, reportedWrite, resolveDeployTarget, resolveEmojis, resultFor, runAfter, runGates, runHandlerGates, scopeIdsOf, sealAttachments, sendFlags, sendTarget, serializeReply, setDevChannel, skippedNote, slashRouteLeaves, slowGateMonitor, splitPluginKey, storeComponentRoute, storeInteractionRoute, toWireError, translateSerializationError, warnLinks, wrapHot };
|
|
973
973
|
//# sourceMappingURL=internal.index.mjs.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as StartupPhase, t as ShutdownPhase } from "./phases-
|
|
2
|
-
import {
|
|
3
|
-
import { n as shutdownOf } from "./ServerHost-
|
|
1
|
+
import { n as StartupPhase, t as ShutdownPhase } from "./phases-Dojnr-ng.mjs";
|
|
2
|
+
import { a as LifecycleTask, t as CoordinatedShutdown } from "./CoordinatedShutdown-BVtNNSsF.mjs";
|
|
3
|
+
import { n as shutdownOf } from "./ServerHost-whpI0h4d.mjs";
|
|
4
4
|
import { Logger } from "@seedcord/logger";
|
|
5
5
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
6
6
|
import { HealthCheckConfig, HealthCheckOption } from "@seedcord/types";
|
|
@@ -13,10 +13,13 @@ export declare class InFlight {
|
|
|
13
13
|
private readonly logger;
|
|
14
14
|
private readonly label;
|
|
15
15
|
private readonly running;
|
|
16
|
-
private
|
|
16
|
+
private readonly opening;
|
|
17
|
+
private state;
|
|
17
18
|
constructor(logger: Logger, label: string);
|
|
18
19
|
get closed(): boolean;
|
|
20
|
+
open(): void;
|
|
19
21
|
close(): void;
|
|
22
|
+
admit<Result>(work: () => Promise<Result>): Promise<Result | undefined>;
|
|
20
23
|
track(work: Promise<unknown>): void;
|
|
21
24
|
drain(timeoutMs: number): Promise<void>;
|
|
22
25
|
private settleAll;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as settleWithin } from "./PluginLifecycle-
|
|
3
|
-
import { n as shutdownOf, r as assertDeclaredRuntime } from "./ServerHost-
|
|
1
|
+
import { v as ShutdownPhase, y as StartupPhase } from "./Bus-W1K-uMeO.mjs";
|
|
2
|
+
import { n as settleWithin } from "./PluginLifecycle-DdwRzcDj.mjs";
|
|
3
|
+
import { n as shutdownOf, r as assertDeclaredRuntime } from "./ServerHost-DHg-Y2_q.mjs";
|
|
4
4
|
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
5
5
|
import { SeedcordError } from "@seedcord/errors/internal";
|
|
6
6
|
import { Logger } from "@seedcord/logger";
|
|
@@ -18,16 +18,29 @@ var InFlight = class {
|
|
|
18
18
|
logger;
|
|
19
19
|
label;
|
|
20
20
|
running = /* @__PURE__ */ new Set();
|
|
21
|
-
|
|
21
|
+
opening = Promise.withResolvers();
|
|
22
|
+
state = "held";
|
|
22
23
|
constructor(logger, label) {
|
|
23
24
|
this.logger = logger;
|
|
24
25
|
this.label = label;
|
|
25
26
|
}
|
|
26
27
|
get closed() {
|
|
27
|
-
return this.
|
|
28
|
+
return this.state === "closed";
|
|
29
|
+
}
|
|
30
|
+
open() {
|
|
31
|
+
if (this.state !== "held") return;
|
|
32
|
+
this.state = "open";
|
|
33
|
+
this.opening.resolve(true);
|
|
28
34
|
}
|
|
29
35
|
close() {
|
|
30
|
-
this.
|
|
36
|
+
this.state = "closed";
|
|
37
|
+
this.opening.resolve(false);
|
|
38
|
+
}
|
|
39
|
+
admit(work) {
|
|
40
|
+
if (this.state === "closed") return Promise.resolve(void 0);
|
|
41
|
+
const admitted = this.state === "open" ? work() : this.opening.promise.then((opened) => opened ? work() : void 0);
|
|
42
|
+
this.track(admitted);
|
|
43
|
+
return admitted;
|
|
31
44
|
}
|
|
32
45
|
track(work) {
|
|
33
46
|
this.running.add(work);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-internal.index.mjs","names":[],"sources":["../src/node/Lifecycle/drainWindow.ts","../src/node/Lifecycle/InFlight.ts","../src/node/HealthResponder.ts","../src/node/HealthCheck.ts","../src/node/envaptBaseDir.ts","../src/node-internal.index.ts"],"sourcesContent":["// gateway already used 5s. discord's 3s first-ack deadline is the floor.\nexport const DRAIN_WINDOW_MS = 5000;\n\nconst DRAIN_HEADROOM_MS = 1000;\nexport const DRAIN_TASK_TIMEOUT_MS = DRAIN_WINDOW_MS + DRAIN_HEADROOM_MS;\n","import { settleWithin } from '#src/lifecycle/withTimeout';\n\nimport type { Logger } from '@seedcord/logger';\n\nexport class InFlight {\n private readonly running = new Set<Promise<unknown>>();\n private isClosed = false;\n\n constructor(\n private readonly logger: Logger,\n private readonly label: string\n ) {}\n\n public get closed(): boolean {\n return this.isClosed;\n }\n\n public close(): void {\n this.isClosed = true;\n }\n\n public track(work: Promise<unknown>): void {\n this.running.add(work);\n const forget = (): void => {\n this.running.delete(work);\n };\n void work.then(forget, forget);\n }\n\n public async drain(timeoutMs: number): Promise<void> {\n await settleWithin(this.settleAll(), timeoutMs);\n // nothing cancels these before the process exits\n if (this.running.size > 0) {\n this.logger.warn(`${this.label}: ${String(this.running.size)} still running when the drain window closed`);\n }\n }\n\n // settling work can track more work\n private async settleAll(): Promise<void> {\n while (this.running.size > 0) await Promise.allSettled(this.running);\n }\n}\n","import type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nconst HTTP_OK = 200;\nconst HTTP_SERVICE_UNAVAILABLE = 503;\nconst DEFAULT_HEALTH_CHECK_PATH = '/health';\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\n\nexport function healthCheckConfig(option?: HealthCheckOption): HealthCheckConfig | undefined {\n if (option === false) return undefined;\n return typeof option === 'object' ? option : {};\n}\n\nexport class HealthResponder {\n public readonly path: string;\n\n constructor(path?: string) {\n this.path = path ?? DEFAULT_HEALTH_CHECK_PATH;\n }\n\n public static fromOption(option?: HealthCheckOption): HealthResponder | undefined {\n const config = healthCheckConfig(option);\n return config && new HealthResponder(config.path);\n }\n\n public tryRespond(req: IncomingMessage, res: ServerResponse): boolean {\n if (!this.matches(req.method, req.url)) return false;\n\n res.writeHead(HTTP_OK, JSON_HEADERS);\n res.end(this.body());\n return true;\n }\n\n public answer(request: Request, ready: boolean): Response | undefined {\n if (!this.matches(request.method, new URL(request.url).pathname)) return undefined;\n if (!ready) return new Response(null, { status: HTTP_SERVICE_UNAVAILABLE });\n return new Response(this.body(), { status: HTTP_OK, headers: JSON_HEADERS });\n }\n\n private matches(method: string | undefined, path: string | undefined): boolean {\n return method === 'GET' && path === this.path;\n }\n\n private body(): string {\n return JSON.stringify({ status: 'ok', timestamp: Date.now() });\n }\n}\n","import { createServer } from 'node:http';\n\nimport { paint, SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\nimport { isPortInUse } from '@seedcord/utils/internal';\n\nimport { ShutdownPhase } from '#src/lifecycle/phases';\n\nimport { healthCheckConfig, HealthResponder } from './HealthResponder';\n\nimport type { CoordinatedShutdown } from './Lifecycle/CoordinatedShutdown';\nimport type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\n\nconst HTTP_NOT_FOUND = 404;\n\nconst DEFAULT_HEALTH_CHECK_PORT = 6967;\n\nexport class HealthCheck {\n public readonly logger = new Logger('HealthCheck', { channel: 'health' });\n\n private readonly _port: number;\n private readonly _host: string | undefined;\n\n private readonly responder: HealthResponder;\n private server?: Server;\n\n constructor(shutdown: CoordinatedShutdown, options?: HealthCheckConfig) {\n this._port = options?.port ?? DEFAULT_HEALTH_CHECK_PORT;\n this._host = options?.host;\n this.responder = new HealthResponder(options?.path);\n\n shutdown.addTask(ShutdownPhase.Drain, 'stop-healthcheck-server', () => this.stop());\n }\n\n public get port(): number {\n return this._port;\n }\n\n public get host(): string | undefined {\n return this._host;\n }\n\n public get path(): string {\n return this.responder.path;\n }\n\n public static fromOption(shutdown: CoordinatedShutdown, option?: HealthCheckOption): HealthCheck | undefined {\n const config = healthCheckConfig(option);\n return config && new HealthCheck(shutdown, config);\n }\n\n public async init(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n if (this.responder.tryRespond(req, res)) return;\n\n res.writeHead(HTTP_NOT_FOUND, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'not found' }));\n });\n this.server = server;\n\n const onListenError = (error: Error): void => {\n if (isPortInUse(error)) {\n reject(\n new SeedcordError(SeedcordErrorCode.PortInUse, [this._port, 'healthCheck.port'], {\n cause: error\n })\n );\n return;\n }\n reject(error);\n };\n server.on('error', onListenError);\n\n server.once('listening', () => {\n // a late error would reject an already-settled promise, and node throws process-wide without a listener here\n server.removeListener('error', onListenError);\n server.on('error', (err) => this.logger.error('Health check server error', err));\n\n // binds all interfaces, so log an address a browser can open\n const address = this._host ?? 'localhost';\n this.logger.info(\n `${paint.mint.bold(paint.check)} Health check server listening on ${paint.sky.bold(`http://${address}:${this._port}${this.path}`)}`\n );\n resolve();\n });\n\n if (this._host) {\n this.logger.debug(`Binding health check server to ${this._host}`);\n server.listen(this._port, this._host);\n } else {\n this.logger.debug('Binding health check server to all interfaces');\n server.listen(this._port);\n }\n });\n }\n\n public stop(): Promise<void> {\n const server = this.server;\n // close() on a non-listening server invokes its callback with ERR_SERVER_NOT_RUNNING and\n // never fires 'close', so without this guard the promise would hang the shutdown phase.\n if (!server?.listening) return Promise.resolve();\n\n return new Promise((resolve, reject) => {\n // settle only in the callback, a 'close'-event resolve would swallow the callback's error\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n this.logger.info(paint.coral.bold('Health check server stopped'));\n resolve();\n });\n });\n }\n}\n","import { botConfigDir, setBuiltFilesHook } from '@seedcord/utils/internal';\nimport { Envapter } from 'envapt';\n\nexport function setEnvaptBaseDir(): void {\n setBuiltFilesHook(() => {\n const configDir = botConfigDir();\n if (configDir !== undefined && Envapter.source.supportsFiles) Envapter.baseDir = configDir;\n });\n}\n","import { assertDeclaredRuntime } from '#node/assertRuntimeVersion';\n\n// the edge bundle never reaches this entry\nassertDeclaredRuntime();\n\nexport * from '#node/Lifecycle';\nexport * from '#node/HealthCheck';\nexport { HealthResponder } from '#node/HealthResponder';\nexport { settleWithin } from '#src/lifecycle/withTimeout';\nexport { shutdownOf } from '#node/ServerHost';\nexport { setEnvaptBaseDir } from '#node/envaptBaseDir';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"node-internal.index.mjs","names":[],"sources":["../src/node/Lifecycle/drainWindow.ts","../src/node/Lifecycle/InFlight.ts","../src/node/HealthResponder.ts","../src/node/HealthCheck.ts","../src/node/envaptBaseDir.ts","../src/node-internal.index.ts"],"sourcesContent":["// gateway already used 5s. discord's 3s first-ack deadline is the floor.\nexport const DRAIN_WINDOW_MS = 5000;\n\nconst DRAIN_HEADROOM_MS = 1000;\nexport const DRAIN_TASK_TIMEOUT_MS = DRAIN_WINDOW_MS + DRAIN_HEADROOM_MS;\n","import { settleWithin } from '#src/lifecycle/withTimeout';\n\nimport type { Logger } from '@seedcord/logger';\n\nexport class InFlight {\n private readonly running = new Set<Promise<unknown>>();\n private readonly opening = Promise.withResolvers<boolean>();\n private state: 'held' | 'open' | 'closed' = 'held';\n\n constructor(\n private readonly logger: Logger,\n private readonly label: string\n ) {}\n\n public get closed(): boolean {\n return this.state === 'closed';\n }\n\n public open(): void {\n if (this.state !== 'held') return;\n this.state = 'open';\n this.opening.resolve(true);\n }\n\n public close(): void {\n this.state = 'closed';\n this.opening.resolve(false);\n }\n\n public admit<Result>(work: () => Promise<Result>): Promise<Result | undefined> {\n if (this.state === 'closed') return Promise.resolve(undefined);\n const admitted =\n this.state === 'open' ? work() : this.opening.promise.then((opened) => (opened ? work() : undefined));\n this.track(admitted);\n return admitted;\n }\n\n public track(work: Promise<unknown>): void {\n this.running.add(work);\n const forget = (): void => {\n this.running.delete(work);\n };\n void work.then(forget, forget);\n }\n\n public async drain(timeoutMs: number): Promise<void> {\n await settleWithin(this.settleAll(), timeoutMs);\n // nothing cancels these before the process exits\n if (this.running.size > 0) {\n this.logger.warn(`${this.label}: ${String(this.running.size)} still running when the drain window closed`);\n }\n }\n\n // settling work can track more work\n private async settleAll(): Promise<void> {\n while (this.running.size > 0) await Promise.allSettled(this.running);\n }\n}\n","import type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nconst HTTP_OK = 200;\nconst HTTP_SERVICE_UNAVAILABLE = 503;\nconst DEFAULT_HEALTH_CHECK_PATH = '/health';\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\n\nexport function healthCheckConfig(option?: HealthCheckOption): HealthCheckConfig | undefined {\n if (option === false) return undefined;\n return typeof option === 'object' ? option : {};\n}\n\nexport class HealthResponder {\n public readonly path: string;\n\n constructor(path?: string) {\n this.path = path ?? DEFAULT_HEALTH_CHECK_PATH;\n }\n\n public static fromOption(option?: HealthCheckOption): HealthResponder | undefined {\n const config = healthCheckConfig(option);\n return config && new HealthResponder(config.path);\n }\n\n public tryRespond(req: IncomingMessage, res: ServerResponse): boolean {\n if (!this.matches(req.method, req.url)) return false;\n\n res.writeHead(HTTP_OK, JSON_HEADERS);\n res.end(this.body());\n return true;\n }\n\n public answer(request: Request, ready: boolean): Response | undefined {\n if (!this.matches(request.method, new URL(request.url).pathname)) return undefined;\n if (!ready) return new Response(null, { status: HTTP_SERVICE_UNAVAILABLE });\n return new Response(this.body(), { status: HTTP_OK, headers: JSON_HEADERS });\n }\n\n private matches(method: string | undefined, path: string | undefined): boolean {\n return method === 'GET' && path === this.path;\n }\n\n private body(): string {\n return JSON.stringify({ status: 'ok', timestamp: Date.now() });\n }\n}\n","import { createServer } from 'node:http';\n\nimport { paint, SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\nimport { isPortInUse } from '@seedcord/utils/internal';\n\nimport { ShutdownPhase } from '#src/lifecycle/phases';\n\nimport { healthCheckConfig, HealthResponder } from './HealthResponder';\n\nimport type { CoordinatedShutdown } from './Lifecycle/CoordinatedShutdown';\nimport type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\n\nconst HTTP_NOT_FOUND = 404;\n\nconst DEFAULT_HEALTH_CHECK_PORT = 6967;\n\nexport class HealthCheck {\n public readonly logger = new Logger('HealthCheck', { channel: 'health' });\n\n private readonly _port: number;\n private readonly _host: string | undefined;\n\n private readonly responder: HealthResponder;\n private server?: Server;\n\n constructor(shutdown: CoordinatedShutdown, options?: HealthCheckConfig) {\n this._port = options?.port ?? DEFAULT_HEALTH_CHECK_PORT;\n this._host = options?.host;\n this.responder = new HealthResponder(options?.path);\n\n shutdown.addTask(ShutdownPhase.Drain, 'stop-healthcheck-server', () => this.stop());\n }\n\n public get port(): number {\n return this._port;\n }\n\n public get host(): string | undefined {\n return this._host;\n }\n\n public get path(): string {\n return this.responder.path;\n }\n\n public static fromOption(shutdown: CoordinatedShutdown, option?: HealthCheckOption): HealthCheck | undefined {\n const config = healthCheckConfig(option);\n return config && new HealthCheck(shutdown, config);\n }\n\n public async init(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n if (this.responder.tryRespond(req, res)) return;\n\n res.writeHead(HTTP_NOT_FOUND, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'not found' }));\n });\n this.server = server;\n\n const onListenError = (error: Error): void => {\n if (isPortInUse(error)) {\n reject(\n new SeedcordError(SeedcordErrorCode.PortInUse, [this._port, 'healthCheck.port'], {\n cause: error\n })\n );\n return;\n }\n reject(error);\n };\n server.on('error', onListenError);\n\n server.once('listening', () => {\n // a late error would reject an already-settled promise, and node throws process-wide without a listener here\n server.removeListener('error', onListenError);\n server.on('error', (err) => this.logger.error('Health check server error', err));\n\n // binds all interfaces, so log an address a browser can open\n const address = this._host ?? 'localhost';\n this.logger.info(\n `${paint.mint.bold(paint.check)} Health check server listening on ${paint.sky.bold(`http://${address}:${this._port}${this.path}`)}`\n );\n resolve();\n });\n\n if (this._host) {\n this.logger.debug(`Binding health check server to ${this._host}`);\n server.listen(this._port, this._host);\n } else {\n this.logger.debug('Binding health check server to all interfaces');\n server.listen(this._port);\n }\n });\n }\n\n public stop(): Promise<void> {\n const server = this.server;\n // close() on a non-listening server invokes its callback with ERR_SERVER_NOT_RUNNING and\n // never emits 'close', so without this guard the promise would hang the shutdown phase.\n if (!server?.listening) return Promise.resolve();\n\n return new Promise((resolve, reject) => {\n // settle only in the callback, a 'close'-event resolve would swallow the callback's error\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n this.logger.info(paint.coral.bold('Health check server stopped'));\n resolve();\n });\n });\n }\n}\n","import { botConfigDir, setBuiltFilesHook } from '@seedcord/utils/internal';\nimport { Envapter } from 'envapt';\n\nexport function setEnvaptBaseDir(): void {\n setBuiltFilesHook(() => {\n const configDir = botConfigDir();\n if (configDir !== undefined && Envapter.source.supportsFiles) Envapter.baseDir = configDir;\n });\n}\n","import { assertDeclaredRuntime } from '#node/assertRuntimeVersion';\n\n// the edge bundle never reaches this entry\nassertDeclaredRuntime();\n\nexport * from '#node/Lifecycle';\nexport * from '#node/HealthCheck';\nexport { HealthResponder } from '#node/HealthResponder';\nexport { settleWithin } from '#src/lifecycle/withTimeout';\nexport { shutdownOf } from '#node/ServerHost';\nexport { setEnvaptBaseDir } from '#node/envaptBaseDir';\n"],"mappings":"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"}
|
package/dist/node.index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as CoordinatedStartup, t as CoordinatedShutdown } from "./CoordinatedShutdown-
|
|
2
|
-
import { t as ServerHost } from "./ServerHost-
|
|
3
|
-
export { CoordinatedShutdown, CoordinatedStartup, ServerHost };
|
|
1
|
+
import { i as StartupTasks, n as ShutdownTasks, r as CoordinatedStartup, t as CoordinatedShutdown } from "./CoordinatedShutdown-BVtNNSsF.mjs";
|
|
2
|
+
import { t as ServerHost } from "./ServerHost-whpI0h4d.mjs";
|
|
3
|
+
export { CoordinatedShutdown, CoordinatedStartup, ServerHost, type ShutdownTasks, type StartupTasks };
|
package/dist/node.index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as settleWithin } from "./PluginLifecycle-
|
|
3
|
-
import { n as CoordinatedLifecycle, t as CoordinatedStartup } from "./CoordinatedStartup-
|
|
4
|
-
import { t as ServerHost } from "./ServerHost-
|
|
1
|
+
import { v as ShutdownPhase } from "./Bus-W1K-uMeO.mjs";
|
|
2
|
+
import { n as settleWithin } from "./PluginLifecycle-DdwRzcDj.mjs";
|
|
3
|
+
import { n as CoordinatedLifecycle, t as CoordinatedStartup } from "./CoordinatedStartup-DA3gW8op.mjs";
|
|
4
|
+
import { t as ServerHost } from "./ServerHost-DHg-Y2_q.mjs";
|
|
5
5
|
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
6
6
|
import { SeedcordRangeError } from "@seedcord/errors/internal";
|
|
7
7
|
|
|
@@ -10,15 +10,16 @@ const PHASE_ORDER = [
|
|
|
10
10
|
1,
|
|
11
11
|
2,
|
|
12
12
|
3,
|
|
13
|
-
4
|
|
13
|
+
4,
|
|
14
|
+
5
|
|
14
15
|
];
|
|
16
|
+
const SHUTDOWN_SIGNALS = ["SIGTERM", "SIGINT"];
|
|
15
17
|
const LOG_FLUSH_DELAY_MS = 3e3;
|
|
16
18
|
const DEFAULT_SHUTDOWN_DEADLINE_MS = 25e3;
|
|
17
19
|
var CoordinatedShutdown = class extends CoordinatedLifecycle {
|
|
18
20
|
#running;
|
|
19
21
|
#exitCode = 0;
|
|
20
|
-
#
|
|
21
|
-
#onSigInt = null;
|
|
22
|
+
#signalHandlers = /* @__PURE__ */ new Map();
|
|
22
23
|
#startupGate;
|
|
23
24
|
#deadlineMs = DEFAULT_SHUTDOWN_DEADLINE_MS;
|
|
24
25
|
#phasesExpireAt = Infinity;
|
|
@@ -59,28 +60,21 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
|
|
|
59
60
|
}
|
|
60
61
|
/** @internal */
|
|
61
62
|
registerSignalHandlers() {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
process.on("SIGTERM", this.#onSigTerm);
|
|
71
|
-
process.on("SIGINT", this.#onSigInt);
|
|
63
|
+
for (const signal of SHUTDOWN_SIGNALS) {
|
|
64
|
+
const handler = () => {
|
|
65
|
+
this.logger.info(`Received ${paint.amber.bold(signal)} signal`);
|
|
66
|
+
this.run(0);
|
|
67
|
+
};
|
|
68
|
+
this.#signalHandlers.set(signal, handler);
|
|
69
|
+
process.on(signal, handler);
|
|
70
|
+
}
|
|
72
71
|
}
|
|
73
72
|
/** @internal */
|
|
74
73
|
removeSignalHandlers() {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.#onSigTerm = null;
|
|
78
|
-
}
|
|
79
|
-
if (!this.#onSigInt) return;
|
|
80
|
-
process.off("SIGINT", this.#onSigInt);
|
|
81
|
-
this.#onSigInt = null;
|
|
74
|
+
for (const [signal, handler] of this.#signalHandlers) process.off(signal, handler);
|
|
75
|
+
this.#signalHandlers.clear();
|
|
82
76
|
}
|
|
83
|
-
/** @internal
|
|
77
|
+
/** @internal */
|
|
84
78
|
gateOnStartup(settled) {
|
|
85
79
|
this.#startupGate = settled;
|
|
86
80
|
}
|
|
@@ -109,10 +103,10 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
|
|
|
109
103
|
if (this.#startupGate) await settleWithin(this.#startupGate, this.#deadlineMs);
|
|
110
104
|
const failures = [];
|
|
111
105
|
await settleWithin(this.#runPhases(failures), Math.max(this.#phasesExpireAt - performance.now(), 0));
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
106
|
+
const unfinishedPhase = this.#runningPhase;
|
|
107
|
+
if (unfinishedPhase !== void 0) this.logger.error(`Shutdown deadline of ${paint.sky.bold(this.#deadlineMs)}ms elapsed at phase ${paint.iris.bold(this.phaseEnum[unfinishedPhase])}`);
|
|
114
108
|
if (failures.length > 0) this.logger.error(`${paint.coral.bold("Coordinated shutdown failed")}`, ...failures);
|
|
115
|
-
else if (
|
|
109
|
+
else if (unfinishedPhase === void 0) this.logger.info(`${paint.mint.bold("Coordinated shutdown completed")} successfully`);
|
|
116
110
|
} finally {
|
|
117
111
|
if (exitProcess) {
|
|
118
112
|
this.logger.debug(`${paint.coral.bold("Exiting")} process with code ${paint.sky.bold(this.#exitCode)}`);
|
package/dist/node.index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.index.mjs","names":[],"sources":["../src/node/Lifecycle/CoordinatedShutdown.ts"],"sourcesContent":["import { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordRangeError } from '@seedcord/errors/internal';\n\nimport { CoordinatedLifecycle } from '#src/lifecycle/CoordinatedLifecycle';\nimport { ShutdownPhase } from '#src/lifecycle/phases';\nimport { settleWithin } from '#src/lifecycle/withTimeout';\n\nimport type { LifecycleTask } from '#src/lifecycle/LifecycleTypes';\n\nconst PHASE_ORDER: ShutdownPhase[] = [\n ShutdownPhase.Unbind,\n ShutdownPhase.Drain,\n ShutdownPhase.Disconnect,\n ShutdownPhase.Logout\n];\n\n// gives the logger's file sink time to flush before process.exit\nconst LOG_FLUSH_DELAY_MS = 3000;\n\n// 25s plus LOG_FLUSH_DELAY_MS stays under kubernetes' 30s SIGKILL window\nconst DEFAULT_SHUTDOWN_DEADLINE_MS = 25_000;\n\nexport class CoordinatedShutdown extends CoordinatedLifecycle<ShutdownPhase> {\n #running?: Promise<void>;\n #exitCode = 0;\n #
|
|
1
|
+
{"version":3,"file":"node.index.mjs","names":[],"sources":["../src/node/Lifecycle/CoordinatedShutdown.ts"],"sourcesContent":["import { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordRangeError } from '@seedcord/errors/internal';\n\nimport { CoordinatedLifecycle } from '#src/lifecycle/CoordinatedLifecycle';\nimport { ShutdownPhase } from '#src/lifecycle/phases';\nimport { settleWithin } from '#src/lifecycle/withTimeout';\n\nimport type { LifecycleTask } from '#src/lifecycle/LifecycleTypes';\nimport type { TaskShutdownPhase } from '#src/lifecycle/phases';\nimport type { Tail } from '@seedcord/types';\n\n/** Adds work to a node bot's shutdown, in any phase before `ShutdownPhase.Release`. */\nexport interface ShutdownTasks {\n addTask(phase: TaskShutdownPhase, ...rest: Tail<Parameters<CoordinatedShutdown['addTask']>>): void;\n}\n\nconst PHASE_ORDER: ShutdownPhase[] = [\n ShutdownPhase.Unbind,\n ShutdownPhase.Drain,\n ShutdownPhase.Disconnect,\n ShutdownPhase.Logout,\n ShutdownPhase.Release\n];\n\nconst SHUTDOWN_SIGNALS: NodeJS.Signals[] = ['SIGTERM', 'SIGINT'];\n\n// gives the logger's file sink time to flush before process.exit\nconst LOG_FLUSH_DELAY_MS = 3000;\n\n// 25s plus LOG_FLUSH_DELAY_MS stays under kubernetes' 30s SIGKILL window\nconst DEFAULT_SHUTDOWN_DEADLINE_MS = 25_000;\n\nexport class CoordinatedShutdown extends CoordinatedLifecycle<ShutdownPhase> {\n #running?: Promise<void>;\n #exitCode = 0;\n readonly #signalHandlers = new Map<NodeJS.Signals, () => void>();\n #startupGate?: Promise<void>;\n #deadlineMs = DEFAULT_SHUTDOWN_DEADLINE_MS;\n #phasesExpireAt = Infinity;\n #runningPhase: ShutdownPhase | undefined;\n\n public constructor(deadlineMs?: number) {\n super('Shutdown', PHASE_ORDER, ShutdownPhase);\n\n if (deadlineMs !== undefined) this.setDeadline(deadlineMs);\n }\n\n /** @internal */\n public setDeadline(deadlineMs: number): void {\n if (!Number.isFinite(deadlineMs) || deadlineMs <= 0) {\n throw new SeedcordRangeError(SeedcordErrorCode.LifecycleInvalidTimeout, [\n 'lifecycle.shutdownDeadline',\n deadlineMs\n ]);\n }\n this.#deadlineMs = deadlineMs;\n }\n\n async #runPhases(failures: unknown[]): Promise<void> {\n for (const phase of PHASE_ORDER) {\n // set first so the deadline log shows this phase\n this.#runningPhase = phase;\n // runPhases keeps going after settleWithin stops waiting on it\n if (performance.now() >= this.#phasesExpireAt) return;\n try {\n await this.runPhase(phase);\n } catch (error) {\n failures.push(error);\n }\n }\n this.#runningPhase = undefined;\n }\n\n protected canAddTask(): boolean {\n return true;\n }\n\n protected canRemoveTask(): boolean {\n return true;\n }\n\n protected getTaskType(): string {\n return 'shutdown';\n }\n\n protected async executeTasksInPhase(\n phase: ShutdownPhase,\n tasks: LifecycleTask[]\n ): Promise<PromiseSettledResult<void>[]> {\n const promises = tasks.map((task) => this.runTaskWithTimeout(phase, task));\n return Promise.allSettled(promises);\n }\n\n /** @internal */\n public registerSignalHandlers(): void {\n for (const signal of SHUTDOWN_SIGNALS) {\n const handler = (): void => {\n this.logger.info(`Received ${paint.amber.bold(signal)} signal`);\n void this.run(0);\n };\n this.#signalHandlers.set(signal, handler);\n process.on(signal, handler);\n }\n }\n\n /** @internal */\n public removeSignalHandlers(): void {\n for (const [signal, handler] of this.#signalHandlers) process.off(signal, handler);\n this.#signalHandlers.clear();\n }\n\n // run() awaits this so boot finishes registering its dispose tasks first\n /** @internal */\n public gateOnStartup(settled: Promise<void>): void {\n this.#startupGate = settled;\n }\n\n public override addTask(\n phase: ShutdownPhase,\n taskName: string,\n task: LifecycleTask['task'],\n timeoutMs = 5000\n ): void {\n super.addTask(phase, taskName, task, timeoutMs);\n }\n\n /** @internal */\n public override removeTask(phase: ShutdownPhase, taskName: string): boolean {\n return super.removeTask(phase, taskName);\n }\n\n // a later call waits on the first run and never repeats a task\n /** @internal */\n public run(exitCode = 0, exitProcess = true): Promise<void> {\n this.removeSignalHandlers();\n\n if (this.#running) {\n // a crash mid-shutdown must still leave a failing code for whatever supervises the process\n if (exitCode > this.#exitCode) this.#exitCode = exitCode;\n return this.#running;\n }\n\n this.#exitCode = exitCode;\n this.#running = this.#shutDown(exitProcess);\n return this.#running;\n }\n\n async #shutDown(exitProcess: boolean): Promise<void> {\n this.logger.info(\n `${paint.amber.bold('Starting')} coordinated shutdown with exit code ${paint.sky.bold(this.#exitCode)}`\n );\n\n try {\n this.#phasesExpireAt = performance.now() + this.#deadlineMs;\n // a startup that outlasts the deadline leaves its own dispose tasks unregistered\n if (this.#startupGate) await settleWithin(this.#startupGate, this.#deadlineMs);\n\n const failures: unknown[] = [];\n await settleWithin(this.#runPhases(failures), Math.max(this.#phasesExpireAt - performance.now(), 0));\n\n const unfinishedPhase = this.#runningPhase;\n if (unfinishedPhase !== undefined) {\n this.logger.error(\n `Shutdown deadline of ${paint.sky.bold(this.#deadlineMs)}ms elapsed at phase ${paint.iris.bold(this.phaseEnum[unfinishedPhase])}`\n );\n }\n\n if (failures.length > 0) {\n this.logger.error(`${paint.coral.bold('Coordinated shutdown failed')}`, ...failures);\n } else if (unfinishedPhase === undefined) {\n this.logger.info(`${paint.mint.bold('Coordinated shutdown completed')} successfully`);\n }\n } finally {\n if (exitProcess) {\n this.logger.debug(`${paint.coral.bold('Exiting')} process with code ${paint.sky.bold(this.#exitCode)}`);\n setTimeout(() => {\n process.exit(this.#exitCode);\n }, LOG_FLUSH_DELAY_MS);\n } else {\n this.logger.debug(`${paint.amber.bold('Skipping')} process exit (dev mode)`);\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -3,8 +3,8 @@ import { Logger } from "@seedcord/logger";
|
|
|
3
3
|
import { TypedEventEmitter } from "@seedcord/event-emitter";
|
|
4
4
|
import { REST } from "@discordjs/rest";
|
|
5
5
|
import { UUID } from "@seedcord/types/internal";
|
|
6
|
-
import { Config, DispatchBag, DispatchState, EventFrequency, IRateLimiter, RenderContext, ReplyResponse, TypedExclude } from "@seedcord/types";
|
|
7
|
-
import { Constructor } from "type-fest";
|
|
6
|
+
import { Config, DispatchBag, DispatchState, EventFrequency, IRateLimiter, KvDriver, RenderContext, ReplyResponse, TypedExclude } from "@seedcord/types";
|
|
7
|
+
import { Constructor, Jsonify, Simplify } from "type-fest";
|
|
8
8
|
//#region src/stops/Notice.d.ts
|
|
9
9
|
/**
|
|
10
10
|
* Base class for a user-facing refusal or a reported fault.
|
|
@@ -104,15 +104,15 @@ declare const WebhookUrlMetadataKey: unique symbol;
|
|
|
104
104
|
declare const InteractionRouteKeys: Record<InteractionKind, symbol>;
|
|
105
105
|
//#endregion
|
|
106
106
|
//#region src/dispatch/DispatchContext.d.ts
|
|
107
|
-
/**
|
|
108
|
-
* A typed bag allocated once per interaction dispatch and handed to the handler. The middleware and i18n
|
|
109
|
-
* work reads and writes it through {@link DispatchState}. Today the bag is empty, so it only carries the
|
|
110
|
-
* dispatched `routeId`.
|
|
111
|
-
*/
|
|
107
|
+
/** State shared by middleware and handlers during one dispatch. */
|
|
112
108
|
declare class DispatchContext implements DispatchBag {
|
|
113
|
-
readonly routeId: string;
|
|
114
109
|
private readonly state;
|
|
115
110
|
readonly id: `${string}-${string}-${string}-${string}-${string}`;
|
|
111
|
+
/**
|
|
112
|
+
* The dispatch identifier, such as `slash:search/docs`, `button:approve`, or `event:messageCreate`.
|
|
113
|
+
* Component identifiers contain the custom-ID prefix without encoded params.
|
|
114
|
+
*/
|
|
115
|
+
readonly routeId: string;
|
|
116
116
|
constructor(routeId: string);
|
|
117
117
|
set<Key extends keyof DispatchState>(key: Key, value: DispatchState[Key]): void;
|
|
118
118
|
get<Key extends keyof DispatchState>(key: Key): DispatchState[Key] | undefined;
|
|
@@ -124,6 +124,97 @@ declare class DispatchContext implements DispatchBag {
|
|
|
124
124
|
require<Key extends keyof DispatchState>(key: Key): Exclude<DispatchState[Key], undefined>;
|
|
125
125
|
}
|
|
126
126
|
//#endregion
|
|
127
|
+
//#region src/kv/KeyedQueue.d.ts
|
|
128
|
+
declare class KeyedQueue {
|
|
129
|
+
private readonly tails;
|
|
130
|
+
run<TResult>(key: string, task: () => Promise<TResult>): Promise<TResult>;
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/kv/KvCollection.d.ts
|
|
134
|
+
type Stored<TRecord> = Simplify<Jsonify<TRecord>>;
|
|
135
|
+
/**
|
|
136
|
+
* The records of one type, stored under one name in `config.kv`. Get one from
|
|
137
|
+
* `kv.collection<T>('name')`.
|
|
138
|
+
*
|
|
139
|
+
* Every record goes through JSON on its way in and out. A `Date` you store reads back from `get()`
|
|
140
|
+
* as an ISO string.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* interface Profile { bio: string; joinedAt: Date }
|
|
145
|
+
* const profiles = this.core.kv.collection<Profile>('profile');
|
|
146
|
+
*
|
|
147
|
+
* await profiles.set(user.id, { bio: 'hi', joinedAt: new Date() });
|
|
148
|
+
* const profile = await profiles.get(user.id); // { bio: string; joinedAt: string } | undefined
|
|
149
|
+
* await profiles.delete(user.id);
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
declare class KvCollection<TRecord> {
|
|
153
|
+
#private;
|
|
154
|
+
constructor(driver: KvDriver, prefix: string, updates: KeyedQueue);
|
|
155
|
+
/** Reads the record stored under `id`. Resolves to `undefined` when nothing is there or the record expired. */
|
|
156
|
+
get(id: string): Promise<Stored<TRecord> | undefined>;
|
|
157
|
+
/**
|
|
158
|
+
* Stores `value` under `id` and replaces whatever was there.
|
|
159
|
+
*
|
|
160
|
+
* @param ttlMs - How long the record lasts, in milliseconds. Leave it out to keep the record until you delete it.
|
|
161
|
+
*/
|
|
162
|
+
set(id: string, value: TRecord, ttlMs?: number): Promise<void>;
|
|
163
|
+
/** Removes the record under `id`. */
|
|
164
|
+
delete(id: string): Promise<void>;
|
|
165
|
+
/**
|
|
166
|
+
* Stores what `next` returns for the record under `id`, then resolves to that value. `next` gets
|
|
167
|
+
* the current record, or `undefined` when there is none.
|
|
168
|
+
*
|
|
169
|
+
* Use it over a `get()` then a `set()` when two calls can change one record at the same time.
|
|
170
|
+
* `set()`, `delete()`, and `update()` calls on one id run one after the other in this process.
|
|
171
|
+
* None of them loses another's change. Two processes sharing one backend can still both read
|
|
172
|
+
* before either writes.
|
|
173
|
+
*
|
|
174
|
+
* @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.
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* // counts a run of the command and resolves to the new count
|
|
178
|
+
* const runs = await counts.update(user.id, (count) => (count ?? 0) + 1);
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
update(id: string, next: (current: Stored<TRecord> | undefined) => TRecord, ttlMs?: number | ((value: TRecord) => number)): Promise<TRecord>;
|
|
182
|
+
/**
|
|
183
|
+
* Yields `[id, record]` for every record in this collection that hasn't expired. Use it to read
|
|
184
|
+
* back what the bot saved before a restart.
|
|
185
|
+
*
|
|
186
|
+
* @throws A **SeedcordError** when the backend can't list its keys, like `@keyv/memcache`.
|
|
187
|
+
* @example
|
|
188
|
+
* ```ts
|
|
189
|
+
* // schedules every saved reminder again after a restart
|
|
190
|
+
* for await (const [id, reminder] of reminders.entries()) schedule(id, reminder);
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
entries(): AsyncIterable<[id: string, record: Stored<TRecord>]>;
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/kv/Kv.d.ts
|
|
197
|
+
type ConfiguredDriver = NonNullable<Config['kv']>;
|
|
198
|
+
type JsonSafe<TValue> = TValue extends string | number | boolean | null | undefined ? TValue : TValue extends {
|
|
199
|
+
toJSON(): unknown;
|
|
200
|
+
} ? TValue : TValue extends ((...args: never[]) => unknown) ? never : TValue extends ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> ? never : TValue extends readonly (infer Item)[] ? readonly JsonSafe<Item>[] : TValue extends object ? { [K in keyof TValue]: JsonSafe<TValue[K]>; } : never;
|
|
201
|
+
type CollectionName<TRecord> = unknown extends TRecord ? 'Pass the record type, like kv.collection<Profile>(name).' : [TRecord] extends [JsonSafe<TRecord>] ? string : 'This record type holds a value JSON cannot keep, like a function, Map, Set, or bigint.';
|
|
202
|
+
/**
|
|
203
|
+
* The bot's key-value store, or one prefixed slice of it. `core.kv` holds your handlers' records. A
|
|
204
|
+
* plugin's `this.kv` holds the plugin's.
|
|
205
|
+
*/
|
|
206
|
+
declare class Kv {
|
|
207
|
+
#private;
|
|
208
|
+
/** What you passed as `config.kv`. Register its type in `KvTypes` to call the library's own methods on it. */
|
|
209
|
+
readonly driver: ConfiguredDriver;
|
|
210
|
+
constructor(driver: ConfiguredDriver, ...path: string[]);
|
|
211
|
+
/**
|
|
212
|
+
* Records of one type under `name`. Pass the type argument. A record type holding a function,
|
|
213
|
+
* `Map`, `Set`, or `bigint` fails to compile here.
|
|
214
|
+
*/
|
|
215
|
+
collection<TRecord>(name: CollectionName<TRecord>): KvCollection<TRecord>;
|
|
216
|
+
}
|
|
217
|
+
//#endregion
|
|
127
218
|
//#region src/subscribers/publishDefault.d.ts
|
|
128
219
|
/** @internal PublishableKey excludes this key so publish() never emits it externally. */
|
|
129
220
|
declare const PublishDefault: unique symbol;
|
|
@@ -392,7 +483,7 @@ declare class Bus extends TypedEventEmitter<SubscriptionTuples> {
|
|
|
392
483
|
/** @internal */
|
|
393
484
|
readonly [loggerSlot]: Logger;
|
|
394
485
|
private readonly subscribersMap;
|
|
395
|
-
private readonly
|
|
486
|
+
private readonly onceRuns;
|
|
396
487
|
private readonly webhookProbes;
|
|
397
488
|
constructor(core: CoreBase);
|
|
398
489
|
/** @internal keep this inside start(). a bad webhook url thrown outside it never resets the singleton guard */
|
|
@@ -460,6 +551,8 @@ interface CoreBase {
|
|
|
460
551
|
readonly rateLimiter: IRateLimiter;
|
|
461
552
|
/** Publish and subscribe to framework and application events. */
|
|
462
553
|
readonly bus: Bus;
|
|
554
|
+
/** Your handlers' records in `config.kv`, under the `app:` prefix. */
|
|
555
|
+
readonly kv: Kv;
|
|
463
556
|
}
|
|
464
557
|
//#endregion
|
|
465
558
|
//#region src/lifecycle/phases.d.ts
|
|
@@ -485,8 +578,14 @@ declare enum ShutdownPhase {
|
|
|
485
578
|
/** External resources close. Plugin dispose runs here by default. */
|
|
486
579
|
Disconnect = 3,
|
|
487
580
|
/** The client disconnects last so in-flight handlers can still reference it during drain. */
|
|
488
|
-
Logout = 4
|
|
581
|
+
Logout = 4,
|
|
582
|
+
/**
|
|
583
|
+
* Resources seedcord holds for the bot, like the `config.kv` driver, close after every plugin
|
|
584
|
+
* has stopped. Only seedcord runs work here.
|
|
585
|
+
*/
|
|
586
|
+
Release = 5
|
|
489
587
|
}
|
|
588
|
+
type TaskShutdownPhase = Exclude<ShutdownPhase, ShutdownPhase.Release>;
|
|
490
589
|
//#endregion
|
|
491
|
-
export {
|
|
492
|
-
//# sourceMappingURL=phases-
|
|
590
|
+
export { RegisterSubscriber as A, EventMiddlewareMetadataKey as B, reportedWrite as C, sendTarget as D, checkAckLegality as E, Kv as F, InteractionRouteKeys as G, InteractionKind as H, KvCollection as I, WebhookUrlMetadataKey as J, MiddlewareKind as K, DispatchContext as L, UnregisterSubscriber as M, VerifyWebhooks as N, AckTrace as O, PublishDefault as P, CommandMetadataKey as R, ReplyTelemetry as S, ReplyMethod as T, InteractionMetadataKey as U, GatedMetadataKey as V, InteractionMiddlewareMetadataKey as W, Notice as Y, ResponseOutcome as _, Bus as a, SubscriptionKey as b, busLoggerOf as c, AttemptedWrite as d, DefaultSubscriptions as f, ResponseFailed as g, ResponseAttempt as h, CoreBase as i, RegisteredCount as j, RegisterDefaults as k, registrationFor as l, FaultSource as m, StartupPhase as n, StoredSubscriberCtor as o, DispatchOutcome as p, SubscribeMetadataKey as q, TaskShutdownPhase as r, SubscriberRegistration as s, ShutdownPhase as t, Subscriber as u, ResponseSent as v, AckState as w, Subscriptions as x, SubscriptionData as y, EventMetadataKey as z };
|
|
591
|
+
//# sourceMappingURL=phases-Dojnr-ng.d.mts.map
|
package/dist/plugin.index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as PluginTypes, t as Generated } from "./Generated-ByC53_A2.mjs";
|
|
2
|
+
import { n as PluginHost, p as PluginLifecycleSpec, s as Plugin, u as PluginOptions } from "./PluginHost-DWBgBS6Q.mjs";
|
|
2
3
|
import { HmrAware, HmrUpdateEvent } from "@seedcord/types";
|
|
3
|
-
export { type HmrAware, type HmrUpdateEvent, Plugin, PluginHost, type PluginLifecycleSpec, type PluginOptions };
|
|
4
|
+
export { type Generated, type HmrAware, type HmrUpdateEvent, Plugin, PluginHost, type PluginLifecycleSpec, type PluginOptions, type PluginTypes };
|
package/dist/plugin.index.mjs
CHANGED