next-safe-action-query
Version:
Type-safe server actions with built-in validation for Next.js
8 lines (7 loc) • 6.43 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/index.ts"],
"sourcesContent": ["\"use client\";\n\nimport {\n\ttype QueryKey,\n\ttype UseQueryOptions,\n\tuseQuery,\n} from \"@tanstack/react-query\";\nimport type {\n\tInferSafeActionFnInput,\n\tInferSafeActionFnResult,\n\tSafeActionFn,\n} from \"next-safe-action\";\nimport type { HookSafeActionFn } from \"next-safe-action/hooks\";\n\n// Custom error class for safe action errors\nexport class SafeActionError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly type: \"server\" | \"validation\" | \"network\",\n\t\tpublic readonly details?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"SafeActionError\";\n\t}\n}\n\n// Options for the useSafeActionQuery hook\nexport interface UseSafeActionQueryOptions<TData, TInput>\n\textends Omit<\n\t\tUseQueryOptions<TData, SafeActionError>,\n\t\t\"queryKey\" | \"queryFn\"\n\t> {\n\tactionInput: TInput;\n\tonServerError?: (error: Error) => void;\n\tonValidationErrors?: (errors: string[]) => void;\n\tonNetworkError?: (error: Error) => void;\n\n\t// This is for compatibility with the useQuery hook\n\tonSuccess?: (data: TData) => void;\n\tonError?: (error: Error) => void;\n}\n\nexport type SafeActionQueryOptionsWithOutInput<\n\t// biome-ignore lint/suspicious/noExplicitAny: next-safe-action compatibility\n\tTAction extends SafeActionFn<any, any, any, any, any>,\n> = Omit<\n\tUseSafeActionQueryOptions<\n\t\tInferSafeActionFnResult<TAction>[\"data\"],\n\t\tInferSafeActionFnInput<TAction>[\"clientInput\"]\n\t>,\n\t\"actionInput\"\n>;\n\n/**\n * Generic hook for using Next Safe Actions with TanStack Query\n * Properly infers types from the safe action function\n * Supports actions with or without bind arguments\n */\nexport function useSafeActionQuery<\n\t// biome-ignore lint/suspicious/noExplicitAny: next-safe-action compatibility\n\tTAction extends HookSafeActionFn<any, any, any, TData>,\n\tTData = InferSafeActionFnResult<TAction>[\"data\"],\n\tTInput = InferSafeActionFnInput<TAction>[\"clientInput\"],\n>(\n\tqueryKey: QueryKey,\n\taction: TAction,\n\toptions: UseSafeActionQueryOptions<TData, TInput>,\n): SafeActionQueryResult<TData> {\n\tconst {\n\t\tactionInput,\n\t\tonServerError,\n\t\tonValidationErrors,\n\t\tonNetworkError,\n\t\tonSuccess,\n\t\tonError,\n\t\t...queryOptions\n\t} = options;\n\n\treturn useQuery<TData, SafeActionError>({\n\t\tqueryKey,\n\t\tqueryFn: async (): Promise<TData> => {\n\t\t\ttry {\n\t\t\t\tconst result = await action(actionInput);\n\n\t\t\t\t// Handle server errors\n\t\t\t\tif (result?.serverError) {\n\t\t\t\t\tonServerError?.(result.serverError);\n\t\t\t\t\tonError?.(new Error(result.serverError));\n\t\t\t\t\tthrow new SafeActionError(result.serverError, \"server\");\n\t\t\t\t}\n\n\t\t\t\t// Handle validation errors\n\t\t\t\tif (result?.validationErrors) {\n\t\t\t\t\tconst errors = result.validationErrors._errors ?? [];\n\t\t\t\t\tonValidationErrors?.(errors);\n\t\t\t\t\tonError?.(new Error(errors.join(\", \") || \"Validation failed\"));\n\t\t\t\t\tthrow new SafeActionError(\n\t\t\t\t\t\terrors.join(\", \") || \"Validation failed\",\n\t\t\t\t\t\t\"validation\",\n\t\t\t\t\t\tresult.validationErrors,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (result?.data === undefined || result?.data === null) {\n\t\t\t\t\tthrow new SafeActionError(\"No data returned from action\", \"server\");\n\t\t\t\t}\n\n\t\t\t\tonSuccess?.(result.data);\n\n\t\t\t\treturn result.data as TData;\n\t\t\t} catch (error) {\n\t\t\t\t// Handle network/unexpected errors\n\t\t\t\tif (error instanceof SafeActionError) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\n\t\t\t\tconst networkError =\n\t\t\t\t\terror instanceof Error ? error : new Error(\"Unknown error occurred\");\n\t\t\t\tonNetworkError?.(networkError);\n\t\t\t\tonError?.(networkError);\n\t\t\t\tthrow new SafeActionError(\n\t\t\t\t\t`Network error: ${networkError.message}`,\n\t\t\t\t\t\"network\",\n\t\t\t\t\tnetworkError,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t\tretry: (failureCount: number, error: Error) => {\n\t\t\t// Don't retry validation errors\n\t\t\tif (error instanceof SafeActionError && error.type === \"validation\") {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t// Don't retry server errors (they're likely permanent)\n\t\t\tif (error instanceof SafeActionError && error.type === \"server\") {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t// Retry network errors up to 3 times\n\t\t\treturn failureCount < 3;\n\t\t},\n\t\t// Set reasonable defaults\n\t\tstaleTime: 30 * 1000, // 30 seconds default\n\t\tthrowOnError: false, // Return errors as state by default\n\t\t...queryOptions,\n\t});\n}\n\n// Helper type for inferring the hook return type\nexport type SafeActionQueryResult<TData> = ReturnType<\n\ttypeof useQuery<TData, SafeActionError>\n>;\n\nexport type { QueryKey } from \"@tanstack/react-query\";\nexport type {\n\tInferSafeActionFnInput,\n\tInferSafeActionFnResult,\n\tSafeActionFn,\n} from \"next-safe-action\";\nexport type { HookSafeActionFn } from \"next-safe-action/hooks\";\n"],
"mappings": ";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,yBAIO;AASA,MAAM,wBAAwB,MAAM;AAAA,EAC1C,YACC,SACgB,MACA,SACf;AACD,UAAM,OAAO;AAHG;AACA;AAGhB,SAAK,OAAO;AAAA,EACb;AACD;AAkCO,SAAS,mBAMf,UACA,QACA,SAC+B;AAC/B,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACJ,IAAI;AAEJ,aAAO,6BAAiC;AAAA,IACvC;AAAA,IACA,SAAS,YAA4B;AACpC,UAAI;AACH,cAAM,SAAS,MAAM,OAAO,WAAW;AAGvC,YAAI,QAAQ,aAAa;AACxB,0BAAgB,OAAO,WAAW;AAClC,oBAAU,IAAI,MAAM,OAAO,WAAW,CAAC;AACvC,gBAAM,IAAI,gBAAgB,OAAO,aAAa,QAAQ;AAAA,QACvD;AAGA,YAAI,QAAQ,kBAAkB;AAC7B,gBAAM,SAAS,OAAO,iBAAiB,WAAW,CAAC;AACnD,+BAAqB,MAAM;AAC3B,oBAAU,IAAI,MAAM,OAAO,KAAK,IAAI,KAAK,mBAAmB,CAAC;AAC7D,gBAAM,IAAI;AAAA,YACT,OAAO,KAAK,IAAI,KAAK;AAAA,YACrB;AAAA,YACA,OAAO;AAAA,UACR;AAAA,QACD;AAEA,YAAI,QAAQ,SAAS,UAAa,QAAQ,SAAS,MAAM;AACxD,gBAAM,IAAI,gBAAgB,gCAAgC,QAAQ;AAAA,QACnE;AAEA,oBAAY,OAAO,IAAI;AAEvB,eAAO,OAAO;AAAA,MACf,SAAS,OAAO;AAEf,YAAI,iBAAiB,iBAAiB;AACrC,gBAAM;AAAA,QACP;AAEA,cAAM,eACL,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,wBAAwB;AACpE,yBAAiB,YAAY;AAC7B,kBAAU,YAAY;AACtB,cAAM,IAAI;AAAA,UACT,kBAAkB,aAAa,OAAO;AAAA,UACtC;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,IACA,OAAO,CAAC,cAAsB,UAAiB;AAE9C,UAAI,iBAAiB,mBAAmB,MAAM,SAAS,cAAc;AACpE,eAAO;AAAA,MACR;AAEA,UAAI,iBAAiB,mBAAmB,MAAM,SAAS,UAAU;AAChE,eAAO;AAAA,MACR;AAEA,aAAO,eAAe;AAAA,IACvB;AAAA;AAAA,IAEA,WAAW,KAAK;AAAA;AAAA,IAChB,cAAc;AAAA;AAAA,IACd,GAAG;AAAA,EACJ,CAAC;AACF;",
"names": []
}