hanzio 3.1.1 → 3.2.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 CHANGED
@@ -562,6 +562,12 @@ const text = typedSwitch(response as Response, 'code', {
562
562
  404: () => 'Not found'
563
563
  })
564
564
 
565
+ // Annotate the result to keep literal return types
566
+ const tone: 'info' | 'danger' = typedSwitch(response as Response, 'code', {
567
+ 200: () => 'info',
568
+ 404: () => 'danger'
569
+ })
570
+
565
571
  const event = { type: 'scroll', offset: 4 } as Event
566
572
  typedSwitch(event, 'type', {
567
573
  ...typedSwitchGroup(event, 'type', ['click', 'scroll'], (e) => e.type)
@@ -140,8 +140,11 @@ declare const DATE_PRESETS: {
140
140
  * follows the locale: `'D MMM'` is "5 Mar" in en-GB and "Mar 5" in en-US.
141
141
  */
142
142
  export type DatePreset = keyof typeof DATE_PRESETS;
143
- /** A date in one of the `DatePreset` layouts, in local time. */
144
- export declare function formatDate(date: DateInput, preset: DatePreset, locale: string): string;
143
+ /**
144
+ * A date in one of the `DatePreset` layouts, or with any
145
+ * `Intl.DateTimeFormat` options, in local time unless they set `timeZone`.
146
+ */
147
+ export declare function formatDate(date: DateInput, format: DatePreset | Intl.DateTimeFormatOptions, locale: string): string;
145
148
  /** Medium date and short time: "5 Mar 2026, 14:30" (en-GB). */
146
149
  export declare function formatDateTime(date: DateInput, locale: string): string;
147
150
  /**
@@ -182,7 +185,7 @@ export declare function pluralize(count: number, forms: Partial<Record<Intl.LDML
182
185
  * `export const fmt = withLocale(() => uiLocales.tag(appI18n.locale()))`.
183
186
  */
184
187
  export declare function withLocale(getLocale: () => string): {
185
- formatDate: (date: DateInput, preset: "D MMM" | "D MMM YYYY" | "DD/MM/YYYY" | "DD/MM/YYYY HH:mm" | "HH:mm" | "MMMM YYYY" | "ddd" | "dddd, MMMM D") => string;
188
+ formatDate: (date: DateInput, format: "D MMM" | "D MMM YYYY" | "DD/MM/YYYY" | "DD/MM/YYYY HH:mm" | "HH:mm" | "MMMM YYYY" | "ddd" | "dddd, MMMM D" | Intl.DateTimeFormatOptions) => string;
186
189
  formatDateTime: (date: DateInput) => string;
187
190
  formatCompactTime: (date: DateInput) => string;
188
191
  formatDayDivider: (date: DateInput) => string;
@@ -193,8 +193,9 @@ var DATE_PRESETS = {
193
193
  "dddd, MMMM D": { weekday: "long", day: "numeric", month: "long" },
194
194
  "MMMM YYYY": { month: "long", year: "numeric" }
195
195
  };
196
- function formatDate(date, preset, locale) {
197
- return getDateTimeFormat(locale, DATE_PRESETS[preset]).format(toTime(date));
196
+ function formatDate(date, format, locale) {
197
+ const options = typeof format === "string" ? DATE_PRESETS[format] : format;
198
+ return getDateTimeFormat(locale, options).format(toTime(date));
198
199
  }
199
200
  function formatDateTime(date, locale) {
200
201
  return getDateTimeFormat(locale, {
@@ -249,7 +250,7 @@ function pluralize(count, forms, locale) {
249
250
  }
250
251
  function withLocale(getLocale) {
251
252
  return {
252
- formatDate: (date, preset) => formatDate(date, preset, getLocale()),
253
+ formatDate: (date, format) => formatDate(date, format, getLocale()),
253
254
  formatDateTime: (date) => formatDateTime(date, getLocale()),
254
255
  formatCompactTime: (date) => formatCompactTime(date, getLocale()),
255
256
  formatDayDivider: (date) => formatDayDivider(date, getLocale()),
@@ -12,6 +12,7 @@ import {
12
12
 
13
13
  // src/i18n/cli.ts
14
14
  import { existsSync } from "node:fs";
15
+ import * as nodeModule from "node:module";
15
16
  import { dirname, resolve } from "node:path";
16
17
  import { pathToFileURL } from "node:url";
17
18
  import { z } from "zod";
@@ -21,10 +22,41 @@ var CONFIG_NAMES = [
21
22
  "i18n.config.js",
22
23
  "i18n.config.mjs"
23
24
  ];
25
+ var BUNDLER_SUFFIXES = [
26
+ ".ts",
27
+ ".mts",
28
+ ".js",
29
+ ".mjs",
30
+ "/index.ts",
31
+ "/index.mts",
32
+ "/index.js",
33
+ "/index.mjs"
34
+ ];
35
+ function resolveLikeBundlers() {
36
+ nodeModule.registerHooks?.({
37
+ resolve(specifier, context, nextResolve) {
38
+ try {
39
+ return nextResolve(specifier, context);
40
+ } catch (error) {
41
+ const code = error.code;
42
+ const retry = specifier.startsWith(".") && (code === "ERR_MODULE_NOT_FOUND" || code === "ERR_UNSUPPORTED_DIR_IMPORT");
43
+ if (!retry)
44
+ throw error;
45
+ for (const suffix of BUNDLER_SUFFIXES) {
46
+ try {
47
+ return nextResolve(specifier + suffix, context);
48
+ } catch {}
49
+ }
50
+ throw error;
51
+ }
52
+ }
53
+ });
54
+ }
24
55
  async function loadConfig(file) {
25
56
  const path = file ? resolve(file) : CONFIG_NAMES.map((name) => resolve(name)).find((path) => existsSync(path));
26
57
  if (!path || !existsSync(path))
27
58
  throw new CliError2(`No i18n config found: pass --config or add ${CONFIG_NAMES[0]}.`);
59
+ resolveLikeBundlers();
28
60
  const loaded = await import(pathToFileURL(path).href);
29
61
  if (!loaded.default?.catalogs)
30
62
  throw new CliError2(`${path} must default-export an i18n config`);
@@ -80,6 +80,23 @@ type RawHandlerReturnType<T> = T extends (...args: any[]) => infer R ? R : never
80
80
  type RawCasesReturnType<Cases> = {
81
81
  [K in keyof Cases]: RawHandlerReturnType<Cases[K]>;
82
82
  }[keyof Cases];
83
+ /**
84
+ * `R` is only inferred from the call's contextual type. Without one it stays
85
+ * `unknown`, and the overload returns the handlers' own return types.
86
+ */
87
+ type Annotated<R, Inferred> = unknown extends R ? Inferred : R;
88
+ /**
89
+ * TypeScript fixes a handler's return type the first time it checks it, and
90
+ * an object-mode call (`value, key, cases`) is first tried against the
91
+ * plain-value overload with a default (`value, cases, defaultCase`). This type
92
+ * types those cases' handlers by the annotation there too, so they keep their
93
+ * literals. No value satisfies it: `default` would have to be `never`.
94
+ */
95
+ type ObjectCasesContext<R> = {
96
+ [key: string]: (value: never) => R;
97
+ } & {
98
+ default: never;
99
+ };
83
100
  /** Only required, defined handlers guarantee that a case is handled. */
84
101
  type HandledCaseKeys<Cases> = {
85
102
  [K in keyof Cases]-?: {} extends Pick<Cases, K> ? never : undefined extends Cases[K] ? never : K;
@@ -113,13 +130,21 @@ export type PartialObjectSwitchCases<T, K extends keyof T, R> = T[K] extends Dis
113
130
  type NoUnknownCases<Cases, Allowed extends string> = {
114
131
  [P in keyof Cases as `${P & (string | number)}` extends Allowed ? never : P]: never;
115
132
  };
133
+ export declare function typedSwitch<T extends Discriminant, R, Cases extends {
134
+ [V in T as `${V}`]: (value: V) => R;
135
+ } & NoUnknownCases<Cases, `${T}`>>(value: T, cases: Cases): Annotated<R, RawCasesReturnType<Cases>>;
136
+ export declare function typedSwitch<T extends Discriminant, R, Cases extends {
137
+ [V in T as `${V}`]?: (value: V) => R;
138
+ } & NoUnknownCases<Cases, `${T}`>, DefaultCase extends ((value: Exclude<T, ValuesForKeys<T, HandledCaseKeys<Cases>>>) => R) | ObjectCasesContext<R>>(value: T, cases: Cases, defaultCase: DefaultCase): Annotated<R, RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>>;
139
+ export declare function typedSwitch<T extends object, K extends keyof T, R, Cases extends ObjectCases<T, K, R> & NoUnknownCases<Cases, `${Extract<T[K], Discriminant>}`>>(value: T, key: K, cases: Cases): Annotated<R, RawCasesReturnType<Cases>>;
140
+ export declare function typedSwitch<T extends object, K extends keyof T, R, Cases extends PartialObjectCases<T, K, R> & NoUnknownCases<Cases, `${Extract<T[K], Discriminant>}`>, DefaultCase extends (value: Exclude<T, Record<K, ValuesForKeys<T[K], HandledCaseKeys<Cases>>>>) => R>(value: T, key: K, cases: Cases, defaultCase: DefaultCase): Annotated<R, RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>>;
116
141
  export declare function typedSwitch<T extends Discriminant, Cases extends {
117
142
  [V in T as `${V}`]: (value: V) => unknown;
118
143
  } & NoUnknownCases<Cases, `${T}`>>(value: T, cases: Cases): RawCasesReturnType<Cases>;
144
+ 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>;
119
145
  export declare function typedSwitch<T extends Discriminant, Cases extends {
120
146
  [V in T as `${V}`]?: (value: V) => unknown;
121
147
  } & 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
148
  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
149
  /**
125
150
  * Builds cases that share one handler, for spreading into `typedSwitch` cases.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hanzio",
3
- "version": "3.1.1",
3
+ "version": "3.2.0",
4
4
  "description": "A lightweight set of utility functions for TypeScript",
5
5
  "type": "module",
6
6
  "module": "./dist/src/index.js",
@@ -87,6 +87,13 @@ formatDateTime(date, 'en-GB') // '5 Mar 2026, 07:04' (newer ICU: '5 Mar 2026 at
87
87
  Presets: `'DD/MM/YYYY'`, `'DD/MM/YYYY HH:mm'`, `'HH:mm'`, `'ddd'`, `'D MMM'`,
88
88
  `'D MMM YYYY'`, `'dddd, MMMM D'`, `'MMMM YYYY'`.
89
89
 
90
+ For any other layout, pass `Intl.DateTimeFormat` options instead of a preset:
91
+
92
+ ```ts
93
+ formatDate(date, { weekday: 'long', day: 'numeric', month: 'long' }, 'en-GB') // 'Thursday 5 March'
94
+ formatDate(date, { dateStyle: 'short', timeZone: 'Europe/Copenhagen' }, 'da-DK') // '05.03.2026'
95
+ ```
96
+
90
97
  `formatCompactTime(date, locale, { now? })` is an activity timestamp for list
91
98
  rows: the time today, "yesterday", else the date (with the year when it is not
92
99
  this year). `formatDayDivider(date, locale, { now? })` labels a chat timeline:
@@ -401,13 +401,17 @@ const DATE_PRESETS = {
401
401
  */
402
402
  export type DatePreset = keyof typeof DATE_PRESETS
403
403
 
404
- /** A date in one of the `DatePreset` layouts, in local time. */
404
+ /**
405
+ * A date in one of the `DatePreset` layouts, or with any
406
+ * `Intl.DateTimeFormat` options, in local time unless they set `timeZone`.
407
+ */
405
408
  export function formatDate(
406
409
  date: DateInput,
407
- preset: DatePreset,
410
+ format: DatePreset | Intl.DateTimeFormatOptions,
408
411
  locale: string
409
412
  ): string {
410
- return getDateTimeFormat(locale, DATE_PRESETS[preset]).format(toTime(date))
413
+ const options = typeof format === 'string' ? DATE_PRESETS[format] : format
414
+ return getDateTimeFormat(locale, options).format(toTime(date))
411
415
  }
412
416
 
413
417
  /** Medium date and short time: "5 Mar 2026, 14:30" (en-GB). */
@@ -519,8 +523,10 @@ export function pluralize(
519
523
  */
520
524
  export function withLocale(getLocale: () => string) {
521
525
  return {
522
- formatDate: (date: DateInput, preset: DatePreset) =>
523
- formatDate(date, preset, getLocale()),
526
+ formatDate: (
527
+ date: DateInput,
528
+ format: DatePreset | Intl.DateTimeFormatOptions
529
+ ) => formatDate(date, format, getLocale()),
524
530
  formatDateTime: (date: DateInput) => formatDateTime(date, getLocale()),
525
531
  formatCompactTime: (date: DateInput) =>
526
532
  formatCompactTime(date, getLocale()),
@@ -203,7 +203,9 @@ Both read `i18n.config.ts` (or `.mts`, `.js`, `.mjs`, or `--config <file>`)
203
203
  from the current directory; paths in it are relative to the config file.
204
204
  `defineI18nConfig` rejects a `baseLocale`, `disjointNamespaces` catalog or
205
205
  `identical.allowKeys` locale the config does not declare. A
206
- `.ts` config needs Bun, or a Node version that strips types. The bin is built
206
+ `.ts` config needs Bun or Node 22.18+. Its relative imports may leave out the
207
+ extension or `/index`, as TypeScript's bundler resolution allows, so
208
+ `npx hanzio-i18n check` works without `bun --bun`. The bin is built
207
209
  on [`hanzio/cli`](../cli/README.md), so it needs `zod` installed; `--help`
208
210
  lists its options and it exits 1 on catalog problems, 2 on bad arguments.
209
211
 
package/src/i18n/cli.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  // a Node version that strips types.
6
6
 
7
7
  import { existsSync } from 'node:fs'
8
+ import * as nodeModule from 'node:module'
8
9
  import { dirname, resolve } from 'node:path'
9
10
  import { pathToFileURL } from 'node:url'
10
11
  import { z } from 'zod'
@@ -25,6 +26,44 @@ const CONFIG_NAMES = [
25
26
 
26
27
  type Loaded = { config: I18nConfig; root: string }
27
28
 
29
+ // Node resolves a config's imports like a browser: `./src/locales` fails
30
+ // because it names neither a file nor an extension. Bun resolves it like a
31
+ // bundler. These are the suffixes TypeScript's bundler resolution tries.
32
+ const BUNDLER_SUFFIXES = [
33
+ '.ts',
34
+ '.mts',
35
+ '.js',
36
+ '.mjs',
37
+ '/index.ts',
38
+ '/index.mts',
39
+ '/index.js',
40
+ '/index.mjs'
41
+ ]
42
+
43
+ function resolveLikeBundlers() {
44
+ // Missing under Bun, which needs no help, and under Node before 22.15.
45
+ nodeModule.registerHooks?.({
46
+ resolve(specifier, context, nextResolve) {
47
+ try {
48
+ return nextResolve(specifier, context)
49
+ } catch (error) {
50
+ const code = (error as { code?: string }).code
51
+ const retry =
52
+ specifier.startsWith('.') &&
53
+ (code === 'ERR_MODULE_NOT_FOUND' ||
54
+ code === 'ERR_UNSUPPORTED_DIR_IMPORT')
55
+ if (!retry) throw error
56
+ for (const suffix of BUNDLER_SUFFIXES) {
57
+ try {
58
+ return nextResolve(specifier + suffix, context)
59
+ } catch {}
60
+ }
61
+ throw error
62
+ }
63
+ }
64
+ })
65
+ }
66
+
28
67
  async function loadConfig(file: string | undefined): Promise<Loaded> {
29
68
  const path = file
30
69
  ? resolve(file)
@@ -33,6 +72,7 @@ async function loadConfig(file: string | undefined): Promise<Loaded> {
33
72
  throw new CliError(
34
73
  `No i18n config found: pass --config or add ${CONFIG_NAMES[0]}.`
35
74
  )
75
+ resolveLikeBundlers()
36
76
  const loaded = (await import(pathToFileURL(path).href)) as {
37
77
  default?: I18nConfig
38
78
  }
@@ -246,21 +246,32 @@ const result = typedSwitch(status, {
246
246
 
247
247
  ### Enforcing a Return Type
248
248
 
249
- Annotate the result. Every handler's return type must fit, and exhaustiveness
250
- checking still applies:
249
+ Annotate the result, or switch in a function with a declared return type.
250
+ Every handler is checked against it, exhaustiveness checking still applies,
251
+ and literals keep their types instead of widening to `string`:
251
252
 
252
253
  ```typescript
253
- interface HasId {
254
- id: string
255
- }
254
+ type Severity = 'success' | 'info' | 'warn' | 'danger'
256
255
 
257
- const result: HasId = typedSwitch(status, {
258
- success: () => ({ id: '123', name: 'John' }),
259
- error: () => ({ id: '456', code: 500 }),
260
- pending: () => ({ id: '789' }),
256
+ const severity: Severity = typedSwitch(quality, {
257
+ excellent: () => 'success',
258
+ good: () => 'info',
259
+ fair: () => 'warn',
260
+ poor: () => 'danger',
261
261
  })
262
+
263
+ function orderBy(sort: Sort): Prisma.OrderOrderByWithRelationInput[] {
264
+ return typedSwitch(sort, {
265
+ name: () => [{ name: 'asc' }],
266
+ newest: () => [{ createdAt: 'desc' }],
267
+ })
268
+ }
262
269
  ```
263
270
 
271
+ Without an annotation, or inside a generic call such as `useState(typedSwitch(...))`,
272
+ the result is the union of the handlers' widened return types. In Vue, annotate
273
+ the getter: `computed((): Severity => typedSwitch(...))`.
274
+
264
275
  ## Error Handling
265
276
 
266
277
  When a case is unhandled at runtime (e.g., due to unsafe casts), `typedSwitch` throws a descriptive error:
@@ -88,6 +88,23 @@ type RawCasesReturnType<Cases> = {
88
88
  [K in keyof Cases]: RawHandlerReturnType<Cases[K]>
89
89
  }[keyof Cases]
90
90
 
91
+ /**
92
+ * `R` is only inferred from the call's contextual type. Without one it stays
93
+ * `unknown`, and the overload returns the handlers' own return types.
94
+ */
95
+ type Annotated<R, Inferred> = unknown extends R ? Inferred : R
96
+
97
+ /**
98
+ * TypeScript fixes a handler's return type the first time it checks it, and
99
+ * an object-mode call (`value, key, cases`) is first tried against the
100
+ * plain-value overload with a default (`value, cases, defaultCase`). This type
101
+ * types those cases' handlers by the annotation there too, so they keep their
102
+ * literals. No value satisfies it: `default` would have to be `never`.
103
+ */
104
+ type ObjectCasesContext<R> = { [key: string]: (value: never) => R } & {
105
+ default: never
106
+ }
107
+
91
108
  /** Only required, defined handlers guarantee that a case is handled. */
92
109
  type HandledCaseKeys<Cases> = {
93
110
  // biome-ignore lint/complexity/noBannedTypes: Empty-object assignability detects optional keys and index signatures.
@@ -173,6 +190,65 @@ type CasesRecord = Record<string, Handler | undefined>
173
190
  // Function overloads
174
191
  // ═══════════════════════════════════════════════════════════════════════════
175
192
 
193
+ // Each mode has two overloads. The first four take the result type `R` from
194
+ // the call's contextual type, such as an annotated variable or a declared
195
+ // return type, so handlers that return literals ('info', [{ name: 'asc' }])
196
+ // keep them. When that does not type-check, for example inside a generic call
197
+ // like useState(typedSwitch(...)), the last four union the handlers' types.
198
+
199
+ // Plain value input - all cases required, typed by the annotation
200
+ export function typedSwitch<
201
+ T extends Discriminant,
202
+ R,
203
+ Cases extends { [V in T as `${V}`]: (value: V) => R } & NoUnknownCases<
204
+ Cases,
205
+ `${T}`
206
+ >
207
+ >(value: T, cases: Cases): Annotated<R, RawCasesReturnType<Cases>>
208
+
209
+ // Plain value input - partial cases with default, typed by the annotation.
210
+ // It comes before the object overloads, which also take three arguments.
211
+ export function typedSwitch<
212
+ T extends Discriminant,
213
+ R,
214
+ Cases extends {
215
+ [V in T as `${V}`]?: (value: V) => R
216
+ } & NoUnknownCases<Cases, `${T}`>,
217
+ DefaultCase extends
218
+ | ((value: Exclude<T, ValuesForKeys<T, HandledCaseKeys<Cases>>>) => R)
219
+ | ObjectCasesContext<R>
220
+ >(
221
+ value: T,
222
+ cases: Cases,
223
+ defaultCase: DefaultCase
224
+ ): Annotated<R, RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>>
225
+
226
+ // Object input with key - all cases required, typed by the annotation
227
+ export function typedSwitch<
228
+ T extends object,
229
+ K extends keyof T,
230
+ R,
231
+ Cases extends ObjectCases<T, K, R> &
232
+ NoUnknownCases<Cases, `${Extract<T[K], Discriminant>}`>
233
+ >(value: T, key: K, cases: Cases): Annotated<R, RawCasesReturnType<Cases>>
234
+
235
+ // Object input with key - partial cases with default, typed by the annotation
236
+ export function typedSwitch<
237
+ T extends object,
238
+ K extends keyof T,
239
+ R,
240
+ Cases extends PartialObjectCases<T, K, R> &
241
+ NoUnknownCases<Cases, `${Extract<T[K], Discriminant>}`>,
242
+ DefaultCase extends (
243
+ value: Exclude<T, Record<K, ValuesForKeys<T[K], HandledCaseKeys<Cases>>>>
244
+ ) => R
245
+ >(
246
+ value: T,
247
+ key: K,
248
+ cases: Cases,
249
+ defaultCase: DefaultCase
250
+ ): Annotated<R, RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>>
251
+
176
252
  // Plain value input - all cases required (no default)
177
253
  export function typedSwitch<
178
254
  T extends Discriminant,
@@ -182,6 +258,14 @@ export function typedSwitch<
182
258
  >
183
259
  >(value: T, cases: Cases): RawCasesReturnType<Cases>
184
260
 
261
+ // Object input with key - all cases required (no default)
262
+ export function typedSwitch<
263
+ T extends object,
264
+ K extends keyof T,
265
+ Cases extends ObjectCases<T, K, unknown> &
266
+ NoUnknownCases<Cases, `${Extract<T[K], Discriminant>}`>
267
+ >(value: T, key: K, cases: Cases): RawCasesReturnType<Cases>
268
+
185
269
  // Plain value input - partial cases allowed (with default)
186
270
  // The default handler only receives the cases not covered by `cases`.
187
271
  export function typedSwitch<
@@ -198,14 +282,6 @@ export function typedSwitch<
198
282
  defaultCase: DefaultCase
199
283
  ): RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>
200
284
 
201
- // Object input with key - all cases required (no default)
202
- export function typedSwitch<
203
- T extends object,
204
- K extends keyof T,
205
- Cases extends ObjectCases<T, K, unknown> &
206
- NoUnknownCases<Cases, `${Extract<T[K], Discriminant>}`>
207
- >(value: T, key: K, cases: Cases): RawCasesReturnType<Cases>
208
-
209
285
  // Object input with key - partial cases allowed (with default)
210
286
  // The default handler only receives the union members not covered by `cases`.
211
287
  export function typedSwitch<