hanzio 3.0.0 → 3.1.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 (112) hide show
  1. package/README.md +114 -3
  2. package/dist/api-wrapper/connection-auth.d.ts +61 -0
  3. package/dist/cli/fields.d.ts +30 -0
  4. package/dist/cli/index.d.ts +122 -0
  5. package/dist/format/index.d.ts +91 -0
  6. package/dist/format/phone.d.ts +19 -0
  7. package/dist/http/index.d.ts +14 -0
  8. package/dist/i18n/catalog.d.ts +47 -0
  9. package/dist/i18n/check.d.ts +45 -0
  10. package/dist/i18n/cli.d.ts +2 -0
  11. package/dist/i18n/content-rules.d.ts +55 -0
  12. package/dist/i18n/i18next.d.ts +39 -0
  13. package/dist/i18n/index.d.ts +16 -1
  14. package/dist/i18n/locales.d.ts +62 -0
  15. package/dist/i18n/vite.d.ts +16 -0
  16. package/dist/i18n/vue.d.ts +93 -0
  17. package/dist/{index-kj1phgrx.js → index-29mmh343.js} +24 -1
  18. package/dist/{index-zbj9s3s3.js → index-2kz392vw.js} +1 -1
  19. package/dist/index-5n2e326q.js +409 -0
  20. package/dist/{index-hy7kbb99.js → index-5tzqfmnj.js} +1 -1
  21. package/dist/{index-0zvtcbzy.js → index-6f2w90pp.js} +69 -1
  22. package/dist/index-6ragggbs.js +473 -0
  23. package/dist/index-mq2qgc20.js +116 -0
  24. package/dist/index-najbyzpr.js +99 -0
  25. package/dist/index-ww09bm49.js +250 -0
  26. package/dist/iso/countries.d.ts +262 -0
  27. package/dist/iso/currencies.d.ts +165 -0
  28. package/dist/iso/index.d.ts +4 -0
  29. package/dist/iso/languages.d.ts +195 -0
  30. package/dist/iso/phone-language.d.ts +5 -0
  31. package/dist/promise/index.d.ts +31 -0
  32. package/dist/redis/client.d.ts +49 -0
  33. package/dist/redis/concurrency-limit.d.ts +63 -0
  34. package/dist/redis/index.d.ts +3 -0
  35. package/dist/redis/lua-script.d.ts +13 -0
  36. package/dist/secrets/infisical-identity-file.d.ts +12 -0
  37. package/dist/src/api-wrapper/connection-auth.js +82 -0
  38. package/dist/src/api-wrapper/index.js +1 -1
  39. package/dist/src/cli/index.js +10 -0
  40. package/dist/src/format/index.js +79 -2
  41. package/dist/src/format/phone.js +21 -0
  42. package/dist/src/http/index.js +69 -0
  43. package/dist/src/i18n/check.js +15 -0
  44. package/dist/src/i18n/cli.js +71 -0
  45. package/dist/src/i18n/i18next.js +48 -0
  46. package/dist/src/i18n/index.js +16 -34
  47. package/dist/src/i18n/vite.js +25 -0
  48. package/dist/src/i18n/vue.js +51 -0
  49. package/dist/src/index.js +14 -4
  50. package/dist/src/iso/index.js +655 -0
  51. package/dist/src/redis/index.js +577 -0
  52. package/dist/src/secrets/index.js +78 -35
  53. package/dist/src/sitemap/index.js +1 -1
  54. package/dist/src/storage/index.js +8 -117
  55. package/dist/src/testing/index.js +348 -0
  56. package/dist/src/zod/index.js +20 -253
  57. package/dist/string/index.d.ts +21 -0
  58. package/dist/testing/connection-auth-contract.d.ts +43 -0
  59. package/dist/testing/helpers.d.ts +26 -0
  60. package/dist/testing/index.d.ts +5 -0
  61. package/dist/testing/module-mocker.d.ts +30 -0
  62. package/dist/testing/prisma.d.ts +30 -0
  63. package/dist/testing/window.d.ts +18 -0
  64. package/dist/zod/index.d.ts +10 -0
  65. package/dist/zod/introspect.d.ts +2 -1
  66. package/package.json +96 -2
  67. package/src/api-wrapper/README.md +52 -0
  68. package/src/api-wrapper/connection-auth.ts +142 -0
  69. package/src/cli/README.md +220 -0
  70. package/src/cli/fields.ts +213 -0
  71. package/src/cli/index.ts +532 -0
  72. package/src/format/README.md +82 -2
  73. package/src/format/index.ts +148 -0
  74. package/src/format/phone.ts +43 -0
  75. package/src/http/README.md +25 -0
  76. package/src/http/index.ts +94 -0
  77. package/src/i18n/README.md +206 -3
  78. package/src/i18n/catalog.ts +139 -0
  79. package/src/i18n/check.ts +376 -0
  80. package/src/i18n/cli.ts +90 -0
  81. package/src/i18n/content-rules.ts +179 -0
  82. package/src/i18n/i18next.ts +89 -0
  83. package/src/i18n/index.ts +34 -0
  84. package/src/i18n/locales.ts +135 -0
  85. package/src/i18n/vite.ts +40 -0
  86. package/src/i18n/vue.ts +82 -0
  87. package/src/index.ts +1 -1
  88. package/src/iso/README.md +44 -0
  89. package/src/iso/countries.ts +272 -0
  90. package/src/iso/currencies.ts +172 -0
  91. package/src/iso/index.ts +8 -0
  92. package/src/iso/languages.ts +208 -0
  93. package/src/iso/phone-language.ts +46 -0
  94. package/src/promise/index.ts +100 -0
  95. package/src/redis/README.md +112 -0
  96. package/src/redis/client.ts +239 -0
  97. package/src/redis/concurrency-limit.ts +591 -0
  98. package/src/redis/index.ts +15 -0
  99. package/src/redis/lua-script.ts +43 -0
  100. package/src/secrets/README.md +5 -1
  101. package/src/secrets/infisical-identity-file.ts +69 -0
  102. package/src/secrets/infisical.ts +18 -3
  103. package/src/string/index.ts +59 -0
  104. package/src/testing/README.md +119 -0
  105. package/src/testing/connection-auth-contract.ts +303 -0
  106. package/src/testing/helpers.ts +41 -0
  107. package/src/testing/index.ts +12 -0
  108. package/src/testing/module-mocker.ts +50 -0
  109. package/src/testing/prisma.ts +78 -0
  110. package/src/testing/window.ts +119 -0
  111. package/src/zod/index.ts +20 -0
  112. package/src/zod/introspect.ts +1 -1
@@ -0,0 +1,50 @@
1
+ type ModuleExports = Record<string, unknown>
2
+
3
+ /**
4
+ * `mock.module` that can be undone. Bun replaces a mocked module for the whole
5
+ * process and `mock.restore()` does not bring it back
6
+ * (https://github.com/oven-sh/bun/issues/7823), so one suite's stub silently
7
+ * leaks into every later suite.
8
+ *
9
+ * Mocks spread the module's real exports first, so exports you don't override
10
+ * keep working, and `clear()` re-registers the originals. Call it in
11
+ * `afterEach` or `afterAll`.
12
+ *
13
+ * Bun resolves specifiers relative to the file that registers the mock, so
14
+ * pass absolute paths or package names. A `resolve` function maps your own
15
+ * shorthand (an `@/` alias, a source-root-relative path) to absolute paths.
16
+ *
17
+ * @example
18
+ * const moduleMocker = new ModuleMocker((specifier) =>
19
+ * specifier.startsWith('@/') ? path.resolve(srcDir, specifier.slice(2)) : specifier
20
+ * )
21
+ * await moduleMocker.mock('@/lib/posthog', () => ({ captureException: mock() }))
22
+ * afterAll(() => moduleMocker.clear())
23
+ */
24
+ export class ModuleMocker {
25
+ private restorers: (() => void)[] = []
26
+
27
+ constructor(
28
+ private readonly resolve: (specifier: string) => string = (specifier) =>
29
+ specifier
30
+ ) {}
31
+
32
+ async mock(
33
+ specifier: string,
34
+ renderMocks: () => ModuleExports
35
+ ): Promise<void> {
36
+ const { mock } = await import('bun:test')
37
+ const resolved = this.resolve(specifier)
38
+ const original = { ...(await import(resolved)) } as ModuleExports
39
+ const merged = { ...original, ...renderMocks() }
40
+ mock.module(resolved, () => merged)
41
+ this.restorers.push(() => {
42
+ mock.module(resolved, () => original)
43
+ })
44
+ }
45
+
46
+ clear(): void {
47
+ for (const restore of this.restorers) restore()
48
+ this.restorers = []
49
+ }
50
+ }
@@ -0,0 +1,78 @@
1
+ import type { Mock, mock } from 'bun:test'
2
+
3
+ export type PrismaMethodMock = Mock<(...args: unknown[]) => Promise<unknown>>
4
+
5
+ /**
6
+ * A Prisma client where every model delegate and every method is a Bun mock,
7
+ * created on first access, like `jest-mock-extended`'s `mockDeep()`.
8
+ * `$`-prefixed client methods (`$transaction`, `$queryRaw`) are mocks too.
9
+ */
10
+ export type DeepMockedPrisma<Client> = {
11
+ [K in keyof Client]: Client[K] extends (...args: never[]) => unknown
12
+ ? PrismaMethodMock
13
+ : { [Method in keyof Client[K]]: PrismaMethodMock }
14
+ } & {
15
+ /** Resets every mock created so far. Call it in `beforeEach`. */
16
+ $reset: () => void
17
+ }
18
+
19
+ /**
20
+ * Builds a deep auto-mock of a Prisma client from Bun's `mock`. Register it
21
+ * with `mock.module` in place of the module that exports your client.
22
+ *
23
+ * @example
24
+ * import { mock } from 'bun:test'
25
+ * import type { prisma } from '../lib/prisma'
26
+ *
27
+ * export const prismaMock = createPrismaMock<typeof prisma>(mock)
28
+ * mock.module(prismaModulePath, () => ({ ...prismaExports, prisma: prismaMock }))
29
+ *
30
+ * prismaMock.user.findUnique.mockResolvedValue({ id: 'u1' })
31
+ * expect(prismaMock.user.findUnique).toHaveBeenCalledWith({ where: { id: 'u1' } })
32
+ */
33
+ export function createPrismaMock<Client>(
34
+ createMock: typeof mock
35
+ ): DeepMockedPrisma<Client> {
36
+ const created: PrismaMethodMock[] = []
37
+ const reset = () => {
38
+ for (const method of created) method.mockReset()
39
+ }
40
+
41
+ const methodsOf = (methods: Map<string, PrismaMethodMock>, key: string) => {
42
+ let method = methods.get(key)
43
+ if (!method) {
44
+ method = createMock() as unknown as PrismaMethodMock
45
+ created.push(method)
46
+ methods.set(key, method)
47
+ }
48
+ return method
49
+ }
50
+
51
+ const delegates = new Map<string, object>()
52
+ const rootMethods = new Map<string, PrismaMethodMock>()
53
+
54
+ const delegateFor = (model: string) => {
55
+ let delegate = delegates.get(model)
56
+ if (!delegate) {
57
+ const methods = new Map<string, PrismaMethodMock>()
58
+ delegate = new Proxy(
59
+ {},
60
+ {
61
+ get: (_target, method) =>
62
+ typeof method === 'symbol' ? undefined : methodsOf(methods, method)
63
+ }
64
+ )
65
+ delegates.set(model, delegate)
66
+ }
67
+ return delegate
68
+ }
69
+
70
+ return new Proxy({} as DeepMockedPrisma<Client>, {
71
+ get(_target, property) {
72
+ if (property === '$reset') return reset
73
+ if (typeof property === 'symbol') return undefined
74
+ if (property.startsWith('$')) return methodsOf(rootMethods, property)
75
+ return delegateFor(property)
76
+ }
77
+ })
78
+ }
@@ -0,0 +1,119 @@
1
+ /** A complete in-memory `Storage` (`localStorage` / `sessionStorage`). */
2
+ export function createTestStorage(): Storage {
3
+ const store = new Map<string, string>()
4
+ return {
5
+ get length() {
6
+ return store.size
7
+ },
8
+ clear: () => store.clear(),
9
+ getItem: (key) => store.get(key) ?? null,
10
+ key: (index) => [...store.keys()][index] ?? null,
11
+ removeItem: (key) => {
12
+ store.delete(key)
13
+ },
14
+ setItem: (key, value) => {
15
+ store.set(key, String(value))
16
+ }
17
+ }
18
+ }
19
+
20
+ type ShimLocation = Pick<
21
+ URL,
22
+ | 'href'
23
+ | 'origin'
24
+ | 'protocol'
25
+ | 'host'
26
+ | 'hostname'
27
+ | 'port'
28
+ | 'pathname'
29
+ | 'search'
30
+ | 'hash'
31
+ >
32
+
33
+ type ShimWindow = {
34
+ location: ShimLocation
35
+ addEventListener: (...args: unknown[]) => void
36
+ removeEventListener: (...args: unknown[]) => void
37
+ localStorage: Storage
38
+ }
39
+
40
+ const noop = () => {}
41
+
42
+ function locationOf(href: string): ShimLocation {
43
+ const { origin, protocol, host, hostname, port, pathname, search, hash } =
44
+ new URL(href)
45
+ return {
46
+ href,
47
+ origin,
48
+ protocol,
49
+ host,
50
+ hostname,
51
+ port,
52
+ pathname,
53
+ search,
54
+ hash
55
+ }
56
+ }
57
+
58
+ function isObject(value: unknown): value is Record<string, unknown> {
59
+ return typeof value === 'object' && value !== null
60
+ }
61
+
62
+ /**
63
+ * Installs a minimal `globalThis.window` for unit tests of browser-facing code
64
+ * that only probes `window` (`location`, `localStorage`, event listeners), and
65
+ * returns a `reset` to call in `afterEach`.
66
+ *
67
+ * Tests may assign a partial `window`. The shim keeps its own shape and copies
68
+ * `location`, `localStorage` and the listener functions from the assigned
69
+ * object, because libraries such as axios and PostHog read those at import
70
+ * time. It installs no `document`: Vue and PrismJS treat one as a full DOM.
71
+ *
72
+ * @example
73
+ * // test-setup.ts, preloaded through bunfig.toml
74
+ * const resetWindow = installWindowShim()
75
+ * afterEach(resetWindow)
76
+ */
77
+ export function installWindowShim(href = 'http://localhost/'): () => void {
78
+ const shim: ShimWindow = {
79
+ location: locationOf(href),
80
+ addEventListener: noop,
81
+ removeEventListener: noop,
82
+ localStorage: createTestStorage()
83
+ }
84
+
85
+ const assign = (value: unknown) => {
86
+ if (!isObject(value)) return
87
+ if (value.localStorage) shim.localStorage = value.localStorage as Storage
88
+ shim.location =
89
+ isObject(value.location) && typeof value.location.href === 'string'
90
+ ? (value.location as ShimLocation)
91
+ : locationOf(href)
92
+ shim.addEventListener =
93
+ typeof value.addEventListener === 'function'
94
+ ? (value.addEventListener as ShimWindow['addEventListener'])
95
+ : noop
96
+ shim.removeEventListener =
97
+ typeof value.removeEventListener === 'function'
98
+ ? (value.removeEventListener as ShimWindow['removeEventListener'])
99
+ : noop
100
+ }
101
+
102
+ const install = () => {
103
+ Object.defineProperty(globalThis, 'window', {
104
+ configurable: true,
105
+ enumerable: true,
106
+ get: () => shim,
107
+ set: assign
108
+ })
109
+ }
110
+
111
+ install()
112
+ return () => {
113
+ shim.location = locationOf(href)
114
+ shim.addEventListener = noop
115
+ shim.removeEventListener = noop
116
+ shim.localStorage = createTestStorage()
117
+ install()
118
+ }
119
+ }
package/src/zod/index.ts CHANGED
@@ -97,6 +97,26 @@ export const zJsonObject: z.ZodType<JsonObject> = z.record(
97
97
  zJsonValue
98
98
  )
99
99
 
100
+ /** An E.164 phone number: `+`, then 7 to 15 digits with no leading zero. */
101
+ export const E164_REGEX = /^\+[1-9]\d{6,14}$/
102
+
103
+ /**
104
+ * An optional E.164 phone number for forms: a cleared input submits `''`,
105
+ * which means "no number", so `''` and `null` both parse to `null`.
106
+ *
107
+ * Deliberately not `union([regex, literal('')])`: that serializes the empty
108
+ * literal as `enum: [""]`, which Gemini function declarations reject.
109
+ */
110
+ export const zOptionalE164 = z
111
+ .string()
112
+ .refine(
113
+ (value) => value === '' || E164_REGEX.test(value),
114
+ 'Phone number must be in E.164 format (e.g. +14155551234)'
115
+ )
116
+ .describe('E.164 phone number (e.g. +14155551234); empty string clears it')
117
+ .nullable()
118
+ .transform((value) => value || null)
119
+
100
120
  /** `JSON.parse` + `schema.safeParse` without throwing. */
101
121
  export function parseJson<S extends z.ZodType>(
102
122
  text: string,
@@ -6,7 +6,7 @@ function pipeInput(def: z.core.$ZodPipeDef): Schema {
6
6
  return defOf(def.in).type === 'transform' ? def.out : def.in
7
7
  }
8
8
 
9
- function unwrapOnce(def: Def): Schema | undefined {
9
+ export function unwrapOnce(def: Def): Schema | undefined {
10
10
  switch (def.type) {
11
11
  case 'optional':
12
12
  case 'nullable':