diff --git a/frontend/common/Binder.js b/frontend/common/Binder.js index 21c9842dd9..1e7a6bea6d 100644 --- a/frontend/common/Binder.js +++ b/frontend/common/Binder.js @@ -1,4 +1,4 @@ -import immer from "../imports/immer.js" +import { produce } from "../imports/immer.js" import { timeout_promise, ws_address_from_base } from "./PlutoConnection.js" import { with_query_params } from "./URLTools.js" @@ -95,7 +95,7 @@ export const start_binder = async ({ setStatePromise, connect, launch_params }) // view stats on https://stats.plutojl.org/ count_stat(`binder-start`) await setStatePromise( - immer((/** @type {import("../components/Editor.js").EditorState} */ state) => { + produce((/** @type {import("../components/Editor.js").EditorState} */ state) => { state.backend_launch_phase = BackendLaunchPhase.requesting state.disable_ui = false // Clear the Status of the process that generated the HTML @@ -107,7 +107,7 @@ export const start_binder = async ({ setStatePromise, connect, launch_params }) const { binder_session_url, binder_session_token } = await request_binder(launch_params.binder_url.replace("mybinder.org/v2/", "mybinder.org/build/"), { on_log: (logs) => setStatePromise( - immer((/** @type {import("../components/Editor.js").EditorState} */ state) => { + produce((/** @type {import("../components/Editor.js").EditorState} */ state) => { state.backend_launch_logs = logs }) ), @@ -122,7 +122,7 @@ export const start_binder = async ({ setStatePromise, connect, launch_params }) } await setStatePromise( - immer((/** @type {import("../components/Editor.js").EditorState} */ state) => { + produce((/** @type {import("../components/Editor.js").EditorState} */ state) => { state.backend_launch_phase = BackendLaunchPhase.created state.binder_session_url = binder_session_url state.binder_session_token = binder_session_token @@ -133,7 +133,7 @@ export const start_binder = async ({ setStatePromise, connect, launch_params }) await fetch(with_token(binder_session_url)) await setStatePromise( - immer((/** @type {import("../components/Editor.js").EditorState} */ state) => { + produce((/** @type {import("../components/Editor.js").EditorState} */ state) => { state.backend_launch_phase = BackendLaunchPhase.responded }) ) @@ -197,7 +197,7 @@ export const start_binder = async ({ setStatePromise, connect, launch_params }) console.info("notebook_id:", new_notebook_id) await setStatePromise( - immer((/** @type {import("../components/Editor.js").EditorState} */ state) => { + produce((/** @type {import("../components/Editor.js").EditorState} */ state) => { state.notebook.notebook_id = new_notebook_id state.backend_launch_phase = BackendLaunchPhase.notebook_running state.refresh_target = edit_url diff --git a/frontend/common/RunLocal.js b/frontend/common/RunLocal.js index b43e8b90b5..2acdb33f06 100644 --- a/frontend/common/RunLocal.js +++ b/frontend/common/RunLocal.js @@ -1,4 +1,4 @@ -import immer from "../imports/immer.js" +import { produce } from "../imports/immer.js" import { BackendLaunchPhase } from "./Binder.js" import { timeout_promise } from "./PlutoConnection.js" import { with_query_params } from "./URLTools.js" @@ -18,7 +18,7 @@ export const start_local = async ({ setStatePromise, connect, launch_params }) = if (launch_params.pluto_server_url == null || launch_params.notebookfile == null) throw Error("Invalid launch parameters for starting locally.") await setStatePromise( - immer((/** @type {import("../components/Editor.js").EditorState} */ state) => { + produce((/** @type {import("../components/Editor.js").EditorState} */ state) => { state.backend_launch_phase = BackendLaunchPhase.responded state.disable_ui = false // Clear the Status of the process that generated the HTML @@ -63,7 +63,7 @@ export const start_local = async ({ setStatePromise, connect, launch_params }) = window.history.replaceState({}, "", edit_url) await setStatePromise( - immer((/** @type {import("../components/Editor.js").EditorState} */ state) => { + produce((/** @type {import("../components/Editor.js").EditorState} */ state) => { state.notebook.notebook_id = new_notebook_id state.backend_launch_phase = BackendLaunchPhase.notebook_running }) diff --git a/frontend/common/SliderServerClient.js b/frontend/common/SliderServerClient.js index 40403a8cfa..a08f9f3815 100644 --- a/frontend/common/SliderServerClient.js +++ b/frontend/common/SliderServerClient.js @@ -1,7 +1,7 @@ import { trailingslash } from "./Binder.js" import { plutohash_arraybuffer, debounced_promises, base64url_arraybuffer } from "./PlutoHash.js" import { pack, unpack } from "./MsgPack.js" -import immer from "../imports/immer.js" +import { produce } from "../imports/immer.js" import _ from "../imports/lodash.js" const assert_response_ok = (/** @type {Response} */ r) => (r.ok ? r : Promise.reject(r)) @@ -39,9 +39,19 @@ export const nothing_actions = ({ actions }) => ]) ) +/** + * @param {{ + * setStatePromise: any, + * launch_params: import("../components/Editor.js").LaunchParameters, + * actions: any, + * get_original_state: () => import("../components/Editor.js").NotebookData, + * get_current_state: () => import("../components/Editor.js").NotebookData, + * apply_notebook_patches: (patches: import("../imports/immer.js").Patch[], old_state?: import("../components/Editor.js").NotebookData?, get_reverse_patches?: boolean) => Promise, + * }} props + */ export const slider_server_actions = ({ setStatePromise, launch_params, actions, get_original_state, get_current_state, apply_notebook_patches }) => { setStatePromise( - immer((state) => { + produce((/** @type {import("../components/Editor.js").EditorState} */ state) => { state.slider_server.connecting = true }) ) @@ -62,7 +72,7 @@ export const slider_server_actions = ({ setStatePromise, launch_params, actions, bond_connections.then((x) => { console.log("Bond connections:", x) setStatePromise( - immer((state) => { + produce((/** @type {import("../components/Editor.js").EditorState} */ state) => { state.slider_server.connecting = false state.slider_server.interactive = Object.keys(x).length > 0 }) @@ -85,7 +95,7 @@ export const slider_server_actions = ({ setStatePromise, launch_params, actions, const update_cells_running = async (running) => await setStatePromise( - immer((state) => { + produce((/** @type {import("../components/Editor.js").EditorState} */ state) => { running_cells.forEach((cell_id) => (state.notebook.cell_results[cell_id][starts.has(cell_id) ? "running" : "queued"] = running)) }) ) @@ -128,12 +138,12 @@ export const slider_server_actions = ({ setStatePromise, launch_params, actions, await apply_notebook_patches( patches, - immer((state) => { + produce(get_current_state(), (state) => { const original = get_original_state() ids_of_cells_that_ran.forEach((id) => { state.cell_results[id] = original.cell_results[id] }) - })(get_current_state()) + }) ) } catch (e) { console.error(unpacked, e) @@ -148,7 +158,7 @@ export const slider_server_actions = ({ setStatePromise, launch_params, actions, ...nothing_actions({ actions }), set_bond: async (symbol, value) => { setStatePromise( - immer((state) => { + produce((/** @type {import("../components/Editor.js").EditorState} */ state) => { state.notebook.bonds[symbol] = { value: value } }) ) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index af69e81dcc..f1a874958b 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -1,6 +1,6 @@ import { html, Component } from "../imports/Preact.js" import * as preact from "../imports/Preact.js" -import immer, { applyPatches, produceWithPatches } from "../imports/immer.js" +import { produce, applyPatches, produceWithPatches } from "../imports/immer.js" import _ from "../imports/lodash.js" import { empty_notebook_state, set_disable_ui_css } from "../editor.js" @@ -20,7 +20,7 @@ import { Scroller } from "./Scroller.js" import { ExportBanner } from "./ExportBanner.js" import { Popup } from "./Popup.js" -import { slice_utf8, length_utf8 } from "../common/UnicodeTools.js" +import { slice_utf8 } from "../common/UnicodeTools.js" import { has_ctrl_or_cmd_pressed, ctrl_or_cmd_name, @@ -297,6 +297,10 @@ export const url_logo_small = document.head.querySelector("link[rel='pluto-logo- * extended_components: any, * is_recording: boolean, * recording_waiting_to_start: boolean, + * slider_server: { + * connecting: boolean, + * interactive: boolean, + * }, * }} */ @@ -365,7 +369,7 @@ export class Editor extends Component { set_doc_query: (query) => this.setState({ desired_doc_query: query }), set_local_cell: (cell_id, new_val) => { return this.setStatePromise( - immer((/** @type {EditorState} */ state) => { + produce((/** @type {EditorState} */ state) => { state.cell_inputs_local[cell_id] = { code: new_val, } @@ -422,7 +426,7 @@ export class Editor extends Component { * See ** 1 ** */ this.setState( - immer((/** @type {EditorState} */ state) => { + produce((/** @type {EditorState} */ state) => { // Deselect everything first, to clean things up state.selected_cells = [] @@ -460,7 +464,7 @@ export class Editor extends Component { const new_code = `${block_start}\n\t${cell.code.replace(/\n/g, "\n\t")}\n${block_end}` await this.setStatePromise( - immer((/** @type {EditorState} */ state) => { + produce((/** @type {EditorState} */ state) => { state.cell_inputs_local[cell_id] = { code: new_code, } @@ -488,7 +492,7 @@ export class Editor extends Component { }) this.setState( - immer((/** @type {EditorState} */ state) => { + produce((/** @type {EditorState} */ state) => { for (let cell of cells_to_add) { state.cell_inputs_local[cell.cell_id] = cell } @@ -617,7 +621,7 @@ export class Editor extends Component { // This is a "dirty" trick, as this should actually be stored in some shared request_status => status state // But for now... this is fine 😼 await this.setStatePromise( - immer((/** @type {EditorState} */ state) => { + produce((/** @type {EditorState} */ state) => { for (let cell_id of cell_ids) { if (state.notebook.cell_results[cell_id] != null) { state.notebook.cell_results[cell_id].queued = this.is_process_ready() @@ -691,7 +695,7 @@ export class Editor extends Component { let _copy_of_patches, reverse_of_patches = [] this.setState( - immer((/** @type {EditorState} */ state) => { + produce((/** @type {EditorState} */ state) => { let new_notebook try { // To test this, uncomment the lines below: @@ -1170,7 +1174,7 @@ patch: ${JSON.stringify( window.plutoDesktop?.ipcRenderer.once("PLUTO-MOVE-NOTEBOOK", async (/** @type {string?} */ loc) => { if (!!loc) await this.setStatePromise( - immer((/** @type {EditorState} */ state) => { + produce((/** @type {EditorState} */ state) => { state.notebook.in_temp_dir = false state.notebook.path = loc }) @@ -1640,7 +1644,7 @@ The notebook file saves every time you run a cell.` apply_notebook_patches=${this.apply_notebook_patches} reset_notebook_state=${() => this.setStatePromise( - immer((/** @type {EditorState} */ state) => { + produce((/** @type {EditorState} */ state) => { state.notebook = this.props.initial_notebook_state }) )} diff --git a/frontend/components/Editor/LaunchBackendButton.js b/frontend/components/Editor/LaunchBackendButton.js index e835a5e2eb..51b03b84c5 100644 --- a/frontend/components/Editor/LaunchBackendButton.js +++ b/frontend/components/Editor/LaunchBackendButton.js @@ -1,15 +1,15 @@ -import { html, useMemo, useEffect } from "../../imports/Preact.js" +import { html, useMemo } from "../../imports/Preact.js" import * as preact from "../../imports/Preact.js" import { RunLocalButton, BinderButton } from "../EditOrRunButton.js" import { start_local } from "../../common/RunLocal.js" import { BackendLaunchPhase, start_binder } from "../../common/Binder.js" -import immer, { applyPatches, produceWithPatches } from "../../imports/immer.js" +import { produce } from "../../imports/immer.js" export const EditorLaunchBackendButton = ({ editor, launch_params, status }) => { try { const EnvRun = useMemo( // @ts-ignore - () => window?.pluto_injected_environment?.environment?.({ client: editor.client, editor, imports: { immer, preact } })?.custom_run_or_edit, + () => window?.pluto_injected_environment?.environment?.({ client: editor.client, editor, imports: { immer: produce, preact } })?.custom_run_or_edit, [editor.client, editor] ) // @ts-ignore diff --git a/frontend/components/FrontmatterInput.js b/frontend/components/FrontmatterInput.js index e278036aa5..c5ede6c610 100644 --- a/frontend/components/FrontmatterInput.js +++ b/frontend/components/FrontmatterInput.js @@ -5,7 +5,7 @@ import _ from "../imports/lodash.js" import "https://cdn.jsdelivr.net/gh/fonsp/rebel-tag-input@1.0.6/lib/rebel-tag-input.mjs" //@ts-ignore -import immer from "../imports/immer.js" +import { produce } from "../imports/immer.js" import { useDialog } from "../common/useDialog.js" import { FeaturedCard } from "./welcome/FeaturedCard.js" import { useEventListener } from "../common/useEventListener.js" @@ -26,7 +26,7 @@ export const FrontMatterInput = ({ filename, remote_frontmatter, set_remote_fron const fm_setter = (key) => (value) => set_frontmatter( - immer((fm) => { + produce((fm) => { _.set(fm, key, value) }) ) @@ -87,7 +87,7 @@ export const FrontMatterInput = ({ filename, remote_frontmatter, set_remote_fron onClick=${() => { // TODO set_frontmatter( - immer((fm) => { + produce((fm) => { _.unset(fm, path) }) ) @@ -103,7 +103,7 @@ export const FrontMatterInput = ({ filename, remote_frontmatter, set_remote_fron const fieldname = prompt("Field name:") if (fieldname) { set_frontmatter( - immer((fm) => { + produce((fm) => { _.set(fm, `${base_path}${fieldname}`, null) }) ) diff --git a/frontend/components/LiveDocsTab.js b/frontend/components/LiveDocsTab.js index 96e41dc75c..4f985f48cc 100644 --- a/frontend/components/LiveDocsTab.js +++ b/frontend/components/LiveDocsTab.js @@ -1,5 +1,5 @@ import { html, useState, useRef, useLayoutEffect, useEffect, useMemo, useContext } from "../imports/Preact.js" -import immer from "../imports/immer.js" +import { produce } from "../imports/immer.js" import observablehq from "../common/SetupCellEnvironment.js" import { RawHTMLContainer, highlight } from "./CellOutput.js" @@ -26,7 +26,7 @@ export let LiveDocsTab = ({ focus_on_open, desired_doc_query, on_update_doc_quer body: `

Welcome to the Live docs! Keep this little window open while you work on the notebook, and you will get documentation of everything you type!

You can also type a query above.


Still stuck? Here are some tips.

`, loading: false, }) - let update_state = (mutation) => set_state(immer((state) => mutation(state))) + let update_state = (mutation) => set_state(produce((state) => mutation(state))) useEffect(() => { if (state.loading) { diff --git a/frontend/components/RecordingUI.js b/frontend/components/RecordingUI.js index ebd170f7ba..05d7502823 100644 --- a/frontend/components/RecordingUI.js +++ b/frontend/components/RecordingUI.js @@ -2,7 +2,7 @@ import _ from "../imports/lodash.js" import { createSilentAudio, create_recorder } from "../common/AudioRecording.js" import { html, useEffect, useState, useRef, useCallback, useLayoutEffect, useMemo } from "../imports/Preact.js" import { AudioPlayer } from "./AudioPlayer.js" -import immer from "../imports/immer.js" +import { produce } from "../imports/immer.js" import { base64_arraybuffer, blob_url_to_data_url } from "../common/PlutoHash.js" import { pack, unpack } from "../common/MsgPack.js" diff --git a/frontend/imports/immer.d.ts b/frontend/imports/immer.d.ts index 35884f8800..9e2bc16de4 100644 --- a/frontend/imports/immer.d.ts +++ b/frontend/imports/immer.d.ts @@ -1,19 +1,262 @@ -export type Patch = { - path: Array - op: "add" | "replace" | "remove" - value: any +/** + * The sentinel value returned by producers to replace the draft with undefined. + */ +declare const NOTHING: unique symbol; +/** + * To let Immer treat your class instances as plain immutable objects + * (albeit with a custom prototype), you must define either an instance property + * or a static property on each of your custom classes. + * + * Otherwise, your class instance will never be drafted, which means it won't be + * safe to mutate in a produce callback. + */ +declare const DRAFTABLE: unique symbol; + +type AnyFunc = (...args: any[]) => any; +type PrimitiveType = number | string | boolean; +/** Object types that should never be mapped */ +type AtomicObject = Function | Promise | Date | RegExp; +/** + * If the lib "ES2015.Collection" is not included in tsconfig.json, + * types like ReadonlyArray, WeakMap etc. fall back to `any` (specified nowhere) + * or `{}` (from the node types), in both cases entering an infinite recursion in + * pattern matching type mappings + * This type can be used to cast these types to `void` in these cases. + */ +type IfAvailable = true | false extends (T extends never ? true : false) ? Fallback : keyof T extends never ? Fallback : T; +/** + * These should also never be mapped but must be tested after regular Map and + * Set + */ +type WeakReferences = IfAvailable> | IfAvailable>; +type WritableDraft = { + -readonly [K in keyof T]: Draft; +}; +/** Convert a readonly type into a mutable type, if possible */ +type Draft = T extends PrimitiveType ? T : T extends AtomicObject ? T : T extends ReadonlyMap ? Map, Draft> : T extends ReadonlySet ? Set> : T extends WeakReferences ? T : T extends object ? WritableDraft : T; +/** Convert a mutable type into a readonly type */ +type Immutable = T extends PrimitiveType ? T : T extends AtomicObject ? T : T extends ReadonlyMap ? ReadonlyMap, Immutable> : T extends ReadonlySet ? ReadonlySet> : T extends WeakReferences ? T : T extends object ? { + readonly [K in keyof T]: Immutable; +} : T; +interface Patch { + op: "replace" | "remove" | "add"; + path: (string | number)[]; + value?: any; +} +type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void; +/** + * Utility types + */ +type PatchesTuple = readonly [T, Patch[], Patch[]]; +type ValidRecipeReturnType = State | void | undefined | (State extends undefined ? typeof NOTHING : never); +type ReturnTypeWithPatchesIfNeeded = UsePatches extends true ? PatchesTuple : State; +/** + * Core Producer inference + */ +type InferRecipeFromCurried = Curried extends (base: infer State, ...rest: infer Args) => any ? ReturnType extends State ? (draft: Draft, ...rest: Args) => ValidRecipeReturnType> : never : never; +type InferInitialStateFromCurried = Curried extends (base: infer State, ...rest: any[]) => any ? State : never; +type InferCurriedFromRecipe = Recipe extends (draft: infer DraftState, ...args: infer RestArgs) => any ? ReturnType extends ValidRecipeReturnType ? (base: Immutable, ...args: RestArgs) => ReturnTypeWithPatchesIfNeeded : never : never; +type InferCurriedFromInitialStateAndRecipe = Recipe extends (draft: Draft, ...rest: infer RestArgs) => ValidRecipeReturnType ? (base?: State | undefined, ...args: RestArgs) => ReturnTypeWithPatchesIfNeeded : never; +/** + * The `produce` function takes a value and a "recipe function" (whose + * return value often depends on the base state). The recipe function is + * free to mutate its first argument however it wants. All mutations are + * only ever applied to a __copy__ of the base state. + * + * Pass only a function to create a "curried producer" which relieves you + * from passing the recipe function every time. + * + * Only plain objects and arrays are made mutable. All other objects are + * considered uncopyable. + * + * Note: This function is __bound__ to its `Immer` instance. + * + * @param {any} base - the initial state + * @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified + * @param {Function} patchListener - optional function that will be called with all the patches produced here + * @returns {any} a new state, or the initial state if nothing was modified + */ +interface IProduce { + /** Curried producer that infers the recipe from the curried output function (e.g. when passing to setState) */ + (recipe: InferRecipeFromCurried, initialState?: InferInitialStateFromCurried): Curried; + /** Curried producer that infers curried from the recipe */ + (recipe: Recipe): InferCurriedFromRecipe; + /** Curried producer that infers curried from the State generic, which is explicitly passed in. */ + (recipe: (state: Draft, initialState: State) => ValidRecipeReturnType): (state?: State) => State; + (recipe: (state: Draft, ...args: Args) => ValidRecipeReturnType, initialState: State): (state?: State, ...args: Args) => State; + (recipe: (state: Draft) => ValidRecipeReturnType): (state: State) => State; + (recipe: (state: Draft, ...args: Args) => ValidRecipeReturnType): (state: State, ...args: Args) => State; + /** Curried producer with initial state, infers recipe from initial state */ + (recipe: Recipe, initialState: State): InferCurriedFromInitialStateAndRecipe; + /** Normal producer */ + >(// By using a default inferred D, rather than Draft in the recipe, we can override it. + base: Base, recipe: (draft: D) => ValidRecipeReturnType, listener?: PatchListener): Base; } +/** + * Like `produce`, but instead of just returning the new state, + * a tuple is returned with [nextState, patches, inversePatches] + * + * Like produce, this function supports currying + */ +interface IProduceWithPatches { + (recipe: Recipe): InferCurriedFromRecipe; + (recipe: Recipe, initialState: State): InferCurriedFromInitialStateAndRecipe; + >(base: Base, recipe: (draft: D) => ValidRecipeReturnType, listener?: PatchListener): PatchesTuple; +} +/** + * The type for `recipe function` + */ +type Producer = (draft: Draft) => ValidRecipeReturnType>; + +type Objectish = AnyObject | AnyArray | AnyMap | AnySet; +type AnyObject = { + [key: string]: any; +}; +type AnyArray = Array; +type AnySet = Set; +type AnyMap = Map; -// type PatchesCallback = (patches: Array, inversePatch: Array) => void +/** Returns true if the given value is an Immer draft */ +declare function isDraft(value: any): boolean; +/** Returns true if the given value can be drafted by Immer */ +declare function isDraftable(value: any): boolean; +/** Get the underlying object that is represented by the given draft */ +declare function original(value: T): T | undefined; +/** + * Freezes draftable objects. Returns the original object. + * By default freezes shallowly, but if the second argument is `true` it will freeze recursively. + * + * @param obj + * @param deep + */ +declare function freeze(obj: T, deep?: boolean): T; + +interface ProducersFns { + produce: IProduce; + produceWithPatches: IProduceWithPatches; +} +type StrictMode = boolean | "class_only"; +declare class Immer implements ProducersFns { + autoFreeze_: boolean; + useStrictShallowCopy_: StrictMode; + constructor(config?: { + autoFreeze?: boolean; + useStrictShallowCopy?: StrictMode; + }); + /** + * The `produce` function takes a value and a "recipe function" (whose + * return value often depends on the base state). The recipe function is + * free to mutate its first argument however it wants. All mutations are + * only ever applied to a __copy__ of the base state. + * + * Pass only a function to create a "curried producer" which relieves you + * from passing the recipe function every time. + * + * Only plain objects and arrays are made mutable. All other objects are + * considered uncopyable. + * + * Note: This function is __bound__ to its `Immer` instance. + * + * @param {any} base - the initial state + * @param {Function} recipe - function that receives a proxy of the base state as first argument and which can be freely modified + * @param {Function} patchListener - optional function that will be called with all the patches produced here + * @returns {any} a new state, or the initial state if nothing was modified + */ + produce: IProduce; + produceWithPatches: IProduceWithPatches; + createDraft(base: T): Draft; + finishDraft>(draft: D, patchListener?: PatchListener): D extends Draft ? T : never; + /** + * Pass true to automatically freeze all copies created by Immer. + * + * By default, auto-freezing is enabled. + */ + setAutoFreeze(value: boolean): void; + /** + * Pass true to enable strict shallow copy. + * + * By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties. + */ + setUseStrictShallowCopy(value: StrictMode): void; + applyPatches(base: T, patches: readonly Patch[]): T; +} -declare function produce(value: T, mutator: (draft: T) => void | T): T -declare function produce(mutator: (draft: T) => void | T): (value: T) => T +/** Takes a snapshot of the current state of a draft and finalizes it (but without freezing). This is a great utility to print the current state during debugging (no Proxies in the way). The output of current can also be safely leaked outside the producer. */ +declare function current(value: T): T; -export default produce +declare function enablePatches(): void; -declare function applyPatches(value: T, patches: Array): T +declare function enableMapSet(): void; -export { applyPatches } +/** + * The `produce` function takes a value and a "recipe function" (whose + * return value often depends on the base state). The recipe function is + * free to mutate its first argument however it wants. All mutations are + * only ever applied to a __copy__ of the base state. + * + * Pass only a function to create a "curried producer" which relieves you + * from passing the recipe function every time. + * + * Only plain objects and arrays are made mutable. All other objects are + * considered uncopyable. + * + * Note: This function is __bound__ to its `Immer` instance. + * + * @param {any} base - the initial state + * @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified + * @param {Function} patchListener - optional function that will be called with all the patches produced here + * @returns {any} a new state, or the initial state if nothing was modified + */ +declare const produce: IProduce; +/** + * Like `produce`, but `produceWithPatches` always returns a tuple + * [nextState, patches, inversePatches] (instead of just the next state) + */ +declare const produceWithPatches: IProduceWithPatches; +/** + * Pass true to automatically freeze all copies created by Immer. + * + * Always freeze by default, even in production mode + */ +declare const setAutoFreeze: (value: boolean) => void; +/** + * Pass true to enable strict shallow copy. + * + * By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties. + */ +declare const setUseStrictShallowCopy: (value: StrictMode) => void; +/** + * Apply an array of Immer patches to the first argument. + * + * This function is a producer, which means copy-on-write is in effect. + */ +declare const applyPatches: (base: T, patches: readonly Patch[]) => T; +/** + * Create an Immer draft from the given base state, which may be a draft itself. + * The draft can be modified until you finalize it with the `finishDraft` function. + */ +declare const createDraft: (base: T) => Draft; +/** + * Finalize an Immer draft from a `createDraft` call, returning the base state + * (if no changes were made) or a modified copy. The draft must *not* be + * mutated afterwards. + * + * Pass a function as the 2nd argument to generate Immer patches based on the + * changes that were made. + */ +declare const finishDraft: (draft: D, patchListener?: PatchListener | undefined) => D extends Draft ? T : never; +/** + * This function is actually a no-op, but can be used to cast an immutable type + * to an draft type and make TypeScript happy + * + * @param value + */ +declare function castDraft(value: T): Draft; +/** + * This function is actually a no-op, but can be used to cast a mutable type + * to an immutable type and make TypeScript happy + * @param value + */ +declare function castImmutable(value: T): Immutable; -declare function produceWithPatches(value: T, mutator: (draft: T) => void | T): [T, Array, Array] -export { produceWithPatches } +export { Draft, Immer, Immutable, Objectish, Patch, PatchListener, Producer, StrictMode, WritableDraft, applyPatches, castDraft, castImmutable, createDraft, current, enableMapSet, enablePatches, finishDraft, freeze, DRAFTABLE as immerable, isDraft, isDraftable, NOTHING as nothing, original, produce, produceWithPatches, setAutoFreeze, setUseStrictShallowCopy }; \ No newline at end of file diff --git a/frontend/imports/immer.js b/frontend/imports/immer.js index aec3b510ac..0281a7f3d6 100644 --- a/frontend/imports/immer.js +++ b/frontend/imports/immer.js @@ -1,8 +1,6 @@ // @ts-nocheck -import immer, { produceWithPatches, applyPatches, enablePatches, setAutoFreeze } from "https://cdn.jsdelivr.net/npm/immer@8.0.0/dist/immer.esm.js" - -export { applyPatches, produceWithPatches } -export default immer +export { produce, produceWithPatches, applyPatches } from "https://cdn.jsdelivr.net/npm/immer@10.1.1/dist/immer.mjs" +import { enablePatches, setAutoFreeze } from "https://cdn.jsdelivr.net/npm/immer@10.1.1/dist/immer.mjs" enablePatches()