@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.
@@ -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.2",
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
  ".": {
@@ -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`.
@@ -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
- })
@@ -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) {}
@@ -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
- }
package/tsdown.config.ts DELETED
@@ -1,8 +0,0 @@
1
- import { defineConfig } from 'tsdown'
2
-
3
- export default defineConfig({
4
- entry: './src/index.ts',
5
- dts: true,
6
- minify: true,
7
- shims: true,
8
- })