@tiesen/effect-tanstack-query 0.0.2 → 0.0.3
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/dist/index.d.mts +68 -0
- package/dist/index.mjs +1 -0
- package/package.json +4 -1
- package/.github/workflows/ci.yml +0 -37
- package/.github/workflows/release.yml +0 -26
- package/.github/workflows/setup/action.yml +0 -15
- package/.tegami/publish-lock.yaml +0 -11
- package/CHANGELOG.md +0 -21
- package/examples/client.ts +0 -36
- package/examples/contract.ts +0 -28
- package/examples/server.ts +0 -27
- package/oxfmt.config.ts +0 -38
- package/oxlint.config.ts +0 -124
- package/src/index.ts +0 -137
- package/src/types.ts +0 -93
- package/tegami.ts +0 -27
- package/tsconfig.json +0 -30
- package/tsdown.config.ts +0 -8
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Service } from "effect/Context";
|
|
2
|
+
import { ManagedRuntime } from "effect/ManagedRuntime";
|
|
3
|
+
import { MutationOptions, QueryOptions } from "@tanstack/query-core";
|
|
4
|
+
import { Schema } from "effect/Schema";
|
|
5
|
+
import { Client } from "effect/unstable/httpapi/HttpApiClient";
|
|
6
|
+
import { HttpApiEndpoint } from "effect/unstable/httpapi/HttpApiEndpoint";
|
|
7
|
+
//#region src/types.d.ts
|
|
8
|
+
type TanstackQueryOptionsProxy<T> = T extends Client.Method<HttpApiEndpoint<infer _Key, infer Method, infer _Path, infer Params, infer Query, infer Payload, infer Headers, infer Success, infer Error>, infer _Error, infer _Requires> ? {
|
|
9
|
+
queryOptions: Method extends 'GET' ? <TQuery = QueryOptions<UnwrapCodec<Success>, UnwrapCodec<Error>>>(input: MakeOptionalInput<([Params] extends [never] ? {} : {
|
|
10
|
+
params: UnwrapCodec<Params>;
|
|
11
|
+
}) & ([Query] extends [never] ? {} : {
|
|
12
|
+
query: UnwrapCodec<Query>;
|
|
13
|
+
}) & ([Headers] extends [never] ? {} : {
|
|
14
|
+
headers: UnwrapCodec<Headers>;
|
|
15
|
+
})>, options?: Omit<TQuery, 'queryKey' | 'queryFn'>) => TQuery : never;
|
|
16
|
+
mutationOptions: Method extends 'GET' ? never : <TMutation = MutationOptions<UnwrapCodec<Success>, UnwrapCodec<Error>, UnwrapCodec<Payload>>>(input: MakeOptionalInput<([Params] extends [never] ? {} : {
|
|
17
|
+
params: UnwrapCodec<Params>;
|
|
18
|
+
}) & ([Headers] extends [never] ? {} : {
|
|
19
|
+
headers: UnwrapCodec<Headers>;
|
|
20
|
+
})>, options?: Omit<TMutation, 'mutationKey' | 'mutationFn'>) => TMutation;
|
|
21
|
+
getQueryKey: Method extends 'GET' ? (input?: Partial<MakeOptionalInput<([Params] extends [never] ? {} : {
|
|
22
|
+
params: UnwrapCodec<Params>;
|
|
23
|
+
}) & ([Query] extends [never] ? {} : {
|
|
24
|
+
query: UnwrapCodec<Query>;
|
|
25
|
+
}) & ([Headers] extends [never] ? {} : {
|
|
26
|
+
headers: UnwrapCodec<Headers>;
|
|
27
|
+
})>>) => readonly unknown[] : never;
|
|
28
|
+
} : T extends object ? { readonly [K in keyof T]: TanstackQueryOptionsProxy<T[K]>; } : T;
|
|
29
|
+
type UnwrapCodec<T> = T extends Schema<unknown> ? Schema.Type<T> : T extends object ? { [K in keyof T]: UnwrapCodec<T[K]>; } : T;
|
|
30
|
+
type MakeOptionalInput<T> = keyof T extends never ? void | undefined : T;
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/index.d.ts
|
|
33
|
+
/**
|
|
34
|
+
* Creates a type-safe proxy that bridges Effect `HttpApiClient` endpoints
|
|
35
|
+
* with TanStack Query options (`queryOptions`, `mutationOptions`, and `getQueryKey`).
|
|
36
|
+
*
|
|
37
|
+
* @param tag - The Effect `Service` tag used to locate the client implementation within the Context.
|
|
38
|
+
* @param runtime - The Effect `ManagedRuntime` used to execute effects as promises.
|
|
39
|
+
*
|
|
40
|
+
* @returns A proxy object structured like the target API client, providing TanStack Query helpers for each endpoint.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* class ApiGroup extends HttpApiGroup.make('ApiGroup')
|
|
45
|
+
* .add(HttpApiEndpoint.get('hello', '/hello', { success: Schema.String, query: Schema.Struct({ name: Schema.String }) }))
|
|
46
|
+
* .add(HttpApiEndpoint.post('bye', '/bye', { success: Schema.String, payload: Schema.Struct({ name: Schema.String }) })) {}
|
|
47
|
+
*
|
|
48
|
+
* class Api extends HttpApi.make('Api').add(ApiGroup) {}
|
|
49
|
+
*
|
|
50
|
+
* class ApiClient extends Context.Service<ApiClient, HttpApiClient.ForApi<typeof Api>>()('ApiClient') {
|
|
51
|
+
* public static live = Layer.effect(this, HttpApiClient.make(Api)).pipe(Layer.provide(FetchHttpClient.layer))
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* const runtime = ManagedRuntime.make(ApiClient.live)
|
|
55
|
+
* const api = createTanstackQueryOptionsProxy(ApiClient, runtime);
|
|
56
|
+
*
|
|
57
|
+
* // Example usage in a React component with TanStack Query
|
|
58
|
+
* const { data } = useQuery(api.hello.queryOptions({ query: { name: 'World' } }))
|
|
59
|
+
* // ^? const data: string = "Hello, World!"
|
|
60
|
+
*
|
|
61
|
+
* const { mutate, data } = useMutation(api.bye.mutationOptions())
|
|
62
|
+
* // ^? const data: string = "Goodbye, World!"
|
|
63
|
+
* mutate({ name: 'World' })
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare function createTanstackQueryOptionsProxy<TServiceTag, TService>(tag: Service<TServiceTag, TService>, runtime: ManagedRuntime<TServiceTag, never>): TanstackQueryOptionsProxy<TService>;
|
|
67
|
+
//#endregion
|
|
68
|
+
export { type TanstackQueryOptionsProxy, createTanstackQueryOptionsProxy };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as e from"effect/Effect";function t(t,n){let r=new Map,i=a=>{let o=a.join(`.`);if(r.has(o))return r.get(o);let s=new Proxy(()=>{},{get(e,t){if(typeof t!=`symbol`&&t!==`then`)return i([...a,String(t)])},apply(r,i,o){let s=a.at(-1),c=a.slice(0,-1),[l,u]=o,d=(r,i)=>n.runPromise(e.gen(function*(){let e=yield*t;for(let t of c)e=e[t];return yield*e(r)}),{signal:i}),f=(e,t)=>[{type:e},...c,...t?[t]:[]];if(s===`queryOptions`)return{...u,queryKey:f(`query`,l),queryFn:({signal:e})=>d(l,e)};if(s===`getQueryKey`)return f(`query`,l);if(s===`mutationOptions`)return{...u,mutationKey:f(`mutation`,l),mutationFn:e=>d({payload:e})}}});return r.set(o,s),s};return i([])}export{t as createTanstackQueryOptionsProxy};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiesen/effect-tanstack-query",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect",
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"type": "git",
|
|
13
13
|
"url": "git+https://github.com/tiesen243/effect-tanstack-query.git"
|
|
14
14
|
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
15
18
|
"type": "module",
|
|
16
19
|
"exports": {
|
|
17
20
|
".": {
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
branches: ['*']
|
|
6
|
-
|
|
7
|
-
concurrency:
|
|
8
|
-
group: ${{ github.workflow }}-${{ github.ref }}
|
|
9
|
-
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
|
10
|
-
|
|
11
|
-
env:
|
|
12
|
-
FORCE_COLOR: 3
|
|
13
|
-
|
|
14
|
-
jobs:
|
|
15
|
-
format:
|
|
16
|
-
runs-on: ubuntu-latest
|
|
17
|
-
steps:
|
|
18
|
-
- uses: actions/checkout@v6
|
|
19
|
-
- uses: ./.github/workflows/setup
|
|
20
|
-
- name: Format
|
|
21
|
-
run: bun format
|
|
22
|
-
|
|
23
|
-
lint:
|
|
24
|
-
runs-on: ubuntu-latest
|
|
25
|
-
steps:
|
|
26
|
-
- uses: actions/checkout@v6
|
|
27
|
-
- uses: ./.github/workflows/setup
|
|
28
|
-
- name: Lint
|
|
29
|
-
run: bun lint
|
|
30
|
-
|
|
31
|
-
typecheck:
|
|
32
|
-
runs-on: ubuntu-latest
|
|
33
|
-
steps:
|
|
34
|
-
- uses: actions/checkout@v6
|
|
35
|
-
- uses: ./.github/workflows/setup
|
|
36
|
-
- name: Typecheck
|
|
37
|
-
run: bun typecheck
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
name: Release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
env:
|
|
9
|
-
FORCE_COLOR: 3
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
publish:
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
permissions:
|
|
15
|
-
contents: write
|
|
16
|
-
id-token: write
|
|
17
|
-
pull-requests: write
|
|
18
|
-
steps:
|
|
19
|
-
- uses: actions/checkout@v6
|
|
20
|
-
- uses: ./.github/workflows/setup
|
|
21
|
-
|
|
22
|
-
- name: Version & publish packages
|
|
23
|
-
run: bun tegami ci
|
|
24
|
-
env:
|
|
25
|
-
GITHUB_TOKEN: ${{ github.token }}
|
|
26
|
-
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
name: 'Setup Project'
|
|
2
|
-
description: 'Checkouts repo, sets up bun and installs dependencies'
|
|
3
|
-
runs:
|
|
4
|
-
using: 'composite'
|
|
5
|
-
steps:
|
|
6
|
-
- name: Setup Bun
|
|
7
|
-
uses: oven-sh/setup-bun@v2
|
|
8
|
-
|
|
9
|
-
- name: Install Dependencies
|
|
10
|
-
shell: bash
|
|
11
|
-
run: bun ci
|
|
12
|
-
|
|
13
|
-
- name: Build Packages
|
|
14
|
-
shell: bash
|
|
15
|
-
run: bun run build
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
core:changelogs:
|
|
2
|
-
- content: "---\npackages:\n npm:@tiesen/effect-tanstack-query:\n type: patch\n---\n\n## Fix exports\n\nFix the `exports` field in `package.json` to correctly expose package entry points and improve module resolution.\n"
|
|
3
|
-
filename: 2026-07-28-ms4ep3t9.md
|
|
4
|
-
v: 0.0.0
|
|
5
|
-
core:packages:
|
|
6
|
-
- changelogIds:
|
|
7
|
-
- 2026-07-28-ms4ep3t9.md
|
|
8
|
-
id: npm:@tiesen/effect-tanstack-query
|
|
9
|
-
updated: true
|
|
10
|
-
npm:packages:
|
|
11
|
-
- id: npm:@tiesen/effect-tanstack-query
|
package/CHANGELOG.md
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
## @tiesen/effect-tanstack-query@0.0.2
|
|
2
|
-
|
|
3
|
-
### Fix exports
|
|
4
|
-
|
|
5
|
-
Fix the `exports` field in `package.json` to correctly expose package entry points and improve module resolution.
|
|
6
|
-
|
|
7
|
-
## @tiesen/effect-tanstack-query@0.0.1
|
|
8
|
-
|
|
9
|
-
### Initial Release
|
|
10
|
-
|
|
11
|
-
First official release of `effect-tanstack-query` — a type-safe bridge between **Effect HTTP API clients** and **TanStack Query**.
|
|
12
|
-
|
|
13
|
-
### Features
|
|
14
|
-
|
|
15
|
-
- **TanStack Query Options Proxy**: Automatically generate `queryOptions`, `mutationOptions`, and `getQueryKey` from Effect `HttpApiClient` endpoints using ES6 Proxies.
|
|
16
|
-
- **End-to-End Type Safety**:
|
|
17
|
-
- Full type inference for `params`, `query`, `headers`, and `payload` schema inputs.
|
|
18
|
-
- Automatic resolution of `Success` and `Error` response types.
|
|
19
|
-
- Unwrap Effect `Schema` types seamlessly for TanStack Query options.
|
|
20
|
-
- **Smart Arguments**: Supports optional input parameters when an endpoint requires no request parameters/payload.
|
|
21
|
-
- **Effect Runtime Execution**: Seamlessly executes Effect pipelines as Promises backed by `ManagedRuntime` and supports request cancellation via `AbortSignal`.
|
package/examples/client.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { QueryClient } from '@tanstack/query-core'
|
|
2
|
-
import { Context, Function, Layer, ManagedRuntime } from 'effect'
|
|
3
|
-
import {
|
|
4
|
-
FetchHttpClient,
|
|
5
|
-
HttpClient,
|
|
6
|
-
HttpClientRequest,
|
|
7
|
-
} from 'effect/unstable/http'
|
|
8
|
-
import { HttpApiClient } from 'effect/unstable/httpapi'
|
|
9
|
-
|
|
10
|
-
import { createTanstackQueryOptionsProxy } from '../dist/index.mjs'
|
|
11
|
-
import { Api } from './contract'
|
|
12
|
-
|
|
13
|
-
class ApiClient extends Context.Service<
|
|
14
|
-
ApiClient,
|
|
15
|
-
HttpApiClient.ForApi<typeof Api>
|
|
16
|
-
>()('ApiClient') {
|
|
17
|
-
public static live = Layer.effect(
|
|
18
|
-
this,
|
|
19
|
-
HttpApiClient.make(Api, {
|
|
20
|
-
transformClient: (client) =>
|
|
21
|
-
client.pipe(
|
|
22
|
-
HttpClient.mapRequest(
|
|
23
|
-
// oxlint-disable-next-line unicorn/max-nested-calls
|
|
24
|
-
Function.flow(HttpClientRequest.prependUrl('http://localhost:3000'))
|
|
25
|
-
)
|
|
26
|
-
),
|
|
27
|
-
})
|
|
28
|
-
).pipe(Layer.provide(FetchHttpClient.layer))
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const runtime = ManagedRuntime.make(ApiClient.live)
|
|
32
|
-
export const api = createTanstackQueryOptionsProxy(ApiClient, runtime)
|
|
33
|
-
|
|
34
|
-
new QueryClient().invalidateQueries({
|
|
35
|
-
queryKey: api.ApiGroup.hello.getQueryKey(),
|
|
36
|
-
})
|
package/examples/contract.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// oxlint-disable max-classes-per-file
|
|
2
|
-
|
|
3
|
-
import { Schema } from 'effect'
|
|
4
|
-
import { HttpApi, HttpApiEndpoint, HttpApiGroup } from 'effect/unstable/httpapi'
|
|
5
|
-
|
|
6
|
-
class ApiGroup extends HttpApiGroup.make('ApiGroup')
|
|
7
|
-
.add(
|
|
8
|
-
HttpApiEndpoint.get('hello', '/hello/:name', {
|
|
9
|
-
success: Schema.String,
|
|
10
|
-
params: Schema.Struct({
|
|
11
|
-
name: Schema.String,
|
|
12
|
-
}),
|
|
13
|
-
query: Schema.Struct({
|
|
14
|
-
// oxlint-disable-next-line unicorn/max-nested-calls
|
|
15
|
-
greeting: Schema.String.pipe(Schema.optionalKey),
|
|
16
|
-
}),
|
|
17
|
-
})
|
|
18
|
-
)
|
|
19
|
-
.add(
|
|
20
|
-
HttpApiEndpoint.post('goodbye', '/goodbye', {
|
|
21
|
-
success: Schema.String,
|
|
22
|
-
payload: Schema.Struct({
|
|
23
|
-
name: Schema.String,
|
|
24
|
-
}),
|
|
25
|
-
})
|
|
26
|
-
) {}
|
|
27
|
-
|
|
28
|
-
export class Api extends HttpApi.make('Api').add(ApiGroup) {}
|
package/examples/server.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { Effect, Layer } from 'effect'
|
|
2
|
-
import { HttpRouter, HttpServer } from 'effect/unstable/http'
|
|
3
|
-
import { HttpApiBuilder } from 'effect/unstable/httpapi'
|
|
4
|
-
|
|
5
|
-
import { Api } from './contract'
|
|
6
|
-
|
|
7
|
-
const ApiGroupLive = HttpApiBuilder.group(Api, 'ApiGroup', (handlers) =>
|
|
8
|
-
handlers
|
|
9
|
-
.handle('hello', ({ params, query }) =>
|
|
10
|
-
Effect.succeed(`${query.greeting ?? 'Hello'}, ${params.name}!`)
|
|
11
|
-
)
|
|
12
|
-
.handle('goodbye', ({ payload }) =>
|
|
13
|
-
Effect.succeed(`Goodbye, ${payload.name}!`)
|
|
14
|
-
)
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
const ApiLive = HttpApiBuilder.layer(Api, {
|
|
18
|
-
openapiPath: '/openapi.json',
|
|
19
|
-
}).pipe(
|
|
20
|
-
Layer.provide([ApiGroupLive]),
|
|
21
|
-
Layer.provide(HttpRouter.cors()),
|
|
22
|
-
Layer.provide(HttpServer.layerServices)
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
export default {
|
|
26
|
-
fetch: HttpRouter.toWebHandler(ApiLive).handler,
|
|
27
|
-
}
|
package/oxfmt.config.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'oxfmt'
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
printWidth: 80,
|
|
5
|
-
tabWidth: 2,
|
|
6
|
-
arrowParens: 'always',
|
|
7
|
-
bracketSameLine: false,
|
|
8
|
-
bracketSpacing: true,
|
|
9
|
-
endOfLine: 'lf',
|
|
10
|
-
quoteProps: 'as-needed',
|
|
11
|
-
semi: false,
|
|
12
|
-
singleQuote: true,
|
|
13
|
-
jsxSingleQuote: true,
|
|
14
|
-
trailingComma: 'es5',
|
|
15
|
-
useTabs: false,
|
|
16
|
-
|
|
17
|
-
sortImports: {
|
|
18
|
-
groups: [
|
|
19
|
-
'side_effect_style',
|
|
20
|
-
'type-import',
|
|
21
|
-
['value-builtin', 'value-external'],
|
|
22
|
-
'type-internal',
|
|
23
|
-
'value-internal',
|
|
24
|
-
['type-parent', 'type-sibling', 'type-index'],
|
|
25
|
-
['value-parent', 'value-sibling', 'value-index'],
|
|
26
|
-
],
|
|
27
|
-
newlinesBetween: true,
|
|
28
|
-
order: 'asc',
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
sortPackageJson: {
|
|
32
|
-
sortScripts: true,
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
sortTailwindcss: {
|
|
36
|
-
functions: ['clsx', 'cva', 'cn'],
|
|
37
|
-
},
|
|
38
|
-
})
|
package/oxlint.config.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'oxlint'
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
plugins: [
|
|
5
|
-
'eslint',
|
|
6
|
-
'typescript',
|
|
7
|
-
'unicorn',
|
|
8
|
-
'oxc',
|
|
9
|
-
'import',
|
|
10
|
-
'jsdoc',
|
|
11
|
-
'node',
|
|
12
|
-
'promise',
|
|
13
|
-
],
|
|
14
|
-
env: {
|
|
15
|
-
browser: true,
|
|
16
|
-
},
|
|
17
|
-
categories: {
|
|
18
|
-
correctness: 'error',
|
|
19
|
-
perf: 'error',
|
|
20
|
-
restriction: 'error',
|
|
21
|
-
suspicious: 'error',
|
|
22
|
-
pedantic: 'error',
|
|
23
|
-
style: 'error',
|
|
24
|
-
},
|
|
25
|
-
rules: {
|
|
26
|
-
'arrow-body-style': ['error', 'as-needed'],
|
|
27
|
-
'capitalized-comments': 'off',
|
|
28
|
-
curly: 'off',
|
|
29
|
-
'func-style': 'off',
|
|
30
|
-
'id-length': 'off',
|
|
31
|
-
'init-declarations': 'off',
|
|
32
|
-
'max-depth': 'off',
|
|
33
|
-
'max-lines': 'off',
|
|
34
|
-
'max-lines-per-function': 'off',
|
|
35
|
-
'max-params': 'off',
|
|
36
|
-
'max-statements': 'off',
|
|
37
|
-
'new-cap': 'off',
|
|
38
|
-
'no-await-in-loop': 'off',
|
|
39
|
-
'no-console': 'off',
|
|
40
|
-
'no-continue': 'off',
|
|
41
|
-
'no-duplicate-imports': ['error', { allowSeparateTypeImports: true }],
|
|
42
|
-
'no-implicit-coercion': 'off',
|
|
43
|
-
'no-inline-comments': 'off',
|
|
44
|
-
'no-magic-numbers': 'off',
|
|
45
|
-
'no-use-before-define': 'off',
|
|
46
|
-
'no-ternary': 'off',
|
|
47
|
-
'no-undefined': 'off',
|
|
48
|
-
// Avoid conflict with typescript/no-floating-promises
|
|
49
|
-
'no-void': ['error', { allowAsStatement: true }],
|
|
50
|
-
'sort-keys': 'off',
|
|
51
|
-
// Rely on oxfmt `sortImports` instead
|
|
52
|
-
'sort-imports': 'off',
|
|
53
|
-
|
|
54
|
-
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
|
55
|
-
'import/exports-last': 'off',
|
|
56
|
-
'import/extensions': 'off',
|
|
57
|
-
'import/group-exports': 'off',
|
|
58
|
-
'import/max-dependencies': 'off',
|
|
59
|
-
'import/no-anonymous-default-export': 'off',
|
|
60
|
-
'import/no-commonjs': 'off',
|
|
61
|
-
'import/no-default-export': 'off',
|
|
62
|
-
'import/no-dynamic-require': 'off',
|
|
63
|
-
'import/no-named-export': 'off',
|
|
64
|
-
'import/no-namespace': 'off',
|
|
65
|
-
'import/no-nodejs-modules': 'off',
|
|
66
|
-
'import/no-relative-parent-imports': 'off',
|
|
67
|
-
'import/no-unassigned-import': 'off',
|
|
68
|
-
'import/prefer-default-export': 'off',
|
|
69
|
-
'import/unambiguous': 'off',
|
|
70
|
-
|
|
71
|
-
'jsdoc/require-param': 'off',
|
|
72
|
-
'jsdoc/require-param-type': 'off',
|
|
73
|
-
'jsdoc/require-returns': 'off',
|
|
74
|
-
'jsdoc/require-returns-type': 'off',
|
|
75
|
-
|
|
76
|
-
'unicorn/explicit-length-check': 'off',
|
|
77
|
-
'unicorn/no-array-callback-reference': 'off',
|
|
78
|
-
'unicorn/no-null': 'off',
|
|
79
|
-
'unicorn/no-process-exit': 'off',
|
|
80
|
-
'unicorn/number-literal-case': 'off',
|
|
81
|
-
'unicorn/prefer-global-this': 'off',
|
|
82
|
-
'unicorn/prefer-string-raw': 'off',
|
|
83
|
-
'unicorn/prefer-top-level-await': 'off',
|
|
84
|
-
|
|
85
|
-
'typescript/explicit-function-return-type': 'off',
|
|
86
|
-
'typescript/explicit-module-boundary-types': 'off',
|
|
87
|
-
'typescript/no-require-imports': 'off',
|
|
88
|
-
'typescript/no-var-requires': 'off',
|
|
89
|
-
'typescript/no-namespace': 'off',
|
|
90
|
-
'typescript/require-await': 'off',
|
|
91
|
-
|
|
92
|
-
'node/no-process-env': 'off',
|
|
93
|
-
|
|
94
|
-
'oxc/no-async-await': 'off',
|
|
95
|
-
'oxc/no-map-spread': 'off',
|
|
96
|
-
'oxc/no-optional-chaining': 'off',
|
|
97
|
-
'oxc/no-rest-spread-properties': 'off',
|
|
98
|
-
|
|
99
|
-
'promise/always-return': 'off',
|
|
100
|
-
'promise/catch-or-return': 'off',
|
|
101
|
-
},
|
|
102
|
-
overrides: [
|
|
103
|
-
{
|
|
104
|
-
// Shared test file overrides — framework-specific test rules
|
|
105
|
-
// are in separate jest/ and vitest/ configs to avoid conflicts.
|
|
106
|
-
files: [
|
|
107
|
-
'**/*.{test,spec}.{ts,tsx,js,jsx}',
|
|
108
|
-
'**/__tests__/**/*.{ts,tsx,js,jsx}',
|
|
109
|
-
],
|
|
110
|
-
rules: {
|
|
111
|
-
// Disabled: mock callbacks often need empty functions
|
|
112
|
-
'no-empty-function': 'off',
|
|
113
|
-
// Disabled: mock factories use Promise.resolve/reject (conflicts with require-await)
|
|
114
|
-
'promise/prefer-await-to-then': 'off',
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
files: ['**/env.ts'],
|
|
119
|
-
rules: {
|
|
120
|
-
'node/no-process-env': 'off',
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
})
|
package/src/index.ts
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import type { MutationOptions, QueryOptions } from '@tanstack/query-core'
|
|
2
|
-
import type { Service } from 'effect/Context'
|
|
3
|
-
import type { ManagedRuntime } from 'effect/ManagedRuntime'
|
|
4
|
-
|
|
5
|
-
import * as Effect from 'effect/Effect'
|
|
6
|
-
|
|
7
|
-
import type { TanstackQueryOptionsProxy } from './types'
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Creates a type-safe proxy that bridges Effect `HttpApiClient` endpoints
|
|
11
|
-
* with TanStack Query options (`queryOptions`, `mutationOptions`, and `getQueryKey`).
|
|
12
|
-
*
|
|
13
|
-
* @param tag - The Effect `Service` tag used to locate the client implementation within the Context.
|
|
14
|
-
* @param runtime - The Effect `ManagedRuntime` used to execute effects as promises.
|
|
15
|
-
*
|
|
16
|
-
* @returns A proxy object structured like the target API client, providing TanStack Query helpers for each endpoint.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```ts
|
|
20
|
-
* class ApiGroup extends HttpApiGroup.make('ApiGroup')
|
|
21
|
-
* .add(HttpApiEndpoint.get('hello', '/hello', { success: Schema.String, query: Schema.Struct({ name: Schema.String }) }))
|
|
22
|
-
* .add(HttpApiEndpoint.post('bye', '/bye', { success: Schema.String, payload: Schema.Struct({ name: Schema.String }) })) {}
|
|
23
|
-
*
|
|
24
|
-
* class Api extends HttpApi.make('Api').add(ApiGroup) {}
|
|
25
|
-
*
|
|
26
|
-
* class ApiClient extends Context.Service<ApiClient, HttpApiClient.ForApi<typeof Api>>()('ApiClient') {
|
|
27
|
-
* public static live = Layer.effect(this, HttpApiClient.make(Api)).pipe(Layer.provide(FetchHttpClient.layer))
|
|
28
|
-
* }
|
|
29
|
-
*
|
|
30
|
-
* const runtime = ManagedRuntime.make(ApiClient.live)
|
|
31
|
-
* const api = createTanstackQueryOptionsProxy(ApiClient, runtime);
|
|
32
|
-
*
|
|
33
|
-
* // Example usage in a React component with TanStack Query
|
|
34
|
-
* const { data } = useQuery(api.hello.queryOptions({ query: { name: 'World' } }))
|
|
35
|
-
* // ^? const data: string = "Hello, World!"
|
|
36
|
-
*
|
|
37
|
-
* const { mutate, data } = useMutation(api.bye.mutationOptions())
|
|
38
|
-
* // ^? const data: string = "Goodbye, World!"
|
|
39
|
-
* mutate({ name: 'World' })
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
function createTanstackQueryOptionsProxy<TServiceTag, TService>(
|
|
43
|
-
tag: Service<TServiceTag, TService>,
|
|
44
|
-
runtime: ManagedRuntime<TServiceTag, never>
|
|
45
|
-
): TanstackQueryOptionsProxy<TService> {
|
|
46
|
-
/**
|
|
47
|
-
* Internal cache for memoizing Proxy instances to avoid unnecessary re-allocations.
|
|
48
|
-
*/
|
|
49
|
-
const cache = new Map<string, unknown>()
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Recursively creates or retrieves a cached Proxy instance for a given property path.
|
|
53
|
-
*
|
|
54
|
-
* @param path - An array of property keys representing the object trajectory (e.g., `['users', 'getById', 'queryOptions']`).
|
|
55
|
-
* @returns A proxy function handling property accesses and method invocations.
|
|
56
|
-
*/
|
|
57
|
-
const createProxy = (path: string[]): unknown => {
|
|
58
|
-
const cacheKey = path.join('.')
|
|
59
|
-
if (cache.has(cacheKey)) return cache.get(cacheKey)
|
|
60
|
-
|
|
61
|
-
const proxy = new Proxy(
|
|
62
|
-
() => {
|
|
63
|
-
// This function is intentionally left empty. The proxy will handle method calls and property accesses.
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
get(_target, prop) {
|
|
67
|
-
if (typeof prop === 'symbol' || prop === 'then') return
|
|
68
|
-
return createProxy([...path, String(prop)])
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
apply(_target, _thisArg, args) {
|
|
72
|
-
const action = path.at(-1)
|
|
73
|
-
const apiPath = path.slice(0, -1)
|
|
74
|
-
const [input, options] = args
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Executes the targeted Effect HTTP API endpoint by navigating the service instance.
|
|
78
|
-
*
|
|
79
|
-
* @param params - Parameters/payload passed into the target endpoint function.
|
|
80
|
-
* @param signal - Optional `AbortSignal` for canceling request execution.
|
|
81
|
-
* @returns A Promise resolving to the Effect result.
|
|
82
|
-
*/
|
|
83
|
-
const execute = (params: unknown, signal?: AbortSignal) =>
|
|
84
|
-
runtime.runPromise(
|
|
85
|
-
Effect.gen(function* executeGen() {
|
|
86
|
-
// oxlint-disable-next-line typescript/no-explicit-any
|
|
87
|
-
const api: any = yield* tag
|
|
88
|
-
|
|
89
|
-
let fn = api
|
|
90
|
-
for (const p of apiPath) fn = fn[p]
|
|
91
|
-
|
|
92
|
-
return yield* fn(params)
|
|
93
|
-
}) as Effect.Effect<unknown, unknown, TServiceTag>,
|
|
94
|
-
{ signal }
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Constructs a consistent, structured array key for TanStack Query caching.
|
|
99
|
-
*
|
|
100
|
-
* @param queryType - The operation type (`'query'` or `'mutation'`).
|
|
101
|
-
* @param inp - The input parameters or payload associated with the endpoint request.
|
|
102
|
-
* @returns A read-only query key array.
|
|
103
|
-
*/
|
|
104
|
-
const createKey = (queryType: string, inp: unknown) => [
|
|
105
|
-
{ type: queryType },
|
|
106
|
-
...apiPath,
|
|
107
|
-
...(inp ? [inp] : []),
|
|
108
|
-
]
|
|
109
|
-
|
|
110
|
-
if (action === 'queryOptions')
|
|
111
|
-
return {
|
|
112
|
-
...options,
|
|
113
|
-
queryKey: createKey('query', input),
|
|
114
|
-
queryFn: ({ signal }) => execute(input, signal),
|
|
115
|
-
} satisfies QueryOptions
|
|
116
|
-
|
|
117
|
-
if (action === 'getQueryKey') return createKey('query', input)
|
|
118
|
-
|
|
119
|
-
if (action === 'mutationOptions')
|
|
120
|
-
return {
|
|
121
|
-
...options,
|
|
122
|
-
mutationKey: createKey('mutation', input),
|
|
123
|
-
mutationFn: (payload) => execute({ payload }),
|
|
124
|
-
} satisfies MutationOptions
|
|
125
|
-
},
|
|
126
|
-
}
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
cache.set(cacheKey, proxy)
|
|
130
|
-
return proxy
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return createProxy([]) as TanstackQueryOptionsProxy<TService>
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export type { TanstackQueryOptionsProxy } from './types'
|
|
137
|
-
export { createTanstackQueryOptionsProxy }
|
package/src/types.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// oxlint-disable typescript/no-empty-object-type typescript/ban-types
|
|
2
|
-
|
|
3
|
-
import type { MutationOptions, QueryOptions } from '@tanstack/query-core'
|
|
4
|
-
import type { Schema } from 'effect/Schema'
|
|
5
|
-
import type { Client } from 'effect/unstable/httpapi/HttpApiClient'
|
|
6
|
-
import type { HttpApiEndpoint } from 'effect/unstable/httpapi/HttpApiEndpoint'
|
|
7
|
-
|
|
8
|
-
export type TanstackQueryOptionsProxy<T> =
|
|
9
|
-
T extends Client.Method<
|
|
10
|
-
HttpApiEndpoint<
|
|
11
|
-
infer _Key,
|
|
12
|
-
infer Method,
|
|
13
|
-
infer _Path,
|
|
14
|
-
infer Params,
|
|
15
|
-
infer Query,
|
|
16
|
-
infer Payload,
|
|
17
|
-
infer Headers,
|
|
18
|
-
infer Success,
|
|
19
|
-
infer Error
|
|
20
|
-
>,
|
|
21
|
-
infer _Error,
|
|
22
|
-
infer _Requires
|
|
23
|
-
>
|
|
24
|
-
? {
|
|
25
|
-
queryOptions: Method extends 'GET'
|
|
26
|
-
? <TQuery = QueryOptions<UnwrapCodec<Success>, UnwrapCodec<Error>>>(
|
|
27
|
-
input: MakeOptionalInput<
|
|
28
|
-
([Params] extends [never]
|
|
29
|
-
? {}
|
|
30
|
-
: { params: UnwrapCodec<Params> }) &
|
|
31
|
-
([Query] extends [never]
|
|
32
|
-
? {}
|
|
33
|
-
: { query: UnwrapCodec<Query> }) &
|
|
34
|
-
([Headers] extends [never]
|
|
35
|
-
? {}
|
|
36
|
-
: { headers: UnwrapCodec<Headers> })
|
|
37
|
-
>,
|
|
38
|
-
options?: Omit<TQuery, 'queryKey' | 'queryFn'>
|
|
39
|
-
) => TQuery
|
|
40
|
-
: never
|
|
41
|
-
mutationOptions: Method extends 'GET'
|
|
42
|
-
? never
|
|
43
|
-
: <
|
|
44
|
-
TMutation = MutationOptions<
|
|
45
|
-
UnwrapCodec<Success>,
|
|
46
|
-
UnwrapCodec<Error>,
|
|
47
|
-
UnwrapCodec<Payload>
|
|
48
|
-
>,
|
|
49
|
-
>(
|
|
50
|
-
input: MakeOptionalInput<
|
|
51
|
-
([Params] extends [never]
|
|
52
|
-
? {}
|
|
53
|
-
: { params: UnwrapCodec<Params> }) &
|
|
54
|
-
([Headers] extends [never]
|
|
55
|
-
? {}
|
|
56
|
-
: { headers: UnwrapCodec<Headers> })
|
|
57
|
-
>,
|
|
58
|
-
options?: Omit<TMutation, 'mutationKey' | 'mutationFn'>
|
|
59
|
-
) => TMutation
|
|
60
|
-
|
|
61
|
-
getQueryKey: Method extends 'GET'
|
|
62
|
-
? (
|
|
63
|
-
input?: Partial<
|
|
64
|
-
MakeOptionalInput<
|
|
65
|
-
([Params] extends [never]
|
|
66
|
-
? {}
|
|
67
|
-
: { params: UnwrapCodec<Params> }) &
|
|
68
|
-
([Query] extends [never]
|
|
69
|
-
? {}
|
|
70
|
-
: { query: UnwrapCodec<Query> }) &
|
|
71
|
-
([Headers] extends [never]
|
|
72
|
-
? {}
|
|
73
|
-
: { headers: UnwrapCodec<Headers> })
|
|
74
|
-
>
|
|
75
|
-
>
|
|
76
|
-
) => readonly unknown[]
|
|
77
|
-
: never
|
|
78
|
-
}
|
|
79
|
-
: T extends object
|
|
80
|
-
? {
|
|
81
|
-
readonly [K in keyof T]: TanstackQueryOptionsProxy<T[K]>
|
|
82
|
-
}
|
|
83
|
-
: T
|
|
84
|
-
|
|
85
|
-
type UnwrapCodec<T> =
|
|
86
|
-
T extends Schema<unknown>
|
|
87
|
-
? Schema.Type<T>
|
|
88
|
-
: T extends object
|
|
89
|
-
? { [K in keyof T]: UnwrapCodec<T[K]> }
|
|
90
|
-
: T
|
|
91
|
-
|
|
92
|
-
// oxlint-disable-next-line typescript/no-invalid-void-type
|
|
93
|
-
type MakeOptionalInput<T> = keyof T extends never ? void | undefined : T
|
package/tegami.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { tegami } from 'tegami'
|
|
2
|
-
import { runCli } from 'tegami/cli'
|
|
3
|
-
import { github } from 'tegami/plugins/github'
|
|
4
|
-
|
|
5
|
-
const paper = tegami({
|
|
6
|
-
npm: {
|
|
7
|
-
client: 'bun',
|
|
8
|
-
updateLockFile: true,
|
|
9
|
-
onBreakPeerDep: 'set',
|
|
10
|
-
trustedPublish: {
|
|
11
|
-
provider: 'github',
|
|
12
|
-
workflow: 'release.yml',
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
|
|
16
|
-
plugins: [
|
|
17
|
-
github({
|
|
18
|
-
repo: 'tiesen243/effect-tanstack-query',
|
|
19
|
-
versionPr: {
|
|
20
|
-
base: 'main',
|
|
21
|
-
},
|
|
22
|
-
}),
|
|
23
|
-
],
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
// oxlint-disable-next-line node/no-top-level-await
|
|
27
|
-
await runCli(paper)
|
package/tsconfig.json
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
// Environment setup & latest features
|
|
4
|
-
"lib": ["es2024", "dom"],
|
|
5
|
-
"target": "es2024",
|
|
6
|
-
"module": "preserve",
|
|
7
|
-
"moduleDetection": "force",
|
|
8
|
-
"allowJs": true,
|
|
9
|
-
|
|
10
|
-
// Bundler mode
|
|
11
|
-
"moduleResolution": "bundler",
|
|
12
|
-
"allowImportingTsExtensions": true,
|
|
13
|
-
"verbatimModuleSyntax": true,
|
|
14
|
-
"noEmit": true,
|
|
15
|
-
|
|
16
|
-
// Best practices
|
|
17
|
-
"strict": true,
|
|
18
|
-
"skipLibCheck": true,
|
|
19
|
-
"noFallthroughCasesInSwitch": true,
|
|
20
|
-
"noUncheckedIndexedAccess": true,
|
|
21
|
-
"noImplicitOverride": true,
|
|
22
|
-
|
|
23
|
-
// Some stricter flags (disabled by default)
|
|
24
|
-
"noUnusedLocals": false,
|
|
25
|
-
"noUnusedParameters": false,
|
|
26
|
-
"noPropertyAccessFromIndexSignature": false
|
|
27
|
-
},
|
|
28
|
-
"include": ["src/*.ts", "examples/*.ts"],
|
|
29
|
-
"exclude": ["node_modules", "dist"]
|
|
30
|
-
}
|