UNPKG

7.58 kBPlain TextView Raw
1import {NOTHING} from "../internal"
2
3type AnyFunc = (...args: any[]) => any
4
5type PrimitiveType = number | string | boolean
6
7/** Object types that should never be mapped */
8type AtomicObject = Function | Promise<any> | Date | RegExp
9
10/**
11 * If the lib "ES2015.Collection" is not included in tsconfig.json,
12 * types like ReadonlyArray, WeakMap etc. fall back to `any` (specified nowhere)
13 * or `{}` (from the node types), in both cases entering an infinite recursion in
14 * pattern matching type mappings
15 * This type can be used to cast these types to `void` in these cases.
16 */
17export type IfAvailable<T, Fallback = void> =
18 // fallback if any
19 true | false extends (T extends never
20 ? true
21 : false)
22 ? Fallback // fallback if empty type
23 : keyof T extends never
24 ? Fallback // original type
25 : T
26
27/**
28 * These should also never be mapped but must be tested after regular Map and
29 * Set
30 */
31type WeakReferences = IfAvailable<WeakMap<any, any>> | IfAvailable<WeakSet<any>>
32
33export type WritableDraft<T> = {-readonly [K in keyof T]: Draft<T[K]>}
34
35/** Convert a readonly type into a mutable type, if possible */
36export type Draft<T> = T extends PrimitiveType
37 ? T
38 : T extends AtomicObject
39 ? T
40 : T extends ReadonlyMap<infer K, infer V> // Map extends ReadonlyMap
41 ? Map<Draft<K>, Draft<V>>
42 : T extends ReadonlySet<infer V> // Set extends ReadonlySet
43 ? Set<Draft<V>>
44 : T extends WeakReferences
45 ? T
46 : T extends object
47 ? WritableDraft<T>
48 : T
49
50/** Convert a mutable type into a readonly type */
51export type Immutable<T> = T extends PrimitiveType
52 ? T
53 : T extends AtomicObject
54 ? T
55 : T extends ReadonlyMap<infer K, infer V> // Map extends ReadonlyMap
56 ? ReadonlyMap<Immutable<K>, Immutable<V>>
57 : T extends ReadonlySet<infer V> // Set extends ReadonlySet
58 ? ReadonlySet<Immutable<V>>
59 : T extends WeakReferences
60 ? T
61 : T extends object
62 ? {readonly [K in keyof T]: Immutable<T[K]>}
63 : T
64
65export interface Patch {
66 op: "replace" | "remove" | "add"
67 path: (string | number)[]
68 value?: any
69}
70
71export type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void
72
73/** Converts `nothing` into `undefined` */
74type FromNothing<T> = T extends typeof NOTHING ? undefined : T
75
76/** The inferred return type of `produce` */
77export type Produced<Base, Return> = Return extends void
78 ? Base
79 : FromNothing<Return>
80
81/**
82 * Utility types
83 */
84type PatchesTuple<T> = readonly [T, Patch[], Patch[]]
85
86type ValidRecipeReturnType<State> =
87 | State
88 | void
89 | undefined
90 | (State extends undefined ? typeof NOTHING : never)
91
92type ReturnTypeWithPatchesIfNeeded<
93 State,
94 UsePatches extends boolean
95> = UsePatches extends true ? PatchesTuple<State> : State
96
97/**
98 * Core Producer inference
99 */
100type InferRecipeFromCurried<Curried> = Curried extends (
101 base: infer State,
102 ...rest: infer Args
103) => any // extra assertion to make sure this is a proper curried function (state, args) => state
104 ? ReturnType<Curried> extends State
105 ? (
106 draft: Draft<State>,
107 ...rest: Args
108 ) => ValidRecipeReturnType<Draft<State>>
109 : never
110 : never
111
112type InferInitialStateFromCurried<Curried> = Curried extends (
113 base: infer State,
114 ...rest: any[]
115) => any // extra assertion to make sure this is a proper curried function (state, args) => state
116 ? State
117 : never
118
119type InferCurriedFromRecipe<
120 Recipe,
121 UsePatches extends boolean
122> = Recipe extends (draft: infer DraftState, ...args: infer RestArgs) => any // verify return type
123 ? ReturnType<Recipe> extends ValidRecipeReturnType<DraftState>
124 ? (
125 base: Immutable<DraftState>,
126 ...args: RestArgs
127 ) => ReturnTypeWithPatchesIfNeeded<DraftState, UsePatches> // N.b. we return mutable draftstate, in case the recipe's first arg isn't read only, and that isn't expected as output either
128 : never // incorrect return type
129 : never // not a function
130
131type InferCurriedFromInitialStateAndRecipe<
132 State,
133 Recipe,
134 UsePatches extends boolean
135> = Recipe extends (
136 draft: Draft<State>,
137 ...rest: infer RestArgs
138) => ValidRecipeReturnType<State>
139 ? (
140 base?: State | undefined,
141 ...args: RestArgs
142 ) => ReturnTypeWithPatchesIfNeeded<State, UsePatches>
143 : never // recipe doesn't match initial state
144
145/**
146 * The `produce` function takes a value and a "recipe function" (whose
147 * return value often depends on the base state). The recipe function is
148 * free to mutate its first argument however it wants. All mutations are
149 * only ever applied to a __copy__ of the base state.
150 *
151 * Pass only a function to create a "curried producer" which relieves you
152 * from passing the recipe function every time.
153 *
154 * Only plain objects and arrays are made mutable. All other objects are
155 * considered uncopyable.
156 *
157 * Note: This function is __bound__ to its `Immer` instance.
158 *
159 * @param {any} base - the initial state
160 * @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
161 * @param {Function} patchListener - optional function that will be called with all the patches produced here
162 * @returns {any} a new state, or the initial state if nothing was modified
163 */
164export interface IProduce {
165 /** Curried producer that infers the recipe from the curried output function (e.g. when passing to setState) */
166 <Curried>(
167 recipe: InferRecipeFromCurried<Curried>,
168 initialState?: InferInitialStateFromCurried<Curried>
169 ): Curried
170
171 /** Curried producer that infers curried from the recipe */
172 <Recipe extends AnyFunc>(recipe: Recipe): InferCurriedFromRecipe<
173 Recipe,
174 false
175 >
176
177 /** Curried producer that infers curried from the State generic, which is explicitly passed in. */
178 <State>(
179 recipe: (
180 state: Draft<State>,
181 initialState: State
182 ) => ValidRecipeReturnType<State>
183 ): (state?: State) => State
184 <State, Args extends any[]>(
185 recipe: (
186 state: Draft<State>,
187 ...args: Args
188 ) => ValidRecipeReturnType<State>,
189 initialState: State
190 ): (state?: State, ...args: Args) => State
191 <State>(recipe: (state: Draft<State>) => ValidRecipeReturnType<State>): (
192 state: State
193 ) => State
194 <State, Args extends any[]>(
195 recipe: (state: Draft<State>, ...args: Args) => ValidRecipeReturnType<State>
196 ): (state: State, ...args: Args) => State
197
198 /** Curried producer with initial state, infers recipe from initial state */
199 <State, Recipe extends Function>(
200 recipe: Recipe,
201 initialState: State
202 ): InferCurriedFromInitialStateAndRecipe<State, Recipe, false>
203
204 /** Normal producer */
205 <Base, D = Draft<Base>>( // By using a default inferred D, rather than Draft<Base> in the recipe, we can override it.
206 base: Base,
207 recipe: (draft: D) => ValidRecipeReturnType<D>,
208 listener?: PatchListener
209 ): Base
210}
211
212/**
213 * Like `produce`, but instead of just returning the new state,
214 * a tuple is returned with [nextState, patches, inversePatches]
215 *
216 * Like produce, this function supports currying
217 */
218export interface IProduceWithPatches {
219 // Types copied from IProduce, wrapped with PatchesTuple
220 <Recipe extends AnyFunc>(recipe: Recipe): InferCurriedFromRecipe<Recipe, true>
221 <State, Recipe extends Function>(
222 recipe: Recipe,
223 initialState: State
224 ): InferCurriedFromInitialStateAndRecipe<State, Recipe, true>
225 <Base, D = Draft<Base>>(
226 base: Base,
227 recipe: (draft: D) => ValidRecipeReturnType<D>,
228 listener?: PatchListener
229 ): PatchesTuple<Base>
230}
231
232/**
233 * The type for `recipe function`
234 */
235export type Producer<T> = (draft: Draft<T>) => ValidRecipeReturnType<Draft<T>>
236
237// Fixes #507: bili doesn't export the types of this file if there is no actual source in it..
238// hopefully it get's tree-shaken away for everyone :)
239export function never_used() {}