@zairakai/js-utils 1.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.
- package/LICENSE +21 -0
- package/README.md +270 -0
- package/dist/arrays.cjs +210 -0
- package/dist/arrays.d.cts +119 -0
- package/dist/arrays.d.ts +119 -0
- package/dist/arrays.js +32 -0
- package/dist/chunk-27YHP2CK.js +407 -0
- package/dist/chunk-3WNRYKPG.js +37 -0
- package/dist/chunk-42CHLXT7.js +214 -0
- package/dist/chunk-6F4PWJZI.js +0 -0
- package/dist/chunk-7SXRFZBB.js +173 -0
- package/dist/chunk-F6RSTW65.js +156 -0
- package/dist/chunk-G7ZJ23DW.js +253 -0
- package/dist/chunk-IPP7PA6H.js +136 -0
- package/dist/chunk-LDSWHSRX.js +96 -0
- package/dist/chunk-TY75OOIQ.js +700 -0
- package/dist/chunk-W6JEMFAF.js +54 -0
- package/dist/chunk-XEJLBAXE.js +164 -0
- package/dist/chunk-Z7G3SIQH.js +270 -0
- package/dist/chunk-ZJPKS2MQ.js +101 -0
- package/dist/collections.cjs +797 -0
- package/dist/collections.d.cts +353 -0
- package/dist/collections.d.ts +353 -0
- package/dist/collections.js +17 -0
- package/dist/datetime.cjs +80 -0
- package/dist/datetime.d.cts +75 -0
- package/dist/datetime.d.ts +75 -0
- package/dist/datetime.js +24 -0
- package/dist/equals.cjs +121 -0
- package/dist/equals.d.cts +24 -0
- package/dist/equals.d.ts +24 -0
- package/dist/equals.js +8 -0
- package/dist/formatters.cjs +201 -0
- package/dist/formatters.d.cts +180 -0
- package/dist/formatters.d.ts +180 -0
- package/dist/formatters.js +48 -0
- package/dist/index.cjs +2906 -0
- package/dist/index.d.cts +120 -0
- package/dist/index.d.ts +120 -0
- package/dist/index.js +348 -0
- package/dist/number.cjs +279 -0
- package/dist/number.d.cts +177 -0
- package/dist/number.d.ts +177 -0
- package/dist/number.js +10 -0
- package/dist/obj.cjs +427 -0
- package/dist/obj.d.cts +177 -0
- package/dist/obj.d.ts +177 -0
- package/dist/obj.js +12 -0
- package/dist/php-arrays.cjs +954 -0
- package/dist/php-arrays.d.cts +256 -0
- package/dist/php-arrays.d.ts +256 -0
- package/dist/php-arrays.js +70 -0
- package/dist/runtime.cjs +134 -0
- package/dist/runtime.d.cts +90 -0
- package/dist/runtime.d.ts +90 -0
- package/dist/runtime.js +24 -0
- package/dist/schemas.cjs +86 -0
- package/dist/schemas.d.cts +108 -0
- package/dist/schemas.d.ts +108 -0
- package/dist/schemas.js +22 -0
- package/dist/str.cjs +499 -0
- package/dist/str.d.cts +282 -0
- package/dist/str.d.ts +282 -0
- package/dist/str.js +11 -0
- package/dist/types.cjs +18 -0
- package/dist/types.d.cts +13 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.js +1 -0
- package/dist/validator.cjs +251 -0
- package/dist/validator.d.cts +99 -0
- package/dist/validator.d.ts +99 -0
- package/dist/validator.js +11 -0
- package/dist/validators.cjs +217 -0
- package/dist/validators.d.cts +216 -0
- package/dist/validators.d.ts +216 -0
- package/dist/validators.js +64 -0
- package/package.json +180 -0
- package/src/arrays.ts +316 -0
- package/src/collections.ts +866 -0
- package/src/datetime.ts +103 -0
- package/src/equals.ts +134 -0
- package/src/formatters.ts +342 -0
- package/src/index.ts +36 -0
- package/src/number.ts +281 -0
- package/src/obj.ts +303 -0
- package/src/php-arrays.ts +445 -0
- package/src/pipe.ts +29 -0
- package/src/runtime.ts +194 -0
- package/src/schemas.ts +136 -0
- package/src/str.ts +438 -0
- package/src/types.ts +13 -0
- package/src/validator.ts +157 -0
- package/src/validators.ts +359 -0
package/src/number.ts
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fluent number manipulation class inspired by Laravel's Number and modern utilities.
|
|
3
|
+
*/
|
|
4
|
+
export class Numberable {
|
|
5
|
+
protected value: number
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create a new Numberable instance.
|
|
9
|
+
*
|
|
10
|
+
* @param {unknown} value The initial value
|
|
11
|
+
*/
|
|
12
|
+
constructor(value: unknown) {
|
|
13
|
+
this.value = Number(value ?? 0)
|
|
14
|
+
if (isNaN(this.value)) {
|
|
15
|
+
this.value = 0
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the raw number value.
|
|
21
|
+
*
|
|
22
|
+
* @returns {number} The raw value
|
|
23
|
+
*/
|
|
24
|
+
toNumber(): number {
|
|
25
|
+
return this.value
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Alias for toNumber().
|
|
30
|
+
*
|
|
31
|
+
* @returns {number} The raw value
|
|
32
|
+
*/
|
|
33
|
+
get(): number {
|
|
34
|
+
return this.value
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Format the number with locale-specific formatting.
|
|
39
|
+
*
|
|
40
|
+
* @param {number} [decimals=0] The number of decimal points
|
|
41
|
+
* @param {string} [locale='en-US'] The locale to use for formatting
|
|
42
|
+
* @returns {string} The formatted number
|
|
43
|
+
*/
|
|
44
|
+
format(decimals: number = 0, locale: string = 'en-US'): string {
|
|
45
|
+
return new Intl.NumberFormat(locale, {
|
|
46
|
+
minimumFractionDigits: decimals,
|
|
47
|
+
maximumFractionDigits: decimals,
|
|
48
|
+
}).format(this.value)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Format the number as currency.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} [currency='USD'] The currency code (e.g., 'USD', 'EUR')
|
|
55
|
+
* @param {string} [locale='en-US'] The locale to use for formatting
|
|
56
|
+
* @returns {string} The formatted currency string
|
|
57
|
+
*/
|
|
58
|
+
currency(currency: string = 'USD', locale: string = 'en-US'): string {
|
|
59
|
+
return new Intl.NumberFormat(locale, {
|
|
60
|
+
style: 'currency',
|
|
61
|
+
currency,
|
|
62
|
+
}).format(this.value)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Format the number as a percentage.
|
|
67
|
+
*
|
|
68
|
+
* @param {number} [decimals=0] The number of decimal points
|
|
69
|
+
* @param {string} [locale='en-US'] The locale to use for formatting
|
|
70
|
+
* @returns {string} The formatted percentage string
|
|
71
|
+
*/
|
|
72
|
+
percentage(decimals: number = 0, locale: string = 'en-US'): string {
|
|
73
|
+
return new Intl.NumberFormat(locale, {
|
|
74
|
+
style: 'percent',
|
|
75
|
+
minimumFractionDigits: decimals,
|
|
76
|
+
maximumFractionDigits: decimals,
|
|
77
|
+
}).format(this.value / 100)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Format the number as a human-readable file size.
|
|
82
|
+
*
|
|
83
|
+
* @param {number} [precision=2] The number of decimal points
|
|
84
|
+
* @returns {string} The formatted file size (e.g., "1.50 MB")
|
|
85
|
+
*/
|
|
86
|
+
fileSize(precision: number = 2): string {
|
|
87
|
+
if (0 === this.value) {
|
|
88
|
+
return '0 B'
|
|
89
|
+
}
|
|
90
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
91
|
+
const i = Math.floor(Math.log(this.value) / Math.log(1024))
|
|
92
|
+
return `${parseFloat((this.value / Math.pow(1024, i)).toFixed(precision))} ${units[i]}`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Abbreviate the number (e.g., 1K, 1M, 1B).
|
|
97
|
+
*
|
|
98
|
+
* @param {number} [precision=1] The number of decimal points
|
|
99
|
+
* @returns {string} The abbreviated number
|
|
100
|
+
*/
|
|
101
|
+
abbreviate(precision: number = 1): string {
|
|
102
|
+
if (1000 > this.value) {
|
|
103
|
+
return String(this.value)
|
|
104
|
+
}
|
|
105
|
+
const units = ['', 'K', 'M', 'B', 'T']
|
|
106
|
+
const i = Math.floor(Math.log10(this.value) / 3)
|
|
107
|
+
return `${parseFloat((this.value / Math.pow(1000, i)).toFixed(precision))}${units[i]}`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Add an ordinal suffix to the number (e.g., 1st, 2nd, 3rd).
|
|
112
|
+
*
|
|
113
|
+
* @returns {string} The number with its ordinal suffix
|
|
114
|
+
*/
|
|
115
|
+
ordinal(): string {
|
|
116
|
+
const s = ['th', 'st', 'nd', 'rd']
|
|
117
|
+
const v = this.value % 100
|
|
118
|
+
return this.value + (s[(v - 20) % 10] || s[v] || s[0])
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Clamp the number between a minimum and maximum value.
|
|
123
|
+
*
|
|
124
|
+
* @param {number} min The minimum value
|
|
125
|
+
* @param {number} max The maximum value
|
|
126
|
+
* @returns {this} The Numberable instance
|
|
127
|
+
*/
|
|
128
|
+
clamp(min: number, max: number): this {
|
|
129
|
+
this.value = Math.min(Math.max(this.value, min), max)
|
|
130
|
+
return this
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Check if the number is between a minimum and maximum value.
|
|
135
|
+
*
|
|
136
|
+
* @param {number} min The minimum value
|
|
137
|
+
* @param {number} max The maximum value
|
|
138
|
+
* @param {boolean} [inclusive=true] Whether to include the boundaries
|
|
139
|
+
* @returns {boolean} True if the number is between min and max
|
|
140
|
+
*/
|
|
141
|
+
isBetween(min: number, max: number, inclusive: boolean = true): boolean {
|
|
142
|
+
return inclusive ? this.value >= min && this.value <= max : this.value > min && this.value < max
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Add a value to the current number.
|
|
147
|
+
*
|
|
148
|
+
* @param {number} value The value to add
|
|
149
|
+
* @returns {this} The Numberable instance
|
|
150
|
+
*/
|
|
151
|
+
add(value: number): this {
|
|
152
|
+
this.value += value
|
|
153
|
+
return this
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Subtract a value from the current number.
|
|
158
|
+
*
|
|
159
|
+
* @param {number} value The value to subtract
|
|
160
|
+
* @returns {this} The Numberable instance
|
|
161
|
+
*/
|
|
162
|
+
sub(value: number): this {
|
|
163
|
+
this.value -= value
|
|
164
|
+
return this
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Multiply the current number by a value.
|
|
169
|
+
*
|
|
170
|
+
* @param {number} value The value to multiply by
|
|
171
|
+
* @returns {this} The Numberable instance
|
|
172
|
+
*/
|
|
173
|
+
mul(value: number): this {
|
|
174
|
+
this.value *= value
|
|
175
|
+
return this
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Divide the current number by a value.
|
|
180
|
+
*
|
|
181
|
+
* @param {number} value The value to divide by
|
|
182
|
+
* @returns {this} The Numberable instance
|
|
183
|
+
*/
|
|
184
|
+
div(value: number): this {
|
|
185
|
+
if (0 !== value) {
|
|
186
|
+
this.value /= value
|
|
187
|
+
}
|
|
188
|
+
return this
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Round the number to a specified precision.
|
|
193
|
+
*
|
|
194
|
+
* @param {number} [precision=0] The number of decimal points
|
|
195
|
+
* @returns {this} The Numberable instance
|
|
196
|
+
*/
|
|
197
|
+
round(precision: number = 0): this {
|
|
198
|
+
const factor = Math.pow(10, precision)
|
|
199
|
+
this.value = Math.round(this.value * factor) / factor
|
|
200
|
+
return this
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Round the number up to a specified precision.
|
|
205
|
+
*
|
|
206
|
+
* @param {number} [precision=0] The number of decimal points
|
|
207
|
+
* @returns {this} The Numberable instance
|
|
208
|
+
*/
|
|
209
|
+
ceil(precision: number = 0): this {
|
|
210
|
+
const factor = Math.pow(10, precision)
|
|
211
|
+
this.value = Math.ceil(this.value * factor) / factor
|
|
212
|
+
return this
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Round the number down to a specified precision.
|
|
217
|
+
*
|
|
218
|
+
* @param {number} [precision=0] The number of decimal points
|
|
219
|
+
* @returns {this} The Numberable instance
|
|
220
|
+
*/
|
|
221
|
+
floor(precision: number = 0): this {
|
|
222
|
+
const factor = Math.pow(10, precision)
|
|
223
|
+
this.value = Math.floor(this.value * factor) / factor
|
|
224
|
+
return this
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Pipe the current Numberable instance to a callback.
|
|
229
|
+
*
|
|
230
|
+
* @param {Function} callback The callback to execute
|
|
231
|
+
* @returns {U} The result of the callback
|
|
232
|
+
*/
|
|
233
|
+
pipe<U>(callback: (num: this) => U): U {
|
|
234
|
+
return callback(this)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Execute a callback if a condition is met.
|
|
239
|
+
*
|
|
240
|
+
* @param {boolean | Function} condition The condition to check
|
|
241
|
+
* @param {Function} callback The callback to execute if condition is true
|
|
242
|
+
* @returns {this} The Numberable instance
|
|
243
|
+
*/
|
|
244
|
+
when(condition: boolean | (() => boolean), callback: (num: this) => void): this {
|
|
245
|
+
const shouldExecute = 'function' === typeof condition ? condition() : condition
|
|
246
|
+
if (shouldExecute) {
|
|
247
|
+
callback(this)
|
|
248
|
+
}
|
|
249
|
+
return this
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Create a new fluent Numberable instance.
|
|
255
|
+
*
|
|
256
|
+
* @param {unknown} [value] The initial value
|
|
257
|
+
* @returns {Numberable} A new Numberable instance
|
|
258
|
+
*/
|
|
259
|
+
export const num = (value?: unknown): Numberable => {
|
|
260
|
+
return new Numberable(value)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Static methods for number manipulation.
|
|
265
|
+
*/
|
|
266
|
+
export const NumberHelper = {
|
|
267
|
+
/** Create a new Numberable instance */
|
|
268
|
+
of: (value: unknown) => new Numberable(value),
|
|
269
|
+
/** Format the number with locale-specific formatting */
|
|
270
|
+
format: (value: number, decimals = 0, locale = 'en-US') => new Numberable(value).format(decimals, locale),
|
|
271
|
+
/** Format the number as currency */
|
|
272
|
+
currency: (value: number, currency = 'USD', locale = 'en-US') => new Numberable(value).currency(currency, locale),
|
|
273
|
+
/** Format the number as a percentage */
|
|
274
|
+
percentage: (value: number, decimals = 0, locale = 'en-US') => new Numberable(value).percentage(decimals, locale),
|
|
275
|
+
/** Format the number as a human-readable file size */
|
|
276
|
+
fileSize: (value: number, precision = 2) => new Numberable(value).fileSize(precision),
|
|
277
|
+
/** Abbreviate the number */
|
|
278
|
+
abbreviate: (value: number, precision = 1) => new Numberable(value).abbreviate(precision),
|
|
279
|
+
/** Add an ordinal suffix to the number */
|
|
280
|
+
ordinal: (value: number) => new Numberable(value).ordinal(),
|
|
281
|
+
}
|
package/src/obj.ts
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { diff, isEqual } from './equals.js'
|
|
2
|
+
import { data_get, data_set } from './runtime.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fluent object manipulation class with state tracking.
|
|
6
|
+
*/
|
|
7
|
+
export class Objectable<T extends Record<string, unknown>> {
|
|
8
|
+
protected value: T
|
|
9
|
+
protected originalValue: T
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create a new Objectable instance.
|
|
13
|
+
*
|
|
14
|
+
* @param {T} value The initial object value
|
|
15
|
+
*/
|
|
16
|
+
constructor(value: T) {
|
|
17
|
+
this.value = structuredClone(value)
|
|
18
|
+
this.originalValue = structuredClone(value)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the current object state.
|
|
23
|
+
*
|
|
24
|
+
* @returns {T} The current object
|
|
25
|
+
*/
|
|
26
|
+
toObject(): T {
|
|
27
|
+
return this.value
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Alias for toObject().
|
|
32
|
+
*
|
|
33
|
+
* @returns {T} The current object
|
|
34
|
+
*/
|
|
35
|
+
get(): T {
|
|
36
|
+
return this.value
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get the original state from construction.
|
|
41
|
+
*
|
|
42
|
+
* @returns {T} The original object
|
|
43
|
+
*/
|
|
44
|
+
getOriginal(): T {
|
|
45
|
+
return this.originalValue
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Sync original state with current state (marking it as clean).
|
|
50
|
+
*
|
|
51
|
+
* @returns {this} The Objectable instance
|
|
52
|
+
*/
|
|
53
|
+
syncOriginal(): this {
|
|
54
|
+
this.originalValue = structuredClone(this.value)
|
|
55
|
+
return this
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Determine if any attribute has been modified.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} [key] Optional key to check specifically
|
|
62
|
+
* @returns {boolean} True if dirty, false otherwise
|
|
63
|
+
*/
|
|
64
|
+
isDirty(key?: string): boolean {
|
|
65
|
+
if (key) {
|
|
66
|
+
return !isEqual(data_get(this.originalValue, key), data_get(this.value, key))
|
|
67
|
+
}
|
|
68
|
+
return !isEqual(this.originalValue, this.value)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Determine if the object is equivalent to its original state.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} [key] Optional key to check specifically
|
|
75
|
+
* @returns {boolean} True if clean, false otherwise
|
|
76
|
+
*/
|
|
77
|
+
isClean(key?: string): boolean {
|
|
78
|
+
return !this.isDirty(key)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get the attributes that have been modified.
|
|
83
|
+
*
|
|
84
|
+
* @returns {Partial<T>} The dirty attributes
|
|
85
|
+
*/
|
|
86
|
+
getDirty(): Partial<T> {
|
|
87
|
+
return diff(this.originalValue, this.value) as Partial<T>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get a value by dot notation.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} key The key to retrieve
|
|
94
|
+
* @param {unknown} [defaultValue=null] The default value if not found
|
|
95
|
+
* @returns {unknown} The retrieved value
|
|
96
|
+
*/
|
|
97
|
+
dataGet(key: string, defaultValue: unknown = null): unknown {
|
|
98
|
+
return data_get(this.value, key, defaultValue)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Set a value by dot notation.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} key The key to set
|
|
105
|
+
* @param {unknown} value The value to set
|
|
106
|
+
* @returns {this} The Objectable instance
|
|
107
|
+
*/
|
|
108
|
+
dataSet(key: string, value: unknown): this {
|
|
109
|
+
data_set(this.value, key, value)
|
|
110
|
+
return this
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get only specified keys.
|
|
115
|
+
*
|
|
116
|
+
* @param {K[]} keys The keys to include
|
|
117
|
+
* @returns {Objectable<Pick<T, K>>} A new Objectable instance
|
|
118
|
+
*/
|
|
119
|
+
only<K extends keyof T>(keys: K[]): Objectable<Pick<T, K>> {
|
|
120
|
+
const result: Record<string, unknown> = {}
|
|
121
|
+
keys.forEach((key) => {
|
|
122
|
+
const k = key as string
|
|
123
|
+
if (k in this.value) {
|
|
124
|
+
result[k] = this.value[k]
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
return new Objectable(result as Pick<T, K>)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get all keys except specified ones.
|
|
132
|
+
*
|
|
133
|
+
* @param {K[]} keys The keys to exclude
|
|
134
|
+
* @returns {Objectable<Omit<T, K>>} A new Objectable instance
|
|
135
|
+
*/
|
|
136
|
+
except<K extends keyof T>(keys: K[]): Objectable<Omit<T, K>> {
|
|
137
|
+
const result: Record<string, unknown> = { ...this.value }
|
|
138
|
+
keys.forEach((key) => {
|
|
139
|
+
const k = key as string
|
|
140
|
+
delete result[k]
|
|
141
|
+
})
|
|
142
|
+
return new Objectable(result as Omit<T, K>)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Map over object entries.
|
|
147
|
+
*
|
|
148
|
+
* @param {Function} callback The callback to execute
|
|
149
|
+
* @returns {Record<string, U>} The resulting object
|
|
150
|
+
*/
|
|
151
|
+
map<U>(callback: (value: T[keyof T], key: keyof T) => U): Record<string, U> {
|
|
152
|
+
const result: Record<string, U> = {}
|
|
153
|
+
for (const [key, val] of Object.entries(this.value)) {
|
|
154
|
+
result[key] = callback(val as T[keyof T], key as keyof T)
|
|
155
|
+
}
|
|
156
|
+
return result
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Filter object entries.
|
|
161
|
+
*
|
|
162
|
+
* @param {Function} callback The callback to execute
|
|
163
|
+
* @returns {this} The Objectable instance
|
|
164
|
+
*/
|
|
165
|
+
filter(callback: (value: T[keyof T], key: keyof T) => boolean): this {
|
|
166
|
+
const result: Record<string, unknown> = {}
|
|
167
|
+
for (const [key, val] of Object.entries(this.value)) {
|
|
168
|
+
if (callback(val as T[keyof T], key as keyof T)) {
|
|
169
|
+
result[key] = val
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
this.value = result as T
|
|
173
|
+
return this
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Merge with another object.
|
|
178
|
+
*
|
|
179
|
+
* @param {U} other The object to merge with
|
|
180
|
+
* @returns {Objectable<T & U>} A new Objectable instance
|
|
181
|
+
*/
|
|
182
|
+
merge<U extends Record<string, unknown>>(other: U): Objectable<T & U> {
|
|
183
|
+
return new Objectable({ ...this.value, ...other } as T & U)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Deep merge with another object (non-mutating on source).
|
|
188
|
+
*
|
|
189
|
+
* @param {Record<string, unknown>} other The object to deep merge with
|
|
190
|
+
* @returns {this} The Objectable instance
|
|
191
|
+
*/
|
|
192
|
+
mergeDeep(other: Record<string, unknown>): this {
|
|
193
|
+
const merge = (target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> => {
|
|
194
|
+
const result: Record<string, unknown> = { ...target }
|
|
195
|
+
for (const key of Object.keys(source)) {
|
|
196
|
+
const srcVal = source[key]
|
|
197
|
+
const tgtVal = target[key]
|
|
198
|
+
if (
|
|
199
|
+
srcVal !== null &&
|
|
200
|
+
'object' === typeof srcVal &&
|
|
201
|
+
!Array.isArray(srcVal) &&
|
|
202
|
+
tgtVal !== null &&
|
|
203
|
+
'object' === typeof tgtVal &&
|
|
204
|
+
!Array.isArray(tgtVal)
|
|
205
|
+
) {
|
|
206
|
+
result[key] = merge(tgtVal as Record<string, unknown>, srcVal as Record<string, unknown>)
|
|
207
|
+
} else {
|
|
208
|
+
result[key] = srcVal
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return result
|
|
212
|
+
}
|
|
213
|
+
this.value = merge(this.value, other) as T
|
|
214
|
+
return this
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Check if object has key.
|
|
219
|
+
*
|
|
220
|
+
* @param {string} key The key to check
|
|
221
|
+
* @returns {boolean} True if key exists
|
|
222
|
+
*/
|
|
223
|
+
has(key: string): boolean {
|
|
224
|
+
return key in this.value
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get keys.
|
|
229
|
+
*
|
|
230
|
+
* @returns {string[]} The object keys
|
|
231
|
+
*/
|
|
232
|
+
keys(): string[] {
|
|
233
|
+
return Object.keys(this.value)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Get values.
|
|
238
|
+
*
|
|
239
|
+
* @returns {unknown[]} The object values
|
|
240
|
+
*/
|
|
241
|
+
values(): unknown[] {
|
|
242
|
+
return Object.values(this.value)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Clone the object.
|
|
247
|
+
*
|
|
248
|
+
* @returns {Objectable<T>} A new cloned Objectable instance
|
|
249
|
+
*/
|
|
250
|
+
clone(): Objectable<T> {
|
|
251
|
+
return new Objectable(structuredClone(this.value))
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Pipe to a callback.
|
|
256
|
+
*
|
|
257
|
+
* @param {Function} callback The callback to execute
|
|
258
|
+
* @returns {U} The result of the callback
|
|
259
|
+
*/
|
|
260
|
+
pipe<U>(callback: (obj: this) => U): U {
|
|
261
|
+
return callback(this)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Execute callback if condition is met.
|
|
266
|
+
*
|
|
267
|
+
* @param {boolean | Function} condition The condition to check
|
|
268
|
+
* @param {Function} callback The callback to execute
|
|
269
|
+
* @returns {this} The Objectable instance
|
|
270
|
+
*/
|
|
271
|
+
when(condition: boolean | (() => boolean), callback: (obj: this) => void): this {
|
|
272
|
+
const shouldExecute = 'function' === typeof condition ? (condition as () => boolean)() : condition
|
|
273
|
+
if (shouldExecute) {
|
|
274
|
+
callback(this)
|
|
275
|
+
}
|
|
276
|
+
return this
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Create a new fluent Objectable instance.
|
|
282
|
+
*
|
|
283
|
+
* @param {T} value The initial value
|
|
284
|
+
* @returns {Objectable<T>} A new Objectable instance
|
|
285
|
+
*/
|
|
286
|
+
export const obj = <T extends Record<string, unknown>>(value: T): Objectable<T> => {
|
|
287
|
+
return new Objectable(value)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Static methods for object manipulation.
|
|
292
|
+
*/
|
|
293
|
+
export const Obj = {
|
|
294
|
+
/** Create a new Objectable instance */
|
|
295
|
+
of: <T extends Record<string, unknown>>(value: T) => new Objectable(value),
|
|
296
|
+
/** Get object keys */
|
|
297
|
+
keys: (value: Record<string, unknown>) => Object.keys(value),
|
|
298
|
+
/** Get object values */
|
|
299
|
+
values: (value: Record<string, unknown>) => Object.values(value),
|
|
300
|
+
/** Merge objects */
|
|
301
|
+
merge: (target: Record<string, unknown>, ...sources: Record<string, unknown>[]) =>
|
|
302
|
+
Object.assign({}, target, ...sources),
|
|
303
|
+
}
|