hanzio 2.2.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 (116) hide show
  1. package/README.md +133 -7
  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 +22 -6
  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/typedswitch/index.d.ts +55 -34
  65. package/dist/zod/index.d.ts +10 -0
  66. package/dist/zod/introspect.d.ts +2 -1
  67. package/package.json +97 -2
  68. package/src/api-wrapper/README.md +57 -2
  69. package/src/api-wrapper/SKILL.md +256 -0
  70. package/src/api-wrapper/connection-auth.ts +142 -0
  71. package/src/cli/README.md +220 -0
  72. package/src/cli/fields.ts +213 -0
  73. package/src/cli/index.ts +532 -0
  74. package/src/format/README.md +82 -2
  75. package/src/format/index.ts +148 -0
  76. package/src/format/phone.ts +43 -0
  77. package/src/http/README.md +25 -0
  78. package/src/http/index.ts +94 -0
  79. package/src/i18n/README.md +206 -3
  80. package/src/i18n/catalog.ts +139 -0
  81. package/src/i18n/check.ts +376 -0
  82. package/src/i18n/cli.ts +90 -0
  83. package/src/i18n/content-rules.ts +179 -0
  84. package/src/i18n/i18next.ts +89 -0
  85. package/src/i18n/index.ts +34 -0
  86. package/src/i18n/locales.ts +135 -0
  87. package/src/i18n/vite.ts +40 -0
  88. package/src/i18n/vue.ts +82 -0
  89. package/src/index.ts +1 -1
  90. package/src/iso/README.md +44 -0
  91. package/src/iso/countries.ts +272 -0
  92. package/src/iso/currencies.ts +172 -0
  93. package/src/iso/index.ts +8 -0
  94. package/src/iso/languages.ts +208 -0
  95. package/src/iso/phone-language.ts +46 -0
  96. package/src/promise/index.ts +100 -0
  97. package/src/redis/README.md +112 -0
  98. package/src/redis/client.ts +239 -0
  99. package/src/redis/concurrency-limit.ts +591 -0
  100. package/src/redis/index.ts +15 -0
  101. package/src/redis/lua-script.ts +43 -0
  102. package/src/secrets/README.md +5 -1
  103. package/src/secrets/infisical-identity-file.ts +69 -0
  104. package/src/secrets/infisical.ts +18 -3
  105. package/src/string/index.ts +59 -0
  106. package/src/testing/README.md +119 -0
  107. package/src/testing/connection-auth-contract.ts +303 -0
  108. package/src/testing/helpers.ts +41 -0
  109. package/src/testing/index.ts +12 -0
  110. package/src/testing/module-mocker.ts +50 -0
  111. package/src/testing/prisma.ts +78 -0
  112. package/src/testing/window.ts +119 -0
  113. package/src/typedswitch/README.md +55 -39
  114. package/src/typedswitch/index.ts +97 -83
  115. package/src/zod/index.ts +20 -0
  116. package/src/zod/introspect.ts +1 -1
@@ -1,261 +1,24 @@
1
+ import {
2
+ zodToTypeString2,
3
+ zodToExample2,
4
+ unwrapSchema2,
5
+ getEnumValues2,
6
+ getObjectShape2,
7
+ zodDefaults2
8
+ } from "../../index-ww09bm49.js";
1
9
  import {
2
10
  toArray2
3
- } from "../../index-hy7kbb99.js";
11
+ } from "../../index-5tzqfmnj.js";
4
12
  import {
5
13
  ok2,
6
14
  err2
7
- } from "../../index-0zvtcbzy.js";
15
+ } from "../../index-6f2w90pp.js";
8
16
  import {
9
17
  isRecord2
10
18
  } from "../../index-8r2vdzjg.js";
11
19
 
12
20
  // src/zod/index.ts
13
21
  import { z as z3 } from "zod";
14
-
15
- // src/zod/zodToTypeString.ts
16
- var visiting = new Set;
17
- function walk(schema, onCycle, fn) {
18
- if (visiting.has(schema))
19
- return onCycle;
20
- visiting.add(schema);
21
- try {
22
- return fn(defOf(schema));
23
- } finally {
24
- visiting.delete(schema);
25
- }
26
- }
27
- function defOf(schema) {
28
- return schema._zod.def;
29
- }
30
- function pipeTarget(def) {
31
- return defOf(def.out).type === "transform" ? def.in : def.out;
32
- }
33
- function literal(value) {
34
- if (typeof value === "string")
35
- return JSON.stringify(value);
36
- return typeof value === "bigint" ? `${value}n` : String(value);
37
- }
38
- function wrap(type) {
39
- return /[|&]/.test(type) ? `(${type})` : type;
40
- }
41
- function zodToTypeString(schema, indent = 0) {
42
- return walk(schema, "unknown", (def) => {
43
- const pad = " ".repeat(indent);
44
- const innerPad = " ".repeat(indent + 1);
45
- const render = (inner) => zodToTypeString(inner, indent);
46
- switch (def.type) {
47
- case "object": {
48
- const entries = Object.entries(def.shape);
49
- if (entries.length === 0)
50
- return "{}";
51
- const fields = entries.map(([key, value]) => {
52
- const valueDef = defOf(value);
53
- const isOptional = valueDef.type === "optional";
54
- const typeStr = zodToTypeString(isOptional ? valueDef.innerType : value, indent + 1);
55
- return `${innerPad}${key}${isOptional ? "?" : ""}: ${typeStr}`;
56
- });
57
- return `{
58
- ${fields.join(`
59
- `)}
60
- ${pad}}`;
61
- }
62
- case "array":
63
- return `${wrap(render(def.element))}[]`;
64
- case "tuple": {
65
- const items = def.items.map(render);
66
- if (def.rest)
67
- items.push(`...${wrap(render(def.rest))}[]`);
68
- return `[${items.join(", ")}]`;
69
- }
70
- case "string":
71
- case "number":
72
- case "bigint":
73
- case "boolean":
74
- case "symbol":
75
- case "null":
76
- case "undefined":
77
- case "void":
78
- case "never":
79
- case "any":
80
- case "unknown":
81
- return def.type;
82
- case "date":
83
- return "Date";
84
- case "record":
85
- return `Record<${render(def.keyType)}, ${render(def.valueType)}>`;
86
- case "map":
87
- return `Map<${render(def.keyType)}, ${render(def.valueType)}>`;
88
- case "set":
89
- return `Set<${render(def.valueType)}>`;
90
- case "promise":
91
- return `Promise<${render(def.innerType)}>`;
92
- case "enum":
93
- return Object.values(def.entries).map(literal).join(" | ");
94
- case "literal":
95
- return def.values.map(literal).join(" | ");
96
- case "union":
97
- return def.options.map(render).join(" | ");
98
- case "intersection":
99
- return `${wrap(render(def.left))} & ${wrap(render(def.right))}`;
100
- case "optional":
101
- return `${render(def.innerType)} | undefined`;
102
- case "nullable":
103
- return `${render(def.innerType)} | null`;
104
- case "nonoptional": {
105
- const innerDef = defOf(def.innerType);
106
- return render(innerDef.type === "optional" ? innerDef.innerType : def.innerType);
107
- }
108
- case "default":
109
- case "prefault":
110
- case "catch":
111
- case "readonly":
112
- return render(def.innerType);
113
- case "lazy":
114
- return render(def.getter());
115
- case "pipe":
116
- return render(pipeTarget(def));
117
- default:
118
- return "unknown";
119
- }
120
- });
121
- }
122
- function zodToExample(schema) {
123
- return walk(schema, null, (def) => {
124
- switch (def.type) {
125
- case "object": {
126
- const result = {};
127
- for (const [key, value] of Object.entries(def.shape)) {
128
- if (defOf(value).type !== "optional") {
129
- result[key] = zodToExample(value);
130
- }
131
- }
132
- return result;
133
- }
134
- case "array":
135
- return [zodToExample(def.element)];
136
- case "set":
137
- return [zodToExample(def.valueType)];
138
- case "tuple":
139
- return def.items.map(zodToExample);
140
- case "string":
141
- return "";
142
- case "number":
143
- case "bigint":
144
- return 0;
145
- case "boolean":
146
- return false;
147
- case "date":
148
- return new Date(0).toISOString();
149
- case "enum":
150
- return Object.values(def.entries)[0];
151
- case "literal":
152
- return def.values[0];
153
- case "union":
154
- return zodToExample(def.options[0]);
155
- case "intersection": {
156
- const left = zodToExample(def.left);
157
- const right = zodToExample(def.right);
158
- return isRecord2(left) && isRecord2(right) ? { ...left, ...right } : left;
159
- }
160
- case "optional":
161
- case "nullable":
162
- case "nonoptional":
163
- case "default":
164
- case "prefault":
165
- case "catch":
166
- case "readonly":
167
- case "promise":
168
- return zodToExample(def.innerType);
169
- case "lazy":
170
- return zodToExample(def.getter());
171
- case "pipe":
172
- return zodToExample(pipeTarget(def));
173
- case "record":
174
- case "map":
175
- return {};
176
- default:
177
- return null;
178
- }
179
- });
180
- }
181
-
182
- // src/zod/introspect.ts
183
- function pipeInput(def) {
184
- return defOf(def.in).type === "transform" ? def.out : def.in;
185
- }
186
- function unwrapOnce(def) {
187
- switch (def.type) {
188
- case "optional":
189
- case "nullable":
190
- case "default":
191
- case "prefault":
192
- case "nonoptional":
193
- case "catch":
194
- case "readonly":
195
- return def.innerType;
196
- case "lazy":
197
- return def.getter();
198
- case "pipe":
199
- return pipeInput(def);
200
- default:
201
- return;
202
- }
203
- }
204
- function unwrapSchema(schema) {
205
- const inner = unwrapOnce(defOf(schema));
206
- return inner ? unwrapSchema(inner) : schema;
207
- }
208
- function getEnumValues(schema) {
209
- const def = defOf(unwrapSchema(schema));
210
- return def.type === "enum" ? Object.values(def.entries).map(String) : undefined;
211
- }
212
- function getObjectShape(schema) {
213
- const def = defOf(unwrapSchema(schema));
214
- return def.type === "object" ? def.shape : undefined;
215
- }
216
- function zodDefaults(schema) {
217
- return defaultFor(schema);
218
- }
219
- function defaultFor(schema) {
220
- return walk(schema, undefined, (def) => {
221
- switch (def.type) {
222
- case "default":
223
- case "prefault":
224
- return def.defaultValue;
225
- case "optional":
226
- return;
227
- case "nullable":
228
- return null;
229
- case "object": {
230
- const result = {};
231
- for (const [key, child] of Object.entries(def.shape)) {
232
- result[key] = defaultFor(child);
233
- }
234
- return result;
235
- }
236
- case "array":
237
- return [];
238
- case "string":
239
- return "";
240
- case "number":
241
- return 0;
242
- case "bigint":
243
- return 0n;
244
- case "boolean":
245
- return false;
246
- case "enum":
247
- return Object.values(def.entries)[0];
248
- case "literal":
249
- return def.values[0];
250
- case "union":
251
- return def.options[0] && defaultFor(def.options[0]);
252
- default: {
253
- const inner = unwrapOnce(def);
254
- return inner && defaultFor(inner);
255
- }
256
- }
257
- });
258
- }
259
22
  // src/zod/jsonToZodSchemaMapper.ts
260
23
  import { z } from "zod";
261
24
  function jsonToZodSchemaMapper(jsonObject, zodSchema, config) {
@@ -342,6 +105,8 @@ var zJsonValue = z3.lazy(() => z3.union([
342
105
  zJsonObject
343
106
  ]));
344
107
  var zJsonObject = z3.record(z3.string(), zJsonValue);
108
+ var E164_REGEX = /^\+[1-9]\d{6,14}$/;
109
+ var zOptionalE164 = z3.string().refine((value) => value === "" || E164_REGEX.test(value), "Phone number must be in E.164 format (e.g. +14155551234)").describe("E.164 phone number (e.g. +14155551234); empty string clears it").nullable().transform((value) => value || null);
345
110
  function parseJson(text, schema) {
346
111
  let value;
347
112
  try {
@@ -359,12 +124,13 @@ function zSingleOrArray(schema) {
359
124
  }
360
125
  var normalizeSingleOrArray = toArray2;
361
126
  export {
127
+ E164_REGEX,
362
128
  asId,
363
129
  createIdHelpers,
364
130
  createMinMaxFilter,
365
131
  createZId,
366
- getEnumValues,
367
- getObjectShape,
132
+ getEnumValues2 as getEnumValues,
133
+ getObjectShape2 as getObjectShape,
368
134
  jsonToZodSchemaMapper,
369
135
  normalizeSingleOrArray,
370
136
  parseId,
@@ -372,12 +138,13 @@ export {
372
138
  safeParseId,
373
139
  toGtLtFilter,
374
140
  toGteLteFilter,
375
- unwrapSchema,
141
+ unwrapSchema2 as unwrapSchema,
376
142
  zJsonObject,
377
143
  zJsonValue,
144
+ zOptionalE164,
378
145
  zSingleOrArray,
379
- zodDefaults,
146
+ zodDefaults2 as zodDefaults,
380
147
  zodMinMaxFilter,
381
- zodToExample,
382
- zodToTypeString
148
+ zodToExample2 as zodToExample,
149
+ zodToTypeString2 as zodToTypeString
383
150
  };
@@ -79,3 +79,24 @@ export declare function initials(name: string | null | undefined, options?: {
79
79
  }): string;
80
80
  /** `JSON.parse` that returns a `Result` instead of throwing. */
81
81
  export declare function safeJsonParse<T = unknown>(text: string): Result<T, SyntaxError>;
82
+ /** Escapes `& < > " '` for HTML or XML text and attribute values. */
83
+ export declare function escapeHtml(text: string): string;
84
+ /**
85
+ * Removes emoji and tidies the spacing they leave, so `'Hi there 👋, welcome'`
86
+ * becomes `'Hi there, welcome'`. Use it for text that is spoken (TTS drops
87
+ * emoji from the audio but not from the transcript). Text without emoji is
88
+ * returned unchanged.
89
+ */
90
+ export declare function stripEmoji(text: string): string;
91
+ /**
92
+ * Lowercased extension of a file name or path, without the dot. `undefined`
93
+ * when there is none; like `path.extname`, a leading dot (`.env`) is not one.
94
+ */
95
+ export declare function fileExtension(filename: string): string | undefined;
96
+ /**
97
+ * Normalizes a value the way a JSON round trip does: `undefined` properties
98
+ * are dropped, `Date`s become ISO strings and `NaN` becomes `null`. Top-level
99
+ * `undefined` becomes `null`. Use it before storing loosely typed data in a
100
+ * JSON column. Throws on `bigint` and circular references.
101
+ */
102
+ export declare function toJsonValue(value: unknown): JsonValue;
@@ -0,0 +1,43 @@
1
+ export type ConnectionAuthContractInput = {
2
+ reset: () => void;
3
+ create: (fetchImpl: typeof fetch, variant?: string) => {
4
+ read: () => Promise<unknown>;
5
+ login: (options?: {
6
+ signal?: AbortSignal;
7
+ }) => Promise<string>;
8
+ };
9
+ identityChanges: string[];
10
+ isLogin: (url: string) => boolean;
11
+ loginResponse: (token: string) => Response;
12
+ readResponse: () => Response;
13
+ tokenHeader: string;
14
+ };
15
+ /**
16
+ * Registers the races every client built on `ConnectionAuthCache`
17
+ * (`hanzio/api-wrapper/connection-auth`) must survive, run through the real
18
+ * client and its transport: shared refreshes, rotated credentials, late 401s,
19
+ * aborted waiters and failed fresh logins. Await it at the top level of a
20
+ * `bun:test` file.
21
+ *
22
+ * - `create(fetch, variant)` builds a client on the given fetch. `variant` is
23
+ * one of `identityChanges` (a credential change that must force a new login),
24
+ * `'settings'` (a non-auth setting that must reuse the login), `'connection'`
25
+ * (another connection) or `'secret'` (a rotated secret).
26
+ * - `read()` makes one authenticated request; on a 401 it calls `reject` and
27
+ * retries once. `login()` forces a fresh login and resolves its token.
28
+ * - `reset()` clears the cache between tests.
29
+ * - `tokenHeader` is where requests carry the token; `'authorization'` means
30
+ * `Bearer <token>`.
31
+ *
32
+ * @example
33
+ * await connectionAuthContract({
34
+ * reset: () => authCache.clear(),
35
+ * create: (fetch, variant) => new ShopApi(credentialsFor(variant), fetch),
36
+ * identityChanges: ['apiKey', 'secret'],
37
+ * isLogin: (url) => url.endsWith('/oauth/token'),
38
+ * loginResponse: (token) => Response.json({ access_token: token, expires_in: 3600 }),
39
+ * readResponse: () => Response.json({ items: [] }),
40
+ * tokenHeader: 'authorization'
41
+ * })
42
+ */
43
+ export declare function connectionAuthContract(input: ConnectionAuthContractInput): Promise<void>;
@@ -0,0 +1,26 @@
1
+ type LogMethod = (...args: unknown[]) => void;
2
+ export type NoopLogger = {
3
+ trace: LogMethod;
4
+ debug: LogMethod;
5
+ info: LogMethod;
6
+ warn: LogMethod;
7
+ error: LogMethod;
8
+ fatal: LogMethod;
9
+ bind: (...args: unknown[]) => NoopLogger;
10
+ child: (...args: unknown[]) => NoopLogger;
11
+ };
12
+ /** A logger that discards everything; `bind` and `child` return itself. */
13
+ export declare const noopLogger: NoopLogger;
14
+ /**
15
+ * Reads one argument from a mock's call history as `T`. Bun types
16
+ * `mock.calls` as `never[]` for loosely typed mocks.
17
+ *
18
+ * @example
19
+ * const input = mockCallArg<CreateUserInput>(createUser) // first call, first argument
20
+ */
21
+ export declare function mockCallArg<T>(fn: {
22
+ mock: {
23
+ calls: unknown[][];
24
+ };
25
+ }, callIndex?: number, argIndex?: number): T;
26
+ export {};
@@ -0,0 +1,5 @@
1
+ export { type ConnectionAuthContractInput, connectionAuthContract } from './connection-auth-contract';
2
+ export { mockCallArg, type NoopLogger, noopLogger } from './helpers';
3
+ export { ModuleMocker } from './module-mocker';
4
+ export { createPrismaMock, type DeepMockedPrisma, type PrismaMethodMock } from './prisma';
5
+ export { createTestStorage, installWindowShim } from './window';
@@ -0,0 +1,30 @@
1
+ type ModuleExports = Record<string, unknown>;
2
+ /**
3
+ * `mock.module` that can be undone. Bun replaces a mocked module for the whole
4
+ * process and `mock.restore()` does not bring it back
5
+ * (https://github.com/oven-sh/bun/issues/7823), so one suite's stub silently
6
+ * leaks into every later suite.
7
+ *
8
+ * Mocks spread the module's real exports first, so exports you don't override
9
+ * keep working, and `clear()` re-registers the originals. Call it in
10
+ * `afterEach` or `afterAll`.
11
+ *
12
+ * Bun resolves specifiers relative to the file that registers the mock, so
13
+ * pass absolute paths or package names. A `resolve` function maps your own
14
+ * shorthand (an `@/` alias, a source-root-relative path) to absolute paths.
15
+ *
16
+ * @example
17
+ * const moduleMocker = new ModuleMocker((specifier) =>
18
+ * specifier.startsWith('@/') ? path.resolve(srcDir, specifier.slice(2)) : specifier
19
+ * )
20
+ * await moduleMocker.mock('@/lib/posthog', () => ({ captureException: mock() }))
21
+ * afterAll(() => moduleMocker.clear())
22
+ */
23
+ export declare class ModuleMocker {
24
+ private readonly resolve;
25
+ private restorers;
26
+ constructor(resolve?: (specifier: string) => string);
27
+ mock(specifier: string, renderMocks: () => ModuleExports): Promise<void>;
28
+ clear(): void;
29
+ }
30
+ export {};
@@ -0,0 +1,30 @@
1
+ import type { Mock, mock } from 'bun:test';
2
+ export type PrismaMethodMock = Mock<(...args: unknown[]) => Promise<unknown>>;
3
+ /**
4
+ * A Prisma client where every model delegate and every method is a Bun mock,
5
+ * created on first access, like `jest-mock-extended`'s `mockDeep()`.
6
+ * `$`-prefixed client methods (`$transaction`, `$queryRaw`) are mocks too.
7
+ */
8
+ export type DeepMockedPrisma<Client> = {
9
+ [K in keyof Client]: Client[K] extends (...args: never[]) => unknown ? PrismaMethodMock : {
10
+ [Method in keyof Client[K]]: PrismaMethodMock;
11
+ };
12
+ } & {
13
+ /** Resets every mock created so far. Call it in `beforeEach`. */
14
+ $reset: () => void;
15
+ };
16
+ /**
17
+ * Builds a deep auto-mock of a Prisma client from Bun's `mock`. Register it
18
+ * with `mock.module` in place of the module that exports your client.
19
+ *
20
+ * @example
21
+ * import { mock } from 'bun:test'
22
+ * import type { prisma } from '../lib/prisma'
23
+ *
24
+ * export const prismaMock = createPrismaMock<typeof prisma>(mock)
25
+ * mock.module(prismaModulePath, () => ({ ...prismaExports, prisma: prismaMock }))
26
+ *
27
+ * prismaMock.user.findUnique.mockResolvedValue({ id: 'u1' })
28
+ * expect(prismaMock.user.findUnique).toHaveBeenCalledWith({ where: { id: 'u1' } })
29
+ */
30
+ export declare function createPrismaMock<Client>(createMock: typeof mock): DeepMockedPrisma<Client>;
@@ -0,0 +1,18 @@
1
+ /** A complete in-memory `Storage` (`localStorage` / `sessionStorage`). */
2
+ export declare function createTestStorage(): Storage;
3
+ /**
4
+ * Installs a minimal `globalThis.window` for unit tests of browser-facing code
5
+ * that only probes `window` (`location`, `localStorage`, event listeners), and
6
+ * returns a `reset` to call in `afterEach`.
7
+ *
8
+ * Tests may assign a partial `window`. The shim keeps its own shape and copies
9
+ * `location`, `localStorage` and the listener functions from the assigned
10
+ * object, because libraries such as axios and PostHog read those at import
11
+ * time. It installs no `document`: Vue and PrismJS treat one as a full DOM.
12
+ *
13
+ * @example
14
+ * // test-setup.ts, preloaded through bunfig.toml
15
+ * const resetWindow = installWindowShim()
16
+ * afterEach(resetWindow)
17
+ */
18
+ export declare function installWindowShim(href?: string): () => void;
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Supports two input modes:
5
5
  *
6
- * 1. STRING INPUT - switch on a string/enum value directly:
6
+ * 1. PLAIN VALUE INPUT - switch on a string, number or boolean value directly.
7
+ * Cases are keyed by `String(value)`:
7
8
  *
8
9
  * @example
9
10
  * const status: 'success' | 'error' = 'success'
@@ -19,6 +20,9 @@
19
20
  * success: (val) => 'handled success',
20
21
  * }, (val) => 'default handler')
21
22
  *
23
+ * typedSwitch(code, { 200: (c) => 'ok', 404: (c) => 'missing' }) // c: 200, 404
24
+ * typedSwitch(flag, { true: () => 'on', false: () => 'off' })
25
+ *
22
26
  *
23
27
  * 2. OBJECT INPUT - switch on a discriminated union with custom key:
24
28
  *
@@ -46,20 +50,27 @@
46
50
  * })
47
51
  *
48
52
  *
49
- * 3. WITH CONSTRAINT - use `typedSwitch<T>` to enforce return type:
53
+ * 3. GROUPED CASES - share one handler between several values with
54
+ * `typedSwitchGroup`. The handler receives the union of the grouped
55
+ * members, and the spread keeps exhaustiveness and return type inference:
50
56
  *
51
57
  * @example
52
- * interface HasId { id: string }
58
+ * typedSwitch(event, 'type', {
59
+ * ...typedSwitchGroup(event, 'type', ['click', 'scroll'], (e) => e.type), // e: ClickEvent | ScrollEvent
60
+ * key: (e) => e.key,
61
+ * })
53
62
  *
54
- * // All handlers must return something with { id: string }
55
- * typedSwitch<HasId>(status, {
56
- * success: () => ({ id: '123', name: 'John' }), // ✓ OK
57
- * error: () => ({ id: '456' }), // ✓ OK
63
+ * typedSwitch(status, {
64
+ * ...typedSwitchGroup(status, ['success', 'pending'], (s) => 'fine'), // s: 'success' | 'pending'
65
+ * error: () => 'failed',
58
66
  * })
59
67
  *
60
- * // Type error - missing 'id' property
61
- * typedSwitch<HasId>(status, {
62
- * success: () => ({ name: 'John' }), // ✗ Error!
68
+ *
69
+ * 4. ENFORCING A RETURN TYPE - annotate the result:
70
+ *
71
+ * @example
72
+ * const result: { id: string } = typedSwitch(status, {
73
+ * success: () => ({ id: '123' }),
63
74
  * error: () => ({ id: '456' }),
64
75
  * })
65
76
  */
@@ -95,30 +106,40 @@ type PartialObjectCases<T, K extends keyof T, R> = {
95
106
  export type ObjectSwitchCases<T, K extends keyof T, R> = T[K] extends Discriminant ? ObjectCases<T, K, R | Promise<R>> : never;
96
107
  /** Partial cases for object input with custom key (when default is provided) */
97
108
  export type PartialObjectSwitchCases<T, K extends keyof T, R> = T[K] extends Discriminant ? PartialObjectCases<T, K, R | Promise<R>> : never;
98
- export declare function typedSwitch<T extends string, Cases extends {
99
- [K in T]: (value: K) => unknown;
100
- }>(value: T, cases: Cases): RawCasesReturnType<Cases>;
101
- export declare function typedSwitch<T extends string, Cases extends {
102
- [K in T]?: (value: K) => unknown;
103
- }, DefaultCase extends (value: Exclude<T, HandledCaseKeys<Cases>>) => unknown>(value: T, cases: Cases, defaultCase: DefaultCase): RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>;
104
- export declare function typedSwitch<T extends object, K extends keyof T, Cases extends ObjectCases<T, K, unknown>>(value: T, key: K, cases: Cases): RawCasesReturnType<Cases>;
105
- export declare function typedSwitch<T extends object, K extends keyof T, Cases extends PartialObjectCases<T, K, unknown>, DefaultCase extends (value: Exclude<T, Record<K, ValuesForKeys<T[K], HandledCaseKeys<Cases>>>>) => unknown>(value: T, key: K, cases: Cases, defaultCase: DefaultCase): RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>;
106
- export declare function typedSwitch<Constraint, T extends string = string>(value: T, cases: {
107
- [K in T]: (value: K) => Constraint;
108
- }): Constraint;
109
- export declare function typedSwitch<Constraint, T extends string = string>(value: T, cases: {
110
- [K in T]: (value: K) => Constraint | Promise<Constraint>;
111
- }): Constraint | Promise<Constraint>;
112
- export declare function typedSwitch<Constraint, T extends string = string>(value: T, cases: {
113
- [K in T]?: (value: K) => Constraint;
114
- }, defaultCase: (value: T) => Constraint): Constraint;
115
- export declare function typedSwitch<Constraint, T extends string = string>(value: T, cases: {
116
- [K in T]?: (value: K) => Constraint | Promise<Constraint>;
117
- }, defaultCase: (value: T) => Constraint | Promise<Constraint>): Constraint | Promise<Constraint>;
118
- export declare function typedSwitch<Constraint, T extends Record<string, unknown> = Record<string, unknown>, K extends keyof T = keyof T>(value: T, key: K, cases: ObjectCases<T, K, Constraint>): Constraint;
119
- export declare function typedSwitch<Constraint, T extends Record<string, unknown> = Record<string, unknown>, K extends keyof T = keyof T>(value: T, key: K, cases: ObjectCases<T, K, Constraint | Promise<Constraint>>): Constraint | Promise<Constraint>;
120
- export declare function typedSwitch<Constraint, T extends Record<string, unknown> = Record<string, unknown>, K extends keyof T = keyof T>(value: T, key: K, cases: PartialObjectCases<T, K, Constraint>, defaultCase: (value: T) => Constraint): Constraint;
121
- export declare function typedSwitch<Constraint, T extends Record<string, unknown> = Record<string, unknown>, K extends keyof T = keyof T>(value: T, key: K, cases: PartialObjectCases<T, K, Constraint | Promise<Constraint>>, defaultCase: (value: T) => Constraint | Promise<Constraint>): Constraint | Promise<Constraint>;
109
+ /**
110
+ * Rejects case keys that are not values of the switched union, so a typo
111
+ * fails to compile instead of silently falling through to the default.
112
+ */
113
+ type NoUnknownCases<Cases, Allowed extends string> = {
114
+ [P in keyof Cases as `${P & (string | number)}` extends Allowed ? never : P]: never;
115
+ };
116
+ export declare function typedSwitch<T extends Discriminant, Cases extends {
117
+ [V in T as `${V}`]: (value: V) => unknown;
118
+ } & NoUnknownCases<Cases, `${T}`>>(value: T, cases: Cases): RawCasesReturnType<Cases>;
119
+ export declare function typedSwitch<T extends Discriminant, Cases extends {
120
+ [V in T as `${V}`]?: (value: V) => unknown;
121
+ } & NoUnknownCases<Cases, `${T}`>, DefaultCase extends (value: Exclude<T, ValuesForKeys<T, HandledCaseKeys<Cases>>>) => unknown>(value: T, cases: Cases, defaultCase: DefaultCase): RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>;
122
+ export declare function typedSwitch<T extends object, K extends keyof T, Cases extends ObjectCases<T, K, unknown> & NoUnknownCases<Cases, `${Extract<T[K], Discriminant>}`>>(value: T, key: K, cases: Cases): RawCasesReturnType<Cases>;
123
+ export declare function typedSwitch<T extends object, K extends keyof T, Cases extends PartialObjectCases<T, K, unknown> & NoUnknownCases<Cases, `${Extract<T[K], Discriminant>}`>, DefaultCase extends (value: Exclude<T, Record<K, ValuesForKeys<T[K], HandledCaseKeys<Cases>>>>) => unknown>(value: T, key: K, cases: Cases, defaultCase: DefaultCase): RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>;
124
+ /**
125
+ * Builds cases that share one handler, for spreading into `typedSwitch` cases.
126
+ * Pass the switched value so its type is inferred; the handler receives the
127
+ * union of the grouped members. A case listed after the spread replaces the
128
+ * grouped handler for that key; listing it before the spread is a type error.
129
+ * If two groups list the same key, the later group wins.
130
+ *
131
+ * @example
132
+ * typedSwitch(event, 'type', {
133
+ * ...typedSwitchGroup(event, 'type', ['click', 'scroll'], (e) => e.type),
134
+ * key: (e) => e.key,
135
+ * })
136
+ */
137
+ export declare function typedSwitchGroup<T extends Discriminant, const Keys extends readonly T[], R>(value: T, keys: Keys, handler: (value: Keys[number]) => R): {
138
+ [V in Keys[number] as `${V}`]: (value: V) => R;
139
+ };
140
+ export declare function typedSwitchGroup<T extends object, K extends keyof T, const Keys extends readonly Extract<T[K], Discriminant>[], R>(value: T, key: K, keys: Keys, handler: (value: Extract<T, Record<K, Keys[number]>>) => R): {
141
+ [V in Keys[number] as `${V}`]: (value: Extract<T, Record<K, V>>) => R;
142
+ };
122
143
  /**
123
144
  * Exhaustiveness helper for regular `switch` statements. Place it in the
124
145
  * `default` branch: it only type-checks when every case has been handled, and
@@ -30,6 +30,16 @@ export declare function createIdHelpers<TypeNames extends string>(): {
30
30
  /** Any JSON value (no `undefined`, `NaN` or `Infinity`). */
31
31
  export declare const zJsonValue: z.ZodType<JsonValue>;
32
32
  export declare const zJsonObject: z.ZodType<JsonObject>;
33
+ /** An E.164 phone number: `+`, then 7 to 15 digits with no leading zero. */
34
+ export declare const E164_REGEX: RegExp;
35
+ /**
36
+ * An optional E.164 phone number for forms: a cleared input submits `''`,
37
+ * which means "no number", so `''` and `null` both parse to `null`.
38
+ *
39
+ * Deliberately not `union([regex, literal('')])`: that serializes the empty
40
+ * literal as `enum: [""]`, which Gemini function declarations reject.
41
+ */
42
+ export declare const zOptionalE164: z.ZodPipe<z.ZodNullable<z.ZodString>, z.ZodTransform<string | null, string | null>>;
33
43
  /** `JSON.parse` + `schema.safeParse` without throwing. */
34
44
  export declare function parseJson<S extends z.ZodType>(text: string, schema: S): Result<z.output<S>, SyntaxError | z.ZodError>;
35
45
  export declare function zSingleOrArray<T extends z.ZodType>(schema: T): z.ZodUnion<readonly [z.ZodArray<T>, T]>;
@@ -1,5 +1,6 @@
1
1
  import type { z } from 'zod';
2
- import { type Schema } from './zodToTypeString';
2
+ import { type Def, type Schema } from './zodToTypeString';
3
+ export declare function unwrapOnce(def: Def): Schema | undefined;
3
4
  /**
4
5
  * Strips optional/nullable/default/prefault/nonoptional/catch/readonly/lazy
5
6
  * wrappers and pipes (input side) until the schema that carries the shape.