import type { Action, UnknownAction, Reducer } from 'redux' import type { Selector } from 'reselect' import type { ActionCreatorWithoutPayload, PayloadAction, PayloadActionCreator, PrepareAction, _ActionCreatorWithPreparedPayload, } from './createAction' import { createAction } from './createAction' import type { ActionMatcherDescriptionCollection, CaseReducer, ReducerWithInitialState, } from './createReducer' import { createReducer } from './createReducer' import type { ActionReducerMapBuilder, TypedActionCreator } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' import type { Id, Tail, TypeGuard } from './tsHelpers' import type { InjectConfig } from './combineSlices' import type { AsyncThunk, AsyncThunkConfig, AsyncThunkOptions, AsyncThunkPayloadCreator, OverrideThunkApiConfigs, } from './createAsyncThunk' import { createAsyncThunk as _createAsyncThunk } from './createAsyncThunk' import { emplace } from './utils' const asyncThunkSymbol = /* @__PURE__ */ Symbol.for( 'rtk-slice-createasyncthunk', ) // type is annotated because it's too long to infer export const asyncThunkCreator: { [asyncThunkSymbol]: typeof _createAsyncThunk } = { [asyncThunkSymbol]: _createAsyncThunk, } interface InjectIntoConfig extends InjectConfig { reducerPath?: NewReducerPath } /** * The return value of `createSlice` * * @public */ export interface Slice< State = any, CaseReducers extends SliceCaseReducers = SliceCaseReducers, Name extends string = string, ReducerPath extends string = Name, Selectors extends SliceSelectors = SliceSelectors, > { /** * The slice name. */ name: Name /** * The slice reducer path. */ reducerPath: ReducerPath /** * The slice's reducer. */ reducer: Reducer /** * Action creators for the types of actions that are handled by the slice * reducer. */ actions: CaseReducerActions /** * The individual case reducer functions that were passed in the `reducers` parameter. * This enables reuse and testing if they were defined inline when calling `createSlice`. */ caseReducers: SliceDefinedCaseReducers /** * Provides access to the initial state value given to the slice. * If a lazy state initializer was provided, it will be called and a fresh value returned. */ getInitialState: () => State /** * Get localised slice selectors (expects to be called with *just* the slice's state as the first parameter) */ getSelectors(): Id> /** * Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state) */ getSelectors( selectState: (rootState: RootState) => State, ): Id> /** * Selectors that assume the slice's state is `rootState[slice.reducerPath]` (which is usually the case) * * Equivalent to `slice.getSelectors((state: RootState) => state[slice.reducerPath])`. */ get selectors(): Id< SliceDefinedSelectors > /** * Inject slice into provided reducer (return value from `combineSlices`), and return injected slice. */ injectInto( this: this, injectable: { inject: ( slice: { reducerPath: string; reducer: Reducer }, config?: InjectConfig, ) => void }, config?: InjectIntoConfig, ): InjectedSlice /** * Select the slice state, using the slice's current reducerPath. * * Will throw an error if slice is not found. */ selectSlice(state: { [K in ReducerPath]: State }): State } /** * A slice after being called with `injectInto(reducer)`. * * Selectors can now be called with an `undefined` value, in which case they use the slice's initial state. */ interface InjectedSlice< State = any, CaseReducers extends SliceCaseReducers = SliceCaseReducers, Name extends string = string, ReducerPath extends string = Name, Selectors extends SliceSelectors = SliceSelectors, > extends Omit< Slice, 'getSelectors' | 'selectors' > { /** * Get localised slice selectors (expects to be called with *just* the slice's state as the first parameter) */ getSelectors(): Id> /** * Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state) */ getSelectors( selectState: (rootState: RootState) => State | undefined, ): Id> /** * Selectors that assume the slice's state is `rootState[slice.name]` (which is usually the case) * * Equivalent to `slice.getSelectors((state: RootState) => state[slice.name])`. */ get selectors(): Id< SliceDefinedSelectors< State, Selectors, { [K in ReducerPath]?: State | undefined } > > /** * Select the slice state, using the slice's current reducerPath. * * Returns initial state if slice is not found. */ selectSlice(state: { [K in ReducerPath]?: State | undefined }): State } /** * Options for `createSlice()`. * * @public */ export interface CreateSliceOptions< State = any, CR extends SliceCaseReducers = SliceCaseReducers, Name extends string = string, ReducerPath extends string = Name, Selectors extends SliceSelectors = SliceSelectors, > { /** * The slice's name. Used to namespace the generated action types. */ name: Name /** * The slice's reducer path. Used when injecting into a combined slice reducer. */ reducerPath?: ReducerPath /** * The initial state that should be used when the reducer is called the first time. This may also be a "lazy initializer" function, which should return an initial state value when called. This will be used whenever the reducer is called with `undefined` as its state value, and is primarily useful for cases like reading initial state from `localStorage`. */ initialState: State | (() => State) /** * A mapping from action types to action-type-specific *case reducer* * functions. For every action type, a matching action creator will be * generated using `createAction()`. */ reducers: | ValidateSliceCaseReducers | ((creators: ReducerCreators) => CR) /** * A callback that receives a *builder* object to define * case reducers via calls to `builder.addCase(actionCreatorOrType, reducer)`. * * * @example ```ts import { createAction, createSlice, Action } from '@reduxjs/toolkit' const incrementBy = createAction('incrementBy') const decrement = createAction('decrement') interface RejectedAction extends Action { error: Error } function isRejectedAction(action: Action): action is RejectedAction { return action.type.endsWith('rejected') } createSlice({ name: 'counter', initialState: 0, reducers: {}, extraReducers: builder => { builder .addCase(incrementBy, (state, action) => { // action is inferred correctly here if using TS }) // You can chain calls, or have separate `builder.addCase()` lines each time .addCase(decrement, (state, action) => {}) // You can match a range of action types .addMatcher( isRejectedAction, // `action` will be inferred as a RejectedAction due to isRejectedAction being defined as a type guard (state, action) => {} ) // and provide a default case if no other handlers matched .addDefaultCase((state, action) => {}) } }) ``` */ extraReducers?: (builder: ActionReducerMapBuilder) => void /** * A map of selectors that receive the slice's state and any additional arguments, and return a result. */ selectors?: Selectors } export enum ReducerType { reducer = 'reducer', reducerWithPrepare = 'reducerWithPrepare', asyncThunk = 'asyncThunk', } interface ReducerDefinition { _reducerDefinitionType: T } export interface CaseReducerDefinition< S = any, A extends Action = UnknownAction, > extends CaseReducer, ReducerDefinition {} /** * A CaseReducer with a `prepare` method. * * @public */ export type CaseReducerWithPrepare = { reducer: CaseReducer prepare: PrepareAction } export interface CaseReducerWithPrepareDefinition< State, Action extends PayloadAction, > extends CaseReducerWithPrepare, ReducerDefinition {} export interface AsyncThunkSliceReducerConfig< State, ThunkArg extends any, Returned = unknown, ThunkApiConfig extends AsyncThunkConfig = {}, > { pending?: CaseReducer< State, ReturnType['pending']> > rejected?: CaseReducer< State, ReturnType['rejected']> > fulfilled?: CaseReducer< State, ReturnType['fulfilled']> > settled?: CaseReducer< State, ReturnType< AsyncThunk['rejected' | 'fulfilled'] > > options?: AsyncThunkOptions } export interface AsyncThunkSliceReducerDefinition< State, ThunkArg extends any, Returned = unknown, ThunkApiConfig extends AsyncThunkConfig = {}, > extends AsyncThunkSliceReducerConfig< State, ThunkArg, Returned, ThunkApiConfig >, ReducerDefinition { payloadCreator: AsyncThunkPayloadCreator } /** * Providing these as part of the config would cause circular types, so we disallow passing them */ type PreventCircular = { [K in keyof ThunkApiConfig]: K extends 'state' | 'dispatch' ? never : ThunkApiConfig[K] } interface AsyncThunkCreator< State, CurriedThunkApiConfig extends PreventCircular = PreventCircular, > { ( payloadCreator: AsyncThunkPayloadCreator< Returned, ThunkArg, CurriedThunkApiConfig >, config?: AsyncThunkSliceReducerConfig< State, ThunkArg, Returned, CurriedThunkApiConfig >, ): AsyncThunkSliceReducerDefinition< State, ThunkArg, Returned, CurriedThunkApiConfig > < Returned, ThunkArg, ThunkApiConfig extends PreventCircular = {}, >( payloadCreator: AsyncThunkPayloadCreator< Returned, ThunkArg, ThunkApiConfig >, config?: AsyncThunkSliceReducerConfig< State, ThunkArg, Returned, ThunkApiConfig >, ): AsyncThunkSliceReducerDefinition withTypes< ThunkApiConfig extends PreventCircular, >(): AsyncThunkCreator< State, OverrideThunkApiConfigs > } export interface ReducerCreators { reducer( caseReducer: CaseReducer, ): CaseReducerDefinition reducer( caseReducer: CaseReducer>, ): CaseReducerDefinition> asyncThunk: AsyncThunkCreator preparedReducer>( prepare: Prepare, reducer: CaseReducer< State, ReturnType<_ActionCreatorWithPreparedPayload> >, ): { _reducerDefinitionType: ReducerType.reducerWithPrepare prepare: Prepare reducer: CaseReducer< State, ReturnType<_ActionCreatorWithPreparedPayload> > } } /** * The type describing a slice's `reducers` option. * * @public */ export type SliceCaseReducers = | Record | Record< string, | CaseReducer> | CaseReducerWithPrepare> > /** * The type describing a slice's `selectors` option. */ export type SliceSelectors = { [K: string]: (sliceState: State, ...args: any[]) => any } type SliceActionType< SliceName extends string, ActionName extends keyof any, > = ActionName extends string | number ? `${SliceName}/${ActionName}` : string /** * Derives the slice's `actions` property from the `reducers` options * * @public */ export type CaseReducerActions< CaseReducers extends SliceCaseReducers, SliceName extends string, > = { [Type in keyof CaseReducers]: CaseReducers[Type] extends infer Definition ? Definition extends { prepare: any } ? ActionCreatorForCaseReducerWithPrepare< Definition, SliceActionType > : Definition extends AsyncThunkSliceReducerDefinition< any, infer ThunkArg, infer Returned, infer ThunkApiConfig > ? AsyncThunk : Definition extends { reducer: any } ? ActionCreatorForCaseReducer< Definition['reducer'], SliceActionType > : ActionCreatorForCaseReducer< Definition, SliceActionType > : never } /** * Get a `PayloadActionCreator` type for a passed `CaseReducerWithPrepare` * * @internal */ type ActionCreatorForCaseReducerWithPrepare< CR extends { prepare: any }, Type extends string, > = _ActionCreatorWithPreparedPayload /** * Get a `PayloadActionCreator` type for a passed `CaseReducer` * * @internal */ type ActionCreatorForCaseReducer = CR extends ( state: any, action: infer Action, ) => any ? Action extends { payload: infer P } ? PayloadActionCreator : ActionCreatorWithoutPayload : ActionCreatorWithoutPayload /** * Extracts the CaseReducers out of a `reducers` object, even if they are * tested into a `CaseReducerWithPrepare`. * * @internal */ type SliceDefinedCaseReducers> = { [Type in keyof CaseReducers]: CaseReducers[Type] extends infer Definition ? Definition extends AsyncThunkSliceReducerDefinition ? Id< Pick< Required, 'fulfilled' | 'rejected' | 'pending' | 'settled' > > : Definition extends { reducer: infer Reducer } ? Reducer : Definition : never } type RemappedSelector = S extends Selector ? Selector & { unwrapped: S } : never /** * Extracts the final selector type from the `selectors` object. * * Removes the `string` index signature from the default value. */ type SliceDefinedSelectors< State, Selectors extends SliceSelectors, RootState, > = { [K in keyof Selectors as string extends K ? never : K]: RemappedSelector< Selectors[K], RootState > } /** * Used on a SliceCaseReducers object. * Ensures that if a CaseReducer is a `CaseReducerWithPrepare`, that * the `reducer` and the `prepare` function use the same type of `payload`. * * Might do additional such checks in the future. * * This type is only ever useful if you want to write your own wrapper around * `createSlice`. Please don't use it otherwise! * * @public */ export type ValidateSliceCaseReducers< S, ACR extends SliceCaseReducers, > = ACR & { [T in keyof ACR]: ACR[T] extends { reducer(s: S, action?: infer A): any } ? { prepare(...a: never[]): Omit } : {} } function getType(slice: string, actionKey: string): string { return `${slice}/${actionKey}` } interface BuildCreateSliceConfig { creators?: { asyncThunk?: typeof asyncThunkCreator } } export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) { const cAT = creators?.asyncThunk?.[asyncThunkSymbol] return function createSlice< State, CaseReducers extends SliceCaseReducers, Name extends string, Selectors extends SliceSelectors, ReducerPath extends string = Name, >( options: CreateSliceOptions< State, CaseReducers, Name, ReducerPath, Selectors >, ): Slice { const { name, reducerPath = name as unknown as ReducerPath } = options if (!name) { throw new Error('`name` is a required option for createSlice') } if ( typeof process !== 'undefined' && process.env.NODE_ENV === 'development' ) { if (options.initialState === undefined) { console.error( 'You must provide an `initialState` value that is not `undefined`. You may have misspelled `initialState`', ) } } const reducers = (typeof options.reducers === 'function' ? options.reducers(buildReducerCreators()) : options.reducers) || {} const reducerNames = Object.keys(reducers) const context: ReducerHandlingContext = { sliceCaseReducersByName: {}, sliceCaseReducersByType: {}, actionCreators: {}, sliceMatchers: [], } const contextMethods: ReducerHandlingContextMethods = { addCase( typeOrActionCreator: string | TypedActionCreator, reducer: CaseReducer, ) { const type = typeof typeOrActionCreator === 'string' ? typeOrActionCreator : typeOrActionCreator.type if (!type) { throw new Error( '`context.addCase` cannot be called with an empty action type', ) } if (type in context.sliceCaseReducersByType) { throw new Error( '`context.addCase` cannot be called with two reducers for the same action type: ' + type, ) } context.sliceCaseReducersByType[type] = reducer return contextMethods }, addMatcher(matcher, reducer) { context.sliceMatchers.push({ matcher, reducer }) return contextMethods }, exposeAction(name, actionCreator) { context.actionCreators[name] = actionCreator return contextMethods }, exposeCaseReducer(name, reducer) { context.sliceCaseReducersByName[name] = reducer return contextMethods }, } reducerNames.forEach((reducerName) => { const reducerDefinition = reducers[reducerName] const reducerDetails: ReducerDetails = { reducerName, type: getType(name, reducerName), createNotation: typeof options.reducers === 'function', } if (isAsyncThunkSliceReducerDefinition(reducerDefinition)) { handleThunkCaseReducerDefinition( reducerDetails, reducerDefinition, contextMethods, cAT, ) } else { handleNormalReducerDefinition( reducerDetails, reducerDefinition as any, contextMethods, ) } }) function buildReducer() { if (process.env.NODE_ENV !== 'production') { if (typeof options.extraReducers === 'object') { throw new Error( "The object notation for `createSlice.extraReducers` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createSlice", ) } } const [ extraReducers = {}, actionMatchers = [], defaultCaseReducer = undefined, ] = typeof options.extraReducers === 'function' ? executeReducerBuilderCallback(options.extraReducers) : [options.extraReducers] const finalCaseReducers = { ...extraReducers, ...context.sliceCaseReducersByType, } return createReducer(options.initialState, (builder) => { for (let key in finalCaseReducers) { builder.addCase(key, finalCaseReducers[key] as CaseReducer) } for (let sM of context.sliceMatchers) { builder.addMatcher(sM.matcher, sM.reducer) } for (let m of actionMatchers) { builder.addMatcher(m.matcher, m.reducer) } if (defaultCaseReducer) { builder.addDefaultCase(defaultCaseReducer) } }) } const selectSelf = (state: State) => state const injectedSelectorCache = new Map< boolean, WeakMap< (rootState: any) => State | undefined, Record any> > >() let _reducer: ReducerWithInitialState function reducer(state: State | undefined, action: UnknownAction) { if (!_reducer) _reducer = buildReducer() return _reducer(state, action) } function getInitialState() { if (!_reducer) _reducer = buildReducer() return _reducer.getInitialState() } function makeSelectorProps( reducerPath: CurrentReducerPath, injected = false, ): Pick< Slice, 'getSelectors' | 'selectors' | 'selectSlice' | 'reducerPath' > { function selectSlice(state: { [K in CurrentReducerPath]: State }) { let sliceState = state[reducerPath] if (typeof sliceState === 'undefined') { if (injected) { sliceState = getInitialState() } else if (process.env.NODE_ENV !== 'production') { throw new Error( 'selectSlice returned undefined for an uninjected slice reducer', ) } } return sliceState } function getSelectors( selectState: (rootState: any) => State = selectSelf, ) { const selectorCache = emplace(injectedSelectorCache, injected, { insert: () => new WeakMap(), }) return emplace(selectorCache, selectState, { insert: () => { const map: Record> = {} for (const [name, selector] of Object.entries( options.selectors ?? {}, )) { map[name] = wrapSelector( selector, selectState, getInitialState, injected, ) } return map }, }) as any } return { reducerPath, getSelectors, get selectors() { return getSelectors(selectSlice) }, selectSlice, } } const slice: Slice = { name, reducer, actions: context.actionCreators as any, caseReducers: context.sliceCaseReducersByName as any, getInitialState, ...makeSelectorProps(reducerPath), injectInto(injectable, { reducerPath: pathOpt, ...config } = {}) { const newReducerPath = pathOpt ?? reducerPath injectable.inject({ reducerPath: newReducerPath, reducer }, config) return { ...slice, ...makeSelectorProps(newReducerPath, true), } as any }, } return slice } } function wrapSelector>( selector: S, selectState: Selector, getInitialState: () => State, injected?: boolean, ) { function wrapper(rootState: NewState, ...args: any[]) { let sliceState = selectState(rootState) if (typeof sliceState === 'undefined') { if (injected) { sliceState = getInitialState() } else if (process.env.NODE_ENV !== 'production') { throw new Error( 'selectState returned undefined for an uninjected slice reducer', ) } } return selector(sliceState, ...args) } wrapper.unwrapped = selector return wrapper as RemappedSelector } /** * A function that accepts an initial state, an object full of reducer * functions, and a "slice name", and automatically generates * action creators and action types that correspond to the * reducers and state. * * @public */ export const createSlice = /* @__PURE__ */ buildCreateSlice() interface ReducerHandlingContext { sliceCaseReducersByName: Record< string, | CaseReducer | Pick< AsyncThunkSliceReducerDefinition, 'fulfilled' | 'rejected' | 'pending' | 'settled' > > sliceCaseReducersByType: Record> sliceMatchers: ActionMatcherDescriptionCollection actionCreators: Record } interface ReducerHandlingContextMethods { /** * Adds a case reducer to handle a single action type. * @param actionCreator - Either a plain action type string, or an action creator generated by [`createAction`](./createAction) that can be used to determine the action type. * @param reducer - The actual case reducer function. */ addCase>( actionCreator: ActionCreator, reducer: CaseReducer>, ): ReducerHandlingContextMethods /** * Adds a case reducer to handle a single action type. * @param actionCreator - Either a plain action type string, or an action creator generated by [`createAction`](./createAction) that can be used to determine the action type. * @param reducer - The actual case reducer function. */ addCase>( type: Type, reducer: CaseReducer, ): ReducerHandlingContextMethods /** * Allows you to match incoming actions against your own filter function instead of only the `action.type` property. * @remarks * If multiple matcher reducers match, all of them will be executed in the order * they were defined in - even if a case reducer already matched. * All calls to `builder.addMatcher` must come after any calls to `builder.addCase` and before any calls to `builder.addDefaultCase`. * @param matcher - A matcher function. In TypeScript, this should be a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) * function * @param reducer - The actual case reducer function. * */ addMatcher( matcher: TypeGuard, reducer: CaseReducer, ): ReducerHandlingContextMethods /** * Add an action to be exposed under the final `slice.actions` key. * @param name The key to be exposed as. * @param actionCreator The action to expose. * @example * context.exposeAction("addPost", createAction("addPost")); * * export const { addPost } = slice.actions * * dispatch(addPost(post)) */ exposeAction( name: string, actionCreator: Function, ): ReducerHandlingContextMethods /** * Add a case reducer to be exposed under the final `slice.caseReducers` key. * @param name The key to be exposed as. * @param reducer The reducer to expose. * @example * context.exposeCaseReducer("addPost", (state, action: PayloadAction) => { * state.push(action.payload) * }) * * slice.caseReducers.addPost([], addPost(post)) */ exposeCaseReducer( name: string, reducer: | CaseReducer | Pick< AsyncThunkSliceReducerDefinition, 'fulfilled' | 'rejected' | 'pending' | 'settled' >, ): ReducerHandlingContextMethods } interface ReducerDetails { /** The key the reducer was defined under */ reducerName: string /** The predefined action type, i.e. `${slice.name}/${reducerName}` */ type: string /** Whether create. notation was used when defining reducers */ createNotation: boolean } function buildReducerCreators(): ReducerCreators { function asyncThunk( payloadCreator: AsyncThunkPayloadCreator, config: AsyncThunkSliceReducerConfig, ): AsyncThunkSliceReducerDefinition { return { _reducerDefinitionType: ReducerType.asyncThunk, payloadCreator, ...config, } } asyncThunk.withTypes = () => asyncThunk return { reducer(caseReducer: CaseReducer) { return Object.assign( { // hack so the wrapping function has the same name as the original // we need to create a wrapper so the `reducerDefinitionType` is not assigned to the original [caseReducer.name](...args: Parameters) { return caseReducer(...args) }, }[caseReducer.name], { _reducerDefinitionType: ReducerType.reducer, } as const, ) }, preparedReducer(prepare, reducer) { return { _reducerDefinitionType: ReducerType.reducerWithPrepare, prepare, reducer, } }, asyncThunk: asyncThunk as any, } } function handleNormalReducerDefinition( { type, reducerName, createNotation }: ReducerDetails, maybeReducerWithPrepare: | CaseReducer | CaseReducerWithPrepare>, context: ReducerHandlingContextMethods, ) { let caseReducer: CaseReducer let prepareCallback: PrepareAction | undefined if ('reducer' in maybeReducerWithPrepare) { if ( createNotation && !isCaseReducerWithPrepareDefinition(maybeReducerWithPrepare) ) { throw new Error( 'Please use the `create.preparedReducer` notation for prepared action creators with the `create` notation.', ) } caseReducer = maybeReducerWithPrepare.reducer prepareCallback = maybeReducerWithPrepare.prepare } else { caseReducer = maybeReducerWithPrepare } context .addCase(type, caseReducer) .exposeCaseReducer(reducerName, caseReducer) .exposeAction( reducerName, prepareCallback ? createAction(type, prepareCallback) : createAction(type), ) } function isAsyncThunkSliceReducerDefinition( reducerDefinition: any, ): reducerDefinition is AsyncThunkSliceReducerDefinition { return reducerDefinition._reducerDefinitionType === ReducerType.asyncThunk } function isCaseReducerWithPrepareDefinition( reducerDefinition: any, ): reducerDefinition is CaseReducerWithPrepareDefinition { return ( reducerDefinition._reducerDefinitionType === ReducerType.reducerWithPrepare ) } function handleThunkCaseReducerDefinition( { type, reducerName }: ReducerDetails, reducerDefinition: AsyncThunkSliceReducerDefinition, context: ReducerHandlingContextMethods, cAT: typeof _createAsyncThunk | undefined, ) { if (!cAT) { throw new Error( 'Cannot use `create.asyncThunk` in the built-in `createSlice`. ' + 'Use `buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })` to create a customised version of `createSlice`.', ) } const { payloadCreator, fulfilled, pending, rejected, settled, options } = reducerDefinition const thunk = cAT(type, payloadCreator, options as any) context.exposeAction(reducerName, thunk) if (fulfilled) { context.addCase(thunk.fulfilled, fulfilled) } if (pending) { context.addCase(thunk.pending, pending) } if (rejected) { context.addCase(thunk.rejected, rejected) } if (settled) { context.addMatcher(thunk.settled, settled) } context.exposeCaseReducer(reducerName, { fulfilled: fulfilled || noop, pending: pending || noop, rejected: rejected || noop, settled: settled || noop, }) } function noop() {}