@warlock.js/notifications 5.23.2 → 5.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/cjs/index.cjs +1 -1
- package/cjs/rabbitmq-driver-DvWaCPES.cjs +3 -0
- package/cjs/{src-DbAWktJm.cjs → src-BVlqtCR2.cjs} +22 -6
- package/cjs/src-BVlqtCR2.cjs.map +1 -0
- package/esm/herald/src/communicators/broker-registry.mjs.map +1 -1
- package/esm/herald/src/communicators/broker.mjs.map +1 -1
- package/esm/herald/src/drivers/rabbitmq/rabbitmq-channel.mjs.map +1 -1
- package/esm/herald/src/drivers/rabbitmq/rabbitmq-driver.mjs.map +1 -1
- package/esm/herald/src/message-managers/prepare-consumer-subscription.mjs.map +1 -1
- package/esm/herald/src/utils/connect-to-broker.mjs.map +1 -1
- package/esm/seal/src/config.mjs.map +1 -1
- package/esm/seal/src/factory/validate.mjs.map +1 -1
- package/esm/seal/src/factory/validators.mjs +1 -1
- package/esm/seal/src/factory/validators.mjs.map +1 -1
- package/esm/seal/src/helpers/date-helpers.mjs.map +1 -1
- package/esm/seal/src/helpers/get-field-value.mjs.map +1 -1
- package/esm/seal/src/helpers/path-helpers.mjs.map +1 -1
- package/esm/seal/src/mutators/array-mutators.mjs.map +1 -1
- package/esm/seal/src/mutators/date-mutators.mjs.map +1 -1
- package/esm/seal/src/mutators/number-mutators.mjs.map +1 -1
- package/esm/seal/src/mutators/object-mutators.mjs.map +1 -1
- package/esm/seal/src/mutators/scalar-mutators.mjs.map +1 -1
- package/esm/seal/src/mutators/string-mutators.mjs.map +1 -1
- package/esm/seal/src/rules/array/array-rules.mjs.map +1 -1
- package/esm/seal/src/rules/color/color-rules.mjs.map +1 -1
- package/esm/seal/src/rules/common/enum.mjs.map +1 -1
- package/esm/seal/src/rules/common/equals-field-rules.mjs.map +1 -1
- package/esm/seal/src/rules/common/type-rules.mjs.map +1 -1
- package/esm/seal/src/rules/conditional/forbidden-if-rules.mjs.map +1 -1
- package/esm/seal/src/rules/conditional/present-if-rules.mjs.map +1 -1
- package/esm/seal/src/rules/conditional/present-unless-rules.mjs.map +1 -1
- package/esm/seal/src/rules/conditional/present-with-rules.mjs.map +1 -1
- package/esm/seal/src/rules/conditional/present-without-rules.mjs.map +1 -1
- package/esm/seal/src/rules/conditional/required-if-rules.mjs.map +1 -1
- package/esm/seal/src/rules/conditional/required-unless-rules.mjs.map +1 -1
- package/esm/seal/src/rules/conditional/required-with-rules.mjs.map +1 -1
- package/esm/seal/src/rules/conditional/required-without-rules.mjs.map +1 -1
- package/esm/seal/src/rules/core/equal.mjs.map +1 -1
- package/esm/seal/src/rules/core/forbidden.mjs.map +1 -1
- package/esm/seal/src/rules/core/required.mjs.map +1 -1
- package/esm/seal/src/rules/core/union.mjs.map +1 -1
- package/esm/seal/src/rules/core/when.mjs.map +1 -1
- package/esm/seal/src/rules/date/date-comparison-rules.mjs.map +1 -1
- package/esm/seal/src/rules/date/date-day-rules.mjs.map +1 -1
- package/esm/seal/src/rules/date/date-field-comparison-rules.mjs.map +1 -1
- package/esm/seal/src/rules/date/date-period-rules.mjs.map +1 -1
- package/esm/seal/src/rules/date/date-relative-rules.mjs.map +1 -1
- package/esm/seal/src/rules/date/date-special-rules.mjs.map +1 -1
- package/esm/seal/src/rules/date/date.mjs.map +1 -1
- package/esm/seal/src/rules/length/length-rules.mjs.map +1 -1
- package/esm/seal/src/rules/number/number-rules.mjs.map +1 -1
- package/esm/seal/src/rules/scalar/accepted-rule.mjs.map +1 -1
- package/esm/seal/src/rules/scalar/declined-rule.mjs.map +1 -1
- package/esm/seal/src/rules/string/alpha.mjs.map +1 -1
- package/esm/seal/src/rules/string/credit-card.mjs.map +1 -1
- package/esm/seal/src/rules/string/email.mjs.map +1 -1
- package/esm/seal/src/rules/string/pattern.mjs.map +1 -1
- package/esm/seal/src/rules/string/string-comparison.mjs.map +1 -1
- package/esm/seal/src/rules/string/strong-password-rule.mjs.map +1 -1
- package/esm/seal/src/rules/string/url.mjs.map +1 -1
- package/esm/seal/src/rules/string/without-whitespace.mjs.map +1 -1
- package/esm/seal/src/types/date-types.mjs.map +1 -1
- package/esm/seal/src/validators/any-validator.mjs.map +1 -1
- package/esm/seal/src/validators/array-validator.mjs +1 -0
- package/esm/seal/src/validators/array-validator.mjs.map +1 -1
- package/esm/seal/src/validators/base-validator.mjs +12 -1
- package/esm/seal/src/validators/base-validator.mjs.map +1 -1
- package/esm/seal/src/validators/boolean-validator.mjs +1 -0
- package/esm/seal/src/validators/boolean-validator.mjs.map +1 -1
- package/esm/seal/src/validators/date-validator.mjs +1 -0
- package/esm/seal/src/validators/date-validator.mjs.map +1 -1
- package/esm/seal/src/validators/float-validator.mjs.map +1 -1
- package/esm/seal/src/validators/int-validator.mjs.map +1 -1
- package/esm/seal/src/validators/literal-validator.mjs +1 -3
- package/esm/seal/src/validators/literal-validator.mjs.map +1 -1
- package/esm/seal/src/validators/methods/equality-conditional-methods.mjs.map +1 -1
- package/esm/seal/src/validators/methods/forbidden-methods.mjs.map +1 -1
- package/esm/seal/src/validators/methods/present-methods.mjs.map +1 -1
- package/esm/seal/src/validators/methods/required-methods.mjs.map +1 -1
- package/esm/seal/src/validators/number-validator.mjs +1 -0
- package/esm/seal/src/validators/number-validator.mjs.map +1 -1
- package/esm/seal/src/validators/numeric-validator.mjs.map +1 -1
- package/esm/seal/src/validators/object-validator.mjs +1 -1
- package/esm/seal/src/validators/object-validator.mjs.map +1 -1
- package/esm/seal/src/validators/record-validator.mjs.map +1 -1
- package/esm/seal/src/validators/scalar-validator.mjs +1 -0
- package/esm/seal/src/validators/scalar-validator.mjs.map +1 -1
- package/esm/seal/src/validators/string-validator.mjs.map +1 -1
- package/esm/seal/src/validators/tuple-validator.mjs.map +1 -1
- package/esm/seal/src/validators/union-validator.mjs.map +1 -1
- package/llms-full.txt +891 -891
- package/package.json +7 -7
- package/skills/configure-notifications/SKILL.md +138 -138
- package/skills/define-channel/SKILL.md +107 -107
- package/skills/define-notification/SKILL.md +133 -133
- package/skills/observe-notifications/SKILL.md +66 -66
- package/skills/queue-notifications/SKILL.md +110 -110
- package/skills/send-ad-hoc/SKILL.md +95 -95
- package/skills/use-in-app/SKILL.md +172 -172
- package/skills/write-notification-migration/SKILL.md +66 -66
- package/cjs/rabbitmq-driver-C0o3Pz15.cjs +0 -3
- package/cjs/src-DbAWktJm.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broker-registry.mjs","names":[],"sources":["../../../../../../../../../herald/src/communicators/broker-registry.ts"],"sourcesContent":["import { EventEmitter } from \"node:events\";\
|
|
1
|
+
{"version":3,"file":"broker-registry.mjs","names":[],"sources":["../../../../../../../../../herald/src/communicators/broker-registry.ts"],"sourcesContent":["import { EventEmitter } from \"node:events\";\nimport type { BrokerRegistryEvent, BrokerRegistryListener } from \"../types\";\nimport { Broker, type BrokerOptions } from \"./broker\";\n\n/**\n * Error thrown when a broker is not found\n */\nexport class MissingBrokerError extends Error {\n public readonly brokerName?: string;\n\n public constructor(message: string, brokerName?: string) {\n super(message);\n this.name = \"MissingBrokerError\";\n this.brokerName = brokerName;\n }\n}\n\n/**\n * Broker Registry\n *\n * Maintains registry of named brokers.\n * Similar to DataSourceRegistry in @warlock.js/cascade\n *\n * @example\n * ```typescript\n * // Register a broker\n * brokerRegistry.register({\n * name: \"default\",\n * driver: rabbitMQDriver,\n * isDefault: true,\n * });\n *\n * // Get the default broker\n * const comm = brokerRegistry.get();\n *\n * // Get a specific broker by name\n * const analytics = brokerRegistry.get(\"analytics\");\n *\n * // Listen for events\n * brokerRegistry.on(\"connected\", (comm) => {\n * console.log(`${comm.name} connected`);\n * });\n * ```\n */\nclass BrokerRegistry {\n private readonly sources = new Map<string, Broker>();\n private defaultSource?: Broker;\n private readonly events = new EventEmitter();\n\n /**\n * Register a new broker\n *\n * Sets up event forwarding from the driver to the registry.\n *\n * @param options - Broker configuration\n * @returns The registered broker instance\n *\n * @example\n * ```typescript\n * const broker = brokerRegistry.register({\n * name: \"primary\",\n * driver: myDriver,\n * isDefault: true,\n * });\n * ```\n */\n public register(options: BrokerOptions): Broker {\n const broker = new Broker(options);\n this.sources.set(broker.name, broker);\n\n const isNewDefault = broker.isDefault || !this.defaultSource;\n\n if (isNewDefault) {\n this.defaultSource = broker;\n }\n\n // Emit registration events\n this.events.emit(\"registered\", broker);\n\n if (isNewDefault) {\n this.events.emit(\"default-registered\", broker);\n }\n\n // Forward driver events to registry\n broker.driver.on(\"connected\", () => {\n this.events.emit(\"connected\", broker);\n });\n\n broker.driver.on(\"disconnected\", () => {\n this.events.emit(\"disconnected\", broker);\n });\n\n return broker;\n }\n\n /**\n * Clear all registered brokers\n */\n public clear(): void {\n this.defaultSource = undefined;\n this.sources.clear();\n }\n\n /**\n * Listen for registry events\n *\n * @param event - Event to listen for\n * @param listener - Callback function\n *\n * @example\n * ```typescript\n * brokerRegistry.on(\"registered\", (comm) => {\n * console.log(`Broker \"${comm.name}\" registered`);\n * });\n *\n * brokerRegistry.on(\"connected\", (comm) => {\n * console.log(`Broker \"${comm.name}\" connected`);\n * });\n * ```\n */\n public on(event: BrokerRegistryEvent, listener: BrokerRegistryListener): void {\n this.events.on(event, listener);\n }\n\n /**\n * Listen for a registry event once\n *\n * @param event - Event to listen for\n * @param listener - Callback function\n */\n public once(event: BrokerRegistryEvent, listener: BrokerRegistryListener): void {\n this.events.once(event, listener);\n }\n\n /**\n * Remove an event listener\n *\n * @param event - Event to stop listening for\n * @param listener - Callback to remove\n */\n public off(event: BrokerRegistryEvent, listener: BrokerRegistryListener): void {\n this.events.off(event, listener);\n }\n\n /**\n * Get a broker by name or the default one\n *\n * @param name - Optional broker name\n * @returns Broker instance\n * @throws MissingBrokerError if not found\n *\n * @example\n * ```typescript\n * // Get default broker\n * const comm = brokerRegistry.get();\n *\n * // Get specific broker\n * const analytics = brokerRegistry.get(\"analytics\");\n * ```\n */\n public get(name?: string): Broker {\n if (name !== undefined) {\n const source = this.sources.get(name);\n if (!source) {\n throw new MissingBrokerError(`Broker \"${name}\" is not registered.`, name);\n }\n return source;\n }\n\n if (!this.defaultSource) {\n throw new MissingBrokerError(\"No default broker registered.\");\n }\n\n return this.defaultSource;\n }\n\n /**\n * Check if a broker exists\n *\n * @param name - Broker name to check\n * @returns True if exists\n */\n public has(name: string): boolean {\n return this.sources.has(name);\n }\n\n /**\n * Check if any brokers are registered\n */\n public hasAny(): boolean {\n return this.sources.size > 0;\n }\n\n /**\n * Get all registered brokers\n *\n * @returns Array of all brokers\n *\n * @example\n * ```typescript\n * // Disconnect all brokers\n * for (const comm of brokerRegistry.getAll()) {\n * await comm.disconnect();\n * }\n * ```\n */\n public getAll(): Broker[] {\n return Array.from(this.sources.values());\n }\n\n /**\n * Get all broker names\n *\n * @returns Array of broker names\n */\n public getNames(): string[] {\n return Array.from(this.sources.keys());\n }\n\n /**\n * Get the default broker (if any)\n *\n * @returns Default broker or undefined\n */\n public getDefault(): Broker | undefined {\n return this.defaultSource;\n }\n}\n\n/**\n * Global broker registry instance\n */\nexport const brokerRegistry = new BrokerRegistry();\n"],"mappings":";;;;;;;AAOA,IAAa,qBAAb,cAAwC,MAAM;CAC5C,AAAgB;CAEhB,AAAO,YAAY,SAAiB,YAAqB;EACvD,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,aAAa;CACpB;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,IAAM,iBAAN,MAAqB;CACnB,AAAiB,0BAAU,IAAI,IAAoB;CACnD,AAAQ;CACR,AAAiB,SAAS,IAAI,aAAa;;;;;;;;;;;;;;;;;;CAmB3C,AAAO,SAAS,SAAgC;EAC9C,MAAM,SAAS,IAAI,OAAO,OAAO;EACjC,KAAK,QAAQ,IAAI,OAAO,MAAM,MAAM;EAEpC,MAAM,eAAe,OAAO,aAAa,CAAC,KAAK;EAE/C,IAAI,cACF,KAAK,gBAAgB;EAIvB,KAAK,OAAO,KAAK,cAAc,MAAM;EAErC,IAAI,cACF,KAAK,OAAO,KAAK,sBAAsB,MAAM;EAI/C,OAAO,OAAO,GAAG,mBAAmB;GAClC,KAAK,OAAO,KAAK,aAAa,MAAM;EACtC,CAAC;EAED,OAAO,OAAO,GAAG,sBAAsB;GACrC,KAAK,OAAO,KAAK,gBAAgB,MAAM;EACzC,CAAC;EAED,OAAO;CACT;;;;CAKA,AAAO,QAAc;EACnB,KAAK,gBAAgB;EACrB,KAAK,QAAQ,MAAM;CACrB;;;;;;;;;;;;;;;;;;CAmBA,AAAO,GAAG,OAA4B,UAAwC;EAC5E,KAAK,OAAO,GAAG,OAAO,QAAQ;CAChC;;;;;;;CAQA,AAAO,KAAK,OAA4B,UAAwC;EAC9E,KAAK,OAAO,KAAK,OAAO,QAAQ;CAClC;;;;;;;CAQA,AAAO,IAAI,OAA4B,UAAwC;EAC7E,KAAK,OAAO,IAAI,OAAO,QAAQ;CACjC;;;;;;;;;;;;;;;;;CAkBA,AAAO,IAAI,MAAuB;EAChC,IAAI,SAAS,QAAW;GACtB,MAAM,SAAS,KAAK,QAAQ,IAAI,IAAI;GACpC,IAAI,CAAC,QACH,MAAM,IAAI,mBAAmB,WAAW,KAAK,uBAAuB,IAAI;GAE1E,OAAO;EACT;EAEA,IAAI,CAAC,KAAK,eACR,MAAM,IAAI,mBAAmB,+BAA+B;EAG9D,OAAO,KAAK;CACd;;;;;;;CAQA,AAAO,IAAI,MAAuB;EAChC,OAAO,KAAK,QAAQ,IAAI,IAAI;CAC9B;;;;CAKA,AAAO,SAAkB;EACvB,OAAO,KAAK,QAAQ,OAAO;CAC7B;;;;;;;;;;;;;;CAeA,AAAO,SAAmB;EACxB,OAAO,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC;CACzC;;;;;;CAOA,AAAO,WAAqB;EAC1B,OAAO,MAAM,KAAK,KAAK,QAAQ,KAAK,CAAC;CACvC;;;;;;CAOA,AAAO,aAAiC;EACtC,OAAO,KAAK;CACd;AACF;;;;AAKA,MAAa,iBAAiB,IAAI,eAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broker.mjs","names":[],"sources":["../../../../../../../../../herald/src/communicators/broker.ts"],"sourcesContent":["import type { BrokerDriverContract } from \"../contracts\";\
|
|
1
|
+
{"version":3,"file":"broker.mjs","names":[],"sources":["../../../../../../../../../herald/src/communicators/broker.ts"],"sourcesContent":["import type { BrokerDriverContract } from \"../contracts\";\nimport type { ChannelContract } from \"../contracts/channel.contract\";\nimport { EventMessage } from \"../message-managers/event-message\";\nimport { type EventConsumerClass } from \"../message-managers/types\";\nimport type { ChannelOptions } from \"../types\";\n\n/**\n * Options for creating a Broker\n */\nexport interface BrokerOptions {\n /** Unique name for this broker */\n name: string;\n /** The underlying driver */\n driver: BrokerDriverContract;\n /** Whether this is the default broker */\n isDefault?: boolean;\n}\n\n/**\n * Broker - wrapper around a driver with metadata\n *\n * Similar to DataSource in @warlock.js/cascade\n *\n * @example\n * ```typescript\n * const broker = new Broker({\n * name: \"default\",\n * driver: rabbitMQDriver,\n * isDefault: true,\n * });\n *\n * // Get a channel\n * const channel = broker.channel(\"user.created\");\n * ```\n */\nexport class Broker {\n /** Unique name identifying this broker */\n public readonly name: string;\n\n /** The underlying driver */\n public readonly driver: BrokerDriverContract;\n\n /** Whether this is the default broker */\n public readonly isDefault: boolean;\n\n /**\n * Create a new Broker\n *\n * @param options - Broker configuration\n */\n public constructor(options: BrokerOptions) {\n this.name = options.name;\n this.driver = options.driver;\n this.isDefault = Boolean(options.isDefault);\n }\n\n /**\n * Subscribe the given consumer\n */\n public subscribe(consumer: EventConsumerClass<any>) {\n return this.driver.subscribe(consumer);\n }\n\n /**\n * Publish the given event message\n */\n public publish<TPayload = Record<string, any>>(event: EventMessage<TPayload>) {\n this.driver.publish(event);\n }\n\n /**\n * Get or create a channel\n *\n * @param name - Channel name\n * @param options - Channel options\n * @returns Channel instance\n *\n * @example\n * ```typescript\n * // Simple channel\n * const channel = broker.channel(\"notifications\");\n *\n * // Typed channel with schema\n * const orderChannel = broker.channel<OrderPayload>(\"orders\", {\n * schema: OrderSchema,\n * durable: true,\n * });\n * ```\n */\n public channel<TPayload = unknown>(\n name: string,\n options?: ChannelOptions<TPayload>,\n ): ChannelContract<TPayload> {\n return this.driver.channel<TPayload>(name, options);\n }\n\n /**\n * Check if the broker is connected\n */\n public get isConnected(): boolean {\n return this.driver.isConnected;\n }\n\n /**\n * Connect the underlying driver\n */\n public async connect(): Promise<void> {\n await this.driver.connect();\n }\n\n /**\n * Disconnect the underlying driver\n */\n public async disconnect(): Promise<void> {\n await this.driver.disconnect();\n }\n\n /**\n * Start consuming messages\n */\n public async startConsuming(): Promise<void> {\n await this.driver.startConsuming();\n }\n\n /**\n * Stop consuming messages\n */\n public async stopConsuming(): Promise<void> {\n await this.driver.stopConsuming();\n }\n\n /**\n * Health check\n */\n public async healthCheck() {\n return this.driver.healthCheck();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmCA,IAAa,SAAb,MAAoB;;CAElB,AAAgB;;CAGhB,AAAgB;;CAGhB,AAAgB;;;;;;CAOhB,AAAO,YAAY,SAAwB;EACzC,KAAK,OAAO,QAAQ;EACpB,KAAK,SAAS,QAAQ;EACtB,KAAK,YAAY,QAAQ,QAAQ,SAAS;CAC5C;;;;CAKA,AAAO,UAAU,UAAmC;EAClD,OAAO,KAAK,OAAO,UAAU,QAAQ;CACvC;;;;CAKA,AAAO,QAAwC,OAA+B;EAC5E,KAAK,OAAO,QAAQ,KAAK;CAC3B;;;;;;;;;;;;;;;;;;;;CAqBA,AAAO,QACL,MACA,SAC2B;EAC3B,OAAO,KAAK,OAAO,QAAkB,MAAM,OAAO;CACpD;;;;CAKA,IAAW,cAAuB;EAChC,OAAO,KAAK,OAAO;CACrB;;;;CAKA,MAAa,UAAyB;EACpC,MAAM,KAAK,OAAO,QAAQ;CAC5B;;;;CAKA,MAAa,aAA4B;EACvC,MAAM,KAAK,OAAO,WAAW;CAC/B;;;;CAKA,MAAa,iBAAgC;EAC3C,MAAM,KAAK,OAAO,eAAe;CACnC;;;;CAKA,MAAa,gBAA+B;EAC1C,MAAM,KAAK,OAAO,cAAc;CAClC;;;;CAKA,MAAa,cAAc;EACzB,OAAO,KAAK,OAAO,YAAY;CACjC;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rabbitmq-channel.mjs","names":[],"sources":["../../../../../../../../../../herald/src/drivers/rabbitmq/rabbitmq-channel.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\r\nimport { v } from \"@warlock.js/seal\";\r\nimport { randomUUID } from \"node:crypto\";\r\nimport type { ChannelContract } from \"../../contracts\";\r\nimport type {\r\n ChannelOptions,\r\n ChannelStats,\r\n Message,\r\n MessageContext,\r\n MessageHandler,\r\n MessageMetadata,\r\n PublishOptions,\r\n RequestOptions,\r\n ResponseHandler,\r\n SubscribeOptions,\r\n Subscription,\r\n} from \"../../types\";\r\n\r\n/**\r\n * RabbitMQ Channel Implementation\r\n *\r\n * Wraps a RabbitMQ queue/exchange with a unified API.\r\n *\r\n * @template TPayload - The typed payload\r\n */\r\nexport class RabbitMQChannel<TPayload = unknown> implements ChannelContract<TPayload> {\r\n public readonly name: string;\r\n public readonly options: ChannelOptions<TPayload>;\r\n\r\n private readonly amqpChannel: any;\r\n private readonly subscriptions = new Map<string, RabbitMQSubscription>();\r\n private asserted = false;\r\n\r\n /**\r\n * Create a new RabbitMQ channel\r\n */\r\n public constructor(name: string, amqpChannel: any, options?: ChannelOptions<TPayload>) {\r\n this.name = name;\r\n this.amqpChannel = amqpChannel;\r\n this.options = options ?? {};\r\n }\r\n\r\n /**\r\n * Assert the queue exists\r\n */\r\n public async assert(): Promise<void> {\r\n if (this.asserted) return;\r\n\r\n const queueOptions = {\r\n durable: this.options.durable ?? true,\r\n autoDelete: this.options.autoDelete ?? false,\r\n exclusive: this.options.exclusive ?? false,\r\n messageTtl: this.options.messageTtl,\r\n maxLength: this.options.maxLength,\r\n deadLetterExchange: this.options.deadLetter?.channel ? \"\" : undefined,\r\n deadLetterRoutingKey: this.options.deadLetter?.channel,\r\n };\r\n\r\n await this.amqpChannel.assertQueue(this.name, queueOptions);\r\n this.asserted = true;\r\n }\r\n\r\n /**\r\n * Publish a message\r\n */\r\n public async publish(payload: TPayload, options?: PublishOptions): Promise<void> {\r\n await this.assert();\r\n\r\n // Validate with schema if provided\r\n if (this.options.schema) {\r\n const context = {\r\n allValues: payload,\r\n value: payload,\r\n };\r\n const result = await v.validate(this.options.schema, payload, { context });\r\n if (!result.isValid) {\r\n throw new Error(`Message validation failed: ${JSON.stringify(result.errors)}`);\r\n }\r\n payload = result.data as TPayload;\r\n }\r\n\r\n const messageId = randomUUID();\r\n const timestamp = new Date();\r\n\r\n const messageContent = JSON.stringify({\r\n payload,\r\n metadata: {\r\n messageId,\r\n timestamp: timestamp.toISOString(),\r\n correlationId: options?.correlationId,\r\n headers: options?.headers,\r\n },\r\n });\r\n\r\n const publishOptions: any = {\r\n persistent: options?.persistent ?? true,\r\n messageId,\r\n timestamp: timestamp.getTime(),\r\n correlationId: options?.correlationId,\r\n expiration: options?.expiration?.toString(),\r\n priority: options?.priority,\r\n headers: options?.headers,\r\n };\r\n\r\n // Handle delayed messages (requires rabbitmq-delayed-message-exchange plugin)\r\n if (options?.delay) {\r\n publishOptions.headers = {\r\n ...publishOptions.headers,\r\n \"x-delay\": options.delay,\r\n };\r\n }\r\n\r\n this.amqpChannel.sendToQueue(this.name, Buffer.from(messageContent), publishOptions);\r\n }\r\n\r\n /**\r\n * Publish multiple messages\r\n */\r\n public async publishBatch(messages: TPayload[], options?: PublishOptions): Promise<void> {\r\n for (const payload of messages) {\r\n await this.publish(payload, options);\r\n }\r\n }\r\n\r\n /**\r\n * Subscribe to messages\r\n *\r\n * Smart auto-ack behavior (when autoAck is not true):\r\n * - If handler completes successfully without explicit ack/nack/reject → auto-ack\r\n * - If handler throws an error → auto-nack (with retry if configured)\r\n * - If handler explicitly calls ack/nack/reject → respects that call\r\n */\r\n public async subscribe(\r\n handler: MessageHandler<TPayload>,\r\n options?: SubscribeOptions,\r\n ): Promise<Subscription> {\r\n await this.assert();\r\n\r\n // Use consumerId from options if provided, otherwise generate a random one\r\n const subscriptionId = options?.consumerId ?? randomUUID();\r\n\r\n // Set prefetch if specified\r\n if (options?.prefetch) {\r\n await this.amqpChannel.prefetch(options.prefetch);\r\n }\r\n\r\n // If autoAck is true, RabbitMQ handles ack immediately (fire-and-forget)\r\n const isFireAndForget = options?.autoAck === true;\r\n\r\n const consumerOptions = {\r\n noAck: isFireAndForget,\r\n exclusive: options?.exclusive ?? false,\r\n consumerTag: options?.group ?? subscriptionId,\r\n };\r\n\r\n const { consumerTag } = await this.amqpChannel.consume(\r\n this.name,\r\n async (msg: any) => {\r\n if (!msg) return;\r\n\r\n // Track if acknowledgment was handled explicitly\r\n let ackHandled = isFireAndForget;\r\n\r\n // Populated once the message is successfully parsed, so the catch\r\n // block below can dead-letter with the full envelope. Stays\r\n // `undefined` when `JSON.parse` itself is what threw.\r\n let parsedMessage: Message<TPayload> | undefined;\r\n\r\n try {\r\n const content = JSON.parse(msg.content.toString());\r\n let payload = content.payload as TPayload;\r\n\r\n // Validate with schema if provided\r\n if (this.options.schema) {\r\n const context = {\r\n allValues: payload,\r\n value: payload,\r\n };\r\n const result = await v.validate(this.options.schema, payload, { context });\r\n if (!result.isValid) {\r\n // Reject invalid messages\r\n this.amqpChannel.nack(msg, false, false);\r\n return;\r\n }\r\n payload = result.data as TPayload;\r\n }\r\n\r\n const metadata: MessageMetadata = {\r\n messageId: msg.properties.messageId || content.metadata?.messageId || randomUUID(),\r\n timestamp: new Date(msg.properties.timestamp || content.metadata?.timestamp),\r\n correlationId: msg.properties.correlationId || content.metadata?.correlationId,\r\n replyTo: msg.properties.replyTo,\r\n priority: msg.properties.priority,\r\n headers: msg.properties.headers,\r\n retryCount: msg.properties.headers?.[\"x-retry-count\"] || 0,\r\n originalChannel: this.name,\r\n };\r\n\r\n const message: Message<TPayload> = {\r\n metadata,\r\n payload,\r\n raw: msg,\r\n };\r\n\r\n parsedMessage = message;\r\n\r\n const context: MessageContext = {\r\n ack: async () => {\r\n if (!ackHandled) {\r\n ackHandled = true;\r\n this.amqpChannel.ack(msg);\r\n }\r\n },\r\n nack: async (requeue = true) => {\r\n if (!ackHandled) {\r\n ackHandled = true;\r\n this.amqpChannel.nack(msg, false, requeue);\r\n }\r\n },\r\n reject: async () => {\r\n if (!ackHandled) {\r\n ackHandled = true;\r\n this.amqpChannel.reject(msg, false);\r\n }\r\n },\r\n reply: async <T>(replyPayload: T) => {\r\n if (msg.properties.replyTo) {\r\n const replyContent = JSON.stringify({\r\n payload: replyPayload,\r\n metadata: {\r\n messageId: randomUUID(),\r\n timestamp: new Date().toISOString(),\r\n correlationId: msg.properties.correlationId,\r\n },\r\n });\r\n\r\n this.amqpChannel.sendToQueue(msg.properties.replyTo, Buffer.from(replyContent), {\r\n correlationId: msg.properties.correlationId,\r\n });\r\n }\r\n },\r\n retry: async (delay?: number) => {\r\n if (ackHandled) return;\r\n ackHandled = true;\r\n\r\n await this.retryOrGiveUp(msg, metadata.retryCount || 0, options, message, delay);\r\n },\r\n };\r\n\r\n // Execute handler\r\n await handler(message, context);\r\n\r\n // Smart auto-ack: if handler succeeded and didn't explicitly handle ack\r\n if (!ackHandled) {\r\n this.amqpChannel.ack(msg);\r\n }\r\n } catch (error) {\r\n // Smart auto-nack: if handler threw and didn't explicitly handle ack\r\n if (ackHandled) return;\r\n\r\n if (options?.retry) {\r\n // A bare `nack(msg, false, true)` redelivers the ORIGINAL message\r\n // untouched — amqplib/RabbitMQ do not add an `x-retry-count`\r\n // header on requeue, so a plain requeue here never advances the\r\n // counter and `maxRetries`/`deadLetter` are silently never\r\n // reached. Route through the same bounded-retry path `ctx.retry()`\r\n // uses, so the counter increments (and the cap/dead-letter fires)\r\n // on this automatic path too.\r\n const currentRetryCount = msg.properties.headers?.[\"x-retry-count\"] || 0;\r\n await this.retryOrGiveUp(msg, currentRetryCount, options, parsedMessage);\r\n } else {\r\n // No retry configured - reject without requeue (already bounded:\r\n // a single attempt, no requeue loop possible).\r\n this.amqpChannel.nack(msg, false, false);\r\n }\r\n }\r\n },\r\n consumerOptions,\r\n );\r\n\r\n const subscription = new RabbitMQSubscription(\r\n subscriptionId,\r\n this.name,\r\n consumerTag,\r\n this.amqpChannel,\r\n );\r\n\r\n this.subscriptions.set(subscriptionId, subscription);\r\n\r\n return subscription;\r\n }\r\n\r\n /**\r\n * Unsubscribe by consumer ID\r\n */\r\n public async unsubscribeById(consumerId: string): Promise<void> {\r\n const subscription = this.subscriptions.get(consumerId);\r\n if (subscription) {\r\n await subscription.unsubscribe();\r\n this.subscriptions.delete(consumerId);\r\n }\r\n }\r\n\r\n /**\r\n * Stop consuming messages on this channel.\r\n * Cancels all active subscriptions gracefully.\r\n */\r\n public async stopConsuming(): Promise<void> {\r\n const cancellations = Array.from(this.subscriptions.values()).map(sub =>\r\n sub.unsubscribe(),\r\n );\r\n await Promise.all(cancellations);\r\n }\r\n\r\n /**\r\n * Send message to dead-letter queue\r\n */\r\n private async sendToDeadLetter(\r\n message: Message<TPayload>,\r\n deadLetterChannel: string,\r\n ): Promise<void> {\r\n const content = JSON.stringify({\r\n payload: message.payload,\r\n metadata: {\r\n ...message.metadata,\r\n originalChannel: this.name,\r\n },\r\n });\r\n\r\n this.amqpChannel.sendToQueue(deadLetterChannel, Buffer.from(content), { persistent: true });\r\n }\r\n\r\n /**\r\n * Dead-letter a message whose body couldn't be parsed into a {@link Message}\r\n * (e.g. `JSON.parse` itself threw before an envelope existed). Forwards the\r\n * raw AMQP bytes/properties as-is rather than re-deriving a payload, so a\r\n * malformed message isn't lost.\r\n */\r\n private sendToDeadLetterRaw(msg: any, deadLetterChannel: string): void {\r\n this.amqpChannel.sendToQueue(deadLetterChannel, msg.content, {\r\n ...msg.properties,\r\n persistent: true,\r\n });\r\n }\r\n\r\n /**\r\n * Bounded retry shared by the explicit `ctx.retry()` call and the automatic\r\n * catch when a handler throws without calling it itself — so both paths\r\n * honor the same cap instead of the automatic path silently requeueing\r\n * forever (see `subscribe()`'s catch block).\r\n *\r\n * Under the cap: republishes with an incremented `x-retry-count` header —\r\n * NOT a plain `nack(msg, false, true)`, which redelivers the original\r\n * message untouched and never advances the counter.\r\n *\r\n * At/over the cap: dead-letters if configured, otherwise drops the message\r\n * with a loud `log.error` (never a silent drop) so an operator can see a\r\n * poison message was discarded instead of it vanishing without a trace.\r\n */\r\n private async retryOrGiveUp(\r\n msg: any,\r\n currentRetryCount: number,\r\n options: SubscribeOptions | undefined,\r\n parsedMessage: Message<TPayload> | undefined,\r\n delay?: number,\r\n ): Promise<void> {\r\n const retryCount = currentRetryCount + 1;\r\n const maxRetries = options?.retry?.maxRetries ?? 3;\r\n\r\n if (retryCount > maxRetries) {\r\n if (options?.deadLetter) {\r\n if (parsedMessage) {\r\n await this.sendToDeadLetter(parsedMessage, options.deadLetter.channel);\r\n } else {\r\n this.sendToDeadLetterRaw(msg, options.deadLetter.channel);\r\n }\r\n } else {\r\n log.error(\r\n \"herald\",\r\n \"poison-message\",\r\n `Dropping message on channel \"${this.name}\" after ${retryCount - 1} failed ` +\r\n `${retryCount - 1 === 1 ? \"retry\" : \"retries\"} (maxRetries: ${maxRetries}) with no ` +\r\n `dead-letter channel configured.`,\r\n { channel: this.name, retryCount: retryCount - 1, maxRetries },\r\n );\r\n this.amqpChannel.reject(msg, false);\r\n return;\r\n }\r\n\r\n this.amqpChannel.ack(msg);\r\n return;\r\n }\r\n\r\n const headers: Record<string, unknown> = {\r\n ...msg.properties.headers,\r\n \"x-retry-count\": retryCount,\r\n };\r\n\r\n if (delay) {\r\n headers[\"x-delay\"] = delay;\r\n }\r\n\r\n this.amqpChannel.sendToQueue(this.name, msg.content, { ...msg.properties, headers });\r\n\r\n this.amqpChannel.ack(msg);\r\n }\r\n\r\n /**\r\n * Request-response pattern\r\n */\r\n public async request<TResponse = unknown>(\r\n payload: TPayload,\r\n options?: RequestOptions,\r\n ): Promise<TResponse> {\r\n await this.assert();\r\n\r\n const correlationId = randomUUID();\r\n const timeout = options?.timeout ?? 30000;\r\n\r\n // Create exclusive reply queue\r\n const { queue: replyQueue } = await this.amqpChannel.assertQueue(\"\", {\r\n exclusive: true,\r\n autoDelete: true,\r\n });\r\n\r\n return new Promise<TResponse>((resolve, reject) => {\r\n const timeoutId = setTimeout(() => {\r\n reject(new Error(`Request timeout after ${timeout}ms`));\r\n }, timeout);\r\n\r\n // Consume reply\r\n this.amqpChannel.consume(\r\n replyQueue,\r\n (msg: any) => {\r\n if (msg?.properties.correlationId === correlationId) {\r\n clearTimeout(timeoutId);\r\n const content = JSON.parse(msg.content.toString());\r\n resolve(content.payload as TResponse);\r\n }\r\n },\r\n { noAck: true },\r\n );\r\n\r\n // Send request\r\n const messageContent = JSON.stringify({\r\n payload,\r\n metadata: {\r\n messageId: randomUUID(),\r\n timestamp: new Date().toISOString(),\r\n correlationId,\r\n },\r\n });\r\n\r\n this.amqpChannel.sendToQueue(this.name, Buffer.from(messageContent), {\r\n correlationId,\r\n replyTo: replyQueue,\r\n expiration: timeout.toString(),\r\n ...options,\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Register response handler for RPC\r\n */\r\n public async respond<TResponse = unknown>(\r\n handler: ResponseHandler<TPayload, TResponse>,\r\n ): Promise<Subscription> {\r\n return this.subscribe(async (message, ctx) => {\r\n const response = await handler(message, ctx);\r\n await ctx.reply(response);\r\n await ctx.ack();\r\n });\r\n }\r\n\r\n /**\r\n * Get queue statistics\r\n */\r\n public async stats(): Promise<ChannelStats> {\r\n await this.assert();\r\n\r\n const queueInfo = await this.amqpChannel.checkQueue(this.name);\r\n\r\n return {\r\n name: this.name,\r\n messageCount: queueInfo.messageCount,\r\n consumerCount: queueInfo.consumerCount,\r\n };\r\n }\r\n\r\n /**\r\n * Purge all messages\r\n */\r\n public async purge(): Promise<number> {\r\n await this.assert();\r\n\r\n const result = await this.amqpChannel.purgeQueue(this.name);\r\n return result.messageCount;\r\n }\r\n\r\n /**\r\n * Check if queue exists\r\n */\r\n public async exists(): Promise<boolean> {\r\n try {\r\n await this.amqpChannel.checkQueue(this.name);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Delete the queue\r\n */\r\n public async delete(): Promise<void> {\r\n // Cancel all subscriptions\r\n for (const subscription of this.subscriptions.values()) {\r\n await subscription.unsubscribe();\r\n }\r\n this.subscriptions.clear();\r\n\r\n try {\r\n await this.amqpChannel.deleteQueue(this.name);\r\n } catch {\r\n // Ignore if queue doesn't exist\r\n }\r\n\r\n this.asserted = false;\r\n }\r\n}\r\n\r\n/**\r\n * RabbitMQ Subscription Implementation\r\n */\r\nclass RabbitMQSubscription implements Subscription {\r\n public readonly id: string;\r\n public readonly channel: string;\r\n public readonly consumerTag: string;\r\n\r\n private readonly amqpChannel: any;\r\n private _isActive = true;\r\n\r\n public constructor(id: string, channel: string, consumerTag: string, amqpChannel: any) {\r\n this.id = id;\r\n this.channel = channel;\r\n this.consumerTag = consumerTag;\r\n this.amqpChannel = amqpChannel;\r\n }\r\n\r\n public async unsubscribe(): Promise<void> {\r\n if (!this._isActive) return;\r\n\r\n await this.amqpChannel.cancel(this.consumerTag);\r\n this._isActive = false;\r\n }\r\n\r\n public async pause(): Promise<void> {\r\n // RabbitMQ doesn't have native pause, cancel consumer\r\n await this.amqpChannel.cancel(this.consumerTag);\r\n }\r\n\r\n public async resume(): Promise<void> {\r\n // Would need to re-subscribe - not directly supported\r\n throw new Error(\"Resume is not supported for RabbitMQ. Please create a new subscription.\");\r\n }\r\n\r\n public isActive(): boolean {\r\n return this._isActive;\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rabbitmq-channel.mjs","names":[],"sources":["../../../../../../../../../../herald/src/drivers/rabbitmq/rabbitmq-channel.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport { v } from \"@warlock.js/seal\";\nimport { randomUUID } from \"node:crypto\";\nimport type { ChannelContract } from \"../../contracts\";\nimport type {\n ChannelOptions,\n ChannelStats,\n Message,\n MessageContext,\n MessageHandler,\n MessageMetadata,\n PublishOptions,\n RequestOptions,\n ResponseHandler,\n SubscribeOptions,\n Subscription,\n} from \"../../types\";\n\n/**\n * RabbitMQ Channel Implementation\n *\n * Wraps a RabbitMQ queue/exchange with a unified API.\n *\n * @template TPayload - The typed payload\n */\nexport class RabbitMQChannel<TPayload = unknown> implements ChannelContract<TPayload> {\n public readonly name: string;\n public readonly options: ChannelOptions<TPayload>;\n\n private readonly amqpChannel: any;\n private readonly subscriptions = new Map<string, RabbitMQSubscription>();\n private asserted = false;\n\n /**\n * Create a new RabbitMQ channel\n */\n public constructor(name: string, amqpChannel: any, options?: ChannelOptions<TPayload>) {\n this.name = name;\n this.amqpChannel = amqpChannel;\n this.options = options ?? {};\n }\n\n /**\n * Assert the queue exists\n */\n public async assert(): Promise<void> {\n if (this.asserted) return;\n\n const queueOptions = {\n durable: this.options.durable ?? true,\n autoDelete: this.options.autoDelete ?? false,\n exclusive: this.options.exclusive ?? false,\n messageTtl: this.options.messageTtl,\n maxLength: this.options.maxLength,\n deadLetterExchange: this.options.deadLetter?.channel ? \"\" : undefined,\n deadLetterRoutingKey: this.options.deadLetter?.channel,\n };\n\n await this.amqpChannel.assertQueue(this.name, queueOptions);\n this.asserted = true;\n }\n\n /**\n * Publish a message\n */\n public async publish(payload: TPayload, options?: PublishOptions): Promise<void> {\n await this.assert();\n\n // Validate with schema if provided\n if (this.options.schema) {\n const context = {\n allValues: payload,\n value: payload,\n };\n const result = await v.validate(this.options.schema, payload, { context });\n if (!result.isValid) {\n throw new Error(`Message validation failed: ${JSON.stringify(result.errors)}`);\n }\n payload = result.data as TPayload;\n }\n\n const messageId = randomUUID();\n const timestamp = new Date();\n\n const messageContent = JSON.stringify({\n payload,\n metadata: {\n messageId,\n timestamp: timestamp.toISOString(),\n correlationId: options?.correlationId,\n headers: options?.headers,\n },\n });\n\n const publishOptions: any = {\n persistent: options?.persistent ?? true,\n messageId,\n timestamp: timestamp.getTime(),\n correlationId: options?.correlationId,\n expiration: options?.expiration?.toString(),\n priority: options?.priority,\n headers: options?.headers,\n };\n\n // Handle delayed messages (requires rabbitmq-delayed-message-exchange plugin)\n if (options?.delay) {\n publishOptions.headers = {\n ...publishOptions.headers,\n \"x-delay\": options.delay,\n };\n }\n\n this.amqpChannel.sendToQueue(this.name, Buffer.from(messageContent), publishOptions);\n }\n\n /**\n * Publish multiple messages\n */\n public async publishBatch(messages: TPayload[], options?: PublishOptions): Promise<void> {\n for (const payload of messages) {\n await this.publish(payload, options);\n }\n }\n\n /**\n * Subscribe to messages\n *\n * Smart auto-ack behavior (when autoAck is not true):\n * - If handler completes successfully without explicit ack/nack/reject → auto-ack\n * - If handler throws an error → auto-nack (with retry if configured)\n * - If handler explicitly calls ack/nack/reject → respects that call\n */\n public async subscribe(\n handler: MessageHandler<TPayload>,\n options?: SubscribeOptions,\n ): Promise<Subscription> {\n await this.assert();\n\n // Use consumerId from options if provided, otherwise generate a random one\n const subscriptionId = options?.consumerId ?? randomUUID();\n\n // Set prefetch if specified\n if (options?.prefetch) {\n await this.amqpChannel.prefetch(options.prefetch);\n }\n\n // If autoAck is true, RabbitMQ handles ack immediately (fire-and-forget)\n const isFireAndForget = options?.autoAck === true;\n\n const consumerOptions = {\n noAck: isFireAndForget,\n exclusive: options?.exclusive ?? false,\n consumerTag: options?.group ?? subscriptionId,\n };\n\n const { consumerTag } = await this.amqpChannel.consume(\n this.name,\n async (msg: any) => {\n if (!msg) return;\n\n // Track if acknowledgment was handled explicitly\n let ackHandled = isFireAndForget;\n\n // Populated once the message is successfully parsed, so the catch\n // block below can dead-letter with the full envelope. Stays\n // `undefined` when `JSON.parse` itself is what threw.\n let parsedMessage: Message<TPayload> | undefined;\n\n try {\n const content = JSON.parse(msg.content.toString());\n let payload = content.payload as TPayload;\n\n // Validate with schema if provided\n if (this.options.schema) {\n const context = {\n allValues: payload,\n value: payload,\n };\n const result = await v.validate(this.options.schema, payload, { context });\n if (!result.isValid) {\n // Reject invalid messages\n this.amqpChannel.nack(msg, false, false);\n return;\n }\n payload = result.data as TPayload;\n }\n\n const metadata: MessageMetadata = {\n messageId: msg.properties.messageId || content.metadata?.messageId || randomUUID(),\n timestamp: new Date(msg.properties.timestamp || content.metadata?.timestamp),\n correlationId: msg.properties.correlationId || content.metadata?.correlationId,\n replyTo: msg.properties.replyTo,\n priority: msg.properties.priority,\n headers: msg.properties.headers,\n retryCount: msg.properties.headers?.[\"x-retry-count\"] || 0,\n originalChannel: this.name,\n };\n\n const message: Message<TPayload> = {\n metadata,\n payload,\n raw: msg,\n };\n\n parsedMessage = message;\n\n const context: MessageContext = {\n ack: async () => {\n if (!ackHandled) {\n ackHandled = true;\n this.amqpChannel.ack(msg);\n }\n },\n nack: async (requeue = true) => {\n if (!ackHandled) {\n ackHandled = true;\n this.amqpChannel.nack(msg, false, requeue);\n }\n },\n reject: async () => {\n if (!ackHandled) {\n ackHandled = true;\n this.amqpChannel.reject(msg, false);\n }\n },\n reply: async <T>(replyPayload: T) => {\n if (msg.properties.replyTo) {\n const replyContent = JSON.stringify({\n payload: replyPayload,\n metadata: {\n messageId: randomUUID(),\n timestamp: new Date().toISOString(),\n correlationId: msg.properties.correlationId,\n },\n });\n\n this.amqpChannel.sendToQueue(msg.properties.replyTo, Buffer.from(replyContent), {\n correlationId: msg.properties.correlationId,\n });\n }\n },\n retry: async (delay?: number) => {\n if (ackHandled) return;\n ackHandled = true;\n\n await this.retryOrGiveUp(msg, metadata.retryCount || 0, options, message, delay);\n },\n };\n\n // Execute handler\n await handler(message, context);\n\n // Smart auto-ack: if handler succeeded and didn't explicitly handle ack\n if (!ackHandled) {\n this.amqpChannel.ack(msg);\n }\n } catch (error) {\n // Smart auto-nack: if handler threw and didn't explicitly handle ack\n if (ackHandled) return;\n\n if (options?.retry) {\n // A bare `nack(msg, false, true)` redelivers the ORIGINAL message\n // untouched — amqplib/RabbitMQ do not add an `x-retry-count`\n // header on requeue, so a plain requeue here never advances the\n // counter and `maxRetries`/`deadLetter` are silently never\n // reached. Route through the same bounded-retry path `ctx.retry()`\n // uses, so the counter increments (and the cap/dead-letter fires)\n // on this automatic path too.\n const currentRetryCount = msg.properties.headers?.[\"x-retry-count\"] || 0;\n await this.retryOrGiveUp(msg, currentRetryCount, options, parsedMessage);\n } else {\n // No retry configured - reject without requeue (already bounded:\n // a single attempt, no requeue loop possible).\n this.amqpChannel.nack(msg, false, false);\n }\n }\n },\n consumerOptions,\n );\n\n const subscription = new RabbitMQSubscription(\n subscriptionId,\n this.name,\n consumerTag,\n this.amqpChannel,\n );\n\n this.subscriptions.set(subscriptionId, subscription);\n\n return subscription;\n }\n\n /**\n * Unsubscribe by consumer ID\n */\n public async unsubscribeById(consumerId: string): Promise<void> {\n const subscription = this.subscriptions.get(consumerId);\n if (subscription) {\n await subscription.unsubscribe();\n this.subscriptions.delete(consumerId);\n }\n }\n\n /**\n * Stop consuming messages on this channel.\n * Cancels all active subscriptions gracefully.\n */\n public async stopConsuming(): Promise<void> {\n const cancellations = Array.from(this.subscriptions.values()).map(sub =>\n sub.unsubscribe(),\n );\n await Promise.all(cancellations);\n }\n\n /**\n * Send message to dead-letter queue\n */\n private async sendToDeadLetter(\n message: Message<TPayload>,\n deadLetterChannel: string,\n ): Promise<void> {\n const content = JSON.stringify({\n payload: message.payload,\n metadata: {\n ...message.metadata,\n originalChannel: this.name,\n },\n });\n\n this.amqpChannel.sendToQueue(deadLetterChannel, Buffer.from(content), { persistent: true });\n }\n\n /**\n * Dead-letter a message whose body couldn't be parsed into a {@link Message}\n * (e.g. `JSON.parse` itself threw before an envelope existed). Forwards the\n * raw AMQP bytes/properties as-is rather than re-deriving a payload, so a\n * malformed message isn't lost.\n */\n private sendToDeadLetterRaw(msg: any, deadLetterChannel: string): void {\n this.amqpChannel.sendToQueue(deadLetterChannel, msg.content, {\n ...msg.properties,\n persistent: true,\n });\n }\n\n /**\n * Bounded retry shared by the explicit `ctx.retry()` call and the automatic\n * catch when a handler throws without calling it itself — so both paths\n * honor the same cap instead of the automatic path silently requeueing\n * forever (see `subscribe()`'s catch block).\n *\n * Under the cap: republishes with an incremented `x-retry-count` header —\n * NOT a plain `nack(msg, false, true)`, which redelivers the original\n * message untouched and never advances the counter.\n *\n * At/over the cap: dead-letters if configured, otherwise drops the message\n * with a loud `log.error` (never a silent drop) so an operator can see a\n * poison message was discarded instead of it vanishing without a trace.\n */\n private async retryOrGiveUp(\n msg: any,\n currentRetryCount: number,\n options: SubscribeOptions | undefined,\n parsedMessage: Message<TPayload> | undefined,\n delay?: number,\n ): Promise<void> {\n const retryCount = currentRetryCount + 1;\n const maxRetries = options?.retry?.maxRetries ?? 3;\n\n if (retryCount > maxRetries) {\n if (options?.deadLetter) {\n if (parsedMessage) {\n await this.sendToDeadLetter(parsedMessage, options.deadLetter.channel);\n } else {\n this.sendToDeadLetterRaw(msg, options.deadLetter.channel);\n }\n } else {\n log.error(\n \"herald\",\n \"poison-message\",\n `Dropping message on channel \"${this.name}\" after ${retryCount - 1} failed ` +\n `${retryCount - 1 === 1 ? \"retry\" : \"retries\"} (maxRetries: ${maxRetries}) with no ` +\n `dead-letter channel configured.`,\n { channel: this.name, retryCount: retryCount - 1, maxRetries },\n );\n this.amqpChannel.reject(msg, false);\n return;\n }\n\n this.amqpChannel.ack(msg);\n return;\n }\n\n const headers: Record<string, unknown> = {\n ...msg.properties.headers,\n \"x-retry-count\": retryCount,\n };\n\n if (delay) {\n headers[\"x-delay\"] = delay;\n }\n\n this.amqpChannel.sendToQueue(this.name, msg.content, { ...msg.properties, headers });\n\n this.amqpChannel.ack(msg);\n }\n\n /**\n * Request-response pattern\n */\n public async request<TResponse = unknown>(\n payload: TPayload,\n options?: RequestOptions,\n ): Promise<TResponse> {\n await this.assert();\n\n const correlationId = randomUUID();\n const timeout = options?.timeout ?? 30000;\n\n // Create exclusive reply queue\n const { queue: replyQueue } = await this.amqpChannel.assertQueue(\"\", {\n exclusive: true,\n autoDelete: true,\n });\n\n return new Promise<TResponse>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error(`Request timeout after ${timeout}ms`));\n }, timeout);\n\n // Consume reply\n this.amqpChannel.consume(\n replyQueue,\n (msg: any) => {\n if (msg?.properties.correlationId === correlationId) {\n clearTimeout(timeoutId);\n const content = JSON.parse(msg.content.toString());\n resolve(content.payload as TResponse);\n }\n },\n { noAck: true },\n );\n\n // Send request\n const messageContent = JSON.stringify({\n payload,\n metadata: {\n messageId: randomUUID(),\n timestamp: new Date().toISOString(),\n correlationId,\n },\n });\n\n this.amqpChannel.sendToQueue(this.name, Buffer.from(messageContent), {\n correlationId,\n replyTo: replyQueue,\n expiration: timeout.toString(),\n ...options,\n });\n });\n }\n\n /**\n * Register response handler for RPC\n */\n public async respond<TResponse = unknown>(\n handler: ResponseHandler<TPayload, TResponse>,\n ): Promise<Subscription> {\n return this.subscribe(async (message, ctx) => {\n const response = await handler(message, ctx);\n await ctx.reply(response);\n await ctx.ack();\n });\n }\n\n /**\n * Get queue statistics\n */\n public async stats(): Promise<ChannelStats> {\n await this.assert();\n\n const queueInfo = await this.amqpChannel.checkQueue(this.name);\n\n return {\n name: this.name,\n messageCount: queueInfo.messageCount,\n consumerCount: queueInfo.consumerCount,\n };\n }\n\n /**\n * Purge all messages\n */\n public async purge(): Promise<number> {\n await this.assert();\n\n const result = await this.amqpChannel.purgeQueue(this.name);\n return result.messageCount;\n }\n\n /**\n * Check if queue exists\n */\n public async exists(): Promise<boolean> {\n try {\n await this.amqpChannel.checkQueue(this.name);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Delete the queue\n */\n public async delete(): Promise<void> {\n // Cancel all subscriptions\n for (const subscription of this.subscriptions.values()) {\n await subscription.unsubscribe();\n }\n this.subscriptions.clear();\n\n try {\n await this.amqpChannel.deleteQueue(this.name);\n } catch {\n // Ignore if queue doesn't exist\n }\n\n this.asserted = false;\n }\n}\n\n/**\n * RabbitMQ Subscription Implementation\n */\nclass RabbitMQSubscription implements Subscription {\n public readonly id: string;\n public readonly channel: string;\n public readonly consumerTag: string;\n\n private readonly amqpChannel: any;\n private _isActive = true;\n\n public constructor(id: string, channel: string, consumerTag: string, amqpChannel: any) {\n this.id = id;\n this.channel = channel;\n this.consumerTag = consumerTag;\n this.amqpChannel = amqpChannel;\n }\n\n public async unsubscribe(): Promise<void> {\n if (!this._isActive) return;\n\n await this.amqpChannel.cancel(this.consumerTag);\n this._isActive = false;\n }\n\n public async pause(): Promise<void> {\n // RabbitMQ doesn't have native pause, cancel consumer\n await this.amqpChannel.cancel(this.consumerTag);\n }\n\n public async resume(): Promise<void> {\n // Would need to re-subscribe - not directly supported\n throw new Error(\"Resume is not supported for RabbitMQ. Please create a new subscription.\");\n }\n\n public isActive(): boolean {\n return this._isActive;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rabbitmq-driver.mjs","names":[],"sources":["../../../../../../../../../../herald/src/drivers/rabbitmq/rabbitmq-driver.ts"],"sourcesContent":["import { EventEmitter } from \"node:events\";\r\nimport type { BrokerDriverContract, ChannelContract } from \"../../contracts\";\r\nimport { EventMessage } from \"../../message-managers/event-message\";\r\nimport { type EventConsumerClass } from \"../../message-managers/types\";\r\nimport type {\r\n BrokerDriverType,\r\n BrokerEvent,\r\n BrokerEventListener,\r\n ChannelOptions,\r\n HealthCheckResult,\r\n RabbitMQConnectionOptions,\r\n} from \"../../types\";\r\nimport { prepareConsumerSubscription } from \"./../../message-managers/prepare-consumer-subscription\";\r\nimport { RabbitMQChannel } from \"./rabbitmq-channel\";\r\n\r\n// ============================================================\r\n// Lazy-loaded amqplib Module\r\n// ============================================================\r\n\r\n/**\r\n * Shape of the lazily-imported amqplib module\r\n */\r\ntype AmqplibModule = typeof import(\"amqplib\");\r\n\r\n/**\r\n * The single amqplib load, shared by every caller.\r\n *\r\n * Memoized as a promise rather than as a resolved value so the loader stays\r\n * idempotent: without it, two callers arriving before the first `import()`\r\n * settles would each start their own load and the last writer would win, so a\r\n * caller could end up observing a module instance it never awaited.\r\n *\r\n * Resolves to `undefined` when amqplib is not installed.\r\n */\r\nlet amqplibModulePromise: Promise<AmqplibModule | undefined> | undefined;\r\n\r\n/**\r\n * Installation instructions for amqplib\r\n */\r\nconst AMQPLIB_INSTALL_INSTRUCTIONS = `\r\nRabbitMQ driver requires the amqplib package.\r\nInstall it with:\r\n\r\n npx warlock add herald --driver=rabbitmq\r\n\r\nOr manually:\r\n\r\n npm install amqplib\r\n pnpm add amqplib\r\n yarn add amqplib\r\n`.trim();\r\n\r\n/**\r\n * Strip `user:password@` credentials from any `amqp(s)://` URL embedded in a\r\n * string. The connection URL carries plaintext broker credentials, and\r\n * amqplib/Node's URL parser commonly echoes the offending URL verbatim in a\r\n * malformed-URL error (e.g. an unencoded `@`/`:` in the password) — applied\r\n * to every error `connect()` surfaces so a credential never reaches whatever\r\n * the host app does with a thrown connection error (console.error,\r\n * structured logging, an error tracker).\r\n */\r\nfunction redactAmqpCredentials(message: string): string {\r\n return message.replace(/(amqps?:\\/\\/)[^/@\\s]+@/gi, \"$1****:****@\");\r\n}\r\n\r\n/**\r\n * Load amqplib, reusing the single shared load for every caller.\r\n *\r\n * @returns The amqplib module, or `undefined` when it is not installed.\r\n */\r\nfunction loadAmqplibModule(): Promise<AmqplibModule | undefined> {\r\n if (!amqplibModulePromise) {\r\n amqplibModulePromise = import(\"amqplib\").catch(() => undefined);\r\n }\r\n\r\n return amqplibModulePromise;\r\n}\r\n\r\n// ============================================================\r\n// RabbitMQ Driver\r\n// ============================================================\r\n\r\n/**\r\n * RabbitMQ Driver\r\n *\r\n * Implementation of BrokerDriverContract for RabbitMQ/AMQP.\r\n *\r\n * **Important:** This driver requires the `amqplib` package to be installed.\r\n * Install it with: `npx warlock add herald --driver=rabbitmq` or `npm install amqplib`\r\n *\r\n * @example\r\n * ```typescript\r\n * const driver = new RabbitMQDriver({\r\n * driver: \"rabbitmq\",\r\n * host: \"localhost\",\r\n * port: 5672,\r\n * username: \"guest\",\r\n * password: \"guest\",\r\n * });\r\n *\r\n * await driver.connect();\r\n * const channel = driver.channel(\"user.created\");\r\n * ```\r\n */\r\nexport class RabbitMQDriver implements BrokerDriverContract {\r\n public readonly name = \"rabbitmq\" as const;\r\n\r\n public readonly consumers: EventConsumerClass[] = [];\r\n\r\n private readonly options: RabbitMQConnectionOptions;\r\n private readonly events = new EventEmitter();\r\n private readonly channels = new Map<string, ChannelContract<any>>();\r\n\r\n private connection: any = null;\r\n private amqpChannel: any = null;\r\n private _isConnected = false;\r\n\r\n /**\r\n * Create a new RabbitMQ driver\r\n *\r\n * @param options - RabbitMQ connection options\r\n */\r\n public constructor(options: RabbitMQConnectionOptions) {\r\n this.options = options;\r\n }\r\n\r\n /**\r\n * Whether connected to RabbitMQ\r\n */\r\n public get isConnected(): boolean {\r\n return this._isConnected;\r\n }\r\n\r\n /**\r\n * Subscribe the given consumer class to the driver\r\n *\r\n * @param consumer - Consumer class to subscribe\r\n *\r\n * @example\r\n * ```typescript\r\n * driver.subscribe(UserUpdatedConsumer);\r\n * ```\r\n */\r\n public subscribe(Consumer: EventConsumerClass) {\r\n if (this.isConnected) {\r\n this.channel(Consumer.eventName).subscribe(\r\n prepareConsumerSubscription(Consumer, (error, eventName) => {\r\n this.events.emit(\"error\", error, eventName);\r\n }),\r\n {\r\n consumerId: Consumer.consumerId,\r\n },\r\n );\r\n } else {\r\n this.consumers.push(Consumer);\r\n }\r\n\r\n return () => {\r\n this.unsubscribe(Consumer);\r\n };\r\n }\r\n\r\n /**\r\n * Unsubscribe the given consumer\r\n */\r\n public unsubscribe(Consumer: EventConsumerClass): void {\r\n if (this.isConnected) {\r\n this.channel(Consumer.eventName).unsubscribeById(Consumer.consumerId);\r\n }\r\n const index = this.consumers.indexOf(Consumer);\r\n if (index > -1) {\r\n this.consumers.splice(index, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Publish the given event message.\r\n * Auto-creates the channel if it hasn't been accessed before.\r\n */\r\n public publish<TPayload = Record<string, any>>(event: EventMessage<TPayload>): void {\r\n this.channel(event.eventName).publish(event.serialize());\r\n }\r\n\r\n /**\r\n * Connect to RabbitMQ\r\n */\r\n public async connect(): Promise<void> {\r\n const amqplib = await loadAmqplibModule();\r\n\r\n if (!amqplib) {\r\n throw new Error(`amqplib is not installed.\\n\\n${AMQPLIB_INSTALL_INSTRUCTIONS}`);\r\n }\r\n\r\n try {\r\n // Build connection URL\r\n const url = this.buildConnectionUrl();\r\n\r\n // Build connection options merging our options with native client options\r\n const connectOptions = {\r\n heartbeat: this.options.heartbeat ?? 60,\r\n timeout: this.options.connectionTimeout,\r\n // Merge native amqplib client options\r\n ...this.options.clientOptions,\r\n };\r\n\r\n // Connect using cached module\r\n this.connection = await amqplib.connect(url, connectOptions);\r\n\r\n // Create channel\r\n this.amqpChannel = await this.connection.createChannel();\r\n\r\n // Set prefetch if specified\r\n if (this.options.prefetch) {\r\n await this.amqpChannel.prefetch(this.options.prefetch);\r\n }\r\n\r\n this._isConnected = true;\r\n this.events.emit(\"connected\");\r\n\r\n for (const consumer of this.consumers) {\r\n this.subscribe(consumer);\r\n }\r\n\r\n this.consumers.length = 0;\r\n\r\n // Handle connection close\r\n this.connection.on(\"close\", () => {\r\n this._isConnected = false;\r\n this.events.emit(\"disconnected\");\r\n\r\n if (this.options.reconnect !== false) {\r\n this.handleReconnect();\r\n }\r\n });\r\n\r\n // Handle errors\r\n this.connection.on(\"error\", (error: Error) => {\r\n this.events.emit(\"error\", error);\r\n });\r\n } catch (error) {\r\n this._isConnected = false;\r\n const message = error instanceof Error ? error.message : String(error);\r\n throw new Error(`Failed to connect to RabbitMQ: ${redactAmqpCredentials(message)}`);\r\n }\r\n }\r\n\r\n /**\r\n * Build connection URL from options\r\n */\r\n private buildConnectionUrl(): string {\r\n if (this.options.uri) {\r\n return this.options.uri;\r\n }\r\n\r\n const protocol = \"amqp\";\r\n const host = this.options.host ?? \"localhost\";\r\n const port = this.options.port ?? 5672;\r\n const vhost = this.options.vhost ?? \"/\";\r\n // URI-encoded so a credential containing a reserved URL character\r\n // (`@`, `:`, `/`, whitespace — common in generated secrets) can't produce\r\n // a malformed URL whose parser error echoes the raw credential back.\r\n const username = encodeURIComponent(this.options.username ?? \"guest\");\r\n const password = encodeURIComponent(this.options.password ?? \"guest\");\r\n\r\n const encodedVhost = encodeURIComponent(vhost);\r\n\r\n return `${protocol}://${username}:${password}@${host}:${port}/${encodedVhost}`;\r\n }\r\n\r\n /**\r\n * Handle reconnection\r\n */\r\n private async handleReconnect(): Promise<void> {\r\n const delay = this.options.reconnectDelay ?? 5000;\r\n let attempt = 0;\r\n\r\n const tryReconnect = async () => {\r\n attempt++;\r\n this.events.emit(\"reconnecting\", attempt);\r\n\r\n try {\r\n await this.connect();\r\n } catch {\r\n setTimeout(tryReconnect, delay);\r\n }\r\n };\r\n\r\n setTimeout(tryReconnect, delay);\r\n }\r\n\r\n /**\r\n * Disconnect from RabbitMQ\r\n */\r\n public async disconnect(): Promise<void> {\r\n if (this.amqpChannel) {\r\n try {\r\n await this.amqpChannel.close();\r\n } catch {\r\n // Ignore close errors\r\n }\r\n this.amqpChannel = null;\r\n }\r\n\r\n if (this.connection) {\r\n try {\r\n await this.connection.close();\r\n } catch {\r\n // Ignore close errors\r\n }\r\n this.connection = null;\r\n }\r\n\r\n this._isConnected = false;\r\n this.events.emit(\"disconnected\");\r\n }\r\n\r\n /**\r\n * Register event listener\r\n */\r\n public on(event: BrokerEvent, listener: BrokerEventListener): void {\r\n this.events.on(event, listener as any);\r\n }\r\n\r\n /**\r\n * Remove event listener\r\n */\r\n public off(event: BrokerEvent, listener: BrokerEventListener): void {\r\n this.events.off(event, listener as any);\r\n }\r\n\r\n /**\r\n * Get or create a channel\r\n */\r\n public channel<TPayload = unknown>(\r\n name: string,\r\n options?: ChannelOptions<TPayload>,\r\n ): ChannelContract<TPayload> {\r\n // Check cache\r\n const existing = this.channels.get(name);\r\n if (existing) {\r\n return existing as ChannelContract<TPayload>;\r\n }\r\n\r\n // Create new channel\r\n const channel = new RabbitMQChannel<TPayload>(name, this.amqpChannel, options);\r\n\r\n this.channels.set(name, channel);\r\n return channel;\r\n }\r\n\r\n /**\r\n * Start consuming messages\r\n */\r\n public async startConsuming(): Promise<void> {\r\n // Channels start consuming when subscribe() is called\r\n // This method is for batch start if needed\r\n }\r\n\r\n /**\r\n * Stop consuming messages from all subscribed channels.\r\n * Gracefully cancels all active consumers.\r\n */\r\n public async stopConsuming(): Promise<void> {\r\n const stops = Array.from(this.channels.values()).map(channel =>\r\n (channel as RabbitMQChannel<any>).stopConsuming(),\r\n );\r\n await Promise.all(stops);\r\n }\r\n\r\n /**\r\n * Health check\r\n */\r\n public async healthCheck(): Promise<HealthCheckResult> {\r\n if (!this._isConnected || !this.connection) {\r\n return {\r\n healthy: false,\r\n error: \"Not connected to RabbitMQ\",\r\n };\r\n }\r\n\r\n const start = Date.now();\r\n\r\n try {\r\n // Simple check - verify channel is open\r\n await this.amqpChannel.checkQueue(\"amq.rabbitmq.reply-to\").catch(() => {\r\n // Queue might not exist, but if we get here, connection is alive\r\n });\r\n\r\n return {\r\n healthy: true,\r\n latency: Date.now() - start,\r\n };\r\n } catch (error) {\r\n return {\r\n healthy: false,\r\n error: error instanceof Error ? error.message : String(error),\r\n latency: Date.now() - start,\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * Get all channel names\r\n */\r\n public getChannelNames(): string[] {\r\n return Array.from(this.channels.keys());\r\n }\r\n\r\n /**\r\n * Close a specific channel\r\n */\r\n public async closeChannel(name: string): Promise<void> {\r\n const channel = this.channels.get(name);\r\n if (channel) {\r\n await channel.delete();\r\n this.channels.delete(name);\r\n }\r\n }\r\n\r\n /**\r\n * Get the raw AMQP channel (for advanced use)\r\n */\r\n public getRawChannel(): any {\r\n return this.amqpChannel;\r\n }\r\n\r\n /**\r\n * Get the raw connection (for advanced use)\r\n */\r\n public getRawConnection(): any {\r\n return this.connection;\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rabbitmq-driver.mjs","names":[],"sources":["../../../../../../../../../../herald/src/drivers/rabbitmq/rabbitmq-driver.ts"],"sourcesContent":["import { EventEmitter } from \"node:events\";\nimport type { BrokerDriverContract, ChannelContract } from \"../../contracts\";\nimport { EventMessage } from \"../../message-managers/event-message\";\nimport { type EventConsumerClass } from \"../../message-managers/types\";\nimport type {\n BrokerDriverType,\n BrokerEvent,\n BrokerEventListener,\n ChannelOptions,\n HealthCheckResult,\n RabbitMQConnectionOptions,\n} from \"../../types\";\nimport { prepareConsumerSubscription } from \"./../../message-managers/prepare-consumer-subscription\";\nimport { RabbitMQChannel } from \"./rabbitmq-channel\";\n\n// ============================================================\n// Lazy-loaded amqplib Module\n// ============================================================\n\n/**\n * Shape of the lazily-imported amqplib module\n */\ntype AmqplibModule = typeof import(\"amqplib\");\n\n/**\n * The single amqplib load, shared by every caller.\n *\n * Memoized as a promise rather than as a resolved value so the loader stays\n * idempotent: without it, two callers arriving before the first `import()`\n * settles would each start their own load and the last writer would win, so a\n * caller could end up observing a module instance it never awaited.\n *\n * Resolves to `undefined` when amqplib is not installed.\n */\nlet amqplibModulePromise: Promise<AmqplibModule | undefined> | undefined;\n\n/**\n * Installation instructions for amqplib\n */\nconst AMQPLIB_INSTALL_INSTRUCTIONS = `\nRabbitMQ driver requires the amqplib package.\nInstall it with:\n\n npx warlock add herald --driver=rabbitmq\n\nOr manually:\n\n npm install amqplib\n pnpm add amqplib\n yarn add amqplib\n`.trim();\n\n/**\n * Strip `user:password@` credentials from any `amqp(s)://` URL embedded in a\n * string. The connection URL carries plaintext broker credentials, and\n * amqplib/Node's URL parser commonly echoes the offending URL verbatim in a\n * malformed-URL error (e.g. an unencoded `@`/`:` in the password) — applied\n * to every error `connect()` surfaces so a credential never reaches whatever\n * the host app does with a thrown connection error (console.error,\n * structured logging, an error tracker).\n */\nfunction redactAmqpCredentials(message: string): string {\n return message.replace(/(amqps?:\\/\\/)[^/@\\s]+@/gi, \"$1****:****@\");\n}\n\n/**\n * Load amqplib, reusing the single shared load for every caller.\n *\n * @returns The amqplib module, or `undefined` when it is not installed.\n */\nfunction loadAmqplibModule(): Promise<AmqplibModule | undefined> {\n if (!amqplibModulePromise) {\n amqplibModulePromise = import(\"amqplib\").catch(() => undefined);\n }\n\n return amqplibModulePromise;\n}\n\n// ============================================================\n// RabbitMQ Driver\n// ============================================================\n\n/**\n * RabbitMQ Driver\n *\n * Implementation of BrokerDriverContract for RabbitMQ/AMQP.\n *\n * **Important:** This driver requires the `amqplib` package to be installed.\n * Install it with: `npx warlock add herald --driver=rabbitmq` or `npm install amqplib`\n *\n * @example\n * ```typescript\n * const driver = new RabbitMQDriver({\n * driver: \"rabbitmq\",\n * host: \"localhost\",\n * port: 5672,\n * username: \"guest\",\n * password: \"guest\",\n * });\n *\n * await driver.connect();\n * const channel = driver.channel(\"user.created\");\n * ```\n */\nexport class RabbitMQDriver implements BrokerDriverContract {\n public readonly name = \"rabbitmq\" as const;\n\n public readonly consumers: EventConsumerClass[] = [];\n\n private readonly options: RabbitMQConnectionOptions;\n private readonly events = new EventEmitter();\n private readonly channels = new Map<string, ChannelContract<any>>();\n\n private connection: any = null;\n private amqpChannel: any = null;\n private _isConnected = false;\n\n /**\n * Create a new RabbitMQ driver\n *\n * @param options - RabbitMQ connection options\n */\n public constructor(options: RabbitMQConnectionOptions) {\n this.options = options;\n }\n\n /**\n * Whether connected to RabbitMQ\n */\n public get isConnected(): boolean {\n return this._isConnected;\n }\n\n /**\n * Subscribe the given consumer class to the driver\n *\n * @param consumer - Consumer class to subscribe\n *\n * @example\n * ```typescript\n * driver.subscribe(UserUpdatedConsumer);\n * ```\n */\n public subscribe(Consumer: EventConsumerClass) {\n if (this.isConnected) {\n this.channel(Consumer.eventName).subscribe(\n prepareConsumerSubscription(Consumer, (error, eventName) => {\n this.events.emit(\"error\", error, eventName);\n }),\n {\n consumerId: Consumer.consumerId,\n },\n );\n } else {\n this.consumers.push(Consumer);\n }\n\n return () => {\n this.unsubscribe(Consumer);\n };\n }\n\n /**\n * Unsubscribe the given consumer\n */\n public unsubscribe(Consumer: EventConsumerClass): void {\n if (this.isConnected) {\n this.channel(Consumer.eventName).unsubscribeById(Consumer.consumerId);\n }\n const index = this.consumers.indexOf(Consumer);\n if (index > -1) {\n this.consumers.splice(index, 1);\n }\n }\n\n /**\n * Publish the given event message.\n * Auto-creates the channel if it hasn't been accessed before.\n */\n public publish<TPayload = Record<string, any>>(event: EventMessage<TPayload>): void {\n this.channel(event.eventName).publish(event.serialize());\n }\n\n /**\n * Connect to RabbitMQ\n */\n public async connect(): Promise<void> {\n const amqplib = await loadAmqplibModule();\n\n if (!amqplib) {\n throw new Error(`amqplib is not installed.\\n\\n${AMQPLIB_INSTALL_INSTRUCTIONS}`);\n }\n\n try {\n // Build connection URL\n const url = this.buildConnectionUrl();\n\n // Build connection options merging our options with native client options\n const connectOptions = {\n heartbeat: this.options.heartbeat ?? 60,\n timeout: this.options.connectionTimeout,\n // Merge native amqplib client options\n ...this.options.clientOptions,\n };\n\n // Connect using cached module\n this.connection = await amqplib.connect(url, connectOptions);\n\n // Create channel\n this.amqpChannel = await this.connection.createChannel();\n\n // Set prefetch if specified\n if (this.options.prefetch) {\n await this.amqpChannel.prefetch(this.options.prefetch);\n }\n\n this._isConnected = true;\n this.events.emit(\"connected\");\n\n for (const consumer of this.consumers) {\n this.subscribe(consumer);\n }\n\n this.consumers.length = 0;\n\n // Handle connection close\n this.connection.on(\"close\", () => {\n this._isConnected = false;\n this.events.emit(\"disconnected\");\n\n if (this.options.reconnect !== false) {\n this.handleReconnect();\n }\n });\n\n // Handle errors\n this.connection.on(\"error\", (error: Error) => {\n this.events.emit(\"error\", error);\n });\n } catch (error) {\n this._isConnected = false;\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to connect to RabbitMQ: ${redactAmqpCredentials(message)}`);\n }\n }\n\n /**\n * Build connection URL from options\n */\n private buildConnectionUrl(): string {\n if (this.options.uri) {\n return this.options.uri;\n }\n\n const protocol = \"amqp\";\n const host = this.options.host ?? \"localhost\";\n const port = this.options.port ?? 5672;\n const vhost = this.options.vhost ?? \"/\";\n // URI-encoded so a credential containing a reserved URL character\n // (`@`, `:`, `/`, whitespace — common in generated secrets) can't produce\n // a malformed URL whose parser error echoes the raw credential back.\n const username = encodeURIComponent(this.options.username ?? \"guest\");\n const password = encodeURIComponent(this.options.password ?? \"guest\");\n\n const encodedVhost = encodeURIComponent(vhost);\n\n return `${protocol}://${username}:${password}@${host}:${port}/${encodedVhost}`;\n }\n\n /**\n * Handle reconnection\n */\n private async handleReconnect(): Promise<void> {\n const delay = this.options.reconnectDelay ?? 5000;\n let attempt = 0;\n\n const tryReconnect = async () => {\n attempt++;\n this.events.emit(\"reconnecting\", attempt);\n\n try {\n await this.connect();\n } catch {\n setTimeout(tryReconnect, delay);\n }\n };\n\n setTimeout(tryReconnect, delay);\n }\n\n /**\n * Disconnect from RabbitMQ\n */\n public async disconnect(): Promise<void> {\n if (this.amqpChannel) {\n try {\n await this.amqpChannel.close();\n } catch {\n // Ignore close errors\n }\n this.amqpChannel = null;\n }\n\n if (this.connection) {\n try {\n await this.connection.close();\n } catch {\n // Ignore close errors\n }\n this.connection = null;\n }\n\n this._isConnected = false;\n this.events.emit(\"disconnected\");\n }\n\n /**\n * Register event listener\n */\n public on(event: BrokerEvent, listener: BrokerEventListener): void {\n this.events.on(event, listener as any);\n }\n\n /**\n * Remove event listener\n */\n public off(event: BrokerEvent, listener: BrokerEventListener): void {\n this.events.off(event, listener as any);\n }\n\n /**\n * Get or create a channel\n */\n public channel<TPayload = unknown>(\n name: string,\n options?: ChannelOptions<TPayload>,\n ): ChannelContract<TPayload> {\n // Check cache\n const existing = this.channels.get(name);\n if (existing) {\n return existing as ChannelContract<TPayload>;\n }\n\n // Create new channel\n const channel = new RabbitMQChannel<TPayload>(name, this.amqpChannel, options);\n\n this.channels.set(name, channel);\n return channel;\n }\n\n /**\n * Start consuming messages\n */\n public async startConsuming(): Promise<void> {\n // Channels start consuming when subscribe() is called\n // This method is for batch start if needed\n }\n\n /**\n * Stop consuming messages from all subscribed channels.\n * Gracefully cancels all active consumers.\n */\n public async stopConsuming(): Promise<void> {\n const stops = Array.from(this.channels.values()).map(channel =>\n (channel as RabbitMQChannel<any>).stopConsuming(),\n );\n await Promise.all(stops);\n }\n\n /**\n * Health check\n */\n public async healthCheck(): Promise<HealthCheckResult> {\n if (!this._isConnected || !this.connection) {\n return {\n healthy: false,\n error: \"Not connected to RabbitMQ\",\n };\n }\n\n const start = Date.now();\n\n try {\n // Simple check - verify channel is open\n await this.amqpChannel.checkQueue(\"amq.rabbitmq.reply-to\").catch(() => {\n // Queue might not exist, but if we get here, connection is alive\n });\n\n return {\n healthy: true,\n latency: Date.now() - start,\n };\n } catch (error) {\n return {\n healthy: false,\n error: error instanceof Error ? error.message : String(error),\n latency: Date.now() - start,\n };\n }\n }\n\n /**\n * Get all channel names\n */\n public getChannelNames(): string[] {\n return Array.from(this.channels.keys());\n }\n\n /**\n * Close a specific channel\n */\n public async closeChannel(name: string): Promise<void> {\n const channel = this.channels.get(name);\n if (channel) {\n await channel.delete();\n this.channels.delete(name);\n }\n }\n\n /**\n * Get the raw AMQP channel (for advanced use)\n */\n public getRawChannel(): any {\n return this.amqpChannel;\n }\n\n /**\n * Get the raw connection (for advanced use)\n */\n public getRawConnection(): any {\n return this.connection;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prepare-consumer-subscription.mjs","names":[],"sources":["../../../../../../../../../herald/src/message-managers/prepare-consumer-subscription.ts"],"sourcesContent":["import type { MessageHandler } from \"./../types\";\
|
|
1
|
+
{"version":3,"file":"prepare-consumer-subscription.mjs","names":[],"sources":["../../../../../../../../../herald/src/message-managers/prepare-consumer-subscription.ts"],"sourcesContent":["import type { MessageHandler } from \"./../types\";\nimport { type EventConsumerClass } from \"./types\";\n\nexport function prepareConsumerSubscription(\n Consumer: EventConsumerClass,\n onError?: (error: unknown, consumerName: string) => void,\n) {\n const callback: MessageHandler<any> = async (message, ctx) => {\n const envelope = message.payload;\n let eventPayload = envelope.payload;\n\n if (envelope.version) {\n if (!Consumer.isAcceptedVersion(Number(envelope.version))) {\n ctx.ack(); // Acknowledge but don't process\n return;\n }\n }\n\n const consumer = new Consumer();\n\n if (consumer.schema) {\n const result = await consumer.validate(eventPayload);\n if (!result || result.isValid === false) {\n ctx.nack();\n return;\n }\n\n eventPayload = result.data;\n }\n try {\n await consumer.handle(eventPayload, {\n payload: eventPayload,\n eventName: Consumer.eventName,\n messageId: message.metadata.messageId!,\n occurredAt: envelope.occurredAt ? new Date(envelope.occurredAt) : undefined,\n metadata: envelope.metadata,\n version: envelope.version,\n message,\n });\n ctx.ack(); // Auto-ack on success?\n } catch (error) {\n // Bounded retry instead of an unconditional requeue: a message that\n // reliably throws (bad payload, a handler bug, a downstream outage)\n // would otherwise be nack+requeued forever, pinning the consumer in a\n // hot loop. `ctx.retry()` caps redelivery and dead-letters/drops (with\n // a loud log) once the cap is hit.\n await ctx.retry();\n if (onError) {\n onError(error, Consumer.eventName);\n }\n }\n };\n\n return callback;\n}\n"],"mappings":";AAGA,SAAgB,4BACd,UACA,SACA;CACA,MAAM,WAAgC,OAAO,SAAS,QAAQ;EAC5D,MAAM,WAAW,QAAQ;EACzB,IAAI,eAAe,SAAS;EAE5B,IAAI,SAAS,SACX;OAAI,CAAC,SAAS,kBAAkB,OAAO,SAAS,OAAO,CAAC,GAAG;IACzD,IAAI,IAAI;IACR;GACF;;EAGF,MAAM,WAAW,IAAI,SAAS;EAE9B,IAAI,SAAS,QAAQ;GACnB,MAAM,SAAS,MAAM,SAAS,SAAS,YAAY;GACnD,IAAI,CAAC,UAAU,OAAO,YAAY,OAAO;IACvC,IAAI,KAAK;IACT;GACF;GAEA,eAAe,OAAO;EACxB;EACA,IAAI;GACF,MAAM,SAAS,OAAO,cAAc;IAClC,SAAS;IACT,WAAW,SAAS;IACpB,WAAW,QAAQ,SAAS;IAC5B,YAAY,SAAS,aAAa,IAAI,KAAK,SAAS,UAAU,IAAI;IAClE,UAAU,SAAS;IACnB,SAAS,SAAS;IAClB;GACF,CAAC;GACD,IAAI,IAAI;EACV,SAAS,OAAO;GAMd,MAAM,IAAI,MAAM;GAChB,IAAI,SACF,QAAQ,OAAO,SAAS,SAAS;EAErC;CACF;CAEA,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect-to-broker.mjs","names":[],"sources":["../../../../../../../../../herald/src/utils/connect-to-broker.ts"],"sourcesContent":["import { Broker, brokerRegistry } from \"../communicators\";\
|
|
1
|
+
{"version":3,"file":"connect-to-broker.mjs","names":[],"sources":["../../../../../../../../../herald/src/utils/connect-to-broker.ts"],"sourcesContent":["import { Broker, brokerRegistry } from \"../communicators\";\nimport type { BrokerDriverContract, ChannelContract } from \"../contracts\";\nimport { type EventConsumerClass, EventMessage } from \"../message-managers\";\nimport type { ChannelOptions, ConnectionOptions, RabbitMQConnectionOptions } from \"../types\";\n\n/**\n * Connect to a message broker and register it.\n *\n * This is a high-level utility function that simplifies connection setup\n * for most projects. It handles driver instantiation, connection,\n * broker creation, and automatic registration.\n *\n * **Supported Drivers:**\n * - `rabbitmq` (default) - RabbitMQ/AMQP driver\n * - `kafka` - Apache Kafka driver (coming soon)\n *\n * @param options - Connection configuration options\n * @returns A connected and registered Broker instance\n * @throws {Error} If connection fails or driver is not implemented\n *\n * @example\n * ```typescript\n * // RabbitMQ connection\n * const broker = await connectToBroker({\n * driver: \"rabbitmq\",\n * host: \"localhost\",\n * port: 5672,\n * username: \"guest\",\n * password: \"guest\",\n * });\n *\n * // Use the broker\n * await broker.channel(\"user.created\").publish({ userId: 1 });\n * ```\n *\n * @example\n * ```typescript\n * // Multiple brokers\n * await connectToBroker({\n * driver: \"rabbitmq\",\n * name: \"notifications\",\n * isDefault: true,\n * host: process.env.RABBITMQ_HOST,\n * });\n *\n * await connectToBroker({\n * driver: \"rabbitmq\",\n * name: \"analytics\",\n * host: process.env.ANALYTICS_RABBITMQ_HOST,\n * });\n *\n * // Use default broker\n * herald().channel(\"notifications\").publish({ ... });\n *\n * // Use specific broker\n * herald(\"analytics\").channel(\"events\").publish({ ... });\n * ```\n */\nexport async function connectToBroker(options: ConnectionOptions): Promise<Broker> {\n // Default values\n const driverType = options.driver ?? \"rabbitmq\";\n const brokerName = options.name ?? \"default\";\n const isDefault = options.isDefault ?? true;\n\n // Create driver based on type\n let driver: BrokerDriverContract;\n\n switch (driverType) {\n case \"rabbitmq\": {\n const rabbitOptions = options as RabbitMQConnectionOptions;\n // Dynamic import to avoid requiring amqplib if not used\n const { RabbitMQDriver } = await import(\"../drivers/rabbitmq/rabbitmq-driver\");\n driver = new RabbitMQDriver(rabbitOptions);\n break;\n }\n\n case \"kafka\": {\n // const kafkaOptions = options as KafkaConnectionOptions;\n // Dynamic import to avoid requiring kafkajs if not used\n throw new Error(\n \"Kafka driver is not yet implemented. Coming soon! For now, please use RabbitMQ.\",\n );\n }\n\n default:\n throw new Error(`Unknown driver: \"${driverType}\". Supported drivers: rabbitmq, kafka`);\n }\n\n // Create broker\n const broker = brokerRegistry.register({\n name: brokerName,\n driver,\n isDefault,\n });\n\n // Connect to the message broker\n try {\n await driver.connect();\n } catch (error) {\n throw new Error(\n `Failed to connect to ${driverType}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n return broker;\n}\n\n/**\n * Get a broker by name or the default one.\n *\n * This is the main entry point for using brokers in your application.\n * Named after the package — `herald()` carries your messages!\n *\n * @param name - Optional broker name (uses default if not provided)\n * @returns Broker instance\n * @throws MissingBrokerError if broker not found\n *\n * @example\n * // Get default broker\n * const channel = herald().channel(\"user.created\");\n * await channel.publish({ userId: 1 });\n *\n * // Get specific broker\n * const analyticsChannel = herald(\"analytics\").channel(\"events\");\n * await analyticsChannel.publish({ event: \"page_view\" });\n *\n * // Subscribe to messages\n * herald()\n * .channel<UserPayload>(\"user.created\")\n * .subscribe(async (message, ctx) => {\n * console.log(\"User created:\", message.payload);\n * await ctx.ack();\n * });\n * ```\n */\nexport function herald(name?: string): Broker {\n return brokerRegistry.get(name);\n}\n\n/**\n * Get channel instance for the given name from default broker.\n *\n * Shorthand for `herald().channel(name, options)`.\n *\n * @param name - Channel name\n * @param options - Optional channel options\n * @returns Channel instance\n * @throws MissingBrokerError if broker not found\n *\n * @example\n * ```typescript\n * const channel = heraldChannel(\"user.created\");\n * await channel.publish({ userId: 1 });\n * ```\n */\nexport function heraldChannel<TPayload = unknown>(\n name: string,\n options?: ChannelOptions<TPayload>,\n): ChannelContract<TPayload> {\n return herald().channel<TPayload>(name, options);\n}\n\n/**\n * Publish an EventMessage to the default broker.\n *\n * @param event - Event message to publish\n * @returns Promise that resolves when the event is published\n * @throws Error if the broker is not connected\n *\n * @example\n * ```typescript\n * await publishEvent(new UserUpdatedEvent({ id: 1, name: \"John Doe\" }));\n * ```\n */\nexport async function publishEvent<TPayload = Record<string, any>>(event: EventMessage<TPayload>) {\n return herald().publish(event);\n}\n\n/**\n * Subscribe an EventConsumer class to the default broker.\n *\n * @param Consumer - Event consumer class\n * @returns Unsubscribe function\n * @throws MissingBrokerError if broker not found\n *\n * @example\n * ```typescript\n * await subscribeConsumer(UserUpdatedConsumer);\n * ```\n */\nexport async function subscribeConsumer<TPayload = Record<string, any>>(\n Consumer: EventConsumerClass<TPayload>,\n) {\n return herald().subscribe(Consumer);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,eAAsB,gBAAgB,SAA6C;CAEjF,MAAM,aAAa,QAAQ,UAAU;CACrC,MAAM,aAAa,QAAQ,QAAQ;CACnC,MAAM,YAAY,QAAQ,aAAa;CAGvC,IAAI;CAEJ,QAAQ,YAAR;EACE,KAAK,YAAY;GACf,MAAM,gBAAgB;GAEtB,MAAM,EAAE,mBAAmB,MAAM,OAAO;GACxC,SAAS,IAAI,eAAe,aAAa;GACzC;EACF;EAEA,KAAK,SAGH,MAAM,IAAI,MACR,iFACF;EAGF,SACE,MAAM,IAAI,MAAM,oBAAoB,WAAW,sCAAsC;CACzF;CAGA,MAAM,SAAS,eAAe,SAAS;EACrC,MAAM;EACN;EACA;CACF,CAAC;CAGD,IAAI;EACF,MAAM,OAAO,QAAQ;CACvB,SAAS,OAAO;EACd,MAAM,IAAI,MACR,wBAAwB,WAAW,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAC9F;CACF;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,OAAO,MAAuB;CAC5C,OAAO,eAAe,IAAI,IAAI;AAChC;;;;;;;;;;;;;;;;;AAkBA,SAAgB,cACd,MACA,SAC2B;CAC3B,OAAO,OAAO,CAAC,CAAC,QAAkB,MAAM,OAAO;AACjD;;;;;;;;;;;;;AAcA,eAAsB,aAA6C,OAA+B;CAChG,OAAO,OAAO,CAAC,CAAC,QAAQ,KAAK;AAC/B;;;;;;;;;;;;;AAcA,eAAsB,kBACpB,UACA;CACA,OAAO,OAAO,CAAC,CAAC,UAAU,QAAQ;AACpC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":[],"sources":["../../../../../../../../seal/src/config.ts"],"sourcesContent":["/**\
|
|
1
|
+
{"version":3,"file":"config.mjs","names":[],"sources":["../../../../../../../../seal/src/config.ts"],"sourcesContent":["/**\n * Seal Configuration\n *\n * Global configuration for the Seal validation library\n */\n\nimport type { AttributeTranslation, RuleTranslation } from \"./types\";\n\nexport type TranslateRuleCallback = (\n ruleTranslation: RuleTranslation,\n) => string;\n\nexport type TranslateAttributeCallback = (\n attributeTranslation: AttributeTranslation,\n) => string;\n\nexport type SealConfig = {\n /**\n * Translation function for error messages\n * Default: returns the key with attributes replaced\n */\n translateRule?: TranslateRuleCallback;\n /**\n * Translation function for attribute messages\n */\n translateAttribute?: TranslateAttributeCallback;\n\n /**\n * Default to first error only in validation\n * Default: true\n */\n firstErrorOnly?: boolean;\n};\n\n/**\n * Global Seal configuration\n */\nconst config: SealConfig = {\n firstErrorOnly: true,\n};\n\n/**\n * Configure Seal globally\n *\n * @example\n * ```ts\n * import { configureSeal } from \"@warlock.js/seal\";\n * import { trans } from \"@mongez/localization\";\n *\n * configureSeal({\n * translateRule: (ruleTranslation) => trans(`validation.${ruleTranslation.rule.name}`, ruleTranslation.attributes),\n * translateAttribute: (attributeTranslation) => trans(`validation.attributes.${attributeTranslation.rule.name}.${attributeTranslation.attribute}`, attributeTranslation.context.allValues),\n * });\n * ```\n */\nexport function configureSeal(options: Partial<SealConfig>): void {\n Object.assign(config, options);\n}\n\n/**\n * Get current Seal configuration\n */\nexport function getSealConfig(): SealConfig {\n return config;\n}\n\n/**\n * Reset configuration to defaults\n */\nexport function resetSealConfig(): void {\n delete config.translateRule;\n delete config.translateAttribute;\n\n config.firstErrorOnly = true;\n}\n"],"mappings":";;;;AAqCA,MAAM,SAAqB,EACzB,gBAAgB,KAClB;;;;AAuBA,SAAgB,gBAA4B;CAC1C,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.mjs","names":[],"sources":["../../../../../../../../../seal/src/factory/validate.ts"],"sourcesContent":["import { getSealConfig } from \"../config\";\
|
|
1
|
+
{"version":3,"file":"validate.mjs","names":[],"sources":["../../../../../../../../../seal/src/factory/validate.ts"],"sourcesContent":["import { getSealConfig } from \"../config\";\nimport type { SchemaContext, ValidationResult } from \"../types\";\nimport type { BaseValidator } from \"../validators\";\nimport type { ValidateOptions } from \"./validators\";\n\n/**\n * Validate data against a schema\n */\nexport const validate = async <T extends BaseValidator>(\n schema: T,\n data: any, // Temporarily use any - will fix type inference\n { context: extendedContext, ...configurations }: ValidateOptions = getSealConfig() || {},\n): Promise<ValidationResult> => {\n const context: SchemaContext = {\n allValues: data,\n parent: null,\n value: data,\n key: \"\",\n path: \"\",\n context: extendedContext,\n rootContext: extendedContext,\n translateRule(ruleTranslation) {\n return configurations.translateRule?.(ruleTranslation) ?? \"\";\n },\n translateAttribute(attributeTranslation) {\n return configurations.translateAttribute?.(attributeTranslation) ?? \"\";\n },\n configurations,\n };\n\n const result = await schema.validate(data, context);\n\n // A failed validation never hands back the input it just rejected.\n //\n // `object` used to return the raw input on failure while `discriminatedUnion`\n // returned `undefined` — the same call shape with two different contracts.\n // The object behaviour was the dangerous one: validating an outbound DTO\n // specifically to keep internal fields out of a response, then doing the\n // natural `const { data } = await v.validate(dto, record); reply.send(data)`,\n // shipped every field the schema existed to exclude. A guard that returns the\n // unsafe value on failure reads as safe and isn't.\n if (!result.isValid) {\n return { ...result, data: undefined };\n }\n\n return result;\n};\n"],"mappings":";;;;;;AAQA,MAAa,WAAW,OACtB,QACA,MACA,EAAE,SAAS,iBAAiB,GAAG,mBAAoC,cAAc,KAAK,CAAC,MACzD;CAC9B,MAAM,UAAyB;EAC7B,WAAW;EACX,QAAQ;EACR,OAAO;EACP,KAAK;EACL,MAAM;EACN,SAAS;EACT,aAAa;EACb,cAAc,iBAAiB;GAC7B,OAAO,eAAe,gBAAgB,eAAe,KAAK;EAC5D;EACA,mBAAmB,sBAAsB;GACvC,OAAO,eAAe,qBAAqB,oBAAoB,KAAK;EACtE;EACA;CACF;CAEA,MAAM,SAAS,MAAM,OAAO,SAAS,MAAM,OAAO;CAWlD,IAAI,CAAC,OAAO,SACV,OAAO;EAAE,GAAG;EAAQ,MAAM;CAAU;CAGtC,OAAO;AACT"}
|
|
@@ -92,7 +92,7 @@ const v = {
|
|
|
92
92
|
* Implementation is loosely typed; the public `ValidatorV.enum`
|
|
93
93
|
* surface declares the precise overloads.
|
|
94
94
|
*/
|
|
95
|
-
enum: ((values, errorMessage) => Array.isArray(values) ? new
|
|
95
|
+
enum: ((values, errorMessage) => Array.isArray(values) ? new ScalarValidator().oneOf(values, errorMessage) : new ScalarValidator().enum(values, errorMessage)),
|
|
96
96
|
/** Create a number validator */
|
|
97
97
|
number: (errorMessage) => new NumberValidator(errorMessage),
|
|
98
98
|
/** Create a numeric validator */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validators.mjs","names":["validateFunction"],"sources":["../../../../../../../../../seal/src/factory/validators.ts"],"sourcesContent":["import type { SealConfig } from \"../config\";\r\nimport { object } from \"../object\";\r\nimport { string } from \"../string\";\r\nimport type { StandardSchemaV1 } from \"../standard-schema/types\";\r\nimport type { Schema, SchemaContext, ValidationResult } from \"../types\";\r\nimport type { Infer } from \"../types/inference-types\";\r\nimport { AnyValidator } from \"../validators/any-validator\";\r\nimport { ArrayValidator } from \"../validators/array-validator\";\r\nimport type { BaseValidator } from \"../validators/base-validator\";\r\nimport { BooleanValidator } from \"../validators/boolean-validator\";\r\nimport { ComputedValidator } from \"../validators/computed-validator\";\r\nimport { DateValidator } from \"../validators/date-validator\";\r\nimport { DiscriminatedUnionValidator } from \"../validators/discriminated-union-validator\";\r\nimport { FloatValidator } from \"../validators/float-validator\";\r\nimport { InstanceOfValidator } from \"../validators/instanceof-validator\";\r\nimport { IntValidator } from \"../validators/int-validator\";\r\nimport { LazyValidator } from \"../validators/lazy-validator\";\r\nimport { LiteralValidator } from \"../validators/literal-validator\";\r\nimport { ManagedValidator } from \"../validators/managed-validator\";\r\nimport { NumberValidator } from \"../validators/number-validator\";\r\nimport { NumericValidator } from \"../validators/numeric-validator\";\r\nimport { ObjectValidator } from \"../validators/object-validator\";\r\nimport { RecordValidator } from \"../validators/record-validator\";\r\nimport { ScalarValidator } from \"../validators/scalar-validator\";\r\nimport { StringValidator } from \"../validators/string-validator\";\r\nimport { TupleValidator } from \"../validators/tuple-validator\";\r\nimport { UnionValidator } from \"../validators/union-validator\";\r\n// BaseValidator prototype augmentations — side-effect imports so the `v`\r\n// factory always exposes the chain methods (.required, .requiredIf, .sameAs,\r\n// .present, .forbidden, .when, …) regardless of which entry path loaded it.\r\n// The package barrel (src/index.ts) loads these via the validators barrel, but\r\n// importing the factory leaf directly would otherwise yield a half-built `v`.\r\nimport \"../validators/methods/equality-conditional-methods\";\r\nimport \"../validators/methods/forbidden-methods\";\r\nimport \"../validators/methods/present-methods\";\r\nimport \"../validators/methods/required-methods\";\r\nimport { validate as validateFunction } from \"./validate\";\r\n\r\n/**\r\n * Seal factory object - creates instances of validators\r\n *\r\n * Use 'v' to create validation schemas (seals) for your data\r\n */\r\nexport const v: ValidatorV = {\r\n /** Create an object validator */\r\n object,\r\n\r\n /** Create an any validator */\r\n any: () => new AnyValidator() as AnyValidator & StandardSchemaV1<any>,\r\n\r\n /** Create an array validator */\r\n array: <T extends BaseValidator>(validator: T, errorMessage?: string) =>\r\n new ArrayValidator(validator, errorMessage) as ArrayValidator & {\r\n validator: T;\r\n } & StandardSchemaV1<Array<Infer<T>>>,\r\n\r\n /** Create a record validator - object with dynamic keys and consistent value types */\r\n record: <T extends BaseValidator>(validator?: T, errorMessage?: string) =>\r\n new RecordValidator(validator || v.any(), errorMessage) as RecordValidator & {\r\n valueValidator: T;\r\n } & StandardSchemaV1<Record<string, Infer<T>>>,\r\n\r\n /** Create a tuple validator - fixed-length array with position-specific types */\r\n tuple: <T extends BaseValidator[]>(validators: T, errorMessage?: string) =>\r\n new TupleValidator(validators, errorMessage) as TupleValidator & {\r\n validators: T;\r\n } & StandardSchemaV1<{ [K in keyof T]: Infer<T[K]> }>,\r\n\r\n /** Create a date validator */\r\n date: (errorMessage?: string) =>\r\n new DateValidator(errorMessage) as DateValidator & StandardSchemaV1<Date>,\r\n\r\n /**\r\n * Create a literal validator — narrows to the union of given literal values\r\n *\r\n * @example\r\n * v.literal(\"items\") // type: \"items\"\r\n * v.literal(\"draft\", \"published\") // type: \"draft\" | \"published\"\r\n * v.literal(1, 2, 3) // type: 1 | 2 | 3\r\n */\r\n literal: <T extends readonly [string | number | boolean, ...(string | number | boolean)[]]>(\r\n ...values: T\r\n ) => new LiteralValidator<T>(values) as LiteralValidator<T> & StandardSchemaV1<T[number]>,\r\n\r\n /**\r\n * Create an instanceof validator — value must be `instanceof` the constructor\r\n *\r\n * @example\r\n * v.instanceof(File) // type: File\r\n * v.instanceof(Buffer) // type: Buffer\r\n * v.instanceof(MyClass) // type: MyClass\r\n */\r\n instanceof: <T>(ctor: new (...args: any[]) => T, errorMessage?: string) =>\r\n new InstanceOfValidator<T>(ctor, errorMessage) as InstanceOfValidator<T> & StandardSchemaV1<T>,\r\n\r\n /**\r\n * Create a lazy validator — defers resolution of the inner validator until\r\n * validate-time. Use for recursive or forward-referenced schemas.\r\n *\r\n * @example\r\n * type Category = { name: string; children: Category[] };\r\n *\r\n * const category: ObjectValidator<...> = v.object({\r\n * name: v.string(),\r\n * children: v.array(v.lazy(() => category)),\r\n * });\r\n */\r\n lazy: <T extends BaseValidator>(thunk: () => T) =>\r\n new LazyValidator(thunk) as LazyValidator<T> & StandardSchemaV1<Infer<T>>,\r\n\r\n /** Create a string validator */\r\n string,\r\n\r\n /** Create an email validator */\r\n email: (emailErrorMessage?: string, errorMessage?: string) =>\r\n new StringValidator(errorMessage).email(emailErrorMessage) as StringValidator &\r\n StandardSchemaV1<string>,\r\n\r\n /**\r\n * Create an enum validator — preserves the literal union of accepted\r\n * values in the inferred Standard Schema output.\r\n *\r\n * Two input shapes:\r\n * - Tuple of literals: `v.enum([\"draft\", \"published\"])` →\r\n * `StandardSchemaV1<\"draft\" | \"published\">`\r\n * - TypeScript enum object: `v.enum(Status)` →\r\n * `StandardSchemaV1<Status[keyof Status]>`\r\n *\r\n * Implementation is loosely typed; the public `ValidatorV.enum`\r\n * surface declares the precise overloads.\r\n */\r\n enum: ((values: any, errorMessage?: string) =>\r\n Array.isArray(values)\r\n ? new StringValidator().oneOf(values, errorMessage)\r\n : new ScalarValidator().enum(values, errorMessage)) as ValidatorV[\"enum\"],\r\n\r\n /** Create a number validator */\r\n number: (errorMessage?: string) =>\r\n new NumberValidator(errorMessage) as NumberValidator & StandardSchemaV1<number>,\r\n\r\n /** Create a numeric validator */\r\n numeric: (errorMessage?: string) =>\r\n new NumericValidator(errorMessage) as NumericValidator & StandardSchemaV1<number>,\r\n\r\n /** Create an integer validator */\r\n int: (errorMessage?: string) =>\r\n new IntValidator(errorMessage) as IntValidator & StandardSchemaV1<number>,\r\n\r\n /** Create a float validator */\r\n float: (errorMessage?: string) =>\r\n new FloatValidator(errorMessage) as FloatValidator & StandardSchemaV1<number>,\r\n\r\n /** Create a boolean validator */\r\n boolean: (errorMessage?: string) =>\r\n new BooleanValidator(errorMessage) as BooleanValidator & StandardSchemaV1<boolean>,\r\n\r\n /** Create a scalar validator */\r\n scalar: (errorMessage?: string) =>\r\n new ScalarValidator(errorMessage) as ScalarValidator &\r\n StandardSchemaV1<string | number | boolean>,\r\n\r\n /** Create a union validator - validates against multiple types */\r\n union: <T extends BaseValidator[]>(validators: T, errorMessage?: string) =>\r\n new UnionValidator().union(validators, errorMessage) as UnionValidator &\r\n StandardSchemaV1<Infer<T[number]>>,\r\n\r\n /**\r\n * Create a discriminated union — routes payloads by a shared literal\r\n * discriminator field. Each branch must be a `v.object(...)` with the\r\n * discriminator typed as `v.literal(...)`.\r\n *\r\n * @example\r\n * const email = v.object({ type: v.literal(\"email\"), email: v.string().email() });\r\n * const sms = v.object({ type: v.literal(\"sms\"), phone: v.string() });\r\n * const notif = v.discriminatedUnion(\"type\", [email, sms]);\r\n * // type T = Infer<typeof notif>;\r\n * // → { type: \"email\", email: string } | { type: \"sms\", phone: string }\r\n */\r\n discriminatedUnion: <K extends string, Branches extends ReadonlyArray<ObjectValidator<any>>>(\r\n discriminator: K,\r\n validators: Branches,\r\n ) =>\r\n new DiscriminatedUnionValidator(discriminator, validators) as DiscriminatedUnionValidator<\r\n K,\r\n Branches\r\n > &\r\n StandardSchemaV1<Infer<Branches[number]>>,\r\n\r\n /** Create a computed field validator - derives value from other validated fields */\r\n computed: <TResult = any>(\r\n callback: (data: any, context: SchemaContext) => TResult | Promise<TResult>,\r\n resultValidator?: BaseValidator,\r\n ) =>\r\n new ComputedValidator<TResult>(callback, resultValidator) as ComputedValidator<TResult> &\r\n StandardSchemaV1<TResult>,\r\n\r\n /** Create a managed field validator - framework-injected value */\r\n managed: <TResult = any>(\r\n callback: (context: SchemaContext) => TResult | Promise<TResult>,\r\n resultValidator?: BaseValidator,\r\n ) =>\r\n new ManagedValidator<TResult>(callback, resultValidator) as ManagedValidator<TResult> &\r\n StandardSchemaV1<TResult>,\r\n\r\n /** Validate data against a schema */\r\n validate: validateFunction,\r\n} as unknown as ValidatorV;\r\n\r\nexport type ValidateOptions = {\r\n context?: Record<string, any>;\r\n /**\r\n * Rendered as `:value` in every error message — translated or custom —\r\n * instead of the submitted input. Unset keeps the raw value.\r\n */\r\n redactValue?: string;\r\n} & SealConfig;\r\n\r\nexport interface ValidatorV {\r\n object: <T extends Schema>(\r\n schema: T,\r\n errorMessage?: string,\r\n ) => ObjectValidator<T> & StandardSchemaV1<Infer<ObjectValidator<T>>>;\r\n any: () => AnyValidator & StandardSchemaV1<any>;\r\n array: <T extends BaseValidator>(\r\n validator: T,\r\n errorMessage?: string,\r\n ) => ArrayValidator & {\r\n validator: T;\r\n } & StandardSchemaV1<Array<Infer<T>>>;\r\n record: <T extends BaseValidator>(\r\n validator?: T,\r\n errorMessage?: string,\r\n ) => RecordValidator & {\r\n valueValidator: T;\r\n } & StandardSchemaV1<Record<string, Infer<T>>>;\r\n tuple: <T extends BaseValidator[]>(\r\n validators: T,\r\n errorMessage?: string,\r\n ) => TupleValidator & {\r\n validators: T;\r\n } & StandardSchemaV1<{ [K in keyof T]: Infer<T[K]> }>;\r\n date: (errorMessage?: string) => DateValidator & StandardSchemaV1<Date>;\r\n literal: <T extends readonly [string | number | boolean, ...(string | number | boolean)[]]>(\r\n ...values: T\r\n ) => LiteralValidator<T> & StandardSchemaV1<T[number]>;\r\n instanceof: <T>(\r\n ctor: new (...args: any[]) => T,\r\n errorMessage?: string,\r\n ) => InstanceOfValidator<T> & StandardSchemaV1<T>;\r\n lazy: <T extends BaseValidator>(thunk: () => T) => LazyValidator<T> & StandardSchemaV1<Infer<T>>;\r\n string: (errorMessage?: string) => StringValidator & StandardSchemaV1<string>;\r\n email: (errorMessage?: string) => StringValidator & StandardSchemaV1<string>;\r\n enum: {\r\n <const T extends readonly (string | number | boolean)[]>(\r\n values: T,\r\n errorMessage?: string,\r\n ): ScalarValidator & StandardSchemaV1<T[number]>;\r\n <const T extends Record<string, string | number>>(\r\n values: T,\r\n errorMessage?: string,\r\n ): ScalarValidator & StandardSchemaV1<T[keyof T]>;\r\n };\r\n number: (errorMessage?: string) => NumberValidator & StandardSchemaV1<number>;\r\n numeric: (errorMessage?: string) => NumericValidator & StandardSchemaV1<number>;\r\n int: (errorMessage?: string) => IntValidator & StandardSchemaV1<number>;\r\n float: (errorMessage?: string) => FloatValidator & StandardSchemaV1<number>;\r\n boolean: (errorMessage?: string) => BooleanValidator & StandardSchemaV1<boolean>;\r\n scalar: (errorMessage?: string) => ScalarValidator & StandardSchemaV1<string | number | boolean>;\r\n union: <T extends BaseValidator[]>(\r\n validators: T,\r\n errorMessage?: string,\r\n ) => UnionValidator & StandardSchemaV1<Infer<T[number]>>;\r\n discriminatedUnion: <K extends string, Branches extends ReadonlyArray<ObjectValidator<any>>>(\r\n discriminator: K,\r\n validators: Branches,\r\n ) => DiscriminatedUnionValidator<K, Branches> & StandardSchemaV1<Infer<Branches[number]>>;\r\n computed: <TResult = any>(\r\n callback: (data: any, context: SchemaContext) => TResult | Promise<TResult>,\r\n resultValidator?: BaseValidator,\r\n ) => ComputedValidator<TResult> & StandardSchemaV1<TResult>;\r\n managed: <TResult = any>(\r\n callback?: (context: SchemaContext) => TResult | Promise<TResult>,\r\n resultValidator?: BaseValidator,\r\n ) => ManagedValidator<TResult> & StandardSchemaV1<TResult>;\r\n validate: <T extends BaseValidator>(\r\n schema: T,\r\n data: any,\r\n options?: ValidateOptions,\r\n ) => Promise<ValidationResult>;\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,IAAgB;;CAE3B;;CAGA,WAAW,IAAI,aAAa;;CAG5B,QAAiC,WAAc,iBAC7C,IAAI,eAAe,WAAW,YAAY;;CAK5C,SAAkC,WAAe,iBAC/C,IAAI,gBAAgB,aAAa,EAAE,IAAI,GAAG,YAAY;;CAKxD,QAAmC,YAAe,iBAChD,IAAI,eAAe,YAAY,YAAY;;CAK7C,OAAO,iBACL,IAAI,cAAc,YAAY;;;;;;;;;CAUhC,UACE,GAAG,WACA,IAAI,iBAAoB,MAAM;;;;;;;;;CAUnC,aAAgB,MAAiC,iBAC/C,IAAI,oBAAuB,MAAM,YAAY;;;;;;;;;;;;;CAc/C,OAAgC,UAC9B,IAAI,cAAc,KAAK;;CAGzB;;CAGA,QAAQ,mBAA4B,iBAClC,IAAI,gBAAgB,YAAY,CAAC,CAAC,MAAM,iBAAiB;;;;;;;;;;;;;;CAgB3D,QAAQ,QAAa,iBACnB,MAAM,QAAQ,MAAM,IAChB,IAAI,gBAAgB,CAAC,CAAC,MAAM,QAAQ,YAAY,IAChD,IAAI,gBAAgB,CAAC,CAAC,KAAK,QAAQ,YAAY;;CAGrD,SAAS,iBACP,IAAI,gBAAgB,YAAY;;CAGlC,UAAU,iBACR,IAAI,iBAAiB,YAAY;;CAGnC,MAAM,iBACJ,IAAI,aAAa,YAAY;;CAG/B,QAAQ,iBACN,IAAI,eAAe,YAAY;;CAGjC,UAAU,iBACR,IAAI,iBAAiB,YAAY;;CAGnC,SAAS,iBACP,IAAI,gBAAgB,YAAY;;CAIlC,QAAmC,YAAe,iBAChD,IAAI,eAAe,CAAC,CAAC,MAAM,YAAY,YAAY;;;;;;;;;;;;;CAerD,qBACE,eACA,eAEA,IAAI,4BAA4B,eAAe,UAAU;;CAO3D,WACE,UACA,oBAEA,IAAI,kBAA2B,UAAU,eAAe;;CAI1D,UACE,UACA,oBAEA,IAAI,iBAA0B,UAAU,eAAe;;CAI/CA;AACZ"}
|
|
1
|
+
{"version":3,"file":"validators.mjs","names":["validateFunction"],"sources":["../../../../../../../../../seal/src/factory/validators.ts"],"sourcesContent":["import type { SealConfig } from \"../config\";\nimport { object } from \"../object\";\nimport { string } from \"../string\";\nimport type { StandardSchemaV1 } from \"../standard-schema/types\";\nimport type { Schema, SchemaContext, ValidationResult } from \"../types\";\nimport type { Infer } from \"../types/inference-types\";\nimport { AnyValidator } from \"../validators/any-validator\";\nimport { ArrayValidator } from \"../validators/array-validator\";\nimport type { BaseValidator } from \"../validators/base-validator\";\nimport { BooleanValidator } from \"../validators/boolean-validator\";\nimport { ComputedValidator } from \"../validators/computed-validator\";\nimport { DateValidator } from \"../validators/date-validator\";\nimport { DiscriminatedUnionValidator } from \"../validators/discriminated-union-validator\";\nimport { FloatValidator } from \"../validators/float-validator\";\nimport { InstanceOfValidator } from \"../validators/instanceof-validator\";\nimport { IntValidator } from \"../validators/int-validator\";\nimport { LazyValidator } from \"../validators/lazy-validator\";\nimport { LiteralValidator } from \"../validators/literal-validator\";\nimport { ManagedValidator } from \"../validators/managed-validator\";\nimport { NumberValidator } from \"../validators/number-validator\";\nimport { NumericValidator } from \"../validators/numeric-validator\";\nimport { ObjectValidator } from \"../validators/object-validator\";\nimport { RecordValidator } from \"../validators/record-validator\";\nimport { ScalarValidator } from \"../validators/scalar-validator\";\nimport { StringValidator } from \"../validators/string-validator\";\nimport { TupleValidator } from \"../validators/tuple-validator\";\nimport { UnionValidator } from \"../validators/union-validator\";\n// BaseValidator prototype augmentations — side-effect imports so the `v`\n// factory always exposes the chain methods (.required, .requiredIf, .sameAs,\n// .present, .forbidden, .when, …) regardless of which entry path loaded it.\n// The package barrel (src/index.ts) loads these via the validators barrel, but\n// importing the factory leaf directly would otherwise yield a half-built `v`.\nimport \"../validators/methods/equality-conditional-methods\";\nimport \"../validators/methods/forbidden-methods\";\nimport \"../validators/methods/present-methods\";\nimport \"../validators/methods/required-methods\";\nimport { validate as validateFunction } from \"./validate\";\n\n/**\n * Seal factory object - creates instances of validators\n *\n * Use 'v' to create validation schemas (seals) for your data\n */\nexport const v: ValidatorV = {\n /** Create an object validator */\n object,\n\n /** Create an any validator */\n any: () => new AnyValidator() as AnyValidator & StandardSchemaV1<any>,\n\n /** Create an array validator */\n array: <T extends BaseValidator>(validator: T, errorMessage?: string) =>\n new ArrayValidator(validator, errorMessage) as ArrayValidator & {\n validator: T;\n } & StandardSchemaV1<Array<Infer<T>>>,\n\n /** Create a record validator - object with dynamic keys and consistent value types */\n record: <T extends BaseValidator>(validator?: T, errorMessage?: string) =>\n new RecordValidator(validator || v.any(), errorMessage) as RecordValidator & {\n valueValidator: T;\n } & StandardSchemaV1<Record<string, Infer<T>>>,\n\n /** Create a tuple validator - fixed-length array with position-specific types */\n tuple: <T extends BaseValidator[]>(validators: T, errorMessage?: string) =>\n new TupleValidator(validators, errorMessage) as TupleValidator & {\n validators: T;\n } & StandardSchemaV1<{ [K in keyof T]: Infer<T[K]> }>,\n\n /** Create a date validator */\n date: (errorMessage?: string) =>\n new DateValidator(errorMessage) as DateValidator & StandardSchemaV1<Date>,\n\n /**\n * Create a literal validator — narrows to the union of given literal values\n *\n * @example\n * v.literal(\"items\") // type: \"items\"\n * v.literal(\"draft\", \"published\") // type: \"draft\" | \"published\"\n * v.literal(1, 2, 3) // type: 1 | 2 | 3\n */\n literal: <T extends readonly [string | number | boolean, ...(string | number | boolean)[]]>(\n ...values: T\n ) => new LiteralValidator<T>(values) as LiteralValidator<T> & StandardSchemaV1<T[number]>,\n\n /**\n * Create an instanceof validator — value must be `instanceof` the constructor\n *\n * @example\n * v.instanceof(File) // type: File\n * v.instanceof(Buffer) // type: Buffer\n * v.instanceof(MyClass) // type: MyClass\n */\n instanceof: <T>(ctor: new (...args: any[]) => T, errorMessage?: string) =>\n new InstanceOfValidator<T>(ctor, errorMessage) as InstanceOfValidator<T> & StandardSchemaV1<T>,\n\n /**\n * Create a lazy validator — defers resolution of the inner validator until\n * validate-time. Use for recursive or forward-referenced schemas.\n *\n * @example\n * type Category = { name: string; children: Category[] };\n *\n * const category: ObjectValidator<...> = v.object({\n * name: v.string(),\n * children: v.array(v.lazy(() => category)),\n * });\n */\n lazy: <T extends BaseValidator>(thunk: () => T) =>\n new LazyValidator(thunk) as LazyValidator<T> & StandardSchemaV1<Infer<T>>,\n\n /** Create a string validator */\n string,\n\n /** Create an email validator */\n email: (emailErrorMessage?: string, errorMessage?: string) =>\n new StringValidator(errorMessage).email(emailErrorMessage) as StringValidator &\n StandardSchemaV1<string>,\n\n /**\n * Create an enum validator — preserves the literal union of accepted\n * values in the inferred Standard Schema output.\n *\n * Two input shapes:\n * - Tuple of literals: `v.enum([\"draft\", \"published\"])` →\n * `StandardSchemaV1<\"draft\" | \"published\">`\n * - TypeScript enum object: `v.enum(Status)` →\n * `StandardSchemaV1<Status[keyof Status]>`\n *\n * Implementation is loosely typed; the public `ValidatorV.enum`\n * surface declares the precise overloads.\n */\n enum: ((values: any, errorMessage?: string) =>\n Array.isArray(values)\n ? new ScalarValidator().oneOf(values, errorMessage)\n : new ScalarValidator().enum(values, errorMessage)) as ValidatorV[\"enum\"],\n\n /** Create a number validator */\n number: (errorMessage?: string) =>\n new NumberValidator(errorMessage) as NumberValidator & StandardSchemaV1<number>,\n\n /** Create a numeric validator */\n numeric: (errorMessage?: string) =>\n new NumericValidator(errorMessage) as NumericValidator & StandardSchemaV1<number>,\n\n /** Create an integer validator */\n int: (errorMessage?: string) =>\n new IntValidator(errorMessage) as IntValidator & StandardSchemaV1<number>,\n\n /** Create a float validator */\n float: (errorMessage?: string) =>\n new FloatValidator(errorMessage) as FloatValidator & StandardSchemaV1<number>,\n\n /** Create a boolean validator */\n boolean: (errorMessage?: string) =>\n new BooleanValidator(errorMessage) as BooleanValidator & StandardSchemaV1<boolean>,\n\n /** Create a scalar validator */\n scalar: (errorMessage?: string) =>\n new ScalarValidator(errorMessage) as ScalarValidator &\n StandardSchemaV1<string | number | boolean>,\n\n /** Create a union validator - validates against multiple types */\n union: <T extends BaseValidator[]>(validators: T, errorMessage?: string) =>\n new UnionValidator().union(validators, errorMessage) as UnionValidator &\n StandardSchemaV1<Infer<T[number]>>,\n\n /**\n * Create a discriminated union — routes payloads by a shared literal\n * discriminator field. Each branch must be a `v.object(...)` with the\n * discriminator typed as `v.literal(...)`.\n *\n * @example\n * const email = v.object({ type: v.literal(\"email\"), email: v.string().email() });\n * const sms = v.object({ type: v.literal(\"sms\"), phone: v.string() });\n * const notif = v.discriminatedUnion(\"type\", [email, sms]);\n * // type T = Infer<typeof notif>;\n * // → { type: \"email\", email: string } | { type: \"sms\", phone: string }\n */\n discriminatedUnion: <K extends string, Branches extends ReadonlyArray<ObjectValidator<any>>>(\n discriminator: K,\n validators: Branches,\n ) =>\n new DiscriminatedUnionValidator(discriminator, validators) as DiscriminatedUnionValidator<\n K,\n Branches\n > &\n StandardSchemaV1<Infer<Branches[number]>>,\n\n /** Create a computed field validator - derives value from other validated fields */\n computed: <TResult = any>(\n callback: (data: any, context: SchemaContext) => TResult | Promise<TResult>,\n resultValidator?: BaseValidator,\n ) =>\n new ComputedValidator<TResult>(callback, resultValidator) as ComputedValidator<TResult> &\n StandardSchemaV1<TResult>,\n\n /** Create a managed field validator - framework-injected value */\n managed: <TResult = any>(\n callback: (context: SchemaContext) => TResult | Promise<TResult>,\n resultValidator?: BaseValidator,\n ) =>\n new ManagedValidator<TResult>(callback, resultValidator) as ManagedValidator<TResult> &\n StandardSchemaV1<TResult>,\n\n /** Validate data against a schema */\n validate: validateFunction,\n} as unknown as ValidatorV;\n\nexport type ValidateOptions = {\n context?: Record<string, any>;\n /**\n * Rendered as `:value` in every error message — translated or custom —\n * instead of the submitted input. Unset keeps the raw value.\n */\n redactValue?: string;\n} & SealConfig;\n\nexport interface ValidatorV {\n object: <T extends Schema>(\n schema: T,\n errorMessage?: string,\n ) => ObjectValidator<T> & StandardSchemaV1<Infer<ObjectValidator<T>>>;\n any: () => AnyValidator & StandardSchemaV1<any>;\n array: <T extends BaseValidator>(\n validator: T,\n errorMessage?: string,\n ) => ArrayValidator & {\n validator: T;\n } & StandardSchemaV1<Array<Infer<T>>>;\n record: <T extends BaseValidator>(\n validator?: T,\n errorMessage?: string,\n ) => RecordValidator & {\n valueValidator: T;\n } & StandardSchemaV1<Record<string, Infer<T>>>;\n tuple: <T extends BaseValidator[]>(\n validators: T,\n errorMessage?: string,\n ) => TupleValidator & {\n validators: T;\n } & StandardSchemaV1<{ [K in keyof T]: Infer<T[K]> }>;\n date: (errorMessage?: string) => DateValidator & StandardSchemaV1<Date>;\n literal: <T extends readonly [string | number | boolean, ...(string | number | boolean)[]]>(\n ...values: T\n ) => LiteralValidator<T> & StandardSchemaV1<T[number]>;\n instanceof: <T>(\n ctor: new (...args: any[]) => T,\n errorMessage?: string,\n ) => InstanceOfValidator<T> & StandardSchemaV1<T>;\n lazy: <T extends BaseValidator>(thunk: () => T) => LazyValidator<T> & StandardSchemaV1<Infer<T>>;\n string: (errorMessage?: string) => StringValidator & StandardSchemaV1<string>;\n email: (errorMessage?: string) => StringValidator & StandardSchemaV1<string>;\n enum: {\n <const T extends readonly (string | number | boolean)[]>(\n values: T,\n errorMessage?: string,\n ): ScalarValidator & StandardSchemaV1<T[number]>;\n <const T extends Record<string, string | number>>(\n values: T,\n errorMessage?: string,\n ): ScalarValidator & StandardSchemaV1<T[keyof T]>;\n };\n number: (errorMessage?: string) => NumberValidator & StandardSchemaV1<number>;\n numeric: (errorMessage?: string) => NumericValidator & StandardSchemaV1<number>;\n int: (errorMessage?: string) => IntValidator & StandardSchemaV1<number>;\n float: (errorMessage?: string) => FloatValidator & StandardSchemaV1<number>;\n boolean: (errorMessage?: string) => BooleanValidator & StandardSchemaV1<boolean>;\n scalar: (errorMessage?: string) => ScalarValidator & StandardSchemaV1<string | number | boolean>;\n union: <T extends BaseValidator[]>(\n validators: T,\n errorMessage?: string,\n ) => UnionValidator & StandardSchemaV1<Infer<T[number]>>;\n discriminatedUnion: <K extends string, Branches extends ReadonlyArray<ObjectValidator<any>>>(\n discriminator: K,\n validators: Branches,\n ) => DiscriminatedUnionValidator<K, Branches> & StandardSchemaV1<Infer<Branches[number]>>;\n computed: <TResult = any>(\n callback: (data: any, context: SchemaContext) => TResult | Promise<TResult>,\n resultValidator?: BaseValidator,\n ) => ComputedValidator<TResult> & StandardSchemaV1<TResult>;\n managed: <TResult = any>(\n callback?: (context: SchemaContext) => TResult | Promise<TResult>,\n resultValidator?: BaseValidator,\n ) => ManagedValidator<TResult> & StandardSchemaV1<TResult>;\n validate: <T extends BaseValidator>(\n schema: T,\n data: any,\n options?: ValidateOptions,\n ) => Promise<ValidationResult>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,IAAgB;;CAE3B;;CAGA,WAAW,IAAI,aAAa;;CAG5B,QAAiC,WAAc,iBAC7C,IAAI,eAAe,WAAW,YAAY;;CAK5C,SAAkC,WAAe,iBAC/C,IAAI,gBAAgB,aAAa,EAAE,IAAI,GAAG,YAAY;;CAKxD,QAAmC,YAAe,iBAChD,IAAI,eAAe,YAAY,YAAY;;CAK7C,OAAO,iBACL,IAAI,cAAc,YAAY;;;;;;;;;CAUhC,UACE,GAAG,WACA,IAAI,iBAAoB,MAAM;;;;;;;;;CAUnC,aAAgB,MAAiC,iBAC/C,IAAI,oBAAuB,MAAM,YAAY;;;;;;;;;;;;;CAc/C,OAAgC,UAC9B,IAAI,cAAc,KAAK;;CAGzB;;CAGA,QAAQ,mBAA4B,iBAClC,IAAI,gBAAgB,YAAY,CAAC,CAAC,MAAM,iBAAiB;;;;;;;;;;;;;;CAgB3D,QAAQ,QAAa,iBACnB,MAAM,QAAQ,MAAM,IAChB,IAAI,gBAAgB,CAAC,CAAC,MAAM,QAAQ,YAAY,IAChD,IAAI,gBAAgB,CAAC,CAAC,KAAK,QAAQ,YAAY;;CAGrD,SAAS,iBACP,IAAI,gBAAgB,YAAY;;CAGlC,UAAU,iBACR,IAAI,iBAAiB,YAAY;;CAGnC,MAAM,iBACJ,IAAI,aAAa,YAAY;;CAG/B,QAAQ,iBACN,IAAI,eAAe,YAAY;;CAGjC,UAAU,iBACR,IAAI,iBAAiB,YAAY;;CAGnC,SAAS,iBACP,IAAI,gBAAgB,YAAY;;CAIlC,QAAmC,YAAe,iBAChD,IAAI,eAAe,CAAC,CAAC,MAAM,YAAY,YAAY;;;;;;;;;;;;;CAerD,qBACE,eACA,eAEA,IAAI,4BAA4B,eAAe,UAAU;;CAO3D,WACE,UACA,oBAEA,IAAI,kBAA2B,UAAU,eAAe;;CAI1D,UACE,UACA,oBAEA,IAAI,iBAA0B,UAAU,eAAe;;CAI/CA;AACZ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"date-helpers.mjs","names":[],"sources":["../../../../../../../../../seal/src/helpers/date-helpers.ts"],"sourcesContent":["/**\
|
|
1
|
+
{"version":3,"file":"date-helpers.mjs","names":[],"sources":["../../../../../../../../../seal/src/helpers/date-helpers.ts"],"sourcesContent":["/**\n * Detect if value is a date value or field name\n *\n * Date values:\n * - Date instance\n * - Number (timestamp)\n * - String with date separators (- or /)\n *\n * Field names:\n * - Strings without - or /\n *\n * @param value - Value to check\n * @returns true if it's a date value, false if it's a field name\n */\nexport function isDateValue(value: any): boolean {\n if (value instanceof Date) return true;\n\n const date = new Date(value);\n\n return !isNaN(date.getTime());\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,SAAgB,YAAY,OAAqB;CAC/C,IAAI,iBAAiB,MAAM,OAAO;CAElC,MAAM,OAAO,IAAI,KAAK,KAAK;CAE3B,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC;AAC9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-field-value.mjs","names":[],"sources":["../../../../../../../../../seal/src/helpers/get-field-value.ts"],"sourcesContent":["import { get } from \"@mongez/reinforcements\";\
|
|
1
|
+
{"version":3,"file":"get-field-value.mjs","names":[],"sources":["../../../../../../../../../seal/src/helpers/get-field-value.ts"],"sourcesContent":["import { get } from \"@mongez/reinforcements\";\nimport type { ContextualSchemaRule, SchemaContext } from \"../types\";\n\n/**\n * Get field value based on scope from rule options\n *\n * This utility extracts a field value from either the global context or sibling context\n * based on the `scope` option in the rule's context.\n *\n * @param rule - The contextual schema rule containing options with:\n * - `field` (or custom fieldKey): The field name to extract\n * - `scope` (optional): Either \"global\" (default) or \"sibling\"\n * @param context - The schema validation context containing allValues and parent\n * @param fieldKey - The key in rule.context.options that contains the field name (defaults to \"field\")\n *\n * @returns The value of the specified field from the appropriate source\n *\n * @example\n * // In a validation rule with scope support:\n * async validate(value: any, context) {\n * const otherFieldValue = getFieldValue(this, context);\n * // Extracts from context.allValues if scope is \"global\"\n * // Extracts from context.parent if scope is \"sibling\"\n * }\n *\n * @example\n * // Using a custom field key:\n * async validate(value: any, context) {\n * const compareValue = getFieldValue(this, context, \"compareField\");\n * // Looks for rule.context.options.compareField instead of .field\n * }\n *\n * @remarks\n * - **Global scope** (`scope: \"global\"`): Searches the entire input data (context.allValues)\n * - **Sibling scope** (`scope: \"sibling\"`): Searches only within the parent object (context.parent)\n * - Uses `@mongez/reinforcements.get()` to support nested paths (e.g., \"user.email\")\n */\nexport function getFieldValue(\n rule: ContextualSchemaRule,\n context: SchemaContext,\n fieldKey = \"field\",\n) {\n const field = rule.context.options[fieldKey];\n const scope = rule.context.options.scope || \"global\";\n const source = scope === \"sibling\" ? context.parent : context.allValues;\n return get(source, field);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAgB,cACd,MACA,SACA,WAAW,SACX;CACA,MAAM,QAAQ,KAAK,QAAQ,QAAQ;CAGnC,OAAO,KAFO,KAAK,QAAQ,QAAQ,SAAS,cACnB,YAAY,QAAQ,SAAS,QAAQ,WAC3C,KAAK;AAC1B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-helpers.mjs","names":[],"sources":["../../../../../../../../../seal/src/helpers/path-helpers.ts"],"sourcesContent":["/**\
|
|
1
|
+
{"version":3,"file":"path-helpers.mjs","names":[],"sources":["../../../../../../../../../seal/src/helpers/path-helpers.ts"],"sourcesContent":["/**\n * Set the key path for nested validation\n */\nexport const setKeyPath = (path: string, key: string): string => {\n if (!path) return key;\n return `${path}.${key}`;\n};\n"],"mappings":";;;;AAGA,MAAa,cAAc,MAAc,QAAwB;CAC/D,IAAI,CAAC,MAAM,OAAO;CAClB,OAAO,GAAG,KAAK,GAAG;AACpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"array-mutators.mjs","names":[],"sources":["../../../../../../../../../seal/src/mutators/array-mutators.ts"],"sourcesContent":["import { get } from \"@mongez/reinforcements\";\
|
|
1
|
+
{"version":3,"file":"array-mutators.mjs","names":[],"sources":["../../../../../../../../../seal/src/mutators/array-mutators.ts"],"sourcesContent":["import { get } from \"@mongez/reinforcements\";\nimport { isEmptyValue } from \"../helpers/is-empty-value\";\nimport type { Mutator } from \"../types\";\n\n/** Reverse array order */\nexport const flipArrayMutator: Mutator = async (value) => {\n return value.reverse();\n};\n\n/** Reverse array order (alias) */\nexport const reverseArrayMutator: Mutator = async (value) => {\n return value.reverse();\n};\n\n/** Sort array */\nexport const sortArrayMutator: Mutator = async (value, context) => {\n if (!Array.isArray(value)) return value;\n\n const sortDirection = context?.options.direction ?? \"asc\";\n const sortByKey = context?.options.key ?? null;\n\n return value.sort((a: any, b: any) => {\n if (sortByKey) {\n const aValue = get(a, sortByKey);\n const bValue = get(b, sortByKey);\n\n if (sortDirection === \"asc\") {\n return aValue - bValue;\n }\n return bValue - aValue;\n }\n\n if (sortDirection === \"asc\") {\n return a - b;\n }\n return b - a;\n });\n};\n\n/** Make array have only unique values */\nexport const uniqueArrayMutator: Mutator = async (value) => {\n return [...new Set(value)];\n};\n\n/** Remove empty elements from array */\nexport const removeEmptyArrayElementsMutator: Mutator = async (value) => {\n return value.filter((item: any) => !isEmptyValue(item));\n};\n"],"mappings":";;;;;AAKA,MAAa,mBAA4B,OAAO,UAAU;CACxD,OAAO,MAAM,QAAQ;AACvB;;AAQA,MAAa,mBAA4B,OAAO,OAAO,YAAY;CACjE,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO;CAElC,MAAM,gBAAgB,SAAS,QAAQ,aAAa;CACpD,MAAM,YAAY,SAAS,QAAQ,OAAO;CAE1C,OAAO,MAAM,MAAM,GAAQ,MAAW;EACpC,IAAI,WAAW;GACb,MAAM,SAAS,IAAI,GAAG,SAAS;GAC/B,MAAM,SAAS,IAAI,GAAG,SAAS;GAE/B,IAAI,kBAAkB,OACpB,OAAO,SAAS;GAElB,OAAO,SAAS;EAClB;EAEA,IAAI,kBAAkB,OACpB,OAAO,IAAI;EAEb,OAAO,IAAI;CACb,CAAC;AACH;;AAGA,MAAa,qBAA8B,OAAO,UAAU;CAC1D,OAAO,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC;AAC3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"date-mutators.mjs","names":[],"sources":["../../../../../../../../../seal/src/mutators/date-mutators.ts"],"sourcesContent":["import dayjs from \"dayjs\";\
|
|
1
|
+
{"version":3,"file":"date-mutators.mjs","names":[],"sources":["../../../../../../../../../seal/src/mutators/date-mutators.ts"],"sourcesContent":["import dayjs from \"dayjs\";\nimport type { Mutator } from \"../types\";\n\n/**\n * Convert value to Date object\n * Returns Invalid Date if value cannot be converted\n */\nexport const dateMutator: Mutator = async (value) => {\n // Handle null/undefined\n if (!value) {\n return;\n }\n\n // Already a Date object\n if (value instanceof Date) {\n return value;\n }\n\n // Try to convert to Date\n const date = new Date(value);\n\n // Return the date (valid or Invalid Date)\n // dateRule will validate and fail if invalid\n return date;\n};\n\n/** Convert date to ISO string format */\nexport const toISOStringMutator: Mutator = async (value) => {\n const date = new Date(value);\n return date.toISOString();\n};\n\n/** Convert date to Unix timestamp (milliseconds) */\nexport const toTimestampMutator: Mutator = async (value) => {\n const date = new Date(value);\n return date.getTime();\n};\n\n/** Convert date to start of day (00:00:00) */\nexport const toStartOfDayMutator: Mutator = async (value) => {\n const date = new Date(value);\n date.setHours(0, 0, 0, 0);\n return date;\n};\n\n/** Convert date to end of day (23:59:59.999) */\nexport const toEndOfDayMutator: Mutator = async (value) => {\n const date = new Date(value);\n date.setHours(23, 59, 59, 999);\n return date;\n};\n\n/** Add or subtract days from date */\nexport const addDaysMutator: Mutator = async (value, context) => {\n const date = new Date(value);\n const days = context?.options.days ?? 0;\n date.setDate(date.getDate() + days);\n return date;\n};\n\n/** Add or subtract months from date */\nexport const addMonthsMutator: Mutator = async (value, context) => {\n const date = new Date(value);\n const months = context?.options.months ?? 0;\n date.setMonth(date.getMonth() + months);\n return date;\n};\n\n/** Add or subtract years from date */\nexport const addYearsMutator: Mutator = async (value, context) => {\n const date = new Date(value);\n const years = context?.options.years ?? 0;\n date.setFullYear(date.getFullYear() + years);\n return date;\n};\n\n/** Add or subtract hours from date */\nexport const addHoursMutator: Mutator = async (value, context) => {\n const date = new Date(value);\n const hours = context?.options.hours ?? 0;\n date.setHours(date.getHours() + hours);\n return date;\n};\n\n/** Convert date to UTC */\nexport const toUTCMutator: Mutator = async (value) => {\n const date = new Date(value);\n return new Date(date.toUTCString());\n};\n\n/** Convert date to specific format using dayjs */\nexport const toFormatMutator: Mutator = async (value, context) => {\n const format = context?.options.format ?? \"YYYY-MM-DD\";\n return dayjs(value).format(format);\n};\n\n/** Convert to date only (remove time) */\nexport const toDateOnlyMutator: Mutator = async (value) => {\n const date = new Date(value);\n return date.toISOString().split(\"T\")[0];\n};\n\n/** Convert to time only (HH:MM:SS) */\nexport const toTimeOnlyMutator: Mutator = async (value) => {\n const date = new Date(value);\n return date.toTimeString().split(\" \")[0];\n};\n\n/** Set to start of month */\nexport const toStartOfMonthMutator: Mutator = async (value) => {\n const date = new Date(value);\n date.setDate(1);\n date.setHours(0, 0, 0, 0);\n return date;\n};\n\n/** Set to end of month */\nexport const toEndOfMonthMutator: Mutator = async (value) => {\n const date = new Date(value);\n date.setMonth(date.getMonth() + 1);\n date.setDate(0);\n date.setHours(23, 59, 59, 999);\n return date;\n};\n\n/** Set to start of year */\nexport const toStartOfYearMutator: Mutator = async (value) => {\n const date = new Date(value);\n date.setMonth(0);\n date.setDate(1);\n date.setHours(0, 0, 0, 0);\n return date;\n};\n\n/** Set to end of year */\nexport const toEndOfYearMutator: Mutator = async (value) => {\n const date = new Date(value);\n date.setMonth(11);\n date.setDate(31);\n date.setHours(23, 59, 59, 999);\n return date;\n};\n"],"mappings":";;;;;;;;;AAOA,MAAa,cAAuB,OAAO,UAAU;CAEnD,IAAI,CAAC,OACH;CAIF,IAAI,iBAAiB,MACnB,OAAO;CAQT,OAAO,IAJU,KAAK,KAIZ;AACZ;;AAeA,MAAa,sBAA+B,OAAO,UAAU;CAC3D,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,KAAK,SAAS,GAAG,GAAG,GAAG,CAAC;CACxB,OAAO;AACT;;AAGA,MAAa,oBAA6B,OAAO,UAAU;CACzD,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,KAAK,SAAS,IAAI,IAAI,IAAI,GAAG;CAC7B,OAAO;AACT;;AAGA,MAAa,iBAA0B,OAAO,OAAO,YAAY;CAC/D,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,MAAM,OAAO,SAAS,QAAQ,QAAQ;CACtC,KAAK,QAAQ,KAAK,QAAQ,IAAI,IAAI;CAClC,OAAO;AACT;;AAGA,MAAa,mBAA4B,OAAO,OAAO,YAAY;CACjE,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,MAAM,SAAS,SAAS,QAAQ,UAAU;CAC1C,KAAK,SAAS,KAAK,SAAS,IAAI,MAAM;CACtC,OAAO;AACT;;AAGA,MAAa,kBAA2B,OAAO,OAAO,YAAY;CAChE,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,MAAM,QAAQ,SAAS,QAAQ,SAAS;CACxC,KAAK,YAAY,KAAK,YAAY,IAAI,KAAK;CAC3C,OAAO;AACT;;AAGA,MAAa,kBAA2B,OAAO,OAAO,YAAY;CAChE,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,MAAM,QAAQ,SAAS,QAAQ,SAAS;CACxC,KAAK,SAAS,KAAK,SAAS,IAAI,KAAK;CACrC,OAAO;AACT;;AAGA,MAAa,eAAwB,OAAO,UAAU;CACpD,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,OAAO,IAAI,KAAK,KAAK,YAAY,CAAC;AACpC;;AAqBA,MAAa,wBAAiC,OAAO,UAAU;CAC7D,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,KAAK,QAAQ,CAAC;CACd,KAAK,SAAS,GAAG,GAAG,GAAG,CAAC;CACxB,OAAO;AACT;;AAGA,MAAa,sBAA+B,OAAO,UAAU;CAC3D,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,KAAK,SAAS,KAAK,SAAS,IAAI,CAAC;CACjC,KAAK,QAAQ,CAAC;CACd,KAAK,SAAS,IAAI,IAAI,IAAI,GAAG;CAC7B,OAAO;AACT;;AAGA,MAAa,uBAAgC,OAAO,UAAU;CAC5D,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,KAAK,SAAS,CAAC;CACf,KAAK,QAAQ,CAAC;CACd,KAAK,SAAS,GAAG,GAAG,GAAG,CAAC;CACxB,OAAO;AACT;;AAGA,MAAa,qBAA8B,OAAO,UAAU;CAC1D,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,KAAK,SAAS,EAAE;CAChB,KAAK,QAAQ,EAAE;CACf,KAAK,SAAS,IAAI,IAAI,IAAI,GAAG;CAC7B,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-mutators.mjs","names":[],"sources":["../../../../../../../../../seal/src/mutators/number-mutators.ts"],"sourcesContent":["import { round } from \"@mongez/reinforcements\";\
|
|
1
|
+
{"version":3,"file":"number-mutators.mjs","names":[],"sources":["../../../../../../../../../seal/src/mutators/number-mutators.ts"],"sourcesContent":["import { round } from \"@mongez/reinforcements\";\nimport { isNumeric } from \"@mongez/supportive-is\";\nimport type { Mutator } from \"../types\";\n\n/** Convert value to number */\nexport const numberMutator: Mutator = async (value) => {\n if (!value) return value;\n return Number(value);\n};\n\n/** Round number to specified decimals */\nexport const roundNumberMutator: Mutator = async (value, context) => {\n return round(value, context?.options?.decimals ?? 2);\n};\n\n/** Convert to boolean */\nexport const booleanMutator: Mutator = async (value) => {\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n return Boolean(value);\n};\n\nexport const numericMutator: Mutator = async (value) => {\n if (!isNumeric(value)) return value;\n return Number(value);\n};\n\n/** Absolute value mutator */\nexport const absMutator: Mutator = async (value) => {\n return Math.abs(Number(value));\n};\n\n/** Ceil mutator */\nexport const ceilMutator: Mutator = async (value) => {\n return Math.ceil(Number(value));\n};\n\n/** Floor mutator */\nexport const floorMutator: Mutator = async (value) => {\n return Math.floor(Number(value));\n};\n\n/**\n * Round mutator\n * Supports decimal precision via options.decimals\n */\nexport const roundMutator: Mutator = async (value, context) => {\n const decimals = context?.options?.decimals ?? 0;\n // If decimals is 0, use standard Math.round for integers\n if (decimals === 0) {\n return Math.round(Number(value));\n }\n // Otherwise use reinforcements round helper for precision\n return round(Number(value), decimals);\n};\n\n/**\n * To fixed mutator — rounds to a fixed number of decimal places, as a number.\n *\n * `Number.prototype.toFixed` returns a *string*, which the number validator's\n * own type rule then rejected: `v.number().toFixed(2)` could never produce a\n * valid result. Coercing back to a number keeps the method usable where it\n * lives. For the fixed-point string form, format at the edge instead of asking\n * a number schema to output a string.\n */\nexport const toFixedMutator: Mutator = async (value, context) => {\n const decimals = context?.options?.decimals ?? 2;\n return Number(Number(value).toFixed(decimals));\n};\n"],"mappings":";;;;;;AAKA,MAAa,gBAAyB,OAAO,UAAU;CACrD,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,OAAO,KAAK;AACrB;AAcA,MAAa,iBAA0B,OAAO,UAAU;CACtD,IAAI,CAAC,UAAU,KAAK,GAAG,OAAO;CAC9B,OAAO,OAAO,KAAK;AACrB;;AAGA,MAAa,aAAsB,OAAO,UAAU;CAClD,OAAO,KAAK,IAAI,OAAO,KAAK,CAAC;AAC/B;;AAGA,MAAa,cAAuB,OAAO,UAAU;CACnD,OAAO,KAAK,KAAK,OAAO,KAAK,CAAC;AAChC;;AAGA,MAAa,eAAwB,OAAO,UAAU;CACpD,OAAO,KAAK,MAAM,OAAO,KAAK,CAAC;AACjC;;;;;AAMA,MAAa,eAAwB,OAAO,OAAO,YAAY;CAC7D,MAAM,WAAW,SAAS,SAAS,YAAY;CAE/C,IAAI,aAAa,GACf,OAAO,KAAK,MAAM,OAAO,KAAK,CAAC;CAGjC,OAAO,MAAM,OAAO,KAAK,GAAG,QAAQ;AACtC;;;;;;;;;;AAWA,MAAa,iBAA0B,OAAO,OAAO,YAAY;CAC/D,MAAM,WAAW,SAAS,SAAS,YAAY;CAC/C,OAAO,OAAO,OAAO,KAAK,CAAC,CAAC,QAAQ,QAAQ,CAAC;AAC/C"}
|