@seedcord/core 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"internal.index.mjs","names":["logger","logger"],"sources":["../src/decorators/brands.ts","../src/gates/runGates.ts","../src/gates/slowGate.ts","../src/miscellaneous/emojiConfig.ts","../src/hmr/wrapHot.ts","../src/hmr/HmrManager.ts","../src/stops/asError.ts","../src/reply/ackLegality.ts","../src/reply/AckTrace.ts","../src/reply/responseReport.ts","../src/reply/translateSerialization.ts","../src/reply/serializeReply.ts","../src/reply/BaseReplySender.ts","../src/dispatch/dispatchReport.ts"],"sourcesContent":["// A handler base declares these so a route decorator can read what the handler is for. Nothing\n// assigns them at runtime.\n\nexport const SlashRouteBrand: unique symbol = Symbol('seedcord:brand:slashRoute');\nexport const AutocompleteRouteBrand: unique symbol = Symbol('seedcord:brand:autocompleteRoute');\nexport const ContextMenuKindBrand: unique symbol = Symbol('seedcord:brand:contextMenuKind');\nexport const ComponentKindBrand: unique symbol = Symbol('seedcord:brand:componentKind');\nexport const ComponentDefsBrand: unique symbol = Symbol('seedcord:brand:componentDefs');\n","import 'reflect-metadata';\n\nimport { GatedMetadataKey, InteractionRouteKeys } from '#src/metadataKeys';\n\nimport { discardCommits, runCheck, runCommits } from './effects';\n\nimport type { Gate, GateContextBase } from './Gate';\n\n// a combinator (`and`/`or`) reports once under its joined name.\nexport type GateObserver = (gateName: string, elapsedMs: number) => void;\n\n// the stable id a route decorator stored, e.g. slash:daily or button:confirm. null for a plain event handler.\nexport function routeIdOf(handlerCtor: object): string | null {\n for (const [kind, key] of Object.entries(InteractionRouteKeys)) {\n // justified: getMetadata returns any, this key only ever stores the route/prefix string array\n const routes = Reflect.getMetadata(key, handlerCtor) as string[] | undefined;\n if (routes?.length) return `${kind}:${routes.join(',')}`;\n }\n return null;\n}\n\nasync function timedCheck(gate: Gate<GateContextBase>, ctx: GateContextBase, observe: GateObserver): Promise<void> {\n const start = performance.now();\n // finally so a refusing gate still reports\n try {\n await runCheck(gate, ctx);\n } finally {\n observe(gate.name, performance.now() - start);\n }\n}\n\n// the first refusal throws out to the dispatcher boundary. an effect gate's commit runs once the\n// whole set passes, and observe never times the commit phase.\nexport async function runGates(\n gates: readonly Gate<GateContextBase>[],\n ctx: GateContextBase,\n observe?: GateObserver\n): Promise<void> {\n try {\n for (const gate of gates) {\n await (observe ? timedCheck(gate, ctx, observe) : runCheck(gate, ctx));\n }\n await runCommits(ctx);\n } finally {\n discardCommits(ctx);\n }\n}\n\n// dispatchers call this before execute, inside the boundary, so a refusal renders or drops. an explicit\n// routeId is for manifest-driven handlers, which carry no route metadata to derive one from.\nexport async function runHandlerGates(\n handlerCtor: object,\n ctx: GateContextBase,\n routeId?: string,\n observe?: GateObserver\n): Promise<void> {\n // justified: getMetadata returns any, and this key only ever stores the @Gated gate array\n const gates = Reflect.getMetadata(GatedMetadataKey, handlerCtor) as readonly Gate<GateContextBase>[] | undefined;\n if (!gates) return;\n await runGates(gates, { ...ctx, routeId: routeId ?? routeIdOf(handlerCtor) }, observe);\n}\n","import { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { Envapter } from 'envapt';\n\nimport type { GateObserver } from './runGates';\n\n// combined check time slower than this cuts into the 3s interaction-ack budget\nconst SLOW_GATE_MS = 750;\n\n// lazy because the logger reads the environment, which binds after this module loads\nlet gateLogger: Logger | undefined;\nfunction logger(): Logger {\n gateLogger ??= new Logger('Gates', { channel: 'gates' });\n return gateLogger;\n}\n\n/**\n * collects each gate check's elapsed time for one dispatch. `report` warns once when the checks sum\n * past the threshold, naming the total, the route, and each gate's share.\n */\nexport interface SlowGateMonitor {\n readonly observe: GateObserver;\n report(routeId: string | null): void;\n}\n\n// undefined in production, so timedCheck skips the clock there. read per call, the environment binds late\nexport function slowGateMonitor(): SlowGateMonitor | undefined {\n if (Envapter.isProduction) return undefined;\n const readings: { name: string; ms: number }[] = [];\n return {\n observe: (name, ms) => readings.push({ name, ms }),\n report(routeId) {\n const total = readings.reduce((sum, reading) => sum + reading.ms, 0);\n if (total < SLOW_GATE_MS) return;\n const shares = readings\n .toSorted((a, b) => b.ms - a.ms)\n .map((reading) => `${paint.sky.bold(reading.name)} ${paint.mute(`${Math.round(reading.ms)}ms`)}`);\n const route = routeId === null ? '' : ` for ${paint.sky.bold(routeId)}`;\n const headline = `gates${route} took ${paint.amber(`${Math.round(total)}ms`)} of the 3s ack budget`;\n logger().utils.block(headline, shares, 'warn', (text) => text);\n }\n };\n}\n","// narrows a configured emoji value to the [name, guildId] form\nexport function isEmojiTuple(value: unknown): value is readonly [string, string] {\n return Array.isArray(value) && value.length === 2 && typeof value[0] === 'string' && typeof value[1] === 'string';\n}\n","import type { DevChannel } from '@seedcord/types/internal';\n\n// the minimal raw-hot shape, satisfied by both vite's `import.meta.hot` and the CLI's `NormalizedHotChannel`.\ninterface RawHot {\n send(event: string, data?: unknown): void;\n on(event: string, cb: (data: unknown) => void): void;\n}\n\n/** @internal */\nexport function wrapHot<TSend, TRecv>(hot: RawHot): DevChannel<TSend, TRecv> {\n return {\n send: (event, data) => hot.send(event, data),\n // justified: RawHot types every payload as unknown, and the wire contract guarantees it matches TRecv[Key]\n on: (event, cb) => hot.on(event, cb as (data: unknown) => void)\n };\n}\n","// scoped here so import.meta.hot stays untyped in the rest of the package\n/// <reference types=\"vite/client\" />\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { Envapter } from 'envapt';\n\nimport { setDevChannel } from './devChannel';\nimport { wrapHot } from './wrapHot';\n\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\nimport type { SeedcordCliEvents, SeedcordFrameworkEvents } from '@seedcord/types/internal';\n\nexport class HmrManager {\n private readonly logger = new Logger('HMR', { channel: 'hmr' });\n private readonly listeners = new Set<HmrAware>();\n\n /** @internal */\n public init(): void {\n if (!import.meta.hot) return;\n // the only raw `import.meta.hot` read in the framework\n const channel = wrapHot<SeedcordFrameworkEvents, SeedcordCliEvents>(import.meta.hot);\n setDevChannel(channel);\n\n if (Envapter.isDevelopment || Envapter.isTest) {\n this.logger.debug('Enabled');\n channel.on('seedcord:hmr', (payload) => void this.handleUpdate(payload));\n }\n }\n\n /** @internal */\n public register(listener: HmrAware): void {\n this.listeners.add(listener);\n }\n\n private async handleUpdate(event: HmrUpdateEvent): Promise<void> {\n const promises = [...this.listeners].map(async (listener) => {\n try {\n await listener.onHmr(event);\n } catch (error) {\n this.logger.error(`Error handling HMR update in ${paint.sky.bold(listener.constructor.name)}`, error);\n }\n });\n\n await Promise.all(promises);\n }\n}\n","// dropping a non-Error throw would leave the user with no fault card, and every report payload declares an Error\n/** @internal */\nexport function asError(caught: unknown): Error {\n return Error.isError(caught) ? caught : new Error(String(caught), { cause: caught });\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport type { AckTrace } from './AckTrace';\n\nexport type AckState = 'unacked' | 'deferred-reply' | 'deferred-update' | 'replied';\n\nexport type ReplyMethod =\n 'reply' | 'defer' | 'deferUpdate' | 'update' | 'followUp' | 'edit' | 'delete' | 'send' | 'showModal';\n\ninterface Illegal {\n // spliced into the message after \"was called when\"\n readonly reason: string;\n readonly alternative: string;\n}\n\nconst showModalReason: Illegal = {\n reason: 'this interaction was already acknowledged',\n alternative: 'showModal() must be the initial response.'\n};\n\nfunction deferReason(what: string): Illegal {\n return {\n reason: `this interaction was already acknowledged with ${what}`,\n alternative: 'A deferral can only be the initial response.'\n };\n}\n\n// a missing entry means legal in that state\nconst ILLEGAL: Record<Exclude<ReplyMethod, 'send'>, Partial<Record<AckState, Illegal>>> = {\n reply: {\n 'deferred-reply': {\n reason: 'this interaction was already deferred',\n alternative: 'Use edit() to fill the deferred reply or followUp() for a new message.'\n },\n 'deferred-update': {\n reason: 'this interaction was already acknowledged with a deferred update',\n alternative: 'Use followUp() for a new message.'\n },\n replied: {\n reason: 'this interaction was already replied to',\n alternative: 'Use followUp() for a new message or edit() to rewrite the reply.'\n }\n },\n defer: {\n 'deferred-reply': deferReason('a deferred reply'),\n 'deferred-update': deferReason('a deferred update'),\n replied: deferReason('a reply')\n },\n deferUpdate: {\n 'deferred-reply': deferReason('a deferred reply'),\n 'deferred-update': deferReason('a deferred update'),\n replied: deferReason('a reply')\n },\n update: {\n 'deferred-reply': {\n reason: 'this interaction was deferred as a reply',\n alternative: 'The placeholder is yours via edit(). update() rewrites a source message.'\n },\n replied: {\n reason: 'this interaction was already replied to',\n alternative: 'update() rewrites a source message. Use edit() or followUp() instead.'\n }\n },\n followUp: {\n unacked: {\n reason: 'this interaction has not been acknowledged yet',\n alternative: 'Ack first with reply() or defer().'\n }\n },\n edit: {\n unacked: {\n reason: 'nothing has been sent yet',\n alternative: 'Send with reply() or defer() first, then edit() rewrites it.'\n }\n },\n delete: {\n unacked: {\n reason: 'nothing has been sent yet',\n alternative: 'Acknowledge first with reply() or defer(), then delete() removes it.'\n }\n },\n showModal: {\n 'deferred-reply': showModalReason,\n 'deferred-update': showModalReason,\n replied: showModalReason\n }\n};\n\nconst SEND_TARGET: Record<AckState, 'reply' | 'edit' | 'followUp'> = {\n unacked: 'reply',\n 'deferred-reply': 'edit',\n 'deferred-update': 'followUp',\n replied: 'followUp'\n};\n\n// throws before any API call, and ackedBy becomes the throw cause with its stack pointing at the first ack\nexport function checkAckLegality(method: ReplyMethod, state: AckState, routeId: string, ackedBy?: AckTrace): void {\n if (method === 'send') return;\n const illegal = ILLEGAL[method][state];\n if (!illegal) return;\n throw new SeedcordError(\n SeedcordErrorCode.ReplyIllegalAckState,\n [method, illegal.reason, illegal.alternative, routeId],\n { cause: ackedBy }\n );\n}\n\nexport function sendTarget(state: AckState): 'reply' | 'edit' | 'followUp' {\n return SEND_TARGET[state];\n}\n","import type { ReplyMethod } from './ackLegality';\n\n// the first ack's stack, passed as the throw cause so a double-ack points to both lines\nexport class AckTrace extends Error {\n public constructor(method: ReplyMethod) {\n super(`${method}() acknowledged this interaction`);\n this.name = 'AckTrace';\n }\n}\n","import { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\n\nimport { asError } from '#stops/asError';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { Bus } from '#subscribers/Bus';\nimport type { ResponseOutcome } from '#subscribers/types/Subscriptions';\nimport type { ReplyMethod } from './ackLegality';\n\nlet replyLogger: Logger | undefined;\nfunction logger(): Logger {\n replyLogger ??= new Logger('Reply', { channel: 'interactions' });\n return replyLogger;\n}\n\nexport interface ReplyTelemetry {\n readonly bus: Bus;\n readonly interactionId: string;\n}\n\n// the reply verbs plus the autocomplete choices callback, which runs outside the ack state machine\nexport type WriteMethod = ReplyMethod | 'respond';\n\nexport interface ResponseReport {\n readonly routeId: string;\n readonly method: WriteMethod;\n // performance.now() captured before the write began\n readonly startedAt: number;\n readonly outcome: ResponseOutcome;\n readonly messageId: string | null;\n readonly error?: Error;\n}\n\n/** @internal */\nexport function publishResponse(telemetry: ReplyTelemetry, report: ResponseReport): void {\n const durationMs = performance.now() - report.startedAt;\n telemetry.bus[PublishDefault]('responseAttempted', {\n routeId: report.routeId,\n interactionId: telemetry.interactionId,\n method: report.method,\n outcome: report.outcome,\n durationMs,\n messageId: report.messageId,\n ...(report.error && { error: report.error })\n });\n\n // after publish so a throwing sink doesn't reach the publish above\n logger().trace(\n `${paint.sky.bold(report.routeId)} ${report.method} ${report.outcome} ${paint.mute('in')} ${Math.round(durationMs)}ms`\n );\n}\n\nexport async function reportedWrite<Result>(\n telemetry: ReplyTelemetry,\n routeId: string,\n method: WriteMethod,\n write: () => Promise<Result>\n): Promise<Result> {\n const startedAt = performance.now();\n const result = await attemptWrite(telemetry, routeId, method, startedAt, write);\n publishResponse(telemetry, { routeId, method, startedAt, outcome: 'sent', messageId: null });\n return result;\n}\n\n// publishes the failed arm when write() throws, since the caller's success report sits after the write and a throw skips it\nexport async function attemptWrite<Result>(\n telemetry: ReplyTelemetry,\n routeId: string,\n method: WriteMethod,\n startedAt: number,\n write: () => Promise<Result>\n): Promise<Result> {\n try {\n return await write();\n } catch (caught) {\n const error = asError(caught);\n publishResponse(telemetry, { routeId, method, startedAt, outcome: 'failed', messageId: null, error });\n // rethrown raw\n throw caught;\n }\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\n// shapeshift aggregate errors nest, so the cap bounds the recursion\nconst MAX_DEPTH = 8;\n\ninterface ShapeshiftLike {\n readonly message?: unknown;\n readonly errors?: unknown;\n readonly constraint?: unknown;\n readonly validator?: unknown;\n readonly expected?: unknown;\n readonly given?: unknown;\n}\n\nconst OPERATOR_WORD: Record<string, string> = {\n '<=': 'at most',\n '>=': 'at least',\n '<': 'fewer than',\n '>': 'more than'\n};\n\nconst KIND_WORD: Record<string, string> = {\n string: 'a string',\n number: 'a number',\n boolean: 'a boolean',\n bigint: 'a bigint',\n array: 'a list',\n tuple: 'a list',\n date: 'a date',\n object: 'an object'\n};\n\nfunction asRecord(value: unknown): ShapeshiftLike | null {\n return typeof value === 'object' && value !== null ? value : null;\n}\n\nfunction firstLine(message: unknown): string {\n if (typeof message !== 'string') return 'unknown validation failure';\n return message.split('\\n')[0] ?? message;\n}\n\n// strings over this limit render as a character count\nconst GIVEN_PREVIEW_LIMIT = 40;\n\nfunction gotPhrase(given: unknown): string {\n if (given === undefined) return 'nothing';\n if (given === null) return 'null';\n if (typeof given === 'string') {\n if (given.length === 0) return 'an empty string';\n if (given.length > GIVEN_PREVIEW_LIMIT) return `${given.length} characters`;\n return JSON.stringify(given);\n }\n if (Array.isArray(given)) return given.length === 0 ? 'an empty list' : `${given.length} items`;\n if (typeof given === 'number' || typeof given === 'bigint' || typeof given === 'boolean') return String(given);\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- a null-prototype value has no constructor at runtime\n if (typeof given === 'object') return given.constructor?.name ?? 'an object';\n return typeof given;\n}\n\n// shapeshift constraint strings take the form 'expected.length >= 1' or 'expected <= 16777215'\nfunction needPhrase(expected: string, given: unknown): string | null {\n const bounds = [...expected.matchAll(/expected(\\.length)?\\s*(<=|>=|<|>)\\s*(\\d+)/g)];\n if (bounds.length === 0) return null;\n const phrases = bounds.map(([, lengthPart, operator, limit]) => {\n const word = OPERATOR_WORD[operator ?? ''] ?? operator ?? '';\n if (!lengthPart) return `${word} ${limit}`;\n const unit = typeof given === 'string' ? 'character' : 'item';\n return `${word} ${limit} ${Number(limit) === 1 ? unit : `${unit}s`}`;\n });\n return phrases.join(' and ');\n}\n\nfunction instanceName(expected: unknown): string {\n if (typeof expected === 'function' && expected.name) return expected.name;\n return 'specific class instance';\n}\n\nfunction describeLeaf(record: ShapeshiftLike): string {\n if (typeof record.constraint === 'string') {\n const need = typeof record.expected === 'string' ? needPhrase(record.expected, record.given) : null;\n if (need) return `need ${need}, got ${gotPhrase(record.given)}`;\n return `${firstLine(record.message)}${'given' in record ? ` (got ${gotPhrase(record.given)})` : ''}`;\n }\n if (typeof record.expected === 'function') {\n return `need a ${instanceName(record.expected)}, got ${gotPhrase(record.given)}`;\n }\n if (typeof record.validator === 'string') {\n const kind = KIND_WORD[record.validator.replace(/^s\\./, '').replace(/\\(.*$/, '')];\n if (kind) return `need ${kind}, got ${gotPhrase(record.given)}`;\n return `${firstLine(record.message)}, got ${gotPhrase(record.given)}`;\n }\n return firstLine(record.message);\n}\n\nfunction describeUnion(branches: unknown[], depth: number): string {\n const records: ShapeshiftLike[] = [];\n for (const branch of branches) {\n const record = asRecord(branch);\n if (record) records.push(record);\n }\n const bounded = records.find((record) => typeof record.constraint === 'string');\n if (bounded) return describeLeaf(bounded);\n\n const names: string[] = [];\n let allInstances = records.length > 0;\n for (const record of records) {\n if (typeof record.expected === 'function') names.push(instanceName(record.expected));\n else allInstances = false;\n }\n if (allInstances) {\n return `need a ${[...new Set(names)].join(' or ')}, got ${gotPhrase(records[0]?.given)}`;\n }\n return detailOf(branches[0], depth + 1);\n}\n\n// shapeshift nests errors as [key, inner] tuples\nfunction detailOf(value: unknown, depth: number): string {\n const record = asRecord(value);\n if (!record || depth >= MAX_DEPTH) return firstLine(record?.message);\n\n if (Array.isArray(record.errors) && record.errors.length > 0) {\n const errors = record.errors as unknown[];\n const [first] = errors;\n if (Array.isArray(first)) {\n const [key, inner] = first as [unknown, unknown];\n return `${String(key)}: ${detailOf(inner, depth + 1)}`;\n }\n return describeUnion(errors, depth);\n }\n\n return describeLeaf(record);\n}\n\n// a shapeshift throw becomes an actionable message with the cause kept, while anything else contributes its own message as the detail\nexport function translateSerializationError(\n error: unknown,\n componentClass: string,\n index: number,\n routeId: string\n): SeedcordError<SeedcordErrorCode.ReplyComponentSerialization> {\n const detail = detailOf(error, 0);\n return new SeedcordError(SeedcordErrorCode.ReplyComponentSerialization, [componentClass, index, detail, routeId], {\n cause: error\n });\n}\n","import { TextDisplayBuilder } from '@discordjs/builders';\n\nimport { translateSerializationError } from './translateSerialization';\n\nimport type { ReplyResponse } from '@seedcord/types';\nimport type { APIMessageTopLevelComponent } from 'discord-api-types/v10';\n\nexport interface SerializedReply<TNative = never> {\n readonly components: APIMessageTopLevelComponent[];\n readonly allowedMentions?: ReplyResponse['allowedMentions'];\n readonly files?: ReplyResponse<TNative>['files'];\n}\n\nexport function serializeReply<TNative>(\n response: ReplyResponse<TNative> | string,\n routeId: string\n): SerializedReply<TNative> {\n if (typeof response === 'string') {\n try {\n return { components: [new TextDisplayBuilder().setContent(response).toJSON()] };\n } catch (error) {\n throw translateSerializationError(error, 'TextDisplayBuilder', 0, routeId);\n }\n }\n\n const components = response.components.map((component, index) => {\n try {\n return component.toJSON();\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- a null-prototype value has no constructor at runtime\n const name = component.constructor?.name;\n throw translateSerializationError(error, !name || name === 'Object' ? 'component' : name, index, routeId);\n }\n });\n\n return {\n components,\n ...(response.allowedMentions && { allowedMentions: response.allowedMentions }),\n ...(response.files && { files: response.files })\n };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport { checkAckLegality, sendTarget } from './ackLegality';\nimport { AckTrace } from './AckTrace';\nimport { attemptWrite, publishResponse } from './responseReport';\nimport { serializeReply } from './serializeReply';\nimport { translateSerializationError } from './translateSerialization';\n\nimport type { ResponseOutcome } from '#subscribers/types/Subscriptions';\nimport type { AckState, ReplyMethod } from './ackLegality';\nimport type { ReplyTelemetry } from './responseReport';\nimport type { SerializedReply } from './serializeReply';\nimport type { DeferOpts, ReplyResponse, SendOpts } from '@seedcord/types';\nimport type { APIModalInteractionResponseCallbackData } from 'discord-api-types/v10';\n\n/** The shape djs modal builders emit from `toJSON()`. */\nexport interface ModalLike {\n toJSON(): APIModalInteractionResponseCallbackData;\n}\n\n/**\n * Tracks acknowledgement state and throws a translated `SeedcordError` on an illegal verb before any\n * transport call. Every verb runs the legality check, calls the transport's wire writer, then updates\n * the state and the ack trace. Each transport binds `TMessage` to its own created-message type and\n * supplies the writers.\n */\nexport abstract class BaseReplySender<TMessage extends { id: string }, TNative = never> {\n private state: AckState;\n // the only ids a targeted edit accepts\n private readonly sent = new Set<string>();\n // the cause carries the first ack's stack so a double-ack reports both lines\n private ackedBy?: AckTrace;\n\n protected constructor(\n protected readonly routeId: string,\n private readonly telemetry: ReplyTelemetry,\n initialState: AckState = 'unacked'\n ) {\n this.state = initialState;\n }\n\n private report(method: ReplyMethod, startedAt: number, outcome: ResponseOutcome, messageId: string | null): void {\n publishResponse(this.telemetry, { routeId: this.routeId, method, startedAt, outcome, messageId });\n }\n\n private async attempt<Result>(\n method: ReplyMethod,\n startedAt: number,\n write: () => Promise<Result>\n ): Promise<Result> {\n return await attemptWrite(this.telemetry, this.routeId, method, startedAt, write);\n }\n\n public async reply(response: ReplyResponse<TNative> | string, opts?: SendOpts): Promise<TMessage> {\n this.checkLegality('reply');\n const startedAt = performance.now();\n const created = await this.attempt('reply', startedAt, () => this.writeReply(response, opts));\n // the callback already acked discord, so record replied before the message guard can throw\n this.transition('reply', 'replied');\n const message = this.remember(this.requireMessage(created, 'reply'));\n this.report('reply', startedAt, 'sent', message.id);\n return message;\n }\n\n public async defer(opts?: DeferOpts): Promise<void> {\n this.checkLegality('defer');\n const startedAt = performance.now();\n await this.attempt('defer', startedAt, () => this.writeDefer(opts));\n this.transition('defer', 'deferred-reply');\n this.report('defer', startedAt, 'sent', null);\n }\n\n public async deferUpdate(): Promise<void> {\n this.checkLegality('deferUpdate');\n const startedAt = performance.now();\n await this.attempt('deferUpdate', startedAt, () => this.writeDeferUpdate());\n this.transition('deferUpdate', 'deferred-update');\n this.report('deferUpdate', startedAt, 'sent', null);\n }\n\n /**\n * After a deferUpdate the state stays deferred-update. Calling this again rewrites. The returned message\n * is the source message, editable only through a bare `update` or `edit`. A targeted `edit` or `delete`\n * of it throws the foreign-target error.\n */\n public async update(response: ReplyResponse<TNative> | string): Promise<TMessage> {\n this.checkLegality('update');\n const startedAt = performance.now();\n // in deferred-update the source message is @original and the ack-legality check above already passed\n if (this.state === 'deferred-update') {\n const edited = await this.attempt('update', startedAt, () => this.editOriginal(response));\n this.report('update', startedAt, 'sent', edited.id);\n return edited;\n }\n const created = await this.attempt('update', startedAt, () => this.writeUpdate(response));\n this.transition('update', 'replied');\n const message = this.remember(this.requireMessage(created, 'update'));\n this.report('update', startedAt, 'sent', message.id);\n return message;\n }\n\n public async followUp(response: ReplyResponse<TNative> | string, opts?: SendOpts): Promise<TMessage> {\n this.checkLegality('followUp');\n const startedAt = performance.now();\n const created = await this.attempt('followUp', startedAt, () => this.writeFollowUp(response, opts));\n const message = this.remember(created);\n this.report('followUp', startedAt, 'sent', message.id);\n return message;\n }\n\n public edit(response: ReplyResponse<TNative> | string): Promise<TMessage>;\n public edit(target: TMessage, response: ReplyResponse<TNative> | string): Promise<TMessage>;\n public async edit(\n targetOrResponse: TMessage | ReplyResponse<TNative> | string,\n maybeResponse?: ReplyResponse<TNative> | string\n ): Promise<TMessage> {\n this.checkLegality('edit');\n const startedAt = performance.now();\n if (maybeResponse === undefined) {\n // justified: the overloads narrow targetOrResponse to a response once maybeResponse is absent\n const edited = await this.attempt('edit', startedAt, () =>\n this.editOriginal(targetOrResponse as ReplyResponse<TNative> | string)\n );\n this.report('edit', startedAt, 'sent', edited.id);\n return edited;\n }\n // justified: the overloads narrow targetOrResponse to a target message once maybeResponse is defined\n const target = targetOrResponse as TMessage;\n if (!this.sent.has(target.id)) {\n throw new SeedcordError(SeedcordErrorCode.ReplyForeignEditTarget, ['edit', target.id, this.routeId]);\n }\n const written = await this.attempt('edit', startedAt, () => this.writeEditTarget(target.id, maybeResponse));\n const message = this.remember(written);\n this.report('edit', startedAt, 'sent', message.id);\n return message;\n }\n\n public delete(): Promise<void>;\n public delete(target: TMessage): Promise<void>;\n public async delete(target?: TMessage): Promise<void> {\n this.checkLegality('delete');\n const startedAt = performance.now();\n if (target === undefined) {\n await this.attempt('delete', startedAt, () => this.writeDeleteOriginal());\n this.report('delete', startedAt, 'sent', null);\n return;\n }\n if (!this.sent.has(target.id)) {\n throw new SeedcordError(SeedcordErrorCode.ReplyForeignEditTarget, ['delete', target.id, this.routeId]);\n }\n await this.attempt('delete', startedAt, () => this.writeDeleteTarget(target.id));\n // evict so a later targeted edit of this id throws foreign\n this.sent.delete(target.id);\n this.report('delete', startedAt, 'sent', null);\n }\n\n /** Routes to the verb the current ack state permits. Every state has a route, so the illegal-ack throw is unreachable. */\n public async send(response: ReplyResponse<TNative> | string, opts?: SendOpts): Promise<TMessage> {\n const target = sendTarget(this.state);\n if (target === 'reply') return await this.reply(response, opts);\n if (target === 'edit') return await this.edit(response);\n return await this.followUp(response, opts);\n }\n\n /** Must be the initial response. */\n public async showModal(modal: ModalLike): Promise<void> {\n this.checkLegality('showModal');\n this.guardModalSource();\n let data: APIModalInteractionResponseCallbackData;\n try {\n data = modal.toJSON();\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- a null-prototype modal has no constructor at runtime\n const name = modal.constructor?.name;\n throw translateSerializationError(error, !name || name === 'Object' ? 'modal' : name, 0, this.routeId);\n }\n const startedAt = performance.now();\n await this.attempt('showModal', startedAt, () => this.writeModal(data));\n this.transition('showModal', 'replied');\n this.report('showModal', startedAt, 'sent', null);\n }\n\n protected serialize(response: ReplyResponse<TNative> | string): SerializedReply<TNative> {\n return serializeReply(response, this.routeId);\n }\n\n protected remember(created: TMessage): TMessage {\n this.sent.add(created.id);\n return created;\n }\n\n // a with_response callback always returns the created message. this only trips if the wire says otherwise\n private requireMessage(created: TMessage | undefined, method: 'reply' | 'update'): TMessage {\n if (!created) throw new SeedcordError(SeedcordErrorCode.ReplyCallbackMissingMessage, [method, this.routeId]);\n return created;\n }\n\n private async editOriginal(response: ReplyResponse<TNative> | string): Promise<TMessage> {\n const edited = await this.writeEditOriginal(response);\n // after a deferUpdate the source message is @original, which this interaction did not send\n if (this.state !== 'deferred-update') this.remember(edited);\n return edited;\n }\n\n private checkLegality(method: ReplyMethod): void {\n checkAckLegality(method, this.state, this.routeId, this.ackedBy);\n }\n\n private transition(method: ReplyMethod, state: AckState): void {\n this.state = state;\n this.ackedBy = new AckTrace(method);\n }\n\n // gateway overrides this to reject a modal-submit source\n protected guardModalSource(): void {}\n\n protected abstract writeReply(\n response: ReplyResponse<TNative> | string,\n opts?: SendOpts\n ): Promise<TMessage | undefined>;\n protected abstract writeDefer(opts?: DeferOpts): Promise<void>;\n protected abstract writeDeferUpdate(): Promise<void>;\n protected abstract writeUpdate(response: ReplyResponse<TNative> | string): Promise<TMessage | undefined>;\n protected abstract writeFollowUp(response: ReplyResponse<TNative> | string, opts?: SendOpts): Promise<TMessage>;\n protected abstract writeEditOriginal(response: ReplyResponse<TNative> | string): Promise<TMessage>;\n protected abstract writeEditTarget(targetId: string, response: ReplyResponse<TNative> | string): Promise<TMessage>;\n protected abstract writeDeleteOriginal(): Promise<void>;\n protected abstract writeDeleteTarget(targetId: string): Promise<void>;\n protected abstract writeModal(data: APIModalInteractionResponseCallbackData): Promise<void>;\n}\n","import { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { timestampFromSnowflake } from '@seedcord/utils';\n\nimport { Notice } from '#stops/Notice';\nimport { Silence } from '#stops/Silence';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { InteractionRoutes } from '#src/metadataKeys';\nimport type { Bus } from '#subscribers/Bus';\nimport type { DispatchOutcome } from '#subscribers/types/Subscriptions';\n\nlet dispatchLogger: Logger | undefined;\nfunction logger(): Logger {\n dispatchLogger ??= new Logger('Dispatch', { channel: 'interactions' });\n return dispatchLogger;\n}\n\n/**\n * Classifies a caught stop for `interactionDispatched` so a gate and a\n * handler label the same stop identically.\n *\n * @internal\n */\nexport function outcomeFor(caught: unknown): DispatchOutcome {\n if (caught instanceof Silence) return 'refused';\n if (caught instanceof Notice) return caught.report ? 'failed' : 'refused';\n return 'failed';\n}\n\n/** The values a dispatcher has once its handler chain settles. */\ninterface DispatchReport {\n readonly routeId: string;\n readonly interactionId: string;\n readonly kind: `${InteractionRoutes}`;\n readonly outcome: DispatchOutcome;\n /** True when no route matched and the unhandled default ran. */\n readonly fallback: boolean;\n /** `performance.now()` captured as the dispatch began. */\n readonly startedAt: number;\n /** {@link queuedMsFor} read at the same moment as `startedAt`. */\n readonly queuedMs: number;\n}\n\n/**\n * Discord's interaction creation to now, read from the snowflake. Called at dispatch entry, since\n * a later read would count the handler run into the queue time as well.\n *\n * @internal\n */\nexport function queuedMsFor(interactionId: string): number {\n // telemetry never breaks a dispatch, and BigInt() throws on a malformed id\n try {\n return Date.now() - timestampFromSnowflake(interactionId);\n } catch {\n return 0;\n }\n}\n\nexport function reportDispatch(bus: Bus, report: DispatchReport): void {\n const durationMs = performance.now() - report.startedAt;\n bus[PublishDefault]('interactionDispatched', {\n routeId: report.routeId,\n interactionId: report.interactionId,\n kind: report.kind,\n outcome: report.outcome,\n fallback: report.fallback,\n durationMs,\n queuedMs: report.queuedMs\n });\n\n // after publish so a throwing sink doesn't reach the publish above\n logger().trace(\n `${paint.sky.bold(report.routeId)} ${report.outcome} ${paint.mute('in')} ${Math.round(durationMs)}ms`\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"internal.index.mjs","names":["logger","logger"],"sources":["../src/decorators/brands.ts","../src/gates/runGates.ts","../src/gates/slowGate.ts","../src/miscellaneous/emojiConfig.ts","../src/hmr/wrapHot.ts","../src/hmr/HmrManager.ts","../src/stops/asError.ts","../src/reply/ackLegality.ts","../src/reply/AckTrace.ts","../src/reply/responseReport.ts","../src/reply/translateSerialization.ts","../src/reply/serializeReply.ts","../src/reply/BaseReplySender.ts","../src/dispatch/dispatchReport.ts"],"sourcesContent":["// A handler base declares these so a route decorator can read what the handler is for. Nothing\n// assigns them at runtime.\n\nexport const SlashRouteBrand: unique symbol = Symbol('seedcord:brand:slashRoute');\nexport const AutocompleteRouteBrand: unique symbol = Symbol('seedcord:brand:autocompleteRoute');\nexport const ContextMenuKindBrand: unique symbol = Symbol('seedcord:brand:contextMenuKind');\nexport const ContextMenuNamesBrand: unique symbol = Symbol('seedcord:brand:contextMenuNames');\nexport const ComponentKindBrand: unique symbol = Symbol('seedcord:brand:componentKind');\nexport const ComponentDefsBrand: unique symbol = Symbol('seedcord:brand:componentDefs');\n","import 'reflect-metadata';\n\nimport { GatedMetadataKey, InteractionRouteKeys } from '#src/metadataKeys';\n\nimport { discardCommits, runCheck, runCommits } from './effects';\n\nimport type { Gate, GateContextBase } from './Gate';\n\n// a combinator (`and`/`or`) reports once under its joined name.\nexport type GateObserver = (gateName: string, elapsedMs: number) => void;\n\n// the stable id a route decorator stored, e.g. slash:daily or button:confirm. null for a plain event handler.\nexport function routeIdOf(handlerCtor: object): string | null {\n for (const [kind, key] of Object.entries(InteractionRouteKeys)) {\n // justified: getMetadata returns any, this key only ever stores the route/prefix string array\n const routes = Reflect.getMetadata(key, handlerCtor) as string[] | undefined;\n if (routes?.length) return `${kind}:${routes.join(',')}`;\n }\n return null;\n}\n\nasync function timedCheck(gate: Gate<GateContextBase>, ctx: GateContextBase, observe: GateObserver): Promise<void> {\n const start = performance.now();\n // finally so a refusing gate still reports\n try {\n await runCheck(gate, ctx);\n } finally {\n observe(gate.name, performance.now() - start);\n }\n}\n\n// the first refusal throws out to the dispatcher boundary. an effect gate's commit runs once the\n// whole set passes, and observe never times the commit phase.\nexport async function runGates(\n gates: readonly Gate<GateContextBase>[],\n ctx: GateContextBase,\n observe?: GateObserver\n): Promise<void> {\n try {\n for (const gate of gates) {\n await (observe ? timedCheck(gate, ctx, observe) : runCheck(gate, ctx));\n }\n await runCommits(ctx);\n } finally {\n discardCommits(ctx);\n }\n}\n\n// dispatchers call this before execute, inside the boundary, so a refusal renders or drops. an explicit\n// routeId is for manifest-driven handlers, which carry no route metadata to derive one from.\nexport async function runHandlerGates(\n handlerCtor: object,\n ctx: GateContextBase,\n routeId?: string,\n observe?: GateObserver\n): Promise<void> {\n // justified: getMetadata returns any, and this key only ever stores the @Gated gate array\n const gates = Reflect.getMetadata(GatedMetadataKey, handlerCtor) as readonly Gate<GateContextBase>[] | undefined;\n if (!gates) return;\n await runGates(gates, { ...ctx, routeId: routeId ?? routeIdOf(handlerCtor) }, observe);\n}\n","import { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { Envapter } from 'envapt';\n\nimport type { GateObserver } from './runGates';\n\n// combined check time slower than this cuts into the 3s interaction-ack budget\nconst SLOW_GATE_MS = 750;\n\n// lazy because the logger reads the environment, which binds after this module loads\nlet gateLogger: Logger | undefined;\nfunction logger(): Logger {\n gateLogger ??= new Logger('Gates', { channel: 'gates' });\n return gateLogger;\n}\n\n/**\n * collects each gate check's elapsed time for one dispatch. `report` warns once when the checks sum\n * past the threshold, naming the total, the route, and each gate's share.\n */\nexport interface SlowGateMonitor {\n readonly observe: GateObserver;\n report(routeId: string | null): void;\n}\n\n// undefined in production, so timedCheck skips the clock there. read per call, the environment binds late\nexport function slowGateMonitor(): SlowGateMonitor | undefined {\n if (Envapter.isProduction) return undefined;\n const readings: { name: string; ms: number }[] = [];\n return {\n observe: (name, ms) => readings.push({ name, ms }),\n report(routeId) {\n const total = readings.reduce((sum, reading) => sum + reading.ms, 0);\n if (total < SLOW_GATE_MS) return;\n const shares = readings\n .toSorted((a, b) => b.ms - a.ms)\n .map((reading) => `${paint.sky.bold(reading.name)} ${paint.mute(`${Math.round(reading.ms)}ms`)}`);\n const route = routeId === null ? '' : ` for ${paint.sky.bold(routeId)}`;\n const headline = `gates${route} took ${paint.amber(`${Math.round(total)}ms`)} of the 3s ack budget`;\n logger().utils.block(headline, shares, 'warn', (text) => text);\n }\n };\n}\n","// narrows a configured emoji value to the [name, guildId] form\nexport function isEmojiTuple(value: unknown): value is readonly [string, string] {\n return Array.isArray(value) && value.length === 2 && typeof value[0] === 'string' && typeof value[1] === 'string';\n}\n","import type { DevChannel } from '@seedcord/types/internal';\n\n// the minimal raw-hot shape, satisfied by both vite's `import.meta.hot` and the CLI's `NormalizedHotChannel`.\ninterface RawHot {\n send(event: string, data?: unknown): void;\n on(event: string, cb: (data: unknown) => void): void;\n}\n\n/** @internal */\nexport function wrapHot<TSend, TRecv>(hot: RawHot): DevChannel<TSend, TRecv> {\n return {\n send: (event, data) => hot.send(event, data),\n // justified: RawHot types every payload as unknown, and the wire contract guarantees it matches TRecv[Key]\n on: (event, cb) => hot.on(event, cb as (data: unknown) => void)\n };\n}\n","// scoped here so import.meta.hot stays untyped in the rest of the package\n/// <reference types=\"vite/client\" />\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { Envapter } from 'envapt';\n\nimport { setDevChannel } from './devChannel';\nimport { wrapHot } from './wrapHot';\n\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\nimport type { SeedcordCliEvents, SeedcordFrameworkEvents } from '@seedcord/types/internal';\n\nexport class HmrManager {\n private readonly logger = new Logger('HMR', { channel: 'hmr' });\n private readonly listeners = new Set<HmrAware>();\n\n /** @internal */\n public init(): void {\n if (!import.meta.hot) return;\n // the only raw `import.meta.hot` read in the framework\n const channel = wrapHot<SeedcordFrameworkEvents, SeedcordCliEvents>(import.meta.hot);\n setDevChannel(channel);\n\n if (Envapter.isDevelopment || Envapter.isTest) {\n this.logger.debug('Enabled');\n channel.on('seedcord:hmr', (payload) => void this.handleUpdate(payload));\n }\n }\n\n /** @internal */\n public register(listener: HmrAware): void {\n this.listeners.add(listener);\n }\n\n private async handleUpdate(event: HmrUpdateEvent): Promise<void> {\n const promises = [...this.listeners].map(async (listener) => {\n try {\n await listener.onHmr(event);\n } catch (error) {\n this.logger.error(`Error handling HMR update in ${paint.sky.bold(listener.constructor.name)}`, error);\n }\n });\n\n await Promise.all(promises);\n }\n}\n","// dropping a non-Error throw would leave the user with no fault card, and every report payload declares an Error\n/** @internal */\nexport function asError(caught: unknown): Error {\n return Error.isError(caught) ? caught : new Error(String(caught), { cause: caught });\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport type { AckTrace } from './AckTrace';\n\nexport type AckState = 'unacked' | 'deferred-reply' | 'deferred-update' | 'replied';\n\nexport type ReplyMethod =\n 'reply' | 'defer' | 'deferUpdate' | 'update' | 'followUp' | 'edit' | 'delete' | 'send' | 'showModal';\n\ninterface Illegal {\n // spliced into the message after \"was called when\"\n readonly reason: string;\n readonly alternative: string;\n}\n\nconst showModalReason: Illegal = {\n reason: 'this interaction was already acknowledged',\n alternative: 'showModal() must be the initial response.'\n};\n\nfunction deferReason(what: string): Illegal {\n return {\n reason: `this interaction was already acknowledged with ${what}`,\n alternative: 'A deferral can only be the initial response.'\n };\n}\n\n// a missing entry means legal in that state\nconst ILLEGAL: Record<Exclude<ReplyMethod, 'send'>, Partial<Record<AckState, Illegal>>> = {\n reply: {\n 'deferred-reply': {\n reason: 'this interaction was already deferred',\n alternative: 'Use edit() to fill the deferred reply or followUp() for a new message.'\n },\n 'deferred-update': {\n reason: 'this interaction was already acknowledged with a deferred update',\n alternative: 'Use followUp() for a new message.'\n },\n replied: {\n reason: 'this interaction was already replied to',\n alternative: 'Use followUp() for a new message or edit() to rewrite the reply.'\n }\n },\n defer: {\n 'deferred-reply': deferReason('a deferred reply'),\n 'deferred-update': deferReason('a deferred update'),\n replied: deferReason('a reply')\n },\n deferUpdate: {\n 'deferred-reply': deferReason('a deferred reply'),\n 'deferred-update': deferReason('a deferred update'),\n replied: deferReason('a reply')\n },\n update: {\n 'deferred-reply': {\n reason: 'this interaction was deferred as a reply',\n alternative: 'The placeholder is yours via edit(). update() rewrites a source message.'\n },\n replied: {\n reason: 'this interaction was already replied to',\n alternative: 'update() rewrites a source message. Use edit() or followUp() instead.'\n }\n },\n followUp: {\n unacked: {\n reason: 'this interaction has not been acknowledged yet',\n alternative: 'Ack first with reply() or defer().'\n }\n },\n edit: {\n unacked: {\n reason: 'nothing has been sent yet',\n alternative: 'Send with reply() or defer() first, then edit() rewrites it.'\n }\n },\n delete: {\n unacked: {\n reason: 'nothing has been sent yet',\n alternative: 'Acknowledge first with reply() or defer(), then delete() removes it.'\n }\n },\n showModal: {\n 'deferred-reply': showModalReason,\n 'deferred-update': showModalReason,\n replied: showModalReason\n }\n};\n\nconst SEND_TARGET: Record<AckState, 'reply' | 'edit' | 'followUp'> = {\n unacked: 'reply',\n 'deferred-reply': 'edit',\n 'deferred-update': 'followUp',\n replied: 'followUp'\n};\n\n// throws before any API call, and ackedBy becomes the throw cause with its stack pointing at the first ack\nexport function checkAckLegality(method: ReplyMethod, state: AckState, routeId: string, ackedBy?: AckTrace): void {\n if (method === 'send') return;\n const illegal = ILLEGAL[method][state];\n if (!illegal) return;\n throw new SeedcordError(\n SeedcordErrorCode.ReplyIllegalAckState,\n [method, illegal.reason, illegal.alternative, routeId],\n { cause: ackedBy }\n );\n}\n\nexport function sendTarget(state: AckState): 'reply' | 'edit' | 'followUp' {\n return SEND_TARGET[state];\n}\n","import type { ReplyMethod } from './ackLegality';\n\n// the first ack's stack, passed as the throw cause so a double-ack points to both lines\nexport class AckTrace extends Error {\n public constructor(method: ReplyMethod) {\n super(`${method}() acknowledged this interaction`);\n this.name = 'AckTrace';\n }\n}\n","import { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\n\nimport { asError } from '#stops/asError';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { Bus } from '#subscribers/Bus';\nimport type { ResponseOutcome } from '#subscribers/types/Subscriptions';\nimport type { ReplyMethod } from './ackLegality';\n\nlet replyLogger: Logger | undefined;\nfunction logger(): Logger {\n replyLogger ??= new Logger('Reply', { channel: 'interactions' });\n return replyLogger;\n}\n\nexport interface ReplyTelemetry {\n readonly bus: Bus;\n readonly interactionId: string;\n}\n\n// the reply verbs plus the autocomplete choices callback, which runs outside the ack state machine\nexport type WriteMethod = ReplyMethod | 'respond';\n\nexport interface ResponseReport {\n readonly routeId: string;\n readonly method: WriteMethod;\n // performance.now() captured before the write began\n readonly startedAt: number;\n readonly outcome: ResponseOutcome;\n readonly messageId: string | null;\n readonly error?: Error;\n}\n\n/** @internal */\nexport function publishResponse(telemetry: ReplyTelemetry, report: ResponseReport): void {\n const durationMs = performance.now() - report.startedAt;\n telemetry.bus[PublishDefault]('responseAttempted', {\n routeId: report.routeId,\n interactionId: telemetry.interactionId,\n method: report.method,\n outcome: report.outcome,\n durationMs,\n messageId: report.messageId,\n ...(report.error && { error: report.error })\n });\n\n // after publish so a throwing sink doesn't reach the publish above\n logger().trace(\n `${paint.sky.bold(report.routeId)} ${report.method} ${report.outcome} ${paint.mute('in')} ${Math.round(durationMs)}ms`\n );\n}\n\nexport async function reportedWrite<Result>(\n telemetry: ReplyTelemetry,\n routeId: string,\n method: WriteMethod,\n write: () => Promise<Result>\n): Promise<Result> {\n const startedAt = performance.now();\n const result = await attemptWrite(telemetry, routeId, method, startedAt, write);\n publishResponse(telemetry, { routeId, method, startedAt, outcome: 'sent', messageId: null });\n return result;\n}\n\n// publishes the failed arm when write() throws, since the caller's success report sits after the write and a throw skips it\nexport async function attemptWrite<Result>(\n telemetry: ReplyTelemetry,\n routeId: string,\n method: WriteMethod,\n startedAt: number,\n write: () => Promise<Result>\n): Promise<Result> {\n try {\n return await write();\n } catch (caught) {\n const error = asError(caught);\n publishResponse(telemetry, { routeId, method, startedAt, outcome: 'failed', messageId: null, error });\n // rethrown raw\n throw caught;\n }\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\n// shapeshift aggregate errors nest, so the cap bounds the recursion\nconst MAX_DEPTH = 8;\n\ninterface ShapeshiftLike {\n readonly message?: unknown;\n readonly errors?: unknown;\n readonly constraint?: unknown;\n readonly validator?: unknown;\n readonly expected?: unknown;\n readonly given?: unknown;\n}\n\nconst OPERATOR_WORD: Record<string, string> = {\n '<=': 'at most',\n '>=': 'at least',\n '<': 'fewer than',\n '>': 'more than'\n};\n\nconst KIND_WORD: Record<string, string> = {\n string: 'a string',\n number: 'a number',\n boolean: 'a boolean',\n bigint: 'a bigint',\n array: 'a list',\n tuple: 'a list',\n date: 'a date',\n object: 'an object'\n};\n\nfunction asRecord(value: unknown): ShapeshiftLike | null {\n return typeof value === 'object' && value !== null ? value : null;\n}\n\nfunction firstLine(message: unknown): string {\n if (typeof message !== 'string') return 'unknown validation failure';\n return message.split('\\n')[0] ?? message;\n}\n\n// strings over this limit render as a character count\nconst GIVEN_PREVIEW_LIMIT = 40;\n\nfunction gotPhrase(given: unknown): string {\n if (given === undefined) return 'nothing';\n if (given === null) return 'null';\n if (typeof given === 'string') {\n if (given.length === 0) return 'an empty string';\n if (given.length > GIVEN_PREVIEW_LIMIT) return `${given.length} characters`;\n return JSON.stringify(given);\n }\n if (Array.isArray(given)) return given.length === 0 ? 'an empty list' : `${given.length} items`;\n if (typeof given === 'number' || typeof given === 'bigint' || typeof given === 'boolean') return String(given);\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- a null-prototype value has no constructor at runtime\n if (typeof given === 'object') return given.constructor?.name ?? 'an object';\n return typeof given;\n}\n\n// shapeshift constraint strings take the form 'expected.length >= 1' or 'expected <= 16777215'\nfunction needPhrase(expected: string, given: unknown): string | null {\n const bounds = [...expected.matchAll(/expected(\\.length)?\\s*(<=|>=|<|>)\\s*(\\d+)/g)];\n if (bounds.length === 0) return null;\n const phrases = bounds.map(([, lengthPart, operator, limit]) => {\n const word = OPERATOR_WORD[operator ?? ''] ?? operator ?? '';\n if (!lengthPart) return `${word} ${limit}`;\n const unit = typeof given === 'string' ? 'character' : 'item';\n return `${word} ${limit} ${Number(limit) === 1 ? unit : `${unit}s`}`;\n });\n return phrases.join(' and ');\n}\n\nfunction instanceName(expected: unknown): string {\n if (typeof expected === 'function' && expected.name) return expected.name;\n return 'specific class instance';\n}\n\nfunction describeLeaf(record: ShapeshiftLike): string {\n if (typeof record.constraint === 'string') {\n const need = typeof record.expected === 'string' ? needPhrase(record.expected, record.given) : null;\n if (need) return `need ${need}, got ${gotPhrase(record.given)}`;\n return `${firstLine(record.message)}${'given' in record ? ` (got ${gotPhrase(record.given)})` : ''}`;\n }\n if (typeof record.expected === 'function') {\n return `need a ${instanceName(record.expected)}, got ${gotPhrase(record.given)}`;\n }\n if (typeof record.validator === 'string') {\n const kind = KIND_WORD[record.validator.replace(/^s\\./, '').replace(/\\(.*$/, '')];\n if (kind) return `need ${kind}, got ${gotPhrase(record.given)}`;\n return `${firstLine(record.message)}, got ${gotPhrase(record.given)}`;\n }\n return firstLine(record.message);\n}\n\nfunction describeUnion(branches: unknown[], depth: number): string {\n const records: ShapeshiftLike[] = [];\n for (const branch of branches) {\n const record = asRecord(branch);\n if (record) records.push(record);\n }\n const bounded = records.find((record) => typeof record.constraint === 'string');\n if (bounded) return describeLeaf(bounded);\n\n const names: string[] = [];\n let allInstances = records.length > 0;\n for (const record of records) {\n if (typeof record.expected === 'function') names.push(instanceName(record.expected));\n else allInstances = false;\n }\n if (allInstances) {\n return `need a ${[...new Set(names)].join(' or ')}, got ${gotPhrase(records[0]?.given)}`;\n }\n return detailOf(branches[0], depth + 1);\n}\n\n// shapeshift nests errors as [key, inner] tuples\nfunction detailOf(value: unknown, depth: number): string {\n const record = asRecord(value);\n if (!record || depth >= MAX_DEPTH) return firstLine(record?.message);\n\n if (Array.isArray(record.errors) && record.errors.length > 0) {\n const errors = record.errors as unknown[];\n const [first] = errors;\n if (Array.isArray(first)) {\n const [key, inner] = first as [unknown, unknown];\n return `${String(key)}: ${detailOf(inner, depth + 1)}`;\n }\n return describeUnion(errors, depth);\n }\n\n return describeLeaf(record);\n}\n\n// a shapeshift throw becomes an actionable message with the cause kept, while anything else contributes its own message as the detail\nexport function translateSerializationError(\n error: unknown,\n componentClass: string,\n index: number,\n routeId: string\n): SeedcordError<SeedcordErrorCode.ReplyComponentSerialization> {\n const detail = detailOf(error, 0);\n return new SeedcordError(SeedcordErrorCode.ReplyComponentSerialization, [componentClass, index, detail, routeId], {\n cause: error\n });\n}\n","import { TextDisplayBuilder } from '@discordjs/builders';\n\nimport { translateSerializationError } from './translateSerialization';\n\nimport type { ReplyResponse } from '@seedcord/types';\nimport type { APIMessageTopLevelComponent } from 'discord-api-types/v10';\n\nexport interface SerializedReply<TNative = never> {\n readonly components: APIMessageTopLevelComponent[];\n readonly allowedMentions?: ReplyResponse['allowedMentions'];\n readonly files?: ReplyResponse<TNative>['files'];\n}\n\nexport function serializeReply<TNative>(\n response: ReplyResponse<TNative> | string,\n routeId: string\n): SerializedReply<TNative> {\n if (typeof response === 'string') {\n try {\n return { components: [new TextDisplayBuilder().setContent(response).toJSON()] };\n } catch (error) {\n throw translateSerializationError(error, 'TextDisplayBuilder', 0, routeId);\n }\n }\n\n const components = response.components.map((component, index) => {\n try {\n return component.toJSON();\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- a null-prototype value has no constructor at runtime\n const name = component.constructor?.name;\n throw translateSerializationError(error, !name || name === 'Object' ? 'component' : name, index, routeId);\n }\n });\n\n return {\n components,\n ...(response.allowedMentions && { allowedMentions: response.allowedMentions }),\n ...(response.files && { files: response.files })\n };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport { checkAckLegality, sendTarget } from './ackLegality';\nimport { AckTrace } from './AckTrace';\nimport { attemptWrite, publishResponse } from './responseReport';\nimport { serializeReply } from './serializeReply';\nimport { translateSerializationError } from './translateSerialization';\n\nimport type { ResponseOutcome } from '#subscribers/types/Subscriptions';\nimport type { AckState, ReplyMethod } from './ackLegality';\nimport type { ReplyTelemetry } from './responseReport';\nimport type { SerializedReply } from './serializeReply';\nimport type { DeferOpts, ReplyResponse, SendOpts } from '@seedcord/types';\nimport type { APIModalInteractionResponseCallbackData } from 'discord-api-types/v10';\n\n/** The shape djs modal builders emit from `toJSON()`. */\nexport interface ModalLike {\n toJSON(): APIModalInteractionResponseCallbackData;\n}\n\n/**\n * Tracks acknowledgement state and throws a translated `SeedcordError` on an illegal verb before any\n * transport call. Every verb runs the legality check, calls the transport's wire writer, then updates\n * the state and the ack trace. Each transport binds `TMessage` to its own created-message type and\n * supplies the writers.\n */\nexport abstract class BaseReplySender<TMessage extends { id: string }, TNative = never> {\n private state: AckState;\n // the only ids a targeted edit accepts\n private readonly sent = new Set<string>();\n // the cause carries the first ack's stack so a double-ack reports both lines\n private ackedBy?: AckTrace;\n\n protected constructor(\n protected readonly routeId: string,\n private readonly telemetry: ReplyTelemetry,\n initialState: AckState = 'unacked'\n ) {\n this.state = initialState;\n }\n\n private report(method: ReplyMethod, startedAt: number, outcome: ResponseOutcome, messageId: string | null): void {\n publishResponse(this.telemetry, { routeId: this.routeId, method, startedAt, outcome, messageId });\n }\n\n private async attempt<Result>(\n method: ReplyMethod,\n startedAt: number,\n write: () => Promise<Result>\n ): Promise<Result> {\n return await attemptWrite(this.telemetry, this.routeId, method, startedAt, write);\n }\n\n public async reply(response: ReplyResponse<TNative> | string, opts?: SendOpts): Promise<TMessage> {\n this.checkLegality('reply');\n const startedAt = performance.now();\n const created = await this.attempt('reply', startedAt, () => this.writeReply(response, opts));\n // the callback already acked discord, so record replied before the message guard can throw\n this.transition('reply', 'replied');\n const message = this.remember(this.requireMessage(created, 'reply'));\n this.report('reply', startedAt, 'sent', message.id);\n return message;\n }\n\n public async defer(opts?: DeferOpts): Promise<void> {\n this.checkLegality('defer');\n const startedAt = performance.now();\n await this.attempt('defer', startedAt, () => this.writeDefer(opts));\n this.transition('defer', 'deferred-reply');\n this.report('defer', startedAt, 'sent', null);\n }\n\n public async deferUpdate(): Promise<void> {\n this.checkLegality('deferUpdate');\n const startedAt = performance.now();\n await this.attempt('deferUpdate', startedAt, () => this.writeDeferUpdate());\n this.transition('deferUpdate', 'deferred-update');\n this.report('deferUpdate', startedAt, 'sent', null);\n }\n\n /**\n * After a deferUpdate the state stays deferred-update. Calling this again rewrites. The returned message\n * is the source message, editable only through a bare `update` or `edit`. A targeted `edit` or `delete`\n * of it throws the foreign-target error.\n */\n public async update(response: ReplyResponse<TNative> | string): Promise<TMessage> {\n this.checkLegality('update');\n const startedAt = performance.now();\n // in deferred-update the source message is @original and the ack-legality check above already passed\n if (this.state === 'deferred-update') {\n const edited = await this.attempt('update', startedAt, () => this.editOriginal(response));\n this.report('update', startedAt, 'sent', edited.id);\n return edited;\n }\n const created = await this.attempt('update', startedAt, () => this.writeUpdate(response));\n this.transition('update', 'replied');\n const message = this.remember(this.requireMessage(created, 'update'));\n this.report('update', startedAt, 'sent', message.id);\n return message;\n }\n\n public async followUp(response: ReplyResponse<TNative> | string, opts?: SendOpts): Promise<TMessage> {\n this.checkLegality('followUp');\n const startedAt = performance.now();\n const created = await this.attempt('followUp', startedAt, () => this.writeFollowUp(response, opts));\n const message = this.remember(created);\n this.report('followUp', startedAt, 'sent', message.id);\n return message;\n }\n\n public edit(response: ReplyResponse<TNative> | string): Promise<TMessage>;\n public edit(target: TMessage, response: ReplyResponse<TNative> | string): Promise<TMessage>;\n public async edit(\n targetOrResponse: TMessage | ReplyResponse<TNative> | string,\n maybeResponse?: ReplyResponse<TNative> | string\n ): Promise<TMessage> {\n this.checkLegality('edit');\n const startedAt = performance.now();\n if (maybeResponse === undefined) {\n // justified: the overloads narrow targetOrResponse to a response once maybeResponse is absent\n const edited = await this.attempt('edit', startedAt, () =>\n this.editOriginal(targetOrResponse as ReplyResponse<TNative> | string)\n );\n this.report('edit', startedAt, 'sent', edited.id);\n return edited;\n }\n // justified: the overloads narrow targetOrResponse to a target message once maybeResponse is defined\n const target = targetOrResponse as TMessage;\n if (!this.sent.has(target.id)) {\n throw new SeedcordError(SeedcordErrorCode.ReplyForeignEditTarget, ['edit', target.id, this.routeId]);\n }\n const written = await this.attempt('edit', startedAt, () => this.writeEditTarget(target.id, maybeResponse));\n const message = this.remember(written);\n this.report('edit', startedAt, 'sent', message.id);\n return message;\n }\n\n public delete(): Promise<void>;\n public delete(target: TMessage): Promise<void>;\n public async delete(target?: TMessage): Promise<void> {\n this.checkLegality('delete');\n const startedAt = performance.now();\n if (target === undefined) {\n await this.attempt('delete', startedAt, () => this.writeDeleteOriginal());\n this.report('delete', startedAt, 'sent', null);\n return;\n }\n if (!this.sent.has(target.id)) {\n throw new SeedcordError(SeedcordErrorCode.ReplyForeignEditTarget, ['delete', target.id, this.routeId]);\n }\n await this.attempt('delete', startedAt, () => this.writeDeleteTarget(target.id));\n // evict so a later targeted edit of this id throws foreign\n this.sent.delete(target.id);\n this.report('delete', startedAt, 'sent', null);\n }\n\n /** Routes to the verb the current ack state permits. Every state has a route, so the illegal-ack throw is unreachable. */\n public async send(response: ReplyResponse<TNative> | string, opts?: SendOpts): Promise<TMessage> {\n const target = sendTarget(this.state);\n if (target === 'reply') return await this.reply(response, opts);\n if (target === 'edit') return await this.edit(response);\n return await this.followUp(response, opts);\n }\n\n /** Must be the initial response. */\n public async showModal(modal: ModalLike): Promise<void> {\n this.checkLegality('showModal');\n this.guardModalSource();\n let data: APIModalInteractionResponseCallbackData;\n try {\n data = modal.toJSON();\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- a null-prototype modal has no constructor at runtime\n const name = modal.constructor?.name;\n throw translateSerializationError(error, !name || name === 'Object' ? 'modal' : name, 0, this.routeId);\n }\n const startedAt = performance.now();\n await this.attempt('showModal', startedAt, () => this.writeModal(data));\n this.transition('showModal', 'replied');\n this.report('showModal', startedAt, 'sent', null);\n }\n\n protected serialize(response: ReplyResponse<TNative> | string): SerializedReply<TNative> {\n return serializeReply(response, this.routeId);\n }\n\n protected remember(created: TMessage): TMessage {\n this.sent.add(created.id);\n return created;\n }\n\n // a with_response callback always returns the created message. this only trips if the wire says otherwise\n private requireMessage(created: TMessage | undefined, method: 'reply' | 'update'): TMessage {\n if (!created) throw new SeedcordError(SeedcordErrorCode.ReplyCallbackMissingMessage, [method, this.routeId]);\n return created;\n }\n\n private async editOriginal(response: ReplyResponse<TNative> | string): Promise<TMessage> {\n const edited = await this.writeEditOriginal(response);\n // after a deferUpdate the source message is @original, which this interaction did not send\n if (this.state !== 'deferred-update') this.remember(edited);\n return edited;\n }\n\n private checkLegality(method: ReplyMethod): void {\n checkAckLegality(method, this.state, this.routeId, this.ackedBy);\n }\n\n private transition(method: ReplyMethod, state: AckState): void {\n this.state = state;\n this.ackedBy = new AckTrace(method);\n }\n\n // gateway overrides this to reject a modal-submit source\n protected guardModalSource(): void {}\n\n protected abstract writeReply(\n response: ReplyResponse<TNative> | string,\n opts?: SendOpts\n ): Promise<TMessage | undefined>;\n protected abstract writeDefer(opts?: DeferOpts): Promise<void>;\n protected abstract writeDeferUpdate(): Promise<void>;\n protected abstract writeUpdate(response: ReplyResponse<TNative> | string): Promise<TMessage | undefined>;\n protected abstract writeFollowUp(response: ReplyResponse<TNative> | string, opts?: SendOpts): Promise<TMessage>;\n protected abstract writeEditOriginal(response: ReplyResponse<TNative> | string): Promise<TMessage>;\n protected abstract writeEditTarget(targetId: string, response: ReplyResponse<TNative> | string): Promise<TMessage>;\n protected abstract writeDeleteOriginal(): Promise<void>;\n protected abstract writeDeleteTarget(targetId: string): Promise<void>;\n protected abstract writeModal(data: APIModalInteractionResponseCallbackData): Promise<void>;\n}\n","import { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { timestampFromSnowflake } from '@seedcord/utils';\n\nimport { Notice } from '#stops/Notice';\nimport { Silence } from '#stops/Silence';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { InteractionRoutes } from '#src/metadataKeys';\nimport type { Bus } from '#subscribers/Bus';\nimport type { DispatchOutcome } from '#subscribers/types/Subscriptions';\n\nlet dispatchLogger: Logger | undefined;\nfunction logger(): Logger {\n dispatchLogger ??= new Logger('Dispatch', { channel: 'interactions' });\n return dispatchLogger;\n}\n\n/**\n * Classifies a caught stop for `interactionDispatched` so a gate and a\n * handler label the same stop identically.\n *\n * @internal\n */\nexport function outcomeFor(caught: unknown): DispatchOutcome {\n if (caught instanceof Silence) return 'refused';\n if (caught instanceof Notice) return caught.report ? 'failed' : 'refused';\n return 'failed';\n}\n\n/** The values a dispatcher has once its handler chain settles. */\ninterface DispatchReport {\n readonly routeId: string;\n readonly interactionId: string;\n readonly kind: `${InteractionRoutes}`;\n readonly outcome: DispatchOutcome;\n /** True when no route matched and the unhandled default ran. */\n readonly fallback: boolean;\n /** `performance.now()` captured as the dispatch began. */\n readonly startedAt: number;\n /** {@link queuedMsFor} read at the same moment as `startedAt`. */\n readonly queuedMs: number;\n}\n\n/**\n * Discord's interaction creation to now, read from the snowflake. Called at dispatch entry, since\n * a later read would count the handler run into the queue time as well.\n *\n * @internal\n */\nexport function queuedMsFor(interactionId: string): number {\n // telemetry never breaks a dispatch, and BigInt() throws on a malformed id\n try {\n return Date.now() - timestampFromSnowflake(interactionId);\n } catch {\n return 0;\n }\n}\n\nexport function reportDispatch(bus: Bus, report: DispatchReport): void {\n const durationMs = performance.now() - report.startedAt;\n bus[PublishDefault]('interactionDispatched', {\n routeId: report.routeId,\n interactionId: report.interactionId,\n kind: report.kind,\n outcome: report.outcome,\n fallback: report.fallback,\n durationMs,\n queuedMs: report.queuedMs\n });\n\n // after publish so a throwing sink doesn't reach the publish above\n logger().trace(\n `${paint.sky.bold(report.routeId)} ${report.outcome} ${paint.mute('in')} ${Math.round(durationMs)}ms`\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAGA,MAAa,kBAAiC,OAAO,2BAA2B;AAChF,MAAa,yBAAwC,OAAO,kCAAkC;AAC9F,MAAa,uBAAsC,OAAO,gCAAgC;AAC1F,MAAa,wBAAuC,OAAO,iCAAiC;AAC5F,MAAa,qBAAoC,OAAO,8BAA8B;AACtF,MAAa,qBAAoC,OAAO,8BAA8B;;;;ACItF,SAAgB,UAAU,aAAoC;CAC1D,KAAK,MAAM,CAAC,MAAM,QAAQ,OAAO,QAAQ,oBAAoB,GAAG;EAE5D,MAAM,SAAS,QAAQ,YAAY,KAAK,WAAW;EACnD,IAAI,QAAQ,QAAQ,OAAO,GAAG,KAAK,GAAG,OAAO,KAAK,GAAG;CACzD;CACA,OAAO;AACX;AAEA,eAAe,WAAW,MAA6B,KAAsB,SAAsC;CAC/G,MAAM,QAAQ,YAAY,IAAI;CAE9B,IAAI;EACA,MAAM,SAAS,MAAM,GAAG;CAC5B,UAAU;EACN,QAAQ,KAAK,MAAM,YAAY,IAAI,IAAI,KAAK;CAChD;AACJ;AAIA,eAAsB,SAClB,OACA,KACA,SACa;CACb,IAAI;EACA,KAAK,MAAM,QAAQ,OACf,OAAO,UAAU,WAAW,MAAM,KAAK,OAAO,IAAI,SAAS,MAAM,GAAG;EAExE,MAAM,WAAW,GAAG;CACxB,UAAU;EACN,eAAe,GAAG;CACtB;AACJ;AAIA,eAAsB,gBAClB,aACA,KACA,SACA,SACa;CAEb,MAAM,QAAQ,QAAQ,YAAY,kBAAkB,WAAW;CAC/D,IAAI,CAAC,OAAO;CACZ,MAAM,SAAS,OAAO;EAAE,GAAG;EAAK,SAAS,WAAW,UAAU,WAAW;CAAE,GAAG,OAAO;AACzF;;;;ACrDA,MAAM,eAAe;AAGrB,IAAI;AACJ,SAASA,WAAiB;CACtB,eAAe,IAAI,OAAO,SAAS,EAAE,SAAS,QAAQ,CAAC;CACvD,OAAO;AACX;AAYA,SAAgB,kBAA+C;CAC3D,IAAI,SAAS,cAAc,OAAO;CAClC,MAAM,WAA2C,CAAC;CAClD,OAAO;EACH,UAAU,MAAM,OAAO,SAAS,KAAK;GAAE;GAAM;EAAG,CAAC;EACjD,OAAO,SAAS;GACZ,MAAM,QAAQ,SAAS,QAAQ,KAAK,YAAY,MAAM,QAAQ,IAAI,CAAC;GACnE,IAAI,QAAQ,cAAc;GAC1B,MAAM,SAAS,SACV,UAAU,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAC/B,KAAK,YAAY,GAAG,MAAM,IAAI,KAAK,QAAQ,IAAI,EAAE,GAAG,MAAM,KAAK,GAAG,KAAK,MAAM,QAAQ,EAAE,EAAE,GAAG,GAAG;GAEpG,MAAM,WAAW,QADH,YAAY,OAAO,KAAK,QAAQ,MAAM,IAAI,KAAK,OAAO,IACrC,QAAQ,MAAM,MAAM,GAAG,KAAK,MAAM,KAAK,EAAE,GAAG,EAAE;GAC7E,SAAO,CAAC,CAAC,MAAM,MAAM,UAAU,QAAQ,SAAS,SAAS,IAAI;EACjE;CACJ;AACJ;;;;ACzCA,SAAgB,aAAa,OAAoD;CAC7E,OAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,KAAK,OAAO,MAAM,OAAO,YAAY,OAAO,MAAM,OAAO;AAC7G;;;;;ACMA,SAAgB,QAAsB,KAAuC;CACzE,OAAO;EACH,OAAO,OAAO,SAAS,IAAI,KAAK,OAAO,IAAI;EAE3C,KAAK,OAAO,OAAO,IAAI,GAAG,OAAO,EAA6B;CAClE;AACJ;;;;ACHA,IAAa,aAAb,MAAwB;CACpB,AAAiB,SAAS,IAAI,OAAO,OAAO,EAAE,SAAS,MAAM,CAAC;CAC9D,AAAiB,4BAAY,IAAI,IAAc;;CAG/C,AAAO,OAAa;EAChB,IAAI,CAAC,YAAY,KAAK;EAEtB,MAAM,UAAU,QAAoD,YAAY,GAAG;EACnF,cAAc,OAAO;EAErB,IAAI,SAAS,iBAAiB,SAAS,QAAQ;GAC3C,KAAK,OAAO,MAAM,SAAS;GAC3B,QAAQ,GAAG,iBAAiB,YAAY,KAAK,KAAK,aAAa,OAAO,CAAC;EAC3E;CACJ;;CAGA,AAAO,SAAS,UAA0B;EACtC,KAAK,UAAU,IAAI,QAAQ;CAC/B;CAEA,MAAc,aAAa,OAAsC;EAC7D,MAAM,WAAW,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,IAAI,OAAO,aAAa;GACzD,IAAI;IACA,MAAM,SAAS,MAAM,KAAK;GAC9B,SAAS,OAAO;IACZ,KAAK,OAAO,MAAM,gCAAgC,MAAM,IAAI,KAAK,SAAS,YAAY,IAAI,KAAK,KAAK;GACxG;EACJ,CAAC;EAED,MAAM,QAAQ,IAAI,QAAQ;CAC9B;AACJ;;;;;AC3CA,SAAgB,QAAQ,QAAwB;CAC5C,OAAO,MAAM,QAAQ,MAAM,IAAI,SAAS,IAAI,MAAM,OAAO,MAAM,GAAG,EAAE,OAAO,OAAO,CAAC;AACvF;;;;ACYA,MAAM,kBAA2B;CAC7B,QAAQ;CACR,aAAa;AACjB;AAEA,SAAS,YAAY,MAAuB;CACxC,OAAO;EACH,QAAQ,kDAAkD;EAC1D,aAAa;CACjB;AACJ;AAGA,MAAM,UAAoF;CACtF,OAAO;EACH,kBAAkB;GACd,QAAQ;GACR,aAAa;EACjB;EACA,mBAAmB;GACf,QAAQ;GACR,aAAa;EACjB;EACA,SAAS;GACL,QAAQ;GACR,aAAa;EACjB;CACJ;CACA,OAAO;EACH,kBAAkB,YAAY,kBAAkB;EAChD,mBAAmB,YAAY,mBAAmB;EAClD,SAAS,YAAY,SAAS;CAClC;CACA,aAAa;EACT,kBAAkB,YAAY,kBAAkB;EAChD,mBAAmB,YAAY,mBAAmB;EAClD,SAAS,YAAY,SAAS;CAClC;CACA,QAAQ;EACJ,kBAAkB;GACd,QAAQ;GACR,aAAa;EACjB;EACA,SAAS;GACL,QAAQ;GACR,aAAa;EACjB;CACJ;CACA,UAAU,EACN,SAAS;EACL,QAAQ;EACR,aAAa;CACjB,EACJ;CACA,MAAM,EACF,SAAS;EACL,QAAQ;EACR,aAAa;CACjB,EACJ;CACA,QAAQ,EACJ,SAAS;EACL,QAAQ;EACR,aAAa;CACjB,EACJ;CACA,WAAW;EACP,kBAAkB;EAClB,mBAAmB;EACnB,SAAS;CACb;AACJ;AAEA,MAAM,cAA+D;CACjE,SAAS;CACT,kBAAkB;CAClB,mBAAmB;CACnB,SAAS;AACb;AAGA,SAAgB,iBAAiB,QAAqB,OAAiB,SAAiB,SAA0B;CAC9G,IAAI,WAAW,QAAQ;CACvB,MAAM,UAAU,QAAQ,OAAO,CAAC;CAChC,IAAI,CAAC,SAAS;CACd,MAAM,IAAI,cACN,kBAAkB,sBAClB;EAAC;EAAQ,QAAQ;EAAQ,QAAQ;EAAa;CAAO,GACrD,EAAE,OAAO,QAAQ,CACrB;AACJ;AAEA,SAAgB,WAAW,OAAgD;CACvE,OAAO,YAAY;AACvB;;;;AC3GA,IAAa,WAAb,cAA8B,MAAM;CAChC,AAAO,YAAY,QAAqB;EACpC,MAAM,GAAG,OAAO,iCAAiC;EACjD,KAAK,OAAO;CAChB;AACJ;;;;ACEA,IAAI;AACJ,SAASC,WAAiB;CACtB,gBAAgB,IAAI,OAAO,SAAS,EAAE,SAAS,eAAe,CAAC;CAC/D,OAAO;AACX;;AAqBA,SAAgB,gBAAgB,WAA2B,QAA8B;CACrF,MAAM,aAAa,YAAY,IAAI,IAAI,OAAO;CAC9C,UAAU,IAAI,eAAe,CAAC,qBAAqB;EAC/C,SAAS,OAAO;EAChB,eAAe,UAAU;EACzB,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB;EACA,WAAW,OAAO;EAClB,GAAI,OAAO,SAAS,EAAE,OAAO,OAAO,MAAM;CAC9C,CAAC;CAGD,SAAO,CAAC,CAAC,MACL,GAAG,MAAM,IAAI,KAAK,OAAO,OAAO,EAAE,GAAG,OAAO,OAAO,GAAG,OAAO,QAAQ,GAAG,MAAM,KAAK,IAAI,EAAE,GAAG,KAAK,MAAM,UAAU,EAAE,GACvH;AACJ;AAEA,eAAsB,cAClB,WACA,SACA,QACA,OACe;CACf,MAAM,YAAY,YAAY,IAAI;CAClC,MAAM,SAAS,MAAM,aAAa,WAAW,SAAS,QAAQ,WAAW,KAAK;CAC9E,gBAAgB,WAAW;EAAE;EAAS;EAAQ;EAAW,SAAS;EAAQ,WAAW;CAAK,CAAC;CAC3F,OAAO;AACX;AAGA,eAAsB,aAClB,WACA,SACA,QACA,WACA,OACe;CACf,IAAI;EACA,OAAO,MAAM,MAAM;CACvB,SAAS,QAAQ;EAEb,gBAAgB,WAAW;GAAE;GAAS;GAAQ;GAAW,SAAS;GAAU,WAAW;GAAM,OAD/E,QAAQ,MAC2E;EAAE,CAAC;EAEpG,MAAM;CACV;AACJ;;;;AC7EA,MAAM,YAAY;AAWlB,MAAM,gBAAwC;CAC1C,MAAM;CACN,MAAM;CACN,KAAK;CACL,KAAK;AACT;AAEA,MAAM,YAAoC;CACtC,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,OAAO;CACP,OAAO;CACP,MAAM;CACN,QAAQ;AACZ;AAEA,SAAS,SAAS,OAAuC;CACrD,OAAO,OAAO,UAAU,YAAY,UAAU,OAAO,QAAQ;AACjE;AAEA,SAAS,UAAU,SAA0B;CACzC,IAAI,OAAO,YAAY,UAAU,OAAO;CACxC,OAAO,QAAQ,MAAM,IAAI,CAAC,CAAC,MAAM;AACrC;AAGA,MAAM,sBAAsB;AAE5B,SAAS,UAAU,OAAwB;CACvC,IAAI,UAAU,QAAW,OAAO;CAChC,IAAI,UAAU,MAAM,OAAO;CAC3B,IAAI,OAAO,UAAU,UAAU;EAC3B,IAAI,MAAM,WAAW,GAAG,OAAO;EAC/B,IAAI,MAAM,SAAS,qBAAqB,OAAO,GAAG,MAAM,OAAO;EAC/D,OAAO,KAAK,UAAU,KAAK;CAC/B;CACA,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,WAAW,IAAI,kBAAkB,GAAG,MAAM,OAAO;CACxF,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW,OAAO,OAAO,KAAK;CAE7G,IAAI,OAAO,UAAU,UAAU,OAAO,MAAM,aAAa,QAAQ;CACjE,OAAO,OAAO;AAClB;AAGA,SAAS,WAAW,UAAkB,OAA+B;CACjE,MAAM,SAAS,CAAC,GAAG,SAAS,SAAS,4CAA4C,CAAC;CAClF,IAAI,OAAO,WAAW,GAAG,OAAO;CAOhC,OANgB,OAAO,KAAK,GAAG,YAAY,UAAU,WAAW;EAC5D,MAAM,OAAO,cAAc,YAAY,OAAO,YAAY;EAC1D,IAAI,CAAC,YAAY,OAAO,GAAG,KAAK,GAAG;EACnC,MAAM,OAAO,OAAO,UAAU,WAAW,cAAc;EACvD,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,KAAK,MAAM,IAAI,OAAO,GAAG,KAAK;CACpE,CACa,CAAC,CAAC,KAAK,OAAO;AAC/B;AAEA,SAAS,aAAa,UAA2B;CAC7C,IAAI,OAAO,aAAa,cAAc,SAAS,MAAM,OAAO,SAAS;CACrE,OAAO;AACX;AAEA,SAAS,aAAa,QAAgC;CAClD,IAAI,OAAO,OAAO,eAAe,UAAU;EACvC,MAAM,OAAO,OAAO,OAAO,aAAa,WAAW,WAAW,OAAO,UAAU,OAAO,KAAK,IAAI;EAC/F,IAAI,MAAM,OAAO,QAAQ,KAAK,QAAQ,UAAU,OAAO,KAAK;EAC5D,OAAO,GAAG,UAAU,OAAO,OAAO,IAAI,WAAW,SAAS,SAAS,UAAU,OAAO,KAAK,EAAE,KAAK;CACpG;CACA,IAAI,OAAO,OAAO,aAAa,YAC3B,OAAO,UAAU,aAAa,OAAO,QAAQ,EAAE,QAAQ,UAAU,OAAO,KAAK;CAEjF,IAAI,OAAO,OAAO,cAAc,UAAU;EACtC,MAAM,OAAO,UAAU,OAAO,UAAU,QAAQ,QAAQ,EAAE,CAAC,CAAC,QAAQ,SAAS,EAAE;EAC/E,IAAI,MAAM,OAAO,QAAQ,KAAK,QAAQ,UAAU,OAAO,KAAK;EAC5D,OAAO,GAAG,UAAU,OAAO,OAAO,EAAE,QAAQ,UAAU,OAAO,KAAK;CACtE;CACA,OAAO,UAAU,OAAO,OAAO;AACnC;AAEA,SAAS,cAAc,UAAqB,OAAuB;CAC/D,MAAM,UAA4B,CAAC;CACnC,KAAK,MAAM,UAAU,UAAU;EAC3B,MAAM,SAAS,SAAS,MAAM;EAC9B,IAAI,QAAQ,QAAQ,KAAK,MAAM;CACnC;CACA,MAAM,UAAU,QAAQ,MAAM,WAAW,OAAO,OAAO,eAAe,QAAQ;CAC9E,IAAI,SAAS,OAAO,aAAa,OAAO;CAExC,MAAM,QAAkB,CAAC;CACzB,IAAI,eAAe,QAAQ,SAAS;CACpC,KAAK,MAAM,UAAU,SACjB,IAAI,OAAO,OAAO,aAAa,YAAY,MAAM,KAAK,aAAa,OAAO,QAAQ,CAAC;MAC9E,eAAe;CAExB,IAAI,cACA,OAAO,UAAU,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,QAAQ,UAAU,QAAQ,EAAE,EAAE,KAAK;CAEzF,OAAO,SAAS,SAAS,IAAI,QAAQ,CAAC;AAC1C;AAGA,SAAS,SAAS,OAAgB,OAAuB;CACrD,MAAM,SAAS,SAAS,KAAK;CAC7B,IAAI,CAAC,UAAU,SAAS,WAAW,OAAO,UAAU,QAAQ,OAAO;CAEnE,IAAI,MAAM,QAAQ,OAAO,MAAM,KAAK,OAAO,OAAO,SAAS,GAAG;EAC1D,MAAM,SAAS,OAAO;EACtB,MAAM,CAAC,SAAS;EAChB,IAAI,MAAM,QAAQ,KAAK,GAAG;GACtB,MAAM,CAAC,KAAK,SAAS;GACrB,OAAO,GAAG,OAAO,GAAG,EAAE,IAAI,SAAS,OAAO,QAAQ,CAAC;EACvD;EACA,OAAO,cAAc,QAAQ,KAAK;CACtC;CAEA,OAAO,aAAa,MAAM;AAC9B;AAGA,SAAgB,4BACZ,OACA,gBACA,OACA,SAC4D;CAC5D,MAAM,SAAS,SAAS,OAAO,CAAC;CAChC,OAAO,IAAI,cAAc,kBAAkB,6BAA6B;EAAC;EAAgB;EAAO;EAAQ;CAAO,GAAG,EAC9G,OAAO,MACX,CAAC;AACL;;;;ACpIA,SAAgB,eACZ,UACA,SACwB;CACxB,IAAI,OAAO,aAAa,UACpB,IAAI;EACA,OAAO,EAAE,YAAY,CAAC,IAAI,mBAAmB,CAAC,CAAC,WAAW,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE;CAClF,SAAS,OAAO;EACZ,MAAM,4BAA4B,OAAO,sBAAsB,GAAG,OAAO;CAC7E;CAaJ,OAAO;EACH,YAXe,SAAS,WAAW,KAAK,WAAW,UAAU;GAC7D,IAAI;IACA,OAAO,UAAU,OAAO;GAC5B,SAAS,OAAO;IAEZ,MAAM,OAAO,UAAU,aAAa;IACpC,MAAM,4BAA4B,OAAO,CAAC,QAAQ,SAAS,WAAW,cAAc,MAAM,OAAO,OAAO;GAC5G;EACJ,CAGa;EACT,GAAI,SAAS,mBAAmB,EAAE,iBAAiB,SAAS,gBAAgB;EAC5E,GAAI,SAAS,SAAS,EAAE,OAAO,SAAS,MAAM;CAClD;AACJ;;;;;;;;;;ACbA,IAAsB,kBAAtB,MAAwF;CAQ7D;CACF;CARrB,AAAQ;CAER,AAAiB,uBAAO,IAAI,IAAY;CAExC,AAAQ;CAER,AAAU,YACN,AAAmB,SACnB,AAAiB,WACjB,eAAyB,WAC3B;EAHqB;EACF;EAGjB,KAAK,QAAQ;CACjB;CAEA,AAAQ,OAAO,QAAqB,WAAmB,SAA0B,WAAgC;EAC7G,gBAAgB,KAAK,WAAW;GAAE,SAAS,KAAK;GAAS;GAAQ;GAAW;GAAS;EAAU,CAAC;CACpG;CAEA,MAAc,QACV,QACA,WACA,OACe;EACf,OAAO,MAAM,aAAa,KAAK,WAAW,KAAK,SAAS,QAAQ,WAAW,KAAK;CACpF;CAEA,MAAa,MAAM,UAA2C,MAAoC;EAC9F,KAAK,cAAc,OAAO;EAC1B,MAAM,YAAY,YAAY,IAAI;EAClC,MAAM,UAAU,MAAM,KAAK,QAAQ,SAAS,iBAAiB,KAAK,WAAW,UAAU,IAAI,CAAC;EAE5F,KAAK,WAAW,SAAS,SAAS;EAClC,MAAM,UAAU,KAAK,SAAS,KAAK,eAAe,SAAS,OAAO,CAAC;EACnE,KAAK,OAAO,SAAS,WAAW,QAAQ,QAAQ,EAAE;EAClD,OAAO;CACX;CAEA,MAAa,MAAM,MAAiC;EAChD,KAAK,cAAc,OAAO;EAC1B,MAAM,YAAY,YAAY,IAAI;EAClC,MAAM,KAAK,QAAQ,SAAS,iBAAiB,KAAK,WAAW,IAAI,CAAC;EAClE,KAAK,WAAW,SAAS,gBAAgB;EACzC,KAAK,OAAO,SAAS,WAAW,QAAQ,IAAI;CAChD;CAEA,MAAa,cAA6B;EACtC,KAAK,cAAc,aAAa;EAChC,MAAM,YAAY,YAAY,IAAI;EAClC,MAAM,KAAK,QAAQ,eAAe,iBAAiB,KAAK,iBAAiB,CAAC;EAC1E,KAAK,WAAW,eAAe,iBAAiB;EAChD,KAAK,OAAO,eAAe,WAAW,QAAQ,IAAI;CACtD;;;;;;CAOA,MAAa,OAAO,UAA8D;EAC9E,KAAK,cAAc,QAAQ;EAC3B,MAAM,YAAY,YAAY,IAAI;EAElC,IAAI,KAAK,UAAU,mBAAmB;GAClC,MAAM,SAAS,MAAM,KAAK,QAAQ,UAAU,iBAAiB,KAAK,aAAa,QAAQ,CAAC;GACxF,KAAK,OAAO,UAAU,WAAW,QAAQ,OAAO,EAAE;GAClD,OAAO;EACX;EACA,MAAM,UAAU,MAAM,KAAK,QAAQ,UAAU,iBAAiB,KAAK,YAAY,QAAQ,CAAC;EACxF,KAAK,WAAW,UAAU,SAAS;EACnC,MAAM,UAAU,KAAK,SAAS,KAAK,eAAe,SAAS,QAAQ,CAAC;EACpE,KAAK,OAAO,UAAU,WAAW,QAAQ,QAAQ,EAAE;EACnD,OAAO;CACX;CAEA,MAAa,SAAS,UAA2C,MAAoC;EACjG,KAAK,cAAc,UAAU;EAC7B,MAAM,YAAY,YAAY,IAAI;EAClC,MAAM,UAAU,MAAM,KAAK,QAAQ,YAAY,iBAAiB,KAAK,cAAc,UAAU,IAAI,CAAC;EAClG,MAAM,UAAU,KAAK,SAAS,OAAO;EACrC,KAAK,OAAO,YAAY,WAAW,QAAQ,QAAQ,EAAE;EACrD,OAAO;CACX;CAIA,MAAa,KACT,kBACA,eACiB;EACjB,KAAK,cAAc,MAAM;EACzB,MAAM,YAAY,YAAY,IAAI;EAClC,IAAI,kBAAkB,QAAW;GAE7B,MAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,iBACtC,KAAK,aAAa,gBAAmD,CACzE;GACA,KAAK,OAAO,QAAQ,WAAW,QAAQ,OAAO,EAAE;GAChD,OAAO;EACX;EAEA,MAAM,SAAS;EACf,IAAI,CAAC,KAAK,KAAK,IAAI,OAAO,EAAE,GACxB,MAAM,IAAI,cAAc,kBAAkB,wBAAwB;GAAC;GAAQ,OAAO;GAAI,KAAK;EAAO,CAAC;EAEvG,MAAM,UAAU,MAAM,KAAK,QAAQ,QAAQ,iBAAiB,KAAK,gBAAgB,OAAO,IAAI,aAAa,CAAC;EAC1G,MAAM,UAAU,KAAK,SAAS,OAAO;EACrC,KAAK,OAAO,QAAQ,WAAW,QAAQ,QAAQ,EAAE;EACjD,OAAO;CACX;CAIA,MAAa,OAAO,QAAkC;EAClD,KAAK,cAAc,QAAQ;EAC3B,MAAM,YAAY,YAAY,IAAI;EAClC,IAAI,WAAW,QAAW;GACtB,MAAM,KAAK,QAAQ,UAAU,iBAAiB,KAAK,oBAAoB,CAAC;GACxE,KAAK,OAAO,UAAU,WAAW,QAAQ,IAAI;GAC7C;EACJ;EACA,IAAI,CAAC,KAAK,KAAK,IAAI,OAAO,EAAE,GACxB,MAAM,IAAI,cAAc,kBAAkB,wBAAwB;GAAC;GAAU,OAAO;GAAI,KAAK;EAAO,CAAC;EAEzG,MAAM,KAAK,QAAQ,UAAU,iBAAiB,KAAK,kBAAkB,OAAO,EAAE,CAAC;EAE/E,KAAK,KAAK,OAAO,OAAO,EAAE;EAC1B,KAAK,OAAO,UAAU,WAAW,QAAQ,IAAI;CACjD;;CAGA,MAAa,KAAK,UAA2C,MAAoC;EAC7F,MAAM,SAAS,WAAW,KAAK,KAAK;EACpC,IAAI,WAAW,SAAS,OAAO,MAAM,KAAK,MAAM,UAAU,IAAI;EAC9D,IAAI,WAAW,QAAQ,OAAO,MAAM,KAAK,KAAK,QAAQ;EACtD,OAAO,MAAM,KAAK,SAAS,UAAU,IAAI;CAC7C;;CAGA,MAAa,UAAU,OAAiC;EACpD,KAAK,cAAc,WAAW;EAC9B,KAAK,iBAAiB;EACtB,IAAI;EACJ,IAAI;GACA,OAAO,MAAM,OAAO;EACxB,SAAS,OAAO;GAEZ,MAAM,OAAO,MAAM,aAAa;GAChC,MAAM,4BAA4B,OAAO,CAAC,QAAQ,SAAS,WAAW,UAAU,MAAM,GAAG,KAAK,OAAO;EACzG;EACA,MAAM,YAAY,YAAY,IAAI;EAClC,MAAM,KAAK,QAAQ,aAAa,iBAAiB,KAAK,WAAW,IAAI,CAAC;EACtE,KAAK,WAAW,aAAa,SAAS;EACtC,KAAK,OAAO,aAAa,WAAW,QAAQ,IAAI;CACpD;CAEA,AAAU,UAAU,UAAqE;EACrF,OAAO,eAAe,UAAU,KAAK,OAAO;CAChD;CAEA,AAAU,SAAS,SAA6B;EAC5C,KAAK,KAAK,IAAI,QAAQ,EAAE;EACxB,OAAO;CACX;CAGA,AAAQ,eAAe,SAA+B,QAAsC;EACxF,IAAI,CAAC,SAAS,MAAM,IAAI,cAAc,kBAAkB,6BAA6B,CAAC,QAAQ,KAAK,OAAO,CAAC;EAC3G,OAAO;CACX;CAEA,MAAc,aAAa,UAA8D;EACrF,MAAM,SAAS,MAAM,KAAK,kBAAkB,QAAQ;EAEpD,IAAI,KAAK,UAAU,mBAAmB,KAAK,SAAS,MAAM;EAC1D,OAAO;CACX;CAEA,AAAQ,cAAc,QAA2B;EAC7C,iBAAiB,QAAQ,KAAK,OAAO,KAAK,SAAS,KAAK,OAAO;CACnE;CAEA,AAAQ,WAAW,QAAqB,OAAuB;EAC3D,KAAK,QAAQ;EACb,KAAK,UAAU,IAAI,SAAS,MAAM;CACtC;CAGA,AAAU,mBAAyB,CAAC;AAexC;;;;AC1NA,IAAI;AACJ,SAAS,SAAiB;CACtB,mBAAmB,IAAI,OAAO,YAAY,EAAE,SAAS,eAAe,CAAC;CACrE,OAAO;AACX;;;;;;;AAQA,SAAgB,WAAW,QAAkC;CACzD,IAAI,kBAAkB,SAAS,OAAO;CACtC,IAAI,kBAAkB,QAAQ,OAAO,OAAO,SAAS,WAAW;CAChE,OAAO;AACX;;;;;;;AAsBA,SAAgB,YAAY,eAA+B;CAEvD,IAAI;EACA,OAAO,KAAK,IAAI,IAAI,uBAAuB,aAAa;CAC5D,QAAQ;EACJ,OAAO;CACX;AACJ;AAEA,SAAgB,eAAe,KAAU,QAA8B;CACnE,MAAM,aAAa,YAAY,IAAI,IAAI,OAAO;CAC9C,IAAI,eAAe,CAAC,yBAAyB;EACzC,SAAS,OAAO;EAChB,eAAe,OAAO;EACtB,MAAM,OAAO;EACb,SAAS,OAAO;EAChB,UAAU,OAAO;EACjB;EACA,UAAU,OAAO;CACrB,CAAC;CAGD,OAAO,CAAC,CAAC,MACL,GAAG,MAAM,IAAI,KAAK,OAAO,OAAO,EAAE,GAAG,OAAO,QAAQ,GAAG,MAAM,KAAK,IAAI,EAAE,GAAG,KAAK,MAAM,UAAU,EAAE,GACtG;AACJ"}
@@ -1,6 +1,7 @@
1
1
  import { n as CommandBuilder, r as DeployResult } from "./basic-Dng__Ena.mjs";
2
- import { _ as Bus, a as PluginCtor, d as Transport, f as TransportAssert, g as CoreBase, h as StartupPhase, i as PluginArgs, l as Runtime, m as ShutdownPhase, n as Initializeable, o as PluginLike, s as ChannelKeyAssert, t as CoreParamAssert, u as RuntimeAssert } from "./Plugin-B6iRzm3g.mjs";
3
- import "./index-uBwh8r7x.mjs";
2
+ import { i as Bus, n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-hBxMCD2V.mjs";
3
+ import "./index-rt8rZg4d.mjs";
4
+ import { a as PluginCtor, d as Transport, f as TransportAssert, i as PluginArgs, l as Runtime, n as Initializeable, o as PluginLike, s as ChannelKeyAssert, t as CoreParamAssert, u as RuntimeAssert } from "./Plugin-CQ9IEj5X.mjs";
4
5
  import { t as ContextMenuLeaves } from "./contextMenuLeaves-COzDYa6c.mjs";
5
6
  import { Logger } from "@seedcord/logger";
6
7
  import { REST } from "@discordjs/rest";
@@ -97,12 +98,13 @@ declare class HealthResponder {
97
98
  /**
98
99
  * Base class for a plugin host, a transport `Seedcord` class.
99
100
  *
100
- * Plugins are attached during configuration and initialized during startup, sequentially in attach
101
- * order within each phase.
101
+ * You attach plugins while configuring the bot. Within one startup phase, their `init()` calls run
102
+ * one after another in attach order.
102
103
  */
103
104
  declare abstract class Pluggable<BotT extends Transport, BotRt extends Runtime> implements CoreBase {
104
105
  abstract readonly config: Config;
105
106
  abstract readonly rest: REST;
107
+ abstract readonly applicationId: string;
106
108
  abstract readonly rateLimiter: IRateLimiter;
107
109
  abstract readonly bus: Bus;
108
110
  /** @see {@link CoordinatedShutdown} */
@@ -127,26 +129,21 @@ declare abstract class Pluggable<BotT extends Transport, BotRt extends Runtime>
127
129
  /** @internal so the next host can construct */
128
130
  protected static reset(): void;
129
131
  /**
130
- * Attaches a plugin to this instance.
132
+ * Attaches a plugin under `key`. Read the instance back as `core[key]`. `seedcord codegen`
133
+ * writes the `Core` augmentation that types it there.
131
134
  *
132
- * The plugin initializes during startup, after every plugin attached before it, and becomes
133
- * available as a property on `core`. `seedcord codegen` writes the `Core` augmentation that
134
- * types it.
135
+ * Startup runs each plugin's `init()` in attach order within its phase.
135
136
  *
136
- * A plugin declaring a `transport` or `runtime` this host does not run is a compile error on
137
- * this call. An edge host takes no plugins at all. A plugin constructor narrowing its first
138
- * parameter past `CoreBase` is a compile error here too. Read the transport `Core` off `this.core`.
137
+ * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.
138
+ * Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).
139
+ * A narrower one fails to compile here.
139
140
  *
140
- * @typeParam Key - The property name for accessing the plugin
141
- * @typeParam Ctor - The plugin constructor type
142
- * @param key - Property name to access the plugin instance
143
- * @param Plugin - Plugin constructor class
144
- * @param args - Additional arguments to pass to the plugin constructor
145
- * @returns This instance with the plugin attached as a typed property
146
- * @throws A **SeedcordError** When called after initialization or if key already exists or is reserved
141
+ * @param key - Also the channel the plugin logs on. Reserved channel names throw.
142
+ * @param args - Whatever your constructor takes after the host.
143
+ * @throws A **SeedcordError** if you attach after the bot has started. A taken or reserved key throws too.
147
144
  * @example
148
- * ```typescript
149
- * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... })
145
+ * ```ts
146
+ * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... });
150
147
  * ```
151
148
  */
152
149
  attach<Key extends string, Ctor extends PluginCtor>(this: this, key: ChannelKeyAssert<Key>, Plugin: Ctor & TransportAssert<InstanceType<Ctor>, BotT> & RuntimeAssert<InstanceType<Ctor>, BotRt> & CoreParamAssert<Ctor>, ...args: PluginArgs<Ctor>): this & Record<Key, InstanceType<Ctor>>;
@@ -181,21 +178,13 @@ declare class SubscriberLoader implements Initializeable, HmrAware {
181
178
  }
182
179
  //#endregion
183
180
  //#region src/node/commands/CommandRegistry.d.ts
184
- /** @internal */
185
- interface CommandRegistryOptions {
186
- readonly dir: string;
187
- readonly rest: REST;
188
- /** Called at deploy time, because the application id only resolves after login. */
189
- readonly applicationId: () => string;
190
- readonly onDeployed?: (result: DeployResult) => void;
191
- }
192
181
  /**
193
182
  * Scans the command directory, builds each command, and PUTs the global and guild scopes to Discord.
194
183
  *
195
184
  * @internal
196
185
  */
197
186
  declare class CommandRegistry implements Initializeable, HmrAware {
198
- private readonly options;
187
+ private readonly core;
199
188
  private readonly logger;
200
189
  private isInitialised;
201
190
  readonly globalCommands: CommandBuilder[];
@@ -205,7 +194,9 @@ declare class CommandRegistry implements Initializeable, HmrAware {
205
194
  private readonly loadedCommands;
206
195
  private readonly hmrHandler?;
207
196
  private readonly pendingEvents;
208
- constructor(options: CommandRegistryOptions);
197
+ private readonly injector;
198
+ private readonly dir;
199
+ constructor(core: CoreBase);
209
200
  private getArtifacts;
210
201
  /** @internal */
211
202
  init(): Promise<void>;
@@ -229,5 +220,5 @@ declare class CommandRegistry implements Initializeable, HmrAware {
229
220
  contextMenuLeaves(): ContextMenuLeaves;
230
221
  }
231
222
  //#endregion
232
- export { CommandRegistry, type CommandRegistryOptions, CoordinatedShutdown, CoordinatedStartup, HealthCheck, HealthResponder, type LifecycleTask, Pluggable, ShutdownPhase, StartupPhase, SubscriberLoader, settleWithin };
223
+ export { CommandRegistry, CoordinatedShutdown, CoordinatedStartup, HealthCheck, HealthResponder, type LifecycleTask, Pluggable, ShutdownPhase, StartupPhase, SubscriberLoader, settleWithin };
233
224
  //# sourceMappingURL=node-internal.index.d.mts.map
@@ -1,8 +1,8 @@
1
- import { S as SubscribeMetadataKey, c as Subscriber, f as BuilderComponent, h as CommandMetadataKey, n as busLoggerOf, r as registrationFor, w as contextMenuLeaves } from "./Bus-DcuUtOEL.mjs";
1
+ import { D as contextMenuLeaves, S as SubscribeMetadataKey, c as Subscriber, f as BuilderComponent, h as CommandMetadataKey, i as PublishDefault, n as busLoggerOf, r as registrationFor } from "./Bus-BvF4VShN.mjs";
2
+ import { t as CommandInjector } from "./subscribers-wnMyNYZd.mjs";
2
3
  import { n as StartupPhase, t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
3
- import "./subscribers-DuHg-bGT.mjs";
4
4
  import { t as getDevChannel } from "./devChannel-CQU7hq7Q.mjs";
5
- import { n as pluginLoggerOf, r as resolvedLifecycleSpecOf } from "./Plugin-Bkeqcjy6.mjs";
5
+ import { n as pluginLoggerOf, r as resolvedLifecycleSpecOf } from "./Plugin-D9ykXA5g.mjs";
6
6
  import { t as slashRouteLeaves } from "./slashRouteLeaves-De1SxqnX.mjs";
7
7
  import { t as HmrModuleHandler } from "./HmrModuleHandler-9AUfKXRl.mjs";
8
8
  import { SlashCommandBuilder } from "@discordjs/builders";
@@ -16,6 +16,22 @@ import { resolve } from "node:path";
16
16
  import { createServer } from "node:http";
17
17
  import { traverseDirectory } from "@seedcord/utils/node";
18
18
 
19
+ //#region src/node/assertNodeVersion.ts
20
+ const RANGE = new RegExp(String.raw`^>=(\d+)(?:\.(\d+))?`);
21
+ const VERSION = /^v?(\d+)\.(\d+)/;
22
+ const SPACES = /\s/g;
23
+ /** @internal */
24
+ function assertNodeVersion(range, running) {
25
+ const required = RANGE.exec(range.replaceAll(SPACES, ""));
26
+ const current = VERSION.exec(running);
27
+ if (!required || !current) return;
28
+ const [, requiredMajor, requiredMinor = "0"] = required;
29
+ const [, currentMajor, currentMinor] = current;
30
+ if (Number(currentMajor) > Number(requiredMajor) || Number(currentMajor) === Number(requiredMajor) && Number(currentMinor) >= Number(requiredMinor)) return;
31
+ throw new SeedcordError(SeedcordErrorCode.UnsupportedNodeVersion, [range, running]);
32
+ }
33
+
34
+ //#endregion
19
35
  //#region src/node/Lifecycle/withTimeout.ts
20
36
  async function raceTimer(work, timeoutMs, onTimeout) {
21
37
  let timer;
@@ -375,8 +391,8 @@ const RESERVED_KEYS = new Set(FRAMEWORK_CHANNELS);
375
391
  /**
376
392
  * Base class for a plugin host, a transport `Seedcord` class.
377
393
  *
378
- * Plugins are attached during configuration and initialized during startup, sequentially in attach
379
- * order within each phase.
394
+ * You attach plugins while configuring the bot. Within one startup phase, their `init()` calls run
395
+ * one after another in attach order.
380
396
  */
381
397
  var Pluggable = class Pluggable {
382
398
  /** @see {@link CoordinatedShutdown} */
@@ -394,6 +410,7 @@ var Pluggable = class Pluggable {
394
410
  static isInstantiated = false;
395
411
  static liveShutdown;
396
412
  constructor(shutdown, startup) {
413
+ assertNodeVersion(">=24.11", process.version);
397
414
  if (Pluggable.isInstantiated) {
398
415
  shutdown.removeSignalHandlers();
399
416
  throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);
@@ -432,26 +449,21 @@ var Pluggable = class Pluggable {
432
449
  Pluggable.isInstantiated = false;
433
450
  }
434
451
  /**
435
- * Attaches a plugin to this instance.
452
+ * Attaches a plugin under `key`. Read the instance back as `core[key]`. `seedcord codegen`
453
+ * writes the `Core` augmentation that types it there.
436
454
  *
437
- * The plugin initializes during startup, after every plugin attached before it, and becomes
438
- * available as a property on `core`. `seedcord codegen` writes the `Core` augmentation that
439
- * types it.
455
+ * Startup runs each plugin's `init()` in attach order within its phase.
440
456
  *
441
- * A plugin declaring a `transport` or `runtime` this host does not run is a compile error on
442
- * this call. An edge host takes no plugins at all. A plugin constructor narrowing its first
443
- * parameter past `CoreBase` is a compile error here too. Read the transport `Core` off `this.core`.
457
+ * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.
458
+ * Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).
459
+ * A narrower one fails to compile here.
444
460
  *
445
- * @typeParam Key - The property name for accessing the plugin
446
- * @typeParam Ctor - The plugin constructor type
447
- * @param key - Property name to access the plugin instance
448
- * @param Plugin - Plugin constructor class
449
- * @param args - Additional arguments to pass to the plugin constructor
450
- * @returns This instance with the plugin attached as a typed property
451
- * @throws A **SeedcordError** When called after initialization or if key already exists or is reserved
461
+ * @param key - Also the channel the plugin logs on. Reserved channel names throw.
462
+ * @param args - Whatever your constructor takes after the host.
463
+ * @throws A **SeedcordError** if you attach after the bot has started. A taken or reserved key throws too.
452
464
  * @example
453
- * ```typescript
454
- * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... })
465
+ * ```ts
466
+ * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... });
455
467
  * ```
456
468
  */
457
469
  attach(key, Plugin, ...args) {
@@ -623,7 +635,7 @@ function indexById(commands) {
623
635
  * @internal
624
636
  */
625
637
  var CommandRegistry = class {
626
- options;
638
+ core;
627
639
  logger = new Logger("Commands", { channel: "commands" });
628
640
  isInitialised = false;
629
641
  globalCommands = [];
@@ -633,12 +645,16 @@ var CommandRegistry = class {
633
645
  loadedCommands = [];
634
646
  hmrHandler;
635
647
  pendingEvents = /* @__PURE__ */ new Map();
636
- constructor(options) {
637
- this.options = options;
638
- if (!options.dir) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["CommandRegistry", "commands"]);
648
+ injector = new CommandInjector();
649
+ dir;
650
+ constructor(core) {
651
+ this.core = core;
652
+ const dir = core.config.bot.commands.path;
653
+ if (!dir) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["CommandRegistry", "commands"]);
654
+ this.dir = dir;
639
655
  if (!Envapter.isDevelopment && !Envapter.isTest) return;
640
656
  this.hmrHandler = new HmrModuleHandler({
641
- handlersDir: options.dir,
657
+ handlersDir: dir,
642
658
  isHandler: this.isCommandClass.bind(this),
643
659
  registerHandler: this.registerCommand.bind(this),
644
660
  unregisterHandler: this.unregisterCommand.bind(this),
@@ -656,7 +672,7 @@ var CommandRegistry = class {
656
672
  this.loading = true;
657
673
  this.loadedCommands.length = 0;
658
674
  try {
659
- await this.loadCommands(this.options.dir);
675
+ await this.loadCommands(this.dir);
660
676
  } finally {
661
677
  this.loading = false;
662
678
  }
@@ -694,7 +710,7 @@ var CommandRegistry = class {
694
710
  }
695
711
  /** @internal */
696
712
  async onHmr(event) {
697
- if (event.file.startsWith(resolve(process.cwd(), this.options.dir))) {
713
+ if (event.file.startsWith(resolve(process.cwd(), this.dir))) {
698
714
  this.pendingEvents.delete(event.file);
699
715
  this.pendingEvents.set(event.file, event);
700
716
  getDevChannel()?.send("seedcord:commands-update-prompt", { files: [...this.pendingEvents.keys()].map((f) => formatFilePath(f)) });
@@ -755,7 +771,7 @@ var CommandRegistry = class {
755
771
  global: /* @__PURE__ */ new Map(),
756
772
  guilds: /* @__PURE__ */ new Map()
757
773
  };
758
- const appId = this.options.applicationId();
774
+ const appId = this.core.applicationId;
759
775
  if (this.globalCommands.length > 0) {
760
776
  result.global = indexById(await this.put(Routes.applicationCommands(appId), this.globalCommands));
761
777
  const tag = this.globalCommands.length === 1 ? "command" : "commands";
@@ -767,12 +783,14 @@ var CommandRegistry = class {
767
783
  const tag = commands.length === 1 ? "command" : "commands";
768
784
  this.logger.utils.block(`Deployed ${paint.iris.bold(commands.length)} ${tag} to ${paint.sky.bold(guildId)}`, this.logger.utils.wrap(commands.map((command) => command.name)), "info");
769
785
  }
770
- this.options.onDeployed?.(result);
786
+ this.injector.inject(result, this.allCommands());
787
+ this.core.bus[PublishDefault]("commandsDeployed", deployedPayload(result));
771
788
  return result;
772
789
  }
773
790
  async put(route, commands) {
774
791
  const body = commands.map((command) => command.toJSON());
775
- return await this.options.rest.put(route, { body });
792
+ const { rest } = this.core;
793
+ return await rest.put(route, { body });
776
794
  }
777
795
  /** Every registered builder, global first. A guild command in N guilds appears once per guild. @internal */
778
796
  allCommands() {
@@ -787,6 +805,18 @@ var CommandRegistry = class {
787
805
  return contextMenuLeaves(this.allCommands());
788
806
  }
789
807
  };
808
+ function deployedPayload(result) {
809
+ const guilds = {};
810
+ for (const [guildId, deployed] of result.guilds) guilds[guildId] = [...deployed.values()];
811
+ return {
812
+ global: [...result.global.values()],
813
+ guilds
814
+ };
815
+ }
816
+
817
+ //#endregion
818
+ //#region src/node-internal.index.ts
819
+ assertNodeVersion(">=24.11", process.version);
790
820
 
791
821
  //#endregion
792
822
  export { CommandRegistry, CoordinatedShutdown, CoordinatedStartup, HealthCheck, HealthResponder, Pluggable, ShutdownPhase, StartupPhase, SubscriberLoader, settleWithin };