@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/schemas.ts ADDED
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Runtime type validation schemas using Zod
3
+ * Provides runtime type checking that persists after TypeScript compilation
4
+ */
5
+
6
+ import { z } from 'zod'
7
+
8
+ /**
9
+ * Email validation schema
10
+ */
11
+ export const EmailSchema = z.string().email('Invalid email format').min(1, 'Email is required')
12
+
13
+ /**
14
+ * Phone validation schema (E.164 format)
15
+ */
16
+ export const PhoneSchema = z.string().regex(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number format')
17
+
18
+ /**
19
+ * URL validation schema
20
+ */
21
+ export const UrlSchema = z.string().url('Invalid URL format')
22
+
23
+ /**
24
+ * Date validation schema (ISO datetime, YYYY-MM-DD, or Date object)
25
+ */
26
+ export const DateSchema = z.union([
27
+ z.string().datetime(),
28
+ z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format (YYYY-MM-DD)'),
29
+ z.date(),
30
+ ])
31
+
32
+ /**
33
+ * API Response schema factory
34
+ *
35
+ * @param {T} dataSchema The schema for the data property
36
+ * @returns {z.ZodObject} The API response schema
37
+ */
38
+ export const ApiResponseSchema = <T extends z.ZodTypeAny>(dataSchema: T) =>
39
+ z.object({
40
+ success: z.boolean(),
41
+ data: dataSchema.optional(),
42
+ message: z.string().optional(),
43
+ errors: z.array(z.string()).optional(),
44
+ })
45
+
46
+ /**
47
+ * Pagination schema
48
+ */
49
+ export const PaginationSchema = z.object({
50
+ page: z.number().int().positive().default(1),
51
+ perPage: z.number().int().min(1).max(100).default(15),
52
+ total: z.number().int().nonnegative(),
53
+ lastPage: z.number().int().positive(),
54
+ })
55
+
56
+ /**
57
+ * Paginated response schema factory
58
+ *
59
+ * @param {T} itemSchema The schema for individual items in the data array
60
+ * @returns {z.ZodObject} The paginated response schema
61
+ */
62
+ export const PaginatedResponseSchema = <T extends z.ZodTypeAny>(itemSchema: T) =>
63
+ z.object({
64
+ data: z.array(itemSchema),
65
+ pagination: PaginationSchema,
66
+ })
67
+
68
+ /**
69
+ * Type inferred from EmailSchema
70
+ */
71
+ export type Email = z.infer<typeof EmailSchema>
72
+
73
+ /**
74
+ * Type inferred from PhoneSchema
75
+ */
76
+ export type Phone = z.infer<typeof PhoneSchema>
77
+
78
+ /**
79
+ * Type inferred from UrlSchema
80
+ */
81
+ export type Url = z.infer<typeof UrlSchema>
82
+
83
+ /**
84
+ * Type inferred from ApiResponseSchema
85
+ */
86
+ export type ApiResponse<T> = z.infer<ReturnType<typeof ApiResponseSchema<z.ZodType<T>>>>
87
+
88
+ /**
89
+ * Type inferred from PaginationSchema
90
+ */
91
+ export type Pagination = z.infer<typeof PaginationSchema>
92
+
93
+ /**
94
+ * Type inferred from PaginatedResponseSchema
95
+ */
96
+ export type PaginatedResponse<T> = z.infer<ReturnType<typeof PaginatedResponseSchema<z.ZodType<T>>>>
97
+
98
+ /**
99
+ * Validates data against a schema and throws detailed errors
100
+ *
101
+ * @param {z.ZodSchema<T>} schema The schema to validate against
102
+ * @param {unknown} data The data to validate
103
+ * @returns {T} The validated data
104
+ * @throws {Error} If validation fails
105
+ */
106
+ export const validateSchema = <T>(schema: z.ZodSchema<T>, data: unknown): T => {
107
+ const result = schema.safeParse(data)
108
+
109
+ if (!result.success) {
110
+ const errors = result.error.issues.map((err: z.ZodIssue) => `${err.path.join('.')}: ${err.message}`)
111
+ throw new Error(`Validation failed: ${errors.join(', ')}`)
112
+ }
113
+
114
+ return result.data
115
+ }
116
+
117
+ /**
118
+ * Validates data against a schema and returns result with success flag
119
+ *
120
+ * @param {z.ZodSchema<T>} schema The schema to validate against
121
+ * @param {unknown} data The data to validate
122
+ * @returns {{ success: true; data: T } | { success: false; errors: string[] }} The validation result
123
+ */
124
+ export const safeValidateSchema = <T>(
125
+ schema: z.ZodSchema<T>,
126
+ data: unknown
127
+ ): { success: true; data: T } | { success: false; errors: string[] } => {
128
+ const result = schema.safeParse(data)
129
+
130
+ if (result.success) {
131
+ return { success: true, data: result.data }
132
+ }
133
+
134
+ const errors = result.error.issues.map((err: z.ZodIssue) => `${err.path.join('.')}: ${err.message}`)
135
+ return { success: false, errors }
136
+ }
package/src/str.ts ADDED
@@ -0,0 +1,438 @@
1
+ import * as formatters from './formatters.js'
2
+
3
+ /**
4
+ * Fluent string manipulation class inspired by Laravel's Str and Stringable
5
+ */
6
+ export class Stringable {
7
+ protected value: string
8
+
9
+ constructor(value: unknown) {
10
+ this.value = String(value ?? '')
11
+ }
12
+
13
+ /**
14
+ * Get the raw string value
15
+ *
16
+ * @returns {string} The raw string
17
+ */
18
+ toString(): string {
19
+ return this.value
20
+ }
21
+
22
+ /**
23
+ * Alias for toString()
24
+ *
25
+ * @returns {string} The raw string
26
+ */
27
+ get(): string {
28
+ return this.value
29
+ }
30
+
31
+ /**
32
+ * Convert to title case
33
+ *
34
+ * @returns {this} The Stringable instance
35
+ */
36
+ title(): this {
37
+ this.value = formatters.titleCase(this.value)
38
+ return this
39
+ }
40
+
41
+ /**
42
+ * Convert to slug
43
+ *
44
+ * @returns {this} The Stringable instance
45
+ */
46
+ slug(): this {
47
+ this.value = formatters.slugify(this.value)
48
+ return this
49
+ }
50
+
51
+ /**
52
+ * Convert to snake_case
53
+ *
54
+ * @returns {this} The Stringable instance
55
+ */
56
+ snake(): this {
57
+ this.value = formatters.snakeCase(this.value)
58
+ return this
59
+ }
60
+
61
+ /**
62
+ * Convert to kebab-case
63
+ *
64
+ * @returns {this} The Stringable instance
65
+ */
66
+ kebab(): this {
67
+ this.value = formatters.kebabCase(this.value)
68
+ return this
69
+ }
70
+
71
+ /**
72
+ * Convert to camelCase
73
+ *
74
+ * @returns {this} The Stringable instance
75
+ */
76
+ camel(): this {
77
+ this.value = formatters.camelCase(this.value)
78
+ return this
79
+ }
80
+
81
+ /**
82
+ * Convert to StudlyCase
83
+ *
84
+ * @returns {this} The Stringable instance
85
+ */
86
+ studly(): this {
87
+ this.value = formatters.studlyCase(this.value)
88
+ return this
89
+ }
90
+
91
+ /**
92
+ * Limit the string length
93
+ *
94
+ * @param {number} size The maximum length
95
+ * @param {string} [end='…'] The string to append if limited
96
+ * @returns {this} The Stringable instance
97
+ */
98
+ limit(size: number, end = '…'): this {
99
+ this.value = formatters.strLimit(this.value, size)
100
+ if (this.value.endsWith('…') && '…' !== end) {
101
+ this.value = this.value.slice(0, -1) + end
102
+ }
103
+ return this
104
+ }
105
+
106
+ /**
107
+ * Append a value to the string
108
+ *
109
+ * @param {...unknown[]} values The values to append
110
+ * @returns {this} The Stringable instance
111
+ */
112
+ append(...values: unknown[]): this {
113
+ this.value += values.join('')
114
+ return this
115
+ }
116
+
117
+ /**
118
+ * Prepend a value to the string
119
+ *
120
+ * @param {...unknown[]} values The values to prepend
121
+ * @returns {this} The Stringable instance
122
+ */
123
+ prepend(...values: unknown[]): this {
124
+ this.value = values.join('') + this.value
125
+ return this
126
+ }
127
+
128
+ /**
129
+ * Cap the string with a value if it doesn't already end with it
130
+ *
131
+ * @param {string} cap The string to end with
132
+ * @returns {this} The Stringable instance
133
+ */
134
+ finish(cap: string): this {
135
+ this.value = formatters.strFinish(this.value, cap)
136
+ return this
137
+ }
138
+
139
+ /**
140
+ * Start the string with a value if it doesn't already start with it
141
+ *
142
+ * @param {string} prefix The string to start with
143
+ * @returns {this} The Stringable instance
144
+ */
145
+ start(prefix: string): this {
146
+ this.value = formatters.strStart(this.value, prefix)
147
+ return this
148
+ }
149
+
150
+ /**
151
+ * Replace the first occurrence of a value
152
+ *
153
+ * @param {string | RegExp} search The value to search for
154
+ * @param {string} replace The value to replace with
155
+ * @returns {this} The Stringable instance
156
+ */
157
+ replace(search: string | RegExp, replace: string): this {
158
+ this.value = this.value.replace(search, replace)
159
+ return this
160
+ }
161
+
162
+ /**
163
+ * Replace all occurrences of a value
164
+ *
165
+ * @param {string | RegExp} search The value to search for
166
+ * @param {string} replace The value to replace with
167
+ * @returns {this} The Stringable instance
168
+ */
169
+ replaceAll(search: string | RegExp, replace: string): this {
170
+ if ('string' === typeof search) {
171
+ this.value = this.value.split(search).join(replace)
172
+ } else {
173
+ const flags = search.flags.includes('g') ? search.flags : `${search.flags}g`
174
+ this.value = this.value.replace(new RegExp(search.source, flags), replace)
175
+ }
176
+ return this
177
+ }
178
+
179
+ /**
180
+ * Reverse the string
181
+ *
182
+ * @returns {this} The Stringable instance
183
+ */
184
+ reverse(): this {
185
+ this.value = formatters.strReverse(this.value)
186
+ return this
187
+ }
188
+
189
+ /**
190
+ * Mask a portion of the string
191
+ *
192
+ * @param {string} character The masking character
193
+ * @param {number} index The starting index
194
+ * @param {number} [length] The number of characters to mask
195
+ * @returns {this} The Stringable instance
196
+ */
197
+ mask(character: string, index: number, length?: number): this {
198
+ this.value = formatters.strMask(this.value, character, index, length)
199
+ return this
200
+ }
201
+
202
+ /**
203
+ * Trim the string
204
+ *
205
+ * @param {string} [chars] The characters to trim (defaults to whitespace)
206
+ * @returns {this} The Stringable instance
207
+ */
208
+ trim(chars?: string): this {
209
+ if (!chars) {
210
+ this.value = this.value.trim()
211
+ } else {
212
+ const pattern = new RegExp(`^[${chars}]+|[${chars}]+$`, 'g')
213
+ this.value = this.value.replace(pattern, '')
214
+ }
215
+ return this
216
+ }
217
+
218
+ /**
219
+ * Convert to lower case
220
+ *
221
+ * @returns {this} The Stringable instance
222
+ */
223
+ lower(): this {
224
+ this.value = this.value.toLowerCase()
225
+ return this
226
+ }
227
+
228
+ /**
229
+ * Convert to upper case
230
+ *
231
+ * @returns {this} The Stringable instance
232
+ */
233
+ upper(): this {
234
+ this.value = this.value.toUpperCase()
235
+ return this
236
+ }
237
+
238
+ /**
239
+ * Capitalize the first letter
240
+ *
241
+ * @returns {this} The Stringable instance
242
+ */
243
+ capitalize(): this {
244
+ this.value = formatters.capitalize(this.value)
245
+ return this
246
+ }
247
+
248
+ /**
249
+ * Check if string contains a value
250
+ *
251
+ * @param {string | string[]} needles The values to search for
252
+ * @returns {boolean} True if the string contains any of the values
253
+ */
254
+ contains(needles: string | string[]): boolean {
255
+ if (Array.isArray(needles)) {
256
+ return formatters.strContainsAny(this.value, needles)
257
+ }
258
+ return this.value.includes(needles)
259
+ }
260
+
261
+ /**
262
+ * Check if string contains all values
263
+ *
264
+ * @param {string[]} needles The values to search for
265
+ * @returns {boolean} True if the string contains all values
266
+ */
267
+ containsAll(needles: string[]): boolean {
268
+ return formatters.strContainsAll(this.value, needles)
269
+ }
270
+
271
+ /**
272
+ * Check if string starts with a value
273
+ *
274
+ * @param {string | string[]} needles The values to check
275
+ * @returns {boolean} True if the string starts with any of the values
276
+ */
277
+ startsWith(needles: string | string[]): boolean {
278
+ if (Array.isArray(needles)) {
279
+ return needles.some((needle) => this.value.startsWith(needle))
280
+ }
281
+ return this.value.startsWith(needles)
282
+ }
283
+
284
+ /**
285
+ * Check if string ends with a value
286
+ *
287
+ * @param {string | string[]} needles The values to check
288
+ * @returns {boolean} True if the string ends with any of the values
289
+ */
290
+ endsWith(needles: string | string[]): boolean {
291
+ if (Array.isArray(needles)) {
292
+ return needles.some((needle) => this.value.endsWith(needle))
293
+ }
294
+ return this.value.endsWith(needles)
295
+ }
296
+
297
+ /**
298
+ * Execute a callback with the stringable and return the result
299
+ *
300
+ * @param {(str: this) => U} callback The callback to execute
301
+ * @returns {U} The result of the callback
302
+ */
303
+ pipe<U>(callback: (str: this) => U): U {
304
+ return callback(this)
305
+ }
306
+
307
+ /**
308
+ * Conditionally execute a callback
309
+ *
310
+ * @param {boolean | (() => boolean)} condition The condition to check
311
+ * @param {(str: this) => void} callback The callback to execute
312
+ * @returns {this} The Stringable instance
313
+ */
314
+ when(condition: boolean | (() => boolean), callback: (str: this) => void): this {
315
+ const shouldExecute = 'function' === typeof condition ? condition() : condition
316
+ if (shouldExecute) {
317
+ callback(this)
318
+ }
319
+ return this
320
+ }
321
+
322
+ /**
323
+ * Execute a callback and return the stringable (for side effects)
324
+ *
325
+ * @param {(str: this) => void} callback The callback to execute
326
+ * @returns {this} The Stringable instance
327
+ */
328
+ tap(callback: (str: this) => void): this {
329
+ callback(this)
330
+ return this
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Create a new fluent stringable instance
336
+ *
337
+ * @param {unknown} [value] The initial string value
338
+ * @returns {Stringable} A new Stringable instance
339
+ */
340
+ export const str = (value?: unknown): Stringable => {
341
+ return new Stringable(value)
342
+ }
343
+
344
+ /**
345
+ * Static methods for string manipulation
346
+ */
347
+ export const Str = {
348
+ /**
349
+ * Create a new fluent stringable instance
350
+ *
351
+ * @param {unknown} value The initial string value
352
+ * @returns {Stringable} A new Stringable instance
353
+ */
354
+ of: (value: unknown) => new Stringable(value),
355
+
356
+ /**
357
+ * Convert a string to a slug
358
+ *
359
+ * @param {string} value The string to slugify
360
+ * @returns {string} The slugified string
361
+ */
362
+ slug: (value: string) => formatters.slugify(value),
363
+
364
+ /**
365
+ * Convert a string to snake_case
366
+ *
367
+ * @param {string} value The string to convert
368
+ * @returns {string} The snake_case string
369
+ */
370
+ snake: (value: string) => formatters.snakeCase(value),
371
+
372
+ /**
373
+ * Convert a string to kebab-case
374
+ *
375
+ * @param {string} value The string to convert
376
+ * @returns {string} The kebab-case string
377
+ */
378
+ kebab: (value: string) => formatters.kebabCase(value),
379
+
380
+ /**
381
+ * Convert a string to camelCase
382
+ *
383
+ * @param {string} value The string to convert
384
+ * @returns {string} The camelCase string
385
+ */
386
+ camel: (value: string) => formatters.camelCase(value),
387
+
388
+ /**
389
+ * Convert a string to StudlyCase
390
+ *
391
+ * @param {string} value The string to convert
392
+ * @returns {string} The StudlyCase string
393
+ */
394
+ studly: (value: string) => formatters.studlyCase(value),
395
+
396
+ /**
397
+ * Convert a string to title case
398
+ *
399
+ * @param {string} value The string to convert
400
+ * @returns {string} The title case string
401
+ */
402
+ title: (value: string) => formatters.titleCase(value),
403
+
404
+ /**
405
+ * Limit the length of a string
406
+ *
407
+ * @param {string} value The string to limit
408
+ * @param {number} size The maximum length
409
+ * @param {string} [end='…'] The string to append if limited
410
+ * @returns {string} The limited string
411
+ */
412
+ limit: (value: string, size: number, end = '…') => {
413
+ const result = formatters.strLimit(value, size)
414
+ return result.endsWith('…') && '…' !== end ? result.slice(0, -1) + end : result
415
+ },
416
+
417
+ /**
418
+ * Generate a random alphanumeric string
419
+ *
420
+ * @param {number} [length=16] The length of the random string
421
+ * @returns {string} The random string
422
+ */
423
+ random: (length = 16): string => {
424
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
425
+ let result = ''
426
+ for (let i = 0; i < length; i++) {
427
+ result += chars.charAt(Math.floor(Math.random() * chars.length))
428
+ }
429
+ return result
430
+ },
431
+
432
+ /**
433
+ * Generate a UUID (version 4)
434
+ *
435
+ * @returns {string} The generated UUID
436
+ */
437
+ uuid: (): string => crypto.randomUUID(),
438
+ }
package/src/types.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Common type definitions for the helpers package
3
+ */
4
+
5
+ /**
6
+ * A generic record object with string keys and unknown values
7
+ */
8
+ export type GenericRecord = Record<string, unknown>
9
+
10
+ /**
11
+ * A type that can be an object record or an array
12
+ */
13
+ export type DataContainer = GenericRecord | unknown[]