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.
- package/README.md +114 -3
- package/dist/api-wrapper/connection-auth.d.ts +61 -0
- package/dist/cli/fields.d.ts +30 -0
- package/dist/cli/index.d.ts +122 -0
- package/dist/format/index.d.ts +91 -0
- package/dist/format/phone.d.ts +19 -0
- package/dist/http/index.d.ts +14 -0
- package/dist/i18n/catalog.d.ts +47 -0
- package/dist/i18n/check.d.ts +45 -0
- package/dist/i18n/cli.d.ts +2 -0
- package/dist/i18n/content-rules.d.ts +55 -0
- package/dist/i18n/i18next.d.ts +39 -0
- package/dist/i18n/index.d.ts +16 -1
- package/dist/i18n/locales.d.ts +62 -0
- package/dist/i18n/vite.d.ts +16 -0
- package/dist/i18n/vue.d.ts +93 -0
- package/dist/{index-kj1phgrx.js → index-29mmh343.js} +24 -1
- package/dist/{index-zbj9s3s3.js → index-2kz392vw.js} +1 -1
- package/dist/index-5n2e326q.js +409 -0
- package/dist/{index-hy7kbb99.js → index-5tzqfmnj.js} +1 -1
- package/dist/{index-0zvtcbzy.js → index-6f2w90pp.js} +69 -1
- package/dist/index-6ragggbs.js +473 -0
- package/dist/index-mq2qgc20.js +116 -0
- package/dist/index-najbyzpr.js +99 -0
- package/dist/index-ww09bm49.js +250 -0
- package/dist/iso/countries.d.ts +262 -0
- package/dist/iso/currencies.d.ts +165 -0
- package/dist/iso/index.d.ts +4 -0
- package/dist/iso/languages.d.ts +195 -0
- package/dist/iso/phone-language.d.ts +5 -0
- package/dist/promise/index.d.ts +31 -0
- package/dist/redis/client.d.ts +49 -0
- package/dist/redis/concurrency-limit.d.ts +63 -0
- package/dist/redis/index.d.ts +3 -0
- package/dist/redis/lua-script.d.ts +13 -0
- package/dist/secrets/infisical-identity-file.d.ts +12 -0
- package/dist/src/api-wrapper/connection-auth.js +82 -0
- package/dist/src/api-wrapper/index.js +1 -1
- package/dist/src/cli/index.js +10 -0
- package/dist/src/format/index.js +79 -2
- package/dist/src/format/phone.js +21 -0
- package/dist/src/http/index.js +69 -0
- package/dist/src/i18n/check.js +15 -0
- package/dist/src/i18n/cli.js +71 -0
- package/dist/src/i18n/i18next.js +48 -0
- package/dist/src/i18n/index.js +16 -34
- package/dist/src/i18n/vite.js +25 -0
- package/dist/src/i18n/vue.js +51 -0
- package/dist/src/index.js +14 -4
- package/dist/src/iso/index.js +655 -0
- package/dist/src/redis/index.js +577 -0
- package/dist/src/secrets/index.js +78 -35
- package/dist/src/sitemap/index.js +1 -1
- package/dist/src/storage/index.js +8 -117
- package/dist/src/testing/index.js +348 -0
- package/dist/src/zod/index.js +20 -253
- package/dist/string/index.d.ts +21 -0
- package/dist/testing/connection-auth-contract.d.ts +43 -0
- package/dist/testing/helpers.d.ts +26 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/module-mocker.d.ts +30 -0
- package/dist/testing/prisma.d.ts +30 -0
- package/dist/testing/window.d.ts +18 -0
- package/dist/zod/index.d.ts +10 -0
- package/dist/zod/introspect.d.ts +2 -1
- package/package.json +96 -2
- package/src/api-wrapper/README.md +52 -0
- package/src/api-wrapper/connection-auth.ts +142 -0
- package/src/cli/README.md +220 -0
- package/src/cli/fields.ts +213 -0
- package/src/cli/index.ts +532 -0
- package/src/format/README.md +82 -2
- package/src/format/index.ts +148 -0
- package/src/format/phone.ts +43 -0
- package/src/http/README.md +25 -0
- package/src/http/index.ts +94 -0
- package/src/i18n/README.md +206 -3
- package/src/i18n/catalog.ts +139 -0
- package/src/i18n/check.ts +376 -0
- package/src/i18n/cli.ts +90 -0
- package/src/i18n/content-rules.ts +179 -0
- package/src/i18n/i18next.ts +89 -0
- package/src/i18n/index.ts +34 -0
- package/src/i18n/locales.ts +135 -0
- package/src/i18n/vite.ts +40 -0
- package/src/i18n/vue.ts +82 -0
- package/src/index.ts +1 -1
- package/src/iso/README.md +44 -0
- package/src/iso/countries.ts +272 -0
- package/src/iso/currencies.ts +172 -0
- package/src/iso/index.ts +8 -0
- package/src/iso/languages.ts +208 -0
- package/src/iso/phone-language.ts +46 -0
- package/src/promise/index.ts +100 -0
- package/src/redis/README.md +112 -0
- package/src/redis/client.ts +239 -0
- package/src/redis/concurrency-limit.ts +591 -0
- package/src/redis/index.ts +15 -0
- package/src/redis/lua-script.ts +43 -0
- package/src/secrets/README.md +5 -1
- package/src/secrets/infisical-identity-file.ts +69 -0
- package/src/secrets/infisical.ts +18 -3
- package/src/string/index.ts +59 -0
- package/src/testing/README.md +119 -0
- package/src/testing/connection-auth-contract.ts +303 -0
- package/src/testing/helpers.ts +41 -0
- package/src/testing/index.ts +12 -0
- package/src/testing/module-mocker.ts +50 -0
- package/src/testing/prisma.ts +78 -0
- package/src/testing/window.ts +119 -0
- package/src/zod/index.ts +20 -0
- 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,
|
package/src/zod/introspect.ts
CHANGED
|
@@ -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':
|