@tiesen/effect-tanstack-query 0.0.7 → 0.0.9

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 CHANGED
@@ -1,13 +1,20 @@
1
1
  # effect-tanstack-query
2
2
 
3
- A lightweight, type-safe bridge between **Effect HTTP API clients** and **TanStack Query** (`queryOptions`, `mutationOptions`, and `getQueryKey`).
3
+ <p align="center">
4
+ <a href="https://npmx.dev/package/@tiesen/effect-tanstack-query"><img src="https://img.shields.io/npm/dw/%40tiesen%2Feffect-tanstack-query" alt="Total Downloads"></a>
5
+ <a href="https://npmx.dev/package/@tiesen/effect-tanstack-query"><img src="https://img.shields.io/npm/v/%40tiesen%2Feffect-tanstack-query" alt="Latest Stable Version"></a>
6
+ <a href="https://npmx.dev/package/@tiesen/effect-tanstack-query"><img src="https://img.shields.io/npm/l/%40tiesen%2Feffect-tanstack-query" alt="License"></a>
7
+ </p>
8
+
9
+ Effortlessly bridge **Effect HTTP API clients** with **TanStack Query**. Enjoy end-to-end type safety, zero-boilerplate options (`queryOptions`, `mutationOptions`, `subscriptionOptions`), and built-in streaming support out of the box.
4
10
 
5
11
  ## Features
6
12
 
7
13
  - **Seamless Integration**: Effortlessly map Effect `HttpApiClient` endpoints to TanStack Query config objects.
8
- - **End-to-End Type Safety**: Automatically infers inputs (`params`, `query`, `headers`, `payload`), success types, and error types directly from your Effect schemas.
9
- - **Zero Boilerplate**: Generates `queryKey` and execution functions under the hood using ES6 Proxies.
10
- - **Developer Friendly**: Smart optional parameters when no input arguments are required.
14
+ - **End-to-End Type Safety**: Automatically infers inputs (`params`, `query`, `payload`), response types, and error types directly from your Effect schemas.
15
+ - **Event Streaming Support**: Native handling for live streams and Server-Sent Events (SSE) via `subscriptionOptions` and `useSubscription`.
16
+ - **Zero Boilerplate**: Generates `queryKey`, and execution logic under the hood using ES6 Proxies.
17
+ - **Smart Developer Experience**: Auto-manages Fiber lifecycles and abort signals, with smart optional parameters when no inputs are required.
11
18
 
12
19
  ## Installation
13
20
 
@@ -15,16 +22,16 @@ Install using your preferred package manager:
15
22
 
16
23
  ```bash
17
24
  # Using npm
18
- npm install @tiesen/effect-tanstack-query
25
+ npm install @tiesen/effect-tanstack-query @tanstack/react-query effect@beta
19
26
 
20
27
  # Using yarn
21
- yarn add @tiesen/effect-tanstack-query
28
+ yarn add @tiesen/effect-tanstack-query @tanstack/react-query effect@beta
22
29
 
23
30
  # Using bun
24
- bun add @tiesen/effect-tanstack-query
31
+ bun add @tiesen/effect-tanstack-query @tanstack/react-query effect@beta
25
32
 
26
33
  # Using pnpm
27
- pnpm add @tiesen/effect-tanstack-query
34
+ pnpm add @tiesen/effect-tanstack-query @tanstack/react-query effect@beta
28
35
  ```
29
36
 
30
37
  ## Quick Start
@@ -33,9 +40,14 @@ pnpm add @tiesen/effect-tanstack-query
33
40
 
34
41
  ```ts
35
42
  import { Schema } from 'effect'
36
- import { HttpApi, HttpApiEndpoint, HttpApiGroup } from 'effect/unstable/httpapi'
43
+ import {
44
+ HttpApi,
45
+ HttpApiEndpoint,
46
+ HttpApiGroup,
47
+ HttpApiSchema,
48
+ } from 'effect/unstable/httpapi'
37
49
 
38
- class ApiGroup extends HttpApiGroup.make('ApiGroup')
50
+ class ApiGroup extends HttpApiGroup.make('group')
39
51
  .add(
40
52
  HttpApiEndpoint.get('hello', '/hello/:name', {
41
53
  success: Schema.String,
@@ -54,6 +66,13 @@ class ApiGroup extends HttpApiGroup.make('ApiGroup')
54
66
  name: Schema.String,
55
67
  }),
56
68
  })
69
+ )
70
+ .add(
71
+ HttpApiEndpoint.get('stream', '/stream', {
72
+ success: HttpApiSchema.StreamSse({
73
+ data: Schema.String,
74
+ }),
75
+ })
57
76
  ) {}
58
77
 
59
78
  export class Api extends HttpApi.make('Api').add(ApiGroup) {}
@@ -62,27 +81,18 @@ export class Api extends HttpApi.make('Api').add(ApiGroup) {}
62
81
  ### 2. Create an Effect HTTP API Client
63
82
 
64
83
  ```ts
65
- import { Context, Function, Layer } from 'effect'
66
- import {
67
- FetchHttpClient,
68
- HttpClient,
69
- HttpClientRequest,
70
- } from 'effect/unstable/http'
84
+ import { Context, Layer } from 'effect'
85
+ import { FetchHttpClient } from 'effect/unstable/http'
71
86
  import { HttpApiClient } from 'effect/unstable/httpapi'
72
87
 
73
88
  class ApiClient extends Context.Service<
74
89
  ApiClient,
75
90
  HttpApiClient.ForApi<typeof Api>
76
91
  >()('ApiClient') {
77
- public static live = Layer.effect(
92
+ public static layer = Layer.effect(
78
93
  this,
79
94
  HttpApiClient.make(Api, {
80
- transformClient: (client) =>
81
- client.pipe(
82
- HttpClient.mapRequest(
83
- Function.flow(HttpClientRequest.prependUrl('http://localhost:3000'))
84
- )
85
- ),
95
+ baseUrl: 'http://localhost:3000',
86
96
  })
87
97
  ).pipe(Layer.provide(FetchHttpClient.layer))
88
98
  }
@@ -94,7 +104,7 @@ class ApiClient extends Context.Service<
94
104
  import { ManagedRuntime } from 'effect'
95
105
  import { createTanstackQueryOptionsProxy } from '@tiesen/effect-tanstack-query'
96
106
 
97
- const runtime = ManagedRuntime.make(ApiClient.live)
107
+ const runtime = ManagedRuntime.make(ApiClient.layer)
98
108
  const api = createTanstackQueryOptionsProxy(ApiClient, runtime)
99
109
  ```
100
110
 
@@ -102,26 +112,42 @@ const api = createTanstackQueryOptionsProxy(ApiClient, runtime)
102
112
 
103
113
  ```ts
104
114
  import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query'
115
+ import { useSubscription } from '@tiesen/effect-tanstack-query/react'
105
116
 
106
117
  // Query Example
107
118
  const query = useQuery(
108
- api.ApiGroup.hello.queryOptions({
119
+ api.group.hello.queryOptions({
109
120
  params: { name: 'John' },
110
121
  query: { greeting: 'Hi' },
111
122
  })
112
123
  )
113
124
 
114
125
  // Mutation Example
115
- const mutation = useMutation(api.ApiGroup.goodbye.mutationOptions())
126
+ const mutation = useMutation(api.group.goodbye.mutationOptions())
116
127
  mutation.mutate({ name: 'John' })
117
128
 
129
+ // Stream Example
130
+ useSubscription(
131
+ api.group.stream.subscriptionOptions(undefined, {
132
+ onData: (data) => console.log('Received data:', data),
133
+ })
134
+ )
135
+
118
136
  // Invalidate Query Example
119
137
  const queryClient = useQueryClient()
120
138
  void queryClient.invalidateQueries({
121
- queryKey: api.ApiGroup.hello.getQueryKey(),
139
+ queryKey: api.group.hello.getQueryKey(),
122
140
  })
123
141
  ```
124
142
 
143
+ ## Downloads
144
+
145
+ <picture>
146
+ <source media="(prefers-color-scheme: dark)" srcset="https://npmx.dev/api/embed/downloads.svg?packages=%40tiesen%2Feffect-tanstack-query&metric=downloads&startDate=2025-08-01&endDate=2026-07-30&mode=dark&granularity=weekly&locale=en-US&accent=oklch%2867.3%25+0+0%29&yLabel=Weekly+Downloads">
147
+ <source media="(prefers-color-scheme: light)" srcset="https://npmx.dev/api/embed/downloads.svg?packages=%40tiesen%2Feffect-tanstack-query&metric=downloads&startDate=2025-08-01&endDate=2026-07-30&mode=light&granularity=weekly&locale=en-US&accent=oklch%2867.3%25+0+0%29&yLabel=Weekly+Downloads">
148
+ <img alt="npmx downloads" src="https://npmx.dev/api/embed/downloads.svg?packages=%40tiesen%2Feffect-tanstack-query&metric=downloads&startDate=2025-08-01&endDate=2026-07-30&mode=light&granularity=weekly&locale=en-US&accent=oklch%2867.3%25+0+0%29&yLabel=Weekly+Downloads">
149
+ </picture>
150
+
125
151
  ## License
126
152
 
127
153
  This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as TanstackQueryOptionsProxy } from "./types-BGHsAv5y.mjs";
1
+ import { r as TanstackQueryOptionsProxy } from "./types-CcB8FTUv.mjs";
2
2
  import { Service } from "effect/Context";
3
3
  import { ManagedRuntime } from "effect/ManagedRuntime";
4
4
  //#region src/index.d.ts
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import*as e from"effect/Effect";import*as t from"effect/Stream";function n(n,r){let i=new Map,a=o=>{let s=o.join(`.`);if(i.has(s))return i.get(s);let c=new Proxy(()=>{},{get(e,t){if(typeof t!=`symbol`&&t!==`then`)return a([...o,String(t)])},apply(i,a,s){let c=o.at(-1),l=o.slice(0,-1),[u,d]=s,f=e.fn(function*(e){let t=yield*n;for(let e of l)t=t[e];return yield*t(e)}),p=(e,t)=>r.runPromise(f(e),{signal:t}),m=(e,t)=>[{type:e},...l,...t?[t]:[]];if(c===`queryOptions`)return{...d,queryKey:m(`query`,u),queryFn:({signal:e})=>p(u,e)};if(c===`getQueryKey`)return m(`query`,u);if(c===`subscriptionOptions`){let n=t.unwrap(f({...u,responseMode:`response-only`}).pipe(e.map(e=>e.stream.pipe(t.decodeText())))),i=d;return{...d,subcriptionKey:m(`subscription`,u),subscriptionFn:({signal:a}={})=>{let o=r.runFork(n.pipe(t.tap(t=>e.sync(()=>i.onData(t))),t.runDrain)),s=()=>o.interruptUnsafe(o.id);return a&&(a.aborted?s():a.addEventListener(`abort`,s,{once:!0})),o.addObserver(e=>{e._tag===`Failure`&&i.onError?.(e.cause)}),s}}}if(c===`mutationOptions`)return{...d,mutationKey:m(`mutation`,u),mutationFn:e=>p({payload:e})}}});return i.set(s,c),c};return a([])}export{n as createTanstackQueryOptionsProxy};
1
+ import*as e from"effect/Effect";import*as t from"effect/Fiber";import*as n from"effect/Schedule";import*as r from"effect/Stream";function i(i,a){let o=new Map,s=c=>{let l=c.join(`.`);if(o.has(l))return o.get(l);let u=new Proxy(()=>{},{get(e,t){if(typeof t!=`symbol`&&t!==`then`)return s([...c,String(t)])},apply(o,s,l){let u=c.at(-1),d=c.slice(0,-1),[f,p]=l,m=e.fn(function*(e){let t=yield*i;for(let e of d)t=t[e];return yield*t(e)}),h=(e,t)=>a.runPromise(m(e),{signal:t}),g=(e,t)=>[{type:e},...d,...t?[t]:[]];if(u===`queryOptions`)return{...p,queryKey:g(`query`,f),queryFn:({signal:e})=>h(f,e)};if(u===`getQueryKey`)return g(`query`,f);if(u===`mutationOptions`)return{...p,mutationKey:g(`mutation`,f),mutationFn:e=>h({payload:e})};if(u===`subscriptionOptions`){let{onData:i,onError:o,keepAliveTimeout:s=`10 seconds`}=p;return{...p,subcriptionKey:g(`subscription`,f),subscriptionFn:({signal:c}={})=>{let l=e.gen(function*(){let t=yield*(yield*m({...f,responseMode:`response-only`})).stream.pipe(r.decodeText,r.splitLines,r.filter(e=>e.length>0),r.share({capacity:`unbounded`})),n=t.pipe(r.filter(e=>e.startsWith(`:keep-alive`)),r.timeout(s),r.tapError(()=>e.logError(`Keep-alive timeout, reconnecting...`))),a=yield*t.pipe(r.filter(e=>e.startsWith(`data:`)),r.map(e=>e.slice(5).trim()),r.filter(e=>e.length>0),r.tap(t=>e.sync(()=>i(t))),r.share({capacity:`unbounded`})),o=r.merge(n,a);yield*r.runDrain(o),yield*e.fail(`restart`)}).pipe(e.catchTag(`HttpClientError`,t=>t.reason._tag===`DecodeError`?e.void:(o&&o(t),e.fail(t))),e.scoped,e.retry(n.exponential(`3 seconds`))),u=a.runFork(l),d=()=>t.interrupt(u);return c&&(c.aborted?d():c.addEventListener(`abort`,d,{once:!0})),d}}}}});return o.set(l,u),u};return s([])}export{i as createTanstackQueryOptionsProxy};
package/dist/react.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as SubscriptionReturns, t as SubscriptionOptions } from "./types-BGHsAv5y.mjs";
1
+ import { n as SubscriptionReturns, t as SubscriptionOptions } from "./types-CcB8FTUv.mjs";
2
2
  //#region src/react.d.ts
3
3
  declare function useSubscription<TData, TError>({ enabled, ...subscriptionOptions }: SubscriptionOptions<TData, TError> & {
4
4
  enabled?: boolean;
package/dist/react.mjs CHANGED
@@ -1 +1 @@
1
- import*as e from"react";function t({enabled:t=!0,...n}){let[r,i]=e.useState(`disconnected`),[a,o]=e.useState(0),s=e.useCallback(()=>{o(e=>e+1)},[]);return e.useEffect(()=>{if(!t){i(`disconnected`);return}i(`connecting`);let e=new AbortController,r=n.subscriptionFn({signal:e.signal});return i(`connected`),()=>{e.abort(),r(),i(`disconnected`)}},[t,n.subscriptionFn,a]),e.useMemo(()=>({status:r,reconnect:s}),[r,s])}export{t as useSubscription};
1
+ import*as e from"react";function t({enabled:t=!0,...n}){let[r,i]=e.useState(`disconnected`),[a,o]=e.useState(0),s=e.useCallback(()=>{o(e=>e+1)},[]),c=e.useMemo(()=>JSON.stringify(n.subcriptionKey),[n.subcriptionKey]);return e.useEffect(()=>{if(!t)return i(`disconnected`);i(`connecting`);let e=new AbortController,r=n.subscriptionFn({signal:e.signal});return i(`connected`),()=>{e.abort(),r(),i(`disconnected`)}},[t,c,a]),e.useMemo(()=>({status:r,reconnect:s}),[r,s])}export{t as useSubscription};
@@ -1,4 +1,5 @@
1
1
  import { DefinedInitialDataOptions, MutationOptions } from "@tanstack/react-query";
2
+ import { Input } from "effect/Duration";
2
3
  import { Schema } from "effect/Schema";
3
4
  import { Client } from "effect/unstable/httpapi/HttpApiClient";
4
5
  import { HttpApiEndpoint } from "effect/unstable/httpapi/HttpApiEndpoint";
@@ -19,9 +20,9 @@ type TanstackQueryOptionsProxy<T> = T extends Client.Method<HttpApiEndpoint<infe
19
20
  }) & ([Headers] extends [never] ? {} : {
20
21
  headers: UnwrapCodec<Headers>;
21
22
  })>, options?: {
22
- onData: (data: StreamSuccess) => void;
23
- onError?: (error: StreamError) => void;
24
- }) => SubscriptionOptions<StreamSuccess, StreamError> : never;
23
+ onData: (data: UnwrapCodec<StreamSuccess>) => void;
24
+ onError?: (error: UnwrapCodec<StreamError>) => void;
25
+ }) => SubscriptionOptions<UnwrapCodec<StreamSuccess>, UnwrapCodec<StreamError>> : never;
25
26
  mutationOptions: Method extends 'GET' ? never : <TMutation = MutationOptions<UnwrapCodec<Success>, UnwrapCodec<Error>, UnwrapCodec<Payload>>>(input: MakeOptionalInput<([Params] extends [never] ? {} : {
26
27
  params: UnwrapCodec<Params>;
27
28
  }) & ([Headers] extends [never] ? {} : {
@@ -43,6 +44,7 @@ interface SubscriptionOptions<TData, TError> {
43
44
  }) => UnsubscribeFn;
44
45
  onData: (data: TData) => void;
45
46
  onError?: (error: TError) => void;
47
+ keepAliveTimeout?: Input;
46
48
  }
47
49
  interface SubscriptionReturns {
48
50
  status: 'connecting' | 'connected' | 'disconnected' | 'error';
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@tiesen/effect-tanstack-query",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "private": false,
5
- "description": " A lightweight, type-safe bridge between Effect HTTP API clients and TanStack Query.",
5
+ "description": "Effortlessly bridge Effect HTTP API clients with TanStack Query. Enjoy end-to-end type safety, zero-boilerplate options, and built-in streaming support out of the box.",
6
6
  "keywords": [
7
7
  "effect",
8
8
  "effect-ts",