Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clarify-union-to-tuple-order.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ylfjuk-core/types": patch
---

Clarify that `UnionToTuple` element order is unspecified and unstable. It cannot predict `Object.values` order or recover duplicate values discarded by a union.
10 changes: 10 additions & 0 deletions .changeset/remove-enum-to-tuple.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@ylfjuk-core/utils": patch
"@ylfjuk/core": patch
---

BREAKING CHANGE: remove `Enum.toTuple`, its `enumToTuple` alias, and the otherwise empty `Enum` namespace. Imports of these APIs must be removed.

The helper's ordered-tuple guarantee was unsound: object types and unions cannot encode runtime insertion order, and unions discard duplicate values even though `Object.values` retains them. The claimed tuple could therefore have incorrect positions and length.

Replace `Enum.toTuple(enumLikeObject)` or `enumToTuple(enumLikeObject)` with `Object.values(enumLikeObject)` for an array without fixed positional typing. When exact positions matter, define an explicit tuple as the source of truth, for example `const names = ["bob", "bert", "bob"] as const`. See the [migration guide](https://github.com/YLfjuk/core/blob/main/packages/utils/README.md#migrate-from-enumtotuple).
15 changes: 11 additions & 4 deletions packages/types/src/union-to-tuple.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import type { LastOfUnion } from "./last-of-union.js";

/**
* Converts a union into an ordered tuple.
* Converts a union into a tuple with unspecified, unstable element order.
*
* @remarks
* Do not rely on a particular order or use this type to predict `Object.values`
* order. Unions do not preserve insertion order or duplicate values.
*
* @see {@link LastOfUnion}
*
* @example
* type Enum = "bert" | "berta" | "bob";
* import type { UnionToTuple } from "@ylfjuk-core/types";
*
* type Name = "bert" | "berta" | "bob";
*
* type EnumAsTuple = UnionToTuple<Enum>; // ["bert", "berta", "bob"]
* type IsEnumAsTuple = IsTuple<EnumAsTuple>; // true
* type NameTuple = UnionToTuple<Name>; // Element order is unspecified.
* type Members = NameTuple[number]; // "bert" | "berta" | "bob"
* type Length = NameTuple["length"]; // 3
*
* @since 0.0.18
*/
Expand Down
32 changes: 32 additions & 0 deletions packages/utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,38 @@ This package is ESM-only and targets ES2024.
import { isDefined } from "@ylfjuk-core/utils";
```

## Migrate from Enum.toTuple

BREAKING CHANGE: `Enum.toTuple`, its `enumToTuple` alias, and the `Enum` namespace have been removed from `@ylfjuk-core/utils` and the `@ylfjuk/core` re-exports. `Enum` contained no other helpers.

The helper's ordered-tuple guarantee was unsound. Object types and unions cannot encode runtime insertion order, and unions discard duplicate values. For example, two object keys with the value `"bob"` produce two array entries at runtime but only one `"bob"` union member, so the claimed tuple could have the wrong length as well as the wrong positions.

Replace calls to `Enum.toTuple(Name)` or `enumToTuple(Name)` with `Object.values(Name)` when you need an array:

```ts
const Name = {
Bob: "bob",
Bert: "bert",
AlsoBob: "bob",
} as const;

const names = Object.values(Name);
// Type: ("bob" | "bert")[]
// Runtime: ["bob", "bert", "bob"]
```

Remove the old imports. The array retains duplicate values and follows JavaScript property enumeration order, but its type does not promise a fixed length or a value at each position.

When exact positional typing matters, define an explicit tuple as the source of truth:

```ts
const names = ["bob", "bert", "bob"] as const;
type Name = (typeof names)[number]; // "bob" | "bert"
// typeof names: readonly ["bob", "bert", "bob"]
```

`UnionToTuple` remains public. Its element order is unspecified and unstable; it cannot predict `Object.values` order or recover duplicates discarded by a union. Do not cast the array to `UnionToTuple` to restore the removed guarantee.

## Development

From the repository root, use Node.js 22.18.0 or newer and run `pnpm run build`, `pnpm run test`, and `pnpm run test:types`.
Expand Down
22 changes: 0 additions & 22 deletions packages/utils/__tests__/enum/to-tuple.spec.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/utils/src/enum/Enum.ts

This file was deleted.

8 changes: 0 additions & 8 deletions packages/utils/src/enum/index.ts

This file was deleted.

21 changes: 0 additions & 21 deletions packages/utils/src/enum/to-tuple.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export * from "./arr";
export * from "./assert-never";
export * from "./bool";
export * from "./enum";
export * from "./infinite-object";
export * from "./is-defined";
export * from "./json";
Expand Down
Loading