@kikiutils/shared 20.0.1 → 20.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/buffer.js.map +1 -1
- package/dist/classes/enhanced-date.d.ts +2 -2
- package/dist/classes/enhanced-date.d.ts.map +1 -1
- package/dist/classes/enhanced-date.js.map +1 -1
- package/dist/classes/path.d.ts +1 -1
- package/dist/classes/path.d.ts.map +1 -1
- package/dist/classes/path.js.map +1 -1
- package/dist/enum.d.ts +3 -3
- package/dist/enum.d.ts.map +1 -1
- package/dist/enum.js.map +1 -1
- package/dist/event-awaiter.d.ts.map +1 -1
- package/dist/event-awaiter.js +1 -0
- package/dist/event-awaiter.js.map +1 -1
- package/dist/logger/_internals/formatter.d.ts +12 -4
- package/dist/logger/_internals/formatter.d.ts.map +1 -1
- package/dist/logger/_internals/formatter.js +12 -4
- package/dist/logger/_internals/formatter.js.map +1 -1
- package/dist/logger/_internals/logger.d.ts +6 -2
- package/dist/logger/_internals/logger.d.ts.map +1 -1
- package/dist/logger/_internals/logger.js +3 -1
- package/dist/logger/_internals/logger.js.map +1 -1
- package/dist/logger/node/_internals/file-writer.d.ts +3 -1
- package/dist/logger/node/_internals/file-writer.d.ts.map +1 -1
- package/dist/logger/node/_internals/file-writer.js +6 -2
- package/dist/logger/node/_internals/file-writer.js.map +1 -1
- package/dist/logger/node/_internals/formatter.d.ts +3 -1
- package/dist/logger/node/_internals/formatter.d.ts.map +1 -1
- package/dist/logger/node/_internals/formatter.js +3 -1
- package/dist/logger/node/_internals/formatter.js.map +1 -1
- package/dist/logger/node/index.js +3 -1
- package/dist/logger/node/index.js.map +1 -1
- package/dist/logger/node/types.d.ts +36 -12
- package/dist/logger/node/types.d.ts.map +1 -1
- package/dist/logger/types.d.ts +44 -19
- package/dist/logger/types.d.ts.map +1 -1
- package/dist/redis/script-runner.d.ts +1 -1
- package/dist/redis/script-runner.d.ts.map +1 -1
- package/dist/redis/script-runner.js.map +1 -1
- package/dist/types/filtered-key-path.d.ts +2 -2
- package/dist/types/filtered-key-path.d.ts.map +1 -1
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/url.d.ts.map +1 -1
- package/dist/url.js.map +1 -1
- package/package.json +3 -3
- package/src/buffer.ts +0 -1
- package/src/classes/enhanced-date.ts +2 -2
- package/src/classes/path.ts +1 -1
- package/src/enum.ts +3 -3
- package/src/event-awaiter.ts +3 -1
- package/src/logger/_internals/formatter.ts +12 -4
- package/src/logger/_internals/logger.ts +6 -2
- package/src/logger/node/_internals/file-writer.ts +6 -2
- package/src/logger/node/_internals/formatter.ts +3 -1
- package/src/logger/node/index.ts +4 -1
- package/src/logger/node/types.ts +36 -12
- package/src/logger/types.ts +45 -19
- package/src/redis/script-runner.ts +1 -1
- package/src/types/filtered-key-path.ts +2 -2
- package/src/types/index.ts +6 -0
- package/src/url.ts +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","names":[],"sources":["../../../src/logger/_internals/logger.ts"],"sourcesContent":["import type {\n Logger,\n LoggerDiagnostic,\n LoggerLevel,\n LoggerMethod,\n LoggerOptions,\n} from '../types';\n\nimport { loggerMethods } from './levels';\nimport type { LoggerRecord } from './types';\n\ninterface LoggerHooks {\n /** A non-throwing reporter created by createDiagnosticReporter. */\n report: (event: LoggerDiagnostic) => void;\n write: (record: LoggerRecord) => Promise<void> | void;\n}\n\n// Constants/Variables\nconst booleanPresentationKeys = [\n 'console',\n 'icons',\n 'timestamp',\n] as const;\n\nconst stringPresentationKeys = [\n 'prefix',\n 'suffix',\n] as const;\n\n// Functions\n\n/** A non-recursive, rate-limited diagnostic boundary shared by server and browser adapters. */\nexport function createDiagnosticReporter(\n onError?: (event: Readonly<LoggerDiagnostic>) => void,\n fallback?: (message: string) => void,\n): (event: LoggerDiagnostic) => void {\n let reporting = false;\n let callbackPending = false;\n const stateByType = new Map<LoggerDiagnostic['type'], { lastReportedAt: number; suppressedCount: number }>();\n\n return (event) => {\n if (reporting) return;\n try {\n const now = Date.now();\n const state = stateByType.get(event.type);\n if (state && now - state.lastReportedAt < 1000) {\n state.suppressedCount++;\n return;\n }\n\n reporting = true;\n const suppressedCount = state?.suppressedCount ?? 0;\n if (suppressedCount) {\n event = {\n ...event,\n suppressedCount,\n };\n }\n\n stateByType.set(\n event.type,\n {\n lastReportedAt: now,\n suppressedCount: 0,\n },\n );\n\n try {\n const summary = suppressedCount ? ` (${suppressedCount} similar diagnostics suppressed)` : '';\n fallback?.(`[logger] ${event.message}${summary}\\n`);\n } catch {\n // Diagnostics must never re-enter the normal logging pipeline.\n }\n\n if (!callbackPending) {\n try {\n callbackPending = true;\n const result: unknown = onError?.(Object.freeze({ ...event }));\n if (result !== null && (typeof result === 'object' || typeof result === 'function')) {\n void Promise.resolve(result).then(\n () => {\n callbackPending = false;\n },\n () => {\n callbackPending = false;\n },\n );\n } else {\n callbackPending = false;\n }\n } catch {\n callbackPending = false;\n // User diagnostic callbacks have the same non-throwing boundary.\n }\n }\n } catch { /* Diagnostics must never escape into the logging pipeline. */ } finally {\n reporting = false;\n }\n };\n}\n\nexport function createLoggerCore(options: LoggerOptions, hooks: LoggerHooks): Readonly<Logger> {\n validateLevel(options.level);\n validatePresentation(options);\n if (options.onError !== undefined && typeof options.onError !== 'function') {\n throw new TypeError('Logger onError must be a function');\n }\n\n const presentation = {\n console: options.console ?? true,\n icons: options.icons ?? true,\n prefix: options.prefix ?? '',\n suffix: options.suffix ?? '',\n timestamp: options.timestamp ?? true,\n };\n\n const { report } = hooks;\n\n let override = options.level === 'env' ? undefined : options.level;\n let previousEnvironment: string | undefined;\n let currentLevel: LoggerLevel = override ?? 'info';\n let environmentRead = false;\n\n function refreshLevel() {\n if (override !== undefined) return;\n const input = readEnvironmentLevel();\n if (environmentRead && input === previousEnvironment) return;\n environmentRead = true;\n previousEnvironment = input;\n const normalized = input?.trim().toLowerCase() || undefined;\n currentLevel = isLevel(normalized) ? normalized : 'info';\n if (normalized !== undefined && !isLevel(normalized)) {\n report({\n message: 'Invalid LOG_LEVEL; using info',\n type: 'invalid-level',\n });\n }\n }\n\n const logger = {\n configure(configuration) {\n validatePresentation(configuration);\n for (const name of booleanPresentationKeys) {\n const value = configuration[name];\n if (value !== undefined) presentation[name] = value;\n }\n\n for (const name of stringPresentationKeys) {\n const value = configuration[name];\n if (value !== undefined) presentation[name] = value;\n }\n },\n isLevelEnabled(level) {\n if (level !== 'success' && !isLevel(level)) throw new TypeError('Invalid logger level');\n refreshLevel();\n return level !== 'silent' && getLevelSeverity(level) >= getLevelSeverity(currentLevel);\n },\n setLevel(level) {\n validateLevel(level);\n if (level === undefined) throw new TypeError('Invalid logger level');\n override = level === 'env' ? undefined : level;\n environmentRead = false;\n if (override !== undefined) {\n currentLevel = override;\n } else {\n refreshLevel();\n }\n },\n } as Logger;\n\n for (const type of Object.keys(loggerMethods) as LoggerMethod[]) {\n logger[type] = async (...args: unknown[]) => {\n try {\n refreshLevel();\n if (loggerMethods[type].severity < getLevelSeverity(currentLevel)) return;\n await hooks.write({\n ...presentation,\n args,\n date: new Date(),\n type,\n });\n } catch (error) {\n report({\n error,\n message: 'Logger output failed',\n type: 'format',\n });\n }\n };\n }\n\n return Object.freeze(logger);\n}\n\nfunction getLevelSeverity(level: LoggerLevel | LoggerMethod): number {\n return level === 'silent' ? Number.POSITIVE_INFINITY : loggerMethods[level].severity;\n}\n\nfunction isLevel(value: unknown): value is LoggerLevel {\n return typeof value === 'string'\n && (value === 'silent' || (value !== 'success' && Object.hasOwn(loggerMethods, value)));\n}\n\nfunction readEnvironmentLevel(): string | undefined {\n return (globalThis as { process?: { env?: { LOG_LEVEL?: string } } }).process?.env?.LOG_LEVEL;\n}\n\nfunction validateLevel(value: unknown): asserts value is 'env' | LoggerLevel | undefined {\n if (value !== undefined && value !== 'env' && !isLevel(value)) throw new TypeError('Invalid logger level');\n}\n\nfunction validatePresentation(options: Pick<LoggerOptions, 'console' | 'icons' | 'prefix' | 'suffix' | 'timestamp'>) {\n for (const name of booleanPresentationKeys) {\n if (options[name] !== undefined && typeof options[name] !== 'boolean') {\n throw new TypeError(`Logger ${name} must be a boolean`);\n }\n }\n\n for (const name of stringPresentationKeys) {\n if (options[name] !== undefined && typeof options[name] !== 'string') {\n throw new TypeError(`Logger ${name} must be a string`);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"logger.js","names":[],"sources":["../../../src/logger/_internals/logger.ts"],"sourcesContent":["import type {\n Logger,\n LoggerDiagnostic,\n LoggerLevel,\n LoggerMethod,\n LoggerOptions,\n} from '../types';\n\nimport { loggerMethods } from './levels';\nimport type { LoggerRecord } from './types';\n\ninterface LoggerHooks {\n /**\n * A non-throwing reporter created by createDiagnosticReporter.\n */\n report: (event: LoggerDiagnostic) => void;\n write: (record: LoggerRecord) => Promise<void> | void;\n}\n\n// Constants/Variables\nconst booleanPresentationKeys = [\n 'console',\n 'icons',\n 'timestamp',\n] as const;\n\nconst stringPresentationKeys = [\n 'prefix',\n 'suffix',\n] as const;\n\n// Functions\n\n/**\n * A non-recursive, rate-limited diagnostic boundary shared by server and browser adapters.\n */\nexport function createDiagnosticReporter(\n onError?: (event: Readonly<LoggerDiagnostic>) => void,\n fallback?: (message: string) => void,\n): (event: LoggerDiagnostic) => void {\n let reporting = false;\n let callbackPending = false;\n const stateByType = new Map<LoggerDiagnostic['type'], { lastReportedAt: number; suppressedCount: number }>();\n\n return (event) => {\n if (reporting) return;\n try {\n const now = Date.now();\n const state = stateByType.get(event.type);\n if (state && now - state.lastReportedAt < 1000) {\n state.suppressedCount++;\n return;\n }\n\n reporting = true;\n const suppressedCount = state?.suppressedCount ?? 0;\n if (suppressedCount) {\n event = {\n ...event,\n suppressedCount,\n };\n }\n\n stateByType.set(\n event.type,\n {\n lastReportedAt: now,\n suppressedCount: 0,\n },\n );\n\n try {\n const summary = suppressedCount ? ` (${suppressedCount} similar diagnostics suppressed)` : '';\n fallback?.(`[logger] ${event.message}${summary}\\n`);\n } catch {\n // Diagnostics must never re-enter the normal logging pipeline.\n }\n\n if (!callbackPending) {\n try {\n callbackPending = true;\n const result: unknown = onError?.(Object.freeze({ ...event }));\n if (result !== null && (typeof result === 'object' || typeof result === 'function')) {\n void Promise.resolve(result).then(\n () => {\n callbackPending = false;\n },\n () => {\n callbackPending = false;\n },\n );\n } else {\n callbackPending = false;\n }\n } catch {\n callbackPending = false;\n // User diagnostic callbacks have the same non-throwing boundary.\n }\n }\n } catch { /* Diagnostics must never escape into the logging pipeline. */ } finally {\n reporting = false;\n }\n };\n}\n\nexport function createLoggerCore(options: LoggerOptions, hooks: LoggerHooks): Readonly<Logger> {\n validateLevel(options.level);\n validatePresentation(options);\n if (options.onError !== undefined && typeof options.onError !== 'function') {\n throw new TypeError('Logger onError must be a function');\n }\n\n const presentation = {\n console: options.console ?? true,\n icons: options.icons ?? true,\n prefix: options.prefix ?? '',\n suffix: options.suffix ?? '',\n timestamp: options.timestamp ?? true,\n };\n\n const { report } = hooks;\n\n let override = options.level === 'env' ? undefined : options.level;\n let previousEnvironment: string | undefined;\n let currentLevel: LoggerLevel = override ?? 'info';\n let environmentRead = false;\n\n function refreshLevel() {\n if (override !== undefined) return;\n const input = readEnvironmentLevel();\n if (environmentRead && input === previousEnvironment) return;\n environmentRead = true;\n previousEnvironment = input;\n const normalized = input?.trim().toLowerCase() || undefined;\n currentLevel = isLevel(normalized) ? normalized : 'info';\n if (normalized !== undefined && !isLevel(normalized)) {\n report({\n message: 'Invalid LOG_LEVEL; using info',\n type: 'invalid-level',\n });\n }\n }\n\n const logger = {\n configure(configuration) {\n validatePresentation(configuration);\n for (const name of booleanPresentationKeys) {\n const value = configuration[name];\n if (value !== undefined) presentation[name] = value;\n }\n\n for (const name of stringPresentationKeys) {\n const value = configuration[name];\n if (value !== undefined) presentation[name] = value;\n }\n },\n isLevelEnabled(level) {\n if (level !== 'success' && !isLevel(level)) throw new TypeError('Invalid logger level');\n refreshLevel();\n return level !== 'silent' && getLevelSeverity(level) >= getLevelSeverity(currentLevel);\n },\n setLevel(level) {\n validateLevel(level);\n if (level === undefined) throw new TypeError('Invalid logger level');\n override = level === 'env' ? undefined : level;\n environmentRead = false;\n if (override !== undefined) {\n currentLevel = override;\n } else {\n refreshLevel();\n }\n },\n } as Logger;\n\n for (const type of Object.keys(loggerMethods) as LoggerMethod[]) {\n logger[type] = async (...args: unknown[]) => {\n try {\n refreshLevel();\n if (loggerMethods[type].severity < getLevelSeverity(currentLevel)) return;\n await hooks.write({\n ...presentation,\n args,\n date: new Date(),\n type,\n });\n } catch (error) {\n report({\n error,\n message: 'Logger output failed',\n type: 'format',\n });\n }\n };\n }\n\n return Object.freeze(logger);\n}\n\nfunction getLevelSeverity(level: LoggerLevel | LoggerMethod): number {\n return level === 'silent' ? Number.POSITIVE_INFINITY : loggerMethods[level].severity;\n}\n\nfunction isLevel(value: unknown): value is LoggerLevel {\n return typeof value === 'string'\n && (value === 'silent' || (value !== 'success' && Object.hasOwn(loggerMethods, value)));\n}\n\nfunction readEnvironmentLevel(): string | undefined {\n return (globalThis as { process?: { env?: { LOG_LEVEL?: string } } }).process?.env?.LOG_LEVEL;\n}\n\nfunction validateLevel(value: unknown): asserts value is 'env' | LoggerLevel | undefined {\n if (value !== undefined && value !== 'env' && !isLevel(value)) throw new TypeError('Invalid logger level');\n}\n\nfunction validatePresentation(options: Pick<LoggerOptions, 'console' | 'icons' | 'prefix' | 'suffix' | 'timestamp'>) {\n for (const name of booleanPresentationKeys) {\n if (options[name] !== undefined && typeof options[name] !== 'boolean') {\n throw new TypeError(`Logger ${name} must be a boolean`);\n }\n }\n\n for (const name of stringPresentationKeys) {\n if (options[name] !== undefined && typeof options[name] !== 'string') {\n throw new TypeError(`Logger ${name} must be a string`);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -5,7 +5,9 @@ interface LoggerFileWriter {
|
|
|
5
5
|
getStats: () => Readonly<NodeLoggerStats>;
|
|
6
6
|
write: (text: string) => Promise<void>;
|
|
7
7
|
}
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* A bounded admission layer over the stream's own ordered write buffer; not a second FIFO.
|
|
10
|
+
*/
|
|
9
11
|
export declare function createFileWriter(options: LoggerFileOptions, report: (event: LoggerDiagnostic) => void): Promise<Readonly<LoggerFileWriter>>;
|
|
10
12
|
export {};
|
|
11
13
|
//# sourceMappingURL=file-writer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-writer.d.ts","names":[],"sources":["../../../../src/logger/node/_internals/file-writer.ts"],"mappings":";;UAiBU;EACN,aAAa;EACb,gBAAgB,SAAS;EACzB,QAAQ,iBAAiB
|
|
1
|
+
{"version":3,"file":"file-writer.d.ts","names":[],"sources":["../../../../src/logger/node/_internals/file-writer.ts"],"mappings":";;UAiBU;EACN,aAAa;EACb,gBAAgB,SAAS;EACzB,QAAQ,iBAAiB;;;;;wBAWP,iBAClB,SAAS,mBACT,SAAS,OAAO,4BACjB,QAAQ,SAAS"}
|
|
@@ -6,7 +6,9 @@ import { createWriteStream } from "node:fs";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
// src/logger/node/_internals/file-writer.ts
|
|
8
8
|
const defaultMaxPendingBytes = 8388608;
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* A bounded admission layer over the stream's own ordered write buffer; not a second FIFO.
|
|
11
|
+
*/
|
|
10
12
|
async function createFileWriter(options, report) {
|
|
11
13
|
const { maxPendingBytes, path, rotate } = validateFileOptions(options);
|
|
12
14
|
const createRotatingStream = rotate ? (await import("rotating-file-stream")).createStream : void 0;
|
|
@@ -147,7 +149,9 @@ async function createFileWriter(options, report) {
|
|
|
147
149
|
}
|
|
148
150
|
});
|
|
149
151
|
}
|
|
150
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* Validate before mkdir, stream creation, or other filesystem side effects.
|
|
154
|
+
*/
|
|
151
155
|
function validateFileOptions(options) {
|
|
152
156
|
if (!options || typeof options !== "object" || !(typeof options.path === "string" || options.path instanceof Path || options.path instanceof URL || Buffer.isBuffer(options.path))) throw new TypeError("file.path must be a string, Buffer, file URL, or Path");
|
|
153
157
|
const path = options.path instanceof URL ? fileURLToPath(options.path) : options.path.toString();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-writer.js","names":[],"sources":["../../../../src/logger/node/_internals/file-writer.ts"],"sourcesContent":["import { Buffer } from 'node:buffer';\nimport { createWriteStream } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport {\n basename,\n dirname,\n} from 'node:path';\nimport type { Writable } from 'node:stream';\nimport { fileURLToPath } from 'node:url';\n\nimport { Path } from '../../../classes/path';\nimport type { LoggerDiagnostic } from '../../types';\nimport type {\n LoggerFileOptions,\n NodeLoggerStats,\n} from '../types';\n\ninterface LoggerFileWriter {\n close: () => Promise<void>;\n getStats: () => Readonly<NodeLoggerStats>;\n write: (text: string) => Promise<void>;\n}\n\n// Constants/Variables\nconst defaultMaxPendingBytes = 8 * 1024 * 1024;\n\n// Functions\n\n/** A bounded admission layer over the stream's own ordered write buffer; not a second FIFO. */\nexport async function createFileWriter(\n options: LoggerFileOptions,\n report: (event: LoggerDiagnostic) => void,\n): Promise<Readonly<LoggerFileWriter>> {\n const {\n maxPendingBytes,\n path,\n rotate,\n } = validateFileOptions(options);\n\n const createRotatingStream = rotate ? (await import('rotating-file-stream')).createStream : undefined;\n await mkdir(dirname(path), { recursive: true });\n const stream: Writable = rotate && createRotatingStream\n ? createRotatingStream(\n basename(path),\n {\n compress: rotate.compress === false ? false : 'gzip',\n ...rotate.interval === undefined ? {} : { interval: rotate.interval === 'hourly' ? '1h' : '1d' },\n intervalBoundary: true,\n intervalUTC: false,\n maxFiles: rotate.keepFiles ?? 30,\n ...rotate.maxTotalSize === undefined ? {} : { maxSize: rotate.maxTotalSize },\n path: dirname(path),\n ...rotate.maxSize === undefined ? {} : { size: rotate.maxSize },\n },\n )\n : createWriteStream(path, { flags: 'a' });\n\n let pendingBytes = 0;\n let droppedCount = 0;\n let writeErrorCount = 0;\n let failed = false;\n let closing = false;\n let closed = false;\n let closePromise: Promise<void> | undefined;\n let initializing = true;\n const unfinished = new Set<() => void>();\n let rejectInitialization: ((error: unknown) => void) | undefined;\n\n const settleAll = () => {\n for (const settle of unfinished) settle();\n };\n\n const fail = (error: unknown) => {\n rejectInitialization?.(error);\n if (!failed) {\n failed = true;\n if (!initializing) {\n writeErrorCount++;\n report({\n error,\n message: 'File output failed and has been disabled.',\n type: 'file',\n });\n }\n }\n\n settleAll();\n if (!stream.destroyed) stream.destroy();\n };\n\n stream.on('error', fail);\n stream.on(\n 'warning',\n (error: unknown) => {\n writeErrorCount++;\n report({\n error,\n message: 'File rotation cleanup failed.',\n type: 'file',\n });\n },\n );\n\n stream.on(\n 'close',\n () => {\n closed = true;\n if (!closing && !failed) fail(new Error('File stream closed unexpectedly.'));\n settleAll();\n },\n );\n\n // A write callback also waits for RFS's asynchronous initialization and initial rotation.\n try {\n await new Promise<void>((resolveReady, rejectReady) => {\n rejectInitialization = rejectReady;\n stream.write(\n Buffer.alloc(0),\n (error) => {\n if (error) rejectReady(error);\n else resolveReady();\n },\n );\n });\n\n if (failed || closed) throw new Error('File output failed during initialization.');\n } catch (error) {\n if (!stream.destroyed) stream.destroy();\n if (!closed) {\n await new Promise<void>((resolveClosed) => {\n stream.once('close', resolveClosed);\n });\n }\n\n throw error;\n } finally {\n rejectInitialization = undefined;\n initializing = false;\n }\n\n return Object.freeze({\n close() {\n if (closePromise) return closePromise;\n closing = true;\n closePromise = new Promise<void>((resolveClosed) => {\n if (closed) {\n resolveClosed();\n return;\n }\n\n stream.once('close', resolveClosed);\n try {\n if (!stream.destroyed) stream.end();\n } catch (error) {\n fail(error);\n }\n });\n\n return closePromise;\n },\n getStats: () => Object.freeze({\n droppedCount,\n pendingBytes,\n writeErrorCount,\n }),\n write(text: string) {\n if (closing || closed || failed) {\n report({\n message: 'File output is closed or unavailable.',\n type: 'closed',\n });\n\n return Promise.resolve();\n }\n\n const bytes = Buffer.byteLength(text);\n if (bytes > maxPendingBytes - pendingBytes) {\n droppedCount++;\n report({\n droppedCount,\n message: 'File output queue is full; the newest record was discarded.',\n type: 'overflow',\n });\n\n return Promise.resolve();\n }\n\n pendingBytes += bytes;\n return new Promise<void>((resolveWritten) => {\n let settled = false;\n const settle = () => {\n if (settled) return;\n settled = true;\n pendingBytes -= bytes;\n unfinished.delete(settle);\n resolveWritten();\n };\n\n unfinished.add(settle);\n try {\n stream.write(\n text,\n (error) => {\n if (error) fail(error);\n settle();\n },\n );\n } catch (error) {\n fail(error);\n }\n });\n },\n });\n}\n\n/** Validate before mkdir, stream creation, or other filesystem side effects. */\nfunction validateFileOptions(options: LoggerFileOptions) {\n if (\n !options\n || typeof options !== 'object'\n || !(\n typeof options.path === 'string'\n || options.path instanceof Path\n || options.path instanceof URL\n || Buffer.isBuffer(options.path)\n )\n ) {\n throw new TypeError('file.path must be a string, Buffer, file URL, or Path');\n }\n\n const path = options.path instanceof URL\n ? fileURLToPath(options.path)\n : options.path.toString();\n\n if (Buffer.isBuffer(options.path) && !Buffer.from(path).equals(options.path)) {\n throw new TypeError('file.path Buffer must contain valid UTF-8');\n }\n\n if (!path.trim() || path.includes('\\0')) {\n throw new TypeError('file.path must be a nonempty path without null bytes');\n }\n\n const maxPendingBytes = options.maxPendingBytes ?? defaultMaxPendingBytes;\n if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {\n throw new RangeError('file.maxPendingBytes must be a positive safe integer');\n }\n\n const rotate = options.rotate;\n if (rotate !== undefined && rotate !== false) {\n if (!rotate || typeof rotate !== 'object') {\n throw new TypeError('file.rotate must be false or an options object');\n }\n\n if (rotate.interval !== undefined && rotate.interval !== 'hourly' && rotate.interval !== 'daily') {\n throw new TypeError('file.rotate.interval must be hourly or daily');\n }\n\n if (rotate.interval === undefined && rotate.maxSize === undefined) {\n throw new TypeError('file.rotate requires interval or maxSize');\n }\n\n const keepFiles = rotate.keepFiles ?? 30;\n if (!Number.isSafeInteger(keepFiles) || keepFiles <= 0) {\n throw new RangeError('file.rotate.keepFiles must be a positive safe integer');\n }\n\n if (rotate.compress !== undefined && typeof rotate.compress !== 'boolean') {\n throw new TypeError('file.rotate.compress must be a boolean');\n }\n\n validateSize(rotate.maxSize, 'file.rotate.maxSize');\n validateSize(rotate.maxTotalSize, 'file.rotate.maxTotalSize');\n }\n\n return {\n maxPendingBytes,\n path: Path.resolve(path).value,\n rotate,\n };\n}\n\nfunction validateSize(value: string | undefined, name: string) {\n if (value === undefined) return;\n if (typeof value !== 'string' || !/^[1-9]\\d*[BKMG]$/.test(value)) {\n throw new TypeError(`${name} must be a positive integer followed by B, K, M, or G`);\n }\n\n const multiplier = {\n B: 1,\n G: 1024 ** 3,\n K: 1024,\n M: 1024 ** 2,\n }[value.at(-1) as 'B' | 'G' | 'K' | 'M'];\n\n if (!Number.isSafeInteger(Number(value.slice(0, -1)) * multiplier)) {\n throw new RangeError(`${name} exceeds the supported size range`);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"file-writer.js","names":[],"sources":["../../../../src/logger/node/_internals/file-writer.ts"],"sourcesContent":["import { Buffer } from 'node:buffer';\nimport { createWriteStream } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport {\n basename,\n dirname,\n} from 'node:path';\nimport type { Writable } from 'node:stream';\nimport { fileURLToPath } from 'node:url';\n\nimport { Path } from '../../../classes/path';\nimport type { LoggerDiagnostic } from '../../types';\nimport type {\n LoggerFileOptions,\n NodeLoggerStats,\n} from '../types';\n\ninterface LoggerFileWriter {\n close: () => Promise<void>;\n getStats: () => Readonly<NodeLoggerStats>;\n write: (text: string) => Promise<void>;\n}\n\n// Constants/Variables\nconst defaultMaxPendingBytes = 8 * 1024 * 1024;\n\n// Functions\n\n/**\n * A bounded admission layer over the stream's own ordered write buffer; not a second FIFO.\n */\nexport async function createFileWriter(\n options: LoggerFileOptions,\n report: (event: LoggerDiagnostic) => void,\n): Promise<Readonly<LoggerFileWriter>> {\n const {\n maxPendingBytes,\n path,\n rotate,\n } = validateFileOptions(options);\n\n const createRotatingStream = rotate ? (await import('rotating-file-stream')).createStream : undefined;\n await mkdir(dirname(path), { recursive: true });\n const stream: Writable = rotate && createRotatingStream\n ? createRotatingStream(\n basename(path),\n {\n compress: rotate.compress === false ? false : 'gzip',\n ...rotate.interval === undefined ? {} : { interval: rotate.interval === 'hourly' ? '1h' : '1d' },\n intervalBoundary: true,\n intervalUTC: false,\n maxFiles: rotate.keepFiles ?? 30,\n ...rotate.maxTotalSize === undefined ? {} : { maxSize: rotate.maxTotalSize },\n path: dirname(path),\n ...rotate.maxSize === undefined ? {} : { size: rotate.maxSize },\n },\n )\n : createWriteStream(path, { flags: 'a' });\n\n let pendingBytes = 0;\n let droppedCount = 0;\n let writeErrorCount = 0;\n let failed = false;\n let closing = false;\n let closed = false;\n let closePromise: Promise<void> | undefined;\n let initializing = true;\n const unfinished = new Set<() => void>();\n let rejectInitialization: ((error: unknown) => void) | undefined;\n\n const settleAll = () => {\n for (const settle of unfinished) settle();\n };\n\n const fail = (error: unknown) => {\n rejectInitialization?.(error);\n if (!failed) {\n failed = true;\n if (!initializing) {\n writeErrorCount++;\n report({\n error,\n message: 'File output failed and has been disabled.',\n type: 'file',\n });\n }\n }\n\n settleAll();\n if (!stream.destroyed) stream.destroy();\n };\n\n stream.on('error', fail);\n stream.on(\n 'warning',\n (error: unknown) => {\n writeErrorCount++;\n report({\n error,\n message: 'File rotation cleanup failed.',\n type: 'file',\n });\n },\n );\n\n stream.on(\n 'close',\n () => {\n closed = true;\n if (!closing && !failed) fail(new Error('File stream closed unexpectedly.'));\n settleAll();\n },\n );\n\n // A write callback also waits for RFS's asynchronous initialization and initial rotation.\n try {\n await new Promise<void>((resolveReady, rejectReady) => {\n rejectInitialization = rejectReady;\n stream.write(\n Buffer.alloc(0),\n (error) => {\n if (error) rejectReady(error);\n else resolveReady();\n },\n );\n });\n\n if (failed || closed) throw new Error('File output failed during initialization.');\n } catch (error) {\n if (!stream.destroyed) stream.destroy();\n if (!closed) {\n await new Promise<void>((resolveClosed) => {\n stream.once('close', resolveClosed);\n });\n }\n\n throw error;\n } finally {\n rejectInitialization = undefined;\n initializing = false;\n }\n\n return Object.freeze({\n close() {\n if (closePromise) return closePromise;\n closing = true;\n closePromise = new Promise<void>((resolveClosed) => {\n if (closed) {\n resolveClosed();\n return;\n }\n\n stream.once('close', resolveClosed);\n try {\n if (!stream.destroyed) stream.end();\n } catch (error) {\n fail(error);\n }\n });\n\n return closePromise;\n },\n getStats: () => Object.freeze({\n droppedCount,\n pendingBytes,\n writeErrorCount,\n }),\n write(text: string) {\n if (closing || closed || failed) {\n report({\n message: 'File output is closed or unavailable.',\n type: 'closed',\n });\n\n return Promise.resolve();\n }\n\n const bytes = Buffer.byteLength(text);\n if (bytes > maxPendingBytes - pendingBytes) {\n droppedCount++;\n report({\n droppedCount,\n message: 'File output queue is full; the newest record was discarded.',\n type: 'overflow',\n });\n\n return Promise.resolve();\n }\n\n pendingBytes += bytes;\n return new Promise<void>((resolveWritten) => {\n let settled = false;\n const settle = () => {\n if (settled) return;\n settled = true;\n pendingBytes -= bytes;\n unfinished.delete(settle);\n resolveWritten();\n };\n\n unfinished.add(settle);\n try {\n stream.write(\n text,\n (error) => {\n if (error) fail(error);\n settle();\n },\n );\n } catch (error) {\n fail(error);\n }\n });\n },\n });\n}\n\n/**\n * Validate before mkdir, stream creation, or other filesystem side effects.\n */\nfunction validateFileOptions(options: LoggerFileOptions) {\n if (\n !options\n || typeof options !== 'object'\n || !(\n typeof options.path === 'string'\n || options.path instanceof Path\n || options.path instanceof URL\n || Buffer.isBuffer(options.path)\n )\n ) {\n throw new TypeError('file.path must be a string, Buffer, file URL, or Path');\n }\n\n const path = options.path instanceof URL\n ? fileURLToPath(options.path)\n : options.path.toString();\n\n if (Buffer.isBuffer(options.path) && !Buffer.from(path).equals(options.path)) {\n throw new TypeError('file.path Buffer must contain valid UTF-8');\n }\n\n if (!path.trim() || path.includes('\\0')) {\n throw new TypeError('file.path must be a nonempty path without null bytes');\n }\n\n const maxPendingBytes = options.maxPendingBytes ?? defaultMaxPendingBytes;\n if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {\n throw new RangeError('file.maxPendingBytes must be a positive safe integer');\n }\n\n const rotate = options.rotate;\n if (rotate !== undefined && rotate !== false) {\n if (!rotate || typeof rotate !== 'object') {\n throw new TypeError('file.rotate must be false or an options object');\n }\n\n if (rotate.interval !== undefined && rotate.interval !== 'hourly' && rotate.interval !== 'daily') {\n throw new TypeError('file.rotate.interval must be hourly or daily');\n }\n\n if (rotate.interval === undefined && rotate.maxSize === undefined) {\n throw new TypeError('file.rotate requires interval or maxSize');\n }\n\n const keepFiles = rotate.keepFiles ?? 30;\n if (!Number.isSafeInteger(keepFiles) || keepFiles <= 0) {\n throw new RangeError('file.rotate.keepFiles must be a positive safe integer');\n }\n\n if (rotate.compress !== undefined && typeof rotate.compress !== 'boolean') {\n throw new TypeError('file.rotate.compress must be a boolean');\n }\n\n validateSize(rotate.maxSize, 'file.rotate.maxSize');\n validateSize(rotate.maxTotalSize, 'file.rotate.maxTotalSize');\n }\n\n return {\n maxPendingBytes,\n path: Path.resolve(path).value,\n rotate,\n };\n}\n\nfunction validateSize(value: string | undefined, name: string) {\n if (value === undefined) return;\n if (typeof value !== 'string' || !/^[1-9]\\d*[BKMG]$/.test(value)) {\n throw new TypeError(`${name} must be a positive integer followed by B, K, M, or G`);\n }\n\n const multiplier = {\n B: 1,\n G: 1024 ** 3,\n K: 1024,\n M: 1024 ** 2,\n }[value.at(-1) as 'B' | 'G' | 'K' | 'M'];\n\n if (!Number.isSafeInteger(Number(value.slice(0, -1)) * multiplier)) {\n throw new RangeError(`${name} exceeds the supported size range`);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { LoggerRecord } from "../../_internals/types.js";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Preserve user ANSI for console output without invoking custom inspect callbacks/getters.
|
|
4
|
+
*/
|
|
3
5
|
export declare function formatNodeLoggerRecord(record: LoggerRecord, colors?: boolean): string;
|
|
4
6
|
//# sourceMappingURL=formatter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.d.ts","names":[],"sources":["../../../../src/logger/node/_internals/formatter.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"formatter.d.ts","names":[],"sources":["../../../../src/logger/node/_internals/formatter.ts"],"mappings":";;;;wBAUgB,uBAAuB,QAAQ,cAAc"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { formatLoggerParts } from "../../_internals/formatter.js";
|
|
2
2
|
import { inspect } from "node:util";
|
|
3
3
|
// src/logger/node/_internals/formatter.ts
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Preserve user ANSI for console output without invoking custom inspect callbacks/getters.
|
|
6
|
+
*/
|
|
5
7
|
function formatNodeLoggerRecord(record, colors = false) {
|
|
6
8
|
const message = record.args.map((arg) => typeof arg === "string" ? arg : inspect(arg, {
|
|
7
9
|
colors: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.js","names":[],"sources":["../../../../src/logger/node/_internals/formatter.ts"],"sourcesContent":["import { inspect } from 'node:util';\n\nimport { formatLoggerParts } from '../../_internals/formatter';\nimport type { LoggerRecord } from '../../_internals/types';\n\n// Functions\n\n
|
|
1
|
+
{"version":3,"file":"formatter.js","names":[],"sources":["../../../../src/logger/node/_internals/formatter.ts"],"sourcesContent":["import { inspect } from 'node:util';\n\nimport { formatLoggerParts } from '../../_internals/formatter';\nimport type { LoggerRecord } from '../../_internals/types';\n\n// Functions\n\n/**\n * Preserve user ANSI for console output without invoking custom inspect callbacks/getters.\n */\nexport function formatNodeLoggerRecord(record: LoggerRecord, colors = false) {\n const message = record.args.map(\n (arg) => typeof arg === 'string'\n ? arg\n : inspect(\n arg,\n {\n colors: false,\n customInspect: false,\n depth: 6,\n getters: false,\n maxArrayLength: 100,\n maxStringLength: 10_000,\n },\n ),\n ).join(' ');\n\n return formatLoggerParts(\n {\n ...record,\n args: record.args.length ? [message] : [],\n },\n colors,\n ).join(' ');\n}\n"],"mappings":";;;;;;AAUA,SAAgB,uBAAuB,QAAsB,SAAS,OAAO;CACzE,MAAM,UAAU,OAAO,KAAK,KACvB,QAAQ,OAAO,QAAQ,WAClB,MACA,QACE,KACA;EACI,QAAQ;EACR,eAAe;EACf,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,iBAAiB;CACrB,CACJ,CACR,CAAC,CAAC,KAAK,GAAG;CAEV,OAAO,kBACH;EACI,GAAG;EACH,MAAM,OAAO,KAAK,SAAS,CAAC,OAAO,IAAI,CAAC;CAC5C,GACA,MACJ,CAAC,CAAC,KAAK,GAAG;AACd"}
|
|
@@ -76,7 +76,9 @@ async function createNodeLogger(options = {}) {
|
|
|
76
76
|
})
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* One error listener per shared output stream; retained until its last owning logger calls close().
|
|
81
|
+
*/
|
|
80
82
|
function writeConsole(target, text, report, owner) {
|
|
81
83
|
let state = consoleStreams.get(target);
|
|
82
84
|
if (!state) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/logger/node/index.ts"],"sourcesContent":["import { writeSync } from 'node:fs';\nimport { stripVTControlCharacters } from 'node:util';\n\nimport { supportsLoggerColor } from '../_internals/formatter';\nimport { getLoggerStreamName } from '../_internals/levels';\nimport {\n createDiagnosticReporter,\n createLoggerCore,\n} from '../_internals/logger';\nimport type { LoggerDiagnostic } from '../types';\n\nimport { createFileWriter } from './_internals/file-writer';\nimport { formatNodeLoggerRecord } from './_internals/formatter';\nimport type {\n NodeLogger,\n NodeLoggerOptions,\n} from './types';\n\ninterface ConsoleStreamState {\n onClose: () => void;\n onError: (error: Error) => void;\n owners: Set<object>;\n pending: Set<(error?: Error | null) => void>;\n}\n\n// Constants/Variables\nconst consoleStreams = new WeakMap<NodeJS.WriteStream, ConsoleStreamState>();\n\n// Functions\n\n/**\n * Creates a Node.js/Bun logger with optional bounded, append-only file output and rotation.\n *\n * Log calls never throw or reject. Awaiting them waits for processing, not proof of successful\n * persistence: overload drops the newest file record and failures are reported safely. Factory\n * validation/open failures reject. Call close explicitly; no process signal handlers are installed.\n */\nexport async function createNodeLogger(options: NodeLoggerOptions = {}): Promise<Readonly<NodeLogger>> {\n const report = createDiagnosticReporter(options.onError, (message) => writeSync(2, message));\n let writer: Awaited<ReturnType<typeof createFileWriter>> | undefined;\n let closed = false;\n let closePromise: Promise<void> | undefined;\n const consoleOwner = {};\n const consoleTargets = new Set<NodeJS.WriteStream>();\n const consoleWrites = new Set<Promise<void>>();\n const logger = createLoggerCore(\n options,\n {\n report,\n write(record) {\n if (closed) {\n report({\n message: 'Logger is closed.',\n type: 'closed',\n });\n\n return;\n }\n\n if (!record.console && !writer) return;\n const target = process[getLoggerStreamName(record.type)];\n\n const text = formatNodeLoggerRecord(record, record.console && supportsLoggerColor(target));\n if (record.console) {\n const line = `${text}\\n`;\n consoleTargets.add(target);\n const completion = writeConsole(target, line, report, consoleOwner);\n consoleWrites.add(completion);\n void completion.then(() => consoleWrites.delete(completion));\n const fileCompletion = writer?.write(`${stripVTControlCharacters(text)}\\n`);\n return fileCompletion\n ? Promise.all([\n completion,\n fileCompletion,\n ]).then(() => {})\n : completion;\n }\n\n return writer?.write(`${stripVTControlCharacters(text)}\\n`);\n },\n },\n );\n\n if (options.file !== undefined && options.file !== false) writer = await createFileWriter(options.file, report);\n\n return Object.freeze({\n ...logger,\n close() {\n if (closePromise) return closePromise;\n closed = true;\n closePromise = (async () => {\n await Promise.all([\n writer?.close(),\n ...consoleWrites,\n ]);\n\n for (const target of consoleTargets) {\n const state = consoleStreams.get(target);\n if (!state) continue;\n state.owners.delete(consoleOwner);\n if (!state.owners.size) {\n target.off('error', state.onError);\n target.off('close', state.onClose);\n consoleStreams.delete(target);\n }\n }\n\n consoleTargets.clear();\n })();\n return closePromise;\n },\n getStats: () => writer?.getStats() ?? Object.freeze({\n droppedCount: 0,\n pendingBytes: 0,\n writeErrorCount: 0,\n }),\n });\n}\n\n
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/logger/node/index.ts"],"sourcesContent":["import { writeSync } from 'node:fs';\nimport { stripVTControlCharacters } from 'node:util';\n\nimport { supportsLoggerColor } from '../_internals/formatter';\nimport { getLoggerStreamName } from '../_internals/levels';\nimport {\n createDiagnosticReporter,\n createLoggerCore,\n} from '../_internals/logger';\nimport type { LoggerDiagnostic } from '../types';\n\nimport { createFileWriter } from './_internals/file-writer';\nimport { formatNodeLoggerRecord } from './_internals/formatter';\nimport type {\n NodeLogger,\n NodeLoggerOptions,\n} from './types';\n\ninterface ConsoleStreamState {\n onClose: () => void;\n onError: (error: Error) => void;\n owners: Set<object>;\n pending: Set<(error?: Error | null) => void>;\n}\n\n// Constants/Variables\nconst consoleStreams = new WeakMap<NodeJS.WriteStream, ConsoleStreamState>();\n\n// Functions\n\n/**\n * Creates a Node.js/Bun logger with optional bounded, append-only file output and rotation.\n *\n * Log calls never throw or reject. Awaiting them waits for processing, not proof of successful\n * persistence: overload drops the newest file record and failures are reported safely. Factory\n * validation/open failures reject. Call close explicitly; no process signal handlers are installed.\n */\nexport async function createNodeLogger(options: NodeLoggerOptions = {}): Promise<Readonly<NodeLogger>> {\n const report = createDiagnosticReporter(options.onError, (message) => writeSync(2, message));\n let writer: Awaited<ReturnType<typeof createFileWriter>> | undefined;\n let closed = false;\n let closePromise: Promise<void> | undefined;\n const consoleOwner = {};\n const consoleTargets = new Set<NodeJS.WriteStream>();\n const consoleWrites = new Set<Promise<void>>();\n const logger = createLoggerCore(\n options,\n {\n report,\n write(record) {\n if (closed) {\n report({\n message: 'Logger is closed.',\n type: 'closed',\n });\n\n return;\n }\n\n if (!record.console && !writer) return;\n const target = process[getLoggerStreamName(record.type)];\n\n const text = formatNodeLoggerRecord(record, record.console && supportsLoggerColor(target));\n if (record.console) {\n const line = `${text}\\n`;\n consoleTargets.add(target);\n const completion = writeConsole(target, line, report, consoleOwner);\n consoleWrites.add(completion);\n void completion.then(() => consoleWrites.delete(completion));\n const fileCompletion = writer?.write(`${stripVTControlCharacters(text)}\\n`);\n return fileCompletion\n ? Promise.all([\n completion,\n fileCompletion,\n ]).then(() => {})\n : completion;\n }\n\n return writer?.write(`${stripVTControlCharacters(text)}\\n`);\n },\n },\n );\n\n if (options.file !== undefined && options.file !== false) writer = await createFileWriter(options.file, report);\n\n return Object.freeze({\n ...logger,\n close() {\n if (closePromise) return closePromise;\n closed = true;\n closePromise = (async () => {\n await Promise.all([\n writer?.close(),\n ...consoleWrites,\n ]);\n\n for (const target of consoleTargets) {\n const state = consoleStreams.get(target);\n if (!state) continue;\n state.owners.delete(consoleOwner);\n if (!state.owners.size) {\n target.off('error', state.onError);\n target.off('close', state.onClose);\n consoleStreams.delete(target);\n }\n }\n\n consoleTargets.clear();\n })();\n\n return closePromise;\n },\n getStats: () => writer?.getStats() ?? Object.freeze({\n droppedCount: 0,\n pendingBytes: 0,\n writeErrorCount: 0,\n }),\n });\n}\n\n/**\n * One error listener per shared output stream; retained until its last owning logger calls close().\n */\nfunction writeConsole(\n target: NodeJS.WriteStream,\n text: string,\n report: (event: LoggerDiagnostic) => void,\n owner: object,\n) {\n let state = consoleStreams.get(target);\n if (!state) {\n const pending = new Set<(error?: Error | null) => void>();\n const onError = (error: Error) => {\n for (const settle of pending) settle(error);\n };\n\n const onClose = () => onError(new Error('Console stream closed.'));\n state = {\n onClose,\n onError,\n owners: new Set(),\n pending,\n };\n\n consoleStreams.set(target, state);\n target.on('error', onError);\n target.on('close', onClose);\n }\n\n state.owners.add(owner);\n const current = state;\n return new Promise<void>((resolveWritten) => {\n let settled = false;\n const settle = (error?: Error | null) => {\n if (settled) return;\n settled = true;\n current.pending.delete(settle);\n if (error) {\n report({\n error,\n message: 'Console output failed.',\n type: 'console',\n });\n }\n\n resolveWritten();\n };\n\n current.pending.add(settle);\n try {\n target.write(text, settle);\n } catch (error) {\n settle(error instanceof Error ? error : new Error('Console output failed.'));\n }\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,38 +1,62 @@
|
|
|
1
1
|
import { PathLike } from "../../classes/path.js";
|
|
2
2
|
import { Logger, LoggerOptions } from "../types.js";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Specify interval and/or maxSize. Size limits use positive integers with B, K, M, or G (binary units).
|
|
5
|
+
*/
|
|
4
6
|
export type LoggerRotationOptions = {
|
|
5
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Gzip historical files. Defaults to true.
|
|
9
|
+
*/
|
|
6
10
|
compress?: boolean;
|
|
7
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Number of historical files, excluding the active file. Defaults to 30.
|
|
13
|
+
*/
|
|
8
14
|
keepFiles?: number;
|
|
9
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Maximum combined size of historical files.
|
|
17
|
+
*/
|
|
10
18
|
maxTotalSize?: string;
|
|
11
19
|
} & ({
|
|
12
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Opt-in time rotation aligned to the system's local calendar boundary.
|
|
22
|
+
*/
|
|
13
23
|
interval: 'daily' | 'hourly';
|
|
14
24
|
maxSize?: string;
|
|
15
25
|
} | {
|
|
16
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Opt-in time rotation aligned to the system's local calendar boundary.
|
|
28
|
+
*/
|
|
17
29
|
interval?: 'daily' | 'hourly';
|
|
18
30
|
maxSize: string;
|
|
19
31
|
});
|
|
20
32
|
export interface LoggerFileOptions {
|
|
21
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Maximum unfinished payload bytes, including data already submitted to the stream.
|
|
35
|
+
*/
|
|
22
36
|
maxPendingBytes?: number;
|
|
23
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Resolved once at creation. The file is opened in append mode.
|
|
39
|
+
*/
|
|
24
40
|
path: PathLike;
|
|
25
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Omitted or false disables rotation. An object must specify interval or maxSize.
|
|
43
|
+
*/
|
|
26
44
|
rotate?: false | LoggerRotationOptions;
|
|
27
45
|
}
|
|
28
46
|
export interface NodeLogger extends Logger {
|
|
29
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Stop accepting records, finish accepted file writes, then release the stream. Not fsync.
|
|
49
|
+
*/
|
|
30
50
|
close: () => Promise<void>;
|
|
31
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* A fresh, frozen file-output snapshot. Dropped/failed records still resolve their logging promises.
|
|
53
|
+
*/
|
|
32
54
|
getStats: () => Readonly<NodeLoggerStats>;
|
|
33
55
|
}
|
|
34
56
|
export interface NodeLoggerOptions extends LoggerOptions {
|
|
35
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Disabled by default. Each logger owns at most one fixed file destination.
|
|
59
|
+
*/
|
|
36
60
|
file?: false | LoggerFileOptions;
|
|
37
61
|
}
|
|
38
62
|
export interface NodeLoggerStats {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/logger/node/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/logger/node/types.ts"],"mappings":";;;;;YASY;;;;EAIR;;;;EAKA;;;;EAKA;;;;;EAME;EACA;;;;;EAMA;EACA;;iBAIW;;;;EAIb;;;;EAKA,MAAM;;;;EAKN,iBAAiB;;iBAGJ,mBAAmB;;;;EAIhC,aAAa;;;;EAKb,gBAAgB,SAAS;;iBAGZ,0BAA0B;;;;EAIvC,eAAe;;iBAGF;EACb;EACA;EACA"}
|
package/dist/logger/types.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { SafePromise } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Minimum severity to emit; `success` uses the same threshold as `info`.
|
|
4
|
+
*/
|
|
2
5
|
export type LoggerLevel = 'debug' | 'error' | 'fatal' | 'info' | 'silent' | 'trace' | 'warn';
|
|
3
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* A logging method, including the informational success presentation.
|
|
8
|
+
*/
|
|
4
9
|
export type LoggerMethod = 'success' | Exclude<LoggerLevel, 'silent'>;
|
|
5
10
|
/**
|
|
6
11
|
* A logger whose logging methods never throw or reject for recoverable failures.
|
|
@@ -11,40 +16,60 @@ export type LoggerMethod = 'success' | Exclude<LoggerLevel, 'silent'>;
|
|
|
11
16
|
*/
|
|
12
17
|
export interface Logger {
|
|
13
18
|
configure: (options: Pick<LoggerOptions, 'console' | 'icons' | 'prefix' | 'suffix' | 'timestamp'>) => void;
|
|
14
|
-
debug: (...args: unknown[]) =>
|
|
15
|
-
error: (...args: unknown[]) =>
|
|
16
|
-
fatal: (...args: unknown[]) =>
|
|
17
|
-
info: (...args: unknown[]) =>
|
|
18
|
-
/**
|
|
19
|
+
debug: (...args: unknown[]) => SafePromise<void>;
|
|
20
|
+
error: (...args: unknown[]) => SafePromise<void>;
|
|
21
|
+
fatal: (...args: unknown[]) => SafePromise<void>;
|
|
22
|
+
info: (...args: unknown[]) => SafePromise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Accepts success as an informational method; it is not a configurable threshold.
|
|
25
|
+
*/
|
|
19
26
|
isLevelEnabled: (level: LoggerLevel | LoggerMethod) => boolean;
|
|
20
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Sets a severity threshold (not success); env restores environment-driven filtering.
|
|
29
|
+
*/
|
|
21
30
|
setLevel: (level: 'env' | LoggerLevel) => void;
|
|
22
|
-
success: (...args: unknown[]) =>
|
|
23
|
-
trace: (...args: unknown[]) =>
|
|
24
|
-
warn: (...args: unknown[]) =>
|
|
31
|
+
success: (...args: unknown[]) => SafePromise<void>;
|
|
32
|
+
trace: (...args: unknown[]) => SafePromise<void>;
|
|
33
|
+
warn: (...args: unknown[]) => SafePromise<void>;
|
|
25
34
|
}
|
|
26
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* A shallow-frozen failure notification; nested error values retain their original ownership.
|
|
37
|
+
*/
|
|
27
38
|
export interface LoggerDiagnostic {
|
|
28
39
|
droppedCount?: number;
|
|
29
40
|
error?: unknown;
|
|
30
41
|
message: string;
|
|
31
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Same-type notifications suppressed since the previous emitted diagnostic.
|
|
44
|
+
*/
|
|
32
45
|
suppressedCount?: number;
|
|
33
46
|
type: 'closed' | 'console' | 'file' | 'format' | 'invalid-level' | 'overflow';
|
|
34
47
|
}
|
|
35
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Configuration shared by browser and server loggers.
|
|
50
|
+
*/
|
|
36
51
|
export interface LoggerOptions {
|
|
37
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Enables console output. Defaults to `true`.
|
|
54
|
+
*/
|
|
38
55
|
console?: boolean;
|
|
39
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Enables Unicode level icons. Defaults to `true`.
|
|
58
|
+
*/
|
|
40
59
|
icons?: boolean;
|
|
41
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Overrides LOG_LEVEL; `env` restores the environment-driven threshold.
|
|
62
|
+
*/
|
|
42
63
|
level?: 'env' | LoggerLevel;
|
|
43
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Best-effort, rate-limited notification; failures in this callback are contained.
|
|
66
|
+
*/
|
|
44
67
|
onError?: (event: Readonly<LoggerDiagnostic>) => void;
|
|
45
68
|
prefix?: string;
|
|
46
69
|
suffix?: string;
|
|
47
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Enables the fixed local-time millisecond prefix. Defaults to `true`.
|
|
72
|
+
*/
|
|
48
73
|
timestamp?: boolean;
|
|
49
74
|
}
|
|
50
75
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/logger/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/logger/types.ts"],"mappings":";;;;YAKY;;;;YAKA,2BAA2B,QAAQ;;;;;;;;iBAS9B;EACb,YAAY,SAAS,KAAK;EAC1B,WAAW,oBAAoB;EAC/B,WAAW,oBAAoB;EAC/B,WAAW,oBAAoB;EAC/B,UAAU,oBAAoB;;;;EAK9B,iBAAiB,OAAO,cAAc;;;;EAKtC,WAAW,eAAe;EAC1B,aAAa,oBAAoB;EACjC,WAAW,oBAAoB;EAC/B,UAAU,oBAAoB;;;;;iBAMjB;EACb;EACA;EACA;;;;EAKA;EACA;;;;;iBAMa;;;;EAIb;;;;EAKA;;;;EAKA,gBAAgB;;;;EAKhB,WAAW,OAAO,SAAS;EAC3B;EACA;;;;EAKA"}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @returns A promise resolving to the Redis script result after execution completes.
|
|
14
14
|
*/
|
|
15
|
-
export type RedisScriptRunner<T = unknown> = (keys: readonly string[], args:
|
|
15
|
+
export type RedisScriptRunner<T = unknown> = (keys: readonly string[], args: ReadonlyArray<number | string>) => Promise<T>;
|
|
16
16
|
/**
|
|
17
17
|
* A caller-owned Redis client exposing raw command execution.
|
|
18
18
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"script-runner.d.ts","names":[],"sources":["../../src/redis/script-runner.ts"],"mappings":";;;;;;;;;;;;;;YAgBY,kBAAkB,gBAAgB,yBAAyB,
|
|
1
|
+
{"version":3,"file":"script-runner.d.ts","names":[],"sources":["../../src/redis/script-runner.ts"],"mappings":";;;;;;;;;;;;;;YAgBY,kBAAkB,gBAAgB,yBAAyB,MAAM,mCAAmC,QAC5G;;;;;;;;;iBAWa;;;;;;;EAOb,OAAO,iBAAiB,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;wBAwB/B,wBAAwB,aACpC,QAAQ,uBACR,gBACA,uBACD,kBAAkB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"script-runner.js","names":[],"sources":["../../src/redis/script-runner.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\n/**\n * An asynchronous Lua script runner with caller-declared results.\n *\n * @remarks\n * Input arrays are unchanged, and numeric arguments are converted to strings before execution.\n * Results are not cached or validated against the declared type.\n *\n * @typeParam T - The expected Lua result type, without runtime validation.\n *\n * @param keys - The script's Redis keys, in `KEYS` order.\n * @param args - The script's arguments, in `ARGV` order.\n *\n * @returns A promise resolving to the Redis script result after execution completes.\n */\nexport type RedisScriptRunner<T = unknown> = (keys: readonly string[], args:
|
|
1
|
+
{"version":3,"file":"script-runner.js","names":[],"sources":["../../src/redis/script-runner.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\n/**\n * An asynchronous Lua script runner with caller-declared results.\n *\n * @remarks\n * Input arrays are unchanged, and numeric arguments are converted to strings before execution.\n * Results are not cached or validated against the declared type.\n *\n * @typeParam T - The expected Lua result type, without runtime validation.\n *\n * @param keys - The script's Redis keys, in `KEYS` order.\n * @param args - The script's arguments, in `ARGV` order.\n *\n * @returns A promise resolving to the Redis script result after execution completes.\n */\nexport type RedisScriptRunner<T = unknown> = (keys: readonly string[], args: ReadonlyArray<number | string>) => Promise<\n T\n>;\n\n/**\n * A caller-owned Redis client exposing raw command execution.\n *\n * @remarks\n * The client manages connections and command routing. `SCRIPT LOAD` and `EVALSHA` must target the same server\n * for script-cache recovery to succeed. Missing-script rejections must be `Error` instances whose messages\n * begin with the Redis error code `NOSCRIPT`, followed by whitespace or the end of the message.\n */\nexport interface RedisLikeScriptClient {\n /**\n * Sends a raw Redis command and resolves to its reply, or rejects on failure.\n *\n * @param command - The Redis command name.\n * @param args - The command arguments in protocol order.\n */\n send: (command: string, args: string[]) => Promise<unknown>;\n}\n\n/**\n * Creates a Lua script runner that reloads missing cached scripts before retrying execution.\n *\n * @remarks\n * Calls use `EVALSHA` with the SHA-1 digest of the source. A `NOSCRIPT` rejection loads the script with\n * `SCRIPT LOAD` before another execution attempt, provided the attempt limit has not been reached.\n * Concurrent cache misses on the same runner share an in-flight load; reuse the runner to share that load.\n * Load failures and errors other than `NOSCRIPT` propagate without retries. The final execution error is\n * preserved when the attempt limit is reached. The runner does not cache results or close the client.\n *\n * @typeParam T - The expected Lua result type, without runtime validation.\n *\n * @param client - The caller-owned client used for raw Redis commands.\n * @param source - The Lua source loaded when the script is absent from the server's cache.\n * @param maxAttempts - The maximum `EVALSHA` attempts per invocation, including the initial attempt.\n * Must be a positive safe integer. Defaults to `3`; `1` disables script loading and retries.\n *\n * @returns A reusable runner sharing the client and any in-flight script load.\n *\n * @throws RangeError if `maxAttempts` is not a positive safe integer.\n */\nexport function createRedisScriptRunner<T = unknown>(\n client: RedisLikeScriptClient,\n source: string,\n maxAttempts: number = 3,\n): RedisScriptRunner<T> {\n if (!Number.isSafeInteger(maxAttempts) || maxAttempts < 1) {\n throw new RangeError('maxAttempts must be a positive safe integer');\n }\n\n const digest = createHash('sha1').update(source).digest('hex');\n let loadingPromise: Promise<unknown> | undefined;\n\n return async (keys, args) => {\n for (let attempt = 1; ; attempt += 1) {\n try {\n return await client.send(\n 'EVALSHA',\n [\n digest,\n String(keys.length),\n ...keys,\n ...args.map(String),\n ],\n ) as T;\n } catch (error) {\n if (\n !(error instanceof Error)\n || !/^NOSCRIPT(?:\\s|$)/.test(error.message)\n || attempt >= maxAttempts\n ) throw error;\n\n if (loadingPromise === undefined) {\n loadingPromise = client\n .send(\n 'SCRIPT',\n [\n 'LOAD',\n source,\n ],\n )\n .finally(() => loadingPromise = undefined);\n }\n\n await loadingPromise;\n }\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA2DA,SAAgB,wBACZ,QACA,QACA,cAAsB,GACF;CACpB,IAAI,CAAC,OAAO,cAAc,WAAW,KAAK,cAAc,GACpD,MAAM,IAAI,WAAW,6CAA6C;CAGtE,MAAM,SAAS,WAAW,MAAM,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,KAAK;CAC7D,IAAI;CAEJ,OAAO,OAAO,MAAM,SAAS;EACzB,KAAK,IAAI,UAAU,IAAK,WAAW,GAC/B,IAAI;GACA,OAAO,MAAM,OAAO,KAChB,WACA;IACI;IACA,OAAO,KAAK,MAAM;IAClB,GAAG;IACH,GAAG,KAAK,IAAI,MAAM;GACtB,CACJ;EACJ,SAAS,OAAO;GACZ,IACI,EAAE,iBAAiB,UAChB,CAAC,oBAAoB,KAAK,MAAM,OAAO,KACvC,WAAW,aAChB,MAAM;GAER,IAAI,mBAAmB,KAAA,GACnB,iBAAiB,OACZ,KACG,UACA,CACI,QACA,MACJ,CACJ,CAAC,CACA,cAAc,iBAAiB,KAAA,CAAS;GAGjD,MAAM;EACV;CAER;AACJ"}
|
|
@@ -8,9 +8,9 @@ type DefaultPath<K extends number | string, V, U = never, RK = `${K}`> = V exten
|
|
|
8
8
|
* @typeParam U - The value type to select; `never` disables filtering.
|
|
9
9
|
*/
|
|
10
10
|
export type FilteredKeyPath<T, U = never> = T extends ReadonlyArray<infer V> ? (IsTuple<T> extends true ? { [K in TupleKey<T>]-?: PathImpl<Exclude<K, symbol>, T[K], U>; }[TupleKey<T>] : PathImpl<number, V, U>) : { [K in keyof T]-?: PathImpl<Exclude<K, symbol>, T[K], U>; }[keyof T];
|
|
11
|
-
type IsTuple<T extends
|
|
11
|
+
type IsTuple<T extends readonly any[]> = number extends T['length'] ? false : true;
|
|
12
12
|
type PathImpl<K extends number | string, V, U> = [U] extends [never] ? DefaultPath<K, V> : ConditionalPath<K, V, U>;
|
|
13
13
|
type TerminalType = bigint | Blob | boolean | Date | File | null | number | ObjectId | RegExp | string | symbol | undefined;
|
|
14
|
-
type TupleKey<T extends
|
|
14
|
+
type TupleKey<T extends readonly any[]> = Exclude<keyof T, keyof any[]>;
|
|
15
15
|
export {};
|
|
16
16
|
//# sourceMappingURL=filtered-key-path.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filtered-key-path.d.ts","names":[],"sources":["../../src/types/filtered-key-path.ts"],"mappings":";KAKK,gBAAgB,2BAA2B,GAAG,KAAK,UAAU,OAAO,MAAM,YAAY,GAAG,GAAG;KAC5F,YACD,2BACA,GACA,WACA,QAAQ,OACR,UAAU,eAAe,QAAQ,KAAK,gBAAgB,GAAG;;;;;;;YAUjD,gBAAgB,GAAG,aAAa,UAAU,oBAAoB,MAAM,QAAQ,qBAAqB,KAAK,SAAS,OAAO,SAAS,QAAQ,YAAY,EAAE,IAAI,MAAK,SAAS,MAAM,iBAAiB,GAAG,SAAS,WAAW,MAAM,SAAS,QAAQ,YAAY,EAAE,IAAI,YAAW;KAGhR,QAAQ,
|
|
1
|
+
{"version":3,"file":"filtered-key-path.d.ts","names":[],"sources":["../../src/types/filtered-key-path.ts"],"mappings":";KAKK,gBAAgB,2BAA2B,GAAG,KAAK,UAAU,OAAO,MAAM,YAAY,GAAG,GAAG;KAC5F,YACD,2BACA,GACA,WACA,QAAQ,OACR,UAAU,eAAe,QAAQ,KAAK,gBAAgB,GAAG;;;;;;;YAUjD,gBAAgB,GAAG,aAAa,UAAU,oBAAoB,MAAM,QAAQ,qBAAqB,KAAK,SAAS,OAAO,SAAS,QAAQ,YAAY,EAAE,IAAI,MAAK,SAAS,MAAM,iBAAiB,GAAG,SAAS,WAAW,MAAM,SAAS,QAAQ,YAAY,EAAE,IAAI,YAAW;KAGhR,QAAQ,2CAA2C;KACnD,SAAS,2BAA2B,GAAG,MAAM,qBAAqB,YAAY,GAAG,KAAK,gBAAgB,GAAG,GAAG;KAC5G,wBAED,iBAEA,OACA,uBAGA,WACA;KAKC,SAAS,4BAA4B,cAAc"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -28,5 +28,12 @@ export type ReadonlyPartialRecord<K extends keyof any, T> = Readonly<PartialReco
|
|
|
28
28
|
* A readonly `Record`.
|
|
29
29
|
*/
|
|
30
30
|
export type ReadonlyRecord<K extends keyof any, T> = Readonly<Record<K, T>>;
|
|
31
|
+
/**
|
|
32
|
+
* A promise whose rejection is handled internally and can safely be left unawaited.
|
|
33
|
+
* This type only marks that contract; it does not add runtime rejection handling.
|
|
34
|
+
*/
|
|
35
|
+
export type SafePromise<T> = Promise<T> & {
|
|
36
|
+
readonly __safePromise?: true;
|
|
37
|
+
};
|
|
31
38
|
export type { FilteredKeyPath };
|
|
32
39
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":["NodeBlob","NodeFile"],"sources":["../../src/types/index.ts"],"mappings":";;;;;YAWY,cAAc,cAAc,OAAO,SAAS,OAAOA,SAAWC,SAAW;;;;YAKzE;;;;YAKA,aAAa,KAAK,QAAQ,KAAK;;;;YAK/B,cAAc,KAAK,SAAS,KAAK;;;;YAKjC,cAAc,qBAAqB,KAAK,QAAQ,OAAO,GAAG;;;;YAK1D,sBAAsB,qBAAqB,KAAK,SAAS,cAAc,GAAG;;;;YAK1E,eAAe,qBAAqB,KAAK,SAAS,OAAO,GAAG"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":["NodeBlob","NodeFile"],"sources":["../../src/types/index.ts"],"mappings":";;;;;YAWY,cAAc,cAAc,OAAO,SAAS,OAAOA,SAAWC,SAAW;;;;YAKzE;;;;YAKA,aAAa,KAAK,QAAQ,KAAK;;;;YAK/B,cAAc,KAAK,SAAS,KAAK;;;;YAKjC,cAAc,qBAAqB,KAAK,QAAQ,OAAO,GAAG;;;;YAK1D,sBAAsB,qBAAqB,KAAK,SAAS,cAAc,GAAG;;;;YAK1E,eAAe,qBAAqB,KAAK,SAAS,OAAO,GAAG;;;;;YAM5D,YAAY,KAAK,QAAQ;WAAgB"}
|
package/dist/url.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.d.ts","names":[],"sources":["../src/url.ts"],"mappings":";;;;;;;;;;;;;wBAagB,yBAAyB,aAAa;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"url.d.ts","names":[],"sources":["../src/url.ts"],"mappings":";;;;;;;;;;;;;wBAagB,yBAAyB,aAAa;;;;;;;;;;;;;wBAuBtC,mBAAmB,iBAAiB;;;;;;;;;;;;;wBAoBpC,sBAAsB,gBAAgB"}
|
package/dist/url.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.js","names":[],"sources":["../src/url.ts"],"sourcesContent":["/**\n * Appends or updates the `redirect` query parameter on a given URL.\n *\n * @remarks\n * Typically used to preserve the user's current path for post-login navigation.\n *\n * @param url - The target URL to modify.\n * @param redirectPath - The safe same-origin application path starting with `/` to use as the redirect destination.\n *\n * @returns A new URL string with the `redirect` query parameter.\n *\n * @throws Error if `redirectPath` is not a safe same-origin application path.\n */\nexport function appendRedirectParamToUrl(url: string, redirectPath: string) {\n
|
|
1
|
+
{"version":3,"file":"url.js","names":[],"sources":["../src/url.ts"],"sourcesContent":["/**\n * Appends or updates the `redirect` query parameter on a given URL.\n *\n * @remarks\n * Typically used to preserve the user's current path for post-login navigation.\n *\n * @param url - The target URL to modify.\n * @param redirectPath - The safe same-origin application path starting with `/` to use as the redirect destination.\n *\n * @returns A new URL string with the `redirect` query parameter.\n *\n * @throws Error if `redirectPath` is not a safe same-origin application path.\n */\nexport function appendRedirectParamToUrl(url: string, redirectPath: string) {\n if (!isSafeRedirectPath(redirectPath)) throw new Error(`Invalid redirect path: \"${redirectPath}\". Redirect paths must be safe same-origin paths starting with '/'.`);\n\n const [baseAndHash, rawQuery = ''] = url.split('?');\n const [base, hash] = (baseAndHash || '').split('#');\n const searchParams = new URLSearchParams(rawQuery);\n searchParams.set('redirect', redirectPath);\n const queryString = searchParams.toString();\n return hash ? `${base}?${queryString}#${hash}` : `${base}?${queryString}`;\n}\n\n/**\n * Returns whether a value is a safe same-origin redirect path.\n *\n * @remarks\n * Safe redirect paths are absolute application paths such as `/dashboard`.\n * Protocol-relative URLs (`//example.com`), absolute URLs, backslash paths,\n * and non-string values are rejected.\n *\n * @param value - The value to check.\n *\n * @returns Whether the value is safe to use as an application redirect path.\n */\nexport function isSafeRedirectPath(value: unknown): value is string {\n if (typeof value !== 'string') return false;\n if (!value.startsWith('/')) return false;\n if (value.startsWith('//')) return false;\n if (value.includes('\\\\')) return false;\n return true;\n}\n\n/**\n * Normalizes a redirect value into a safe same-origin application path.\n *\n * @remarks\n * If an array is provided, the first value is checked. Unsafe values fall back\n * to the provided fallback path.\n *\n * @param value - The redirect value to normalize.\n * @param fallback - The caller-supplied safe fallback path; it is returned unchanged and is not validated.\n *\n * @returns The validated input path, or the unvalidated fallback path.\n */\nexport function normalizeRedirectPath(value: unknown, fallback = '/') {\n const redirectPath = Array.isArray(value) ? value[0] : value;\n return isSafeRedirectPath(redirectPath) ? redirectPath : fallback;\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,SAAgB,yBAAyB,KAAa,cAAsB;CACxE,IAAI,CAAC,mBAAmB,YAAY,GAAG,MAAM,IAAI,MAAM,2BAA2B,aAAa,oEAAoE;CAEnK,MAAM,CAAC,aAAa,WAAW,MAAM,IAAI,MAAM,GAAG;CAClD,MAAM,CAAC,MAAM,SAAS,eAAe,GAAA,CAAI,MAAM,GAAG;CAClD,MAAM,eAAe,IAAI,gBAAgB,QAAQ;CACjD,aAAa,IAAI,YAAY,YAAY;CACzC,MAAM,cAAc,aAAa,SAAS;CAC1C,OAAO,OAAO,GAAG,KAAK,GAAG,YAAY,GAAG,SAAS,GAAG,KAAK,GAAG;AAChE;;;;;;;;;;;;;AAcA,SAAgB,mBAAmB,OAAiC;CAChE,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI,CAAC,MAAM,WAAW,GAAG,GAAG,OAAO;CACnC,IAAI,MAAM,WAAW,IAAI,GAAG,OAAO;CACnC,IAAI,MAAM,SAAS,IAAI,GAAG,OAAO;CACjC,OAAO;AACX;;;;;;;;;;;;;AAcA,SAAgB,sBAAsB,OAAgB,WAAW,KAAK;CAClE,MAAM,eAAe,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK;CACvD,OAAO,mBAAmB,YAAY,IAAI,eAAe;AAC7D"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kikiutils/shared",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "20.0
|
|
4
|
+
"version": "20.1.0",
|
|
5
5
|
"description": "A lightweight and modular utility library for modern JavaScript and TypeScript — includes secure hashing, flexible logging, datetime tools, Vue/web helpers, storage abstraction, and more.",
|
|
6
6
|
"author": "kiki-kanri",
|
|
7
7
|
"license": "MIT",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"devDependencies": {
|
|
147
147
|
"@antfu/eslint-config": "^10.0.0",
|
|
148
148
|
"@arethetypeswrong/core": "^0.18.5",
|
|
149
|
-
"@kikiutils/eslint-config": "^
|
|
149
|
+
"@kikiutils/eslint-config": "^10.1.0",
|
|
150
150
|
"@kikiutils/tsconfigs": "^7.0.0",
|
|
151
151
|
"@noble/hashes": "^2.4.0",
|
|
152
152
|
"@types/node": "^26.6.6",
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
"eslint": "^10.14.0",
|
|
162
162
|
"file-type": "^22.3.0",
|
|
163
163
|
"jsdom": "^30.1.3",
|
|
164
|
-
"knip": "^6.44.
|
|
164
|
+
"knip": "^6.44.1",
|
|
165
165
|
"lru-cache": "^11.5.3",
|
|
166
166
|
"millify": "^6.1.0",
|
|
167
167
|
"msgpackr": "^2.1.3",
|
package/src/buffer.ts
CHANGED
|
@@ -36,6 +36,5 @@ export async function toBuffer(input: BinaryInput) {
|
|
|
36
36
|
if (input instanceof ArrayBuffer) return Buffer.from(input);
|
|
37
37
|
if (input instanceof Uint8Array) return Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
38
38
|
if (typeof input.arrayBuffer === 'function') return Buffer.from(await input.arrayBuffer());
|
|
39
|
-
// eslint-disable-next-line style/max-len
|
|
40
39
|
throw new TypeError('The provided input is not a supported binary type (Blob, Buffer, File, ArrayBuffer, or Uint8Array).');
|
|
41
40
|
}
|
|
@@ -100,7 +100,7 @@ export class EnhancedDate extends Date {
|
|
|
100
100
|
*
|
|
101
101
|
* @returns A new `EnhancedDate`, or `Invalid Date` if the array is empty or contains an invalid date.
|
|
102
102
|
*/
|
|
103
|
-
static max(dates:
|
|
103
|
+
static max(dates: Array<Date | number>) {
|
|
104
104
|
return new EnhancedDate(dateFns.max(dates));
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -111,7 +111,7 @@ export class EnhancedDate extends Date {
|
|
|
111
111
|
*
|
|
112
112
|
* @returns A new `EnhancedDate`, or `Invalid Date` if the array is empty or contains an invalid date.
|
|
113
113
|
*/
|
|
114
|
-
static min(dates:
|
|
114
|
+
static min(dates: Array<Date | number>) {
|
|
115
115
|
return new EnhancedDate(dateFns.min(dates));
|
|
116
116
|
}
|
|
117
117
|
|