@tanstack/query-core
Version:
The framework agnostic core that powers TanStack Query
235 lines • 6.23 kB
JavaScript
// src/mutation.ts
import { notifyManager } from "./notifyManager.js";
import { Removable } from "./removable.js";
import { canFetch, createRetryer } from "./retryer.js";
var Mutation = class extends Removable {
constructor(config) {
super();
this.mutationId = config.mutationId;
this.#defaultOptions = config.defaultOptions;
this.#mutationCache = config.mutationCache;
this.#observers = [];
this.state = config.state || getDefaultState();
this.setOptions(config.options);
this.scheduleGc();
}
#observers;
#defaultOptions;
#mutationCache;
#retryer;
setOptions(options) {
this.options = { ...this.#defaultOptions, ...options };
this.updateGcTime(this.options.gcTime);
}
get meta() {
return this.options.meta;
}
addObserver(observer) {
if (!this.#observers.includes(observer)) {
this.#observers.push(observer);
this.clearGcTimeout();
this.#mutationCache.notify({
type: "observerAdded",
mutation: this,
observer
});
}
}
removeObserver(observer) {
this.#observers = this.#observers.filter((x) => x !== observer);
this.scheduleGc();
this.#mutationCache.notify({
type: "observerRemoved",
mutation: this,
observer
});
}
optionalRemove() {
if (!this.#observers.length) {
if (this.state.status === "pending") {
this.scheduleGc();
} else {
this.#mutationCache.remove(this);
}
}
}
continue() {
return this.#retryer?.continue() ?? // continuing a mutation assumes that variables are set, mutation must have been dehydrated before
this.execute(this.state.variables);
}
async execute(variables) {
const executeMutation = () => {
this.#retryer = createRetryer({
fn: () => {
if (!this.options.mutationFn) {
return Promise.reject(new Error("No mutationFn found"));
}
return this.options.mutationFn(variables);
},
onFail: (failureCount, error) => {
this.#dispatch({ type: "failed", failureCount, error });
},
onPause: () => {
this.#dispatch({ type: "pause" });
},
onContinue: () => {
this.#dispatch({ type: "continue" });
},
retry: this.options.retry ?? 0,
retryDelay: this.options.retryDelay,
networkMode: this.options.networkMode
});
return this.#retryer.promise;
};
const restored = this.state.status === "pending";
try {
if (!restored) {
this.#dispatch({ type: "pending", variables });
await this.#mutationCache.config.onMutate?.(
variables,
this
);
const context = await this.options.onMutate?.(variables);
if (context !== this.state.context) {
this.#dispatch({
type: "pending",
context,
variables
});
}
}
const data = await executeMutation();
await this.#mutationCache.config.onSuccess?.(
data,
variables,
this.state.context,
this
);
await this.options.onSuccess?.(data, variables, this.state.context);
await this.#mutationCache.config.onSettled?.(
data,
null,
this.state.variables,
this.state.context,
this
);
await this.options.onSettled?.(data, null, variables, this.state.context);
this.#dispatch({ type: "success", data });
return data;
} catch (error) {
try {
await this.#mutationCache.config.onError?.(
error,
variables,
this.state.context,
this
);
await this.options.onError?.(
error,
variables,
this.state.context
);
await this.#mutationCache.config.onSettled?.(
void 0,
error,
this.state.variables,
this.state.context,
this
);
await this.options.onSettled?.(
void 0,
error,
variables,
this.state.context
);
throw error;
} finally {
this.#dispatch({ type: "error", error });
}
}
}
#dispatch(action) {
const reducer = (state) => {
switch (action.type) {
case "failed":
return {
...state,
failureCount: action.failureCount,
failureReason: action.error
};
case "pause":
return {
...state,
isPaused: true
};
case "continue":
return {
...state,
isPaused: false
};
case "pending":
return {
...state,
context: action.context,
data: void 0,
failureCount: 0,
failureReason: null,
error: null,
isPaused: !canFetch(this.options.networkMode),
status: "pending",
variables: action.variables,
submittedAt: Date.now()
};
case "success":
return {
...state,
data: action.data,
failureCount: 0,
failureReason: null,
error: null,
status: "success",
isPaused: false
};
case "error":
return {
...state,
data: void 0,
error: action.error,
failureCount: state.failureCount + 1,
failureReason: action.error,
isPaused: false,
status: "error"
};
}
};
this.state = reducer(this.state);
notifyManager.batch(() => {
this.#observers.forEach((observer) => {
observer.onMutationUpdate(action);
});
this.#mutationCache.notify({
mutation: this,
type: "updated",
action
});
});
}
};
function getDefaultState() {
return {
context: void 0,
data: void 0,
error: null,
failureCount: 0,
failureReason: null,
isPaused: false,
status: "idle",
variables: void 0,
submittedAt: 0
};
}
export {
Mutation,
getDefaultState
};
//# sourceMappingURL=mutation.js.map