@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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +270 -0
  3. package/dist/arrays.cjs +210 -0
  4. package/dist/arrays.d.cts +119 -0
  5. package/dist/arrays.d.ts +119 -0
  6. package/dist/arrays.js +32 -0
  7. package/dist/chunk-27YHP2CK.js +407 -0
  8. package/dist/chunk-3WNRYKPG.js +37 -0
  9. package/dist/chunk-42CHLXT7.js +214 -0
  10. package/dist/chunk-6F4PWJZI.js +0 -0
  11. package/dist/chunk-7SXRFZBB.js +173 -0
  12. package/dist/chunk-F6RSTW65.js +156 -0
  13. package/dist/chunk-G7ZJ23DW.js +253 -0
  14. package/dist/chunk-IPP7PA6H.js +136 -0
  15. package/dist/chunk-LDSWHSRX.js +96 -0
  16. package/dist/chunk-TY75OOIQ.js +700 -0
  17. package/dist/chunk-W6JEMFAF.js +54 -0
  18. package/dist/chunk-XEJLBAXE.js +164 -0
  19. package/dist/chunk-Z7G3SIQH.js +270 -0
  20. package/dist/chunk-ZJPKS2MQ.js +101 -0
  21. package/dist/collections.cjs +797 -0
  22. package/dist/collections.d.cts +353 -0
  23. package/dist/collections.d.ts +353 -0
  24. package/dist/collections.js +17 -0
  25. package/dist/datetime.cjs +80 -0
  26. package/dist/datetime.d.cts +75 -0
  27. package/dist/datetime.d.ts +75 -0
  28. package/dist/datetime.js +24 -0
  29. package/dist/equals.cjs +121 -0
  30. package/dist/equals.d.cts +24 -0
  31. package/dist/equals.d.ts +24 -0
  32. package/dist/equals.js +8 -0
  33. package/dist/formatters.cjs +201 -0
  34. package/dist/formatters.d.cts +180 -0
  35. package/dist/formatters.d.ts +180 -0
  36. package/dist/formatters.js +48 -0
  37. package/dist/index.cjs +2906 -0
  38. package/dist/index.d.cts +120 -0
  39. package/dist/index.d.ts +120 -0
  40. package/dist/index.js +348 -0
  41. package/dist/number.cjs +279 -0
  42. package/dist/number.d.cts +177 -0
  43. package/dist/number.d.ts +177 -0
  44. package/dist/number.js +10 -0
  45. package/dist/obj.cjs +427 -0
  46. package/dist/obj.d.cts +177 -0
  47. package/dist/obj.d.ts +177 -0
  48. package/dist/obj.js +12 -0
  49. package/dist/php-arrays.cjs +954 -0
  50. package/dist/php-arrays.d.cts +256 -0
  51. package/dist/php-arrays.d.ts +256 -0
  52. package/dist/php-arrays.js +70 -0
  53. package/dist/runtime.cjs +134 -0
  54. package/dist/runtime.d.cts +90 -0
  55. package/dist/runtime.d.ts +90 -0
  56. package/dist/runtime.js +24 -0
  57. package/dist/schemas.cjs +86 -0
  58. package/dist/schemas.d.cts +108 -0
  59. package/dist/schemas.d.ts +108 -0
  60. package/dist/schemas.js +22 -0
  61. package/dist/str.cjs +499 -0
  62. package/dist/str.d.cts +282 -0
  63. package/dist/str.d.ts +282 -0
  64. package/dist/str.js +11 -0
  65. package/dist/types.cjs +18 -0
  66. package/dist/types.d.cts +13 -0
  67. package/dist/types.d.ts +13 -0
  68. package/dist/types.js +1 -0
  69. package/dist/validator.cjs +251 -0
  70. package/dist/validator.d.cts +99 -0
  71. package/dist/validator.d.ts +99 -0
  72. package/dist/validator.js +11 -0
  73. package/dist/validators.cjs +217 -0
  74. package/dist/validators.d.cts +216 -0
  75. package/dist/validators.d.ts +216 -0
  76. package/dist/validators.js +64 -0
  77. package/package.json +180 -0
  78. package/src/arrays.ts +316 -0
  79. package/src/collections.ts +866 -0
  80. package/src/datetime.ts +103 -0
  81. package/src/equals.ts +134 -0
  82. package/src/formatters.ts +342 -0
  83. package/src/index.ts +36 -0
  84. package/src/number.ts +281 -0
  85. package/src/obj.ts +303 -0
  86. package/src/php-arrays.ts +445 -0
  87. package/src/pipe.ts +29 -0
  88. package/src/runtime.ts +194 -0
  89. package/src/schemas.ts +136 -0
  90. package/src/str.ts +438 -0
  91. package/src/types.ts +13 -0
  92. package/src/validator.ts +157 -0
  93. 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
+ }