@kikiutils/shared 19.0.0 → 20.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/logger/_internals/logger.d.ts +2 -2
  2. package/dist/logger/_internals/logger.d.ts.map +1 -1
  3. package/dist/logger/_internals/logger.js +2 -2
  4. package/dist/logger/_internals/logger.js.map +1 -1
  5. package/dist/logger/index.d.ts +1 -1
  6. package/dist/logger/index.d.ts.map +1 -1
  7. package/dist/logger/index.js +1 -1
  8. package/dist/logger/index.js.map +1 -1
  9. package/dist/logger/node/_internals/file-writer.d.ts +2 -2
  10. package/dist/logger/node/_internals/file-writer.d.ts.map +1 -1
  11. package/dist/logger/node/_internals/file-writer.js +3 -3
  12. package/dist/logger/node/_internals/file-writer.js.map +1 -1
  13. package/dist/logger/node/index.d.ts +1 -1
  14. package/dist/logger/node/index.d.ts.map +1 -1
  15. package/dist/logger/node/index.js +5 -4
  16. package/dist/logger/node/index.js.map +1 -1
  17. package/dist/logger/node/types.d.ts +2 -2
  18. package/dist/logger/node/types.d.ts.map +1 -1
  19. package/dist/logger/types.d.ts +3 -2
  20. package/dist/logger/types.d.ts.map +1 -1
  21. package/dist/storages/redis/keyed-store.d.ts +4 -2
  22. package/dist/storages/redis/keyed-store.d.ts.map +1 -1
  23. package/dist/storages/redis/keyed-store.js +6 -2
  24. package/dist/storages/redis/keyed-store.js.map +1 -1
  25. package/dist/storages/redis/msgpack.d.ts +10 -1
  26. package/dist/storages/redis/msgpack.d.ts.map +1 -1
  27. package/dist/storages/redis/msgpack.js +17 -1
  28. package/dist/storages/redis/msgpack.js.map +1 -1
  29. package/dist/storages/redis/types.d.ts +30 -0
  30. package/dist/storages/redis/types.d.ts.map +1 -1
  31. package/dist/types/filtered-key-path.js +1 -0
  32. package/dist/types/index.js +1 -0
  33. package/dist/types/vue.js +1 -0
  34. package/package.json +44 -70
  35. package/src/logger/_internals/logger.ts +4 -4
  36. package/src/logger/index.ts +1 -1
  37. package/src/logger/node/_internals/file-writer.ts +6 -6
  38. package/src/logger/node/index.ts +30 -32
  39. package/src/logger/node/types.ts +2 -2
  40. package/src/logger/types.ts +3 -2
  41. package/src/storages/redis/keyed-store.ts +6 -2
  42. package/src/storages/redis/msgpack.ts +17 -1
  43. package/src/storages/redis/types.ts +34 -0
@@ -6,7 +6,7 @@ interface LoggerHooks {
6
6
  write: (record: LoggerRecord) => Promise<void> | void;
7
7
  }
8
8
  /** A non-recursive, rate-limited diagnostic boundary shared by server and browser adapters. */
9
- export declare function createDiagnosticReporter(onError?: (event: LoggerDiagnostic) => void, fallback?: (message: string) => void): (event: LoggerDiagnostic) => void;
10
- export declare function createLoggerCore(options: LoggerOptions, hooks: LoggerHooks): Logger;
9
+ export declare function createDiagnosticReporter(onError?: (event: Readonly<LoggerDiagnostic>) => void, fallback?: (message: string) => void): (event: LoggerDiagnostic) => void;
10
+ export declare function createLoggerCore(options: LoggerOptions, hooks: LoggerHooks): Readonly<Logger>;
11
11
  export {};
12
12
  //# sourceMappingURL=logger.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","names":[],"sources":["../../../src/logger/_internals/logger.ts"],"mappings":";;UAWU;;EAEN,SAAS,OAAO;EAChB,QAAQ,QAAQ,iBAAiB;;;wBAkBrB,yBACZ,WAAW,OAAO,2BAClB,YAAY,4BACZ,OAAO;wBAkEK,iBAAiB,SAAS,eAAe,OAAO,cAAc"}
1
+ {"version":3,"file":"logger.d.ts","names":[],"sources":["../../../src/logger/_internals/logger.ts"],"mappings":";;UAWU;;EAEN,SAAS,OAAO;EAChB,QAAQ,QAAQ,iBAAiB;;;wBAkBrB,yBACZ,WAAW,OAAO,SAAS,4BAC3B,YAAY,4BACZ,OAAO;wBAkEK,iBAAiB,SAAS,eAAe,OAAO,cAAc,SAAS"}
@@ -36,7 +36,7 @@ function createDiagnosticReporter(onError, fallback) {
36
36
  } catch {}
37
37
  if (!callbackPending) try {
38
38
  callbackPending = true;
39
- const result = onError?.(event);
39
+ const result = onError?.(Object.freeze({ ...event }));
40
40
  if (result !== null && (typeof result === "object" || typeof result === "function")) Promise.resolve(result).then(() => {
41
41
  callbackPending = false;
42
42
  }, () => {
@@ -124,7 +124,7 @@ function createLoggerCore(options, hooks) {
124
124
  });
125
125
  }
126
126
  };
127
- return logger;
127
+ return Object.freeze(logger);
128
128
  }
129
129
  function getLevelSeverity(level) {
130
130
  return level === "silent" ? Number.POSITIVE_INFINITY : loggerMethods[level].severity;
@@ -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: 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?.(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): 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 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 /** 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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"}
@@ -6,5 +6,5 @@ import { Logger, LoggerOptions } from "./types.js";
6
6
  * waits for the console invocation, not display or durable delivery. Unspecified
7
7
  * levels follow LOG_LEVEL when a process environment is available, otherwise info.
8
8
  */
9
- export declare function createLogger(options?: LoggerOptions): Logger;
9
+ export declare function createLogger(options?: LoggerOptions): Readonly<Logger>;
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/logger/index.ts"],"mappings":";;;;;;;;wBAoBgB,aAAa,UAAS,gBAAqB"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/logger/index.ts"],"mappings":";;;;;;;;wBAoBgB,aAAa,UAAS,gBAAqB,SAAS"}
@@ -1,5 +1,5 @@
1
- import { loggerMethods } from "./_internals/levels.js";
2
1
  import { formatLoggerConsoleArgs } from "./_internals/formatter.js";
2
+ import { loggerMethods } from "./_internals/levels.js";
3
3
  import { createDiagnosticReporter, createLoggerCore } from "./_internals/logger.js";
4
4
  // src/logger/index.ts
5
5
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/logger/index.ts"],"sourcesContent":["import { formatLoggerConsoleArgs } from './_internals/formatter';\nimport { loggerMethods } from './_internals/levels';\nimport {\n createDiagnosticReporter,\n createLoggerCore,\n} from './_internals/logger';\nimport type {\n Logger,\n LoggerOptions,\n} from './types';\n\n// Functions\n\n/**\n * Creates an independent, browser-safe console logger.\n *\n * Log calls never throw or reject for recoverable output failures. Awaiting a call\n * waits for the console invocation, not display or durable delivery. Unspecified\n * levels follow LOG_LEVEL when a process environment is available, otherwise info.\n */\nexport function createLogger(options: LoggerOptions = {}): Logger {\n const target = globalThis.console;\n const fallback = target?.error?.bind(target);\n return createLoggerCore(\n options,\n {\n report: createDiagnosticReporter(options.onError, (message) => fallback?.(message.trimEnd())),\n write(record) {\n if (!record.console) return;\n target?.[loggerMethods[record.type].consoleMethod](...formatLoggerConsoleArgs(record));\n },\n },\n );\n}\n"],"mappings":";;;;;;;;;;;AAoBA,SAAgB,aAAa,UAAyB,CAAC,GAAW;CAC9D,MAAM,SAAS,WAAW;CAC1B,MAAM,WAAW,QAAQ,OAAO,KAAK,MAAM;CAC3C,OAAO,iBACH,SACA;EACI,QAAQ,yBAAyB,QAAQ,UAAU,YAAY,WAAW,QAAQ,QAAQ,CAAC,CAAC;EAC5F,MAAM,QAAQ;GACV,IAAI,CAAC,OAAO,SAAS;GACrB,SAAS,cAAc,OAAO,KAAK,CAAC,cAAc,CAAC,GAAG,wBAAwB,MAAM,CAAC;EACzF;CACJ,CACJ;AACJ"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/logger/index.ts"],"sourcesContent":["import { formatLoggerConsoleArgs } from './_internals/formatter';\nimport { loggerMethods } from './_internals/levels';\nimport {\n createDiagnosticReporter,\n createLoggerCore,\n} from './_internals/logger';\nimport type {\n Logger,\n LoggerOptions,\n} from './types';\n\n// Functions\n\n/**\n * Creates an independent, browser-safe console logger.\n *\n * Log calls never throw or reject for recoverable output failures. Awaiting a call\n * waits for the console invocation, not display or durable delivery. Unspecified\n * levels follow LOG_LEVEL when a process environment is available, otherwise info.\n */\nexport function createLogger(options: LoggerOptions = {}): Readonly<Logger> {\n const target = globalThis.console;\n const fallback = target?.error?.bind(target);\n return createLoggerCore(\n options,\n {\n report: createDiagnosticReporter(options.onError, (message) => fallback?.(message.trimEnd())),\n write(record) {\n if (!record.console) return;\n target?.[loggerMethods[record.type].consoleMethod](...formatLoggerConsoleArgs(record));\n },\n },\n );\n}\n"],"mappings":";;;;;;;;;;;AAoBA,SAAgB,aAAa,UAAyB,CAAC,GAAqB;CACxE,MAAM,SAAS,WAAW;CAC1B,MAAM,WAAW,QAAQ,OAAO,KAAK,MAAM;CAC3C,OAAO,iBACH,SACA;EACI,QAAQ,yBAAyB,QAAQ,UAAU,YAAY,WAAW,QAAQ,QAAQ,CAAC,CAAC;EAC5F,MAAM,QAAQ;GACV,IAAI,CAAC,OAAO,SAAS;GACrB,SAAS,cAAc,OAAO,KAAK,CAAC,cAAc,CAAC,GAAG,wBAAwB,MAAM,CAAC;EACzF;CACJ,CACJ;AACJ"}
@@ -2,10 +2,10 @@ import { LoggerDiagnostic } from "../../types.js";
2
2
  import { LoggerFileOptions, NodeLoggerStats } from "../types.js";
3
3
  interface LoggerFileWriter {
4
4
  close: () => Promise<void>;
5
- getStats: () => NodeLoggerStats;
5
+ getStats: () => Readonly<NodeLoggerStats>;
6
6
  write: (text: string) => Promise<void>;
7
7
  }
8
8
  /** A bounded admission layer over the stream's own ordered write buffer; not a second FIFO. */
9
- export declare function createFileWriter(options: LoggerFileOptions, report: (event: LoggerDiagnostic) => void): Promise<LoggerFileWriter>;
9
+ export declare function createFileWriter(options: LoggerFileOptions, report: (event: LoggerDiagnostic) => void): Promise<Readonly<LoggerFileWriter>>;
10
10
  export {};
11
11
  //# 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;EAChB,QAAQ,iBAAiB;;;wBASP,iBAClB,SAAS,mBACT,SAAS,OAAO,4BACjB,QAAQ"}
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;;;wBASP,iBAClB,SAAS,mBACT,SAAS,OAAO,4BACjB,QAAQ,SAAS"}
@@ -83,7 +83,7 @@ async function createFileWriter(options, report) {
83
83
  rejectInitialization = void 0;
84
84
  initializing = false;
85
85
  }
86
- return {
86
+ return Object.freeze({
87
87
  close() {
88
88
  if (closePromise) return closePromise;
89
89
  closing = true;
@@ -101,7 +101,7 @@ async function createFileWriter(options, report) {
101
101
  });
102
102
  return closePromise;
103
103
  },
104
- getStats: () => ({
104
+ getStats: () => Object.freeze({
105
105
  droppedCount,
106
106
  pendingBytes,
107
107
  writeErrorCount
@@ -145,7 +145,7 @@ async function createFileWriter(options, report) {
145
145
  }
146
146
  });
147
147
  }
148
- };
148
+ });
149
149
  }
150
150
  /** Validate before mkdir, stream creation, or other filesystem side effects. */
151
151
  function validateFileOptions(options) {
@@ -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: () => 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<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 {\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: () => ({\n droppedCount,\n pendingBytes,\n writeErrorCount,\n }),\n write(text) {\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/** 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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"}
@@ -6,5 +6,5 @@ import { NodeLogger, NodeLoggerOptions } from "./types.js";
6
6
  * persistence: overload drops the newest file record and failures are reported safely. Factory
7
7
  * validation/open failures reject. Call close explicitly; no process signal handlers are installed.
8
8
  */
9
- export declare function createNodeLogger(options?: NodeLoggerOptions): Promise<NodeLogger>;
9
+ export declare function createNodeLogger(options?: NodeLoggerOptions): Promise<Readonly<NodeLogger>>;
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/logger/node/index.ts"],"mappings":";;;;;;;;wBAqCsB,iBAAiB,UAAS,oBAAyB,QAAQ"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/logger/node/index.ts"],"mappings":";;;;;;;;wBAqCsB,iBAAiB,UAAS,oBAAyB,QAAQ,SAAS"}
@@ -1,5 +1,5 @@
1
- import { getLoggerStreamName } from "../_internals/levels.js";
2
1
  import { supportsLoggerColor } from "../_internals/formatter.js";
2
+ import { getLoggerStreamName } from "../_internals/levels.js";
3
3
  import { createDiagnosticReporter, createLoggerCore } from "../_internals/logger.js";
4
4
  import { createFileWriter } from "./_internals/file-writer.js";
5
5
  import { formatNodeLoggerRecord } from "./_internals/formatter.js";
@@ -48,7 +48,8 @@ async function createNodeLogger(options = {}) {
48
48
  }
49
49
  });
50
50
  if (options.file !== void 0 && options.file !== false) writer = await createFileWriter(options.file, report);
51
- return Object.assign(logger, {
51
+ return Object.freeze({
52
+ ...logger,
52
53
  close() {
53
54
  if (closePromise) return closePromise;
54
55
  closed = true;
@@ -68,11 +69,11 @@ async function createNodeLogger(options = {}) {
68
69
  })();
69
70
  return closePromise;
70
71
  },
71
- getStats: () => writer?.getStats() ?? {
72
+ getStats: () => writer?.getStats() ?? Object.freeze({
72
73
  droppedCount: 0,
73
74
  pendingBytes: 0,
74
75
  writeErrorCount: 0
75
- }
76
+ })
76
77
  });
77
78
  }
78
79
  /** One error listener per shared output stream; retained until its last owning logger calls close(). */
@@ -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<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.assign(\n logger,\n {\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() ?? {\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(). */\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":";;;;;;;;AA0BA,MAAM,iCAAiB,IAAI,QAAgD;;;;;;;;AAW3E,eAAsB,iBAAiB,UAA6B,CAAC,GAAwB;CACzF,MAAM,SAAS,yBAAyB,QAAQ,UAAU,YAAY,UAAU,GAAG,OAAO,CAAC;CAC3F,IAAI;CACJ,IAAI,SAAS;CACb,IAAI;CACJ,MAAM,eAAe,CAAC;CACtB,MAAM,iCAAiB,IAAI,IAAwB;CACnD,MAAM,gCAAgB,IAAI,IAAmB;CAC7C,MAAM,SAAS,iBACX,SACA;EACI;EACA,MAAM,QAAQ;GACV,IAAI,QAAQ;IACR,OAAO;KACH,SAAS;KACT,MAAM;IACV,CAAC;IAED;GACJ;GAEA,IAAI,CAAC,OAAO,WAAW,CAAC,QAAQ;GAChC,MAAM,SAAS,QAAQ,oBAAoB,OAAO,IAAI;GAEtD,MAAM,OAAO,uBAAuB,QAAQ,OAAO,WAAW,oBAAoB,MAAM,CAAC;GACzF,IAAI,OAAO,SAAS;IAChB,MAAM,OAAO,GAAG,KAAK;IACrB,eAAe,IAAI,MAAM;IACzB,MAAM,aAAa,aAAa,QAAQ,MAAM,QAAQ,YAAY;IAClE,cAAc,IAAI,UAAU;IAC5B,WAAgB,WAAW,cAAc,OAAO,UAAU,CAAC;IAC3D,MAAM,iBAAiB,QAAQ,MAAM,GAAG,yBAAyB,IAAI,EAAE,GAAG;IAC1E,OAAO,iBACD,QAAQ,IAAI,CACV,YACA,cACJ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IACd;GACV;GAEA,OAAO,QAAQ,MAAM,GAAG,yBAAyB,IAAI,EAAE,GAAG;EAC9D;CACJ,CACJ;CAEA,IAAI,QAAQ,SAAS,KAAA,KAAa,QAAQ,SAAS,OAAO,SAAS,MAAM,iBAAiB,QAAQ,MAAM,MAAM;CAE9G,OAAO,OAAO,OACV,QACA;EACI,QAAQ;GACJ,IAAI,cAAc,OAAO;GACzB,SAAS;GACT,gBAAgB,YAAY;IACxB,MAAM,QAAQ,IAAI,CACd,QAAQ,MAAM,GACd,GAAG,aACP,CAAC;IAED,KAAK,MAAM,UAAU,gBAAgB;KACjC,MAAM,QAAQ,eAAe,IAAI,MAAM;KACvC,IAAI,CAAC,OAAO;KACZ,MAAM,OAAO,OAAO,YAAY;KAChC,IAAI,CAAC,MAAM,OAAO,MAAM;MACpB,OAAO,IAAI,SAAS,MAAM,OAAO;MACjC,OAAO,IAAI,SAAS,MAAM,OAAO;MACjC,eAAe,OAAO,MAAM;KAChC;IACJ;IAEA,eAAe,MAAM;GACzB,EAAA,CAAG;GACH,OAAO;EACX;EACA,gBAAgB,QAAQ,SAAS,KAAK;GAClC,cAAc;GACd,cAAc;GACd,iBAAiB;EACrB;CACJ,CACJ;AACJ;;AAGA,SAAS,aACL,QACA,MACA,QACA,OACF;CACE,IAAI,QAAQ,eAAe,IAAI,MAAM;CACrC,IAAI,CAAC,OAAO;EACR,MAAM,0BAAU,IAAI,IAAoC;EACxD,MAAM,WAAW,UAAiB;GAC9B,KAAK,MAAM,UAAU,SAAS,OAAO,KAAK;EAC9C;EAEA,MAAM,gBAAgB,wBAAQ,IAAI,MAAM,wBAAwB,CAAC;EACjE,QAAQ;GACJ;GACA;GACA,wBAAQ,IAAI,IAAI;GAChB;EACJ;EAEA,eAAe,IAAI,QAAQ,KAAK;EAChC,OAAO,GAAG,SAAS,OAAO;EAC1B,OAAO,GAAG,SAAS,OAAO;CAC9B;CAEA,MAAM,OAAO,IAAI,KAAK;CACtB,MAAM,UAAU;CAChB,OAAO,IAAI,SAAe,mBAAmB;EACzC,IAAI,UAAU;EACd,MAAM,UAAU,UAAyB;GACrC,IAAI,SAAS;GACb,UAAU;GACV,QAAQ,QAAQ,OAAO,MAAM;GAC7B,IAAI,OACA,OAAO;IACH;IACA,SAAS;IACT,MAAM;GACV,CAAC;GAGL,eAAe;EACnB;EAEA,QAAQ,QAAQ,IAAI,MAAM;EAC1B,IAAI;GACA,OAAO,MAAM,MAAM,MAAM;EAC7B,SAAS,OAAO;GACZ,OAAO,iBAAiB,QAAQ,wBAAQ,IAAI,MAAM,wBAAwB,CAAC;EAC/E;CACJ,CAAC;AACL"}
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/** One error listener per shared output stream; retained until its last owning logger calls close(). */\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":";;;;;;;;AA0BA,MAAM,iCAAiB,IAAI,QAAgD;;;;;;;;AAW3E,eAAsB,iBAAiB,UAA6B,CAAC,GAAkC;CACnG,MAAM,SAAS,yBAAyB,QAAQ,UAAU,YAAY,UAAU,GAAG,OAAO,CAAC;CAC3F,IAAI;CACJ,IAAI,SAAS;CACb,IAAI;CACJ,MAAM,eAAe,CAAC;CACtB,MAAM,iCAAiB,IAAI,IAAwB;CACnD,MAAM,gCAAgB,IAAI,IAAmB;CAC7C,MAAM,SAAS,iBACX,SACA;EACI;EACA,MAAM,QAAQ;GACV,IAAI,QAAQ;IACR,OAAO;KACH,SAAS;KACT,MAAM;IACV,CAAC;IAED;GACJ;GAEA,IAAI,CAAC,OAAO,WAAW,CAAC,QAAQ;GAChC,MAAM,SAAS,QAAQ,oBAAoB,OAAO,IAAI;GAEtD,MAAM,OAAO,uBAAuB,QAAQ,OAAO,WAAW,oBAAoB,MAAM,CAAC;GACzF,IAAI,OAAO,SAAS;IAChB,MAAM,OAAO,GAAG,KAAK;IACrB,eAAe,IAAI,MAAM;IACzB,MAAM,aAAa,aAAa,QAAQ,MAAM,QAAQ,YAAY;IAClE,cAAc,IAAI,UAAU;IAC5B,WAAgB,WAAW,cAAc,OAAO,UAAU,CAAC;IAC3D,MAAM,iBAAiB,QAAQ,MAAM,GAAG,yBAAyB,IAAI,EAAE,GAAG;IAC1E,OAAO,iBACD,QAAQ,IAAI,CACV,YACA,cACJ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IACd;GACV;GAEA,OAAO,QAAQ,MAAM,GAAG,yBAAyB,IAAI,EAAE,GAAG;EAC9D;CACJ,CACJ;CAEA,IAAI,QAAQ,SAAS,KAAA,KAAa,QAAQ,SAAS,OAAO,SAAS,MAAM,iBAAiB,QAAQ,MAAM,MAAM;CAE9G,OAAO,OAAO,OAAO;EACjB,GAAG;EACH,QAAQ;GACJ,IAAI,cAAc,OAAO;GACzB,SAAS;GACT,gBAAgB,YAAY;IACxB,MAAM,QAAQ,IAAI,CACd,QAAQ,MAAM,GACd,GAAG,aACP,CAAC;IAED,KAAK,MAAM,UAAU,gBAAgB;KACjC,MAAM,QAAQ,eAAe,IAAI,MAAM;KACvC,IAAI,CAAC,OAAO;KACZ,MAAM,OAAO,OAAO,YAAY;KAChC,IAAI,CAAC,MAAM,OAAO,MAAM;MACpB,OAAO,IAAI,SAAS,MAAM,OAAO;MACjC,OAAO,IAAI,SAAS,MAAM,OAAO;MACjC,eAAe,OAAO,MAAM;KAChC;IACJ;IAEA,eAAe,MAAM;GACzB,EAAA,CAAG;GACH,OAAO;EACX;EACA,gBAAgB,QAAQ,SAAS,KAAK,OAAO,OAAO;GAChD,cAAc;GACd,cAAc;GACd,iBAAiB;EACrB,CAAC;CACL,CAAC;AACL;;AAGA,SAAS,aACL,QACA,MACA,QACA,OACF;CACE,IAAI,QAAQ,eAAe,IAAI,MAAM;CACrC,IAAI,CAAC,OAAO;EACR,MAAM,0BAAU,IAAI,IAAoC;EACxD,MAAM,WAAW,UAAiB;GAC9B,KAAK,MAAM,UAAU,SAAS,OAAO,KAAK;EAC9C;EAEA,MAAM,gBAAgB,wBAAQ,IAAI,MAAM,wBAAwB,CAAC;EACjE,QAAQ;GACJ;GACA;GACA,wBAAQ,IAAI,IAAI;GAChB;EACJ;EAEA,eAAe,IAAI,QAAQ,KAAK;EAChC,OAAO,GAAG,SAAS,OAAO;EAC1B,OAAO,GAAG,SAAS,OAAO;CAC9B;CAEA,MAAM,OAAO,IAAI,KAAK;CACtB,MAAM,UAAU;CAChB,OAAO,IAAI,SAAe,mBAAmB;EACzC,IAAI,UAAU;EACd,MAAM,UAAU,UAAyB;GACrC,IAAI,SAAS;GACb,UAAU;GACV,QAAQ,QAAQ,OAAO,MAAM;GAC7B,IAAI,OACA,OAAO;IACH;IACA,SAAS;IACT,MAAM;GACV,CAAC;GAGL,eAAe;EACnB;EAEA,QAAQ,QAAQ,IAAI,MAAM;EAC1B,IAAI;GACA,OAAO,MAAM,MAAM,MAAM;EAC7B,SAAS,OAAO;GACZ,OAAO,iBAAiB,QAAQ,wBAAQ,IAAI,MAAM,wBAAwB,CAAC;EAC/E;CACJ,CAAC;AACL"}
@@ -28,8 +28,8 @@ export interface LoggerFileOptions {
28
28
  export interface NodeLogger extends Logger {
29
29
  /** Stop accepting records, finish accepted file writes, then release the stream. Not fsync. */
30
30
  close: () => Promise<void>;
31
- /** A fresh snapshot. Dropped/failed records still resolve their logging promises. */
32
- getStats: () => NodeLoggerStats;
31
+ /** A fresh, frozen file-output snapshot. Dropped/failed records still resolve their logging promises. */
32
+ getStats: () => Readonly<NodeLoggerStats>;
33
33
  }
34
34
  export interface NodeLoggerOptions extends LoggerOptions {
35
35
  /** Disabled by default. Each logger owns at most one fixed file destination. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/logger/node/types.ts"],"mappings":";;;YAOY;;EAER;;EAGA;;EAGA;;;EAIE;EACA;;;EAIA;EACA;;iBAIW;;EAEb;;EAGA,MAAM;;EAGN,iBAAiB;;iBAGJ,mBAAmB;;EAEhC,aAAa;;EAGb,gBAAgB;;iBAGH,0BAA0B;;EAEvC,eAAe;;iBAGF;EACb;EACA;EACA"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/logger/node/types.ts"],"mappings":";;;YAOY;;EAER;;EAGA;;EAGA;;;EAIE;EACA;;;EAIA;EACA;;iBAIW;;EAEb;;EAGA,MAAM;;EAGN,iBAAiB;;iBAGJ,mBAAmB;;EAEhC,aAAa;;EAGb,gBAAgB,SAAS;;iBAGZ,0BAA0B;;EAEvC,eAAe;;iBAGF;EACb;EACA;EACA"}
@@ -7,6 +7,7 @@ export type LoggerMethod = 'success' | Exclude<LoggerLevel, 'silent'>;
7
7
  *
8
8
  * Awaiting a method waits for processing, not guaranteed successful delivery or fsync.
9
9
  * Configuration errors may throw from the factory, configure(), or setLevel().
10
+ * Factories freeze the API object; settings remain adjustable through its methods.
10
11
  */
11
12
  export interface Logger {
12
13
  configure: (options: Pick<LoggerOptions, 'console' | 'icons' | 'prefix' | 'suffix' | 'timestamp'>) => void;
@@ -22,7 +23,7 @@ export interface Logger {
22
23
  trace: (...args: unknown[]) => Promise<void>;
23
24
  warn: (...args: unknown[]) => Promise<void>;
24
25
  }
25
- /** A recoverable logging failure, reported without throwing from log methods. */
26
+ /** A shallow-frozen failure notification; nested error values retain their original ownership. */
26
27
  export interface LoggerDiagnostic {
27
28
  droppedCount?: number;
28
29
  error?: unknown;
@@ -40,7 +41,7 @@ export interface LoggerOptions {
40
41
  /** Overrides LOG_LEVEL; `env` restores the environment-driven threshold. */
41
42
  level?: 'env' | LoggerLevel;
42
43
  /** Best-effort, rate-limited notification; failures in this callback are contained. */
43
- onError?: (event: LoggerDiagnostic) => void;
44
+ onError?: (event: Readonly<LoggerDiagnostic>) => void;
44
45
  prefix?: string;
45
46
  suffix?: string;
46
47
  /** Enables the fixed local-time millisecond prefix. Defaults to `true`. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/logger/types.ts"],"mappings":";YACY;;YAGA,2BAA2B,QAAQ;;;;;;;iBAQ9B;EACb,YAAY,SAAS,KAAK;EAC1B,WAAW,oBAAoB;EAC/B,WAAW,oBAAoB;EAC/B,WAAW,oBAAoB;EAC/B,UAAU,oBAAoB;;EAG9B,iBAAiB,OAAO,cAAc;;EAGtC,WAAW,eAAe;EAC1B,aAAa,oBAAoB;EACjC,WAAW,oBAAoB;EAC/B,UAAU,oBAAoB;;;iBAIjB;EACb;EACA;EACA;;EAGA;EACA;;;iBAIa;;EAEb;;EAGA;;EAGA,gBAAgB;;EAGhB,WAAW,OAAO;EAClB;EACA;;EAGA"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/logger/types.ts"],"mappings":";YACY;;YAGA,2BAA2B,QAAQ;;;;;;;;iBAS9B;EACb,YAAY,SAAS,KAAK;EAC1B,WAAW,oBAAoB;EAC/B,WAAW,oBAAoB;EAC/B,WAAW,oBAAoB;EAC/B,UAAU,oBAAoB;;EAG9B,iBAAiB,OAAO,cAAc;;EAGtC,WAAW,eAAe;EAC1B,aAAa,oBAAoB;EACjC,WAAW,oBAAoB;EAC/B,UAAU,oBAAoB;;;iBAIjB;EACb;EACA;EACA;;EAGA;EACA;;;iBAIa;;EAEb;;EAGA;;EAGA,gBAAgB;;EAGhB,WAAW,OAAO,SAAS;EAC3B;EACA;;EAGA"}
@@ -6,7 +6,7 @@ import { RedisLikeStorage } from "./types.js";
6
6
  * @remarks
7
7
  * The key resolver is called synchronously once per operation with the operation's key arguments.
8
8
  * Resolver exceptions propagate synchronously; asynchronous storage failures retain the underlying rejection.
9
- * `setItemWithTtl` accepts its TTL in seconds before the value and key arguments.
9
+ * `setItemWithTtl` and `setItemWithTtlIfAbsent` accept their TTL in seconds before the value and key arguments.
10
10
  * The factory does not open or close the underlying connection; cleanup remains the caller's responsibility.
11
11
  *
12
12
  * @typeParam D - The stored value type used by reads and writes.
@@ -14,9 +14,10 @@ import { RedisLikeStorage } from "./types.js";
14
14
  * @param storage - The shared Redis-like storage instance.
15
15
  *
16
16
  * @returns A factory accepting a key resolver and returning a new frozen keyed facade sharing the storage.
17
- * Reads, writes, removals, and TTL results retain the underlying storage contract.
17
+ * Reads, atomic read-and-removals, writes, removals, and TTL results retain the underlying storage contract.
18
18
  */
19
19
  export declare function createRedisKeyedStore<D = unknown>(storage: MaybeReadonly<RedisLikeStorage>): <P extends any[]>(keyFn: (...args: P) => string) => Readonly<{
20
+ getAndRemoveItem: (...args: P) => Promise<D | null>;
20
21
  getItem: (...args: P) => Promise<D | null>;
21
22
  getItemTtl: (...args: P) => Promise<number>;
22
23
  hasItem: (...args: P) => Promise<boolean>;
@@ -24,5 +25,6 @@ export declare function createRedisKeyedStore<D = unknown>(storage: MaybeReadonl
24
25
  resolveKey: (...args: P) => string;
25
26
  setItem: (value: D, ...args: P) => Promise<boolean>;
26
27
  setItemWithTtl(ttlSeconds: number, value: D, ...args: P): Promise<boolean>;
28
+ setItemWithTtlIfAbsent(ttlSeconds: number, value: D, ...args: P): Promise<boolean>;
27
29
  }>;
28
30
  //# sourceMappingURL=keyed-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"keyed-store.d.ts","names":[],"sources":["../../../src/storages/redis/keyed-store.ts"],"mappings":";;;;;;;;;;;;;;;;;;wBAoBgB,sBAAsB,aAAa,SAAS,cAAc,qBAC9D,iBAAiB,WAAW,MAAM,iBAAY;EAC/B,aAAA,MAAA,MAAC,QAAA;EACE,gBAAA,MAAA,MAAC;EACJ,aAAA,MAAA,MAAC;EACE,gBAAA,MAAA,MAAC;EACD,gBAAA,MAAA;EACL,UAAA,OAAA,MAAC,MAAW,MAAC;EACH,eAAA,oBAAM,OAAS,MAAC,MAAW,IAAC"}
1
+ {"version":3,"file":"keyed-store.d.ts","names":[],"sources":["../../../src/storages/redis/keyed-store.ts"],"mappings":";;;;;;;;;;;;;;;;;;wBAoBgB,sBAAsB,aAAa,SAAS,cAAc,qBAC9D,iBAAiB,WAAW,MAAM,iBAAY;EACtB,sBAAA,MAAA,MAAC,QAAA;EACV,aAAA,MAAA,MAAC,QAAA;EACE,gBAAA,MAAA,MAAC;EACJ,aAAA,MAAA,MAAC;EACE,gBAAA,MAAA,MAAC;EACD,gBAAA,MAAA;EACL,UAAA,OAAA,MAAC,MAAW,MAAC;EACH,eAAA,oBAAM,OAAS,MAAC,MAAW,IAAC;EAGpB,uBAAA,oBAAM,OAAS,MAAC,MAAW,IAAC"}
@@ -5,7 +5,7 @@
5
5
  * @remarks
6
6
  * The key resolver is called synchronously once per operation with the operation's key arguments.
7
7
  * Resolver exceptions propagate synchronously; asynchronous storage failures retain the underlying rejection.
8
- * `setItemWithTtl` accepts its TTL in seconds before the value and key arguments.
8
+ * `setItemWithTtl` and `setItemWithTtlIfAbsent` accept their TTL in seconds before the value and key arguments.
9
9
  * The factory does not open or close the underlying connection; cleanup remains the caller's responsibility.
10
10
  *
11
11
  * @typeParam D - The stored value type used by reads and writes.
@@ -13,10 +13,11 @@
13
13
  * @param storage - The shared Redis-like storage instance.
14
14
  *
15
15
  * @returns A factory accepting a key resolver and returning a new frozen keyed facade sharing the storage.
16
- * Reads, writes, removals, and TTL results retain the underlying storage contract.
16
+ * Reads, atomic read-and-removals, writes, removals, and TTL results retain the underlying storage contract.
17
17
  */
18
18
  function createRedisKeyedStore(storage) {
19
19
  return (keyFn) => Object.freeze({
20
+ getAndRemoveItem: (...args) => storage.getAndRemoveItem(keyFn(...args)),
20
21
  getItem: (...args) => storage.getItem(keyFn(...args)),
21
22
  getItemTtl: (...args) => storage.getItemTtl(keyFn(...args)),
22
23
  hasItem: (...args) => storage.hasItem(keyFn(...args)),
@@ -25,6 +26,9 @@ function createRedisKeyedStore(storage) {
25
26
  setItem: (value, ...args) => storage.setItem(keyFn(...args), value),
26
27
  setItemWithTtl(ttlSeconds, value, ...args) {
27
28
  return storage.setItemWithTtl(keyFn(...args), ttlSeconds, value);
29
+ },
30
+ setItemWithTtlIfAbsent(ttlSeconds, value, ...args) {
31
+ return storage.setItemWithTtlIfAbsent(keyFn(...args), ttlSeconds, value);
28
32
  }
29
33
  });
30
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"keyed-store.js","names":[],"sources":["../../../src/storages/redis/keyed-store.ts"],"sourcesContent":["import type { MaybeReadonly } from '../../types';\n\nimport type { RedisLikeStorage } from './types';\n\n/**\n * Creates a keyed store factory sharing a caller-owned Redis-like storage facade.\n *\n * @remarks\n * The key resolver is called synchronously once per operation with the operation's key arguments.\n * Resolver exceptions propagate synchronously; asynchronous storage failures retain the underlying rejection.\n * `setItemWithTtl` accepts its TTL in seconds before the value and key arguments.\n * The factory does not open or close the underlying connection; cleanup remains the caller's responsibility.\n *\n * @typeParam D - The stored value type used by reads and writes.\n *\n * @param storage - The shared Redis-like storage instance.\n *\n * @returns A factory accepting a key resolver and returning a new frozen keyed facade sharing the storage.\n * Reads, writes, removals, and TTL results retain the underlying storage contract.\n */\nexport function createRedisKeyedStore<D = unknown>(storage: MaybeReadonly<RedisLikeStorage>) {\n return <P extends any[]>(keyFn: (...args: P) => string) => Object.freeze({\n getItem: (...args: P) => storage.getItem<D>(keyFn(...args)),\n getItemTtl: (...args: P) => storage.getItemTtl(keyFn(...args)),\n hasItem: (...args: P) => storage.hasItem(keyFn(...args)),\n removeItem: (...args: P) => storage.removeItem(keyFn(...args)),\n resolveKey: (...args: P) => keyFn(...args),\n setItem: (value: D, ...args: P) => storage.setItem(keyFn(...args), value),\n setItemWithTtl(ttlSeconds: number, value: D, ...args: P) {\n return storage.setItemWithTtl(keyFn(...args), ttlSeconds, value);\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,SAAgB,sBAAmC,SAA0C;CACzF,QAAyB,UAAkC,OAAO,OAAO;EACrE,UAAU,GAAG,SAAY,QAAQ,QAAW,MAAM,GAAG,IAAI,CAAC;EAC1D,aAAa,GAAG,SAAY,QAAQ,WAAW,MAAM,GAAG,IAAI,CAAC;EAC7D,UAAU,GAAG,SAAY,QAAQ,QAAQ,MAAM,GAAG,IAAI,CAAC;EACvD,aAAa,GAAG,SAAY,QAAQ,WAAW,MAAM,GAAG,IAAI,CAAC;EAC7D,aAAa,GAAG,SAAY,MAAM,GAAG,IAAI;EACzC,UAAU,OAAU,GAAG,SAAY,QAAQ,QAAQ,MAAM,GAAG,IAAI,GAAG,KAAK;EACxE,eAAe,YAAoB,OAAU,GAAG,MAAS;GACrD,OAAO,QAAQ,eAAe,MAAM,GAAG,IAAI,GAAG,YAAY,KAAK;EACnE;CACJ,CAAC;AACL"}
1
+ {"version":3,"file":"keyed-store.js","names":[],"sources":["../../../src/storages/redis/keyed-store.ts"],"sourcesContent":["import type { MaybeReadonly } from '../../types';\n\nimport type { RedisLikeStorage } from './types';\n\n/**\n * Creates a keyed store factory sharing a caller-owned Redis-like storage facade.\n *\n * @remarks\n * The key resolver is called synchronously once per operation with the operation's key arguments.\n * Resolver exceptions propagate synchronously; asynchronous storage failures retain the underlying rejection.\n * `setItemWithTtl` and `setItemWithTtlIfAbsent` accept their TTL in seconds before the value and key arguments.\n * The factory does not open or close the underlying connection; cleanup remains the caller's responsibility.\n *\n * @typeParam D - The stored value type used by reads and writes.\n *\n * @param storage - The shared Redis-like storage instance.\n *\n * @returns A factory accepting a key resolver and returning a new frozen keyed facade sharing the storage.\n * Reads, atomic read-and-removals, writes, removals, and TTL results retain the underlying storage contract.\n */\nexport function createRedisKeyedStore<D = unknown>(storage: MaybeReadonly<RedisLikeStorage>) {\n return <P extends any[]>(keyFn: (...args: P) => string) => Object.freeze({\n getAndRemoveItem: (...args: P) => storage.getAndRemoveItem<D>(keyFn(...args)),\n getItem: (...args: P) => storage.getItem<D>(keyFn(...args)),\n getItemTtl: (...args: P) => storage.getItemTtl(keyFn(...args)),\n hasItem: (...args: P) => storage.hasItem(keyFn(...args)),\n removeItem: (...args: P) => storage.removeItem(keyFn(...args)),\n resolveKey: (...args: P) => keyFn(...args),\n setItem: (value: D, ...args: P) => storage.setItem(keyFn(...args), value),\n setItemWithTtl(ttlSeconds: number, value: D, ...args: P) {\n return storage.setItemWithTtl(keyFn(...args), ttlSeconds, value);\n },\n setItemWithTtlIfAbsent(ttlSeconds: number, value: D, ...args: P) {\n return storage.setItemWithTtlIfAbsent(keyFn(...args), ttlSeconds, value);\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,SAAgB,sBAAmC,SAA0C;CACzF,QAAyB,UAAkC,OAAO,OAAO;EACrE,mBAAmB,GAAG,SAAY,QAAQ,iBAAoB,MAAM,GAAG,IAAI,CAAC;EAC5E,UAAU,GAAG,SAAY,QAAQ,QAAW,MAAM,GAAG,IAAI,CAAC;EAC1D,aAAa,GAAG,SAAY,QAAQ,WAAW,MAAM,GAAG,IAAI,CAAC;EAC7D,UAAU,GAAG,SAAY,QAAQ,QAAQ,MAAM,GAAG,IAAI,CAAC;EACvD,aAAa,GAAG,SAAY,QAAQ,WAAW,MAAM,GAAG,IAAI,CAAC;EAC7D,aAAa,GAAG,SAAY,MAAM,GAAG,IAAI;EACzC,UAAU,OAAU,GAAG,SAAY,QAAQ,QAAQ,MAAM,GAAG,IAAI,GAAG,KAAK;EACxE,eAAe,YAAoB,OAAU,GAAG,MAAS;GACrD,OAAO,QAAQ,eAAe,MAAM,GAAG,IAAI,GAAG,YAAY,KAAK;EACnE;EACA,uBAAuB,YAAoB,OAAU,GAAG,MAAS;GAC7D,OAAO,QAAQ,uBAAuB,MAAM,GAAG,IAAI,GAAG,YAAY,KAAK;EAC3E;CACJ,CAAC;AACL"}
@@ -4,7 +4,8 @@ import { RedisLikeAdapter, RedisLikeStorage } from "./types.js";
4
4
  *
5
5
  * @remarks
6
6
  * Adapter rejections and MessagePack serialization errors propagate to the caller.
7
- * Reads resolve to `null` when no raw value exists. Writes resolve to `true` only when the adapter returns `OK`;
7
+ * Reads resolve to `null` when no raw value exists; `getAndRemoveItem` reads and deletes in one atomic adapter call.
8
+ * Writes resolve to `true` only when the adapter returns `OK`;
8
9
  * removal resolves to `true` only when exactly one key is deleted.
9
10
  * The adapter remains caller-owned; creating the facade does not open or close a connection.
10
11
  *
@@ -23,6 +24,11 @@ import { RedisLikeAdapter, RedisLikeStorage } from "./types.js";
23
24
  * const adapter: RedisLikeAdapter = {
24
25
  * delete: async (key) => values.delete(key) ? 1 : 0,
25
26
  * getBuffer: async (key) => values.get(key) ?? null,
27
+ * getBufferAndDelete: async (key) => {
28
+ * const value = values.get(key) ?? null;
29
+ * values.delete(key);
30
+ * return value;
31
+ * },
26
32
  * has: async (key) => values.has(key),
27
33
  * setBuffer: async (key, value) => {
28
34
  * values.set(key, value);
@@ -31,6 +37,9 @@ import { RedisLikeAdapter, RedisLikeStorage } from "./types.js";
31
37
  * setBufferEx: async () => {
32
38
  * throw new Error('TTL writes are not supported by this example adapter');
33
39
  * },
40
+ * setBufferExNx: async () => {
41
+ * throw new Error('TTL writes are not supported by this example adapter');
42
+ * },
34
43
  * ttl: async (key) => values.has(key) ? -1 : -2,
35
44
  * };
36
45
  *
@@ -1 +1 @@
1
- {"version":3,"file":"msgpack.d.ts","names":[],"sources":["../../../src/storages/redis/msgpack.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAwDgB,0BAA0B,SAAS,mBAAmB,SAAS"}
1
+ {"version":3,"file":"msgpack.d.ts","names":[],"sources":["../../../src/storages/redis/msgpack.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAiEgB,0BAA0B,SAAS,mBAAmB,SAAS"}
@@ -6,7 +6,8 @@ if (!isNativeAccelerationEnabled) console.warn("Native acceleration not enabled
6
6
  *
7
7
  * @remarks
8
8
  * Adapter rejections and MessagePack serialization errors propagate to the caller.
9
- * Reads resolve to `null` when no raw value exists. Writes resolve to `true` only when the adapter returns `OK`;
9
+ * Reads resolve to `null` when no raw value exists; `getAndRemoveItem` reads and deletes in one atomic adapter call.
10
+ * Writes resolve to `true` only when the adapter returns `OK`;
10
11
  * removal resolves to `true` only when exactly one key is deleted.
11
12
  * The adapter remains caller-owned; creating the facade does not open or close a connection.
12
13
  *
@@ -25,6 +26,11 @@ if (!isNativeAccelerationEnabled) console.warn("Native acceleration not enabled
25
26
  * const adapter: RedisLikeAdapter = {
26
27
  * delete: async (key) => values.delete(key) ? 1 : 0,
27
28
  * getBuffer: async (key) => values.get(key) ?? null,
29
+ * getBufferAndDelete: async (key) => {
30
+ * const value = values.get(key) ?? null;
31
+ * values.delete(key);
32
+ * return value;
33
+ * },
28
34
  * has: async (key) => values.has(key),
29
35
  * setBuffer: async (key, value) => {
30
36
  * values.set(key, value);
@@ -33,6 +39,9 @@ if (!isNativeAccelerationEnabled) console.warn("Native acceleration not enabled
33
39
  * setBufferEx: async () => {
34
40
  * throw new Error('TTL writes are not supported by this example adapter');
35
41
  * },
42
+ * setBufferExNx: async () => {
43
+ * throw new Error('TTL writes are not supported by this example adapter');
44
+ * },
36
45
  * ttl: async (key) => values.has(key) ? -1 : -2,
37
46
  * };
38
47
  *
@@ -44,6 +53,10 @@ if (!isNativeAccelerationEnabled) console.warn("Native acceleration not enabled
44
53
  */
45
54
  function createRedisMsgpackStorage(adapter) {
46
55
  return Object.freeze({
56
+ async getAndRemoveItem(key) {
57
+ const rawValue = await adapter.getBufferAndDelete(key);
58
+ return rawValue ? decode(rawValue) : null;
59
+ },
47
60
  async getItem(key) {
48
61
  const rawValue = await adapter.getBuffer(key);
49
62
  return rawValue ? decode(rawValue) : null;
@@ -54,6 +67,9 @@ function createRedisMsgpackStorage(adapter) {
54
67
  setItem: async (key, value) => await adapter.setBuffer(key, encode(value)) === "OK",
55
68
  async setItemWithTtl(key, ttlSeconds, value) {
56
69
  return await adapter.setBufferEx(key, ttlSeconds, encode(value)) === "OK";
70
+ },
71
+ async setItemWithTtlIfAbsent(key, ttlSeconds, value) {
72
+ return await adapter.setBufferExNx(key, ttlSeconds, encode(value)) === "OK";
57
73
  }
58
74
  });
59
75
  }
@@ -1 +1 @@
1
- {"version":3,"file":"msgpack.js","names":[],"sources":["../../../src/storages/redis/msgpack.ts"],"sourcesContent":["import {\n decode,\n encode,\n isNativeAccelerationEnabled,\n} from 'msgpackr';\n\nimport type {\n RedisLikeAdapter,\n RedisLikeStorage,\n} from './types';\n\nif (!isNativeAccelerationEnabled) {\n console.warn('Native acceleration not enabled for msgpackr, verify that install finished properly');\n}\n\n/**\n * Creates a frozen Redis-like storage facade using MessagePack serialization.\n *\n * @remarks\n * Adapter rejections and MessagePack serialization errors propagate to the caller.\n * Reads resolve to `null` when no raw value exists. Writes resolve to `true` only when the adapter returns `OK`;\n * removal resolves to `true` only when exactly one key is deleted.\n * The adapter remains caller-owned; creating the facade does not open or close a connection.\n *\n * @param adapter - The caller-owned adapter for raw binary operations.\n *\n * @returns A new frozen facade sharing the supplied adapter.\n *\n * @example\n *\n * ```ts\n * import { createRedisMsgpackStorage } from '@kikiutils/shared/storages/redis/msgpack';\n * import type { RedisLikeAdapter } from '@kikiutils/shared/storages/redis/types';\n *\n * // This in-memory adapter demonstrates non-expiring operations without an external service.\n * const values = new Map<string, Uint8Array>();\n * const adapter: RedisLikeAdapter = {\n * delete: async (key) => values.delete(key) ? 1 : 0,\n * getBuffer: async (key) => values.get(key) ?? null,\n * has: async (key) => values.has(key),\n * setBuffer: async (key, value) => {\n * values.set(key, value);\n * return 'OK';\n * },\n * setBufferEx: async () => {\n * throw new Error('TTL writes are not supported by this example adapter');\n * },\n * ttl: async (key) => values.has(key) ? -1 : -2,\n * };\n *\n * const storage = createRedisMsgpackStorage(adapter);\n * await storage.setItem('profile', { name: 'Alice' });\n * const profile = await storage.getItem<{ name: string }>('profile');\n * console.log(profile); // => { name: 'Alice' }\n * ```\n */\nexport function createRedisMsgpackStorage(adapter: RedisLikeAdapter): Readonly<RedisLikeStorage> {\n return Object.freeze({\n async getItem<T = unknown>(key: string) {\n const rawValue = await adapter.getBuffer(key);\n return rawValue ? decode(rawValue) as T : null;\n },\n getItemTtl: (key: string) => adapter.ttl(key),\n hasItem: (key: string) => adapter.has(key),\n removeItem: async (key: string) => await adapter.delete(key) === 1,\n setItem: async (key: string, value: any) => await adapter.setBuffer(key, encode(value)) === 'OK',\n async setItemWithTtl(key: string, ttlSeconds: number, value: any) {\n return await adapter.setBufferEx(key, ttlSeconds, encode(value)) === 'OK';\n },\n });\n}\n"],"mappings":";;AAWA,IAAI,CAAC,6BACD,QAAQ,KAAK,qFAAqF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CtG,SAAgB,0BAA0B,SAAuD;CAC7F,OAAO,OAAO,OAAO;EACjB,MAAM,QAAqB,KAAa;GACpC,MAAM,WAAW,MAAM,QAAQ,UAAU,GAAG;GAC5C,OAAO,WAAW,OAAO,QAAQ,IAAS;EAC9C;EACA,aAAa,QAAgB,QAAQ,IAAI,GAAG;EAC5C,UAAU,QAAgB,QAAQ,IAAI,GAAG;EACzC,YAAY,OAAO,QAAgB,MAAM,QAAQ,OAAO,GAAG,MAAM;EACjE,SAAS,OAAO,KAAa,UAAe,MAAM,QAAQ,UAAU,KAAK,OAAO,KAAK,CAAC,MAAM;EAC5F,MAAM,eAAe,KAAa,YAAoB,OAAY;GAC9D,OAAO,MAAM,QAAQ,YAAY,KAAK,YAAY,OAAO,KAAK,CAAC,MAAM;EACzE;CACJ,CAAC;AACL"}
1
+ {"version":3,"file":"msgpack.js","names":[],"sources":["../../../src/storages/redis/msgpack.ts"],"sourcesContent":["import {\n decode,\n encode,\n isNativeAccelerationEnabled,\n} from 'msgpackr';\n\nimport type {\n RedisLikeAdapter,\n RedisLikeStorage,\n} from './types';\n\nif (!isNativeAccelerationEnabled) {\n console.warn('Native acceleration not enabled for msgpackr, verify that install finished properly');\n}\n\n/**\n * Creates a frozen Redis-like storage facade using MessagePack serialization.\n *\n * @remarks\n * Adapter rejections and MessagePack serialization errors propagate to the caller.\n * Reads resolve to `null` when no raw value exists; `getAndRemoveItem` reads and deletes in one atomic adapter call.\n * Writes resolve to `true` only when the adapter returns `OK`;\n * removal resolves to `true` only when exactly one key is deleted.\n * The adapter remains caller-owned; creating the facade does not open or close a connection.\n *\n * @param adapter - The caller-owned adapter for raw binary operations.\n *\n * @returns A new frozen facade sharing the supplied adapter.\n *\n * @example\n *\n * ```ts\n * import { createRedisMsgpackStorage } from '@kikiutils/shared/storages/redis/msgpack';\n * import type { RedisLikeAdapter } from '@kikiutils/shared/storages/redis/types';\n *\n * // This in-memory adapter demonstrates non-expiring operations without an external service.\n * const values = new Map<string, Uint8Array>();\n * const adapter: RedisLikeAdapter = {\n * delete: async (key) => values.delete(key) ? 1 : 0,\n * getBuffer: async (key) => values.get(key) ?? null,\n * getBufferAndDelete: async (key) => {\n * const value = values.get(key) ?? null;\n * values.delete(key);\n * return value;\n * },\n * has: async (key) => values.has(key),\n * setBuffer: async (key, value) => {\n * values.set(key, value);\n * return 'OK';\n * },\n * setBufferEx: async () => {\n * throw new Error('TTL writes are not supported by this example adapter');\n * },\n * setBufferExNx: async () => {\n * throw new Error('TTL writes are not supported by this example adapter');\n * },\n * ttl: async (key) => values.has(key) ? -1 : -2,\n * };\n *\n * const storage = createRedisMsgpackStorage(adapter);\n * await storage.setItem('profile', { name: 'Alice' });\n * const profile = await storage.getItem<{ name: string }>('profile');\n * console.log(profile); // => { name: 'Alice' }\n * ```\n */\nexport function createRedisMsgpackStorage(adapter: RedisLikeAdapter): Readonly<RedisLikeStorage> {\n return Object.freeze({\n async getAndRemoveItem<T = unknown>(key: string) {\n const rawValue = await adapter.getBufferAndDelete(key);\n return rawValue ? decode(rawValue) as T : null;\n },\n async getItem<T = unknown>(key: string) {\n const rawValue = await adapter.getBuffer(key);\n return rawValue ? decode(rawValue) as T : null;\n },\n getItemTtl: (key: string) => adapter.ttl(key),\n hasItem: (key: string) => adapter.has(key),\n removeItem: async (key: string) => await adapter.delete(key) === 1,\n setItem: async (key: string, value: any) => await adapter.setBuffer(key, encode(value)) === 'OK',\n async setItemWithTtl(key: string, ttlSeconds: number, value: any) {\n return await adapter.setBufferEx(key, ttlSeconds, encode(value)) === 'OK';\n },\n async setItemWithTtlIfAbsent(key: string, ttlSeconds: number, value: any) {\n return await adapter.setBufferExNx(key, ttlSeconds, encode(value)) === 'OK';\n },\n });\n}\n"],"mappings":";;AAWA,IAAI,CAAC,6BACD,QAAQ,KAAK,qFAAqF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDtG,SAAgB,0BAA0B,SAAuD;CAC7F,OAAO,OAAO,OAAO;EACjB,MAAM,iBAA8B,KAAa;GAC7C,MAAM,WAAW,MAAM,QAAQ,mBAAmB,GAAG;GACrD,OAAO,WAAW,OAAO,QAAQ,IAAS;EAC9C;EACA,MAAM,QAAqB,KAAa;GACpC,MAAM,WAAW,MAAM,QAAQ,UAAU,GAAG;GAC5C,OAAO,WAAW,OAAO,QAAQ,IAAS;EAC9C;EACA,aAAa,QAAgB,QAAQ,IAAI,GAAG;EAC5C,UAAU,QAAgB,QAAQ,IAAI,GAAG;EACzC,YAAY,OAAO,QAAgB,MAAM,QAAQ,OAAO,GAAG,MAAM;EACjE,SAAS,OAAO,KAAa,UAAe,MAAM,QAAQ,UAAU,KAAK,OAAO,KAAK,CAAC,MAAM;EAC5F,MAAM,eAAe,KAAa,YAAoB,OAAY;GAC9D,OAAO,MAAM,QAAQ,YAAY,KAAK,YAAY,OAAO,KAAK,CAAC,MAAM;EACzE;EACA,MAAM,uBAAuB,KAAa,YAAoB,OAAY;GACtE,OAAO,MAAM,QAAQ,cAAc,KAAK,YAAY,OAAO,KAAK,CAAC,MAAM;EAC3E;CACJ,CAAC;AACL"}
@@ -14,6 +14,11 @@ export interface RedisLikeAdapter {
14
14
  * Reads a key and resolves to its raw bytes, or `null` when the key is missing.
15
15
  */
16
16
  getBuffer: (key: string) => Promise<null | Uint8Array>;
17
+ /**
18
+ * Atomically reads and deletes a key, such as with Redis `GETDEL`,
19
+ * and resolves to its raw bytes, or `null` when the key is missing.
20
+ */
21
+ getBufferAndDelete: (key: string) => Promise<null | Uint8Array>;
17
22
  /**
18
23
  * Checks a key and resolves to whether it exists.
19
24
  */
@@ -30,6 +35,15 @@ export interface RedisLikeAdapter {
30
35
  * @param value - The raw bytes to store.
31
36
  */
32
37
  setBufferEx: (key: string, ttlSeconds: number, value: Uint8Array) => Promise<'OK' | void>;
38
+ /**
39
+ * Writes raw bytes with an expiry only when the key is missing, such as with Redis `SET key value EX ttl NX`,
40
+ * and resolves to the backend acknowledgment, or `null` or no value when the key already exists.
41
+ *
42
+ * @param key - The storage key.
43
+ * @param ttlSeconds - The expiry duration in seconds.
44
+ * @param value - The raw bytes to store.
45
+ */
46
+ setBufferExNx: (key: string, ttlSeconds: number, value: Uint8Array) => Promise<'OK' | null | void>;
33
47
  /**
34
48
  * Reads a key's remaining TTL in seconds, or `-1` for no expiry and `-2` for a missing key.
35
49
  */
@@ -43,6 +57,13 @@ export interface RedisLikeAdapter {
43
57
  * `-1` means no expiry, and `-2` means the key is missing. Connection cleanup remains caller-owned.
44
58
  */
45
59
  export interface RedisLikeStorage {
60
+ /**
61
+ * Atomically reads and removes a deserialized value and resolves to `null` when the key is missing.
62
+ * Concurrent callers on the same key receive the value at most once.
63
+ *
64
+ * @typeParam T - The expected value type; deserialization does not validate this type.
65
+ */
66
+ getAndRemoveItem: <T = unknown>(key: string) => Promise<null | T>;
46
67
  /**
47
68
  * Reads a deserialized value and resolves to `null` when the key is missing.
48
69
  *
@@ -73,5 +94,14 @@ export interface RedisLikeStorage {
73
94
  * @param value - The value to serialize and store.
74
95
  */
75
96
  setItemWithTtl: (key: string, ttlSeconds: number, value: any) => Promise<boolean>;
97
+ /**
98
+ * Atomically stores a value with an expiry only when the key is missing
99
+ * and resolves to whether the backend acknowledges the write.
100
+ *
101
+ * @param key - The storage key.
102
+ * @param ttlSeconds - The expiry duration in seconds.
103
+ * @param value - The value to serialize and store.
104
+ */
105
+ setItemWithTtlIfAbsent: (key: string, ttlSeconds: number, value: any) => Promise<boolean>;
76
106
  }
77
107
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/storages/redis/types.ts"],"mappings":";;;;;;;iBAOiB;;;;EAIb,SAAS,gBAAgB;;;;EAKzB,YAAY,gBAAgB,eAAe;;;;EAK3C,MAAM,gBAAgB;;;;EAKtB,YAAY,aAAa,OAAO,eAAe;;;;;;;;EAS/C,cAAc,aAAa,oBAAoB,OAAO,eAAe;;;;EAKrE,MAAM,gBAAgB;;;;;;;;;iBAUT;;;;;;EAMb,UAAU,aAAa,gBAAgB,eAAe;;;;EAKtD,aAAa,gBAAgB;;;;EAK7B,UAAU,gBAAgB;;;;EAK1B,aAAa,gBAAgB;;;;EAK7B,UAAU,aAAa,eAAe;;;;;;;;EAStC,iBAAiB,aAAa,oBAAoB,eAAe"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/storages/redis/types.ts"],"mappings":";;;;;;;iBAOiB;;;;EAIb,SAAS,gBAAgB;;;;EAKzB,YAAY,gBAAgB,eAAe;;;;;EAM3C,qBAAqB,gBAAgB,eAAe;;;;EAKpD,MAAM,gBAAgB;;;;EAKtB,YAAY,aAAa,OAAO,eAAe;;;;;;;;EAS/C,cAAc,aAAa,oBAAoB,OAAO,eAAe;;;;;;;;;EAUrE,gBAAgB,aAAa,oBAAoB,OAAO,eAAe;;;;EAKvE,MAAM,gBAAgB;;;;;;;;;iBAUT;;;;;;;EAOb,mBAAmB,aAAa,gBAAgB,eAAe;;;;;;EAO/D,UAAU,aAAa,gBAAgB,eAAe;;;;EAKtD,aAAa,gBAAgB;;;;EAK7B,UAAU,gBAAgB;;;;EAK1B,aAAa,gBAAgB;;;;EAK7B,UAAU,aAAa,eAAe;;;;;;;;EAStC,iBAAiB,aAAa,oBAAoB,eAAe;;;;;;;;;EAUjE,yBAAyB,aAAa,oBAAoB,eAAe"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kikiutils/shared",
3
3
  "type": "module",
4
- "version": "19.0.0",
4
+ "version": "20.0.1",
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",
@@ -31,68 +31,41 @@
31
31
  ],
32
32
  "sideEffects": false,
33
33
  "exports": {
34
- "./logger": {
35
- "types": "./dist/logger/index.d.ts",
36
- "import": "./dist/logger/index.js",
37
- "require": null
38
- },
39
- "./logger/_internals/formatter": {
40
- "types": null,
41
- "import": null,
42
- "require": null
43
- },
44
- "./logger/_internals/levels": {
45
- "types": null,
46
- "import": null,
47
- "require": null
48
- },
49
- "./logger/_internals/logger": {
50
- "types": null,
51
- "import": null,
52
- "require": null
53
- },
54
- "./logger/_internals/types": {
55
- "types": null,
56
- "import": null,
57
- "require": null
58
- },
59
- "./logger/index": {
60
- "types": null,
61
- "import": null,
62
- "require": null
63
- },
64
- "./logger/node": {
65
- "types": "./dist/logger/node/index.d.ts",
66
- "import": "./dist/logger/node/index.js",
67
- "require": null
68
- },
69
- "./logger/node/_internals/file-writer": {
70
- "types": null,
71
- "import": null,
72
- "require": null
73
- },
74
- "./logger/node/_internals/formatter": {
75
- "types": null,
76
- "import": null,
77
- "require": null
78
- },
79
- "./logger/node/index": {
80
- "types": null,
81
- "import": null,
82
- "require": null
83
- },
84
- "./package.json": "./package.json",
85
- "./types": {
86
- "types": "./dist/types/index.d.ts",
87
- "import": null,
88
- "require": null
89
- },
90
- "./types/index": {
91
- "types": null,
92
- "import": null,
93
- "require": null
94
- },
95
- "./*": "./dist/*.js"
34
+ "./buffer": "./dist/buffer.js",
35
+ "./classes/enhanced-date": "./dist/classes/enhanced-date.js",
36
+ "./classes/path": "./dist/classes/path.js",
37
+ "./classes/precision-number": "./dist/classes/precision-number.js",
38
+ "./clipboard": "./dist/clipboard.js",
39
+ "./crypto-hash": "./dist/crypto-hash.js",
40
+ "./datetime": "./dist/datetime.js",
41
+ "./element-plus": "./dist/element-plus.js",
42
+ "./enum": "./dist/enum.js",
43
+ "./env": "./dist/env.js",
44
+ "./event-awaiter": "./dist/event-awaiter.js",
45
+ "./file": "./dist/file.js",
46
+ "./general": "./dist/general.js",
47
+ "./hash": "./dist/hash.js",
48
+ "./logger": "./dist/logger/index.js",
49
+ "./logger/node": "./dist/logger/node/index.js",
50
+ "./logger/node/types": "./dist/logger/node/types.js",
51
+ "./logger/types": "./dist/logger/types.js",
52
+ "./math": "./dist/math.js",
53
+ "./number": "./dist/number.js",
54
+ "./random": "./dist/random.js",
55
+ "./redis/script-runner": "./dist/redis/script-runner.js",
56
+ "./storages/lru/keyed-store": "./dist/storages/lru/keyed-store.js",
57
+ "./storages/redis/keyed-store": "./dist/storages/redis/keyed-store.js",
58
+ "./storages/redis/msgpack": "./dist/storages/redis/msgpack.js",
59
+ "./storages/redis/types": "./dist/storages/redis/types.js",
60
+ "./string": "./dist/string.js",
61
+ "./time": "./dist/time.js",
62
+ "./types": "./dist/types/index.js",
63
+ "./types/filtered-key-path": "./dist/types/filtered-key-path.js",
64
+ "./types/vue": "./dist/types/vue.js",
65
+ "./url": "./dist/url.js",
66
+ "./vue": "./dist/vue.js",
67
+ "./web": "./dist/web.js",
68
+ "./package.json": "./package.json"
96
69
  },
97
70
  "files": [
98
71
  "./dist",
@@ -172,10 +145,11 @@
172
145
  },
173
146
  "devDependencies": {
174
147
  "@antfu/eslint-config": "^10.0.0",
175
- "@kikiutils/eslint-config": "^9.3.1",
148
+ "@arethetypeswrong/core": "^0.18.5",
149
+ "@kikiutils/eslint-config": "^9.3.2",
176
150
  "@kikiutils/tsconfigs": "^7.0.0",
177
151
  "@noble/hashes": "^2.4.0",
178
- "@types/node": "^26.6.5",
152
+ "@types/node": "^26.6.6",
179
153
  "@vitest/coverage-v8": "^5.0.5",
180
154
  "async-validator": "^4.2.5",
181
155
  "bson": "^7.3.3",
@@ -184,16 +158,16 @@
184
158
  "date-fns": "^4.4.0",
185
159
  "decimal.js": "^10.6.0",
186
160
  "element-plus": "^2.14.7",
187
- "eslint": "^10.13.0",
188
- "file-type": "^22.2.0",
161
+ "eslint": "^10.14.0",
162
+ "file-type": "^22.3.0",
189
163
  "jsdom": "^30.1.3",
190
- "knip": "^6.42.1",
164
+ "knip": "^6.44.0",
191
165
  "lru-cache": "^11.5.3",
192
166
  "millify": "^6.1.0",
193
- "msgpackr": "^2.1.2",
167
+ "msgpackr": "^2.1.3",
194
168
  "publint": "^0.3.25",
195
169
  "rotating-file-stream": "^3.2.10",
196
- "tsdown": "^0.23.2",
170
+ "tsdown": "^0.23.4",
197
171
  "typescript": "^6.0.3",
198
172
  "unplugin-unused": "^0.6.0",
199
173
  "vitest": "^5.0.5",
@@ -31,7 +31,7 @@ const stringPresentationKeys = [
31
31
 
32
32
  /** A non-recursive, rate-limited diagnostic boundary shared by server and browser adapters. */
33
33
  export function createDiagnosticReporter(
34
- onError?: (event: LoggerDiagnostic) => void,
34
+ onError?: (event: Readonly<LoggerDiagnostic>) => void,
35
35
  fallback?: (message: string) => void,
36
36
  ): (event: LoggerDiagnostic) => void {
37
37
  let reporting = false;
@@ -75,7 +75,7 @@ export function createDiagnosticReporter(
75
75
  if (!callbackPending) {
76
76
  try {
77
77
  callbackPending = true;
78
- const result: unknown = onError?.(event);
78
+ const result: unknown = onError?.(Object.freeze({ ...event }));
79
79
  if (result !== null && (typeof result === 'object' || typeof result === 'function')) {
80
80
  void Promise.resolve(result).then(
81
81
  () => {
@@ -99,7 +99,7 @@ export function createDiagnosticReporter(
99
99
  };
100
100
  }
101
101
 
102
- export function createLoggerCore(options: LoggerOptions, hooks: LoggerHooks): Logger {
102
+ export function createLoggerCore(options: LoggerOptions, hooks: LoggerHooks): Readonly<Logger> {
103
103
  validateLevel(options.level);
104
104
  validatePresentation(options);
105
105
  if (options.onError !== undefined && typeof options.onError !== 'function') {
@@ -189,7 +189,7 @@ export function createLoggerCore(options: LoggerOptions, hooks: LoggerHooks): Lo
189
189
  };
190
190
  }
191
191
 
192
- return logger;
192
+ return Object.freeze(logger);
193
193
  }
194
194
 
195
195
  function getLevelSeverity(level: LoggerLevel | LoggerMethod): number {
@@ -18,7 +18,7 @@ import type {
18
18
  * waits for the console invocation, not display or durable delivery. Unspecified
19
19
  * levels follow LOG_LEVEL when a process environment is available, otherwise info.
20
20
  */
21
- export function createLogger(options: LoggerOptions = {}): Logger {
21
+ export function createLogger(options: LoggerOptions = {}): Readonly<Logger> {
22
22
  const target = globalThis.console;
23
23
  const fallback = target?.error?.bind(target);
24
24
  return createLoggerCore(
@@ -17,7 +17,7 @@ import type {
17
17
 
18
18
  interface LoggerFileWriter {
19
19
  close: () => Promise<void>;
20
- getStats: () => NodeLoggerStats;
20
+ getStats: () => Readonly<NodeLoggerStats>;
21
21
  write: (text: string) => Promise<void>;
22
22
  }
23
23
 
@@ -30,7 +30,7 @@ const defaultMaxPendingBytes = 8 * 1024 * 1024;
30
30
  export async function createFileWriter(
31
31
  options: LoggerFileOptions,
32
32
  report: (event: LoggerDiagnostic) => void,
33
- ): Promise<LoggerFileWriter> {
33
+ ): Promise<Readonly<LoggerFileWriter>> {
34
34
  const {
35
35
  maxPendingBytes,
36
36
  path,
@@ -138,7 +138,7 @@ export async function createFileWriter(
138
138
  initializing = false;
139
139
  }
140
140
 
141
- return {
141
+ return Object.freeze({
142
142
  close() {
143
143
  if (closePromise) return closePromise;
144
144
  closing = true;
@@ -158,12 +158,12 @@ export async function createFileWriter(
158
158
 
159
159
  return closePromise;
160
160
  },
161
- getStats: () => ({
161
+ getStats: () => Object.freeze({
162
162
  droppedCount,
163
163
  pendingBytes,
164
164
  writeErrorCount,
165
165
  }),
166
- write(text) {
166
+ write(text: string) {
167
167
  if (closing || closed || failed) {
168
168
  report({
169
169
  message: 'File output is closed or unavailable.',
@@ -210,7 +210,7 @@ export async function createFileWriter(
210
210
  }
211
211
  });
212
212
  },
213
- };
213
+ });
214
214
  }
215
215
 
216
216
  /** Validate before mkdir, stream creation, or other filesystem side effects. */
@@ -35,7 +35,7 @@ const consoleStreams = new WeakMap<NodeJS.WriteStream, ConsoleStreamState>();
35
35
  * persistence: overload drops the newest file record and failures are reported safely. Factory
36
36
  * validation/open failures reject. Call close explicitly; no process signal handlers are installed.
37
37
  */
38
- export async function createNodeLogger(options: NodeLoggerOptions = {}): Promise<NodeLogger> {
38
+ export async function createNodeLogger(options: NodeLoggerOptions = {}): Promise<Readonly<NodeLogger>> {
39
39
  const report = createDiagnosticReporter(options.onError, (message) => writeSync(2, message));
40
40
  let writer: Awaited<ReturnType<typeof createFileWriter>> | undefined;
41
41
  let closed = false;
@@ -83,40 +83,38 @@ export async function createNodeLogger(options: NodeLoggerOptions = {}): Promise
83
83
 
84
84
  if (options.file !== undefined && options.file !== false) writer = await createFileWriter(options.file, report);
85
85
 
86
- return Object.assign(
87
- logger,
88
- {
89
- close() {
90
- if (closePromise) return closePromise;
91
- closed = true;
92
- closePromise = (async () => {
93
- await Promise.all([
94
- writer?.close(),
95
- ...consoleWrites,
96
- ]);
97
-
98
- for (const target of consoleTargets) {
99
- const state = consoleStreams.get(target);
100
- if (!state) continue;
101
- state.owners.delete(consoleOwner);
102
- if (!state.owners.size) {
103
- target.off('error', state.onError);
104
- target.off('close', state.onClose);
105
- consoleStreams.delete(target);
106
- }
86
+ return Object.freeze({
87
+ ...logger,
88
+ close() {
89
+ if (closePromise) return closePromise;
90
+ closed = true;
91
+ closePromise = (async () => {
92
+ await Promise.all([
93
+ writer?.close(),
94
+ ...consoleWrites,
95
+ ]);
96
+
97
+ for (const target of consoleTargets) {
98
+ const state = consoleStreams.get(target);
99
+ if (!state) continue;
100
+ state.owners.delete(consoleOwner);
101
+ if (!state.owners.size) {
102
+ target.off('error', state.onError);
103
+ target.off('close', state.onClose);
104
+ consoleStreams.delete(target);
107
105
  }
106
+ }
108
107
 
109
- consoleTargets.clear();
110
- })();
111
- return closePromise;
112
- },
113
- getStats: () => writer?.getStats() ?? {
114
- droppedCount: 0,
115
- pendingBytes: 0,
116
- writeErrorCount: 0,
117
- },
108
+ consoleTargets.clear();
109
+ })();
110
+ return closePromise;
118
111
  },
119
- );
112
+ getStats: () => writer?.getStats() ?? Object.freeze({
113
+ droppedCount: 0,
114
+ pendingBytes: 0,
115
+ writeErrorCount: 0,
116
+ }),
117
+ });
120
118
  }
121
119
 
122
120
  /** One error listener per shared output stream; retained until its last owning logger calls close(). */
@@ -42,8 +42,8 @@ export interface NodeLogger extends Logger {
42
42
  /** Stop accepting records, finish accepted file writes, then release the stream. Not fsync. */
43
43
  close: () => Promise<void>;
44
44
 
45
- /** A fresh snapshot. Dropped/failed records still resolve their logging promises. */
46
- getStats: () => NodeLoggerStats;
45
+ /** A fresh, frozen file-output snapshot. Dropped/failed records still resolve their logging promises. */
46
+ getStats: () => Readonly<NodeLoggerStats>;
47
47
  }
48
48
 
49
49
  export interface NodeLoggerOptions extends LoggerOptions {
@@ -9,6 +9,7 @@ export type LoggerMethod = 'success' | Exclude<LoggerLevel, 'silent'>;
9
9
  *
10
10
  * Awaiting a method waits for processing, not guaranteed successful delivery or fsync.
11
11
  * Configuration errors may throw from the factory, configure(), or setLevel().
12
+ * Factories freeze the API object; settings remain adjustable through its methods.
12
13
  */
13
14
  export interface Logger {
14
15
  configure: (options: Pick<LoggerOptions, 'console' | 'icons' | 'prefix' | 'suffix' | 'timestamp'>) => void;
@@ -27,7 +28,7 @@ export interface Logger {
27
28
  warn: (...args: unknown[]) => Promise<void>;
28
29
  }
29
30
 
30
- /** A recoverable logging failure, reported without throwing from log methods. */
31
+ /** A shallow-frozen failure notification; nested error values retain their original ownership. */
31
32
  export interface LoggerDiagnostic {
32
33
  droppedCount?: number;
33
34
  error?: unknown;
@@ -50,7 +51,7 @@ export interface LoggerOptions {
50
51
  level?: 'env' | LoggerLevel;
51
52
 
52
53
  /** Best-effort, rate-limited notification; failures in this callback are contained. */
53
- onError?: (event: LoggerDiagnostic) => void;
54
+ onError?: (event: Readonly<LoggerDiagnostic>) => void;
54
55
  prefix?: string;
55
56
  suffix?: string;
56
57
 
@@ -8,7 +8,7 @@ import type { RedisLikeStorage } from './types';
8
8
  * @remarks
9
9
  * The key resolver is called synchronously once per operation with the operation's key arguments.
10
10
  * Resolver exceptions propagate synchronously; asynchronous storage failures retain the underlying rejection.
11
- * `setItemWithTtl` accepts its TTL in seconds before the value and key arguments.
11
+ * `setItemWithTtl` and `setItemWithTtlIfAbsent` accept their TTL in seconds before the value and key arguments.
12
12
  * The factory does not open or close the underlying connection; cleanup remains the caller's responsibility.
13
13
  *
14
14
  * @typeParam D - The stored value type used by reads and writes.
@@ -16,10 +16,11 @@ import type { RedisLikeStorage } from './types';
16
16
  * @param storage - The shared Redis-like storage instance.
17
17
  *
18
18
  * @returns A factory accepting a key resolver and returning a new frozen keyed facade sharing the storage.
19
- * Reads, writes, removals, and TTL results retain the underlying storage contract.
19
+ * Reads, atomic read-and-removals, writes, removals, and TTL results retain the underlying storage contract.
20
20
  */
21
21
  export function createRedisKeyedStore<D = unknown>(storage: MaybeReadonly<RedisLikeStorage>) {
22
22
  return <P extends any[]>(keyFn: (...args: P) => string) => Object.freeze({
23
+ getAndRemoveItem: (...args: P) => storage.getAndRemoveItem<D>(keyFn(...args)),
23
24
  getItem: (...args: P) => storage.getItem<D>(keyFn(...args)),
24
25
  getItemTtl: (...args: P) => storage.getItemTtl(keyFn(...args)),
25
26
  hasItem: (...args: P) => storage.hasItem(keyFn(...args)),
@@ -29,5 +30,8 @@ export function createRedisKeyedStore<D = unknown>(storage: MaybeReadonly<RedisL
29
30
  setItemWithTtl(ttlSeconds: number, value: D, ...args: P) {
30
31
  return storage.setItemWithTtl(keyFn(...args), ttlSeconds, value);
31
32
  },
33
+ setItemWithTtlIfAbsent(ttlSeconds: number, value: D, ...args: P) {
34
+ return storage.setItemWithTtlIfAbsent(keyFn(...args), ttlSeconds, value);
35
+ },
32
36
  });
33
37
  }
@@ -18,7 +18,8 @@ if (!isNativeAccelerationEnabled) {
18
18
  *
19
19
  * @remarks
20
20
  * Adapter rejections and MessagePack serialization errors propagate to the caller.
21
- * Reads resolve to `null` when no raw value exists. Writes resolve to `true` only when the adapter returns `OK`;
21
+ * Reads resolve to `null` when no raw value exists; `getAndRemoveItem` reads and deletes in one atomic adapter call.
22
+ * Writes resolve to `true` only when the adapter returns `OK`;
22
23
  * removal resolves to `true` only when exactly one key is deleted.
23
24
  * The adapter remains caller-owned; creating the facade does not open or close a connection.
24
25
  *
@@ -37,6 +38,11 @@ if (!isNativeAccelerationEnabled) {
37
38
  * const adapter: RedisLikeAdapter = {
38
39
  * delete: async (key) => values.delete(key) ? 1 : 0,
39
40
  * getBuffer: async (key) => values.get(key) ?? null,
41
+ * getBufferAndDelete: async (key) => {
42
+ * const value = values.get(key) ?? null;
43
+ * values.delete(key);
44
+ * return value;
45
+ * },
40
46
  * has: async (key) => values.has(key),
41
47
  * setBuffer: async (key, value) => {
42
48
  * values.set(key, value);
@@ -45,6 +51,9 @@ if (!isNativeAccelerationEnabled) {
45
51
  * setBufferEx: async () => {
46
52
  * throw new Error('TTL writes are not supported by this example adapter');
47
53
  * },
54
+ * setBufferExNx: async () => {
55
+ * throw new Error('TTL writes are not supported by this example adapter');
56
+ * },
48
57
  * ttl: async (key) => values.has(key) ? -1 : -2,
49
58
  * };
50
59
  *
@@ -56,6 +65,10 @@ if (!isNativeAccelerationEnabled) {
56
65
  */
57
66
  export function createRedisMsgpackStorage(adapter: RedisLikeAdapter): Readonly<RedisLikeStorage> {
58
67
  return Object.freeze({
68
+ async getAndRemoveItem<T = unknown>(key: string) {
69
+ const rawValue = await adapter.getBufferAndDelete(key);
70
+ return rawValue ? decode(rawValue) as T : null;
71
+ },
59
72
  async getItem<T = unknown>(key: string) {
60
73
  const rawValue = await adapter.getBuffer(key);
61
74
  return rawValue ? decode(rawValue) as T : null;
@@ -67,5 +80,8 @@ export function createRedisMsgpackStorage(adapter: RedisLikeAdapter): Readonly<R
67
80
  async setItemWithTtl(key: string, ttlSeconds: number, value: any) {
68
81
  return await adapter.setBufferEx(key, ttlSeconds, encode(value)) === 'OK';
69
82
  },
83
+ async setItemWithTtlIfAbsent(key: string, ttlSeconds: number, value: any) {
84
+ return await adapter.setBufferExNx(key, ttlSeconds, encode(value)) === 'OK';
85
+ },
70
86
  });
71
87
  }
@@ -16,6 +16,12 @@ export interface RedisLikeAdapter {
16
16
  */
17
17
  getBuffer: (key: string) => Promise<null | Uint8Array>;
18
18
 
19
+ /**
20
+ * Atomically reads and deletes a key, such as with Redis `GETDEL`,
21
+ * and resolves to its raw bytes, or `null` when the key is missing.
22
+ */
23
+ getBufferAndDelete: (key: string) => Promise<null | Uint8Array>;
24
+
19
25
  /**
20
26
  * Checks a key and resolves to whether it exists.
21
27
  */
@@ -35,6 +41,16 @@ export interface RedisLikeAdapter {
35
41
  */
36
42
  setBufferEx: (key: string, ttlSeconds: number, value: Uint8Array) => Promise<'OK' | void>;
37
43
 
44
+ /**
45
+ * Writes raw bytes with an expiry only when the key is missing, such as with Redis `SET key value EX ttl NX`,
46
+ * and resolves to the backend acknowledgment, or `null` or no value when the key already exists.
47
+ *
48
+ * @param key - The storage key.
49
+ * @param ttlSeconds - The expiry duration in seconds.
50
+ * @param value - The raw bytes to store.
51
+ */
52
+ setBufferExNx: (key: string, ttlSeconds: number, value: Uint8Array) => Promise<'OK' | null | void>;
53
+
38
54
  /**
39
55
  * Reads a key's remaining TTL in seconds, or `-1` for no expiry and `-2` for a missing key.
40
56
  */
@@ -49,6 +65,14 @@ export interface RedisLikeAdapter {
49
65
  * `-1` means no expiry, and `-2` means the key is missing. Connection cleanup remains caller-owned.
50
66
  */
51
67
  export interface RedisLikeStorage {
68
+ /**
69
+ * Atomically reads and removes a deserialized value and resolves to `null` when the key is missing.
70
+ * Concurrent callers on the same key receive the value at most once.
71
+ *
72
+ * @typeParam T - The expected value type; deserialization does not validate this type.
73
+ */
74
+ getAndRemoveItem: <T = unknown>(key: string) => Promise<null | T>;
75
+
52
76
  /**
53
77
  * Reads a deserialized value and resolves to `null` when the key is missing.
54
78
  *
@@ -84,4 +108,14 @@ export interface RedisLikeStorage {
84
108
  * @param value - The value to serialize and store.
85
109
  */
86
110
  setItemWithTtl: (key: string, ttlSeconds: number, value: any) => Promise<boolean>;
111
+
112
+ /**
113
+ * Atomically stores a value with an expiry only when the key is missing
114
+ * and resolves to whether the backend acknowledges the write.
115
+ *
116
+ * @param key - The storage key.
117
+ * @param ttlSeconds - The expiry duration in seconds.
118
+ * @param value - The value to serialize and store.
119
+ */
120
+ setItemWithTtlIfAbsent: (key: string, ttlSeconds: number, value: any) => Promise<boolean>;
87
121
  }