From 5fb157657edc9e6510211f8610d45d987d352ad2 Mon Sep 17 00:00:00 2001 From: unadlib Date: Sun, 11 Oct 2026 20:13:15 +0800 Subject: [PATCH 1/3] docs: name the explicit state types that keep async results typed as the state --- README.md | 4 ++-- website/content/docs/advanced-guides/migration-v2.mdx | 2 +- website/content/docs/api-reference/create.mdx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ad384c3a..14f9bf90 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ const state = create(baseState, (draft) => { In this basic example, the changes to the draft are 'mutative' within the draft callback, and `create()` is finally executed with a new immutable state. -The recipe can be an async function: `create()` then returns a Promise of the result, and the draft stays usable until that Promise settles. `create()` recognizes the Promises of the current JavaScript realm only, so an async function from another realm, such as a `vm` context or an iframe, needs a wrapper from this realm: `create(baseState, async (draft) => { return await recipe(draft); })`. +The recipe can be an async function: `create()` then returns a Promise of the result, and the draft stays usable until that Promise settles. `create()` recognizes the Promises of the current JavaScript realm only, so an async function from another realm, such as a `vm` context or an iframe, needs a wrapper from this realm: `create(baseState, async (draft) => { return await recipe(draft); })`. TypeScript types the result as a Promise as well, except with an explicit state type that is a type parameter, as in `create(base, async (draft) => { … })` in a generic function, or that includes Promises, such as `object` or `unknown`, which gives the state type itself. In generic code, leave out the type argument: `create(base, async (draft) => { … })` gives `Promise`. #### `create(state, fn, options)` @@ -798,7 +798,7 @@ Mutative v2 keeps the v1 API. The changes below, made since v1.3.0, can affect e ### TypeScript -- `create()` with an explicit state type and an async recipe, such as `create(base, async (draft) => { … })`, returns `Promise`, and so do curried producers; v1 typed the result as `State`. +- `create()` with an explicit state type and an async recipe, such as `create(base, async (draft) => { … })`, returns `Promise`, and so do curried producers; v1 typed the result as `State`. A state type that is a type parameter, as in a generic function, or that includes Promises, such as `object` or `unknown`, still gives the state type for async recipes and for recipes that may return a Promise, as in v1; leave out the type argument in generic code. - `apply()` accepts `enableAutoFreeze: true` and then returns `Immutable`, as `apply()` does; v1 rejected the option and typed the frozen result as mutable. With `enableAutoFreeze` typed as `boolean` or optional `true`, the result is `State | Immutable`. With `mutable` typed as `boolean`, optional `boolean`, or optional `true`, `apply()` returns `State | void`. - A curried producer whose recipe annotates its draft and returns a new state, such as `create((draft: State) => ({ ...draft, count: 0 }))`, returns `State`, or `Promise` for an async recipe. v1 typed such a call as a manual draft, so calling the producer did not compile. - With an explicit state type, a recipe that may or may not return a Promise, such as one typed `(draft: State) => void | Promise`, gives `State | Promise`, also in curried producers and creators from `makeCreator()`. v1 typed the result as `State`, as this version still does for a primitive state type, such as `number`. diff --git a/website/content/docs/advanced-guides/migration-v2.mdx b/website/content/docs/advanced-guides/migration-v2.mdx index 20374dc4..94efbe64 100644 --- a/website/content/docs/advanced-guides/migration-v2.mdx +++ b/website/content/docs/advanced-guides/migration-v2.mdx @@ -40,7 +40,7 @@ Mutative v2 keeps the v1 API. The changes below, made since v1.3.0, can affect e ## TypeScript -- `create()` with an explicit state type and an async recipe, such as `create(base, async (draft) => { … })`, returns `Promise`, and so do curried producers; v1 typed the result as `State`. +- `create()` with an explicit state type and an async recipe, such as `create(base, async (draft) => { … })`, returns `Promise`, and so do curried producers; v1 typed the result as `State`. A state type that is a type parameter, as in a generic function, or that includes Promises, such as `object` or `unknown`, still gives the state type for async recipes and for recipes that may return a Promise, as in v1; leave out the type argument in generic code. - `apply()` accepts `enableAutoFreeze: true` and then returns `Immutable`, as `apply()` does; v1 rejected the option and typed the frozen result as mutable. With `enableAutoFreeze` typed as `boolean` or optional `true`, the result is `State | Immutable`. With `mutable` typed as `boolean`, optional `boolean`, or optional `true`, `apply()` returns `State | void`. - A curried producer whose recipe annotates its draft and returns a new state, such as `create((draft: State) => ({ ...draft, count: 0 }))`, returns `State`, or `Promise` for an async recipe. v1 typed such a call as a manual draft, so calling the producer did not compile. - With an explicit state type, a recipe that may or may not return a Promise, such as one typed `(draft: State) => void | Promise`, gives `State | Promise`, also in curried producers and creators from `makeCreator()`. v1 typed the result as `State`, as this version still does for a primitive state type, such as `number`. diff --git a/website/content/docs/api-reference/create.mdx b/website/content/docs/api-reference/create.mdx index eee66009..408cd56c 100644 --- a/website/content/docs/api-reference/create.mdx +++ b/website/content/docs/api-reference/create.mdx @@ -22,7 +22,7 @@ const state = create(baseState, (draft) => { In this basic example, the changes to the draft are 'mutative' within the draft callback, and `create()` is finally executed with a new immutable state. -The recipe can be an async function: `create()` then returns a Promise of the result, and the draft stays usable until that Promise settles. `create()` recognizes the Promises of the current JavaScript realm only, so an async function from another realm, such as a `vm` context or an iframe, needs a wrapper from this realm: `create(baseState, async (draft) => { return await recipe(draft); })`. +The recipe can be an async function: `create()` then returns a Promise of the result, and the draft stays usable until that Promise settles. `create()` recognizes the Promises of the current JavaScript realm only, so an async function from another realm, such as a `vm` context or an iframe, needs a wrapper from this realm: `create(baseState, async (draft) => { return await recipe(draft); })`. TypeScript types the result as a Promise as well, except with an explicit state type that is a type parameter, as in `create(base, async (draft) => { … })` in a generic function, or that includes Promises, such as `object` or `unknown`, which gives the state type itself. In generic code, leave out the type argument: `create(base, async (draft) => { … })` gives `Promise`. ## create(state, fn, options) - options From 10aa560615793d45af95d1cddd2b32ee552c773f Mon Sep 17 00:00:00 2001 From: unadlib Date: Sun, 11 Oct 2026 20:13:21 +0800 Subject: [PATCH 2/3] docs: state that JSON Patch compatibility also needs string paths --- README.md | 2 +- website/content/docs/advanced-guides/pathes.mdx | 2 +- website/content/docs/api-reference/create.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 14f9bf90..2522b923 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ The recipe can be an async function: `create()` then returns a Promise of the re > Enable patch, and return the patches/inversePatches. - > If you need to set the shape of the generated patch in more detail, then you can set `pathAsArray` and `arrayLengthAssignment`。`pathAsArray` default value is `true`, if it's `true`, the path will be an array, otherwise it is a string; `arrayLengthAssignment` default value is `true`, if it's `true`, the array length will be included in the patches, otherwise no include array length(**NOTE**: If `arrayLengthAssignment` is `false`, it is fully compatible with JSON Patch spec, but it may have additional performance loss), [view related discussions](https://github.com/unadlib/mutative/issues/6). + > If you need to set the shape of the generated patch in more detail, then you can set `pathAsArray` and `arrayLengthAssignment`。`pathAsArray` default value is `true`, if it's `true`, the path will be an array, otherwise it is a string; `arrayLengthAssignment` default value is `true`, if it's `true`, the array length will be included in the patches, otherwise no include array length(**NOTE**: If `pathAsArray` and `arrayLengthAssignment` are both `false`, the patches of objects and arrays are fully compatible with JSON Patch spec, but `arrayLengthAssignment: false` may have additional performance loss), [view related discussions](https://github.com/unadlib/mutative/issues/6). > A string path is a JSON Pointer and holds only strings, so it cannot name a Map key that is not a string, or a symbol key: with `pathAsArray: false`, the patch of `draft.set(1, 'b')` would set the key `'1'`. Keep the default array paths for such keys; development builds throw an error when a string path would hold one. diff --git a/website/content/docs/advanced-guides/pathes.mdx b/website/content/docs/advanced-guides/pathes.mdx index b13a146a..216eb367 100644 --- a/website/content/docs/advanced-guides/pathes.mdx +++ b/website/content/docs/advanced-guides/pathes.mdx @@ -236,5 +236,5 @@ expect(inversePatches).toMatchInlineSnapshot(` ``` -If `arrayLengthAssignment` is `false`, it is fully compatible with JSON Patch spec, but it may have additional performance loss. +If `pathAsArray` and `arrayLengthAssignment` are both `false`, the patches of objects and arrays are fully compatible with JSON Patch spec, but `arrayLengthAssignment: false` may have additional performance loss. diff --git a/website/content/docs/api-reference/create.mdx b/website/content/docs/api-reference/create.mdx index 408cd56c..2a914010 100644 --- a/website/content/docs/api-reference/create.mdx +++ b/website/content/docs/api-reference/create.mdx @@ -40,7 +40,7 @@ Then options is optional. > Enable patch, and return the patches/inversePatches. - > If you need to set the shape of the generated patch in more detail, then you can set `pathAsArray` and `arrayLengthAssignment`。`pathAsArray` default value is `true`, if it's `true`, the path will be an array, otherwise it is a string; `arrayLengthAssignment` default value is `true`, if it's `true`, the array length will be included in the patches, otherwise no include array length(**NOTE**: If `arrayLengthAssignment` is `false`, it is fully compatible with JSON Patch spec, but it may have additional performance loss), [view related discussions](https://github.com/unadlib/mutative/issues/6). + > If you need to set the shape of the generated patch in more detail, then you can set `pathAsArray` and `arrayLengthAssignment`。`pathAsArray` default value is `true`, if it's `true`, the path will be an array, otherwise it is a string; `arrayLengthAssignment` default value is `true`, if it's `true`, the array length will be included in the patches, otherwise no include array length(**NOTE**: If `pathAsArray` and `arrayLengthAssignment` are both `false`, the patches of objects and arrays are fully compatible with JSON Patch spec, but `arrayLengthAssignment: false` may have additional performance loss), [view related discussions](https://github.com/unadlib/mutative/issues/6). > A string path is a JSON Pointer and holds only strings, so it cannot name a Map key that is not a string, or a symbol key: with `pathAsArray: false`, the patch of `draft.set(1, 'b')` would set the key `'1'`. Keep the default array paths for such keys; development builds throw an error when a string path would hold one. From 4ab88203dd93e84f6832cddc7f08c7778fd2532c Mon Sep 17 00:00:00 2001 From: unadlib Date: Sun, 11 Oct 2026 20:13:30 +0800 Subject: [PATCH 3/3] docs: state the TypeScript versions that the types support --- README.md | 2 ++ website/content/docs/advanced-guides/typescript.mdx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 2522b923..4d4a0a67 100644 --- a/README.md +++ b/README.md @@ -606,6 +606,8 @@ expect(state.simpleObject).not.toBe(baseState.simpleObject); ## Using TypeScript +Mutative's types support TypeScript 5.0 and later; the package checks compile them with TypeScript 5.0. + - `castDraft()` - `castImmutable()` - `castMutable()` diff --git a/website/content/docs/advanced-guides/typescript.mdx b/website/content/docs/advanced-guides/typescript.mdx index 5772bf8e..2e000877 100644 --- a/website/content/docs/advanced-guides/typescript.mdx +++ b/website/content/docs/advanced-guides/typescript.mdx @@ -4,6 +4,8 @@ title: Typescript Mutative support is built into the typescript types. You can use the `Draft` and `Immutable` types to annotate your code. +Mutative's types support TypeScript 5.0 and later; the package checks compile them with TypeScript 5.0. + ## Using TypeScript - `castDraft()`