|
| 1 | +type Tail<T extends any[]> = ((...t: T) => any) extends ( |
| 2 | + _: any, |
| 3 | + ...tail: infer TT |
| 4 | +) => any |
| 5 | + ? TT |
| 6 | + : [] |
| 7 | + |
| 8 | +/** Object types that should never be mapped */ |
| 9 | +type AtomicObject = |
| 10 | + | Function |
| 11 | + | WeakMap<any, any> |
| 12 | + | WeakSet<any> |
| 13 | + | Promise<any> |
| 14 | + | Date |
| 15 | + | RegExp |
| 16 | + | Boolean |
| 17 | + | Number |
| 18 | + | String |
| 19 | + |
| 20 | +export type Draft<T> = T extends AtomicObject |
| 21 | + ? T |
| 22 | + : T extends Map<infer K, infer V> |
| 23 | + ? DraftMap<K, V> |
| 24 | + : T extends Set<infer V> |
| 25 | + ? DraftSet<V> |
| 26 | + : T extends object |
| 27 | + ? {-readonly [K in keyof T]: Draft<T[K]>} |
| 28 | + : T |
| 29 | + |
| 30 | +// Inline these in ts 3.7 |
| 31 | +interface DraftMap<K, V> extends Map<Draft<K>, Draft<V>> {} |
| 32 | + |
| 33 | +// Inline these in ts 3.7 |
| 34 | +interface DraftSet<V> extends Set<Draft<V>> {} |
| 35 | + |
| 36 | +/** Convert a mutable type into a readonly type */ |
| 37 | +export type Immutable<T> = T extends AtomicObject |
| 38 | + ? T |
| 39 | + : T extends Map<infer K, infer V> // Ideally, but wait for TS 3.7: ? Omit<ImmutableMap<K, V>, "set" | "delete" | "clear"> |
| 40 | + ? ImmutableMap<K, V> |
| 41 | + : T extends Set<infer V> // Ideally, but wait for TS 3.7: ? Omit<ImmutableSet<V>, "add" | "delete" | "clear"> |
| 42 | + ? ImmutableSet<V> |
| 43 | + : T extends object |
| 44 | + ? {readonly [K in keyof T]: Immutable<T[K]>} |
| 45 | + : T |
| 46 | + |
| 47 | +interface ImmutableMap<K, V> extends Map<Immutable<K>, Immutable<V>> {} |
| 48 | + |
| 49 | +interface ImmutableSet<V> extends Set<Immutable<V>> {} |
| 50 | + |
| 51 | +export interface Patch { |
| 52 | + op: "replace" | "remove" | "add" |
| 53 | + path: (string | number)[] |
| 54 | + value?: any |
| 55 | +} |
| 56 | + |
| 57 | +export type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void |
| 58 | + |
| 59 | +/** Converts `nothing` into `undefined` */ |
| 60 | +type FromNothing<T> = T extends Nothing ? undefined : T |
| 61 | + |
| 62 | +/** The inferred return type of `produce` */ |
| 63 | +export type Produced<Base, Return> = Return extends void |
| 64 | + ? Base |
| 65 | + : Return extends Promise<infer Result> |
| 66 | + ? Promise<Result extends void ? Base : FromNothing<Result>> |
| 67 | + : FromNothing<Return> |
| 68 | + |
| 69 | +/** |
| 70 | + * The `produce` function takes a value and a "recipe function" (whose |
| 71 | + * return value often depends on the base state). The recipe function is |
| 72 | + * free to mutate its first argument however it wants. All mutations are |
| 73 | + * only ever applied to a __copy__ of the base state. |
| 74 | + * |
| 75 | + * Pass only a function to create a "curried producer" which relieves you |
| 76 | + * from passing the recipe function every time. |
| 77 | + * |
| 78 | + * Only plain objects and arrays are made mutable. All other objects are |
| 79 | + * considered uncopyable. |
| 80 | + * |
| 81 | + * Note: This function is __bound__ to its `Immer` instance. |
| 82 | + * |
| 83 | + * @param {any} base - the initial state |
| 84 | + * @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified |
| 85 | + * @param {Function} patchListener - optional function that will be called with all the patches produced here |
| 86 | + * @returns {any} a new state, or the initial state if nothing was modified |
| 87 | + */ |
| 88 | +export interface IProduce { |
| 89 | + /** Curried producer */ |
| 90 | + < |
| 91 | + Recipe extends (...args: any[]) => any, |
| 92 | + Params extends any[] = Parameters<Recipe>, |
| 93 | + T = Params[0] |
| 94 | + >( |
| 95 | + recipe: Recipe |
| 96 | + ): <Base extends Immutable<T>>( |
| 97 | + base: Base, |
| 98 | + ...rest: Tail<Params> |
| 99 | + ) => Produced<Base, ReturnType<Recipe>> |
| 100 | + // ^ by making the returned type generic, the actual type of the passed in object is preferred |
| 101 | + // over the type used in the recipe. However, it does have to satisfy the immutable version used in the recipe |
| 102 | + // Note: the type of S is the widened version of T, so it can have more props than T, but that is technically actually correct! |
| 103 | + |
| 104 | + /** Curried producer with initial state */ |
| 105 | + < |
| 106 | + Recipe extends (...args: any[]) => any, |
| 107 | + Params extends any[] = Parameters<Recipe>, |
| 108 | + T = Params[0] |
| 109 | + >( |
| 110 | + recipe: Recipe, |
| 111 | + initialState: Immutable<T> |
| 112 | + ): <Base extends Immutable<T>>( |
| 113 | + base?: Base, |
| 114 | + ...rest: Tail<Params> |
| 115 | + ) => Produced<Base, ReturnType<Recipe>> |
| 116 | + |
| 117 | + /** Normal producer */ |
| 118 | + <Base, D = Draft<Base>, Return = void>( |
| 119 | + base: Base, |
| 120 | + recipe: (draft: D) => Return, |
| 121 | + listener?: PatchListener |
| 122 | + ): Produced<Base, Return> |
| 123 | +} |
| 124 | + |
| 125 | +export const produce: IProduce |
| 126 | +export default produce |
| 127 | + |
| 128 | +/** |
| 129 | + * Like `produce`, but instead of just returning the new state, |
| 130 | + * a tuple is returned with [nextState, patches, inversePatches] |
| 131 | + * |
| 132 | + * Like produce, this function supports currying |
| 133 | + */ |
| 134 | +export interface IProduceWithPatches { |
| 135 | + /** Curried producer */ |
| 136 | + < |
| 137 | + Recipe extends (...args: any[]) => any, |
| 138 | + Params extends any[] = Parameters<Recipe>, |
| 139 | + T = Params[0] |
| 140 | + >( |
| 141 | + recipe: Recipe |
| 142 | + ): <Base extends Immutable<T>>( |
| 143 | + base: Base, |
| 144 | + ...rest: Tail<Params> |
| 145 | + ) => [Produced<Base, ReturnType<Recipe>>, Patch[], Patch[]] |
| 146 | + // ^ by making the returned type generic, the actual type of the passed in object is preferred |
| 147 | + // over the type used in the recipe. However, it does have to satisfy the immutable version used in the recipe |
| 148 | + // Note: the type of S is the widened version of T, so it can have more props than T, but that is technically actually correct! |
| 149 | + |
| 150 | + /** Curried producer with initial state */ |
| 151 | + < |
| 152 | + Recipe extends (...args: any[]) => any, |
| 153 | + Params extends any[] = Parameters<Recipe>, |
| 154 | + T = Params[0] |
| 155 | + >( |
| 156 | + recipe: Recipe, |
| 157 | + initialState: Immutable<T> |
| 158 | + ): <Base extends Immutable<T>>( |
| 159 | + base?: Base, |
| 160 | + ...rest: Tail<Params> |
| 161 | + ) => [Produced<Base, ReturnType<Recipe>>, Patch[], Patch[]] |
| 162 | + |
| 163 | + /** Normal producer */ |
| 164 | + <Base, D = Draft<Base>, Return = void>( |
| 165 | + base: Base, |
| 166 | + recipe: (draft: D) => Return |
| 167 | + ): [Produced<Base, Return>, Patch[], Patch[]] |
| 168 | +} |
| 169 | +export const produceWithPatches: IProduceWithPatches |
| 170 | + |
| 171 | +/** Use a class type for `nothing` so its type is unique */ |
| 172 | +declare class Nothing { |
| 173 | + // This lets us do `Exclude<T, Nothing>` |
| 174 | + private _: any |
| 175 | +} |
| 176 | + |
| 177 | +/** |
| 178 | + * The sentinel value returned by producers to replace the draft with undefined. |
| 179 | + */ |
| 180 | +export const nothing: Nothing |
| 181 | + |
| 182 | +/** |
| 183 | + * To let Immer treat your class instances as plain immutable objects |
| 184 | + * (albeit with a custom prototype), you must define either an instance property |
| 185 | + * or a static property on each of your custom classes. |
| 186 | + * |
| 187 | + * Otherwise, your class instance will never be drafted, which means it won't be |
| 188 | + * safe to mutate in a produce callback. |
| 189 | + */ |
| 190 | +export const immerable: unique symbol |
| 191 | + |
| 192 | +/** |
| 193 | + * Pass true to automatically freeze all copies created by Immer. |
| 194 | + * |
| 195 | + * By default, auto-freezing is disabled in production. |
| 196 | + */ |
| 197 | +export function setAutoFreeze(autoFreeze: boolean): void |
| 198 | + |
| 199 | +/** |
| 200 | + * Pass true to use the ES2015 `Proxy` class when creating drafts, which is |
| 201 | + * always faster than using ES5 proxies. |
| 202 | + * |
| 203 | + * By default, feature detection is used, so calling this is rarely necessary. |
| 204 | + */ |
| 205 | +export function setUseProxies(useProxies: boolean): void |
| 206 | + |
| 207 | +/** |
| 208 | + * Apply an array of Immer patches to the first argument. |
| 209 | + * |
| 210 | + * This function is a producer, which means copy-on-write is in effect. |
| 211 | + */ |
| 212 | +export function applyPatches<S>(base: S, patches: Patch[]): S |
| 213 | + |
| 214 | +/** |
| 215 | + * Create an Immer draft from the given base state, which may be a draft itself. |
| 216 | + * The draft can be modified until you finalize it with the `finishDraft` function. |
| 217 | + */ |
| 218 | +export function createDraft<T>(base: T): Draft<T> |
| 219 | + |
| 220 | +/** |
| 221 | + * Finalize an Immer draft from a `createDraft` call, returning the base state |
| 222 | + * (if no changes were made) or a modified copy. The draft must *not* be |
| 223 | + * mutated afterwards. |
| 224 | + * |
| 225 | + * Pass a function as the 2nd argument to generate Immer patches based on the |
| 226 | + * changes that were made. |
| 227 | + */ |
| 228 | +export function finishDraft<T>(draft: T, listener?: PatchListener): Immutable<T> |
| 229 | + |
| 230 | +/** Get the underlying object that is represented by the given draft */ |
| 231 | +export function original<T>(value: T): T | void |
| 232 | + |
| 233 | +/** Returns true if the given value is an Immer draft */ |
| 234 | +export function isDraft(value: any): boolean |
| 235 | + |
| 236 | +/** Returns true if the given value can be drafted by Immer */ |
| 237 | +export function isDraftable(value: any): boolean |
| 238 | + |
| 239 | +export class Immer { |
| 240 | + constructor(config: { |
| 241 | + useProxies?: boolean |
| 242 | + autoFreeze?: boolean |
| 243 | + onAssign?: ( |
| 244 | + state: ImmerState, |
| 245 | + prop: string | number, |
| 246 | + value: unknown |
| 247 | + ) => void |
| 248 | + onDelete?: (state: ImmerState, prop: string | number) => void |
| 249 | + onCopy?: (state: ImmerState) => void |
| 250 | + }) |
| 251 | + /** |
| 252 | + * The `produce` function takes a value and a "recipe function" (whose |
| 253 | + * return value often depends on the base state). The recipe function is |
| 254 | + * free to mutate its first argument however it wants. All mutations are |
| 255 | + * only ever applied to a __copy__ of the base state. |
| 256 | + * |
| 257 | + * Pass only a function to create a "curried producer" which relieves you |
| 258 | + * from passing the recipe function every time. |
| 259 | + * |
| 260 | + * Only plain objects and arrays are made mutable. All other objects are |
| 261 | + * considered uncopyable. |
| 262 | + * |
| 263 | + * Note: This function is __bound__ to its `Immer` instance. |
| 264 | + * |
| 265 | + * @param {any} base - the initial state |
| 266 | + * @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified |
| 267 | + * @param {Function} patchListener - optional function that will be called with all the patches produced here |
| 268 | + * @returns {any} a new state, or the initial state if nothing was modified |
| 269 | + */ |
| 270 | + produce: IProduce |
| 271 | + /** |
| 272 | + * When true, `produce` will freeze the copies it creates. |
| 273 | + */ |
| 274 | + readonly autoFreeze: boolean |
| 275 | + /** |
| 276 | + * When true, drafts are ES2015 proxies. |
| 277 | + */ |
| 278 | + readonly useProxies: boolean |
| 279 | + /** |
| 280 | + * Pass true to automatically freeze all copies created by Immer. |
| 281 | + * |
| 282 | + * By default, auto-freezing is disabled in production. |
| 283 | + */ |
| 284 | + setAutoFreeze(autoFreeze: boolean): void |
| 285 | + /** |
| 286 | + * Pass true to use the ES2015 `Proxy` class when creating drafts, which is |
| 287 | + * always faster than using ES5 proxies. |
| 288 | + * |
| 289 | + * By default, feature detection is used, so calling this is rarely necessary. |
| 290 | + */ |
| 291 | + setUseProxies(useProxies: boolean): void |
| 292 | +} |
| 293 | + |
| 294 | +export interface ImmerState<T = any> { |
| 295 | + parent?: ImmerState |
| 296 | + base: T |
| 297 | + copy: T |
| 298 | + assigned: {[prop: string]: boolean; [index: number]: boolean} |
| 299 | +} |
| 300 | + |
| 301 | +// Backward compatibility with --target es5 |
| 302 | +declare global { |
| 303 | + interface Set<T> {} |
| 304 | + interface Map<K, V> {} |
| 305 | + interface WeakSet<T> {} |
| 306 | + interface WeakMap<K extends object, V> {} |
| 307 | +} |
0 commit comments