@jay-js/system
Version:
A powerful and flexible TypeScript library for UI, state management, lazy loading, routing and managing draggable elements in modern web applications.
205 lines • 7.18 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { state } from "../state/core/state.js";
import { queryCache } from "./cache.js";
import { defaultRetryDelay, executeWithRetry } from "./utils.js";
/**
* Default mutation options
*/
const DEFAULT_OPTIONS = {
retry: false,
retryDelay: defaultRetryDelay,
};
/**
* Creates a reactive mutation for write operations (POST/PUT/DELETE)
*
* Unlike queries, mutations don't cache results and are designed for write operations.
* They support optimistic updates with automatic rollback on error.
*
* @param fetcher Async function to perform mutation
* @param options Mutation configuration options
* @returns Mutation store with reactive states and control methods
*
* @example
* Basic usage:
* ```typescript
* const createUser = mutation(async (user: User, signal) => {
* const res = await fetch('/api/users', {
* method: 'POST',
* body: JSON.stringify(user),
* signal,
* });
* return res.json();
* });
*
* // Execute mutation
* await createUser.mutate({ name: 'John', email: 'john@example.com' });
* ```
*
* @example
* With optimistic updates:
* ```typescript
* const updateUser = mutation(
* async (user: User, signal) => {
* const res = await fetch(`/api/users/${user.id}`, {
* method: 'PUT',
* body: JSON.stringify(user),
* signal,
* });
* return res.json();
* },
* {
* onMutate: async (newUser) => {
* // Snapshot current state for rollback
* const previousUser = queryCache.get<User>(`user-${newUser.id}`);
*
* // Optimistically update cache
* if (previousUser) {
* queryCache.set(`user-${newUser.id}`, newUser, 300000);
* }
*
* return { previousUser };
* },
* onError: (err, variables, context) => {
* // Rollback on error
* if (context?.previousUser) {
* queryCache.set(`user-${variables.id}`, context.previousUser.data, 300000);
* }
* },
* onSuccess: () => {
* // Invalidate related queries
* queryCache.delete('users');
* },
* invalidateQueries: ['users', 'user-list'],
* }
* );
* ```
*/
export function mutation(fetcher, options = {}) {
const opts = Object.assign(Object.assign({}, DEFAULT_OPTIONS), options);
const internalState = state({
data: null,
error: null,
isLoading: false,
isError: false,
isSuccess: false,
isIdle: true,
status: "idle",
});
let currentController = null;
const mutate = (variables) => __awaiter(this, void 0, void 0, function* () {
if (currentController) {
currentController.abort();
}
currentController = new AbortController();
internalState.set((currentState) => {
return Object.assign(Object.assign({}, currentState), { isLoading: true, isIdle: false, isError: false, status: "loading" });
});
let context;
try {
if (options.onMutate) {
context = yield options.onMutate(variables);
}
const data = yield executeWithRetry((signal) => fetcher(variables, signal), opts.retry, opts.retryDelay, currentController.signal);
internalState.set((currentState) => {
return Object.assign(Object.assign({}, currentState), { data: data, error: null, isError: false, isSuccess: true, status: "success" });
});
if (options.onSuccess) {
yield options.onSuccess(data, variables, context);
}
if (options.invalidateQueries) {
for (const key of options.invalidateQueries) {
queryCache.delete(key);
}
}
if (options.invalidatePattern) {
queryCache.invalidatePattern(options.invalidatePattern);
}
if (options.invalidateIf) {
queryCache.invalidateQueries(options.invalidateIf);
}
if (options.onSettled) {
yield options.onSettled(data, null, variables, context);
}
return data;
}
catch (error) {
const err = error;
internalState.set((currentState) => {
return Object.assign(Object.assign({}, currentState), { error: err, isError: true, isSuccess: false, status: "error" });
});
if (options.onError) {
yield options.onError(err, variables, context);
}
if (options.onSettled) {
yield options.onSettled(undefined, err, variables, context);
}
throw err;
}
finally {
//internalState.value.isLoading = false;
internalState.set((currentState) => {
return Object.assign(Object.assign({}, currentState), { isLoading: false });
});
currentController = null;
}
});
const mutateAsync = (variables) => __awaiter(this, void 0, void 0, function* () {
try {
return yield mutate(variables);
}
catch (_a) {
return undefined;
}
});
const reset = () => {
internalState.set((currentState) => {
return Object.assign(Object.assign({}, currentState), { data: null, error: null, isLoading: false, isError: false, isSuccess: false, isIdle: true, status: "idle" });
});
};
const cancel = () => {
if (currentController) {
currentController.abort();
currentController = null;
}
//internalState.value.isLoading = false;
internalState.set((currentState) => {
return Object.assign(Object.assign({}, currentState), { isLoading: false });
});
};
return {
get data() {
return internalState.value.data;
},
get error() {
return internalState.value.error;
},
get isLoading() {
return internalState.value.isLoading;
},
get isError() {
return internalState.value.isError;
},
get isSuccess() {
return internalState.value.isSuccess;
},
get isIdle() {
return internalState.value.isIdle;
},
get status() {
return internalState.value.status;
},
mutate,
mutateAsync,
reset,
cancel,
};
}
//# sourceMappingURL=mutation.js.map