Skip to content

Commit

Permalink
Update to new version of upsert proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
EskiMojo14 committed Nov 27, 2024
1 parent 4d92026 commit 2ad4bb4
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 94 deletions.
10 changes: 6 additions & 4 deletions packages/toolkit/src/combineSlices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
UnionToIntersection,
WithOptionalProp,
} from './tsHelpers'
import { emplace } from './utils'
import { getOrInsertComputed } from './utils'

type SliceLike<ReducerPath extends string, State> = {
reducerPath: ReducerPath
Expand Down Expand Up @@ -324,8 +324,10 @@ const createStateProxy = <State extends object>(
state: State,
reducerMap: Partial<Record<string, Reducer>>,
) =>
emplace(stateProxyMap, state, {
insert: () =>
getOrInsertComputed(
stateProxyMap,
state,
() =>
new Proxy(state, {
get: (target, prop, receiver) => {
if (prop === ORIGINAL_STATE) return target
Expand All @@ -350,7 +352,7 @@ const createStateProxy = <State extends object>(
return result
},
}),
}) as State
) as State

const original = (state: any) => {
if (!isStateProxy(state)) {
Expand Down
40 changes: 20 additions & 20 deletions packages/toolkit/src/createSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { createReducer } from './createReducer'
import type { ActionReducerMapBuilder, TypedActionCreator } from './mapBuilders'
import { executeReducerBuilderCallback } from './mapBuilders'
import type { Id, TypeGuard } from './tsHelpers'
import { emplace } from './utils'
import { getOrInsertComputed } from './utils'

const asyncThunkSymbol = /* @__PURE__ */ Symbol.for(
'rtk-slice-createasyncthunk',
Expand Down Expand Up @@ -769,25 +769,25 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) {
function getSelectors(
selectState: (rootState: any) => State = selectSelf,
) {
const selectorCache = emplace(injectedSelectorCache, injected, {
insert: () => new WeakMap(),
})

return emplace(selectorCache, selectState, {
insert: () => {
const map: Record<string, Selector<any, any>> = {}
for (const [name, selector] of Object.entries(
options.selectors ?? {},
)) {
map[name] = wrapSelector(
selector,
selectState,
getInitialState,
injected,
)
}
return map
},
const selectorCache = getOrInsertComputed(
injectedSelectorCache,
injected,
() => new WeakMap(),
)

return getOrInsertComputed(selectorCache, selectState, () => {
const map: Record<string, Selector<any, any>> = {}
for (const [name, selector] of Object.entries(
options.selectors ?? {},
)) {
map[name] = wrapSelector(
selector,
selectState,
getInitialState,
injected,
)
}
return map
}) as any
}
return {
Expand Down
4 changes: 2 additions & 2 deletions packages/toolkit/src/dynamicMiddleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { compose } from 'redux'
import { createAction } from '../createAction'
import { isAllOf } from '../matchers'
import { nanoid } from '../nanoid'
import { emplace, find } from '../utils'
import { find, getOrInsertComputed } from '../utils'
import type {
AddMiddleware,
DynamicMiddleware,
Expand Down Expand Up @@ -73,7 +73,7 @@ export const createDynamicMiddleware = <

const getFinalMiddleware: Middleware<{}, State, DispatchType> = (api) => {
const appliedMiddleware = Array.from(middlewareMap.values()).map((entry) =>
emplace(entry.applied, api, { insert: () => entry.middleware(api) }),
getOrInsertComputed(entry.applied, api, entry.middleware),
)
return compose(...appliedMiddleware)
}
Expand Down
93 changes: 25 additions & 68 deletions packages/toolkit/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,81 +87,38 @@ export function freezeDraftable<T>(val: T) {
return isDraftable(val) ? createNextState(val, () => {}) : val
}

interface WeakMapEmplaceHandler<K extends object, V> {
/**
* Will be called to get value, if no value is currently in map.
*/
insert?(key: K, map: WeakMap<K, V>): V
/**
* Will be called to update a value, if one exists already.
*/
update?(previous: V, key: K, map: WeakMap<K, V>): V
}
export function getOrInsert<K extends object, V>(
map: WeakMap<K, V>,
key: K,
value: V,
): V
export function getOrInsert<K, V>(map: Map<K, V>, key: K, value: V): V
export function getOrInsert<K extends object, V>(
map: Map<K, V> | WeakMap<K, V>,
key: K,
value: V,
): V {
if (map.has(key)) return map.get(key) as V

interface MapEmplaceHandler<K, V> {
/**
* Will be called to get value, if no value is currently in map.
*/
insert?(key: K, map: Map<K, V>): V
/**
* Will be called to update a value, if one exists already.
*/
update?(previous: V, key: K, map: Map<K, V>): V
return map.set(key, value).get(key) as V
}

export function emplace<K, V>(
map: Map<K, V>,
export function getOrInsertComputed<K extends object, V>(
map: WeakMap<K, V>,
key: K,
handler: MapEmplaceHandler<K, V>,
compute: (key: K) => V,
): V
export function emplace<K extends object, V>(
map: WeakMap<K, V>,
export function getOrInsertComputed<K, V>(
map: Map<K, V>,
key: K,
handler: WeakMapEmplaceHandler<K, V>,
compute: (key: K) => V,
): V
/**
* Allow inserting a new value, or updating an existing one
* @throws if called for a key with no current value and no `insert` handler is provided
* @returns current value in map (after insertion/updating)
* ```ts
* // return current value if already in map, otherwise initialise to 0 and return that
* const num = emplace(map, key, {
* insert: () => 0
* })
*
* // increase current value by one if already in map, otherwise initialise to 0
* const num = emplace(map, key, {
* update: (n) => n + 1,
* insert: () => 0,
* })
*
* // only update if value's already in the map - and increase it by one
* if (map.has(key)) {
* const num = emplace(map, key, {
* update: (n) => n + 1,
* })
* }
* ```
*
* @remarks
* Based on https://github.com/tc39/proposal-upsert currently in Stage 2 - maybe in a few years we'll be able to replace this with direct method calls
*/
export function emplace<K extends object, V>(
map: WeakMap<K, V>,
export function getOrInsertComputed<K extends object, V>(
map: Map<K, V> | WeakMap<K, V>,
key: K,
handler: WeakMapEmplaceHandler<K, V>,
compute: (key: K) => V,
): V {
if (map.has(key)) {
let value = map.get(key) as V
if (handler.update) {
value = handler.update(value, key, map)
map.set(key, value)
}
return value
}
if (!handler.insert)
throw new Error('No insert provided for key not already in map')
const inserted = handler.insert(key, map)
map.set(key, inserted)
return inserted
if (map.has(key)) return map.get(key) as V

return map.set(key, compute(key)).get(key) as V
}

0 comments on commit 2ad4bb4

Please sign in to comment.