@kikiutils/shared 18.3.0 → 20.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/README.md +0 -7
  2. package/dist/buffer.d.ts +0 -2
  3. package/dist/buffer.d.ts.map +1 -1
  4. package/dist/buffer.js +1 -2
  5. package/dist/classes/enhanced-date.d.ts +0 -2
  6. package/dist/classes/enhanced-date.d.ts.map +1 -1
  7. package/dist/classes/enhanced-date.js +1 -2
  8. package/dist/classes/path.d.ts +1 -3
  9. package/dist/classes/path.d.ts.map +1 -1
  10. package/dist/classes/path.js +1 -2
  11. package/dist/classes/precision-number.d.ts +0 -2
  12. package/dist/classes/precision-number.d.ts.map +1 -1
  13. package/dist/classes/precision-number.js +1 -2
  14. package/dist/clipboard.d.ts +0 -2
  15. package/dist/clipboard.d.ts.map +1 -1
  16. package/dist/clipboard.js +1 -2
  17. package/dist/crypto-hash.d.ts +0 -2
  18. package/dist/crypto-hash.d.ts.map +1 -1
  19. package/dist/crypto-hash.js +1 -2
  20. package/dist/datetime.d.ts +0 -2
  21. package/dist/datetime.d.ts.map +1 -1
  22. package/dist/datetime.js +1 -2
  23. package/dist/element-plus.d.ts +0 -2
  24. package/dist/element-plus.d.ts.map +1 -1
  25. package/dist/element-plus.js +1 -2
  26. package/dist/enum.d.ts +0 -2
  27. package/dist/enum.d.ts.map +1 -1
  28. package/dist/enum.js +1 -2
  29. package/dist/env.d.ts +0 -2
  30. package/dist/env.d.ts.map +1 -1
  31. package/dist/env.js +1 -2
  32. package/dist/event-awaiter.d.ts +0 -2
  33. package/dist/event-awaiter.d.ts.map +1 -1
  34. package/dist/event-awaiter.js +1 -2
  35. package/dist/file.d.ts +0 -2
  36. package/dist/file.d.ts.map +1 -1
  37. package/dist/file.js +1 -2
  38. package/dist/general.d.ts +0 -2
  39. package/dist/general.d.ts.map +1 -1
  40. package/dist/general.js +1 -2
  41. package/dist/hash.d.ts +0 -2
  42. package/dist/hash.d.ts.map +1 -1
  43. package/dist/hash.js +1 -2
  44. package/dist/logger/_internals/formatter.d.ts +13 -0
  45. package/dist/logger/_internals/formatter.d.ts.map +1 -0
  46. package/dist/logger/_internals/formatter.js +57 -0
  47. package/dist/logger/_internals/formatter.js.map +1 -0
  48. package/dist/logger/_internals/levels.d.ts +54 -0
  49. package/dist/logger/_internals/levels.d.ts.map +1 -0
  50. package/dist/logger/_internals/levels.js +59 -0
  51. package/dist/logger/_internals/levels.js.map +1 -0
  52. package/dist/logger/_internals/logger.d.ts +12 -0
  53. package/dist/logger/_internals/logger.d.ts.map +1 -0
  54. package/dist/logger/_internals/logger.js +147 -0
  55. package/dist/logger/_internals/logger.js.map +1 -0
  56. package/dist/logger/_internals/types.d.ts +12 -0
  57. package/dist/logger/_internals/types.d.ts.map +1 -0
  58. package/dist/logger/_internals/types.js +1 -0
  59. package/dist/logger/index.d.ts +10 -0
  60. package/dist/logger/index.d.ts.map +1 -0
  61. package/dist/logger/index.js +25 -0
  62. package/dist/logger/index.js.map +1 -0
  63. package/dist/logger/node/_internals/file-writer.d.ts +11 -0
  64. package/dist/logger/node/_internals/file-writer.d.ts.map +1 -0
  65. package/dist/logger/node/_internals/file-writer.js +188 -0
  66. package/dist/logger/node/_internals/file-writer.js.map +1 -0
  67. package/dist/logger/node/_internals/formatter.d.ts +4 -0
  68. package/dist/logger/node/_internals/formatter.d.ts.map +1 -0
  69. package/dist/logger/node/_internals/formatter.js +21 -0
  70. package/dist/logger/node/_internals/formatter.js.map +1 -0
  71. package/dist/logger/node/index.d.ts +10 -0
  72. package/dist/logger/node/index.d.ts.map +1 -0
  73. package/dist/logger/node/index.js +123 -0
  74. package/dist/logger/node/index.js.map +1 -0
  75. package/dist/logger/node/types.d.ts +43 -0
  76. package/dist/logger/node/types.d.ts.map +1 -0
  77. package/dist/logger/node/types.js +1 -0
  78. package/dist/logger/types.d.ts +50 -0
  79. package/dist/logger/types.d.ts.map +1 -0
  80. package/dist/logger/types.js +1 -0
  81. package/dist/math.d.ts +0 -2
  82. package/dist/math.d.ts.map +1 -1
  83. package/dist/math.js +1 -2
  84. package/dist/number.d.ts +0 -2
  85. package/dist/number.d.ts.map +1 -1
  86. package/dist/number.js +1 -2
  87. package/dist/random.d.ts +0 -2
  88. package/dist/random.d.ts.map +1 -1
  89. package/dist/random.js +1 -2
  90. package/dist/redis/script-runner.d.ts +0 -2
  91. package/dist/redis/script-runner.d.ts.map +1 -1
  92. package/dist/redis/script-runner.js +1 -2
  93. package/dist/storages/lru/keyed-store.d.ts +0 -2
  94. package/dist/storages/lru/keyed-store.d.ts.map +1 -1
  95. package/dist/storages/lru/keyed-store.js +1 -2
  96. package/dist/storages/redis/keyed-store.d.ts +4 -4
  97. package/dist/storages/redis/keyed-store.d.ts.map +1 -1
  98. package/dist/storages/redis/keyed-store.js +7 -4
  99. package/dist/storages/redis/keyed-store.js.map +1 -1
  100. package/dist/storages/redis/msgpack.d.ts +10 -3
  101. package/dist/storages/redis/msgpack.d.ts.map +1 -1
  102. package/dist/storages/redis/msgpack.js +18 -3
  103. package/dist/storages/redis/msgpack.js.map +1 -1
  104. package/dist/storages/redis/types.d.ts +30 -2
  105. package/dist/storages/redis/types.d.ts.map +1 -1
  106. package/dist/string.d.ts +0 -2
  107. package/dist/string.d.ts.map +1 -1
  108. package/dist/string.js +1 -2
  109. package/dist/time.d.ts +0 -2
  110. package/dist/time.d.ts.map +1 -1
  111. package/dist/time.js +1 -2
  112. package/dist/types/filtered-key-path.d.ts +0 -2
  113. package/dist/types/filtered-key-path.d.ts.map +1 -1
  114. package/dist/types/index.d.ts +0 -10
  115. package/dist/types/index.d.ts.map +1 -1
  116. package/dist/types/vue.d.ts +0 -2
  117. package/dist/types/vue.d.ts.map +1 -1
  118. package/dist/url.d.ts +0 -2
  119. package/dist/url.d.ts.map +1 -1
  120. package/dist/url.js +1 -2
  121. package/dist/vue.d.ts +0 -2
  122. package/dist/vue.d.ts.map +1 -1
  123. package/dist/vue.js +1 -2
  124. package/dist/web.d.ts +0 -2
  125. package/dist/web.d.ts.map +1 -1
  126. package/dist/web.js +1 -2
  127. package/package.json +73 -45
  128. package/src/logger/_internals/formatter.ts +80 -0
  129. package/src/logger/_internals/levels.ts +60 -0
  130. package/src/logger/_internals/logger.ts +224 -0
  131. package/src/logger/_internals/types.ts +12 -0
  132. package/src/logger/index.ts +34 -0
  133. package/src/logger/node/_internals/file-writer.ts +298 -0
  134. package/src/logger/node/_internals/formatter.ts +33 -0
  135. package/src/logger/node/index.ts +173 -0
  136. package/src/logger/node/types.ts +58 -0
  137. package/src/logger/types.ts +60 -0
  138. package/src/storages/redis/keyed-store.ts +6 -2
  139. package/src/storages/redis/msgpack.ts +17 -1
  140. package/src/storages/redis/types.ts +34 -0
  141. package/src/types/index.ts +0 -10
  142. package/dist/classes/ssh-client.d.ts +0 -40
  143. package/dist/classes/ssh-client.d.ts.map +0 -1
  144. package/dist/classes/ssh-client.js +0 -130
  145. package/dist/classes/ssh-client.js.map +0 -1
  146. package/dist/consola.d.ts +0 -24
  147. package/dist/consola.d.ts.map +0 -1
  148. package/dist/consola.js +0 -29
  149. package/dist/consola.js.map +0 -1
  150. package/dist/object.d.ts +0 -44
  151. package/dist/object.d.ts.map +0 -1
  152. package/dist/object.js +0 -59
  153. package/dist/object.js.map +0 -1
  154. package/dist/pino.d.ts +0 -25
  155. package/dist/pino.d.ts.map +0 -1
  156. package/dist/pino.js +0 -34
  157. package/dist/pino.js.map +0 -1
  158. package/src/classes/ssh-client.ts +0 -175
  159. package/src/consola.ts +0 -25
  160. package/src/object.ts +0 -78
  161. package/src/pino.ts +0 -33
@@ -0,0 +1,173 @@
1
+ import { writeSync } from 'node:fs';
2
+ import { stripVTControlCharacters } from 'node:util';
3
+
4
+ import { supportsLoggerColor } from '../_internals/formatter';
5
+ import { getLoggerStreamName } from '../_internals/levels';
6
+ import {
7
+ createDiagnosticReporter,
8
+ createLoggerCore,
9
+ } from '../_internals/logger';
10
+ import type { LoggerDiagnostic } from '../types';
11
+
12
+ import { createFileWriter } from './_internals/file-writer';
13
+ import { formatNodeLoggerRecord } from './_internals/formatter';
14
+ import type {
15
+ NodeLogger,
16
+ NodeLoggerOptions,
17
+ } from './types';
18
+
19
+ interface ConsoleStreamState {
20
+ onClose: () => void;
21
+ onError: (error: Error) => void;
22
+ owners: Set<object>;
23
+ pending: Set<(error?: Error | null) => void>;
24
+ }
25
+
26
+ // Constants/Variables
27
+ const consoleStreams = new WeakMap<NodeJS.WriteStream, ConsoleStreamState>();
28
+
29
+ // Functions
30
+
31
+ /**
32
+ * Creates a Node.js/Bun logger with optional bounded, append-only file output and rotation.
33
+ *
34
+ * Log calls never throw or reject. Awaiting them waits for processing, not proof of successful
35
+ * persistence: overload drops the newest file record and failures are reported safely. Factory
36
+ * validation/open failures reject. Call close explicitly; no process signal handlers are installed.
37
+ */
38
+ export async function createNodeLogger(options: NodeLoggerOptions = {}): Promise<Readonly<NodeLogger>> {
39
+ const report = createDiagnosticReporter(options.onError, (message) => writeSync(2, message));
40
+ let writer: Awaited<ReturnType<typeof createFileWriter>> | undefined;
41
+ let closed = false;
42
+ let closePromise: Promise<void> | undefined;
43
+ const consoleOwner = {};
44
+ const consoleTargets = new Set<NodeJS.WriteStream>();
45
+ const consoleWrites = new Set<Promise<void>>();
46
+ const logger = createLoggerCore(
47
+ options,
48
+ {
49
+ report,
50
+ write(record) {
51
+ if (closed) {
52
+ report({
53
+ message: 'Logger is closed.',
54
+ type: 'closed',
55
+ });
56
+
57
+ return;
58
+ }
59
+
60
+ if (!record.console && !writer) return;
61
+ const target = process[getLoggerStreamName(record.type)];
62
+
63
+ const text = formatNodeLoggerRecord(record, record.console && supportsLoggerColor(target));
64
+ if (record.console) {
65
+ const line = `${text}\n`;
66
+ consoleTargets.add(target);
67
+ const completion = writeConsole(target, line, report, consoleOwner);
68
+ consoleWrites.add(completion);
69
+ void completion.then(() => consoleWrites.delete(completion));
70
+ const fileCompletion = writer?.write(`${stripVTControlCharacters(text)}\n`);
71
+ return fileCompletion
72
+ ? Promise.all([
73
+ completion,
74
+ fileCompletion,
75
+ ]).then(() => {})
76
+ : completion;
77
+ }
78
+
79
+ return writer?.write(`${stripVTControlCharacters(text)}\n`);
80
+ },
81
+ },
82
+ );
83
+
84
+ if (options.file !== undefined && options.file !== false) writer = await createFileWriter(options.file, report);
85
+
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);
105
+ }
106
+ }
107
+
108
+ consoleTargets.clear();
109
+ })();
110
+ return closePromise;
111
+ },
112
+ getStats: () => writer?.getStats() ?? Object.freeze({
113
+ droppedCount: 0,
114
+ pendingBytes: 0,
115
+ writeErrorCount: 0,
116
+ }),
117
+ });
118
+ }
119
+
120
+ /** One error listener per shared output stream; retained until its last owning logger calls close(). */
121
+ function writeConsole(
122
+ target: NodeJS.WriteStream,
123
+ text: string,
124
+ report: (event: LoggerDiagnostic) => void,
125
+ owner: object,
126
+ ) {
127
+ let state = consoleStreams.get(target);
128
+ if (!state) {
129
+ const pending = new Set<(error?: Error | null) => void>();
130
+ const onError = (error: Error) => {
131
+ for (const settle of pending) settle(error);
132
+ };
133
+
134
+ const onClose = () => onError(new Error('Console stream closed.'));
135
+ state = {
136
+ onClose,
137
+ onError,
138
+ owners: new Set(),
139
+ pending,
140
+ };
141
+
142
+ consoleStreams.set(target, state);
143
+ target.on('error', onError);
144
+ target.on('close', onClose);
145
+ }
146
+
147
+ state.owners.add(owner);
148
+ const current = state;
149
+ return new Promise<void>((resolveWritten) => {
150
+ let settled = false;
151
+ const settle = (error?: Error | null) => {
152
+ if (settled) return;
153
+ settled = true;
154
+ current.pending.delete(settle);
155
+ if (error) {
156
+ report({
157
+ error,
158
+ message: 'Console output failed.',
159
+ type: 'console',
160
+ });
161
+ }
162
+
163
+ resolveWritten();
164
+ };
165
+
166
+ current.pending.add(settle);
167
+ try {
168
+ target.write(text, settle);
169
+ } catch (error) {
170
+ settle(error instanceof Error ? error : new Error('Console output failed.'));
171
+ }
172
+ });
173
+ }
@@ -0,0 +1,58 @@
1
+ import type { PathLike } from '../../classes/path';
2
+ import type {
3
+ Logger,
4
+ LoggerOptions,
5
+ } from '../types';
6
+
7
+ /** Specify interval and/or maxSize. Size limits use positive integers with B, K, M, or G (binary units). */
8
+ export type LoggerRotationOptions = {
9
+ /** Gzip historical files. Defaults to true. */
10
+ compress?: boolean;
11
+
12
+ /** Number of historical files, excluding the active file. Defaults to 30. */
13
+ keepFiles?: number;
14
+
15
+ /** Maximum combined size of historical files. */
16
+ maxTotalSize?: string;
17
+ } & (
18
+ | {
19
+ /** Opt-in time rotation aligned to the system's local calendar boundary. */
20
+ interval: 'daily' | 'hourly';
21
+ maxSize?: string;
22
+ }
23
+ | {
24
+ /** Opt-in time rotation aligned to the system's local calendar boundary. */
25
+ interval?: 'daily' | 'hourly';
26
+ maxSize: string;
27
+ }
28
+ );
29
+
30
+ export interface LoggerFileOptions {
31
+ /** Maximum unfinished payload bytes, including data already submitted to the stream. */
32
+ maxPendingBytes?: number;
33
+
34
+ /** Resolved once at creation. The file is opened in append mode. */
35
+ path: PathLike;
36
+
37
+ /** Omitted or false disables rotation. An object must specify interval or maxSize. */
38
+ rotate?: false | LoggerRotationOptions;
39
+ }
40
+
41
+ export interface NodeLogger extends Logger {
42
+ /** Stop accepting records, finish accepted file writes, then release the stream. Not fsync. */
43
+ close: () => Promise<void>;
44
+
45
+ /** A fresh, frozen file-output snapshot. Dropped/failed records still resolve their logging promises. */
46
+ getStats: () => Readonly<NodeLoggerStats>;
47
+ }
48
+
49
+ export interface NodeLoggerOptions extends LoggerOptions {
50
+ /** Disabled by default. Each logger owns at most one fixed file destination. */
51
+ file?: false | LoggerFileOptions;
52
+ }
53
+
54
+ export interface NodeLoggerStats {
55
+ droppedCount: number;
56
+ pendingBytes: number;
57
+ writeErrorCount: number;
58
+ }
@@ -0,0 +1,60 @@
1
+ /** Minimum severity to emit; `success` uses the same threshold as `info`. */
2
+ export type LoggerLevel = 'debug' | 'error' | 'fatal' | 'info' | 'silent' | 'trace' | 'warn';
3
+
4
+ /** A logging method, including the informational success presentation. */
5
+ export type LoggerMethod = 'success' | Exclude<LoggerLevel, 'silent'>;
6
+
7
+ /**
8
+ * A logger whose logging methods never throw or reject for recoverable failures.
9
+ *
10
+ * Awaiting a method waits for processing, not guaranteed successful delivery or fsync.
11
+ * Configuration errors may throw from the factory, configure(), or setLevel().
12
+ * Factories freeze the API object; settings remain adjustable through its methods.
13
+ */
14
+ export interface Logger {
15
+ configure: (options: Pick<LoggerOptions, 'console' | 'icons' | 'prefix' | 'suffix' | 'timestamp'>) => void;
16
+ debug: (...args: unknown[]) => Promise<void>;
17
+ error: (...args: unknown[]) => Promise<void>;
18
+ fatal: (...args: unknown[]) => Promise<void>;
19
+ info: (...args: unknown[]) => Promise<void>;
20
+
21
+ /** Accepts success as an informational method; it is not a configurable threshold. */
22
+ isLevelEnabled: (level: LoggerLevel | LoggerMethod) => boolean;
23
+
24
+ /** Sets a severity threshold (not success); env restores environment-driven filtering. */
25
+ setLevel: (level: 'env' | LoggerLevel) => void;
26
+ success: (...args: unknown[]) => Promise<void>;
27
+ trace: (...args: unknown[]) => Promise<void>;
28
+ warn: (...args: unknown[]) => Promise<void>;
29
+ }
30
+
31
+ /** A shallow-frozen failure notification; nested error values retain their original ownership. */
32
+ export interface LoggerDiagnostic {
33
+ droppedCount?: number;
34
+ error?: unknown;
35
+ message: string;
36
+
37
+ /** Same-type notifications suppressed since the previous emitted diagnostic. */
38
+ suppressedCount?: number;
39
+ type: 'closed' | 'console' | 'file' | 'format' | 'invalid-level' | 'overflow';
40
+ }
41
+
42
+ /** Configuration shared by browser and server loggers. */
43
+ export interface LoggerOptions {
44
+ /** Enables console output. Defaults to `true`. */
45
+ console?: boolean;
46
+
47
+ /** Enables Unicode level icons. Defaults to `true`. */
48
+ icons?: boolean;
49
+
50
+ /** Overrides LOG_LEVEL; `env` restores the environment-driven threshold. */
51
+ level?: 'env' | LoggerLevel;
52
+
53
+ /** Best-effort, rate-limited notification; failures in this callback are contained. */
54
+ onError?: (event: Readonly<LoggerDiagnostic>) => void;
55
+ prefix?: string;
56
+ suffix?: string;
57
+
58
+ /** Enables the fixed local-time millisecond prefix. Defaults to `true`. */
59
+ timestamp?: boolean;
60
+ }
@@ -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
  }
@@ -6,11 +6,6 @@ import type {
6
6
 
7
7
  export type { FilteredKeyPath } from './filtered-key-path';
8
8
 
9
- /**
10
- * A generic string-keyed object for utility APIs that intentionally accept any value shape.
11
- */
12
- export type AnyRecord = Record<string, any>;
13
-
14
9
  /**
15
10
  * Binary-like inputs accepted by `toBuffer` across browser and Node.js runtimes.
16
11
  */
@@ -31,11 +26,6 @@ export type MaybePartial<T> = Partial<T> | T;
31
26
  */
32
27
  export type MaybeReadonly<T> = Readonly<T> | T;
33
28
 
34
- /**
35
- * Number-like value represented as a number or a numeric string.
36
- */
37
- export type Numberish = number | string;
38
-
39
29
  /**
40
30
  * A `Record` where every key is optional.
41
31
  */
@@ -1,40 +0,0 @@
1
- import { PathLike } from "./path.js";
2
- import { Config, NodeSSH, SSHExecCommandOptions, SSHGetPutDirectoryOptions, SSHPutFilesOptions } from "node-ssh";
3
- import { SFTPWrapper, TransferOptions } from "ssh2";
4
- //#region src/classes/ssh-client.d.ts
5
- declare const loggerLevelStringToConsolaLogLevelMap: {
6
- readonly debug: 4;
7
- readonly error: 0;
8
- readonly fatal: 0;
9
- readonly info: 3;
10
- readonly normal: 2;
11
- readonly silent: -999;
12
- readonly trace: 5;
13
- readonly verbose: 999;
14
- readonly warn: 1;
15
- };
16
- export declare class SshClient {
17
- #private;
18
- getDir: (localDirectory: PathLike, remoteDirectory: PathLike, options?: SSHGetPutDirectoryOptions) => Promise<boolean>;
19
- putDir: (localDirectory: PathLike, remoteDirectory: PathLike, options?: SSHGetPutDirectoryOptions) => Promise<boolean>;
20
- constructor(host: string, username: string, password: string, port?: number, connectConfig?: Config);
21
- get nodeSsh(): NodeSSH;
22
- connect(): Promise<boolean>;
23
- disconnect(): boolean;
24
- execCommand(command: string, options?: SSHExecCommandOptions): Promise<void | import("node-ssh").SSHExecCommandResponse>;
25
- execCommandWithIo(command: string, options?: SSHExecCommandOptions): Promise<void | import("node-ssh").SSHExecCommandResponse>;
26
- getDirectory(localDirectory: PathLike, remoteDirectory: PathLike, options?: SSHGetPutDirectoryOptions): Promise<boolean>;
27
- getFile(localFile: PathLike, remoteFile: PathLike, givenSftp?: null | SFTPWrapper, transferOptions?: null | TransferOptions): Promise<boolean>;
28
- isConnected(): boolean;
29
- mkdir(path: PathLike): Promise<boolean>;
30
- putDirectory(localDirectory: PathLike, remoteDirectory: PathLike, options?: SSHGetPutDirectoryOptions): Promise<boolean>;
31
- putFile(localFile: PathLike, remoteFile: PathLike, givenSftp?: null | SFTPWrapper, transferOptions?: null | TransferOptions): Promise<boolean>;
32
- putFiles(files: {
33
- local: PathLike;
34
- remote: PathLike;
35
- }[], options?: SSHPutFilesOptions): Promise<boolean>;
36
- setLoggerLevel(level: keyof typeof loggerLevelStringToConsolaLogLevelMap): void;
37
- }
38
- //#endregion
39
- export {};
40
- //# sourceMappingURL=ssh-client.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ssh-client.d.ts","names":[],"sources":["../../src/classes/ssh-client.ts"],"mappings":";;;;cAgBM;;;;;;;;;;;qBAYO;;EAOT,SAAM,gBA2D6B,UAAQ,iBAAmB,UAAQ,UAAY,8BAAyB;EA1D3G,SAAM,gBAgG6B,UAAQ,iBAAmB,UAAQ,UAAY,8BAAyB;EA7F/F,YAAA,cAAc,kBAAkB,kBAAkB,eAAmB,gBAAgB;MAe7F,WAAO;EAKL,WAAO;EAUb;EAUA,YAAY,iBAAiB,UAAU,wBAAqB,kCAAA;EAI5D,kBAAkB,iBAAiB,UAAU,wBAAqB,kCAAA;EAW5D,aAAa,gBAAgB,UAAU,iBAAiB,UAAU,UAAU,4BAAyB;EASrG,QACF,WAAW,UACX,YAAY,UACZ,mBAAmB,aACnB,yBAAyB,kBAAe;EAW5C;EAIM,MAAM,MAAM,WAAQ;EAUpB,aAAa,gBAAgB,UAAU,iBAAiB,UAAU,UAAU,4BAAyB;EASrG,QACF,WAAW,UACX,YAAY,UACZ,mBAAmB,aACnB,yBAAyB,kBAAe;EAWtC,SAAS;IAAS,OAAO;IAAU,QAAQ;OAAc,UAAU,qBAAkB;EAe3F,eAAe,oBAAoB"}
@@ -1,130 +0,0 @@
1
- import { createConsola } from "consola";
2
- import { NodeSSH } from "node-ssh";
3
- //#region src/classes/ssh-client.ts
4
- const loggerLevelStringToConsolaLogLevelMap = {
5
- debug: 4,
6
- error: 0,
7
- fatal: 0,
8
- info: 3,
9
- normal: 2,
10
- silent: -999,
11
- trace: 5,
12
- verbose: 999,
13
- warn: 1
14
- };
15
- var SshClient = class {
16
- #connectConfig;
17
- #logger;
18
- #nodeSsh;
19
- getDir = this.getDirectory;
20
- putDir = this.putDirectory;
21
- constructor(host, username, password, port = 22, connectConfig) {
22
- this.#connectConfig = {
23
- ...connectConfig,
24
- host,
25
- password,
26
- port,
27
- username
28
- };
29
- this.#logger = createConsola();
30
- this.#nodeSsh = new NodeSSH();
31
- if (process.env.NODE_ENV === "production") this.setLoggerLevel("error");
32
- }
33
- get nodeSsh() {
34
- return this.#nodeSsh;
35
- }
36
- async connect() {
37
- try {
38
- this.#nodeSsh = await this.#nodeSsh.connect(this.#connectConfig);
39
- return true;
40
- } catch (error) {
41
- this.#logger.error(error);
42
- return false;
43
- }
44
- }
45
- disconnect() {
46
- try {
47
- this.#nodeSsh.dispose();
48
- return true;
49
- } catch (error) {
50
- this.#logger.error(error);
51
- return false;
52
- }
53
- }
54
- execCommand(command, options) {
55
- return this.#nodeSsh.execCommand(command, options).catch(() => {});
56
- }
57
- execCommandWithIo(command, options) {
58
- return this.execCommand(command, {
59
- ...options,
60
- onStderr: (data) => process.stderr.write(data.toString()),
61
- onStdout: (data) => process.stdout.write(data.toString())
62
- });
63
- }
64
- async getDirectory(localDirectory, remoteDirectory, options) {
65
- try {
66
- return await this.#nodeSsh.getDirectory(localDirectory.toString(), remoteDirectory.toString(), options);
67
- } catch (error) {
68
- this.#logger.error(error);
69
- return false;
70
- }
71
- }
72
- async getFile(localFile, remoteFile, givenSftp, transferOptions) {
73
- try {
74
- await this.#nodeSsh.getFile(localFile.toString(), remoteFile.toString(), givenSftp, transferOptions);
75
- return true;
76
- } catch (error) {
77
- this.#logger.error(error);
78
- return false;
79
- }
80
- }
81
- isConnected() {
82
- return this.#nodeSsh.isConnected();
83
- }
84
- async mkdir(path) {
85
- try {
86
- await this.#nodeSsh.mkdir(path.toString());
87
- return true;
88
- } catch (error) {
89
- this.#logger.error(error);
90
- return false;
91
- }
92
- }
93
- async putDirectory(localDirectory, remoteDirectory, options) {
94
- try {
95
- return await this.#nodeSsh.putDirectory(localDirectory.toString(), remoteDirectory.toString(), options);
96
- } catch (error) {
97
- this.#logger.error(error);
98
- return false;
99
- }
100
- }
101
- async putFile(localFile, remoteFile, givenSftp, transferOptions) {
102
- try {
103
- await this.#nodeSsh.putFile(localFile.toString(), remoteFile.toString(), givenSftp, transferOptions);
104
- return true;
105
- } catch (error) {
106
- this.#logger.error(error);
107
- return false;
108
- }
109
- }
110
- async putFiles(files, options) {
111
- try {
112
- const convertedFiles = files.map(({ local, remote }) => ({
113
- local: local.toString(),
114
- remote: remote.toString()
115
- }));
116
- await this.#nodeSsh.putFiles(convertedFiles, options);
117
- return true;
118
- } catch (error) {
119
- this.#logger.error(error);
120
- return false;
121
- }
122
- }
123
- setLoggerLevel(level) {
124
- this.#logger.level = loggerLevelStringToConsolaLogLevelMap[level];
125
- }
126
- };
127
- //#endregion
128
- export { SshClient };
129
-
130
- //# sourceMappingURL=ssh-client.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ssh-client.js","names":[],"sources":["../../src/classes/ssh-client.ts"],"sourcesContent":["import { createConsola } from 'consola';\nimport type { ConsolaInstance } from 'consola';\nimport { NodeSSH } from 'node-ssh';\nimport type {\n Config,\n SSHExecCommandOptions,\n SSHGetPutDirectoryOptions,\n SSHPutFilesOptions,\n} from 'node-ssh';\nimport type {\n SFTPWrapper,\n TransferOptions,\n} from 'ssh2';\n\nimport type { PathLike } from './path';\n\nconst loggerLevelStringToConsolaLogLevelMap = {\n debug: 4,\n error: 0,\n fatal: 0,\n info: 3,\n normal: 2,\n silent: -999,\n trace: 5,\n verbose: 999,\n warn: 1,\n} as const;\n\nexport class SshClient {\n // Private instance properties\n readonly #connectConfig: Config;\n readonly #logger: ConsolaInstance;\n #nodeSsh: NodeSSH;\n\n // Public instance properties\n getDir = this.getDirectory;\n putDir = this.putDirectory;\n\n // Constructor\n constructor(host: string, username: string, password: string, port: number = 22, connectConfig?: Config) {\n this.#connectConfig = {\n ...connectConfig,\n host,\n password,\n port,\n username,\n };\n\n this.#logger = createConsola();\n this.#nodeSsh = new NodeSSH();\n if (process.env.NODE_ENV === 'production') this.setLoggerLevel('error');\n }\n\n // Public instance accessors\n get nodeSsh() {\n return this.#nodeSsh;\n }\n\n // Public instance methods\n async connect() {\n try {\n this.#nodeSsh = await this.#nodeSsh.connect(this.#connectConfig);\n return true;\n } catch (error) {\n this.#logger.error(error);\n return false;\n }\n }\n\n disconnect() {\n try {\n this.#nodeSsh.dispose();\n return true;\n } catch (error) {\n this.#logger.error(error);\n return false;\n }\n }\n\n execCommand(command: string, options?: SSHExecCommandOptions) {\n return this.#nodeSsh.execCommand(command, options).catch(() => {});\n }\n\n execCommandWithIo(command: string, options?: SSHExecCommandOptions) {\n return this.execCommand(\n command,\n {\n ...options,\n onStderr: (data) => process.stderr.write(data.toString()),\n onStdout: (data) => process.stdout.write(data.toString()),\n },\n );\n }\n\n async getDirectory(localDirectory: PathLike, remoteDirectory: PathLike, options?: SSHGetPutDirectoryOptions) {\n try {\n return await this.#nodeSsh.getDirectory(localDirectory.toString(), remoteDirectory.toString(), options);\n } catch (error) {\n this.#logger.error(error);\n return false;\n }\n }\n\n async getFile(\n localFile: PathLike,\n remoteFile: PathLike,\n givenSftp?: null | SFTPWrapper,\n transferOptions?: null | TransferOptions,\n ) {\n try {\n await this.#nodeSsh.getFile(localFile.toString(), remoteFile.toString(), givenSftp, transferOptions);\n return true;\n } catch (error) {\n this.#logger.error(error);\n return false;\n }\n }\n\n isConnected() {\n return this.#nodeSsh.isConnected();\n }\n\n async mkdir(path: PathLike) {\n try {\n await this.#nodeSsh.mkdir(path.toString());\n return true;\n } catch (error) {\n this.#logger.error(error);\n return false;\n }\n }\n\n async putDirectory(localDirectory: PathLike, remoteDirectory: PathLike, options?: SSHGetPutDirectoryOptions) {\n try {\n return await this.#nodeSsh.putDirectory(localDirectory.toString(), remoteDirectory.toString(), options);\n } catch (error) {\n this.#logger.error(error);\n return false;\n }\n }\n\n async putFile(\n localFile: PathLike,\n remoteFile: PathLike,\n givenSftp?: null | SFTPWrapper,\n transferOptions?: null | TransferOptions,\n ) {\n try {\n await this.#nodeSsh.putFile(localFile.toString(), remoteFile.toString(), givenSftp, transferOptions);\n return true;\n } catch (error) {\n this.#logger.error(error);\n return false;\n }\n }\n\n async putFiles(files: { local: PathLike; remote: PathLike }[], options?: SSHPutFilesOptions) {\n try {\n const convertedFiles = files.map(({ local, remote }) => ({\n local: local.toString(),\n remote: remote.toString(),\n }));\n\n await this.#nodeSsh.putFiles(convertedFiles, options);\n return true;\n } catch (error) {\n this.#logger.error(error);\n return false;\n }\n }\n\n setLoggerLevel(level: keyof typeof loggerLevelStringToConsolaLogLevelMap) {\n this.#logger.level = loggerLevelStringToConsolaLogLevelMap[level];\n }\n}\n"],"mappings":"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"}
package/dist/consola.d.ts DELETED
@@ -1,24 +0,0 @@
1
- //#region src/consola.d.ts
2
- /**
3
- * A consola logger with environment-controlled verbosity.
4
- *
5
- * @remarks
6
- * `CONSOLA_LOGGER_LEVEL` sets the log level when defined at module initialization;
7
- * otherwise, `NODE_ENV=production` selects `0`, and other environments retain the logger's default level.
8
- * Assign `logger.level` to change verbosity after initialization.
9
- *
10
- * @example
11
- *
12
- * ```ts
13
- * import { logger } from '@kikiutils/shared/consola';
14
- *
15
- * logger.level = 3;
16
- * logger.info('Application started');
17
- * ```
18
- *
19
- * @see {@link https://github.com/unjs/consola#log-level | Consola log levels}
20
- */
21
- export declare const consolaLogger: import("consola").ConsolaInstance;
22
- export declare const logger: import("consola").ConsolaInstance;
23
- //#endregion
24
- //# sourceMappingURL=consola.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"consola.d.ts","names":[],"sources":["../src/consola.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;qBAqBa,iCAAa;qBACb,0BAAM"}