superjs-core 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/async/index.d.ts +84 -1
- package/dist/async/index.js +151 -0
- package/dist/async/index.js.map +1 -1
- package/dist/collection/index.d.ts +7 -1
- package/dist/collection/index.js +55 -0
- package/dist/collection/index.js.map +1 -1
- package/dist/date/index.d.ts +71 -1
- package/dist/date/index.js +121 -1
- package/dist/date/index.js.map +1 -1
- package/dist/error/index.d.ts +148 -0
- package/dist/error/index.js +115 -0
- package/dist/error/index.js.map +1 -0
- package/dist/index-BgG21uJC.d.ts +166 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +506 -0
- package/dist/index.js.map +1 -1
- package/dist/logger/index.d.ts +1 -0
- package/dist/logger/index.js +214 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/transports.d.ts +1 -0
- package/dist/logger/transports.js +122 -0
- package/dist/logger/transports.js.map +1 -0
- package/dist/math/index.d.ts +59 -1
- package/dist/math/index.js +69 -0
- package/dist/math/index.js.map +1 -1
- package/dist/string/index.d.ts +41 -1
- package/dist/string/index.js +117 -0
- package/dist/string/index.js.map +1 -1
- package/dist/validation/index.d.ts +106 -0
- package/dist/validation/index.js +183 -0
- package/dist/validation/index.js.map +1 -0
- package/package.json +17 -1
package/dist/date/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/date/index.ts"],"sourcesContent":["/**\r\n * Error thrown when a date value is invalid.\r\n */\r\nexport class InvalidDateError extends Error {\r\n constructor(input: unknown) {\r\n super(`Invalid date: ${String(input)}`)\r\n this.name = 'InvalidDateError'\r\n }\r\n}\r\n\r\nexport interface DateDiff {\r\n years: number\r\n months: number\r\n days: number\r\n hours: number\r\n minutes: number\r\n seconds: number\r\n}\r\n\r\nconst MONTH_NAMES_SHORT = [\r\n 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',\r\n 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',\r\n] as const\r\n\r\nconst MONTH_NAMES_FULL = [\r\n 'January', 'February', 'March', 'April', 'May', 'June',\r\n 'July', 'August', 'September', 'October', 'November', 'December',\r\n] as const\r\n\r\nconst MONTH_MAP: Record<string, number> = {\r\n jan: 0, january: 0,\r\n feb: 1, february: 1,\r\n mar: 2, march: 2,\r\n apr: 3, april: 3,\r\n may: 4,\r\n jun: 5, june: 5,\r\n jul: 6, july: 6,\r\n aug: 7, august: 7,\r\n sep: 8, september: 8,\r\n oct: 9, october: 9,\r\n nov: 10, november: 10,\r\n dec: 11, december: 11,\r\n}\r\n\r\n/**\r\n * Formats a Date to a string using the given format pattern.\r\n *\r\n * Supported tokens:\r\n * - `YYYY` — 4-digit year\r\n * - `YY` — 2-digit year\r\n * - `MMMM` — full month name\r\n * - `MMM` — abbreviated month name\r\n * - `MM` — 2-digit month (01–12)\r\n * - `DD` — 2-digit day (01–31)\r\n * - `HH` — 2-digit hours (00–23)\r\n * - `mm` — 2-digit minutes\r\n * - `ss` — 2-digit seconds\r\n * - `SSS` — milliseconds\r\n *\r\n * @param date - The date to format.\r\n * @param format - The format string (default `'YYYY-MM-DD'`).\r\n * @returns The formatted date string.\r\n */\r\nexport function formatDate(date: Date, format: string = 'YYYY-MM-DD'): string {\r\n const year = date.getFullYear()\r\n const month = date.getMonth()\r\n const day = date.getDate()\r\n const hours = date.getHours()\r\n const minutes = date.getMinutes()\r\n const seconds = date.getSeconds()\r\n const ms = date.getMilliseconds()\r\n\r\n const pad = (n: number, len: number = 2): string => String(n).padStart(len, '0')\r\n\r\n return format\r\n .replace(/YYYY/g, String(year))\r\n .replace(/YY/g, String(year).slice(-2))\r\n .replace(/MMMM/g, MONTH_NAMES_FULL[month]!)\r\n .replace(/MMM/g, MONTH_NAMES_SHORT[month]!)\r\n .replace(/MM/g, pad(month + 1))\r\n .replace(/DD/g, pad(day))\r\n .replace(/HH/g, pad(hours))\r\n .replace(/mm/g, pad(minutes))\r\n .replace(/ss/g, pad(seconds))\r\n .replace(/SSS/g, pad(ms, 3))\r\n}\r\n\r\n/**\r\n * Parses a date from a string, number (timestamp in ms), or Date object.\r\n *\r\n * Supported string formats:\r\n * - ISO (`YYYY-MM-DD`, `YYYY-MM-DDTHH:mm:ss`)\r\n * - `DD/MM/YYYY` or `DD-MM-YYYY`\r\n * - `DD MMM YYYY` or `DD MMMM YYYY`\r\n * - Unix timestamp (milliseconds)\r\n *\r\n * @param input - The value to parse.\r\n * @returns A valid Date object.\r\n * @throws {InvalidDateError} If the input cannot be parsed.\r\n */\r\nexport function parseDate(input: string | number | Date): Date {\r\n if (input instanceof Date) {\r\n if (isNaN(input.getTime())) throw new InvalidDateError(input)\r\n return new Date(input.getTime())\r\n }\r\n\r\n if (typeof input === 'number') {\r\n const d = new Date(input)\r\n if (isNaN(d.getTime())) throw new InvalidDateError(input)\r\n return d\r\n }\r\n\r\n const trimmed = input.trim()\r\n\r\n // ISO format: YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss\r\n const isoMatch = trimmed.match(/^(\\d{4})-(\\d{2})-(\\d{2})(?:T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?)?(?:Z|[+-]\\d{2}:?\\d{2})?$/)\r\n if (isoMatch) {\r\n const d = new Date(\r\n parseInt(isoMatch[1]!, 10),\r\n parseInt(isoMatch[2]!, 10) - 1,\r\n parseInt(isoMatch[3]!, 10),\r\n isoMatch[4] ? parseInt(isoMatch[4]!, 10) : 0,\r\n isoMatch[5] ? parseInt(isoMatch[5]!, 10) : 0,\r\n isoMatch[6] ? parseInt(isoMatch[6]!, 10) : 0,\r\n isoMatch[7] ? parseInt(isoMatch[7]!.padEnd(3, '0'), 10) : 0\r\n )\r\n if (!isNaN(d.getTime())) return d\r\n }\r\n\r\n // DD/MM/YYYY or DD-MM-YYYY\r\n const dmyMatch = trimmed.match(/^(\\d{1,2})[\\/-](\\d{1,2})[\\/-](\\d{4})$/)\r\n if (dmyMatch) {\r\n const year = parseInt(dmyMatch[3]!, 10)\r\n const month = parseInt(dmyMatch[2]!, 10) - 1\r\n const day = parseInt(dmyMatch[1]!, 10)\r\n const d = new Date(year, month, day)\r\n // Validate that the date didn't overflow (e.g. Feb 29 in non-leap year)\r\n if (!isNaN(d.getTime()) && d.getMonth() === month && d.getDate() === day) return d\r\n }\r\n\r\n // DD MMM YYYY or DD MMMM YYYY\r\n const textMatch = trimmed.match(/^(\\d{1,2})\\s+([a-zA-Z]+)\\s+(\\d{4})$/)\r\n if (textMatch) {\r\n const monthIndex = MONTH_MAP[textMatch[2]!.toLowerCase()]\r\n if (monthIndex !== undefined) {\r\n const year = parseInt(textMatch[3]!, 10)\r\n const day = parseInt(textMatch[1]!, 10)\r\n const d = new Date(year, monthIndex, day)\r\n if (!isNaN(d.getTime()) && d.getMonth() === monthIndex && d.getDate() === day) return d\r\n }\r\n }\r\n\r\n // Unix timestamp (milliseconds) as string\r\n const numMatch = trimmed.match(/^-?\\d+$/)\r\n if (numMatch) {\r\n const d = new Date(parseInt(numMatch[0], 10))\r\n if (!isNaN(d.getTime())) return d\r\n }\r\n\r\n // Fallback: let Date.parse try\r\n const fallback = new Date(trimmed)\r\n if (!isNaN(fallback.getTime())) return fallback\r\n\r\n throw new InvalidDateError(input)\r\n}\r\n\r\nfunction isValidDate(d: Date): boolean {\r\n return d instanceof Date && !isNaN(d.getTime())\r\n}\r\n\r\nconst MS_IN_SECOND = 1000\r\nconst MS_IN_MINUTE = 60 * MS_IN_SECOND\r\nconst MS_IN_HOUR = 60 * MS_IN_MINUTE\r\nconst MS_IN_DAY = 24 * MS_IN_HOUR\r\n\r\n/**\r\n * Computes the difference between two dates.\r\n *\r\n * @param date1 - Start date.\r\n * @param date2 - End date.\r\n * @returns An object with years, months, days, hours, minutes, seconds.\r\n * @throws {InvalidDateError} If either date is invalid.\r\n */\r\nexport function dateDiff(date1: Date, date2: Date): DateDiff {\r\n if (!isValidDate(date1) || !isValidDate(date2)) {\r\n throw new InvalidDateError('Invalid date provided to dateDiff')\r\n }\r\n\r\n let years = date2.getFullYear() - date1.getFullYear()\r\n let months = date2.getMonth() - date1.getMonth()\r\n let days = date2.getDate() - date1.getDate()\r\n\r\n if (days < 0) {\r\n months -= 1\r\n const prevMonth = new Date(date2.getFullYear(), date2.getMonth(), 0)\r\n days += prevMonth.getDate()\r\n }\r\n\r\n if (months < 0) {\r\n years -= 1\r\n months += 12\r\n }\r\n\r\n const msDiff = Math.abs(date2.getTime() - date1.getTime())\r\n const totalSeconds = Math.floor(msDiff / MS_IN_SECOND)\r\n const hours = Math.floor((msDiff % MS_IN_DAY) / MS_IN_HOUR)\r\n const minutes = Math.floor((msDiff % MS_IN_HOUR) / MS_IN_MINUTE)\r\n const seconds = totalSeconds % 60\r\n\r\n return { years, months, days, hours, minutes, seconds }\r\n}\r\n\r\n/**\r\n * Adds days to a date.\r\n *\r\n * @param date - The original date.\r\n * @param days - Number of days to add (negative to subtract).\r\n * @returns A new Date.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function addDays(date: Date, days: number): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n const result = new Date(date.getTime())\r\n result.setDate(result.getDate() + days)\r\n return result\r\n}\r\n\r\n/**\r\n * Adds months to a date. Handles month-end overflow (e.g., Jan 31 + 1 month\r\n * becomes Feb 28).\r\n *\r\n * @param date - The original date.\r\n * @param months - Number of months to add (negative to subtract).\r\n * @returns A new Date.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function addMonths(date: Date, months: number): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n const result = new Date(date.getTime())\r\n const targetMonth = result.getMonth() + months\r\n result.setMonth(targetMonth)\r\n\r\n if (result.getMonth() !== ((targetMonth % 12) + 12) % 12) {\r\n result.setDate(0)\r\n }\r\n\r\n return result\r\n}\r\n\r\n/**\r\n * Adds years to a date. Handles leap-year overflow (e.g., Feb 29 + 1 year\r\n * becomes Feb 28).\r\n *\r\n * @param date - The original date.\r\n * @param years - Number of years to add (negative to subtract).\r\n * @returns A new Date.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function addYears(date: Date, years: number): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n const result = new Date(date.getTime())\r\n const targetYear = result.getFullYear() + years\r\n result.setFullYear(targetYear)\r\n\r\n if (result.getFullYear() !== targetYear) {\r\n result.setDate(0)\r\n }\r\n\r\n return result\r\n}\r\n\r\n/**\r\n * Returns the start of the day (00:00:00.000) for the given date.\r\n *\r\n * @param date - The date.\r\n * @returns A new Date set to midnight.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function startOfDay(date: Date): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0)\r\n}\r\n\r\n/**\r\n * Returns the end of the day (23:59:59.999) for the given date.\r\n *\r\n * @param date - The date.\r\n * @returns A new Date set to the last millisecond of the day.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function endOfDay(date: Date): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999)\r\n}\r\n\r\n/**\r\n * Returns the first moment of the month for the given date.\r\n *\r\n * @param date - The date.\r\n * @returns A new Date set to the start of the month.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function startOfMonth(date: Date): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0)\r\n}\r\n\r\n/**\r\n * Returns the last moment of the month for the given date.\r\n *\r\n * @param date - The date.\r\n * @returns A new Date set to the end of the month.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function endOfMonth(date: Date): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n return new Date(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59, 999)\r\n}\r\n\r\n/**\r\n * Returns the first moment of the year for the given date.\r\n *\r\n * @param date - The date.\r\n * @returns A new Date set to Jan 1 00:00:00.000.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function startOfYear(date: Date): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n return new Date(date.getFullYear(), 0, 1, 0, 0, 0, 0)\r\n}\r\n\r\n/**\r\n * Returns the last moment of the year for the given date.\r\n *\r\n * @param date - The date.\r\n * @returns A new Date set to Dec 31 23:59:59.999.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function endOfYear(date: Date): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n return new Date(date.getFullYear(), 12, 0, 23, 59, 59, 999)\r\n}\r\n\r\n/**\r\n * Checks if the date falls on a weekend (Saturday or Sunday).\r\n *\r\n * @param date - The date to check.\r\n * @returns Whether the date is a weekend.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function isWeekend(date: Date): boolean {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n const day = date.getDay()\r\n return day === 0 || day === 6\r\n}\r\n\r\n/**\r\n * Checks if a year is a leap year.\r\n *\r\n * @param year - The year to check.\r\n * @returns Whether the year is a leap year.\r\n */\r\nexport function isLeapYear(year: number): boolean {\r\n return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0\r\n}\r\n\r\n/**\r\n * Checks if `date1` is before `date2`.\r\n *\r\n * @param date1 - First date.\r\n * @param date2 - Second date.\r\n * @returns Whether `date1` is before `date2`.\r\n * @throws {InvalidDateError} If either date is invalid.\r\n */\r\nexport function isBefore(date1: Date, date2: Date): boolean {\r\n if (!isValidDate(date1) || !isValidDate(date2)) {\r\n throw new InvalidDateError('Invalid date provided to isBefore')\r\n }\r\n return date1.getTime() < date2.getTime()\r\n}\r\n\r\n/**\r\n * Checks if `date1` is after `date2`.\r\n *\r\n * @param date1 - First date.\r\n * @param date2 - Second date.\r\n * @returns Whether `date1` is after `date2`.\r\n * @throws {InvalidDateError} If either date is invalid.\r\n */\r\nexport function isAfter(date1: Date, date2: Date): boolean {\r\n if (!isValidDate(date1) || !isValidDate(date2)) {\r\n throw new InvalidDateError('Invalid date provided to isAfter')\r\n }\r\n return date1.getTime() > date2.getTime()\r\n}\r\n\r\n/**\r\n * Checks if a date is within the inclusive range [start, end].\r\n *\r\n * @param date - The date to check.\r\n * @param start - Start of the range.\r\n * @param end - End of the range.\r\n * @returns Whether the date is between start and end.\r\n * @throws {InvalidDateError} If any date is invalid.\r\n */\r\nexport function isBetween(date: Date, start: Date, end: Date): boolean {\r\n if (!isValidDate(date) || !isValidDate(start) || !isValidDate(end)) {\r\n throw new InvalidDateError('Invalid date provided to isBetween')\r\n }\r\n return date.getTime() >= start.getTime() && date.getTime() <= end.getTime()\r\n}\r\n\r\n/**\r\n * Checks if a date is a business day (Monday to Friday).\r\n *\r\n * @param date - The date to check.\r\n * @returns Whether the date is a business day.\r\n */\r\nexport function isBusinessDay(date: Date): boolean {\r\n return isValidDate(date) && !isWeekend(date)\r\n}\r\n\r\n/**\r\n * Adds business days (skipping weekends) to a date.\r\n *\r\n * @param date - The starting date.\r\n * @param days - Number of business days to add (negative to subtract).\r\n * @returns A new Date.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function addBusinessDays(date: Date, days: number): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n\r\n const result = new Date(date.getTime())\r\n let remaining = Math.abs(days)\r\n const step = days >= 0 ? 1 : -1\r\n\r\n while (remaining > 0) {\r\n result.setDate(result.getDate() + step)\r\n const day = result.getDay()\r\n if (day !== 0 && day !== 6) {\r\n remaining--\r\n }\r\n }\r\n\r\n return result\r\n}\r\n\r\n/**\r\n * Calculates age in years from a birth date.\r\n *\r\n * @param birthDate - The date of birth.\r\n * @returns The age in years.\r\n * @throws {InvalidDateError} If the birth date is invalid.\r\n */\r\nexport function calculateAge(birthDate: Date): number {\r\n if (!isValidDate(birthDate)) throw new InvalidDateError(birthDate)\r\n\r\n const today = new Date()\r\n let age = today.getFullYear() - birthDate.getFullYear()\r\n const monthDiff = today.getMonth() - birthDate.getMonth()\r\n\r\n if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {\r\n age--\r\n }\r\n\r\n return age\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/date/index.ts"],"sourcesContent":["/**\r\n * Error thrown when a date value is invalid.\r\n */\r\nexport class InvalidDateError extends Error {\r\n constructor(input: unknown) {\r\n super(`Invalid date: ${String(input)}`)\r\n this.name = 'InvalidDateError'\r\n }\r\n}\r\n\r\nexport interface DateDiff {\r\n years: number\r\n months: number\r\n days: number\r\n hours: number\r\n minutes: number\r\n seconds: number\r\n}\r\n\r\nconst MONTH_NAMES_SHORT = [\r\n 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',\r\n 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',\r\n] as const\r\n\r\nconst MONTH_NAMES_FULL = [\r\n 'January', 'February', 'March', 'April', 'May', 'June',\r\n 'July', 'August', 'September', 'October', 'November', 'December',\r\n] as const\r\n\r\nconst MONTH_MAP: Record<string, number> = {\r\n jan: 0, january: 0,\r\n feb: 1, february: 1,\r\n mar: 2, march: 2,\r\n apr: 3, april: 3,\r\n may: 4,\r\n jun: 5, june: 5,\r\n jul: 6, july: 6,\r\n aug: 7, august: 7,\r\n sep: 8, september: 8,\r\n oct: 9, october: 9,\r\n nov: 10, november: 10,\r\n dec: 11, december: 11,\r\n}\r\n\r\n/**\r\n * Formats a Date to a string using the given format pattern.\r\n *\r\n * Supported tokens:\r\n * - `YYYY` — 4-digit year\r\n * - `YY` — 2-digit year\r\n * - `MMMM` — full month name\r\n * - `MMM` — abbreviated month name\r\n * - `MM` — 2-digit month (01–12)\r\n * - `DD` — 2-digit day (01–31)\r\n * - `HH` — 2-digit hours (00–23)\r\n * - `mm` — 2-digit minutes\r\n * - `ss` — 2-digit seconds\r\n * - `SSS` — milliseconds\r\n *\r\n * @param date - The date to format.\r\n * @param format - The format string (default `'YYYY-MM-DD'`).\r\n * @returns The formatted date string.\r\n */\r\nexport function formatDate(date: Date, format: string = 'YYYY-MM-DD'): string {\r\n const year = date.getFullYear()\r\n const month = date.getMonth()\r\n const day = date.getDate()\r\n const hours = date.getHours()\r\n const minutes = date.getMinutes()\r\n const seconds = date.getSeconds()\r\n const ms = date.getMilliseconds()\r\n\r\n const pad = (n: number, len: number = 2): string => String(n).padStart(len, '0')\r\n\r\n return format\r\n .replace(/YYYY/g, String(year))\r\n .replace(/YY/g, String(year).slice(-2))\r\n .replace(/MMMM/g, MONTH_NAMES_FULL[month]!)\r\n .replace(/MMM/g, MONTH_NAMES_SHORT[month]!)\r\n .replace(/MM/g, pad(month + 1))\r\n .replace(/DD/g, pad(day))\r\n .replace(/HH/g, pad(hours))\r\n .replace(/mm/g, pad(minutes))\r\n .replace(/ss/g, pad(seconds))\r\n .replace(/SSS/g, pad(ms, 3))\r\n}\r\n\r\n/**\r\n * Parses a date from a string, number (timestamp in ms), or Date object.\r\n *\r\n * Supported string formats:\r\n * - ISO (`YYYY-MM-DD`, `YYYY-MM-DDTHH:mm:ss`)\r\n * - `DD/MM/YYYY` or `DD-MM-YYYY`\r\n * - `DD MMM YYYY` or `DD MMMM YYYY`\r\n * - Unix timestamp (milliseconds)\r\n *\r\n * @param input - The value to parse.\r\n * @returns A valid Date object.\r\n * @throws {InvalidDateError} If the input cannot be parsed.\r\n */\r\nexport function parseDate(input: string | number | Date): Date {\r\n if (input instanceof Date) {\r\n if (isNaN(input.getTime())) throw new InvalidDateError(input)\r\n return new Date(input.getTime())\r\n }\r\n\r\n if (typeof input === 'number') {\r\n const d = new Date(input)\r\n if (isNaN(d.getTime())) throw new InvalidDateError(input)\r\n return d\r\n }\r\n\r\n const trimmed = input.trim()\r\n\r\n // ISO format: YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss\r\n const isoMatch = trimmed.match(/^(\\d{4})-(\\d{2})-(\\d{2})(?:T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?)?(?:Z|[+-]\\d{2}:?\\d{2})?$/)\r\n if (isoMatch) {\r\n const d = new Date(\r\n parseInt(isoMatch[1]!, 10),\r\n parseInt(isoMatch[2]!, 10) - 1,\r\n parseInt(isoMatch[3]!, 10),\r\n isoMatch[4] ? parseInt(isoMatch[4]!, 10) : 0,\r\n isoMatch[5] ? parseInt(isoMatch[5]!, 10) : 0,\r\n isoMatch[6] ? parseInt(isoMatch[6]!, 10) : 0,\r\n isoMatch[7] ? parseInt(isoMatch[7]!.padEnd(3, '0'), 10) : 0\r\n )\r\n if (!isNaN(d.getTime())) return d\r\n }\r\n\r\n // DD/MM/YYYY or DD-MM-YYYY\r\n const dmyMatch = trimmed.match(/^(\\d{1,2})[\\/-](\\d{1,2})[\\/-](\\d{4})$/)\r\n if (dmyMatch) {\r\n const year = parseInt(dmyMatch[3]!, 10)\r\n const month = parseInt(dmyMatch[2]!, 10) - 1\r\n const day = parseInt(dmyMatch[1]!, 10)\r\n const d = new Date(year, month, day)\r\n // Validate that the date didn't overflow (e.g. Feb 29 in non-leap year)\r\n if (!isNaN(d.getTime()) && d.getMonth() === month && d.getDate() === day) return d\r\n }\r\n\r\n // DD MMM YYYY or DD MMMM YYYY\r\n const textMatch = trimmed.match(/^(\\d{1,2})\\s+([a-zA-Z]+)\\s+(\\d{4})$/)\r\n if (textMatch) {\r\n const monthIndex = MONTH_MAP[textMatch[2]!.toLowerCase()]\r\n if (monthIndex !== undefined) {\r\n const year = parseInt(textMatch[3]!, 10)\r\n const day = parseInt(textMatch[1]!, 10)\r\n const d = new Date(year, monthIndex, day)\r\n if (!isNaN(d.getTime()) && d.getMonth() === monthIndex && d.getDate() === day) return d\r\n }\r\n }\r\n\r\n // Unix timestamp (milliseconds) as string\r\n const numMatch = trimmed.match(/^-?\\d+$/)\r\n if (numMatch) {\r\n const d = new Date(parseInt(numMatch[0], 10))\r\n if (!isNaN(d.getTime())) return d\r\n }\r\n\r\n // Fallback: let Date.parse try\r\n const fallback = new Date(trimmed)\r\n if (!isNaN(fallback.getTime())) return fallback\r\n\r\n throw new InvalidDateError(input)\r\n}\r\n\r\nfunction isValidDate(d: Date): boolean {\r\n return d instanceof Date && !isNaN(d.getTime())\r\n}\r\n\r\nconst MS_IN_SECOND = 1000\r\nconst MS_IN_MINUTE = 60 * MS_IN_SECOND\r\nconst MS_IN_HOUR = 60 * MS_IN_MINUTE\r\nconst MS_IN_DAY = 24 * MS_IN_HOUR\r\n\r\n/**\r\n * Computes the difference between two dates.\r\n *\r\n * @param date1 - Start date.\r\n * @param date2 - End date.\r\n * @returns An object with years, months, days, hours, minutes, seconds.\r\n * @throws {InvalidDateError} If either date is invalid.\r\n */\r\nexport function dateDiff(date1: Date, date2: Date): DateDiff {\r\n if (!isValidDate(date1) || !isValidDate(date2)) {\r\n throw new InvalidDateError('Invalid date provided to dateDiff')\r\n }\r\n\r\n let years = date2.getFullYear() - date1.getFullYear()\r\n let months = date2.getMonth() - date1.getMonth()\r\n let days = date2.getDate() - date1.getDate()\r\n\r\n if (days < 0) {\r\n months -= 1\r\n const prevMonth = new Date(date2.getFullYear(), date2.getMonth(), 0)\r\n days += prevMonth.getDate()\r\n }\r\n\r\n if (months < 0) {\r\n years -= 1\r\n months += 12\r\n }\r\n\r\n const msDiff = Math.abs(date2.getTime() - date1.getTime())\r\n const totalSeconds = Math.floor(msDiff / MS_IN_SECOND)\r\n const hours = Math.floor((msDiff % MS_IN_DAY) / MS_IN_HOUR)\r\n const minutes = Math.floor((msDiff % MS_IN_HOUR) / MS_IN_MINUTE)\r\n const seconds = totalSeconds % 60\r\n\r\n return { years, months, days, hours, minutes, seconds }\r\n}\r\n\r\n/**\r\n * Adds days to a date.\r\n *\r\n * @param date - The original date.\r\n * @param days - Number of days to add (negative to subtract).\r\n * @returns A new Date.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function addDays(date: Date, days: number): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n const result = new Date(date.getTime())\r\n result.setDate(result.getDate() + days)\r\n return result\r\n}\r\n\r\n/**\r\n * Adds months to a date. Handles month-end overflow (e.g., Jan 31 + 1 month\r\n * becomes Feb 28).\r\n *\r\n * @param date - The original date.\r\n * @param months - Number of months to add (negative to subtract).\r\n * @returns A new Date.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function addMonths(date: Date, months: number): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n const result = new Date(date.getTime())\r\n const targetMonth = result.getMonth() + months\r\n result.setMonth(targetMonth)\r\n\r\n if (result.getMonth() !== ((targetMonth % 12) + 12) % 12) {\r\n result.setDate(0)\r\n }\r\n\r\n return result\r\n}\r\n\r\n/**\r\n * Adds years to a date. Handles leap-year overflow (e.g., Feb 29 + 1 year\r\n * becomes Feb 28).\r\n *\r\n * @param date - The original date.\r\n * @param years - Number of years to add (negative to subtract).\r\n * @returns A new Date.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function addYears(date: Date, years: number): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n const result = new Date(date.getTime())\r\n const targetYear = result.getFullYear() + years\r\n result.setFullYear(targetYear)\r\n\r\n if (result.getFullYear() !== targetYear) {\r\n result.setDate(0)\r\n }\r\n\r\n return result\r\n}\r\n\r\n/**\r\n * Returns the start of the day (00:00:00.000) for the given date.\r\n *\r\n * @param date - The date.\r\n * @returns A new Date set to midnight.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function startOfDay(date: Date): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0)\r\n}\r\n\r\n/**\r\n * Returns the end of the day (23:59:59.999) for the given date.\r\n *\r\n * @param date - The date.\r\n * @returns A new Date set to the last millisecond of the day.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function endOfDay(date: Date): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999)\r\n}\r\n\r\n/**\r\n * Returns the first moment of the month for the given date.\r\n *\r\n * @param date - The date.\r\n * @returns A new Date set to the start of the month.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function startOfMonth(date: Date): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0)\r\n}\r\n\r\n/**\r\n * Returns the last moment of the month for the given date.\r\n *\r\n * @param date - The date.\r\n * @returns A new Date set to the end of the month.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function endOfMonth(date: Date): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n return new Date(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59, 999)\r\n}\r\n\r\n/**\r\n * Returns the first moment of the year for the given date.\r\n *\r\n * @param date - The date.\r\n * @returns A new Date set to Jan 1 00:00:00.000.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function startOfYear(date: Date): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n return new Date(date.getFullYear(), 0, 1, 0, 0, 0, 0)\r\n}\r\n\r\n/**\r\n * Returns the last moment of the year for the given date.\r\n *\r\n * @param date - The date.\r\n * @returns A new Date set to Dec 31 23:59:59.999.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function endOfYear(date: Date): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n return new Date(date.getFullYear(), 12, 0, 23, 59, 59, 999)\r\n}\r\n\r\n/**\r\n * Checks if the date falls on a weekend (Saturday or Sunday).\r\n *\r\n * @param date - The date to check.\r\n * @returns Whether the date is a weekend.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function isWeekend(date: Date): boolean {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n const day = date.getDay()\r\n return day === 0 || day === 6\r\n}\r\n\r\n/**\r\n * Checks if a year is a leap year.\r\n *\r\n * @param year - The year to check.\r\n * @returns Whether the year is a leap year.\r\n */\r\nexport function isLeapYear(year: number): boolean {\r\n return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0\r\n}\r\n\r\n/**\r\n * Checks if `date1` is before `date2`.\r\n *\r\n * @param date1 - First date.\r\n * @param date2 - Second date.\r\n * @returns Whether `date1` is before `date2`.\r\n * @throws {InvalidDateError} If either date is invalid.\r\n */\r\nexport function isBefore(date1: Date, date2: Date): boolean {\r\n if (!isValidDate(date1) || !isValidDate(date2)) {\r\n throw new InvalidDateError('Invalid date provided to isBefore')\r\n }\r\n return date1.getTime() < date2.getTime()\r\n}\r\n\r\n/**\r\n * Checks if `date1` is after `date2`.\r\n *\r\n * @param date1 - First date.\r\n * @param date2 - Second date.\r\n * @returns Whether `date1` is after `date2`.\r\n * @throws {InvalidDateError} If either date is invalid.\r\n */\r\nexport function isAfter(date1: Date, date2: Date): boolean {\r\n if (!isValidDate(date1) || !isValidDate(date2)) {\r\n throw new InvalidDateError('Invalid date provided to isAfter')\r\n }\r\n return date1.getTime() > date2.getTime()\r\n}\r\n\r\n/**\r\n * Checks if a date is within the inclusive range [start, end].\r\n *\r\n * @param date - The date to check.\r\n * @param start - Start of the range.\r\n * @param end - End of the range.\r\n * @returns Whether the date is between start and end.\r\n * @throws {InvalidDateError} If any date is invalid.\r\n */\r\nexport function isBetween(date: Date, start: Date, end: Date): boolean {\r\n if (!isValidDate(date) || !isValidDate(start) || !isValidDate(end)) {\r\n throw new InvalidDateError('Invalid date provided to isBetween')\r\n }\r\n return date.getTime() >= start.getTime() && date.getTime() <= end.getTime()\r\n}\r\n\r\n/**\r\n * Checks if a date is a business day (Monday to Friday).\r\n *\r\n * @param date - The date to check.\r\n * @returns Whether the date is a business day.\r\n */\r\nexport function isBusinessDay(date: Date): boolean {\r\n return isValidDate(date) && !isWeekend(date)\r\n}\r\n\r\n/**\r\n * Adds business days (skipping weekends) to a date.\r\n *\r\n * @param date - The starting date.\r\n * @param days - Number of business days to add (negative to subtract).\r\n * @returns A new Date.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function addBusinessDays(date: Date, days: number): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n\r\n const result = new Date(date.getTime())\r\n let remaining = Math.abs(days)\r\n const step = days >= 0 ? 1 : -1\r\n\r\n while (remaining > 0) {\r\n result.setDate(result.getDate() + step)\r\n const day = result.getDay()\r\n if (day !== 0 && day !== 6) {\r\n remaining--\r\n }\r\n }\r\n\r\n return result\r\n}\r\n\r\n/**\r\n * Calculates age in years from a birth date.\r\n *\r\n * @param birthDate - The date of birth.\r\n * @returns The age in years.\r\n * @throws {InvalidDateError} If the birth date is invalid.\r\n */\r\nexport function calculateAge(birthDate: Date): number {\r\n if (!isValidDate(birthDate)) throw new InvalidDateError(birthDate)\r\n\r\n const today = new Date()\r\n let age = today.getFullYear() - birthDate.getFullYear()\r\n const monthDiff = today.getMonth() - birthDate.getMonth()\r\n\r\n if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {\r\n age--\r\n }\r\n\r\n return age\r\n}\r\n\r\n// ─── Time Ago & Time Remaining ───────────────────────────────────────────────\r\n\r\ninterface LocaleLabels {\r\n years: { single: string; plural: string }\r\n months: { single: string; plural: string }\r\n weeks: { single: string; plural: string }\r\n days: { single: string; plural: string }\r\n hours: { single: string; plural: string }\r\n minutes: { single: string; plural: string }\r\n seconds: { single: string; plural: string }\r\n}\r\n\r\nconst LOCALE_LABELS: Record<string, LocaleLabels> = {\r\n id: {\r\n years: { single: 'tahun', plural: 'tahun' },\r\n months: { single: 'bulan', plural: 'bulan' },\r\n weeks: { single: 'minggu', plural: 'minggu' },\r\n days: { single: 'hari', plural: 'hari' },\r\n hours: { single: 'jam', plural: 'jam' },\r\n minutes: { single: 'menit', plural: 'menit' },\r\n seconds: { single: 'detik', plural: 'detik' },\r\n },\r\n en: {\r\n years: { single: 'year', plural: 'years' },\r\n months: { single: 'month', plural: 'months' },\r\n weeks: { single: 'week', plural: 'weeks' },\r\n days: { single: 'day', plural: 'days' },\r\n hours: { single: 'hour', plural: 'hours' },\r\n minutes: { single: 'minute', plural: 'minutes' },\r\n seconds: { single: 'second', plural: 'seconds' },\r\n },\r\n}\r\n\r\nfunction getSuffix(diffMs: number, kind: 'ago' | 'remaining', locale: string): string {\r\n if (diffMs < 0) {\r\n return locale === 'en' ? 'ago' : 'yang lalu'\r\n }\r\n if (kind === 'remaining') {\r\n return locale === 'en' ? 'remaining' : 'lagi'\r\n }\r\n return locale === 'en' ? 'ago' : 'yang lalu'\r\n}\r\n\r\nfunction formatRelativeTime(absDiffMs: number, suffix: string, locale: string): string {\r\n const labels = LOCALE_LABELS[locale] ?? LOCALE_LABELS.id!\r\n\r\n const seconds = Math.floor(absDiffMs / 1000)\r\n const minutes = Math.floor(seconds / 60)\r\n const hours = Math.floor(minutes / 60)\r\n const days = Math.floor(hours / 24)\r\n const weeks = Math.floor(days / 7)\r\n const months = Math.floor(days / 30.4375)\r\n const years = Math.floor(days / 365.25)\r\n\r\n let count: number\r\n let unit: keyof LocaleLabels\r\n\r\n if (years >= 1) { count = years; unit = 'years' }\r\n else if (months >= 1) { count = months; unit = 'months' }\r\n else if (weeks >= 1) { count = weeks; unit = 'weeks' }\r\n else if (days >= 1) { count = days; unit = 'days' }\r\n else if (hours >= 1) { count = hours; unit = 'hours' }\r\n else if (minutes >= 1) { count = minutes; unit = 'minutes' }\r\n else { count = Math.max(1, seconds); unit = 'seconds' }\r\n\r\n const label = count === 1 ? labels[unit].single : labels[unit].plural\r\n return `${count} ${label} ${suffix}`\r\n}\r\n\r\n/**\r\n * Returns a human-readable relative time string (e.g. \"5 menit yang lalu\").\r\n *\r\n * @param date - The past date.\r\n * @param options - Options with locale ('id' by default, 'en' supported).\r\n * @returns A relative time string.\r\n */\r\nexport function timeAgo(date: Date, options?: { locale?: string }): string {\r\n const diff = Date.now() - date.getTime()\r\n const locale = options?.locale ?? 'id'\r\n const suffix = getSuffix(diff, 'ago', locale)\r\n return formatRelativeTime(Math.abs(diff), suffix, locale)\r\n}\r\n\r\n/**\r\n * Shows the time remaining until a future date.\r\n *\r\n * @param target - The target future date.\r\n * @param options - Options with locale ('id' by default, 'en' supported).\r\n * @returns A relative time string.\r\n */\r\nexport function timeRemaining(target: Date, options?: { locale?: string }): string {\r\n const diff = target.getTime() - Date.now()\r\n const locale = options?.locale ?? 'id'\r\n const suffix = getSuffix(diff, 'remaining', locale)\r\n return formatRelativeTime(Math.abs(diff), suffix, locale)\r\n}\r\n\r\n// ─── Duration ────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Represents a duration split into calendar and time components.\r\n */\r\nexport interface Duration {\r\n years: number\r\n months: number\r\n days: number\r\n hours: number\r\n minutes: number\r\n seconds: number\r\n}\r\n\r\n/**\r\n * Formats a Duration object into a human-readable string.\r\n *\r\n * @example\r\n * formatDuration({ hours: 2, minutes: 30, seconds: 15 }) // \"2 jam 30 menit 15 detik\"\r\n * formatDuration({ hours: 2, minutes: 30 }, { locale: 'en' }) // \"2 hours 30 minutes\"\r\n *\r\n * @param duration - The duration to format.\r\n * @param options - Options with locale ('id' by default, 'en' supported).\r\n * @returns A formatted duration string.\r\n */\r\nexport function formatDuration(duration: Duration, options?: { locale?: string }): string {\r\n const locale = options?.locale ?? 'id'\r\n const labels = LOCALE_LABELS[locale] ?? LOCALE_LABELS.id!\r\n\r\n const parts: string[] = []\r\n const entries: [keyof Duration, number][] = [\r\n ['years', duration.years],\r\n ['months', duration.months],\r\n ['days', duration.days],\r\n ['hours', duration.hours],\r\n ['minutes', duration.minutes],\r\n ['seconds', duration.seconds],\r\n ]\r\n\r\n for (const [key, value] of entries) {\r\n if (value > 0) {\r\n const label = value === 1 ? labels[key].single : labels[key].plural\r\n parts.push(`${value} ${label}`)\r\n }\r\n }\r\n\r\n if (parts.length === 0) {\r\n const label = labels.seconds.plural\r\n return `0 ${label}`\r\n }\r\n\r\n return parts.join(' ')\r\n}\r\n\r\n// ─── Timezone Helpers ────────────────────────────────────────────────────────\r\n\r\n/** UTC+7 — Western Indonesia Time (WIB). */\r\nexport const TIMEZONE_WIB = 7\r\n\r\n/** UTC+8 — Central Indonesia Time (WITA). */\r\nexport const TIMEZONE_WITA = 8\r\n\r\n/** UTC+9 — Eastern Indonesia Time (WIT). */\r\nexport const TIMEZONE_WIT = 9\r\n\r\n/**\r\n * Converts a date to a specific timezone offset by returning a new Date whose\r\n * local-time getters (getHours, getMinutes etc.) reflect the target timezone.\r\n *\r\n * @param date - The source date.\r\n * @param offsetHours - The timezone offset in hours (e.g. 7 for WIB).\r\n * @returns A new Date adjusted to the target timezone.\r\n * @throws {InvalidDateError} If the input date is invalid.\r\n */\r\nexport function toTimezone(date: Date, offsetHours: number): Date {\r\n if (!isValidDate(date)) throw new InvalidDateError(date)\r\n const utcMs = date.getTime() + date.getTimezoneOffset() * 60000\r\n return new Date(utcMs + offsetHours * 3600000)\r\n}\r\n\r\n/**\r\n * Formats a date in a specific timezone using `formatDate` tokens.\r\n *\r\n * @param date - The source date.\r\n * @param format - The format string (see `formatDate` for supported tokens).\r\n * @param offsetHours - The timezone offset in hours.\r\n * @returns The formatted date string.\r\n */\r\nexport function formatInTimezone(date: Date, format: string, offsetHours: number): string {\r\n return formatDate(toTimezone(date, offsetHours), format)\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Predefined error codes mapped to their default HTTP status codes.
|
|
3
|
+
*/
|
|
4
|
+
interface ErrorCodeMap {
|
|
5
|
+
'BAD_REQUEST': 400;
|
|
6
|
+
'UNAUTHORIZED': 401;
|
|
7
|
+
'FORBIDDEN': 403;
|
|
8
|
+
'NOT_FOUND': 404;
|
|
9
|
+
'CONFLICT': 409;
|
|
10
|
+
'VALIDATION_ERROR': 422;
|
|
11
|
+
'TOO_MANY': 429;
|
|
12
|
+
'INTERNAL': 500;
|
|
13
|
+
'BAD_GATEWAY': 502;
|
|
14
|
+
'UNAVAILABLE': 503;
|
|
15
|
+
}
|
|
16
|
+
/** Union of all known error codes. */
|
|
17
|
+
type ErrorCode = keyof ErrorCodeMap;
|
|
18
|
+
/**
|
|
19
|
+
* A typed error with a machine-readable code, HTTP status, optional details, and cause.
|
|
20
|
+
*
|
|
21
|
+
* - `code` – Short machine-readable identifier (e.g. `'NOT_FOUND'`).
|
|
22
|
+
* - `status` – Defaults to the mapped HTTP status for the code; can be overridden.
|
|
23
|
+
* - `details` – Arbitrary metadata attached to the error.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* throw new TypedError('NOT_FOUND', 'User not found', { details: { userId } })
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare class TypedError extends Error {
|
|
31
|
+
readonly code: string;
|
|
32
|
+
readonly status: number;
|
|
33
|
+
readonly details?: unknown;
|
|
34
|
+
constructor(code: string, message: string, options?: {
|
|
35
|
+
status?: number;
|
|
36
|
+
details?: unknown;
|
|
37
|
+
cause?: unknown;
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Serialize the error to a plain JSON-safe object.
|
|
41
|
+
*/
|
|
42
|
+
toJSON(): {
|
|
43
|
+
name: string;
|
|
44
|
+
message: string;
|
|
45
|
+
code: string;
|
|
46
|
+
status: number;
|
|
47
|
+
details: unknown;
|
|
48
|
+
cause: unknown;
|
|
49
|
+
stack?: string;
|
|
50
|
+
};
|
|
51
|
+
toString(): string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create a typed error from a known error code.
|
|
55
|
+
*
|
|
56
|
+
* The HTTP status is automatically derived from the code but can be overridden
|
|
57
|
+
* via an explicit `status` in options.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* throw createError('NOT_FOUND', 'User not found', { details: { userId: 1 } })
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function createError(code: ErrorCode, message: string, options?: {
|
|
65
|
+
details?: unknown;
|
|
66
|
+
cause?: unknown;
|
|
67
|
+
}): TypedError;
|
|
68
|
+
/**
|
|
69
|
+
* Checks whether an unknown value is a {@link TypedError}.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* if (isTypedError(err)) {
|
|
74
|
+
* console.log(err.code, err.status)
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
declare function isTypedError(error: unknown): error is TypedError;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Collects multiple errors into a single aggregate error.
|
|
82
|
+
*
|
|
83
|
+
* Useful for batch operations where independent steps may each fail and you
|
|
84
|
+
* want to report all failures together rather than throwing on the first one.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* const err = new MultiError([
|
|
89
|
+
* new Error('First failure'),
|
|
90
|
+
* new Error('Second failure'),
|
|
91
|
+
* ])
|
|
92
|
+
* throw err
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
declare class MultiError extends Error {
|
|
96
|
+
readonly errors: Error[];
|
|
97
|
+
constructor(errors: Error[], message?: string);
|
|
98
|
+
/** Number of collected errors. */
|
|
99
|
+
get length(): number;
|
|
100
|
+
/**
|
|
101
|
+
* Check if any collected error satisfies `predicate`.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* if (err.some(e => e.message.includes('timeout'))) { … }
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
some(predicate: (error: Error) => boolean): boolean;
|
|
109
|
+
/** Array of all error messages. */
|
|
110
|
+
get messages(): string[];
|
|
111
|
+
/**
|
|
112
|
+
* Serialize to a plain JSON-safe object.
|
|
113
|
+
*/
|
|
114
|
+
toJSON(): {
|
|
115
|
+
name: string;
|
|
116
|
+
message: string;
|
|
117
|
+
errors: Array<{
|
|
118
|
+
name: string;
|
|
119
|
+
message: string;
|
|
120
|
+
stack?: string;
|
|
121
|
+
}>;
|
|
122
|
+
stack?: string;
|
|
123
|
+
};
|
|
124
|
+
toString(): string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Run `fn` and capture any thrown error.
|
|
128
|
+
*
|
|
129
|
+
* If `fn` returns successfully, `result` holds the return value and `errors`
|
|
130
|
+
* is an empty array. If `fn` throws, `errors` contains the thrown value
|
|
131
|
+
* (wrapped in an `Error` if it is not already one).
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* const { result, errors } = collectErrors(() => {
|
|
136
|
+
* return riskyOperation()
|
|
137
|
+
* })
|
|
138
|
+
* if (errors.length > 0) {
|
|
139
|
+
* console.error('Operation failed', errors)
|
|
140
|
+
* }
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
declare function collectErrors<T>(fn: () => T): {
|
|
144
|
+
result?: T;
|
|
145
|
+
errors: Error[];
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export { type ErrorCode, type ErrorCodeMap, MultiError, TypedError, collectErrors, createError, isTypedError };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// src/error/createError.ts
|
|
2
|
+
var defaultStatus = {
|
|
3
|
+
"BAD_REQUEST": 400,
|
|
4
|
+
"UNAUTHORIZED": 401,
|
|
5
|
+
"FORBIDDEN": 403,
|
|
6
|
+
"NOT_FOUND": 404,
|
|
7
|
+
"CONFLICT": 409,
|
|
8
|
+
"VALIDATION_ERROR": 422,
|
|
9
|
+
"TOO_MANY": 429,
|
|
10
|
+
"INTERNAL": 500,
|
|
11
|
+
"BAD_GATEWAY": 502,
|
|
12
|
+
"UNAVAILABLE": 503
|
|
13
|
+
};
|
|
14
|
+
var TypedError = class extends Error {
|
|
15
|
+
code;
|
|
16
|
+
status;
|
|
17
|
+
details;
|
|
18
|
+
constructor(code, message, options) {
|
|
19
|
+
super(message, { cause: options?.cause });
|
|
20
|
+
this.name = "TypedError";
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.status = options?.status ?? defaultStatus[code] ?? 500;
|
|
23
|
+
this.details = options?.details;
|
|
24
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Serialize the error to a plain JSON-safe object.
|
|
28
|
+
*/
|
|
29
|
+
toJSON() {
|
|
30
|
+
return {
|
|
31
|
+
name: this.name,
|
|
32
|
+
message: this.message,
|
|
33
|
+
code: this.code,
|
|
34
|
+
status: this.status,
|
|
35
|
+
details: this.details,
|
|
36
|
+
cause: this.cause,
|
|
37
|
+
...this.stack ? { stack: this.stack } : {}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
toString() {
|
|
41
|
+
return `${this.name} [${this.code}]: ${this.message}`;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
function createError(code, message, options) {
|
|
45
|
+
return new TypedError(code, message, options);
|
|
46
|
+
}
|
|
47
|
+
function isTypedError(error) {
|
|
48
|
+
return error instanceof TypedError;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/error/MultiError.ts
|
|
52
|
+
var MultiError = class extends Error {
|
|
53
|
+
errors;
|
|
54
|
+
constructor(errors, message) {
|
|
55
|
+
const joined = errors.map((e) => e.message).join("; ");
|
|
56
|
+
super(message ?? joined);
|
|
57
|
+
this.name = "MultiError";
|
|
58
|
+
this.errors = [...errors];
|
|
59
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
60
|
+
}
|
|
61
|
+
/** Number of collected errors. */
|
|
62
|
+
get length() {
|
|
63
|
+
return this.errors.length;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if any collected error satisfies `predicate`.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* if (err.some(e => e.message.includes('timeout'))) { … }
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
some(predicate) {
|
|
74
|
+
return this.errors.some(predicate);
|
|
75
|
+
}
|
|
76
|
+
/** Array of all error messages. */
|
|
77
|
+
get messages() {
|
|
78
|
+
return this.errors.map((e) => e.message);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Serialize to a plain JSON-safe object.
|
|
82
|
+
*/
|
|
83
|
+
toJSON() {
|
|
84
|
+
return {
|
|
85
|
+
name: this.name,
|
|
86
|
+
message: this.message,
|
|
87
|
+
errors: this.errors.map((e) => ({
|
|
88
|
+
name: e.name,
|
|
89
|
+
message: e.message,
|
|
90
|
+
...e.stack ? { stack: e.stack } : {}
|
|
91
|
+
})),
|
|
92
|
+
...this.stack ? { stack: this.stack } : {}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
toString() {
|
|
96
|
+
return `${this.name}: ${this.message}`;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function collectErrors(fn) {
|
|
100
|
+
try {
|
|
101
|
+
return { result: fn(), errors: [] };
|
|
102
|
+
} catch (err) {
|
|
103
|
+
return {
|
|
104
|
+
errors: [err instanceof Error ? err : new Error(String(err))]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
MultiError,
|
|
110
|
+
TypedError,
|
|
111
|
+
collectErrors,
|
|
112
|
+
createError,
|
|
113
|
+
isTypedError
|
|
114
|
+
};
|
|
115
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/error/createError.ts","../../src/error/MultiError.ts"],"sourcesContent":["/**\n * Predefined error codes mapped to their default HTTP status codes.\n */\nexport interface ErrorCodeMap {\n 'BAD_REQUEST': 400\n 'UNAUTHORIZED': 401\n 'FORBIDDEN': 403\n 'NOT_FOUND': 404\n 'CONFLICT': 409\n 'VALIDATION_ERROR': 422\n 'TOO_MANY': 429\n 'INTERNAL': 500\n 'BAD_GATEWAY': 502\n 'UNAVAILABLE': 503\n}\n\n/** Union of all known error codes. */\nexport type ErrorCode = keyof ErrorCodeMap\n\n/** Default HTTP status for each error code. */\nconst defaultStatus: ErrorCodeMap = {\n 'BAD_REQUEST': 400,\n 'UNAUTHORIZED': 401,\n 'FORBIDDEN': 403,\n 'NOT_FOUND': 404,\n 'CONFLICT': 409,\n 'VALIDATION_ERROR': 422,\n 'TOO_MANY': 429,\n 'INTERNAL': 500,\n 'BAD_GATEWAY': 502,\n 'UNAVAILABLE': 503,\n}\n\n/**\n * A typed error with a machine-readable code, HTTP status, optional details, and cause.\n *\n * - `code` – Short machine-readable identifier (e.g. `'NOT_FOUND'`).\n * - `status` – Defaults to the mapped HTTP status for the code; can be overridden.\n * - `details` – Arbitrary metadata attached to the error.\n *\n * @example\n * ```ts\n * throw new TypedError('NOT_FOUND', 'User not found', { details: { userId } })\n * ```\n */\nexport class TypedError extends Error {\n readonly code: string\n readonly status: number\n readonly details?: unknown\n\n constructor(\n code: string,\n message: string,\n options?: { status?: number; details?: unknown; cause?: unknown },\n ) {\n super(message, { cause: options?.cause })\n this.name = 'TypedError'\n this.code = code\n this.status = options?.status ?? defaultStatus[code as ErrorCode] ?? 500\n this.details = options?.details\n\n Object.setPrototypeOf(this, new.target.prototype)\n }\n\n /**\n * Serialize the error to a plain JSON-safe object.\n */\n toJSON(): {\n name: string\n message: string\n code: string\n status: number\n details: unknown\n cause: unknown\n stack?: string\n } {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n status: this.status,\n details: this.details,\n cause: this.cause,\n ...(this.stack ? { stack: this.stack } : {}),\n }\n }\n\n override toString(): string {\n return `${this.name} [${this.code}]: ${this.message}`\n }\n}\n\n/**\n * Create a typed error from a known error code.\n *\n * The HTTP status is automatically derived from the code but can be overridden\n * via an explicit `status` in options.\n *\n * @example\n * ```ts\n * throw createError('NOT_FOUND', 'User not found', { details: { userId: 1 } })\n * ```\n */\nexport function createError(\n code: ErrorCode,\n message: string,\n options?: { details?: unknown; cause?: unknown },\n): TypedError {\n return new TypedError(code, message, options)\n}\n\n/**\n * Checks whether an unknown value is a {@link TypedError}.\n *\n * @example\n * ```ts\n * if (isTypedError(err)) {\n * console.log(err.code, err.status)\n * }\n * ```\n */\nexport function isTypedError(error: unknown): error is TypedError {\n return error instanceof TypedError\n}\n","/**\n * Collects multiple errors into a single aggregate error.\n *\n * Useful for batch operations where independent steps may each fail and you\n * want to report all failures together rather than throwing on the first one.\n *\n * @example\n * ```ts\n * const err = new MultiError([\n * new Error('First failure'),\n * new Error('Second failure'),\n * ])\n * throw err\n * ```\n */\nexport class MultiError extends Error {\n readonly errors: Error[]\n\n constructor(errors: Error[], message?: string) {\n const joined = errors.map((e) => e.message).join('; ')\n super(message ?? joined)\n this.name = 'MultiError'\n this.errors = [...errors]\n\n Object.setPrototypeOf(this, new.target.prototype)\n }\n\n /** Number of collected errors. */\n get length(): number {\n return this.errors.length\n }\n\n /**\n * Check if any collected error satisfies `predicate`.\n *\n * @example\n * ```ts\n * if (err.some(e => e.message.includes('timeout'))) { … }\n * ```\n */\n some(predicate: (error: Error) => boolean): boolean {\n return this.errors.some(predicate)\n }\n\n /** Array of all error messages. */\n get messages(): string[] {\n return this.errors.map((e) => e.message)\n }\n\n /**\n * Serialize to a plain JSON-safe object.\n */\n toJSON(): {\n name: string\n message: string\n errors: Array<{ name: string; message: string; stack?: string }>\n stack?: string\n } {\n return {\n name: this.name,\n message: this.message,\n errors: this.errors.map((e) => ({\n name: e.name,\n message: e.message,\n ...(e.stack ? { stack: e.stack } : {}),\n })),\n ...(this.stack ? { stack: this.stack } : {}),\n }\n }\n\n override toString(): string {\n return `${this.name}: ${this.message}`\n }\n}\n\n/**\n * Run `fn` and capture any thrown error.\n *\n * If `fn` returns successfully, `result` holds the return value and `errors`\n * is an empty array. If `fn` throws, `errors` contains the thrown value\n * (wrapped in an `Error` if it is not already one).\n *\n * @example\n * ```ts\n * const { result, errors } = collectErrors(() => {\n * return riskyOperation()\n * })\n * if (errors.length > 0) {\n * console.error('Operation failed', errors)\n * }\n * ```\n */\nexport function collectErrors<T>(\n fn: () => T,\n): { result?: T; errors: Error[] } {\n try {\n return { result: fn(), errors: [] }\n } catch (err) {\n return {\n errors: [err instanceof Error ? err : new Error(String(err))],\n }\n }\n}\n"],"mappings":";AAoBA,IAAM,gBAA8B;AAAA,EAClC,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,oBAAoB;AAAA,EACpB,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,eAAe;AACjB;AAcO,IAAM,aAAN,cAAyB,MAAM;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EAET,YACE,MACA,SACA,SACA;AACA,UAAM,SAAS,EAAE,OAAO,SAAS,MAAM,CAAC;AACxC,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,SAAS,SAAS,UAAU,cAAc,IAAiB,KAAK;AACrE,SAAK,UAAU,SAAS;AAExB,WAAO,eAAe,MAAM,WAAW,SAAS;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,SAQE;AACA,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK;AAAA,MACd,OAAO,KAAK;AAAA,MACZ,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,IAC5C;AAAA,EACF;AAAA,EAES,WAAmB;AAC1B,WAAO,GAAG,KAAK,IAAI,KAAK,KAAK,IAAI,MAAM,KAAK,OAAO;AAAA,EACrD;AACF;AAaO,SAAS,YACd,MACA,SACA,SACY;AACZ,SAAO,IAAI,WAAW,MAAM,SAAS,OAAO;AAC9C;AAYO,SAAS,aAAa,OAAqC;AAChE,SAAO,iBAAiB;AAC1B;;;AC5GO,IAAM,aAAN,cAAyB,MAAM;AAAA,EAC3B;AAAA,EAET,YAAY,QAAiB,SAAkB;AAC7C,UAAM,SAAS,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI;AACrD,UAAM,WAAW,MAAM;AACvB,SAAK,OAAO;AACZ,SAAK,SAAS,CAAC,GAAG,MAAM;AAExB,WAAO,eAAe,MAAM,WAAW,SAAS;AAAA,EAClD;AAAA;AAAA,EAGA,IAAI,SAAiB;AACnB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,KAAK,WAA+C;AAClD,WAAO,KAAK,OAAO,KAAK,SAAS;AAAA,EACnC;AAAA;AAAA,EAGA,IAAI,WAAqB;AACvB,WAAO,KAAK,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,SAKE;AACA,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,QAAQ,KAAK,OAAO,IAAI,CAAC,OAAO;AAAA,QAC9B,MAAM,EAAE;AAAA,QACR,SAAS,EAAE;AAAA,QACX,GAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAAA,MACtC,EAAE;AAAA,MACF,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,IAC5C;AAAA,EACF;AAAA,EAES,WAAmB;AAC1B,WAAO,GAAG,KAAK,IAAI,KAAK,KAAK,OAAO;AAAA,EACtC;AACF;AAmBO,SAAS,cACd,IACiC;AACjC,MAAI;AACF,WAAO,EAAE,QAAQ,GAAG,GAAG,QAAQ,CAAC,EAAE;AAAA,EACpC,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,QAAQ,CAAC,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,IAC9D;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents the severity level of a log entry.
|
|
3
|
+
* Ordered from least to most severe: debug < info < warn < error.
|
|
4
|
+
*/
|
|
5
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
6
|
+
/**
|
|
7
|
+
* Log function signature bound to a specific severity level.
|
|
8
|
+
*/
|
|
9
|
+
type LogFn = (message: string, meta?: Record<string, unknown>) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Configuration options for creating a Logger instance.
|
|
12
|
+
*/
|
|
13
|
+
interface LoggerOptions {
|
|
14
|
+
/** Minimum log level to output (default: 'info'). */
|
|
15
|
+
level?: LogLevel;
|
|
16
|
+
/** Optional name tag prepended to every message as `[name]`. */
|
|
17
|
+
name?: string;
|
|
18
|
+
/** Custom transport; defaults to {@link consoleTransport}. */
|
|
19
|
+
transport?: Transport$1;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A transport handles the formatted output of log entries.
|
|
23
|
+
* Implementations write to stdout, files, buffers, or remote services.
|
|
24
|
+
*/
|
|
25
|
+
interface Transport$1 {
|
|
26
|
+
log(level: LogLevel, message: string, meta?: Record<string, unknown>): void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Structured logger with level filtering, child loggers, and pluggable transport.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const log = new Logger({ level: 'debug', name: 'app' })
|
|
34
|
+
* log.info('server started', { port: 3000 })
|
|
35
|
+
*
|
|
36
|
+
* const child = log.child({ requestId: 'abc-123' })
|
|
37
|
+
* child.warn('slow query', { durationMs: 450 })
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare class Logger {
|
|
41
|
+
private _level;
|
|
42
|
+
private _name?;
|
|
43
|
+
private _transport;
|
|
44
|
+
private _extraMeta;
|
|
45
|
+
constructor(options?: LoggerOptions);
|
|
46
|
+
private _shouldLog;
|
|
47
|
+
private _log;
|
|
48
|
+
/** Log at `debug` level. Only emitted when the current level is `'debug'`. */
|
|
49
|
+
debug: LogFn;
|
|
50
|
+
/** Log at `info` level. Emitted when level is `'debug'` or `'info'`. */
|
|
51
|
+
info: LogFn;
|
|
52
|
+
/** Log at `warn` level. Emitted when level is `'debug'`, `'info'`, or `'warn'`. */
|
|
53
|
+
warn: LogFn;
|
|
54
|
+
/** Log at `error` level. Always emitted regardless of current level. */
|
|
55
|
+
error: LogFn;
|
|
56
|
+
/**
|
|
57
|
+
* Creates a child logger that inherits the parent's level, name, and transport,
|
|
58
|
+
* but merges `extraMeta` into every log call. Child metadata is shallow-merged
|
|
59
|
+
* on top of the parent's inherited metadata.
|
|
60
|
+
*
|
|
61
|
+
* @param extraMeta - Additional context to include in every log entry.
|
|
62
|
+
*/
|
|
63
|
+
child(extraMeta: Record<string, unknown>): Logger;
|
|
64
|
+
/** Updates the minimum log level for this instance. */
|
|
65
|
+
setLevel(level: LogLevel): void;
|
|
66
|
+
/** Returns the current minimum log level. */
|
|
67
|
+
getLevel(): LogLevel;
|
|
68
|
+
/**
|
|
69
|
+
* Creates a new named Logger.
|
|
70
|
+
* Convenience shorthand for `new Logger({ ...options, name })`.
|
|
71
|
+
*
|
|
72
|
+
* @param name - The name tag shown in log output.
|
|
73
|
+
* @param options - Additional configuration.
|
|
74
|
+
*/
|
|
75
|
+
static create(name: string, options?: LoggerOptions): Logger;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Default transport that writes formatted log lines to `console.log`.
|
|
79
|
+
*
|
|
80
|
+
* Output format: `[LEVEL] message {meta}`
|
|
81
|
+
*
|
|
82
|
+
* When a logger has a name, the format becomes: `[LEVEL] [name] message {meta}`
|
|
83
|
+
*/
|
|
84
|
+
declare const consoleTransport: Transport$1;
|
|
85
|
+
/**
|
|
86
|
+
* Default logger instance at `'info'` level with no name.
|
|
87
|
+
*/
|
|
88
|
+
declare const logger: Logger;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A transport handles the formatted output of log entries.
|
|
92
|
+
*/
|
|
93
|
+
interface Transport {
|
|
94
|
+
log(level: LogLevel, message: string, meta?: Record<string, unknown>): void;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Creates a console transport that writes formatted log lines to `console.log`.
|
|
98
|
+
*
|
|
99
|
+
* The level prefix is optionally colored using ANSI escape codes:
|
|
100
|
+
* - `debug` → gray
|
|
101
|
+
* - `info` → blue
|
|
102
|
+
* - `warn` → yellow
|
|
103
|
+
* - `error` → red
|
|
104
|
+
*
|
|
105
|
+
* @param options - Configuration for the console transport.
|
|
106
|
+
* @param options.colors - Enable ANSI color output (default: `true`).
|
|
107
|
+
* @param options.timestamp - Prepend an ISO-8601 timestamp (default: `false`).
|
|
108
|
+
*/
|
|
109
|
+
declare function createConsoleTransport(options?: {
|
|
110
|
+
colors?: boolean;
|
|
111
|
+
timestamp?: boolean;
|
|
112
|
+
}): Transport;
|
|
113
|
+
/**
|
|
114
|
+
* Creates a transport that outputs structured JSON lines.
|
|
115
|
+
*
|
|
116
|
+
* Each log entry is serialized as a single JSON object with
|
|
117
|
+
* `timestamp`, `level`, `message`, and optional `meta` fields.
|
|
118
|
+
*
|
|
119
|
+
* @param options - Configuration for the JSON transport.
|
|
120
|
+
* @param options.stream - A writable stream (e.g. `process.stdout`).
|
|
121
|
+
* Defaults to `process.stdout` in Node.js, falls
|
|
122
|
+
* back to `console.log` in browsers.
|
|
123
|
+
*/
|
|
124
|
+
declare function createJsonTransport(options?: {
|
|
125
|
+
stream?: {
|
|
126
|
+
write(data: string): void;
|
|
127
|
+
};
|
|
128
|
+
}): Transport;
|
|
129
|
+
/**
|
|
130
|
+
* Creates a transport that appends log entries to a file.
|
|
131
|
+
*
|
|
132
|
+
* Each line is formatted as: `[timestamp] [LEVEL] message {meta}`
|
|
133
|
+
*
|
|
134
|
+
* ⚠️ Node.js only. Silently discards log entries when `fs` is unavailable
|
|
135
|
+
* (browsers, Deno, Bun — though Bun supports `fs`).
|
|
136
|
+
*
|
|
137
|
+
* @param filename - Path to the log file.
|
|
138
|
+
* @param options - Configuration for the file transport.
|
|
139
|
+
* @param options.maxSize - Maximum file size in bytes before rotation
|
|
140
|
+
* (default: 10 MB). **Note:** rotation is not
|
|
141
|
+
* yet implemented; this is reserved for future use.
|
|
142
|
+
*/
|
|
143
|
+
declare function createFileTransport(filename: string, _options?: {
|
|
144
|
+
maxSize?: number;
|
|
145
|
+
}): Transport;
|
|
146
|
+
/**
|
|
147
|
+
* Creates a buffered transport that batches log entries and flushes them
|
|
148
|
+
* to the underlying transport when either the buffer size or flush interval
|
|
149
|
+
* is reached (whichever comes first).
|
|
150
|
+
*
|
|
151
|
+
* Useful for reducing I/O pressure in high-throughput scenarios.
|
|
152
|
+
*
|
|
153
|
+
* @param transport - The underlying transport to flush to.
|
|
154
|
+
* @param options - Configuration for the buffer.
|
|
155
|
+
* @param options.maxSize - Maximum number of entries before forced flush
|
|
156
|
+
* (default: 100).
|
|
157
|
+
* @param options.flushIntervalMs - How often to auto-flush in milliseconds
|
|
158
|
+
* (default: 5000). Set to `0` to disable
|
|
159
|
+
* interval flushing.
|
|
160
|
+
*/
|
|
161
|
+
declare function createBufferedTransport(transport: Transport, options?: {
|
|
162
|
+
maxSize?: number;
|
|
163
|
+
flushIntervalMs?: number;
|
|
164
|
+
}): Transport;
|
|
165
|
+
|
|
166
|
+
export { type LogLevel as L, type Transport$1 as T, Logger as a, createBufferedTransport as b, consoleTransport as c, createConsoleTransport as d, createFileTransport as e, createJsonTransport as f, type Transport as g, type LogFn as h, type LoggerOptions as i, logger as l };
|
package/dist/index.d.ts
CHANGED
|
@@ -13,3 +13,6 @@ export { generateReport } from './dep-exray/reporter/index.js';
|
|
|
13
13
|
export { analyzeUsage } from './dep-exray/analyzer/index.js';
|
|
14
14
|
export { DependencyInfo, ReplacementSuggestion, ScanResult, ScannerConfig, SecurityIssue } from './dep-exray/types.js';
|
|
15
15
|
export { KNOWN_CVES, KNOWN_MAPPINGS } from './dep-exray/known-mappings.js';
|
|
16
|
+
export { isEmail, isNIK, isNPWP, isPhone, isURL } from './validation/index.js';
|
|
17
|
+
export { ErrorCode, MultiError, TypedError, collectErrors, createError, isTypedError } from './error/index.js';
|
|
18
|
+
export { L as LogLevel, a as Logger, T as Transport, c as consoleTransport, b as createBufferedTransport, d as createConsoleTransport, e as createFileTransport, f as createJsonTransport, l as logger } from './index-BgG21uJC.js';
|