@kikiutils/shared 13.4.0 → 13.5.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.
@@ -0,0 +1,180 @@
1
+ import { Decimal } from 'decimal.js';
2
+
3
+ export type PrecisionNumberValue = Decimal.Value | PrecisionNumber | { toString: () => string };
4
+
5
+ /**
6
+ * Class representing a precision number with configurable decimal places and rounding.
7
+ *
8
+ * This class leverages the Decimal.js library to ensure accurate arithmetic operations
9
+ * with floating point numbers. It provides methods for various arithmetic operations
10
+ * (addition, subtraction, multiplication, division) as well as comparison and utility
11
+ * methods to check the state of the number (e.g., if it is finite, an integer, zero, etc.).
12
+ *
13
+ * The class also includes custom symbol methods to support Node.js inspection and primitive
14
+ * type conversion.
15
+ *
16
+ * The class supports in-place modification methods that alter the current instance's value,
17
+ * such as `plus`, `minus`, `times`, `dividedBy`, and others. Additionally, methods prefixed
18
+ * with `to` (e.g., `toPlus`, `toMinus`) return a new instance of `PrecisionNumber` with the
19
+ * modified value, leaving the original instance unchanged.
20
+ */
21
+ export class PrecisionNumber {
22
+ // Private properties
23
+ readonly #decimalPlaces: number;
24
+ readonly #rounding: Decimal.Rounding;
25
+ #decimal: Decimal;
26
+
27
+ constructor(
28
+ value: PrecisionNumberValue = '0',
29
+ decimalPlaces: number = 2,
30
+ rounding: Decimal.Rounding = Decimal.ROUND_DOWN,
31
+ ) {
32
+ this.#decimalPlaces = decimalPlaces;
33
+ this.#rounding = rounding;
34
+ this.#decimal = this.#decimalToFixedDecimal(new Decimal(value.toString().trim()));
35
+ }
36
+
37
+ // Symbols
38
+ [Symbol.for('nodejs.util.inspect.custom')]() {
39
+ return this.value;
40
+ }
41
+
42
+ [Symbol.toPrimitive](hint: string) {
43
+ if (hint === 'number') return this.#decimal.toNumber();
44
+ return this.value;
45
+ }
46
+
47
+ // Private methods
48
+ #decimalToFixedDecimal(decimal: Decimal) {
49
+ return decimal.toDecimalPlaces(this.#decimalPlaces, this.#rounding);
50
+ }
51
+
52
+ // Public getters
53
+ get value() {
54
+ return this.#decimal.toFixed(this.#decimalPlaces, this.#rounding);
55
+ }
56
+
57
+ // Static methods
58
+ static toFixed(
59
+ value: PrecisionNumberValue,
60
+ decimalPlaces: number = 2,
61
+ rounding: Decimal.Rounding = Decimal.ROUND_DOWN,
62
+ ) {
63
+ return new Decimal(value.toString().trim()).toFixed(decimalPlaces, rounding);
64
+ }
65
+
66
+ // Public methods
67
+ absoluteValue() {
68
+ this.#decimal = this.#decimalToFixedDecimal(this.#decimal.absoluteValue());
69
+ return this;
70
+ }
71
+
72
+ dividedBy(value: PrecisionNumberValue) {
73
+ this.#decimal = this.#decimalToFixedDecimal(this.#decimal.dividedBy(value.toString().trim()));
74
+ return this;
75
+ }
76
+
77
+ equals(value: PrecisionNumberValue) {
78
+ return this.#decimal.equals(value.toString().trim());
79
+ }
80
+
81
+ gt(value: PrecisionNumberValue) {
82
+ return this.#decimal.gt(value.toString().trim());
83
+ }
84
+
85
+ gte(value: PrecisionNumberValue) {
86
+ return this.#decimal.gte(value.toString().trim());
87
+ }
88
+
89
+ isFinite() {
90
+ return this.#decimal.isFinite();
91
+ }
92
+
93
+ isInteger() {
94
+ return this.#decimal.isInteger();
95
+ }
96
+
97
+ isNaN() {
98
+ return this.#decimal.isNaN();
99
+ }
100
+
101
+ isNegative() {
102
+ return this.#decimal.isNegative();
103
+ }
104
+
105
+ isPositive() {
106
+ return this.#decimal.isPositive();
107
+ }
108
+
109
+ isZero() {
110
+ return this.#decimal.isZero();
111
+ }
112
+
113
+ lt(value: PrecisionNumberValue) {
114
+ return this.#decimal.lt(value.toString().trim());
115
+ }
116
+
117
+ lte(value: PrecisionNumberValue) {
118
+ return this.#decimal.lte(value.toString().trim());
119
+ }
120
+
121
+ minus(value: PrecisionNumberValue) {
122
+ this.#decimal = this.#decimalToFixedDecimal(this.#decimal.minus(value.toString().trim()));
123
+ return this;
124
+ }
125
+
126
+ negate() {
127
+ this.#decimal = this.#decimalToFixedDecimal(this.#decimal.negated());
128
+ return this;
129
+ }
130
+
131
+ plus(value: PrecisionNumberValue) {
132
+ this.#decimal = this.#decimalToFixedDecimal(this.#decimal.plus(value.toString().trim()));
133
+ return this;
134
+ }
135
+
136
+ times(value: PrecisionNumberValue) {
137
+ this.#decimal = this.#decimalToFixedDecimal(this.#decimal.times(value.toString().trim()));
138
+ return this;
139
+ }
140
+
141
+ toAbsoluteValue() {
142
+ return new PrecisionNumber(this.#decimal.absoluteValue(), this.#decimalPlaces, this.#rounding);
143
+ }
144
+
145
+ toDividedBy(value: PrecisionNumberValue) {
146
+ return new PrecisionNumber(
147
+ this.#decimal.dividedBy(value.toString().trim()),
148
+ this.#decimalPlaces,
149
+ this.#rounding,
150
+ );
151
+ }
152
+
153
+ toJSON() {
154
+ return this.value;
155
+ }
156
+
157
+ toMinus(value: PrecisionNumberValue) {
158
+ return new PrecisionNumber(this.#decimal.minus(value.toString().trim()), this.#decimalPlaces, this.#rounding);
159
+ }
160
+
161
+ toNegated() {
162
+ return new PrecisionNumber(this.#decimal.negated(), this.#decimalPlaces, this.#rounding);
163
+ }
164
+
165
+ toPlus(value: PrecisionNumberValue) {
166
+ return new PrecisionNumber(this.#decimal.plus(value.toString().trim()), this.#decimalPlaces, this.#rounding);
167
+ }
168
+
169
+ toString() {
170
+ return this.value;
171
+ }
172
+
173
+ toFixed(decimalPlaces: number = this.#decimalPlaces, rounding: Decimal.Rounding = this.#rounding) {
174
+ return this.#decimal.toFixed(decimalPlaces, rounding);
175
+ }
176
+
177
+ toTimes(value: PrecisionNumberValue) {
178
+ return new PrecisionNumber(this.#decimal.times(value.toString().trim()), this.#decimalPlaces, this.#rounding);
179
+ }
180
+ }
@@ -0,0 +1,174 @@
1
+ import { createConsola } from 'consola';
2
+ import type { ConsolaInstance } from 'consola';
3
+ import { NodeSSH } from 'node-ssh';
4
+ import type {
5
+ Config,
6
+ SSHExecCommandOptions,
7
+ SSHGetPutDirectoryOptions,
8
+ SSHPutFilesOptions,
9
+ } from 'node-ssh';
10
+ import type {
11
+ SFTPWrapper,
12
+ TransferOptions,
13
+ } from 'ssh2';
14
+
15
+ import type { Nullable } from '../types';
16
+
17
+ import type { PathLike } from './path';
18
+
19
+ const loggerLevelStringToConsolaLogLevelMap = {
20
+ debug: 4,
21
+ error: 0,
22
+ fatal: 0,
23
+ info: 3,
24
+ normal: 2,
25
+ silent: -999,
26
+ trace: 5,
27
+ verbose: 999,
28
+ warn: 1,
29
+ } as const;
30
+
31
+ export class SshClient {
32
+ readonly #connectConfig: Config;
33
+ readonly #logger: ConsolaInstance;
34
+
35
+ #nodeSsh: NodeSSH;
36
+
37
+ constructor(host: string, username: string, password: string, port: number = 22, connectConfig?: Config) {
38
+ this.#connectConfig = {
39
+ ...connectConfig,
40
+ host,
41
+ password,
42
+ port,
43
+ username,
44
+ };
45
+
46
+ this.#logger = createConsola();
47
+ this.#nodeSsh = new NodeSSH();
48
+ if (process.env.NODE_ENV === 'production') this.setLoggerLevel('error');
49
+ }
50
+
51
+ get nodeSsh() {
52
+ return this.#nodeSsh;
53
+ }
54
+
55
+ async connect() {
56
+ try {
57
+ this.#nodeSsh = await this.#nodeSsh.connect(this.#connectConfig);
58
+ return true;
59
+ } catch (error) {
60
+ this.#logger.error(error);
61
+ return false;
62
+ }
63
+ }
64
+
65
+ disconnect() {
66
+ try {
67
+ this.#nodeSsh.dispose();
68
+ return true;
69
+ } catch (error) {
70
+ this.#logger.error(error);
71
+ return false;
72
+ }
73
+ }
74
+
75
+ execCommand(command: string, options?: SSHExecCommandOptions) {
76
+ return this.#nodeSsh.execCommand(command, options).catch(() => {});
77
+ }
78
+
79
+ execCommandWithIo(command: string, options?: SSHExecCommandOptions) {
80
+ return this.execCommand(
81
+ command,
82
+ {
83
+ ...options,
84
+ onStderr: (data) => process.stderr.write(data.toString()),
85
+ onStdout: (data) => process.stdout.write(data.toString()),
86
+ },
87
+ );
88
+ }
89
+
90
+ getDir = this.getDirectory;
91
+
92
+ async getDirectory(localDirectory: PathLike, remoteDirectory: PathLike, options?: SSHGetPutDirectoryOptions) {
93
+ try {
94
+ return await this.#nodeSsh.getDirectory(localDirectory.toString(), remoteDirectory.toString(), options);
95
+ } catch (error) {
96
+ this.#logger.error(error);
97
+ return false;
98
+ }
99
+ }
100
+
101
+ async getFile(
102
+ localFile: PathLike,
103
+ remoteFile: PathLike,
104
+ givenSftp?: Nullable<SFTPWrapper>,
105
+ transferOptions?: Nullable<TransferOptions>,
106
+ ) {
107
+ try {
108
+ await this.#nodeSsh.getFile(localFile.toString(), remoteFile.toString(), givenSftp, transferOptions);
109
+ return true;
110
+ } catch (error) {
111
+ this.#logger.error(error);
112
+ return false;
113
+ }
114
+ }
115
+
116
+ isConnected() {
117
+ return this.#nodeSsh.isConnected();
118
+ }
119
+
120
+ async mkdir(path: PathLike) {
121
+ try {
122
+ await this.#nodeSsh.mkdir(path.toString());
123
+ return true;
124
+ } catch (error) {
125
+ this.#logger.error(error);
126
+ return false;
127
+ }
128
+ }
129
+
130
+ putDir = this.putDirectory;
131
+
132
+ async putDirectory(localDirectory: PathLike, remoteDirectory: PathLike, options?: SSHGetPutDirectoryOptions) {
133
+ try {
134
+ return await this.#nodeSsh.putDirectory(localDirectory.toString(), remoteDirectory.toString(), options);
135
+ } catch (error) {
136
+ this.#logger.error(error);
137
+ return false;
138
+ }
139
+ }
140
+
141
+ async putFile(
142
+ localFile: PathLike,
143
+ remoteFile: PathLike,
144
+ givenSftp?: Nullable<SFTPWrapper>,
145
+ transferOptions?: Nullable<TransferOptions>,
146
+ ) {
147
+ try {
148
+ await this.#nodeSsh.putFile(localFile.toString(), remoteFile.toString(), givenSftp, transferOptions);
149
+ return true;
150
+ } catch (error) {
151
+ this.#logger.error(error);
152
+ return false;
153
+ }
154
+ }
155
+
156
+ async putFiles(files: { local: PathLike; remote: PathLike }[], options?: SSHPutFilesOptions) {
157
+ try {
158
+ const convertedFiles = files.map(({ local, remote }) => ({
159
+ local: local.toString(),
160
+ remote: remote.toString(),
161
+ }));
162
+
163
+ await this.#nodeSsh.putFiles(convertedFiles, options);
164
+ return true;
165
+ } catch (error) {
166
+ this.#logger.error(error);
167
+ return false;
168
+ }
169
+ }
170
+
171
+ setLoggerLevel(level: keyof typeof loggerLevelStringToConsolaLogLevelMap) {
172
+ this.#logger.level = loggerLevelStringToConsolaLogLevelMap[level];
173
+ }
174
+ }
@@ -0,0 +1,46 @@
1
+ import type { ObjectId } from 'bson';
2
+
3
+ /**
4
+ * The following types are based on or inspired by types from the Element Plus project.
5
+ * Source: https://github.com/element-plus/element-plus
6
+ * License: MIT (https://opensource.org/licenses/MIT)
7
+ *
8
+ * Original types might have been modified to suit this project’s needs.
9
+ */
10
+
11
+ /***/
12
+ type ConditionalPath<K extends number | string, V, U> = V extends U ? `${K}` : DefaultPath<K, V, U, never>;
13
+ type DefaultPath<
14
+ K extends number | string,
15
+ V,
16
+ U = never,
17
+ RK = `${K}`,
18
+ > = V extends TerminalType ? RK : `${K}.${FilteredKeyPath<V, U>}`;
19
+
20
+ /**
21
+ * A utility type that generates a union of key paths from a given object type `T`,
22
+ * filtered by a specific type `U`. If `U` is
23
+ * not provided, it defaults to including all key paths.
24
+ *
25
+ * @template T - The object type to traverse.
26
+ * @template U - The type used to filter key paths. Defaults to `never`, meaning no filtering.
27
+ */
28
+ // eslint-disable-next-line style/max-len
29
+ export type FilteredKeyPath<T, U = never> = T extends ReadonlyArray<infer V> ? (IsTuple<T> extends true ? { [K in TupleKey<T>]-?: PathImpl<Exclude<K, symbol>, T[K], U> }[TupleKey<T>] : PathImpl<number, V, U>) : { [K in keyof T]-?: PathImpl<Exclude<K, symbol>, T[K], U> }[keyof T];
30
+ type IsTuple<T extends ReadonlyArray<any>> = number extends T['length'] ? false : true;
31
+ type PathImpl<K extends number | string, V, U> = [U] extends [never] ? DefaultPath<K, V> : ConditionalPath<K, V, U>;
32
+ type TerminalType =
33
+ | bigint
34
+ | Blob
35
+ | boolean
36
+ | Date
37
+ | File
38
+ | null
39
+ | number
40
+ | ObjectId
41
+ | RegExp
42
+ | string
43
+ | symbol
44
+ | undefined;
45
+
46
+ type TupleKey<T extends ReadonlyArray<any>> = Exclude<keyof T, keyof any[]>;
@@ -0,0 +1,18 @@
1
+ import type {
2
+ Buffer,
3
+ Blob as NodeBlob,
4
+ File as NodeFile,
5
+ } from 'node:buffer';
6
+
7
+ export type { FilteredKeyPath } from './filtered-key-path';
8
+
9
+ export type AnyRecord = Record<string, any>;
10
+ export type BinaryInput = Blob | Buffer | File | NodeBlob | NodeFile;
11
+ export type Booleanish = 'false' | 'true' | boolean;
12
+ export type MaybePartial<T> = Partial<T> | T;
13
+ export type MaybeReadonly<T> = Readonly<T> | T;
14
+ export type Nullable<T> = null | T;
15
+ export type Numberish = number | string;
16
+ export type PartialRecord<K extends keyof any, T> = Partial<Record<K, T>>;
17
+ export type ReadonlyPartialRecord<K extends keyof any, T> = Readonly<PartialRecord<K, T>>;
18
+ export type ReadonlyRecord<K extends keyof any, T> = Readonly<Record<K, T>>;
@@ -0,0 +1,18 @@
1
+ import type { GlobalComponents } from 'vue';
2
+
3
+ import type { Nullable } from './';
4
+
5
+ /**
6
+ * A type that represents a reference to a Vue component instance.
7
+ * The reference can be either an instance of the specified component or null.
8
+ *
9
+ * @template K - The key of the component in the GlobalComponents.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import type { ComponentRef } from '@kikiutils/types/vue';
14
+ *
15
+ * const keepAliveRef = ref<ComponentRef<'KeepAlive'>>(null);
16
+ * ```
17
+ */
18
+ export type ComponentRef<K extends keyof GlobalComponents> = Nullable<InstanceType<GlobalComponents[K]>>;
package/src/vue.ts CHANGED
@@ -5,7 +5,8 @@ import {
5
5
  useRoute,
6
6
  } from 'vue-router';
7
7
 
8
- import { appendRedirectParamToUrl } from '@/url';
8
+ import type { Nullable } from './types';
9
+ import { appendRedirectParamToUrl } from './url';
9
10
 
10
11
  /**
11
12
  * Appends the current Vue Router route's fullPath as the `redirect` query parameter to the given URL.
@@ -21,9 +22,9 @@ export function appendRedirectParamFromCurrentRouteToUrl(url: string) {
21
22
  /**
22
23
  * Clears an interval referenced by a Vue ref and sets it to null.
23
24
  *
24
- * @param {Ref<null | ReturnType<typeof setInterval>>} intervalRef - A Vue ref holding a NodeJS.Timeout or null
25
+ * @param {Ref<Nullable<ReturnType<typeof setInterval>>>} intervalRef - A Vue ref holding a NodeJS.Timeout or null
25
26
  */
26
- export function clearIntervalRef(intervalRef: Ref<null | ReturnType<typeof setInterval>>) {
27
+ export function clearIntervalRef(intervalRef: Ref<Nullable<ReturnType<typeof setInterval>>>) {
27
28
  if (intervalRef.value) clearInterval(intervalRef.value);
28
29
  intervalRef.value = null;
29
30
  }
@@ -31,9 +32,9 @@ export function clearIntervalRef(intervalRef: Ref<null | ReturnType<typeof setIn
31
32
  /**
32
33
  * Clears a timeout referenced by a Vue ref and sets it to null.
33
34
  *
34
- * @param {Ref<null | ReturnType<typeof setTimeout>>} timeoutRef - A Vue ref holding a NodeJS.Timeout or null
35
+ * @param {Ref<Nullable<ReturnType<typeof setTimeout>>>} timeoutRef - A Vue ref holding a NodeJS.Timeout or null
35
36
  */
36
- export function clearTimeoutRef(timeoutRef: Ref<null | ReturnType<typeof setTimeout>>) {
37
+ export function clearTimeoutRef(timeoutRef: Ref<Nullable<ReturnType<typeof setTimeout>>>) {
37
38
  if (timeoutRef.value) clearTimeout(timeoutRef.value);
38
39
  timeoutRef.value = null;
39
40
  }
@@ -44,9 +45,9 @@ export function clearTimeoutRef(timeoutRef: Ref<null | ReturnType<typeof setTime
44
45
  *
45
46
  * @template T - The type of the scrollable element (defaults to HTMLElement)
46
47
  *
47
- * @param {Ref<null | T>} containerRef - A ref to the scrollable HTML element
48
+ * @param {Ref<Nullable<T>>} containerRef - A ref to the scrollable HTML element
48
49
  */
49
- export function usePreserveScroll<T extends Element = HTMLElement>(containerRef: Ref<null | T>) {
50
+ export function usePreserveScroll<T extends Element = HTMLElement>(containerRef: Ref<Nullable<T>>) {
50
51
  let scrollLeft = 0;
51
52
  let scrollTop = 0;
52
53
  onActivated(() => {
package/src/web.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { appendRedirectParamToUrl } from '@/url';
1
+ import { appendRedirectParamToUrl } from './url';
2
2
 
3
3
  /**
4
4
  * Appends the current browser URL (including path, query, and hash) as the `redirect` query parameter to the given URL.