@raubjo/architect 0.5.1

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 (68) hide show
  1. package/README.md +860 -0
  2. package/package.json +121 -0
  3. package/src/cache/cache.ts +46 -0
  4. package/src/cache/contract.ts +9 -0
  5. package/src/cache/manager.ts +110 -0
  6. package/src/cache/provider.ts +11 -0
  7. package/src/config/contract.ts +63 -0
  8. package/src/config/discovery.ts +99 -0
  9. package/src/config/env.global.d.ts +6 -0
  10. package/src/config/env.ts +68 -0
  11. package/src/config/index.ts +5 -0
  12. package/src/config/provider.ts +17 -0
  13. package/src/config/repository.ts +164 -0
  14. package/src/container/adapters/builtin.ts +323 -0
  15. package/src/container/contract.ts +43 -0
  16. package/src/container/runtime.ts +29 -0
  17. package/src/events/bus.ts +174 -0
  18. package/src/events/concerns/dispatchable.ts +10 -0
  19. package/src/events/provider.ts +9 -0
  20. package/src/events/types.ts +9 -0
  21. package/src/foundation/application.ts +136 -0
  22. package/src/foundation/current-application.ts +20 -0
  23. package/src/index.ts +58 -0
  24. package/src/log/contract.ts +21 -0
  25. package/src/log/drivers/console.ts +54 -0
  26. package/src/log/drivers/null.ts +23 -0
  27. package/src/log/drivers/stack.ts +46 -0
  28. package/src/log/manager.ts +76 -0
  29. package/src/log/provider.ts +11 -0
  30. package/src/react.ts +2 -0
  31. package/src/renderers/adapters/react.tsx +25 -0
  32. package/src/renderers/adapters/solid.tsx +26 -0
  33. package/src/renderers/adapters/svelte.ts +73 -0
  34. package/src/renderers/adapters/vue.ts +22 -0
  35. package/src/renderers/contract.ts +12 -0
  36. package/src/runtimes/react.tsx +81 -0
  37. package/src/runtimes/solid.tsx +47 -0
  38. package/src/runtimes/svelte.ts +17 -0
  39. package/src/runtimes/vue.ts +34 -0
  40. package/src/solid.ts +2 -0
  41. package/src/store/adapters/contract.ts +11 -0
  42. package/src/store/adapters/indexed-db.ts +187 -0
  43. package/src/store/adapters/local-storage.ts +48 -0
  44. package/src/store/adapters/memory.ts +35 -0
  45. package/src/store/manager.ts +68 -0
  46. package/src/store/provider.ts +10 -0
  47. package/src/store/store.ts +1 -0
  48. package/src/support/arr.ts +372 -0
  49. package/src/support/collection.ts +889 -0
  50. package/src/support/facades/cache.ts +6 -0
  51. package/src/support/facades/config.ts +6 -0
  52. package/src/support/facades/event.ts +6 -0
  53. package/src/support/facades/facade.ts +146 -0
  54. package/src/support/facades/index.ts +5 -0
  55. package/src/support/facades/log.ts +6 -0
  56. package/src/support/facades/store.ts +6 -0
  57. package/src/support/fluent.ts +56 -0
  58. package/src/support/globals.ts +8 -0
  59. package/src/support/lazy-collection.ts +341 -0
  60. package/src/support/manager.ts +53 -0
  61. package/src/support/num.ts +50 -0
  62. package/src/support/pipeline.ts +29 -0
  63. package/src/support/service-provider.ts +19 -0
  64. package/src/support/str.ts +682 -0
  65. package/src/svelte.ts +2 -0
  66. package/src/types/peer-deps.d.ts +10 -0
  67. package/src/vue.ts +2 -0
  68. package/tsconfig.json +15 -0
@@ -0,0 +1,682 @@
1
+ function splitWords(value: string): string[] {
2
+ const normalized = value
3
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
4
+ .replace(/[_\-.]+/g, " ")
5
+ .trim()
6
+ if (!normalized) return []
7
+ return normalized.split(/\s+/)
8
+ }
9
+
10
+ function normalizeForSlug(value: string): string {
11
+ return value
12
+ .normalize("NFKD")
13
+ .replace(/[̀-ͯ]/g, "")
14
+ .toLowerCase()
15
+ .replace(/[^a-z0-9]+/g, "-")
16
+ .replace(/^-+|-+$/g, "")
17
+ }
18
+
19
+ export function lower(value: string): string {
20
+ return value.toLowerCase()
21
+ }
22
+
23
+ export function upper(value: string): string {
24
+ return value.toUpperCase()
25
+ }
26
+
27
+ export function length(value: string): number {
28
+ return value.length
29
+ }
30
+
31
+ export function contains(haystack: string, needle: string | string[], ignoreCase = false): boolean {
32
+ const source = ignoreCase ? haystack.toLowerCase() : haystack
33
+ const needles = Array.isArray(needle) ? needle : [needle]
34
+ return needles.some((part) => source.includes(ignoreCase ? part.toLowerCase() : part))
35
+ }
36
+
37
+ export function startsWith(haystack: string, needle: string | string[]): boolean {
38
+ const needles = Array.isArray(needle) ? needle : [needle]
39
+ return needles.some((part) => haystack.startsWith(part))
40
+ }
41
+
42
+ export function endsWith(haystack: string, needle: string | string[]): boolean {
43
+ const needles = Array.isArray(needle) ? needle : [needle]
44
+ return needles.some((part) => haystack.endsWith(part))
45
+ }
46
+
47
+ export function replace(search: string | RegExp, replaceWith: string, subject: string): string {
48
+ return subject.replace(search, replaceWith)
49
+ }
50
+
51
+ export function snake(value: string, separator = "_"): string {
52
+ return splitWords(value)
53
+ .map((w) => w.toLowerCase())
54
+ .join(separator)
55
+ }
56
+
57
+ export function kebab(value: string): string {
58
+ return snake(value, "-")
59
+ }
60
+
61
+ export function studly(value: string): string {
62
+ return splitWords(value)
63
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
64
+ .join("")
65
+ }
66
+
67
+ export function camel(value: string): string {
68
+ const s = studly(value)
69
+ return s ? s.charAt(0).toLowerCase() + s.slice(1) : ""
70
+ }
71
+
72
+ export function slug(value: string, separator = "-"): string {
73
+ const slugged = normalizeForSlug(value)
74
+ return separator === "-" ? slugged : slugged.replace(/-/g, separator)
75
+ }
76
+
77
+ export function after(subject: string, search: string): string {
78
+ if (search === "") return subject
79
+ const idx = subject.indexOf(search)
80
+ return idx === -1 ? subject : subject.slice(idx + search.length)
81
+ }
82
+
83
+ export function afterLast(subject: string, search: string): string {
84
+ if (search === "") return subject
85
+ const idx = subject.lastIndexOf(search)
86
+ return idx === -1 ? subject : subject.slice(idx + search.length)
87
+ }
88
+
89
+ const APA_MINOR = new Set([
90
+ "a",
91
+ "an",
92
+ "the",
93
+ "and",
94
+ "but",
95
+ "for",
96
+ "nor",
97
+ "or",
98
+ "so",
99
+ "yet",
100
+ "at",
101
+ "by",
102
+ "in",
103
+ "of",
104
+ "on",
105
+ "to",
106
+ "up",
107
+ "as",
108
+ "is",
109
+ "it",
110
+ ])
111
+
112
+ export function apa(value: string): string {
113
+ const words = value.split(/\s+/)
114
+ return words
115
+ .map((word, i) => {
116
+ const lower = word.toLowerCase()
117
+ if (i === 0 || i === words.length - 1 || !APA_MINOR.has(lower)) {
118
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
119
+ }
120
+ return lower
121
+ })
122
+ .join(" ")
123
+ }
124
+
125
+ export function before(subject: string, search: string): string {
126
+ if (search === "") return subject
127
+ const idx = subject.indexOf(search)
128
+ return idx === -1 ? subject : subject.slice(0, idx)
129
+ }
130
+
131
+ export function beforeLast(subject: string, search: string): string {
132
+ if (search === "") return subject
133
+ const idx = subject.lastIndexOf(search)
134
+ return idx === -1 ? subject : subject.slice(0, idx)
135
+ }
136
+
137
+ export function between(subject: string, from: string, to: string): string {
138
+ if (from === "" || to === "") return subject
139
+ const start = subject.indexOf(from)
140
+ if (start === -1) return subject
141
+ const end = subject.lastIndexOf(to)
142
+ if (end === -1 || end <= start) return subject
143
+ return subject.slice(start + from.length, end)
144
+ }
145
+
146
+ export function betweenFirst(subject: string, from: string, to: string): string {
147
+ if (from === "" || to === "") return subject
148
+ const start = subject.indexOf(from)
149
+ if (start === -1) return subject
150
+ const end = subject.indexOf(to, start + from.length)
151
+ if (end === -1) return subject
152
+ return subject.slice(start + from.length, end)
153
+ }
154
+
155
+ export function charAt(value: string, index: number): string | false {
156
+ if (index < 0 || index >= value.length) return false
157
+ return value[index] as string
158
+ }
159
+
160
+ export function chopStart(value: string, needle: string | string[]): string {
161
+ const needles = Array.isArray(needle) ? needle : [needle]
162
+ for (const n of needles) {
163
+ if (value.startsWith(n)) {
164
+ return value.slice(n.length)
165
+ }
166
+ }
167
+ return value
168
+ }
169
+
170
+ export function chopEnd(value: string, needle: string | string[]): string {
171
+ const needles = Array.isArray(needle) ? needle : [needle]
172
+ for (const n of needles) {
173
+ if (value.endsWith(n)) {
174
+ return value.slice(0, value.length - n.length)
175
+ }
176
+ }
177
+ return value
178
+ }
179
+
180
+ export function containsAll(haystack: string, needles: string[], ignoreCase = false): boolean {
181
+ const source = ignoreCase ? haystack.toLowerCase() : haystack
182
+ return needles.every((n) => source.includes(ignoreCase ? n.toLowerCase() : n))
183
+ }
184
+
185
+ export function deduplicate(value: string, char = " "): string {
186
+ const escaped = char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
187
+ return value.replace(new RegExp(`${escaped}+`, "g"), char)
188
+ }
189
+
190
+ export function doesntContain(haystack: string, needle: string | string[], ignoreCase = false): boolean {
191
+ return !contains(haystack, needle, ignoreCase)
192
+ }
193
+
194
+ export function doesntEndWith(haystack: string, needle: string | string[]): boolean {
195
+ return !endsWith(haystack, needle)
196
+ }
197
+
198
+ export function doesntStartWith(haystack: string, needle: string | string[]): boolean {
199
+ return !startsWith(haystack, needle)
200
+ }
201
+
202
+ export function excerpt(value: string, phrase: string, options: { radius?: number; omission?: string } = {}): string {
203
+ const radius = options.radius ?? 100
204
+ const omission = options.omission ?? "..."
205
+
206
+ const idx = value.indexOf(phrase)
207
+ if (idx === -1) return value
208
+
209
+ const start = Math.max(0, idx - radius)
210
+ const end = Math.min(value.length, idx + phrase.length + radius)
211
+
212
+ const prefix = start > 0 ? omission : ""
213
+ const suffix = end < value.length ? omission : ""
214
+
215
+ return prefix + value.slice(start, end) + suffix
216
+ }
217
+
218
+ export function finish(value: string, cap: string): string {
219
+ return value.endsWith(cap) ? value : value + cap
220
+ }
221
+
222
+ export function fromBase64(value: string): string {
223
+ return atob(value)
224
+ }
225
+
226
+ export function headline(value: string): string {
227
+ return splitWords(value)
228
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
229
+ .join(" ")
230
+ }
231
+
232
+ export function initials(value: string, capitalize = false): string {
233
+ const result = value
234
+ .trim()
235
+ .split(/\s+/)
236
+ .map((w) => w.charAt(0))
237
+ .join("")
238
+ return capitalize ? result.toUpperCase() : result.toLowerCase()
239
+ }
240
+
241
+ export function is(pattern: string, value: string, ignoreCase = false): boolean {
242
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")
243
+ const flags = ignoreCase ? "i" : ""
244
+ return new RegExp(`^${escaped}$`, flags).test(value)
245
+ }
246
+
247
+ export function isAscii(value: string): boolean {
248
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: ascii characters
249
+ return /^[\x00-\x7F]*$/.test(value)
250
+ }
251
+
252
+ export function isJson(value: string): boolean {
253
+ try {
254
+ JSON.parse(value)
255
+ return true
256
+ } catch {
257
+ return false
258
+ }
259
+ }
260
+
261
+ export function isUrl(value: string, protocols?: string[]): boolean {
262
+ try {
263
+ const url = new URL(value)
264
+ if (protocols && protocols.length > 0) {
265
+ const proto = url.protocol.replace(/:$/, "")
266
+ return protocols.includes(proto)
267
+ }
268
+ return true
269
+ } catch {
270
+ return false
271
+ }
272
+ }
273
+
274
+ export function isUlid(value: string): boolean {
275
+ return /^[0-9A-HJKMNP-TV-Z]{26}$/i.test(value)
276
+ }
277
+
278
+ export function isUuid(value: string, version?: 1 | 3 | 4 | 5 | 6 | 7 | 8): boolean {
279
+ if (version === undefined) {
280
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)
281
+ }
282
+ const v = version.toString()
283
+ return new RegExp(`^[0-9a-f]{8}-[0-9a-f]{4}-${v}[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$`, "i").test(value)
284
+ }
285
+
286
+ export function isMatch(pattern: string | RegExp, value: string): boolean {
287
+ return parsePattern(pattern).test(value)
288
+ }
289
+
290
+ export function lcfirst(value: string): string {
291
+ return value ? value.charAt(0).toLowerCase() + value.slice(1) : value
292
+ }
293
+
294
+ export function limit(value: string, limitChars = 100, end = "...", preserveWords = false): string {
295
+ if (value.length <= limitChars) return value
296
+ if (!preserveWords) return value.slice(0, limitChars) + end
297
+ const truncated = value.slice(0, limitChars)
298
+ const lastSpace = truncated.lastIndexOf(" ")
299
+ return (lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated) + end
300
+ }
301
+
302
+ export function mask(value: string, char: string, index: number, maskLength?: number): string {
303
+ const len = value.length
304
+ const start = index < 0 ? Math.max(0, len + index) : Math.min(index, len)
305
+ const count = maskLength !== undefined ? Math.min(maskLength, len - start) : len - start
306
+ return value.slice(0, start) + char.repeat(count) + value.slice(start + count)
307
+ }
308
+
309
+ function parsePattern(pattern: string | RegExp): RegExp {
310
+ if (pattern instanceof RegExp) return pattern
311
+ // Support /pattern/flags delimiter syntax
312
+ const delimited = /^\/(.+)\/([gimsuy]*)$/.exec(pattern)
313
+ if (delimited) {
314
+ return new RegExp(delimited[1] as string, delimited[2] || "")
315
+ }
316
+ return new RegExp(pattern)
317
+ }
318
+
319
+ export function match(pattern: string | RegExp, value: string): string {
320
+ const re = parsePattern(pattern)
321
+ const m = value.match(re)
322
+ if (!m) return ""
323
+ return m[1] !== undefined ? m[1] : m[0]
324
+ }
325
+
326
+ export function matchAll(pattern: string | RegExp, value: string): string[] {
327
+ const base = parsePattern(pattern)
328
+ const flags = base.flags.includes("g") ? base.flags : `${base.flags}g`
329
+ const re = new RegExp(base.source, flags)
330
+ const results: string[] = []
331
+ let m: RegExpExecArray | null
332
+ while ((m = re.exec(value)) !== null) {
333
+ results.push(m[1] !== undefined ? m[1] : m[0])
334
+ }
335
+ return results
336
+ }
337
+
338
+ export function padBoth(value: string, padLength: number, pad = " "): string {
339
+ if (value.length >= padLength) return value
340
+ const total = padLength - value.length
341
+ const leftPad = Math.floor(total / 2)
342
+ const rightPad = total - leftPad
343
+ return (
344
+ pad.repeat(Math.ceil(leftPad / pad.length)).slice(0, leftPad) +
345
+ value +
346
+ pad.repeat(Math.ceil(rightPad / pad.length)).slice(0, rightPad)
347
+ )
348
+ }
349
+
350
+ export function padLeft(value: string, padLength: number, pad = " "): string {
351
+ return value.padStart(padLength, pad)
352
+ }
353
+
354
+ export function padRight(value: string, padLength: number, pad = " "): string {
355
+ return value.padEnd(padLength, pad)
356
+ }
357
+
358
+ export function position(haystack: string, needle: string, offset = 0): number | false {
359
+ const idx = haystack.indexOf(needle, offset)
360
+ return idx === -1 ? false : idx
361
+ }
362
+
363
+ export function random(len = 16): string {
364
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
365
+ const result: string[] = []
366
+ if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.getRandomValues === "function") {
367
+ const bytes = new Uint8Array(len)
368
+ globalThis.crypto.getRandomValues(bytes)
369
+ for (const b of bytes) {
370
+ result.push(chars[b % chars.length] as string)
371
+ }
372
+ } else {
373
+ for (let i = 0; i < len; i++) {
374
+ result.push(chars[Math.floor(Math.random() * chars.length)] as string)
375
+ }
376
+ }
377
+ return result.join("")
378
+ }
379
+
380
+ export function remove(search: string | string[], subject: string, caseSensitive = true): string {
381
+ const searches = Array.isArray(search) ? search : [search]
382
+ let result = subject
383
+ for (const s of searches) {
384
+ if (caseSensitive) {
385
+ result = result.split(s).join("")
386
+ } else {
387
+ result = result.replace(new RegExp(s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi"), "")
388
+ }
389
+ }
390
+ return result
391
+ }
392
+
393
+ export function repeat(value: string, times: number): string {
394
+ return value.repeat(times)
395
+ }
396
+
397
+ export function replaceArray(search: string, replaceList: string[], subject: string): string {
398
+ let replaceIdx = 0
399
+ const searchLen = search.length
400
+ let result = ""
401
+ let remaining = subject
402
+ while (replaceIdx < replaceList.length) {
403
+ const pos = remaining.indexOf(search)
404
+ if (pos === -1) break
405
+ result += remaining.slice(0, pos) + (replaceList[replaceIdx] as string)
406
+ remaining = remaining.slice(pos + searchLen)
407
+ replaceIdx++
408
+ }
409
+ return result + remaining
410
+ }
411
+
412
+ export function replaceFirst(search: string, replaceWith: string, subject: string): string {
413
+ const idx = subject.indexOf(search)
414
+ if (idx === -1) return subject
415
+ return subject.slice(0, idx) + replaceWith + subject.slice(idx + search.length)
416
+ }
417
+
418
+ export function replaceLast(search: string, replaceWith: string, subject: string): string {
419
+ const idx = subject.lastIndexOf(search)
420
+ if (idx === -1) return subject
421
+ return subject.slice(0, idx) + replaceWith + subject.slice(idx + search.length)
422
+ }
423
+
424
+ export function replaceMatches(
425
+ pattern: string | RegExp,
426
+ replaceWith: string | ((match: string[]) => string),
427
+ subject: string,
428
+ ): string {
429
+ const base = parsePattern(pattern)
430
+ const flags = base.flags.includes("g") ? base.flags : `${base.flags}g`
431
+ const re = new RegExp(base.source, flags)
432
+ if (typeof replaceWith === "string") {
433
+ return subject.replace(re, replaceWith)
434
+ }
435
+ return subject.replace(re, (...args: unknown[]) => {
436
+ const matchArr = args.slice(0, args.length - 2) as string[]
437
+ return replaceWith(matchArr)
438
+ })
439
+ }
440
+
441
+ export function replaceStart(search: string, replaceWith: string, subject: string): string {
442
+ return subject.startsWith(search) ? replaceWith + subject.slice(search.length) : subject
443
+ }
444
+
445
+ export function replaceEnd(search: string, replaceWith: string, subject: string): string {
446
+ return subject.endsWith(search) ? subject.slice(0, subject.length - search.length) + replaceWith : subject
447
+ }
448
+
449
+ export function reverse(value: string): string {
450
+ return [...value].reverse().join("")
451
+ }
452
+
453
+ export function squish(value: string): string {
454
+ return value.trim().replace(/\s+/g, " ")
455
+ }
456
+
457
+ export function start(value: string, prefix: string): string {
458
+ return value.startsWith(prefix) ? value : prefix + value
459
+ }
460
+
461
+ export function substr(value: string, startIndex: number, len?: number): string {
462
+ if (len === undefined) {
463
+ return value.slice(startIndex)
464
+ }
465
+ return value.slice(startIndex, startIndex < 0 ? startIndex + len : startIndex + len)
466
+ }
467
+
468
+ export function substrCount(value: string, needle: string): number {
469
+ if (needle === "") return 0
470
+ let count = 0
471
+ let idx = 0
472
+ while ((idx = value.indexOf(needle, idx)) !== -1) {
473
+ count++
474
+ idx += needle.length
475
+ }
476
+ return count
477
+ }
478
+
479
+ export function substrReplace(value: string, replacer: string, startIndex: number, len?: number): string {
480
+ const arr = [...value]
481
+ const realStart = startIndex < 0 ? Math.max(0, arr.length + startIndex) : startIndex
482
+ if (len === undefined) {
483
+ arr.splice(realStart, arr.length - realStart, ...replacer)
484
+ } else {
485
+ arr.splice(realStart, len, ...replacer)
486
+ }
487
+ return arr.join("")
488
+ }
489
+
490
+ export function swap(map: Record<string, string>, subject: string): string {
491
+ const keys = Object.keys(map).sort((a, b) => b.length - a.length)
492
+ let result = subject
493
+ for (const key of keys) {
494
+ result = result.split(key).join(map[key] as string)
495
+ }
496
+ return result
497
+ }
498
+
499
+ export function take(value: string, limitChars: number): string {
500
+ if (limitChars >= 0) return value.slice(0, limitChars)
501
+ return value.slice(limitChars)
502
+ }
503
+
504
+ export function title(value: string): string {
505
+ return value.replace(/\S+/g, (w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
506
+ }
507
+
508
+ export function toBase64(value: string): string {
509
+ return btoa(value)
510
+ }
511
+
512
+ export function trim(value: string, chars?: string): string {
513
+ if (!chars) return value.trim()
514
+ const escaped = chars.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
515
+ return value.replace(new RegExp(`^[${escaped}]+|[${escaped}]+$`, "g"), "")
516
+ }
517
+
518
+ export function ltrim(value: string, chars?: string): string {
519
+ if (!chars) return value.trimStart()
520
+ const escaped = chars.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
521
+ return value.replace(new RegExp(`^[${escaped}]+`, "g"), "")
522
+ }
523
+
524
+ export function rtrim(value: string, chars?: string): string {
525
+ if (!chars) return value.trimEnd()
526
+ const escaped = chars.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
527
+ return value.replace(new RegExp(`[${escaped}]+$`, "g"), "")
528
+ }
529
+
530
+ export function ucfirst(value: string): string {
531
+ return value ? value.charAt(0).toUpperCase() + value.slice(1) : value
532
+ }
533
+
534
+ export function ucsplit(value: string): string[] {
535
+ return value.split(/(?=[A-Z])/).filter(Boolean)
536
+ }
537
+
538
+ export function ucwords(value: string): string {
539
+ return value.replace(/(?:^|\s)\S/g, (c) => c.toUpperCase())
540
+ }
541
+
542
+ export function unwrap(value: string, before: string, after?: string): string {
543
+ const end = after ?? before
544
+ let result = value
545
+ if (result.startsWith(before)) result = result.slice(before.length)
546
+ if (result.endsWith(end)) result = result.slice(0, result.length - end.length)
547
+ return result
548
+ }
549
+
550
+ export function wordCount(value: string): number {
551
+ return value.trim() === "" ? 0 : value.trim().split(/\s+/).length
552
+ }
553
+
554
+ export function wordWrap(value: string, characters = 75, breakWith = "\n", cutLongWords = false): string {
555
+ if (value.length <= characters) return value
556
+ const words = value.split(" ")
557
+ const lines: string[] = []
558
+ let current = ""
559
+ for (const word of words) {
560
+ if (cutLongWords && word.length > characters) {
561
+ if (current !== "") {
562
+ lines.push(current)
563
+ current = ""
564
+ }
565
+ let remaining = word
566
+ while (remaining.length > characters) {
567
+ lines.push(remaining.slice(0, characters))
568
+ remaining = remaining.slice(characters)
569
+ }
570
+ current = remaining
571
+ } else if (current === "") {
572
+ current = word
573
+ } else if (current.length + 1 + word.length <= characters) {
574
+ current += ` ${word}`
575
+ } else {
576
+ lines.push(current)
577
+ current = word
578
+ }
579
+ }
580
+ if (current !== "") lines.push(current)
581
+ return lines.join(breakWith)
582
+ }
583
+
584
+ export function words(value: string, count: number, end = "..."): string {
585
+ const parts = value.trim().split(/\s+/)
586
+ if (parts.length <= count) return value
587
+ return parts.slice(0, count).join(" ") + end
588
+ }
589
+
590
+ export function wrap(value: string, before: string, after?: string): string {
591
+ return before + value + (after ?? before)
592
+ }
593
+
594
+ export const Str = {
595
+ after,
596
+ afterLast,
597
+ apa,
598
+ before,
599
+ beforeLast,
600
+ between,
601
+ betweenFirst,
602
+ camel,
603
+ charAt,
604
+ chopEnd,
605
+ chopStart,
606
+ contains,
607
+ containsAll,
608
+ deduplicate,
609
+ doesntContain,
610
+ doesntEndWith,
611
+ doesntStartWith,
612
+ endsWith,
613
+ excerpt,
614
+ finish,
615
+ fromBase64,
616
+ headline,
617
+ initials,
618
+ is,
619
+ isAscii,
620
+ isJson,
621
+ isMatch,
622
+ isUlid,
623
+ isUrl,
624
+ isUuid,
625
+ kebab,
626
+ lcfirst,
627
+ length,
628
+ limit,
629
+ lower,
630
+ ltrim,
631
+ mask,
632
+ match,
633
+ matchAll,
634
+ padBoth,
635
+ padLeft,
636
+ padRight,
637
+ position,
638
+ random,
639
+ remove,
640
+ repeat,
641
+ replace,
642
+ replaceArray,
643
+ replaceEnd,
644
+ replaceFirst,
645
+ replaceLast,
646
+ replaceMatches,
647
+ replaceStart,
648
+ reverse,
649
+ rtrim,
650
+ slug,
651
+ snake,
652
+ squish,
653
+ start,
654
+ startsWith,
655
+ studly,
656
+ substr,
657
+ substrCount,
658
+ substrReplace,
659
+ swap,
660
+ take,
661
+ title,
662
+ toBase64,
663
+ trim,
664
+ ucfirst,
665
+ ucsplit,
666
+ ucwords,
667
+ unwrap,
668
+ upper,
669
+ wordCount,
670
+ wordWrap,
671
+ words,
672
+ wrap,
673
+ }
674
+
675
+ export function registerGlobalStr(): void {
676
+ const globalScope = globalThis as { Str?: typeof Str }
677
+ if (typeof globalScope.Str === "undefined") {
678
+ globalScope.Str = Str
679
+ }
680
+ }
681
+
682
+ export const strTestingHelpers = { splitWords, normalizeForSlug }
package/src/svelte.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { default as Renderer } from "./renderers/adapters/svelte"
2
+ export { containerKey, provideContainer, provideContainer as ContextProvider, useService } from "./runtimes/svelte"
@@ -0,0 +1,10 @@
1
+ declare module "svelte" {
2
+ export function setContext(key: unknown, value: unknown): void
3
+ export function getContext<T>(key: unknown): T
4
+ export function mount(component: unknown, options: unknown): unknown
5
+ export function unmount(instance: unknown): void
6
+ }
7
+
8
+ declare module "solid-js/web/dist/server.js" {
9
+ export * from "solid-js/web"
10
+ }
package/src/vue.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { default as Renderer } from "./renderers/adapters/vue"
2
+ export { ContextProvider, useService } from "./runtimes/vue"
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "paths": {
9
+ "@/*": ["./src/*"]
10
+ },
11
+ "skipLibCheck": true,
12
+ "noEmit": true
13
+ },
14
+ "include": ["src"]
15
+ }