wellcrafted 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +354 -727
- package/dist/error/index.d.ts +70 -2
- package/dist/error/index.d.ts.map +1 -1
- package/dist/error/index.js +51 -1
- package/dist/error/index.js.map +1 -1
- package/dist/index-DRbikk8-.d.ts +28 -0
- package/dist/index-DRbikk8-.d.ts.map +1 -0
- package/dist/query/index.d.ts +119 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/index.js +237 -0
- package/dist/query/index.js.map +1 -0
- package/dist/result/index.d.ts +3 -455
- package/dist/result/index.js +3 -360
- package/dist/result-BCv06xhR.js +30 -0
- package/dist/result-BCv06xhR.js.map +1 -0
- package/dist/result-DxmXBFi5.js +335 -0
- package/dist/result-DxmXBFi5.js.map +1 -0
- package/dist/result-t0dngyiE.d.ts +431 -0
- package/dist/result-t0dngyiE.d.ts.map +1 -0
- package/package.json +7 -2
- package/dist/result/index.d.ts.map +0 -1
- package/dist/result/index.js.map +0 -1
package/dist/error/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { Err } from "../result-t0dngyiE.js";
|
|
2
|
+
|
|
1
3
|
//#region src/error/types.d.ts
|
|
4
|
+
|
|
2
5
|
/**
|
|
3
6
|
* Base error structure for all errors in the Result system.
|
|
4
7
|
*
|
|
@@ -110,7 +113,72 @@ type TaggedError<T extends string> = BaseError & {
|
|
|
110
113
|
* ```
|
|
111
114
|
*/
|
|
112
115
|
declare function extractErrorMessage(error: unknown): string;
|
|
113
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Input type for creating a tagged error (everything except the name)
|
|
118
|
+
*/
|
|
119
|
+
type TaggedErrorWithoutName<T extends string> = Omit<TaggedError<T>, "name">;
|
|
120
|
+
/**
|
|
121
|
+
* Replaces the "Error" suffix with "Err" suffix in error type names.
|
|
122
|
+
*
|
|
123
|
+
* This utility type is used to create companion function names for error constructors
|
|
124
|
+
* that return Err-wrapped results, maintaining consistent naming conventions.
|
|
125
|
+
*
|
|
126
|
+
* @template T - An error type name that must end with "Error"
|
|
127
|
+
* @returns The type name with "Error" replaced by "Err"
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* type NetworkErr = ReplaceErrorWithErr<"NetworkError">; // "NetworkErr"
|
|
132
|
+
* type ValidationErr = ReplaceErrorWithErr<"ValidationError">; // "ValidationErr"
|
|
133
|
+
* type AuthErr = ReplaceErrorWithErr<"AuthError">; // "AuthErr"
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
type ReplaceErrorWithErr<T extends `${string}Error`> = T extends `${infer TBase}Error` ? `${TBase}Err` : never;
|
|
137
|
+
/**
|
|
138
|
+
* Return type for createTaggedError - contains both factory functions.
|
|
139
|
+
*
|
|
140
|
+
* Provides two factory functions:
|
|
141
|
+
* - One that creates plain TaggedError objects (named with "Error" suffix)
|
|
142
|
+
* - One that creates Err-wrapped TaggedError objects (named with "Err" suffix)
|
|
143
|
+
*/
|
|
144
|
+
type TaggedErrorFactories<TErrorName extends `${string}Error`> = { [K in TErrorName]: (input: TaggedErrorWithoutName<K>) => TaggedError<K> } & { [K in ReplaceErrorWithErr<TErrorName>]: (input: TaggedErrorWithoutName<TErrorName>) => Err<TaggedError<TErrorName>> };
|
|
145
|
+
/**
|
|
146
|
+
* Returns two different factory functions for tagged errors.
|
|
147
|
+
*
|
|
148
|
+
* Given an error name like "NetworkError", this returns:
|
|
149
|
+
* - `NetworkError`: Creates a plain TaggedError object
|
|
150
|
+
* - `NetworkErr`: Creates a TaggedError object wrapped in an Err result
|
|
151
|
+
*
|
|
152
|
+
* The naming pattern automatically replaces the "Error" suffix with "Err" suffix
|
|
153
|
+
* for the Result-wrapped version.
|
|
154
|
+
*
|
|
155
|
+
* @param name - The name of the error type (must end with "Error")
|
|
156
|
+
* @returns An object with two factory functions:
|
|
157
|
+
* - [name]: Function that creates plain TaggedError objects
|
|
158
|
+
* - [name with "Error" replaced by "Err"]: Function that creates Err-wrapped TaggedError objects
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* const { NetworkError, NetworkErr } = createTaggedError('NetworkError');
|
|
163
|
+
*
|
|
164
|
+
* // NetworkError: Creates just the error object
|
|
165
|
+
* const error = NetworkError({
|
|
166
|
+
* message: 'Connection failed',
|
|
167
|
+
* context: { url: 'https://api.example.com' },
|
|
168
|
+
* cause: undefined
|
|
169
|
+
* });
|
|
170
|
+
* // Returns: { name: 'NetworkError', message: 'Connection failed', ... }
|
|
171
|
+
*
|
|
172
|
+
* // NetworkErr: Creates error and wraps in Err result
|
|
173
|
+
* return NetworkErr({
|
|
174
|
+
* message: 'Connection failed',
|
|
175
|
+
* context: { url: 'https://api.example.com' },
|
|
176
|
+
* cause: undefined
|
|
177
|
+
* });
|
|
178
|
+
* // Returns: Err({ name: 'NetworkError', message: 'Connection failed', ... })
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
declare function createTaggedError<TErrorName extends `${string}Error`>(name: TErrorName): TaggedErrorFactories<TErrorName>;
|
|
114
182
|
//#endregion
|
|
115
|
-
export { BaseError, TaggedError, extractErrorMessage };
|
|
183
|
+
export { BaseError, TaggedError, createTaggedError, extractErrorMessage };
|
|
116
184
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/error/types.ts","../../src/error/utils.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/error/types.ts","../../src/error/utils.ts"],"sourcesContent":[],"mappings":";;;;;;;AAgBA;;;;AAAgC;AAsDhC;;;;AACiB;;;;ACjCD,KDtBJ,SAAA,GAAY,QCsBW,CAAA;EAoC9B,IAAA,EAAA,MAAA;EAAsB,OAAA,EAAA,MAAA;EAAA,OAAsC,CAAA,EDvDtD,MCuDsD,CAAA,MAAA,EAAA,OAAA,CAAA;EAAC,KAAb,EAAA,OAAA;CAAW,CAAA;AAAZ;AAAA;;;;AAmBT;AAAA;;;;;;;;;;;;;;AAclC;AAuCT;;;;;AAEuB;;;;;;;;;;;;;;;;;;;;;KD9EX,gCAAgC;iBAC5B;;;;;;AAvDhB;;;;AAAgC;AAsDhC;;;;AACiB;;;;ACjCjB;AA+BC;;;;;AAKmD;AAAA;;;;AAmBT;AAAA;;;;;;;;;AAaX,iBApEhB,mBAAA,CAoEgB,KAAA,EAAA,OAAA,CAAA,EAAA,MAAA;;;;KAhC3B,sBAiCC,CAAA,UAAA,MAAA,CAAA,GAjC0C,IAiC1C,CAjC+C,WAiC/C,CAjC2D,CAiC3D,CAAA,EAAA,MAAA,CAAA;AAAG;AAuCT;;;;;AAEuB;;;;;;;;;;KAxDlB,kDACJ,qCAAqC;;;;;;;;KASjC,oEACE,qBAAqB,uBAAuB,OAAO,YAAY,eAE/D,oBAAoB,sBAClB,uBAAuB,gBAC1B,IAAI,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuCN,6DACT,aACJ,qBAAqB"}
|
package/dist/error/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Err } from "../result-DxmXBFi5.js";
|
|
2
|
+
|
|
1
3
|
//#region src/error/utils.ts
|
|
2
4
|
/**
|
|
3
5
|
* Extracts a readable error message from an unknown error value
|
|
@@ -50,7 +52,55 @@ function extractErrorMessage(error) {
|
|
|
50
52
|
}
|
|
51
53
|
return String(error);
|
|
52
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Returns two different factory functions for tagged errors.
|
|
57
|
+
*
|
|
58
|
+
* Given an error name like "NetworkError", this returns:
|
|
59
|
+
* - `NetworkError`: Creates a plain TaggedError object
|
|
60
|
+
* - `NetworkErr`: Creates a TaggedError object wrapped in an Err result
|
|
61
|
+
*
|
|
62
|
+
* The naming pattern automatically replaces the "Error" suffix with "Err" suffix
|
|
63
|
+
* for the Result-wrapped version.
|
|
64
|
+
*
|
|
65
|
+
* @param name - The name of the error type (must end with "Error")
|
|
66
|
+
* @returns An object with two factory functions:
|
|
67
|
+
* - [name]: Function that creates plain TaggedError objects
|
|
68
|
+
* - [name with "Error" replaced by "Err"]: Function that creates Err-wrapped TaggedError objects
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const { NetworkError, NetworkErr } = createTaggedError('NetworkError');
|
|
73
|
+
*
|
|
74
|
+
* // NetworkError: Creates just the error object
|
|
75
|
+
* const error = NetworkError({
|
|
76
|
+
* message: 'Connection failed',
|
|
77
|
+
* context: { url: 'https://api.example.com' },
|
|
78
|
+
* cause: undefined
|
|
79
|
+
* });
|
|
80
|
+
* // Returns: { name: 'NetworkError', message: 'Connection failed', ... }
|
|
81
|
+
*
|
|
82
|
+
* // NetworkErr: Creates error and wraps in Err result
|
|
83
|
+
* return NetworkErr({
|
|
84
|
+
* message: 'Connection failed',
|
|
85
|
+
* context: { url: 'https://api.example.com' },
|
|
86
|
+
* cause: undefined
|
|
87
|
+
* });
|
|
88
|
+
* // Returns: Err({ name: 'NetworkError', message: 'Connection failed', ... })
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
function createTaggedError(name) {
|
|
92
|
+
const errorConstructor = (error) => ({
|
|
93
|
+
...error,
|
|
94
|
+
name
|
|
95
|
+
});
|
|
96
|
+
const errName = name.replace(/Error$/, "Err");
|
|
97
|
+
const errConstructor = (error) => Err(errorConstructor(error));
|
|
98
|
+
return {
|
|
99
|
+
[name]: errorConstructor,
|
|
100
|
+
[errName]: errConstructor
|
|
101
|
+
};
|
|
102
|
+
}
|
|
53
103
|
|
|
54
104
|
//#endregion
|
|
55
|
-
export { extractErrorMessage };
|
|
105
|
+
export { createTaggedError, extractErrorMessage };
|
|
56
106
|
//# sourceMappingURL=index.js.map
|
package/dist/error/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["error: unknown"],"sources":["../../src/error/utils.ts"],"sourcesContent":["/**\n * Extracts a readable error message from an unknown error value\n *\n * This utility is commonly used in mapError functions when converting\n * unknown errors to typed error objects in the Result system.\n *\n * @param error - The unknown error to extract a message from\n * @returns A string representation of the error\n *\n * @example\n * ```ts\n * // With native Error\n * const error = new Error(\"Something went wrong\");\n * const message = extractErrorMessage(error); // \"Something went wrong\"\n *\n * // With string error\n * const stringError = \"String error\";\n * const message2 = extractErrorMessage(stringError); // \"String error\"\n *\n * // With object error\n * const unknownError = { code: 500, details: \"Server error\" };\n * const message3 = extractErrorMessage(unknownError); // '{\"code\":500,\"details\":\"Server error\"}'\n *\n * // Used in mapError function\n * const result = await tryAsync({\n * try: () => riskyOperation(),\n * mapError: (error): NetworkError => ({\n * name: \"NetworkError\",\n * message: extractErrorMessage(error),\n * context: { operation: \"riskyOperation\" },\n * cause: error,\n * }),\n * });\n * ```\n */\nexport function extractErrorMessage(error: unknown): string {\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\n\tif (typeof error === \"string\") {\n\t\treturn error;\n\t}\n\n\tif (typeof error === \"object\" && error !== null) {\n\t\t// Check for common error properties\n\t\tconst errorObj = error as Record<string, unknown>;\n\t\tif (typeof errorObj.message === \"string\") {\n\t\t\treturn errorObj.message;\n\t\t}\n\t\tif (typeof errorObj.error === \"string\") {\n\t\t\treturn errorObj.error;\n\t\t}\n\t\tif (typeof errorObj.description === \"string\") {\n\t\t\treturn errorObj.description;\n\t\t}\n\n\t\t// Fallback to JSON stringification\n\t\ttry {\n\t\t\treturn JSON.stringify(error);\n\t\t} catch {\n\t\t\treturn String(error);\n\t\t}\n\t}\n\n\treturn String(error);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":["error: unknown","name: TErrorName","error: TaggedErrorWithoutName<TErrorName>"],"sources":["../../src/error/utils.ts"],"sourcesContent":["import type { TaggedError } from \"./types.js\";\nimport { Err } from \"../result/result.js\";\n\n/**\n * Extracts a readable error message from an unknown error value\n *\n * This utility is commonly used in mapError functions when converting\n * unknown errors to typed error objects in the Result system.\n *\n * @param error - The unknown error to extract a message from\n * @returns A string representation of the error\n *\n * @example\n * ```ts\n * // With native Error\n * const error = new Error(\"Something went wrong\");\n * const message = extractErrorMessage(error); // \"Something went wrong\"\n *\n * // With string error\n * const stringError = \"String error\";\n * const message2 = extractErrorMessage(stringError); // \"String error\"\n *\n * // With object error\n * const unknownError = { code: 500, details: \"Server error\" };\n * const message3 = extractErrorMessage(unknownError); // '{\"code\":500,\"details\":\"Server error\"}'\n *\n * // Used in mapError function\n * const result = await tryAsync({\n * try: () => riskyOperation(),\n * mapError: (error): NetworkError => ({\n * name: \"NetworkError\",\n * message: extractErrorMessage(error),\n * context: { operation: \"riskyOperation\" },\n * cause: error,\n * }),\n * });\n * ```\n */\nexport function extractErrorMessage(error: unknown): string {\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\n\tif (typeof error === \"string\") {\n\t\treturn error;\n\t}\n\n\tif (typeof error === \"object\" && error !== null) {\n\t\t// Check for common error properties\n\t\tconst errorObj = error as Record<string, unknown>;\n\t\tif (typeof errorObj.message === \"string\") {\n\t\t\treturn errorObj.message;\n\t\t}\n\t\tif (typeof errorObj.error === \"string\") {\n\t\t\treturn errorObj.error;\n\t\t}\n\t\tif (typeof errorObj.description === \"string\") {\n\t\t\treturn errorObj.description;\n\t\t}\n\n\t\t// Fallback to JSON stringification\n\t\ttry {\n\t\t\treturn JSON.stringify(error);\n\t\t} catch {\n\t\t\treturn String(error);\n\t\t}\n\t}\n\n\treturn String(error);\n}\n\n/**\n * Input type for creating a tagged error (everything except the name)\n */\ntype TaggedErrorWithoutName<T extends string> = Omit<TaggedError<T>, \"name\">;\n\n/**\n * Replaces the \"Error\" suffix with \"Err\" suffix in error type names.\n *\n * This utility type is used to create companion function names for error constructors\n * that return Err-wrapped results, maintaining consistent naming conventions.\n *\n * @template T - An error type name that must end with \"Error\"\n * @returns The type name with \"Error\" replaced by \"Err\"\n *\n * @example\n * ```ts\n * type NetworkErr = ReplaceErrorWithErr<\"NetworkError\">; // \"NetworkErr\"\n * type ValidationErr = ReplaceErrorWithErr<\"ValidationError\">; // \"ValidationErr\"\n * type AuthErr = ReplaceErrorWithErr<\"AuthError\">; // \"AuthErr\"\n * ```\n */\ntype ReplaceErrorWithErr<T extends `${string}Error`> =\n\tT extends `${infer TBase}Error` ? `${TBase}Err` : never;\n\n/**\n * Return type for createTaggedError - contains both factory functions.\n *\n * Provides two factory functions:\n * - One that creates plain TaggedError objects (named with \"Error\" suffix)\n * - One that creates Err-wrapped TaggedError objects (named with \"Err\" suffix)\n */\ntype TaggedErrorFactories<TErrorName extends `${string}Error`> = {\n\t[K in TErrorName]: (input: TaggedErrorWithoutName<K>) => TaggedError<K>;\n} & {\n\t[K in ReplaceErrorWithErr<TErrorName>]: (\n\t\tinput: TaggedErrorWithoutName<TErrorName>,\n\t) => Err<TaggedError<TErrorName>>;\n};\n\n/**\n * Returns two different factory functions for tagged errors.\n *\n * Given an error name like \"NetworkError\", this returns:\n * - `NetworkError`: Creates a plain TaggedError object\n * - `NetworkErr`: Creates a TaggedError object wrapped in an Err result\n *\n * The naming pattern automatically replaces the \"Error\" suffix with \"Err\" suffix\n * for the Result-wrapped version.\n *\n * @param name - The name of the error type (must end with \"Error\")\n * @returns An object with two factory functions:\n * - [name]: Function that creates plain TaggedError objects\n * - [name with \"Error\" replaced by \"Err\"]: Function that creates Err-wrapped TaggedError objects\n *\n * @example\n * ```ts\n * const { NetworkError, NetworkErr } = createTaggedError('NetworkError');\n *\n * // NetworkError: Creates just the error object\n * const error = NetworkError({\n * message: 'Connection failed',\n * context: { url: 'https://api.example.com' },\n * cause: undefined\n * });\n * // Returns: { name: 'NetworkError', message: 'Connection failed', ... }\n *\n * // NetworkErr: Creates error and wraps in Err result\n * return NetworkErr({\n * message: 'Connection failed',\n * context: { url: 'https://api.example.com' },\n * cause: undefined\n * });\n * // Returns: Err({ name: 'NetworkError', message: 'Connection failed', ... })\n * ```\n */\nexport function createTaggedError<TErrorName extends `${string}Error`>(\n\tname: TErrorName,\n): TaggedErrorFactories<TErrorName> {\n\tconst errorConstructor = (\n\t\terror: TaggedErrorWithoutName<TErrorName>,\n\t): TaggedError<TErrorName> => ({ ...error, name }) as TaggedError<TErrorName>;\n\n\tconst errName = name.replace(\n\t\t/Error$/,\n\t\t\"Err\",\n\t) as ReplaceErrorWithErr<TErrorName>;\n\tconst errConstructor = (error: TaggedErrorWithoutName<TErrorName>) =>\n\t\tErr(errorConstructor(error));\n\n\treturn {\n\t\t[name]: errorConstructor,\n\t\t[errName]: errConstructor,\n\t} as TaggedErrorFactories<TErrorName>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,oBAAoBA,OAAwB;AAC3D,KAAI,iBAAiB,MACpB,QAAO,MAAM;AAGd,YAAW,UAAU,SACpB,QAAO;AAGR,YAAW,UAAU,YAAY,UAAU,MAAM;EAEhD,MAAM,WAAW;AACjB,aAAW,SAAS,YAAY,SAC/B,QAAO,SAAS;AAEjB,aAAW,SAAS,UAAU,SAC7B,QAAO,SAAS;AAEjB,aAAW,SAAS,gBAAgB,SACnC,QAAO,SAAS;AAIjB,MAAI;AACH,UAAO,KAAK,UAAU,MAAM;EAC5B,QAAO;AACP,UAAO,OAAO,MAAM;EACpB;CACD;AAED,QAAO,OAAO,MAAM;AACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6ED,SAAgB,kBACfC,MACmC;CACnC,MAAM,mBAAmB,CACxBC,WAC8B;EAAE,GAAG;EAAO;CAAM;CAEjD,MAAM,UAAU,KAAK,QACpB,UACA,MACA;CACD,MAAM,iBAAiB,CAACA,UACvB,IAAI,iBAAiB,MAAM,CAAC;AAE7B,QAAO;GACL,OAAO;GACP,UAAU;CACX;AACD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Err, Ok, Result } from "./result-t0dngyiE.js";
|
|
2
|
+
|
|
3
|
+
//#region src/result/utils.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Partitions an array of Result objects into two separate arrays based on their status.
|
|
7
|
+
*
|
|
8
|
+
* @template T - The success type
|
|
9
|
+
* @template E - The error type
|
|
10
|
+
* @param results - An array of Result objects to partition
|
|
11
|
+
* @returns An object containing two arrays:
|
|
12
|
+
* - `oks`: Array of successful Result objects (Ok<T>[])
|
|
13
|
+
* - `errs`: Array of error Result objects (Err<E>[])
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const results = [Ok(1), Err("error"), Ok(2)];
|
|
17
|
+
* const { oks, errs } = partitionResults(results);
|
|
18
|
+
* // oks = [Ok(1), Ok(2)]
|
|
19
|
+
* // errs = [Err("error")]
|
|
20
|
+
*/
|
|
21
|
+
declare function partitionResults<T, E>(results: Result<T, E>[]): {
|
|
22
|
+
oks: Ok<T>[];
|
|
23
|
+
errs: Err<E>[];
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
26
|
+
//#endregion
|
|
27
|
+
export { partitionResults };
|
|
28
|
+
//# sourceMappingURL=index-DRbikk8-.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-DRbikk8-.d.ts","names":[],"sources":["../src/result/utils.ts"],"sourcesContent":[],"mappings":";;;;;;AAmBA;;;;;;;;;AAK+B;;;;;iBALf,gCAAgC,OAAO,GAAG;OAK7C,GAAG;QAAY,IAAI"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Result } from "../result-t0dngyiE.js";
|
|
2
|
+
import "../index-DRbikk8-.js";
|
|
3
|
+
import { MutationFunction, MutationKey, MutationOptions, QueryClient, QueryFunction, QueryKey, QueryOptions } from "@tanstack/query-core";
|
|
4
|
+
|
|
5
|
+
//#region src/query/utils.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Input options for defining a query.
|
|
9
|
+
*
|
|
10
|
+
* Extends TanStack Query's QueryOptions but replaces queryFn with resultQueryFn.
|
|
11
|
+
* This type represents the configuration for creating a query definition with both
|
|
12
|
+
* reactive and imperative interfaces for data fetching.
|
|
13
|
+
*
|
|
14
|
+
* @template TQueryFnData - The type of data returned by the query function
|
|
15
|
+
* @template TError - The type of error that can be thrown
|
|
16
|
+
* @template TData - The type of data returned by the query (after select transform)
|
|
17
|
+
* @template TQueryKey - The type of the query key
|
|
18
|
+
*/
|
|
19
|
+
type DefineQueryInput<TQueryFnData, TError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> = Omit<QueryOptions<TQueryFnData, TError, TData, TQueryKey>, "queryFn"> & {
|
|
20
|
+
queryKey: TQueryKey;
|
|
21
|
+
resultQueryFn: QueryFunction<Result<TQueryFnData, TError>, TQueryKey>;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Output of defineQuery function.
|
|
25
|
+
*
|
|
26
|
+
* Provides both reactive and imperative interfaces for data fetching:
|
|
27
|
+
* - `options()`: Returns config for use with createQuery() in Svelte components
|
|
28
|
+
* - `fetchCached()`: Imperatively fetches data (useful for actions/event handlers)
|
|
29
|
+
*
|
|
30
|
+
* @template TQueryFnData - The type of data returned by the query function
|
|
31
|
+
* @template TError - The type of error that can be thrown
|
|
32
|
+
* @template TData - The type of data returned by the query (after select transform)
|
|
33
|
+
* @template TQueryKey - The type of the query key
|
|
34
|
+
*/
|
|
35
|
+
type DefineQueryOutput<TQueryFnData, TError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> = {
|
|
36
|
+
options: () => QueryOptions<TQueryFnData, TError, TData, TQueryKey>;
|
|
37
|
+
fetchCached: () => Promise<Result<TData, TError>>;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Input options for defining a mutation.
|
|
41
|
+
*
|
|
42
|
+
* Extends TanStack Query's MutationOptions but replaces mutationFn with resultMutationFn.
|
|
43
|
+
* This type represents the configuration for creating a mutation definition with both
|
|
44
|
+
* reactive and imperative interfaces for data mutations.
|
|
45
|
+
*
|
|
46
|
+
* @template TData - The type of data returned by the mutation
|
|
47
|
+
* @template TError - The type of error that can be thrown
|
|
48
|
+
* @template TVariables - The type of variables passed to the mutation
|
|
49
|
+
* @template TContext - The type of context data for optimistic updates
|
|
50
|
+
*/
|
|
51
|
+
type DefineMutationInput<TData, TError, TVariables, TContext = unknown> = Omit<MutationOptions<TData, TError, TVariables, TContext>, "mutationFn"> & {
|
|
52
|
+
mutationKey: MutationKey;
|
|
53
|
+
resultMutationFn: MutationFunction<Result<TData, TError>, TVariables>;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Output of defineMutation function.
|
|
57
|
+
*
|
|
58
|
+
* Provides both reactive and imperative interfaces for data mutations:
|
|
59
|
+
* - `options()`: Returns config for use with createMutation() in Svelte components
|
|
60
|
+
* - `execute()`: Directly executes the mutation and returns a Result
|
|
61
|
+
*
|
|
62
|
+
* @template TData - The type of data returned by the mutation
|
|
63
|
+
* @template TError - The type of error that can be thrown
|
|
64
|
+
* @template TVariables - The type of variables passed to the mutation
|
|
65
|
+
* @template TContext - The type of context data for optimistic updates
|
|
66
|
+
*/
|
|
67
|
+
type DefineMutationOutput<TData, TError, TVariables, TContext = unknown> = {
|
|
68
|
+
options: () => MutationOptions<TData, TError, TVariables, TContext>;
|
|
69
|
+
execute: (variables: TVariables) => Promise<Result<TData, TError>>;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Creates factory functions for defining queries and mutations bound to a specific QueryClient.
|
|
73
|
+
*
|
|
74
|
+
* This factory pattern allows you to create isolated query/mutation definitions that are
|
|
75
|
+
* bound to a specific QueryClient instance, enabling:
|
|
76
|
+
* - Multiple query clients in the same application
|
|
77
|
+
* - Testing with isolated query clients
|
|
78
|
+
* - Framework-agnostic query definitions
|
|
79
|
+
* - Proper separation of concerns between query logic and client instances
|
|
80
|
+
*
|
|
81
|
+
* The returned functions handle Result types automatically, unwrapping them for TanStack Query
|
|
82
|
+
* while maintaining type safety throughout your application.
|
|
83
|
+
*
|
|
84
|
+
* @param queryClient - The QueryClient instance to bind the factories to
|
|
85
|
+
* @returns An object containing defineQuery and defineMutation functions bound to the provided client
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* // Create your query client
|
|
90
|
+
* const queryClient = new QueryClient({
|
|
91
|
+
* defaultOptions: {
|
|
92
|
+
* queries: { staleTime: 5 * 60 * 1000 }
|
|
93
|
+
* }
|
|
94
|
+
* });
|
|
95
|
+
*
|
|
96
|
+
* // Create the factory functions
|
|
97
|
+
* const { defineQuery, defineMutation } = createQueryFactories(queryClient);
|
|
98
|
+
*
|
|
99
|
+
* // Now use defineQuery and defineMutation as before
|
|
100
|
+
* const userQuery = defineQuery({
|
|
101
|
+
* queryKey: ['user', userId],
|
|
102
|
+
* resultQueryFn: () => services.getUser(userId)
|
|
103
|
+
* });
|
|
104
|
+
*
|
|
105
|
+
* // Use in components
|
|
106
|
+
* const query = createQuery(userQuery.options());
|
|
107
|
+
*
|
|
108
|
+
* // Or imperatively
|
|
109
|
+
* const { data, error } = await userQuery.fetchCached();
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
declare function createQueryFactories(queryClient: QueryClient): {
|
|
113
|
+
defineQuery: <TQueryFnData, TError, TData = TQueryFnData, TQueryKey extends QueryKey = readonly unknown[]>(options: DefineQueryInput<TQueryFnData, TError, TData, TQueryKey>) => DefineQueryOutput<TQueryFnData, TError, TData, TQueryKey>;
|
|
114
|
+
defineMutation: <TData, TError, TVariables, TContext = unknown>(options: DefineMutationInput<TData, TError, TVariables, TContext>) => DefineMutationOutput<TData, TError, TVariables, TContext>;
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
117
|
+
//#endregion
|
|
118
|
+
export { createQueryFactories };
|
|
119
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/query/utils.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAwBA;;;;;;;;;;AAKI,KALQ,gBAKR,CAAA,YAAA,EAAA,MAAA,EAAA,QAFK,YAEL,EAAA,kBADe,QACf,GAD0B,QAC1B,CAAA,GAAA,IAAA,CAAK,YAAL,CAAkB,YAAlB,EAAgC,MAAhC,EAAwC,KAAxC,EAA+C,SAA/C,CAAA,EAAA,SAAA,CAAA,GAAA;EAAI,QACG,EAAA,SAAA;EAAS,aACiB,EAArB,aAAqB,CAAP,MAAO,CAAA,YAAA,EAAc,MAAd,CAAA,EAAuB,SAAvB,CAAA;CAAY;;;;AAApB;AAe7B;;;;;;;;AAM0D,KAN9C,iBAM8C,CAAA,YAAA,EAAA,MAAA,EAAA,QAHjD,YAGiD,EAAA,kBAFvC,QAEuC,GAF5B,QAE4B,CAAA,GAAA;EAAS,OAAnD,EAAA,GAAA,GAAA,YAAA,CAAa,YAAb,EAA2B,MAA3B,EAAmC,KAAnC,EAA0C,SAA1C,CAAA;EAAY,WACO,EAAA,GAAA,GAAf,OAAe,CAAP,MAAO,CAAA,KAAA,EAAO,MAAP,CAAA,CAAA;CAAK;;;AAAb;AAe3B;;;;;;;;;AAO2C,KAP/B,mBAO+B,CAAA,KAAA,EAAA,MAAA,EAAA,UAAA,EAAA,WAAA,OAAA,CAAA,GAFvC,IAEuC,CAFlC,eAEkC,CAFlB,KAEkB,EAFX,MAEW,EAFH,UAEG,EAFS,QAET,CAAA,EAAA,YAAA,CAAA,GAAA;EAAK,WAAE,EADpC,WACoC;EAAM,gBAApB,EAAjB,gBAAiB,CAAA,MAAA,CAAO,KAAP,EAAc,MAAd,CAAA,EAAuB,UAAvB,CAAA;CAAM;;AAAP;AAenC;;;;;;;;;;AAO6C,KAPjC,oBAOiC,CAAA,KAAA,EAAA,MAAA,EAAA,UAAA,EAAA,WAAA,OAAA,CAAA,GAAA;EAAM,OAAd,EAAA,GAAA,GADrB,eACqB,CADL,KACK,EADE,MACF,EADU,UACV,EADsB,QACtB,CAAA;EAAO,OAAA,EAAA,CAAA,SAAA,EAAtB,UAAsB,EAAA,GAAP,OAAO,CAAC,MAAD,CAAQ,KAAR,EAAe,MAAf,CAAA,CAAA;AA4C5C,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;AAqLwB;;;;;;;;;;;;;;;;;iBArLR,oBAAA,cAAkC;8CAuD3C,gCACa,wCAET,iBAAiB,cAAc,QAAQ,OAAO,eACrD,kBAAkB,cAAc,QAAQ,OAAO;2EAyHxC,oBAAoB,OAAO,QAAQ,YAAY,cACtD,qBAAqB,OAAO,QAAQ,YAAY"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { Err, Ok, resolve } from "../result-DxmXBFi5.js";
|
|
2
|
+
import "../result-BCv06xhR.js";
|
|
3
|
+
|
|
4
|
+
//#region src/query/utils.ts
|
|
5
|
+
/**
|
|
6
|
+
* Creates factory functions for defining queries and mutations bound to a specific QueryClient.
|
|
7
|
+
*
|
|
8
|
+
* This factory pattern allows you to create isolated query/mutation definitions that are
|
|
9
|
+
* bound to a specific QueryClient instance, enabling:
|
|
10
|
+
* - Multiple query clients in the same application
|
|
11
|
+
* - Testing with isolated query clients
|
|
12
|
+
* - Framework-agnostic query definitions
|
|
13
|
+
* - Proper separation of concerns between query logic and client instances
|
|
14
|
+
*
|
|
15
|
+
* The returned functions handle Result types automatically, unwrapping them for TanStack Query
|
|
16
|
+
* while maintaining type safety throughout your application.
|
|
17
|
+
*
|
|
18
|
+
* @param queryClient - The QueryClient instance to bind the factories to
|
|
19
|
+
* @returns An object containing defineQuery and defineMutation functions bound to the provided client
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Create your query client
|
|
24
|
+
* const queryClient = new QueryClient({
|
|
25
|
+
* defaultOptions: {
|
|
26
|
+
* queries: { staleTime: 5 * 60 * 1000 }
|
|
27
|
+
* }
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // Create the factory functions
|
|
31
|
+
* const { defineQuery, defineMutation } = createQueryFactories(queryClient);
|
|
32
|
+
*
|
|
33
|
+
* // Now use defineQuery and defineMutation as before
|
|
34
|
+
* const userQuery = defineQuery({
|
|
35
|
+
* queryKey: ['user', userId],
|
|
36
|
+
* resultQueryFn: () => services.getUser(userId)
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* // Use in components
|
|
40
|
+
* const query = createQuery(userQuery.options());
|
|
41
|
+
*
|
|
42
|
+
* // Or imperatively
|
|
43
|
+
* const { data, error } = await userQuery.fetchCached();
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
function createQueryFactories(queryClient) {
|
|
47
|
+
/**
|
|
48
|
+
* Creates a query definition that bridges the gap between pure service functions and reactive UI components.
|
|
49
|
+
*
|
|
50
|
+
* This factory function is the cornerstone of our data fetching architecture. It wraps service calls
|
|
51
|
+
* with TanStack Query superpowers while maintaining type safety through Result types.
|
|
52
|
+
*
|
|
53
|
+
* ## Why use defineQuery?
|
|
54
|
+
*
|
|
55
|
+
* 1. **Dual Interface**: Provides both reactive (`.options()`) and imperative (`.fetchCached()`) APIs
|
|
56
|
+
* 2. **Automatic Error Handling**: Service functions return `Result<T, E>` types which are automatically
|
|
57
|
+
* unwrapped by TanStack Query, giving you proper error states in your components
|
|
58
|
+
* 3. **Type Safety**: Full TypeScript support with proper inference for data and error types
|
|
59
|
+
* 4. **Consistency**: Every query in the app follows the same pattern, making it easy to understand
|
|
60
|
+
*
|
|
61
|
+
* @template TQueryFnData - The type of data returned by the query function
|
|
62
|
+
* @template TError - The type of error that can be thrown
|
|
63
|
+
* @template TData - The type of data returned by the query (after select transform)
|
|
64
|
+
* @template TQueryKey - The type of the query key
|
|
65
|
+
*
|
|
66
|
+
* @param options - Query configuration object
|
|
67
|
+
* @param options.queryKey - Unique key for this query (used for caching and refetching)
|
|
68
|
+
* @param options.resultQueryFn - Function that fetches data and returns a Result type
|
|
69
|
+
* @param options.* - Any other TanStack Query options (staleTime, refetchInterval, etc.)
|
|
70
|
+
*
|
|
71
|
+
* @returns Query definition object with two methods:
|
|
72
|
+
* - `options()`: Returns config for use with createQuery() in Svelte components
|
|
73
|
+
* - `fetchCached()`: Imperatively fetches data (useful for actions/event handlers)
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* // Step 1: Define your query in the query layer
|
|
78
|
+
* const userQuery = defineQuery({
|
|
79
|
+
* queryKey: ['users', userId],
|
|
80
|
+
* resultQueryFn: () => services.getUser(userId), // Returns Result<User, ApiError>
|
|
81
|
+
* staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes
|
|
82
|
+
* });
|
|
83
|
+
*
|
|
84
|
+
* // Step 2a: Use reactively in a Svelte component
|
|
85
|
+
* const query = createQuery(userQuery.options());
|
|
86
|
+
* // $query.data is User | undefined
|
|
87
|
+
* // $query.error is ApiError | null
|
|
88
|
+
*
|
|
89
|
+
* // Step 2b: Use imperatively in an action
|
|
90
|
+
* async function prefetchUser() {
|
|
91
|
+
* const { data, error } = await userQuery.fetchCached();
|
|
92
|
+
* if (error) {
|
|
93
|
+
* console.error('Failed to fetch user:', error);
|
|
94
|
+
* }
|
|
95
|
+
* }
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
const defineQuery = (options) => {
|
|
99
|
+
const newOptions = {
|
|
100
|
+
...options,
|
|
101
|
+
queryFn: async (context) => {
|
|
102
|
+
let result = options.resultQueryFn(context);
|
|
103
|
+
if (result instanceof Promise) result = await result;
|
|
104
|
+
return resolve(result);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
options: () => newOptions,
|
|
109
|
+
async fetchCached() {
|
|
110
|
+
try {
|
|
111
|
+
return Ok(await queryClient.fetchQuery({
|
|
112
|
+
queryKey: newOptions.queryKey,
|
|
113
|
+
queryFn: newOptions.queryFn
|
|
114
|
+
}));
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return Err(error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Creates a mutation definition for operations that modify data (create, update, delete).
|
|
123
|
+
*
|
|
124
|
+
* This factory function is the mutation counterpart to defineQuery. It provides a clean way to
|
|
125
|
+
* wrap service functions that perform side effects, while maintaining the same dual interface
|
|
126
|
+
* pattern for maximum flexibility.
|
|
127
|
+
*
|
|
128
|
+
* ## Why use defineMutation?
|
|
129
|
+
*
|
|
130
|
+
* 1. **Dual Interface**: Just like queries, mutations can be used reactively or imperatively
|
|
131
|
+
* 2. **Direct Execution**: The `.execute()` method lets you run mutations without creating hooks,
|
|
132
|
+
* perfect for event handlers and non-component code
|
|
133
|
+
* 3. **Consistent Error Handling**: Service functions return `Result<T, E>` types, ensuring
|
|
134
|
+
* errors are handled consistently throughout the app
|
|
135
|
+
* 4. **Cache Management**: Mutations often update the cache after success (see examples)
|
|
136
|
+
*
|
|
137
|
+
* @template TData - The type of data returned by the mutation
|
|
138
|
+
* @template TError - The type of error that can be thrown
|
|
139
|
+
* @template TVariables - The type of variables passed to the mutation
|
|
140
|
+
* @template TContext - The type of context data for optimistic updates
|
|
141
|
+
*
|
|
142
|
+
* @param options - Mutation configuration object
|
|
143
|
+
* @param options.mutationKey - Unique key for this mutation (used for tracking in-flight state)
|
|
144
|
+
* @param options.resultMutationFn - Function that performs the mutation and returns a Result type
|
|
145
|
+
* @param options.* - Any other TanStack Mutation options (onSuccess, onError, etc.)
|
|
146
|
+
*
|
|
147
|
+
* @returns Mutation definition object with two methods:
|
|
148
|
+
* - `options()`: Returns config for use with createMutation() in Svelte components
|
|
149
|
+
* - `execute()`: Directly executes the mutation and returns a Result
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* // Step 1: Define your mutation with cache updates
|
|
154
|
+
* const createRecording = defineMutation({
|
|
155
|
+
* mutationKey: ['recordings', 'create'],
|
|
156
|
+
* resultMutationFn: async (recording: Recording) => {
|
|
157
|
+
* // Call the service
|
|
158
|
+
* const result = await services.db.createRecording(recording);
|
|
159
|
+
* if (result.error) return Err(result.error);
|
|
160
|
+
*
|
|
161
|
+
* // Update cache on success
|
|
162
|
+
* queryClient.setQueryData(['recordings'], (old) =>
|
|
163
|
+
* [...(old || []), recording]
|
|
164
|
+
* );
|
|
165
|
+
*
|
|
166
|
+
* return Ok(result.data);
|
|
167
|
+
* }
|
|
168
|
+
* });
|
|
169
|
+
*
|
|
170
|
+
* // Step 2a: Use reactively in a component
|
|
171
|
+
* const mutation = createMutation(createRecording.options());
|
|
172
|
+
* // Call with: $mutation.mutate(recordingData)
|
|
173
|
+
*
|
|
174
|
+
* // Step 2b: Use imperatively in an action
|
|
175
|
+
* async function saveRecording(data: Recording) {
|
|
176
|
+
* const { error } = await createRecording.execute(data);
|
|
177
|
+
* if (error) {
|
|
178
|
+
* notify.error.execute({ title: 'Failed to save', description: error.message });
|
|
179
|
+
* } else {
|
|
180
|
+
* notify.success.execute({ title: 'Recording saved!' });
|
|
181
|
+
* }
|
|
182
|
+
* }
|
|
183
|
+
* ```
|
|
184
|
+
*
|
|
185
|
+
* @tip The imperative `.execute()` method is especially useful for:
|
|
186
|
+
* - Event handlers that need to await the result
|
|
187
|
+
* - Sequential operations that depend on each other
|
|
188
|
+
* - Non-component code that needs to trigger mutations
|
|
189
|
+
*/
|
|
190
|
+
const defineMutation = (options) => {
|
|
191
|
+
const newOptions = {
|
|
192
|
+
...options,
|
|
193
|
+
mutationFn: async (variables) => {
|
|
194
|
+
return resolve(await options.resultMutationFn(variables));
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
return {
|
|
198
|
+
options: () => newOptions,
|
|
199
|
+
async execute(variables) {
|
|
200
|
+
try {
|
|
201
|
+
return Ok(await executeMutation(queryClient, newOptions, variables));
|
|
202
|
+
} catch (error) {
|
|
203
|
+
return Err(error);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
return {
|
|
209
|
+
defineQuery,
|
|
210
|
+
defineMutation
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Internal helper that executes a mutation directly using the query client's mutation cache.
|
|
215
|
+
*
|
|
216
|
+
* This is what powers the `.execute()` method on mutations. It bypasses the reactive
|
|
217
|
+
* mutation hooks and runs the mutation imperatively, which is perfect for event handlers
|
|
218
|
+
* and other imperative code.
|
|
219
|
+
*
|
|
220
|
+
* @internal
|
|
221
|
+
* @template TData - The type of data returned by the mutation
|
|
222
|
+
* @template TError - The type of error that can be thrown
|
|
223
|
+
* @template TVariables - The type of variables passed to the mutation
|
|
224
|
+
* @template TContext - The type of context data
|
|
225
|
+
* @param queryClient - The query client instance to use
|
|
226
|
+
* @param options - The mutation options including mutationFn and mutationKey
|
|
227
|
+
* @param variables - The variables to pass to the mutation function
|
|
228
|
+
* @returns Promise that resolves with the mutation result
|
|
229
|
+
*/
|
|
230
|
+
function executeMutation(queryClient, options, variables) {
|
|
231
|
+
const mutation = queryClient.getMutationCache().build(queryClient, options);
|
|
232
|
+
return mutation.execute(variables);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
//#endregion
|
|
236
|
+
export { createQueryFactories };
|
|
237
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["queryClient: QueryClient","options: DefineQueryInput<TQueryFnData, TError, TData, TQueryKey>","context: QueryFunctionContext<TQueryKey>","options: DefineMutationInput<TData, TError, TVariables, TContext>","variables: TVariables","options: MutationOptions<TData, TError, TVariables, TContext>"],"sources":["../../src/query/utils.ts"],"sourcesContent":["import type {\n\tMutationFunction,\n\tMutationKey,\n\tMutationOptions,\n\tQueryClient,\n\tQueryFunction,\n\tQueryFunctionContext,\n\tQueryKey,\n} from \"@tanstack/query-core\";\nimport { Err, Ok, type Result, resolve } from \"../result/index.js\";\nimport type { QueryOptions } from \"@tanstack/query-core\";\n\n/**\n * Input options for defining a query.\n *\n * Extends TanStack Query's QueryOptions but replaces queryFn with resultQueryFn.\n * This type represents the configuration for creating a query definition with both\n * reactive and imperative interfaces for data fetching.\n *\n * @template TQueryFnData - The type of data returned by the query function\n * @template TError - The type of error that can be thrown\n * @template TData - The type of data returned by the query (after select transform)\n * @template TQueryKey - The type of the query key\n */\nexport type DefineQueryInput<\n\tTQueryFnData,\n\tTError,\n\tTData = TQueryFnData,\n\tTQueryKey extends QueryKey = QueryKey,\n> = Omit<QueryOptions<TQueryFnData, TError, TData, TQueryKey>, \"queryFn\"> & {\n\tqueryKey: TQueryKey;\n\tresultQueryFn: QueryFunction<Result<TQueryFnData, TError>, TQueryKey>;\n};\n\n/**\n * Output of defineQuery function.\n *\n * Provides both reactive and imperative interfaces for data fetching:\n * - `options()`: Returns config for use with createQuery() in Svelte components\n * - `fetchCached()`: Imperatively fetches data (useful for actions/event handlers)\n *\n * @template TQueryFnData - The type of data returned by the query function\n * @template TError - The type of error that can be thrown\n * @template TData - The type of data returned by the query (after select transform)\n * @template TQueryKey - The type of the query key\n */\nexport type DefineQueryOutput<\n\tTQueryFnData,\n\tTError,\n\tTData = TQueryFnData,\n\tTQueryKey extends QueryKey = QueryKey,\n> = {\n\toptions: () => QueryOptions<TQueryFnData, TError, TData, TQueryKey>;\n\tfetchCached: () => Promise<Result<TData, TError>>;\n};\n\n/**\n * Input options for defining a mutation.\n *\n * Extends TanStack Query's MutationOptions but replaces mutationFn with resultMutationFn.\n * This type represents the configuration for creating a mutation definition with both\n * reactive and imperative interfaces for data mutations.\n *\n * @template TData - The type of data returned by the mutation\n * @template TError - The type of error that can be thrown\n * @template TVariables - The type of variables passed to the mutation\n * @template TContext - The type of context data for optimistic updates\n */\nexport type DefineMutationInput<\n\tTData,\n\tTError,\n\tTVariables,\n\tTContext = unknown,\n> = Omit<MutationOptions<TData, TError, TVariables, TContext>, \"mutationFn\"> & {\n\tmutationKey: MutationKey;\n\tresultMutationFn: MutationFunction<Result<TData, TError>, TVariables>;\n};\n\n/**\n * Output of defineMutation function.\n *\n * Provides both reactive and imperative interfaces for data mutations:\n * - `options()`: Returns config for use with createMutation() in Svelte components\n * - `execute()`: Directly executes the mutation and returns a Result\n *\n * @template TData - The type of data returned by the mutation\n * @template TError - The type of error that can be thrown\n * @template TVariables - The type of variables passed to the mutation\n * @template TContext - The type of context data for optimistic updates\n */\nexport type DefineMutationOutput<\n\tTData,\n\tTError,\n\tTVariables,\n\tTContext = unknown,\n> = {\n\toptions: () => MutationOptions<TData, TError, TVariables, TContext>;\n\texecute: (variables: TVariables) => Promise<Result<TData, TError>>;\n};\n\n/**\n * Creates factory functions for defining queries and mutations bound to a specific QueryClient.\n *\n * This factory pattern allows you to create isolated query/mutation definitions that are\n * bound to a specific QueryClient instance, enabling:\n * - Multiple query clients in the same application\n * - Testing with isolated query clients\n * - Framework-agnostic query definitions\n * - Proper separation of concerns between query logic and client instances\n *\n * The returned functions handle Result types automatically, unwrapping them for TanStack Query\n * while maintaining type safety throughout your application.\n *\n * @param queryClient - The QueryClient instance to bind the factories to\n * @returns An object containing defineQuery and defineMutation functions bound to the provided client\n *\n * @example\n * ```typescript\n * // Create your query client\n * const queryClient = new QueryClient({\n * defaultOptions: {\n * queries: { staleTime: 5 * 60 * 1000 }\n * }\n * });\n *\n * // Create the factory functions\n * const { defineQuery, defineMutation } = createQueryFactories(queryClient);\n *\n * // Now use defineQuery and defineMutation as before\n * const userQuery = defineQuery({\n * queryKey: ['user', userId],\n * resultQueryFn: () => services.getUser(userId)\n * });\n *\n * // Use in components\n * const query = createQuery(userQuery.options());\n *\n * // Or imperatively\n * const { data, error } = await userQuery.fetchCached();\n * ```\n */\nexport function createQueryFactories(queryClient: QueryClient) {\n\t/**\n\t * Creates a query definition that bridges the gap between pure service functions and reactive UI components.\n\t *\n\t * This factory function is the cornerstone of our data fetching architecture. It wraps service calls\n\t * with TanStack Query superpowers while maintaining type safety through Result types.\n\t *\n\t * ## Why use defineQuery?\n\t *\n\t * 1. **Dual Interface**: Provides both reactive (`.options()`) and imperative (`.fetchCached()`) APIs\n\t * 2. **Automatic Error Handling**: Service functions return `Result<T, E>` types which are automatically\n\t * unwrapped by TanStack Query, giving you proper error states in your components\n\t * 3. **Type Safety**: Full TypeScript support with proper inference for data and error types\n\t * 4. **Consistency**: Every query in the app follows the same pattern, making it easy to understand\n\t *\n\t * @template TQueryFnData - The type of data returned by the query function\n\t * @template TError - The type of error that can be thrown\n\t * @template TData - The type of data returned by the query (after select transform)\n\t * @template TQueryKey - The type of the query key\n\t *\n\t * @param options - Query configuration object\n\t * @param options.queryKey - Unique key for this query (used for caching and refetching)\n\t * @param options.resultQueryFn - Function that fetches data and returns a Result type\n\t * @param options.* - Any other TanStack Query options (staleTime, refetchInterval, etc.)\n\t *\n\t * @returns Query definition object with two methods:\n\t * - `options()`: Returns config for use with createQuery() in Svelte components\n\t * - `fetchCached()`: Imperatively fetches data (useful for actions/event handlers)\n\t *\n\t * @example\n\t * ```typescript\n\t * // Step 1: Define your query in the query layer\n\t * const userQuery = defineQuery({\n\t * queryKey: ['users', userId],\n\t * resultQueryFn: () => services.getUser(userId), // Returns Result<User, ApiError>\n\t * staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes\n\t * });\n\t *\n\t * // Step 2a: Use reactively in a Svelte component\n\t * const query = createQuery(userQuery.options());\n\t * // $query.data is User | undefined\n\t * // $query.error is ApiError | null\n\t *\n\t * // Step 2b: Use imperatively in an action\n\t * async function prefetchUser() {\n\t * const { data, error } = await userQuery.fetchCached();\n\t * if (error) {\n\t * console.error('Failed to fetch user:', error);\n\t * }\n\t * }\n\t * ```\n\t */\n\tconst defineQuery = <\n\t\tTQueryFnData,\n\t\tTError,\n\t\tTData = TQueryFnData,\n\t\tTQueryKey extends QueryKey = QueryKey,\n\t>(\n\t\toptions: DefineQueryInput<TQueryFnData, TError, TData, TQueryKey>,\n\t): DefineQueryOutput<TQueryFnData, TError, TData, TQueryKey> => {\n\t\tconst newOptions = {\n\t\t\t...options,\n\t\t\tqueryFn: async (context: QueryFunctionContext<TQueryKey>) => {\n\t\t\t\tlet result = options.resultQueryFn(context);\n\t\t\t\tif (result instanceof Promise) result = await result;\n\t\t\t\treturn resolve(result);\n\t\t\t},\n\t\t} satisfies QueryOptions<TQueryFnData, TError, TData, TQueryKey>;\n\n\t\treturn {\n\t\t\t/**\n\t\t\t * Returns the query options for reactive usage with TanStack Query hooks.\n\t\t\t * Use this with `createQuery()` in Svelte components for automatic subscriptions.\n\t\t\t * @returns The query options object configured for TanStack Query\n\t\t\t */\n\t\t\toptions: () => newOptions,\n\n\t\t\t/**\n\t\t\t * Fetches data for this query, returning cached data if fresh or refetching if stale/missing.\n\t\t\t *\n\t\t\t * This method is perfect for:\n\t\t\t * - Prefetching data before navigation\n\t\t\t * - Loading data in response to user actions\n\t\t\t * - Accessing query data outside of components\n\t\t\t *\n\t\t\t * @returns Promise that resolves with a Result containing either the data or an error\n\t\t\t *\n\t\t\t * @example\n\t\t\t * const { data, error } = await userQuery.fetchCached();\n\t\t\t * if (error) {\n\t\t\t * console.error('Failed to load user:', error);\n\t\t\t * }\n\t\t\t */\n\t\t\tasync fetchCached(): Promise<Result<TData, TError>> {\n\t\t\t\ttry {\n\t\t\t\t\treturn Ok(\n\t\t\t\t\t\tawait queryClient.fetchQuery<TQueryFnData, Error, TData, TQueryKey>(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tqueryKey: newOptions.queryKey,\n\t\t\t\t\t\t\t\tqueryFn: newOptions.queryFn,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn Err(error as TError);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t};\n\n\t/**\n\t * Creates a mutation definition for operations that modify data (create, update, delete).\n\t *\n\t * This factory function is the mutation counterpart to defineQuery. It provides a clean way to\n\t * wrap service functions that perform side effects, while maintaining the same dual interface\n\t * pattern for maximum flexibility.\n\t *\n\t * ## Why use defineMutation?\n\t *\n\t * 1. **Dual Interface**: Just like queries, mutations can be used reactively or imperatively\n\t * 2. **Direct Execution**: The `.execute()` method lets you run mutations without creating hooks,\n\t * perfect for event handlers and non-component code\n\t * 3. **Consistent Error Handling**: Service functions return `Result<T, E>` types, ensuring\n\t * errors are handled consistently throughout the app\n\t * 4. **Cache Management**: Mutations often update the cache after success (see examples)\n\t *\n\t * @template TData - The type of data returned by the mutation\n\t * @template TError - The type of error that can be thrown\n\t * @template TVariables - The type of variables passed to the mutation\n\t * @template TContext - The type of context data for optimistic updates\n\t *\n\t * @param options - Mutation configuration object\n\t * @param options.mutationKey - Unique key for this mutation (used for tracking in-flight state)\n\t * @param options.resultMutationFn - Function that performs the mutation and returns a Result type\n\t * @param options.* - Any other TanStack Mutation options (onSuccess, onError, etc.)\n\t *\n\t * @returns Mutation definition object with two methods:\n\t * - `options()`: Returns config for use with createMutation() in Svelte components\n\t * - `execute()`: Directly executes the mutation and returns a Result\n\t *\n\t * @example\n\t * ```typescript\n\t * // Step 1: Define your mutation with cache updates\n\t * const createRecording = defineMutation({\n\t * mutationKey: ['recordings', 'create'],\n\t * resultMutationFn: async (recording: Recording) => {\n\t * // Call the service\n\t * const result = await services.db.createRecording(recording);\n\t * if (result.error) return Err(result.error);\n\t *\n\t * // Update cache on success\n\t * queryClient.setQueryData(['recordings'], (old) =>\n\t * [...(old || []), recording]\n\t * );\n\t *\n\t * return Ok(result.data);\n\t * }\n\t * });\n\t *\n\t * // Step 2a: Use reactively in a component\n\t * const mutation = createMutation(createRecording.options());\n\t * // Call with: $mutation.mutate(recordingData)\n\t *\n\t * // Step 2b: Use imperatively in an action\n\t * async function saveRecording(data: Recording) {\n\t * const { error } = await createRecording.execute(data);\n\t * if (error) {\n\t * notify.error.execute({ title: 'Failed to save', description: error.message });\n\t * } else {\n\t * notify.success.execute({ title: 'Recording saved!' });\n\t * }\n\t * }\n\t * ```\n\t *\n\t * @tip The imperative `.execute()` method is especially useful for:\n\t * - Event handlers that need to await the result\n\t * - Sequential operations that depend on each other\n\t * - Non-component code that needs to trigger mutations\n\t */\n\tconst defineMutation = <TData, TError, TVariables, TContext = unknown>(\n\t\toptions: DefineMutationInput<TData, TError, TVariables, TContext>,\n\t): DefineMutationOutput<TData, TError, TVariables, TContext> => {\n\t\tconst newOptions = {\n\t\t\t...options,\n\t\t\tmutationFn: async (variables: TVariables) => {\n\t\t\t\treturn resolve(await options.resultMutationFn(variables));\n\t\t\t},\n\t\t} satisfies MutationOptions<TData, TError, TVariables, TContext>;\n\n\t\treturn {\n\t\t\t/**\n\t\t\t * Returns the mutation options for reactive usage with TanStack Query hooks.\n\t\t\t * Use this with `createMutation()` in Svelte components for reactive mutation state.\n\t\t\t * @returns The mutation options object configured for TanStack Query\n\t\t\t */\n\t\t\toptions: () => newOptions,\n\t\t\t/**\n\t\t\t * Bypasses the reactive mutation hooks and executes the mutation imperatively.\n\t\t\t *\n\t\t\t * This is the recommended way to trigger mutations from:\n\t\t\t * - Button click handlers\n\t\t\t * - Form submissions\n\t\t\t * - Keyboard shortcuts\n\t\t\t * - Any non-component code\n\t\t\t *\n\t\t\t * The method automatically wraps the result in a Result type, so you always\n\t\t\t * get back `{ data, error }` for consistent error handling.\n\t\t\t *\n\t\t\t * @param variables - The variables to pass to the mutation function\n\t\t\t * @returns Promise that resolves with a Result containing either the data or an error\n\t\t\t *\n\t\t\t * @example\n\t\t\t * // In an event handler\n\t\t\t * async function handleSubmit(formData: FormData) {\n\t\t\t * const { data, error } = await createUser.execute(formData);\n\t\t\t * if (error) {\n\t\t\t * notify.error.execute({ title: 'Failed to create user', description: error.message });\n\t\t\t * return;\n\t\t\t * }\n\t\t\t * goto(`/users/${data.id}`);\n\t\t\t * }\n\t\t\t */\n\t\t\tasync execute(variables: TVariables) {\n\t\t\t\ttry {\n\t\t\t\t\treturn Ok(await executeMutation(queryClient, newOptions, variables));\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn Err(error as TError);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t};\n\n\treturn {\n\t\tdefineQuery,\n\t\tdefineMutation,\n\t};\n}\n\n/**\n * Internal helper that executes a mutation directly using the query client's mutation cache.\n *\n * This is what powers the `.execute()` method on mutations. It bypasses the reactive\n * mutation hooks and runs the mutation imperatively, which is perfect for event handlers\n * and other imperative code.\n *\n * @internal\n * @template TData - The type of data returned by the mutation\n * @template TError - The type of error that can be thrown\n * @template TVariables - The type of variables passed to the mutation\n * @template TContext - The type of context data\n * @param queryClient - The query client instance to use\n * @param options - The mutation options including mutationFn and mutationKey\n * @param variables - The variables to pass to the mutation function\n * @returns Promise that resolves with the mutation result\n */\nfunction executeMutation<TData, TError, TVariables, TContext>(\n\tqueryClient: QueryClient,\n\toptions: MutationOptions<TData, TError, TVariables, TContext>,\n\tvariables: TVariables,\n) {\n\tconst mutation = queryClient.getMutationCache().build(queryClient, options);\n\treturn mutation.execute(variables);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6IA,SAAgB,qBAAqBA,aAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoD9D,MAAM,cAAc,CAMnBC,YAC+D;EAC/D,MAAM,aAAa;GAClB,GAAG;GACH,SAAS,OAAOC,YAA6C;IAC5D,IAAI,SAAS,QAAQ,cAAc,QAAQ;AAC3C,QAAI,kBAAkB,QAAS,UAAS,MAAM;AAC9C,WAAO,QAAQ,OAAO;GACtB;EACD;AAED,SAAO;GAMN,SAAS,MAAM;GAkBf,MAAM,cAA8C;AACnD,QAAI;AACH,YAAO,GACN,MAAM,YAAY,WACjB;MACC,UAAU,WAAW;MACrB,SAAS,WAAW;KACpB,EACD,CACD;IACD,SAAQ,OAAO;AACf,YAAO,IAAI,MAAgB;IAC3B;GACD;EACD;CACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuED,MAAM,iBAAiB,CACtBC,YAC+D;EAC/D,MAAM,aAAa;GAClB,GAAG;GACH,YAAY,OAAOC,cAA0B;AAC5C,WAAO,QAAQ,MAAM,QAAQ,iBAAiB,UAAU,CAAC;GACzD;EACD;AAED,SAAO;GAMN,SAAS,MAAM;GA2Bf,MAAM,QAAQA,WAAuB;AACpC,QAAI;AACH,YAAO,GAAG,MAAM,gBAAgB,aAAa,YAAY,UAAU,CAAC;IACpE,SAAQ,OAAO;AACf,YAAO,IAAI,MAAgB;IAC3B;GACD;EACD;CACD;AAED,QAAO;EACN;EACA;CACA;AACD;;;;;;;;;;;;;;;;;;AAmBD,SAAS,gBACRJ,aACAK,SACAD,WACC;CACD,MAAM,WAAW,YAAY,kBAAkB,CAAC,MAAM,aAAa,QAAQ;AAC3E,QAAO,SAAS,QAAQ,UAAU;AAClC"}
|