eden-tanstack-query 0.0.9 → 0.1.0-alpha.1
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 +69 -310
- package/dist/index.d.mts +84 -0
- package/dist/index.d.ts +75 -103
- package/dist/index.js +1 -226
- package/dist/index.mjs +1 -0
- package/package.json +59 -40
- package/src/index.ts +217 -0
- package/src/types.ts +178 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import type { Elysia } from 'elysia'
|
|
2
|
+
import type { QueryKey, QueryClient } from '@tanstack/query-core'
|
|
3
|
+
import { treaty, type Treaty } from '@elysiajs/eden/treaty2'
|
|
4
|
+
import type { EdenTQ, EdenQueryOptions, EdenMutationOptions } from './types'
|
|
5
|
+
|
|
6
|
+
const HTTP_METHODS = [
|
|
7
|
+
'get',
|
|
8
|
+
'post',
|
|
9
|
+
'put',
|
|
10
|
+
'delete',
|
|
11
|
+
'patch',
|
|
12
|
+
'options',
|
|
13
|
+
'head'
|
|
14
|
+
] as const
|
|
15
|
+
|
|
16
|
+
type HttpMethod = (typeof HTTP_METHODS)[number]
|
|
17
|
+
|
|
18
|
+
function isHttpMethod(value: string): value is HttpMethod {
|
|
19
|
+
return HTTP_METHODS.includes(value as HttpMethod)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function materializePath(
|
|
23
|
+
paths: string[],
|
|
24
|
+
params?: Record<string, string | number>
|
|
25
|
+
): string[] {
|
|
26
|
+
const result: string[] = []
|
|
27
|
+
|
|
28
|
+
for (const segment of paths) {
|
|
29
|
+
const match = /^:(.+?)(\?)?$/.exec(segment)
|
|
30
|
+
if (!match) {
|
|
31
|
+
result.push(segment)
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const paramName = match[1]
|
|
36
|
+
const isOptional = !!match[2]
|
|
37
|
+
const value = params?.[paramName]
|
|
38
|
+
|
|
39
|
+
if (value == null) {
|
|
40
|
+
if (isOptional) continue
|
|
41
|
+
throw new Error(`Missing required route parameter: "${paramName}"`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
result.push(String(value))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return result
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function buildQueryKey(
|
|
51
|
+
prefix: QueryKey,
|
|
52
|
+
method: string,
|
|
53
|
+
pathTemplate: string[],
|
|
54
|
+
input?: { params?: unknown; query?: unknown }
|
|
55
|
+
): QueryKey {
|
|
56
|
+
return [
|
|
57
|
+
...prefix,
|
|
58
|
+
method,
|
|
59
|
+
pathTemplate,
|
|
60
|
+
input?.params ?? null,
|
|
61
|
+
input?.query ?? null
|
|
62
|
+
] as const
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function callTreaty(
|
|
66
|
+
raw: any,
|
|
67
|
+
segments: string[],
|
|
68
|
+
method: string,
|
|
69
|
+
input?: { body?: unknown; query?: unknown; headers?: unknown; fetch?: RequestInit }
|
|
70
|
+
): Promise<Treaty.TreatyResponse<any>> {
|
|
71
|
+
let current = raw
|
|
72
|
+
|
|
73
|
+
for (const segment of segments) {
|
|
74
|
+
current = current[segment]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const options: Record<string, unknown> = {}
|
|
78
|
+
if (input?.query !== undefined) options.query = input.query
|
|
79
|
+
if (input?.headers !== undefined) options.headers = input.headers
|
|
80
|
+
if (input?.fetch !== undefined) Object.assign(options, input.fetch)
|
|
81
|
+
|
|
82
|
+
if (method === 'get' || method === 'head') {
|
|
83
|
+
return current[method](Object.keys(options).length > 0 ? options : undefined)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return current[method](
|
|
87
|
+
input?.body,
|
|
88
|
+
Object.keys(options).length > 0 ? options : undefined
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function dataOrThrow<T>(
|
|
93
|
+
promise: Promise<Treaty.TreatyResponse<any>>
|
|
94
|
+
): Promise<T> {
|
|
95
|
+
const result = await promise
|
|
96
|
+
if (result.error) throw result.error
|
|
97
|
+
return result.data as T
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface ProxyContext {
|
|
101
|
+
raw: any
|
|
102
|
+
prefix: QueryKey
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createMethodDecorator(
|
|
106
|
+
ctx: ProxyContext,
|
|
107
|
+
paths: string[],
|
|
108
|
+
method: string
|
|
109
|
+
) {
|
|
110
|
+
const pathTemplate = [...paths]
|
|
111
|
+
|
|
112
|
+
const fn = (
|
|
113
|
+
input?: { params?: Record<string, string | number>; body?: unknown; query?: unknown; headers?: unknown; fetch?: RequestInit }
|
|
114
|
+
) => {
|
|
115
|
+
const materializedPath = materializePath(pathTemplate, input?.params)
|
|
116
|
+
return callTreaty(ctx.raw, materializedPath, method, input)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn.queryKey = (
|
|
120
|
+
input?: { params?: Record<string, string | number>; query?: unknown }
|
|
121
|
+
): QueryKey => {
|
|
122
|
+
return buildQueryKey(ctx.prefix, method, pathTemplate, input)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fn.queryOptions = <TData = unknown>(
|
|
126
|
+
input: { params?: Record<string, string | number>; query?: unknown; headers?: unknown; fetch?: RequestInit },
|
|
127
|
+
overrides?: Partial<EdenQueryOptions<TData>>
|
|
128
|
+
): EdenQueryOptions<TData> => {
|
|
129
|
+
return {
|
|
130
|
+
queryKey: fn.queryKey(input),
|
|
131
|
+
queryFn: () => {
|
|
132
|
+
const materializedPath = materializePath(pathTemplate, input?.params)
|
|
133
|
+
return dataOrThrow(callTreaty(ctx.raw, materializedPath, method, input))
|
|
134
|
+
},
|
|
135
|
+
...overrides
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fn.mutationKey = (
|
|
140
|
+
input?: { params?: Record<string, string | number>; query?: unknown }
|
|
141
|
+
): QueryKey => {
|
|
142
|
+
return buildQueryKey(ctx.prefix, method, pathTemplate, input)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
fn.mutationOptions = <TData = unknown, TVariables = unknown>(
|
|
146
|
+
overrides?: Partial<EdenMutationOptions<TData, unknown, TVariables>>
|
|
147
|
+
): EdenMutationOptions<TData, unknown, TVariables> => {
|
|
148
|
+
return {
|
|
149
|
+
mutationKey: [...ctx.prefix, method, pathTemplate],
|
|
150
|
+
mutationFn: (variables: TVariables) => {
|
|
151
|
+
const vars = variables as { params?: Record<string, string | number>; body?: unknown; query?: unknown; headers?: unknown; fetch?: RequestInit }
|
|
152
|
+
const materializedPath = materializePath(pathTemplate, vars?.params)
|
|
153
|
+
return dataOrThrow(callTreaty(ctx.raw, materializedPath, method, vars))
|
|
154
|
+
},
|
|
155
|
+
...overrides
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
fn.invalidate = async (
|
|
160
|
+
queryClient: QueryClient,
|
|
161
|
+
input?: { params?: Record<string, string | number>; query?: unknown },
|
|
162
|
+
exact = false
|
|
163
|
+
): Promise<void> => {
|
|
164
|
+
const queryKey = input
|
|
165
|
+
? fn.queryKey(input)
|
|
166
|
+
: [...ctx.prefix, method, pathTemplate]
|
|
167
|
+
await queryClient.invalidateQueries({ queryKey, exact })
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return fn
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function createEdenTQProxy(
|
|
174
|
+
ctx: ProxyContext,
|
|
175
|
+
paths: string[] = []
|
|
176
|
+
): any {
|
|
177
|
+
return new Proxy(() => {}, {
|
|
178
|
+
get(_, prop: string): any {
|
|
179
|
+
if (isHttpMethod(prop)) {
|
|
180
|
+
return createMethodDecorator(ctx, paths, prop)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return createEdenTQProxy(
|
|
184
|
+
ctx,
|
|
185
|
+
prop === 'index' ? paths : [...paths, prop]
|
|
186
|
+
)
|
|
187
|
+
},
|
|
188
|
+
apply(_, __, [body]) {
|
|
189
|
+
if (typeof body === 'object' && body !== null) {
|
|
190
|
+
const paramValue = Object.values(body)[0] as string
|
|
191
|
+
return createEdenTQProxy(ctx, [...paths, paramValue])
|
|
192
|
+
}
|
|
193
|
+
return createEdenTQProxy(ctx, paths)
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function createEdenTQ<
|
|
199
|
+
const App extends Elysia<any, any, any, any, any, any, any>
|
|
200
|
+
>(
|
|
201
|
+
domain: string | App,
|
|
202
|
+
config: EdenTQ.Config = {}
|
|
203
|
+
): EdenTQ.Create<App> {
|
|
204
|
+
const { queryKeyPrefix = ['eden'], ...treatyConfig } = config
|
|
205
|
+
|
|
206
|
+
const raw = treaty<App>(domain as any, treatyConfig)
|
|
207
|
+
|
|
208
|
+
const ctx: ProxyContext = {
|
|
209
|
+
raw,
|
|
210
|
+
prefix: queryKeyPrefix
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return createEdenTQProxy(ctx) as EdenTQ.Create<App>
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export type { EdenTQ, EdenQueryOptions, EdenMutationOptions }
|
|
217
|
+
export type { QueryKey, QueryClient } from '@tanstack/query-core'
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { Elysia, ELYSIA_FORM_DATA } from 'elysia'
|
|
2
|
+
import type { Treaty } from '@elysiajs/eden/treaty2'
|
|
3
|
+
import type { QueryKey, QueryClient } from '@tanstack/query-core'
|
|
4
|
+
|
|
5
|
+
type IsNever<T> = [T] extends [never] ? true : false
|
|
6
|
+
|
|
7
|
+
type Prettify<T> = {
|
|
8
|
+
[K in keyof T]: T[K]
|
|
9
|
+
} & {}
|
|
10
|
+
|
|
11
|
+
type Enumerate<
|
|
12
|
+
N extends number,
|
|
13
|
+
Acc extends number[] = []
|
|
14
|
+
> = Acc['length'] extends N
|
|
15
|
+
? Acc[number]
|
|
16
|
+
: Enumerate<N, [...Acc, Acc['length']]>
|
|
17
|
+
|
|
18
|
+
type IntegerRange<F extends number, T extends number> = Exclude<
|
|
19
|
+
Enumerate<T>,
|
|
20
|
+
Enumerate<F>
|
|
21
|
+
>
|
|
22
|
+
|
|
23
|
+
type SuccessCodeRange = IntegerRange<200, 300>
|
|
24
|
+
|
|
25
|
+
type ExtractData<Res extends Record<number, unknown>> =
|
|
26
|
+
Res[Extract<keyof Res, SuccessCodeRange>] extends {
|
|
27
|
+
[ELYSIA_FORM_DATA]: infer Data
|
|
28
|
+
}
|
|
29
|
+
? Data
|
|
30
|
+
: Res[Extract<keyof Res, SuccessCodeRange>]
|
|
31
|
+
|
|
32
|
+
type ExtractError<Res extends Record<number, unknown>> = Exclude<
|
|
33
|
+
keyof Res,
|
|
34
|
+
SuccessCodeRange
|
|
35
|
+
> extends never
|
|
36
|
+
? { status: unknown; value: unknown }
|
|
37
|
+
: {
|
|
38
|
+
[Status in keyof Res]: {
|
|
39
|
+
status: Status
|
|
40
|
+
value: Res[Status] extends { [ELYSIA_FORM_DATA]: infer Data }
|
|
41
|
+
? Data
|
|
42
|
+
: Res[Status]
|
|
43
|
+
}
|
|
44
|
+
}[Exclude<keyof Res, SuccessCodeRange>]
|
|
45
|
+
|
|
46
|
+
interface TQParamBase {
|
|
47
|
+
fetch?: RequestInit
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type SerializeQueryParams<T> = T extends Record<string, any>
|
|
51
|
+
? {
|
|
52
|
+
[K in keyof T]: T[K] extends Date
|
|
53
|
+
? string
|
|
54
|
+
: T[K] extends Date | undefined
|
|
55
|
+
? string | undefined
|
|
56
|
+
: T[K]
|
|
57
|
+
}
|
|
58
|
+
: T
|
|
59
|
+
|
|
60
|
+
type IsEmptyObject<T> = T extends Record<string, never>
|
|
61
|
+
? [keyof T] extends [never]
|
|
62
|
+
? true
|
|
63
|
+
: false
|
|
64
|
+
: false
|
|
65
|
+
|
|
66
|
+
type MaybeEmptyObject<T, K extends PropertyKey> = [T] extends [never]
|
|
67
|
+
? {}
|
|
68
|
+
: [T] extends [undefined]
|
|
69
|
+
? { [P in K]?: T }
|
|
70
|
+
: IsEmptyObject<T> extends true
|
|
71
|
+
? { [P in K]?: T }
|
|
72
|
+
: undefined extends T
|
|
73
|
+
? { [P in K]?: T }
|
|
74
|
+
: { [P in K]: T }
|
|
75
|
+
|
|
76
|
+
type TQMethodParam<
|
|
77
|
+
Body,
|
|
78
|
+
Headers,
|
|
79
|
+
Query,
|
|
80
|
+
Params
|
|
81
|
+
> = MaybeEmptyObject<Headers, 'headers'> &
|
|
82
|
+
MaybeEmptyObject<SerializeQueryParams<Query>, 'query'> &
|
|
83
|
+
MaybeEmptyObject<Params, 'params'> &
|
|
84
|
+
MaybeEmptyObject<Body, 'body'> &
|
|
85
|
+
TQParamBase
|
|
86
|
+
|
|
87
|
+
export interface EdenQueryOptions<TData = unknown, TError = unknown> {
|
|
88
|
+
queryKey: QueryKey
|
|
89
|
+
queryFn: () => Promise<TData>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface EdenMutationOptions<
|
|
93
|
+
TData = unknown,
|
|
94
|
+
TError = unknown,
|
|
95
|
+
TVariables = unknown
|
|
96
|
+
> {
|
|
97
|
+
mutationKey: QueryKey
|
|
98
|
+
mutationFn: (variables: TVariables) => Promise<TData>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface EdenTQMethod<
|
|
102
|
+
Body,
|
|
103
|
+
Headers,
|
|
104
|
+
Query,
|
|
105
|
+
Params,
|
|
106
|
+
Res extends Record<number, unknown>
|
|
107
|
+
> {
|
|
108
|
+
<TQueryFnData = ExtractData<Res>>(
|
|
109
|
+
input: TQMethodParam<Body, Headers, Query, Params>,
|
|
110
|
+
options?: RequestInit
|
|
111
|
+
): Promise<Treaty.TreatyResponse<Res>>
|
|
112
|
+
|
|
113
|
+
queryKey(input?: TQMethodParam<Body, Headers, Query, Params>): QueryKey
|
|
114
|
+
|
|
115
|
+
queryOptions<TData = ExtractData<Res>>(
|
|
116
|
+
input: TQMethodParam<Body, Headers, Query, Params>,
|
|
117
|
+
overrides?: Partial<EdenQueryOptions<TData, ExtractError<Res>>>
|
|
118
|
+
): EdenQueryOptions<TData, ExtractError<Res>>
|
|
119
|
+
|
|
120
|
+
mutationKey(input?: TQMethodParam<Body, Headers, Query, Params>): QueryKey
|
|
121
|
+
|
|
122
|
+
mutationOptions<TData = ExtractData<Res>>(
|
|
123
|
+
overrides?: Partial<EdenMutationOptions<TData, ExtractError<Res>, TQMethodParam<Body, Headers, Query, Params>>>
|
|
124
|
+
): EdenMutationOptions<TData, ExtractError<Res>, TQMethodParam<Body, Headers, Query, Params>>
|
|
125
|
+
|
|
126
|
+
invalidate(
|
|
127
|
+
queryClient: QueryClient,
|
|
128
|
+
input?: TQMethodParam<Body, Headers, Query, Params>,
|
|
129
|
+
exact?: boolean
|
|
130
|
+
): Promise<void>
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export namespace EdenTQ {
|
|
134
|
+
export type Config = Treaty.Config & {
|
|
135
|
+
queryKeyPrefix?: QueryKey
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export type Create<App extends Elysia<any, any, any, any, any, any, any>> =
|
|
139
|
+
App extends {
|
|
140
|
+
'~Routes': infer Schema extends Record<any, any>
|
|
141
|
+
}
|
|
142
|
+
? Prettify<Sign<Schema>> & CreateParams<Schema>
|
|
143
|
+
: 'Please install Elysia before using Eden'
|
|
144
|
+
|
|
145
|
+
export type Sign<in out Route extends Record<any, any>> = {
|
|
146
|
+
[K in keyof Route as K extends `:${string}`
|
|
147
|
+
? never
|
|
148
|
+
: K]: Route[K] extends {
|
|
149
|
+
body: infer Body
|
|
150
|
+
headers: infer Headers
|
|
151
|
+
params: infer Params
|
|
152
|
+
query: infer Query
|
|
153
|
+
response: infer Res extends Record<number, unknown>
|
|
154
|
+
}
|
|
155
|
+
? EdenTQMethod<Body, Headers, Query, Params, Res>
|
|
156
|
+
: CreateParams<Route[K]>
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
type CreateParams<Route extends Record<string, any>> =
|
|
160
|
+
Extract<keyof Route, `:${string}`> extends infer Path extends string
|
|
161
|
+
? IsNever<Path> extends true
|
|
162
|
+
? Prettify<Sign<Route>>
|
|
163
|
+
: (((params: {
|
|
164
|
+
[param in Path extends `:${infer Param}`
|
|
165
|
+
? Param extends `${infer P}?`
|
|
166
|
+
? P
|
|
167
|
+
: Param
|
|
168
|
+
: never]: string | number
|
|
169
|
+
}) => Prettify<Sign<Route[Path]>> &
|
|
170
|
+
CreateParams<Route[Path]>) &
|
|
171
|
+
Prettify<Sign<Route>>) &
|
|
172
|
+
(Path extends `:${string}?`
|
|
173
|
+
? CreateParams<Route[Path]>
|
|
174
|
+
: {})
|
|
175
|
+
: never
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export type { QueryKey, QueryClient }
|