@tuyau/query-core 1.1.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/LICENSE.md +9 -0
- package/build/index.d.ts +174 -0
- package/build/index.js +135 -0
- package/package.json +64 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { QueryFilters, SkipToken, skipToken, QueryFunctionContext } from '@tanstack/query-core';
|
|
2
|
+
import { RawRequestArgs, BaseRequestOptions } from '@tuyau/core/types';
|
|
3
|
+
import { Tuyau } from '@tuyau/core/client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Discriminator for the type of TanStack Query operation.
|
|
7
|
+
* Used in query keys to differentiate between regular queries,
|
|
8
|
+
* infinite queries, and catch-all path filters
|
|
9
|
+
*/
|
|
10
|
+
type QueryType = 'any' | 'infinite' | 'query';
|
|
11
|
+
/**
|
|
12
|
+
* Structured query key used by Tuyau's TanStack Query integration.
|
|
13
|
+
* First element is the route path segments, second is an optional
|
|
14
|
+
* metadata object containing the request arguments and query type
|
|
15
|
+
*/
|
|
16
|
+
type TuyauQueryKey = [
|
|
17
|
+
readonly string[],
|
|
18
|
+
{
|
|
19
|
+
request?: RawRequestArgs<any>;
|
|
20
|
+
type?: Exclude<QueryType, 'any'>;
|
|
21
|
+
}?
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Structured mutation key used by Tuyau's TanStack Query integration.
|
|
25
|
+
* Contains only the route path segments since mutations don't need
|
|
26
|
+
* request-based differentiation
|
|
27
|
+
*/
|
|
28
|
+
type TuyauMutationKey = [readonly string[]];
|
|
29
|
+
/**
|
|
30
|
+
* Options forwarded to the underlying Ky HTTP client, extended
|
|
31
|
+
* with `abortOnUnmount` to automatically abort requests when
|
|
32
|
+
* the component using the query unmounts
|
|
33
|
+
*/
|
|
34
|
+
interface TuyauRequestOptions extends BaseRequestOptions {
|
|
35
|
+
abortOnUnmount?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Base options shared by all Tuyau query/mutation option builders.
|
|
39
|
+
* Allows passing Ky-specific options via the `tuyau` property
|
|
40
|
+
*/
|
|
41
|
+
interface TuyauQueryBaseOptions {
|
|
42
|
+
tuyau?: TuyauRequestOptions;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Methods available on every node of the Tuyau query client proxy tree.
|
|
46
|
+
* Provides path-level query key and filter generation for cache operations
|
|
47
|
+
* like invalidation or removal across all query types for a given route
|
|
48
|
+
*/
|
|
49
|
+
interface DecorateRouterKeyable {
|
|
50
|
+
pathKey: () => TuyauQueryKey;
|
|
51
|
+
pathFilter: (filters?: QueryFilters<TuyauQueryKey>) => WithRequired<QueryFilters<TuyauQueryKey>, 'queryKey'>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Generic function type helper
|
|
55
|
+
*/
|
|
56
|
+
type Fn<Result = void, Args extends unknown[] = unknown[]> = (...args: Args) => Result;
|
|
57
|
+
/**
|
|
58
|
+
* Like `Omit` but distributes over union types.
|
|
59
|
+
* Standard `Omit` collapses unions, this preserves each union member
|
|
60
|
+
*/
|
|
61
|
+
type DistributiveOmit<TObj, TKey extends keyof any> = TObj extends any ? Omit<TObj, TKey> : never;
|
|
62
|
+
/**
|
|
63
|
+
* Makes specific keys of a type required while keeping the rest unchanged
|
|
64
|
+
*/
|
|
65
|
+
type WithRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Type guard that checks if a value is a plain object
|
|
69
|
+
* (not null, not an array)
|
|
70
|
+
*/
|
|
71
|
+
declare function isObject(value: unknown): value is Record<string, unknown>;
|
|
72
|
+
/**
|
|
73
|
+
* Immediately invokes a function and returns its result.
|
|
74
|
+
* Used to create query/mutation functions inline with
|
|
75
|
+
* proper skip token handling
|
|
76
|
+
*/
|
|
77
|
+
declare function invoke(fn: undefined): undefined;
|
|
78
|
+
declare function invoke<T>(fn: Fn<T>): T;
|
|
79
|
+
/**
|
|
80
|
+
* Builds a structured TanStack Query key from route segments,
|
|
81
|
+
* request arguments, and query type.
|
|
82
|
+
*
|
|
83
|
+
* For infinite queries, automatically strips `cursor` and `direction`
|
|
84
|
+
* from query/body params to ensure stable cache keys across pages
|
|
85
|
+
*/
|
|
86
|
+
declare function buildKey(opts: {
|
|
87
|
+
segments: string[];
|
|
88
|
+
request?: unknown;
|
|
89
|
+
type: QueryType;
|
|
90
|
+
}): TuyauQueryKey;
|
|
91
|
+
/**
|
|
92
|
+
* Extracts Ky-compatible HTTP options from Tuyau request options,
|
|
93
|
+
* filtering out Tuyau-specific properties like `abortOnUnmount`
|
|
94
|
+
*/
|
|
95
|
+
declare function extractKyOptions(tuyauOpts?: TuyauRequestOptions): Omit<TuyauRequestOptions, 'abortOnUnmount'>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Converts a camelCase string to snake_case.
|
|
99
|
+
* Used to transform proxy property names (e.g. `getArticles`)
|
|
100
|
+
* into AdonisJS route name segments (e.g. `get_articles`)
|
|
101
|
+
*/
|
|
102
|
+
declare function toSnakeCase(str: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* Joins an array of camelCase segments into a dot-separated
|
|
105
|
+
* snake_case route name that matches AdonisJS conventions.
|
|
106
|
+
* e.g. `['users', 'getProfile']` → `'users.get_profile'`
|
|
107
|
+
*/
|
|
108
|
+
declare function segmentsToRouteName(segments: string[]): string;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Internal options for building a query function
|
|
112
|
+
*/
|
|
113
|
+
interface CreateQueryFnOptions {
|
|
114
|
+
request: RawRequestArgs<any> | SkipToken;
|
|
115
|
+
opts?: TuyauQueryBaseOptions;
|
|
116
|
+
routeName: string;
|
|
117
|
+
client: Tuyau<any>;
|
|
118
|
+
globalOptions?: TuyauRequestOptions;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Creates a TanStack Query `queryFn` from Tuyau route information.
|
|
122
|
+
* Returns `skipToken` when the request is a skip token (conditional queries).
|
|
123
|
+
* Handles abort-on-unmount and Ky options extraction.
|
|
124
|
+
* Ky retries are disabled (retry: 0) to let TanStack Query handle retries
|
|
125
|
+
*/
|
|
126
|
+
declare function createQueryFn(options: CreateQueryFnOptions): typeof skipToken | ((queryFnContext: QueryFunctionContext) => Promise<any>);
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Internal options for building an infinite query function.
|
|
130
|
+
* The `opts` parameter accepts any object with optional `tuyau` and
|
|
131
|
+
* `pageParamKey` properties — this avoids coupling to framework-specific
|
|
132
|
+
* option types (React vs Vue) while still accessing the needed values
|
|
133
|
+
*/
|
|
134
|
+
interface CreateInfiniteQueryFnOptions {
|
|
135
|
+
request: RawRequestArgs<any> | SkipToken;
|
|
136
|
+
opts?: {
|
|
137
|
+
tuyau?: TuyauRequestOptions;
|
|
138
|
+
pageParamKey?: string;
|
|
139
|
+
[key: string]: any;
|
|
140
|
+
};
|
|
141
|
+
routeName: string;
|
|
142
|
+
client: Tuyau<any>;
|
|
143
|
+
globalOptions?: TuyauRequestOptions;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Creates a TanStack Query `queryFn` for infinite queries.
|
|
147
|
+
* Injects the page parameter into the request query using the
|
|
148
|
+
* configured `pageParamKey` (defaults to `'page'`).
|
|
149
|
+
* Returns `skipToken` when the request is a skip token.
|
|
150
|
+
* Ky retries are disabled (retry: 0) to let TanStack Query handle retries
|
|
151
|
+
*/
|
|
152
|
+
declare function createInfiniteQueryFn(options: CreateInfiniteQueryFnOptions): typeof skipToken | ((queryFnContext: QueryFunctionContext) => Promise<any>);
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Builds a mutation key from route path segments
|
|
156
|
+
*/
|
|
157
|
+
declare function getMutationKeyInternal(options: {
|
|
158
|
+
segments: string[];
|
|
159
|
+
}): TuyauMutationKey;
|
|
160
|
+
/**
|
|
161
|
+
* Internal options for building a mutation function
|
|
162
|
+
*/
|
|
163
|
+
interface CreateMutationFnOptions {
|
|
164
|
+
opts?: TuyauQueryBaseOptions;
|
|
165
|
+
routeName: string;
|
|
166
|
+
client: Tuyau<any>;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Creates a TanStack Query `mutationFn` from Tuyau route information.
|
|
170
|
+
* Ky retries are disabled (retry: 0) to let TanStack Query handle retries
|
|
171
|
+
*/
|
|
172
|
+
declare function createMutationFn(options: CreateMutationFnOptions): (request: any) => Promise<any>;
|
|
173
|
+
|
|
174
|
+
export { type CreateInfiniteQueryFnOptions, type CreateMutationFnOptions, type CreateQueryFnOptions, type DecorateRouterKeyable, type DistributiveOmit, type Fn, type QueryType, type TuyauMutationKey, type TuyauQueryBaseOptions, type TuyauQueryKey, type TuyauRequestOptions, type WithRequired, buildKey, createInfiniteQueryFn, createMutationFn, createQueryFn, extractKyOptions, getMutationKeyInternal, invoke, isObject, segmentsToRouteName, toSnakeCase };
|
package/build/index.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
import { skipToken } from "@tanstack/query-core";
|
|
3
|
+
function isObject(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function invoke(fn) {
|
|
7
|
+
return fn?.();
|
|
8
|
+
}
|
|
9
|
+
function buildKey(opts) {
|
|
10
|
+
const { segments, request, type } = opts;
|
|
11
|
+
const splitPath = segments.flatMap((part) => part.split("."));
|
|
12
|
+
if (!request && type === "any") {
|
|
13
|
+
return splitPath.length ? [splitPath] : [];
|
|
14
|
+
}
|
|
15
|
+
if (type === "infinite" && isObject(request)) {
|
|
16
|
+
const query = request.query;
|
|
17
|
+
const body = request.body;
|
|
18
|
+
const hasCursorInQuery = isObject(query) && ("direction" in query || "cursor" in query);
|
|
19
|
+
const hasCursorInBody = isObject(body) && ("direction" in body || "cursor" in body);
|
|
20
|
+
if (hasCursorInQuery || hasCursorInBody) {
|
|
21
|
+
const cleanedRequest = { ...request };
|
|
22
|
+
if (hasCursorInQuery && isObject(query)) {
|
|
23
|
+
const { cursor: _, direction: __, ...cleanQuery } = query;
|
|
24
|
+
cleanedRequest.query = cleanQuery;
|
|
25
|
+
}
|
|
26
|
+
if (hasCursorInBody && isObject(body)) {
|
|
27
|
+
const { cursor: _, direction: __, ...cleanBody } = body;
|
|
28
|
+
cleanedRequest.body = cleanBody;
|
|
29
|
+
}
|
|
30
|
+
return [splitPath, { request: cleanedRequest, type: "infinite" }];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return [
|
|
34
|
+
splitPath,
|
|
35
|
+
{
|
|
36
|
+
...typeof request !== "undefined" && request !== skipToken && { request },
|
|
37
|
+
...type && type !== "any" && { type }
|
|
38
|
+
}
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
function extractKyOptions(tuyauOpts) {
|
|
42
|
+
if (!tuyauOpts) return {};
|
|
43
|
+
const { abortOnUnmount, ...kyOptions } = tuyauOpts;
|
|
44
|
+
return kyOptions;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/helpers.ts
|
|
48
|
+
function toSnakeCase(str) {
|
|
49
|
+
return str.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
|
50
|
+
}
|
|
51
|
+
function segmentsToRouteName(segments) {
|
|
52
|
+
return segments.map(toSnakeCase).join(".");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/query_fn.ts
|
|
56
|
+
import { skipToken as skipToken2 } from "@tanstack/query-core";
|
|
57
|
+
function createQueryFn(options) {
|
|
58
|
+
const { request, routeName, opts, client, globalOptions } = options;
|
|
59
|
+
return invoke(() => {
|
|
60
|
+
if (request === skipToken2) return skipToken2;
|
|
61
|
+
return async (queryFnContext) => {
|
|
62
|
+
const effectiveAbortOnUnmount = opts?.tuyau?.abortOnUnmount ?? globalOptions?.abortOnUnmount ?? false;
|
|
63
|
+
const kyOptions = extractKyOptions(opts?.tuyau);
|
|
64
|
+
return await client.request(routeName, {
|
|
65
|
+
...request,
|
|
66
|
+
...kyOptions,
|
|
67
|
+
retry: 0,
|
|
68
|
+
...effectiveAbortOnUnmount ? { signal: queryFnContext.signal } : {}
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/infinite_query_fn.ts
|
|
75
|
+
import { skipToken as skipToken3 } from "@tanstack/query-core";
|
|
76
|
+
function createInfiniteQueryFn(options) {
|
|
77
|
+
const { request, routeName, opts, client, globalOptions } = options;
|
|
78
|
+
return invoke(() => {
|
|
79
|
+
if (request === skipToken3) return skipToken3;
|
|
80
|
+
return async (queryFnContext) => {
|
|
81
|
+
const { pageParam } = queryFnContext;
|
|
82
|
+
const effectiveAbortOnUnmount = opts?.tuyau?.abortOnUnmount ?? globalOptions?.abortOnUnmount ?? false;
|
|
83
|
+
const kyOptions = extractKyOptions(opts?.tuyau);
|
|
84
|
+
const pageParamKey = opts?.pageParamKey || "page";
|
|
85
|
+
let requestArgs;
|
|
86
|
+
if (isObject(request)) {
|
|
87
|
+
if (pageParam !== void 0) {
|
|
88
|
+
requestArgs = {
|
|
89
|
+
...request,
|
|
90
|
+
query: { ...request.query, [pageParamKey]: pageParam }
|
|
91
|
+
};
|
|
92
|
+
} else {
|
|
93
|
+
requestArgs = request;
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
requestArgs = { query: pageParam !== void 0 ? { [pageParamKey]: pageParam } : {} };
|
|
97
|
+
}
|
|
98
|
+
return await client.request(routeName, {
|
|
99
|
+
...requestArgs,
|
|
100
|
+
...kyOptions,
|
|
101
|
+
retry: 0,
|
|
102
|
+
...effectiveAbortOnUnmount ? { signal: queryFnContext.signal } : {}
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/mutation_fn.ts
|
|
109
|
+
function getMutationKeyInternal(options) {
|
|
110
|
+
const key = [options.segments.flatMap((part) => part.split("."))];
|
|
111
|
+
return key;
|
|
112
|
+
}
|
|
113
|
+
function createMutationFn(options) {
|
|
114
|
+
return async (request) => {
|
|
115
|
+
const kyOptions = extractKyOptions(options.opts?.tuyau);
|
|
116
|
+
const requestArgs = request || {};
|
|
117
|
+
return await options.client.request(options.routeName, {
|
|
118
|
+
...requestArgs,
|
|
119
|
+
...kyOptions,
|
|
120
|
+
retry: 0
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export {
|
|
125
|
+
buildKey,
|
|
126
|
+
createInfiniteQueryFn,
|
|
127
|
+
createMutationFn,
|
|
128
|
+
createQueryFn,
|
|
129
|
+
extractKyOptions,
|
|
130
|
+
getMutationKeyInternal,
|
|
131
|
+
invoke,
|
|
132
|
+
isObject,
|
|
133
|
+
segmentsToRouteName,
|
|
134
|
+
toSnakeCase
|
|
135
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tuyau/query-core",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.1.0",
|
|
5
|
+
"description": "Shared utilities for Tuyau Tanstack Query integrations",
|
|
6
|
+
"author": "Julien Ripouteau <julien@ripouteau.com>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/Julien-R44/tuyau#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/Julien-R44/tuyau.git",
|
|
12
|
+
"directory": "packages/query-core"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/Julien-R44/tuyau/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"adonisjs",
|
|
19
|
+
"typescript",
|
|
20
|
+
"typesafe",
|
|
21
|
+
"tanstack-query",
|
|
22
|
+
"tuyau"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": "./build/index.js"
|
|
26
|
+
},
|
|
27
|
+
"main": "build/index.js",
|
|
28
|
+
"files": [
|
|
29
|
+
"build"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=24.0.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@tanstack/query-core": "^5.74.7",
|
|
36
|
+
"@tuyau/core": "^1.0.0-beta.9"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@tanstack/query-core": "^5.74.7",
|
|
40
|
+
"@tuyau/core": "1.2.1"
|
|
41
|
+
},
|
|
42
|
+
"tsup": {
|
|
43
|
+
"entry": [
|
|
44
|
+
"./src/index.ts"
|
|
45
|
+
],
|
|
46
|
+
"outDir": "./build",
|
|
47
|
+
"clean": true,
|
|
48
|
+
"format": "esm",
|
|
49
|
+
"dts": true,
|
|
50
|
+
"target": "esnext"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public",
|
|
54
|
+
"tag": "latest"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"lint": "oxlint .",
|
|
58
|
+
"typecheck": "tsc --noEmit",
|
|
59
|
+
"build": "tsup-node",
|
|
60
|
+
"test": "pnpm quick:test",
|
|
61
|
+
"quick:test": "node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts --force-exit",
|
|
62
|
+
"checks": "pnpm lint && pnpm typecheck"
|
|
63
|
+
}
|
|
64
|
+
}
|