New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@trpc/client

Package Overview
Dependencies
Maintainers
3
Versions
1108
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@trpc/client - npm Package Compare versions

Comparing version 11.0.0-alpha-tmp-issues-5851-take-two.499 to 11.0.0-alpha-tmp-issues-6374.694

dist/internals/inputWithTrackedEventId.d.ts

210

dist/bundle-analysis.json
{
"bundleSize": 50976,
"bundleOrigSize": 68169,
"bundleReduction": 25.22,
"bundleSize": 66120,
"bundleOrigSize": 83373,
"bundleReduction": 20.69,
"modules": [
{
"id": "/src/links/wsLink.ts",
"size": 13069,
"origSize": 14556,
"size": 18016,
"origSize": 20084,
"renderedExports": [

@@ -16,9 +16,21 @@ "createWSClient",

"dependents": [],
"percent": 25.64,
"reduction": 10.22
"percent": 27.25,
"reduction": 10.3
},
{
"id": "/src/links/httpSubscriptionLink.ts",
"size": 7788,
"origSize": 7568,
"renderedExports": [
"unstable_httpSubscriptionLink"
],
"removedExports": [],
"dependents": [],
"percent": 11.78,
"reduction": 0
},
{
"id": "/src/links/httpBatchStreamLink.ts",
"size": 5861,
"origSize": 6074,
"size": 6006,
"origSize": 6284,
"renderedExports": [

@@ -29,9 +41,9 @@ "unstable_httpBatchStreamLink"

"dependents": [],
"percent": 11.5,
"reduction": 3.51
"percent": 9.08,
"reduction": 4.42
},
{
"id": "/src/links/loggerLink.ts",
"size": 5457,
"origSize": 6694,
"size": 5596,
"origSize": 6946,
"renderedExports": [

@@ -42,4 +54,4 @@ "loggerLink"

"dependents": [],
"percent": 10.71,
"reduction": 18.48
"percent": 8.46,
"reduction": 19.44
},

@@ -58,3 +70,3 @@ {

],
"percent": 8.01,
"percent": 6.18,
"reduction": 5.64

@@ -64,4 +76,4 @@ },

"id": "/src/links/httpBatchLink.ts",
"size": 3925,
"origSize": 4118,
"size": 3937,
"origSize": 4170,
"renderedExports": [

@@ -72,9 +84,9 @@ "httpBatchLink"

"dependents": [],
"percent": 7.7,
"reduction": 4.69
"percent": 5.95,
"reduction": 5.59
},
{
"id": "/src/links/internals/httpUtils.ts",
"size": 3704,
"origSize": 5903,
"size": 3692,
"origSize": 5873,
"renderedExports": [

@@ -87,4 +99,3 @@ "resolveHTTPLinkOptions",

"fetchHTTPResponse",
"httpRequest",
"mergeAbortSignals"
"httpRequest"
],

@@ -98,21 +109,25 @@ "removedExports": [],

],
"percent": 7.27,
"reduction": 37.25
"percent": 5.58,
"reduction": 37.14
},
{
"id": "/src/links/httpSubscriptionLink.ts",
"size": 3522,
"origSize": 3752,
"id": "/src/internals/TRPCUntypedClient.ts",
"size": 3208,
"origSize": 4636,
"renderedExports": [
"unstable_httpSubscriptionLink"
"TRPCUntypedClient",
"untypedClientSymbol"
],
"removedExports": [],
"dependents": [],
"percent": 6.91,
"reduction": 6.13
"dependents": [
"/src/createTRPCUntypedClient.ts",
"/src/createTRPCClient.ts"
],
"percent": 4.85,
"reduction": 30.8
},
{
"id": "/src/links/httpLink.ts",
"size": 3184,
"origSize": 3709,
"size": 3179,
"origSize": 3707,
"renderedExports": [

@@ -123,24 +138,9 @@ "httpLink"

"dependents": [],
"percent": 6.25,
"reduction": 14.15
"percent": 4.81,
"reduction": 14.24
},
{
"id": "/src/internals/TRPCUntypedClient.ts",
"size": 2162,
"origSize": 4126,
"renderedExports": [
"TRPCUntypedClient"
],
"removedExports": [],
"dependents": [
"/src/createTRPCUntypedClient.ts",
"/src/createTRPCClient.ts"
],
"percent": 4.24,
"reduction": 47.6
},
{
"id": "/src/TRPCClientError.ts",
"size": 1940,
"origSize": 3555,
"size": 2787,
"origSize": 3564,
"renderedExports": [

@@ -159,9 +159,21 @@ "TRPCClientError"

],
"percent": 3.81,
"reduction": 45.43
"percent": 4.22,
"reduction": 21.8
},
{
"id": "/src/links/retryLink.ts",
"size": 2194,
"origSize": 2702,
"renderedExports": [
"retryLink"
],
"removedExports": [],
"dependents": [],
"percent": 3.32,
"reduction": 18.8
},
{
"id": "/src/createTRPCClient.ts",
"size": 1187,
"origSize": 4428,
"size": 1228,
"origSize": 4870,
"renderedExports": [

@@ -177,6 +189,23 @@ "clientCallTypeToProcedureType",

],
"percent": 2.33,
"reduction": 73.19
"percent": 1.86,
"reduction": 74.78
},
{
"id": "/src/internals/signals.ts",
"size": 1188,
"origSize": 1236,
"renderedExports": [
"allAbortSignals",
"raceAbortSignals"
],
"removedExports": [],
"dependents": [
"/src/links/httpBatchLink.ts",
"/src/links/httpBatchStreamLink.ts",
"/src/links/httpSubscriptionLink.ts"
],
"percent": 1.8,
"reduction": 3.88
},
{
"id": "/src/links/internals/createChain.ts",

@@ -193,3 +222,3 @@ "size": 690,

],
"percent": 1.35,
"percent": 1.04,
"reduction": 32.75

@@ -206,3 +235,3 @@ },

"dependents": [],
"percent": 1.2,
"percent": 0.92,
"reduction": 44.95

@@ -213,3 +242,3 @@ },

"size": 565,
"origSize": 1699,
"origSize": 1697,
"renderedExports": [

@@ -222,4 +251,4 @@ "getTransformer"

],
"percent": 1.11,
"reduction": 66.75
"percent": 0.85,
"reduction": 66.71
},

@@ -238,3 +267,3 @@ {

],
"percent": 0.84,
"percent": 0.65,
"reduction": 33.54

@@ -253,9 +282,24 @@ },

"dependents": [],
"percent": 0.65,
"percent": 0.5,
"reduction": 15.17
},
{
"id": "/src/internals/inputWithTrackedEventId.ts",
"size": 254,
"origSize": 273,
"renderedExports": [
"inputWithTrackedEventId"
],
"removedExports": [],
"dependents": [
"/src/links/httpSubscriptionLink.ts",
"/src/links/retryLink.ts"
],
"percent": 0.38,
"reduction": 6.96
},
{
"id": "/src/links/internals/urlWithConnectionParams.ts",
"size": 158,
"origSize": 857,
"size": 240,
"origSize": 1016,
"renderedExports": [

@@ -269,4 +313,4 @@ "resultOf"

],
"percent": 0.31,
"reduction": 81.56
"percent": 0.36,
"reduction": 76.38
},

@@ -284,12 +328,16 @@ {

],
"percent": 0.2,
"percent": 0.15,
"reduction": 82.58
},
{
"id": "/src/index.ts",
"id": "/src/unstable-internals.ts",
"size": 0,
"origSize": 588,
"origSize": 90,
"renderedExports": [],
"removedExports": [],
"dependents": [],
"dependents": [
"/src/links/wsLink.ts",
"/src/links/httpSubscriptionLink.ts",
"/src/links/internals/httpUtils.ts"
],
"percent": 0,

@@ -299,12 +347,8 @@ "reduction": 100

{
"id": "/src/unstable-internals.ts",
"id": "/src/index.ts",
"size": 0,
"origSize": 41,
"origSize": 588,
"renderedExports": [],
"removedExports": [],
"dependents": [
"/src/links/wsLink.ts",
"/src/links/httpSubscriptionLink.ts",
"/src/links/internals/httpUtils.ts"
],
"dependents": [],
"percent": 0,

@@ -314,3 +358,3 @@ "reduction": 100

],
"moduleCount": 20
"moduleCount": 23
}

@@ -5,8 +5,15 @@ import type { Unsubscribable } from '@trpc/server/observable';

import type { TRPCSubscriptionObserver, UntypedClientProperties } from './internals/TRPCUntypedClient';
import { TRPCUntypedClient } from './internals/TRPCUntypedClient';
import { TRPCUntypedClient, untypedClientSymbol } from './internals/TRPCUntypedClient';
import type { TRPCClientError } from './TRPCClientError';
/**
* @public
*/
export type TRPCClient<TRouter extends AnyRouter> = DecoratedProcedureRecord<TRouter, TRouter['_def']['record']> & {
[untypedClientSymbol]: TRPCUntypedClient<TRouter>;
};
/**
* @public
* @deprecated use {@link TRPCClient} instead
**/
export type inferRouterClient<TRouter extends AnyRouter> = DecoratedProcedureRecord<TRouter, TRouter['_def']['record']>;
export type inferRouterClient<TRouter extends AnyRouter> = TRPCClient<TRouter>;
type ResolverDef = {

@@ -18,5 +25,6 @@ input: any;

};
type coerceAsyncGeneratorToIterable<T> = T extends AsyncGenerator<infer $T, infer $Return, infer $Next> ? AsyncIterable<$T, $Return, $Next> : T;
/** @internal */
export type Resolver<TDef extends ResolverDef> = (input: TDef['input'], opts?: ProcedureOptions) => Promise<TDef['output']>;
type SubscriptionResolver<TDef extends ResolverDef> = (input: TDef['input'], opts?: Partial<TRPCSubscriptionObserver<TDef['output'], TRPCClientError<TDef>>> & ProcedureOptions) => Unsubscribable;
export type Resolver<TDef extends ResolverDef> = (input: TDef['input'], opts?: ProcedureOptions) => Promise<coerceAsyncGeneratorToIterable<TDef['output']>>;
type SubscriptionResolver<TDef extends ResolverDef> = (input: TDef['input'], opts: Partial<TRPCSubscriptionObserver<TDef['output'], TRPCClientError<TDef>>> & ProcedureOptions) => Unsubscribable;
type DecorateProcedure<TType extends ProcedureType, TDef extends ResolverDef> = TType extends 'query' ? {

@@ -45,3 +53,3 @@ query: Resolver<TDef>;

*/
export type CreateTRPCClient<TRouter extends AnyRouter> = inferRouterClient<TRouter> extends infer $Value ? UntypedClientProperties & keyof $Value extends never ? inferRouterClient<TRouter> : IntersectionError<UntypedClientProperties & keyof $Value> : never;
export type CreateTRPCClient<TRouter extends AnyRouter> = TRPCClient<TRouter> extends infer $Value ? UntypedClientProperties & keyof $Value extends never ? TRPCClient<TRouter> : IntersectionError<UntypedClientProperties & keyof $Value> : never;
/**

@@ -56,4 +64,4 @@ * @internal

*/
export declare function getUntypedClient<TRouter extends AnyRouter>(client: inferRouterClient<TRouter>): TRPCUntypedClient<TRouter>;
export declare function getUntypedClient<TRouter extends AnyRouter>(client: TRPCClient<TRouter>): TRPCUntypedClient<TRouter>;
export {};
//# sourceMappingURL=createTRPCClient.d.ts.map

@@ -17,3 +17,3 @@ 'use strict';

*/ function createTRPCClientProxy(client) {
const proxy = unstableCoreDoNotImport.createRecursiveProxy(({ path , args })=>{
const proxy = unstableCoreDoNotImport.createRecursiveProxy(({ path, args })=>{
const pathCopy = [

@@ -30,3 +30,3 @@ ...path

}
if (key === '__untypedClient') {
if (key === TRPCUntypedClient.untypedClientSymbol) {
return client;

@@ -46,3 +46,3 @@ }

*/ function getUntypedClient(client) {
return client.__untypedClient;
return client[TRPCUntypedClient.untypedClientSymbol];
}

@@ -49,0 +49,0 @@

@@ -15,2 +15,3 @@ 'use strict';

var httpSubscriptionLink = require('./links/httpSubscriptionLink.js');
var retryLink = require('./links/retryLink.js');
var TRPCUntypedClient = require('./internals/TRPCUntypedClient.js');

@@ -39,2 +40,3 @@

exports.unstable_httpSubscriptionLink = httpSubscriptionLink.unstable_httpSubscriptionLink;
exports.retryLink = retryLink.retryLink;
exports.TRPCUntypedClient = TRPCUntypedClient.TRPCUntypedClient;

@@ -13,3 +13,3 @@ import type { AnyClientTypes, CombinedDataTransformer, DataTransformerOptions, TypeError } from '@trpc/server/unstable-core-do-not-import';

* You must use the same transformer on the backend and frontend
* @link https://trpc.io/docs/v11/data-transformers
* @see https://trpc.io/docs/v11/data-transformers
**/

@@ -23,3 +23,3 @@ transformer: DataTransformerOptions;

* You must use the same transformer on the backend and frontend
* @link https://trpc.io/docs/v11/data-transformers
* @see https://trpc.io/docs/v11/data-transformers
**/

@@ -26,0 +26,0 @@ transformer?: TypeError<'You must define a transformer on your your `initTRPC`-object first'>;

import type { Unsubscribable } from '@trpc/server/observable';
import type { AnyRouter, InferrableClientTypes, TypeError } from '@trpc/server/unstable-core-do-not-import';
import type { AnyRouter, inferAsyncIterableYield, InferrableClientTypes, TypeError } from '@trpc/server/unstable-core-do-not-import';
import type { TRPCConnectionState } from '../links/internals/subscriptions';
import type { OperationContext, TRPCClientRuntime, TRPCLink } from '../links/types';

@@ -16,6 +17,7 @@ import { TRPCClientError } from '../TRPCClientError';

}) => void;
onData: (value: TValue) => void;
onData: (value: inferAsyncIterableYield<TValue>) => void;
onError: (err: TError) => void;
onStopped: () => void;
onComplete: () => void;
onConnectionStateChange: (state: TRPCConnectionState<TError>) => void;
}

@@ -40,2 +42,3 @@ /** @internal */

}
export declare const untypedClientSymbol: unique symbol;
//# sourceMappingURL=TRPCUntypedClient.d.ts.map

@@ -7,2 +7,15 @@ 'use strict';

function _define_property(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
class TRPCUntypedClient {

@@ -53,15 +66,31 @@ $request(opts) {

input,
context: opts?.context,
signal: null
context: opts.context,
signal: opts.signal
});
return observable$.subscribe({
next (envelope) {
if (envelope.result.type === 'started') {
opts.onStarted?.({
context: envelope.context
});
} else if (envelope.result.type === 'stopped') {
opts.onStopped?.();
} else {
opts.onData?.(envelope.result.data);
switch(envelope.result.type){
case 'state':
{
opts.onConnectionStateChange?.(envelope.result);
break;
}
case 'started':
{
opts.onStarted?.({
context: envelope.context
});
break;
}
case 'stopped':
{
opts.onStopped?.();
break;
}
case 'data':
case undefined:
{
opts.onData?.(envelope.result.data);
break;
}
}

@@ -78,2 +107,5 @@ },

constructor(opts){
_define_property(this, "links", void 0);
_define_property(this, "runtime", void 0);
_define_property(this, "requestId", void 0);
this.requestId = 0;

@@ -85,3 +117,5 @@ this.runtime = {};

}
const untypedClientSymbol = Symbol('TRPCClient');
exports.TRPCUntypedClient = TRPCUntypedClient;
exports.untypedClientSymbol = untypedClientSymbol;

@@ -40,3 +40,3 @@ /**

*/
signal?: AbortSignal | null;
signal?: AbortSignal | undefined;
}

@@ -43,0 +43,0 @@ /**

@@ -10,2 +10,3 @@ export * from './links/types';

export * from './links/httpSubscriptionLink';
export * from './links/retryLink';
//# sourceMappingURL=links.d.ts.map

@@ -6,2 +6,3 @@ 'use strict';

var dataLoader = require('../internals/dataLoader.js');
var signals = require('../internals/signals.js');
var TRPCClientError = require('../TRPCClientError.js');

@@ -37,3 +38,3 @@ var httpUtils = require('./internals/httpUtils.js');

const inputs = batchOps.map((op)=>op.input);
const ac = httpUtils.mergeAbortSignals(batchOps);
const signal = signals.allAbortSignals(...batchOps.map((op)=>op.signal));
const res = await httpUtils.jsonHttpRequester({

@@ -55,3 +56,3 @@ ...resolvedOpts,

},
signal: ac.signal
signal
});

@@ -73,3 +74,3 @@ const resJSON = Array.isArray(res.json) ? res.json : batchOps.map(()=>res.json);

};
return ({ op })=>{
return ({ op })=>{
return observable.observable((observer)=>{

@@ -76,0 +77,0 @@ /* istanbul ignore if -- @preserve */ if (op.type === 'subscription') {

@@ -9,3 +9,3 @@ import type { AnyClientTypes } from '@trpc/server/unstable-core-do-not-import';

* Headers to be set on outgoing requests or a callback that of said headers
* @link http://trpc.io/docs/client/headers
* @see http://trpc.io/docs/client/headers
*/

@@ -12,0 +12,0 @@ headers?: HTTPHeaders | ((opts: {

@@ -6,2 +6,3 @@ 'use strict';

var dataLoader = require('../internals/dataLoader.js');
var signals = require('../internals/signals.js');
var TRPCClientError = require('../TRPCClientError.js');

@@ -41,6 +42,7 @@ var httpUtils = require('./internals/httpUtils.js');

const inputs = batchOps.map((op)=>op.input);
const ac = httpUtils.mergeAbortSignals(batchOps);
const batchSignals = signals.allAbortSignals(...batchOps.map((op)=>op.signal));
const abortController = new AbortController();
const responsePromise = httpUtils.fetchHTTPResponse({
...resolvedOpts,
signal: ac.signal,
signal: signals.raceAbortSignals(batchSignals, abortController.signal),
type,

@@ -77,3 +79,3 @@ contentTypeHeader: 'application/json',

},
abortController: ac
abortController
});

@@ -110,3 +112,3 @@ const promises = Object.keys(batchOps).map(async (key)=>{

};
return ({ op })=>{
return ({ op })=>{
return observable.observable((observer)=>{

@@ -113,0 +115,0 @@ /* istanbul ignore if -- @preserve */ if (op.type === 'subscription') {

@@ -7,3 +7,3 @@ import type { AnyClientTypes, AnyRouter } from '@trpc/server/unstable-core-do-not-import';

* Headers to be set on outgoing requests or a callback that of said headers
* @link http://trpc.io/docs/client/headers
* @see http://trpc.io/docs/client/headers
*/

@@ -15,5 +15,5 @@ headers?: HTTPHeaders | ((opts: {

/**
* @link https://trpc.io/docs/client/links/httpLink
* @see https://trpc.io/docs/client/links/httpLink
*/
export declare function httpLink<TRouter extends AnyRouter = AnyRouter>(opts: HTTPLinkOptions<TRouter['_def']['_config']['$types']>): TRPCLink<TRouter>;
//# sourceMappingURL=httpLink.d.ts.map

@@ -37,9 +37,9 @@ 'use strict';

/**
* @link https://trpc.io/docs/client/links/httpLink
* @see https://trpc.io/docs/client/links/httpLink
*/ function httpLink(opts) {
const resolvedOpts = httpUtils.resolveHTTPLinkOptions(opts);
return ()=>{
return ({ op })=>{
return ({ op })=>{
return observable.observable((observer)=>{
const { path , input , type } = op;
const { path, input, type } = op;
/* istanbul ignore if -- @preserve */ if (type === 'subscription') {

@@ -46,0 +46,0 @@ throw new Error('Subscriptions are unsupported by `httpLink` - use `httpSubscriptionLink` or `wsLink`');

@@ -1,10 +0,16 @@

import type { AnyClientTypes, inferClientTypes, InferrableClientTypes } from '@trpc/server/unstable-core-do-not-import';
import type { AnyClientTypes, EventSourceLike, inferClientTypes, InferrableClientTypes } from '@trpc/server/unstable-core-do-not-import';
import { type TransformerOptions } from '../unstable-internals';
import { type UrlOptionsWithConnectionParams } from './internals/urlWithConnectionParams';
import type { TRPCLink } from './types';
type HTTPSubscriptionLinkOptions<TRoot extends AnyClientTypes> = {
import type { Operation, TRPCLink } from './types';
type HTTPSubscriptionLinkOptions<TRoot extends AnyClientTypes, TEventSource extends EventSourceLike.AnyConstructor = typeof EventSource> = {
/**
* EventSource options
* EventSource ponyfill
*/
eventSourceOptions?: EventSourceInit;
EventSource?: TEventSource;
/**
* EventSource options or a callback that returns them
*/
eventSourceOptions?: EventSourceLike.InitDictOf<TEventSource> | ((opts: {
op: Operation;
}) => EventSourceLike.InitDictOf<TEventSource> | Promise<EventSourceLike.InitDictOf<TEventSource>>);
} & TransformerOptions<TRoot> & UrlOptionsWithConnectionParams;

@@ -14,4 +20,4 @@ /**

*/
export declare function unstable_httpSubscriptionLink<TInferrable extends InferrableClientTypes>(opts: HTTPSubscriptionLinkOptions<inferClientTypes<TInferrable>>): TRPCLink<TInferrable>;
export declare function unstable_httpSubscriptionLink<TInferrable extends InferrableClientTypes, TEventSource extends EventSourceLike.AnyConstructor>(opts: HTTPSubscriptionLinkOptions<inferClientTypes<TInferrable>, TEventSource>): TRPCLink<TInferrable>;
export {};
//# sourceMappingURL=httpSubscriptionLink.d.ts.map
'use strict';
var observable = require('@trpc/server/observable');
var rpc = require('@trpc/server/rpc');
var unstableCoreDoNotImport = require('@trpc/server/unstable-core-do-not-import');
var inputWithTrackedEventId = require('../internals/inputWithTrackedEventId.js');
var signals = require('../internals/signals.js');
var TRPCClientError = require('../TRPCClientError.js');

@@ -20,2 +23,11 @@ var transformer = require('../internals/transformer.js');

/**
* tRPC error codes that are considered retryable
* With out of the box SSE, the client will reconnect when these errors are encountered
*/ const codes5xx = [
rpc.TRPC_ERROR_CODES_BY_KEY.BAD_GATEWAY,
rpc.TRPC_ERROR_CODES_BY_KEY.SERVICE_UNAVAILABLE,
rpc.TRPC_ERROR_CODES_BY_KEY.GATEWAY_TIMEOUT,
rpc.TRPC_ERROR_CODES_BY_KEY.INTERNAL_SERVER_ERROR
];
/**
* @see https://trpc.io/docs/client/links/httpSubscriptionLink

@@ -25,51 +37,125 @@ */ function unstable_httpSubscriptionLink(opts) {

return ()=>{
return ({ op })=>{
return ({ op })=>{
return observable.observable((observer)=>{
const { type , path , input } = op;
const { type, path, input } = op;
/* istanbul ignore if -- @preserve */ if (type !== 'subscription') {
throw new Error('httpSubscriptionLink only supports subscriptions');
}
let eventSource = null;
let unsubscribed = false;
unstableCoreDoNotImport.run(async ()=>{
const url = httpUtils.getUrl({
transformer: transformer$1,
url: await urlWithConnectionParams(opts),
input,
path,
type,
signal: null
});
/* istanbul ignore if -- @preserve */ if (unsubscribed) {
// already unsubscribed - rare race condition
return;
}
eventSource = new EventSource(url, opts.eventSourceOptions);
const onStarted = ()=>{
let lastEventId = undefined;
const ac = new AbortController();
const signal = signals.raceAbortSignals(op.signal, ac.signal);
const eventSourceStream = unstableCoreDoNotImport.sseStreamConsumer({
url: async ()=>httpUtils.getUrl({
transformer: transformer$1,
url: await urlWithConnectionParams(opts),
input: inputWithTrackedEventId.inputWithTrackedEventId(input, lastEventId),
path,
type,
signal: null
}),
init: ()=>urlWithConnectionParams$1.resultOf(opts.eventSourceOptions, {
op
}),
signal,
deserialize: transformer$1.output.deserialize,
EventSource: opts.EventSource ?? globalThis.EventSource
});
const connectionState = observable.behaviorSubject({
type: 'state',
state: 'connecting',
error: null
});
const connectionSub = connectionState.subscribe({
next (state) {
observer.next({
result: {
type: 'started'
},
context: {
eventSource
}
result: state
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
eventSource.removeEventListener('open', onStarted);
};
// console.log('starting', new Date());
eventSource.addEventListener('open', onStarted);
const iterable = unstableCoreDoNotImport.sseStreamConsumer({
from: eventSource,
deserialize: transformer$1.input.deserialize
});
for await (const chunk of iterable){
// if the `sse({})`-helper is used, we always have an `id` field
const data = 'id' in chunk ? chunk : chunk.data;
observer.next({
result: {
data
}
});
}
});
unstableCoreDoNotImport.run(async ()=>{
for await (const chunk of eventSourceStream){
switch(chunk.type){
case 'ping':
break;
case 'data':
const chunkData = chunk.data;
let result;
if (chunkData.id) {
// if the `tracked()`-helper is used, we always have an `id` field
lastEventId = chunkData.id;
result = {
id: chunkData.id,
data: chunkData
};
} else {
result = {
data: chunkData.data
};
}
observer.next({
result,
context: {
eventSource: chunk.eventSource
}
});
break;
case 'connected':
{
observer.next({
result: {
type: 'started'
},
context: {
eventSource: chunk.eventSource
}
});
connectionState.next({
type: 'state',
state: 'pending',
error: null
});
break;
}
case 'serialized-error':
{
const error = TRPCClientError.TRPCClientError.from({
error: chunk.error
});
if (codes5xx.includes(chunk.error.code)) {
//
connectionState.next({
type: 'state',
state: 'connecting',
error
});
break;
}
//
// non-retryable error, cancel the subscription
throw error;
}
case 'connecting':
{
const lastState = connectionState.get();
const error = chunk.event && TRPCClientError.TRPCClientError.from(chunk.event);
if (!error && lastState.state === 'connecting') {
break;
}
connectionState.next({
type: 'state',
state: 'connecting',
error
});
break;
}
case 'timeout':
{
connectionState.next({
type: 'state',
state: 'connecting',
error: new TRPCClientError.TRPCClientError(`Timeout of ${chunk.ms}ms reached while waiting for a response`)
});
}
}
}
observer.next({

@@ -86,4 +172,4 @@ result: {

observer.complete();
eventSource?.close();
unsubscribed = true;
ac.abort();
connectionSub.unsubscribe();
};

@@ -90,0 +176,0 @@ });

@@ -1,4 +0,4 @@

export declare function isOctetType(input: unknown): input is Uint8Array | Blob;
export declare function isOctetType(input: unknown): input is Uint8Array<ArrayBufferLike> | Blob;
export declare function isFormData(input: unknown): input is FormData;
export declare function isNonJsonSerializable(input: unknown): input is Uint8Array | Blob | FormData;
export declare function isNonJsonSerializable(input: unknown): input is FormData | Uint8Array<ArrayBufferLike> | Blob;
//# sourceMappingURL=contentTypes.d.ts.map

@@ -17,3 +17,3 @@ import type { AnyClientTypes, CombinedDataTransformer, Maybe, ProcedureType, TRPCAcceptHeader, TRPCResponse } from '@trpc/server/unstable-core-do-not-import';

* The HTTP handler must separately allow overriding the method. See:
* @link https://trpc.io/docs/rpc
* @see https://trpc.io/docs/rpc
*/

@@ -68,10 +68,3 @@ methodOverride?: 'POST';

export declare function httpRequest(opts: HTTPRequestOptions): Promise<HTTPResult>;
/**
* Merges multiple abort signals into a single one
* - When all signals have been aborted, the merged signal will be aborted
*/
export declare function mergeAbortSignals(opts: {
signal: Maybe<AbortSignal>;
}[]): AbortController;
export {};
//# sourceMappingURL=httpUtils.d.ts.map

@@ -68,7 +68,34 @@ 'use strict';

};
/**
* Polyfill for DOMException with AbortError name
*/ class AbortError extends Error {
constructor(){
const name = 'AbortError';
super(name);
this.name = name;
this.message = name;
}
}
/**
* Polyfill for `signal.throwIfAborted()`
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/throwIfAborted
*/ const throwIfAborted = (signal)=>{
if (!signal?.aborted) {
return;
}
// If available, use the native implementation
signal.throwIfAborted?.();
// If we have `DOMException`, use it
if (typeof DOMException !== 'undefined') {
throw new DOMException('AbortError', 'AbortError');
}
// Otherwise, use our own implementation
throw new AbortError();
};
async function fetchHTTPResponse(opts) {
opts.signal?.throwIfAborted();
throwIfAborted(opts.signal);
const url = opts.getUrl(opts);
const body = opts.getBody(opts);
const { type } = opts;
const { type } = opts;
const resolvedHeaders = await (async ()=>{

@@ -108,30 +135,2 @@ const heads = await opts.headers();

}
/**
* Merges multiple abort signals into a single one
* - When all signals have been aborted, the merged signal will be aborted
*/ function mergeAbortSignals(opts) {
const ac = new AbortController();
if (opts.some((o)=>!o.signal)) {
return ac;
}
const count = opts.length;
let abortedCount = 0;
const onAbort = ()=>{
if (++abortedCount === count) {
ac.abort();
}
};
for (const o of opts){
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const signal = o.signal;
if (signal.aborted) {
onAbort();
} else {
signal.addEventListener('abort', onAbort, {
once: true
});
}
}
return ac;
}

@@ -144,3 +143,2 @@ exports.fetchHTTPResponse = fetchHTTPResponse;

exports.jsonHttpRequester = jsonHttpRequester;
exports.mergeAbortSignals = mergeAbortSignals;
exports.resolveHTTPLinkOptions = resolveHTTPLinkOptions;
import { type TRPCRequestInfo } from '@trpc/server/http';
/**
* Get the result of a value or function that returns a value
* It also optionally accepts typesafe arguments for the function
*/
export declare const resultOf: <T>(value: T | (() => T)) => T;
export declare const resultOf: <T, TArgs extends any[]>(value: T | ((...args: TArgs) => T), ...args: TArgs) => T;
/**
* A value that can be wrapped in callback
*/
type CallbackOrValue<T> = T | (() => T | Promise<T>);
export type CallbackOrValue<T> = T | (() => T | Promise<T>);
export interface UrlOptionsWithConnectionParams {

@@ -22,3 +23,2 @@ /**

}
export {};
//# sourceMappingURL=urlWithConnectionParams.d.ts.map

@@ -5,6 +5,7 @@ 'use strict';

* Get the result of a value or function that returns a value
*/ const resultOf = (value)=>{
return typeof value === 'function' ? value() : value;
* It also optionally accepts typesafe arguments for the function
*/ const resultOf = (value, ...args)=>{
return typeof value === 'function' ? value(...args) : value;
};
exports.resultOf = resultOf;

@@ -1,2 +0,2 @@

import type { AnyRouter } from '@trpc/server/unstable-core-do-not-import';
import type { AnyRouter, InferrableClientTypes } from '@trpc/server/unstable-core-do-not-import';
import type { TRPCClientError } from '../TRPCClientError';

@@ -8,5 +8,5 @@ import type { Operation, OperationResultEnvelope, TRPCLink } from './types';

};
type EnableFnOptions<TRouter extends AnyRouter> = {
type EnableFnOptions<TRouter extends InferrableClientTypes> = {
direction: 'down';
result: OperationResultEnvelope<unknown> | TRPCClientError<TRouter>;
result: OperationResultEnvelope<unknown, TRPCClientError<TRouter>> | TRPCClientError<TRouter>;
} | (Operation & {

@@ -21,3 +21,3 @@ direction: 'up';

direction: 'down';
result: OperationResultEnvelope<unknown> | TRPCClientError<TRouter>;
result: OperationResultEnvelope<unknown, TRPCClientError<TRouter>> | TRPCClientError<TRouter>;
elapsedMs: number;

@@ -50,3 +50,3 @@ } | {

/**
* @link https://trpc.io/docs/v11/client/links/loggerLink
* @see https://trpc.io/docs/v11/client/links/loggerLink
*/

@@ -53,0 +53,0 @@ export declare function loggerLink<TRouter extends AnyRouter = AnyRouter>(opts?: LoggerLinkOptions<TRouter>): TRPCLink<TRouter>;

@@ -67,3 +67,3 @@ 'use strict';

function constructPartsAndArgs(opts) {
const { direction , type , withContext , path , id , input } = opts;
const { direction, type, withContext, path, id, input } = opts;
const parts = [];

@@ -112,6 +112,6 @@ const args = [];

// maybe this should be moved to it's own package
const defaultLogger = ({ c =console , colorMode ='css' , withContext })=>(props)=>{
const defaultLogger = ({ c = console, colorMode = 'css', withContext })=>(props)=>{
const rawInput = props.input;
const input = isFormData(rawInput) ? Object.fromEntries(rawInput) : rawInput;
const { parts , args } = constructPartsAndArgs({
const { parts, args } = constructPartsAndArgs({
...props,

@@ -122,3 +122,3 @@ colorMode,

});
const fn = props.direction === 'down' && props.result && (props.result instanceof Error || 'error' in props.result.result) ? 'error' : 'log';
const fn = props.direction === 'down' && props.result && (props.result instanceof Error || 'error' in props.result.result && props.result.result.error) ? 'error' : 'log';
c[fn].apply(null, [

@@ -129,36 +129,40 @@ parts.join(' ')

/**
* @link https://trpc.io/docs/v11/client/links/loggerLink
* @see https://trpc.io/docs/v11/client/links/loggerLink
*/ function loggerLink(opts = {}) {
const { enabled =()=>true } = opts;
const { enabled = ()=>true } = opts;
const colorMode = opts.colorMode ?? (typeof window === 'undefined' ? 'ansi' : 'css');
const withContext = opts.withContext ?? colorMode === 'css';
const { logger =defaultLogger({
const { logger = defaultLogger({
c: opts.console,
colorMode,
withContext
}) , } = opts;
}) } = opts;
return ()=>{
return ({ op , next })=>{
return ({ op, next })=>{
return observable.observable((observer)=>{
// ->
enabled({
if (enabled({
...op,
direction: 'up'
}) && logger({
...op,
direction: 'up'
});
})) {
logger({
...op,
direction: 'up'
});
}
const requestStartTime = Date.now();
function logResult(result) {
const elapsedMs = Date.now() - requestStartTime;
enabled({
if (enabled({
...op,
direction: 'down',
result
}) && logger({
...op,
direction: 'down',
elapsedMs,
result
});
})) {
logger({
...op,
direction: 'down',
elapsedMs,
result
});
}
}

@@ -165,0 +169,0 @@ return next(op).pipe(observable.tap({

@@ -5,2 +5,3 @@ import type { Observable, Observer } from '@trpc/server/observable';

import type { TRPCClientError } from '../TRPCClientError';
import type { TRPCConnectionState } from './internals/subscriptions';
export { isNonJsonSerializable, isFormData, isOctetType, } from './internals/contentTypes';

@@ -40,4 +41,4 @@ /**

*/
export interface OperationResultEnvelope<TOutput> {
result: TRPCResultMessage<TOutput>['result'] | TRPCSuccessResponse<TOutput>['result'];
export interface OperationResultEnvelope<TOutput, TError> {
result: TRPCResultMessage<TOutput>['result'] | TRPCSuccessResponse<TOutput>['result'] | TRPCConnectionState<TError>;
context?: OperationContext;

@@ -48,7 +49,7 @@ }

*/
export type OperationResultObservable<TInferrable extends InferrableClientTypes, TOutput> = Observable<OperationResultEnvelope<TOutput>, TRPCClientError<TInferrable>>;
export type OperationResultObservable<TInferrable extends InferrableClientTypes, TOutput> = Observable<OperationResultEnvelope<TOutput, TRPCClientError<TInferrable>>, TRPCClientError<TInferrable>>;
/**
* @internal
*/
export type OperationResultObserver<TInferrable extends InferrableClientTypes, TOutput> = Observer<OperationResultEnvelope<TOutput>, TRPCClientError<TInferrable>>;
export type OperationResultObserver<TInferrable extends InferrableClientTypes, TOutput> = Observer<OperationResultEnvelope<TOutput, TRPCClientError<TInferrable>>, TRPCClientError<TInferrable>>;
/**

@@ -55,0 +56,0 @@ * @internal

@@ -5,2 +5,3 @@ import type { Observer, UnsubscribeFn } from '@trpc/server/observable';

import type { TransformerOptions } from '../unstable-internals';
import type { TRPCConnectionState } from './internals/subscriptions';
import { type UrlOptionsWithConnectionParams } from './internals/urlWithConnectionParams';

@@ -26,2 +27,6 @@ import type { Operation, TRPCLink } from './types';

/**
* Triggered when a WebSocket connection encounters an error
*/
onError?: (evt?: Event) => void;
/**
* Triggered when a WebSocket connection is closed

@@ -47,6 +52,35 @@ */

};
/**
* Send ping messages to the server and kill the connection if no pong message is returned
*/
keepAlive?: {
/**
* @default false
*/
enabled: boolean;
/**
* Send a ping message every this many milliseconds
* @default 5_000
*/
intervalMs?: number;
/**
* Close the WebSocket after this many milliseconds if the server does not respond
* @default 1_000
*/
pongTimeoutMs?: number;
};
}
/**
* @see https://trpc.io/docs/v11/client/links/wsLink
* @deprecated
* 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
* See https://github.com/trpc/trpc/issues/6109
*/
export declare function createWSClient(opts: WebSocketClientOptions): {
close: () => void;
request: (op: Operation, callbacks: WSCallbackObserver<AnyRouter, unknown>) => UnsubscribeFn;
request: (opts: {
op: Operation;
callbacks: WSCallbackObserver<AnyRouter, unknown>;
lastEventId: string | undefined;
}) => UnsubscribeFn;
readonly connection: ({

@@ -67,5 +101,18 @@ id: number;

*/
reconnect: () => void;
reconnect: (cause: Error | null) => void;
connectionState: import("@trpc/server/observable").BehaviorSubject<TRPCConnectionState<TRPCClientError<AnyRouter>>>;
};
/**
* @see https://trpc.io/docs/v11/client/links/wsLink
* @deprecated
* 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
* See https://github.com/trpc/trpc/issues/6109
*/
export type TRPCWebSocketClient = ReturnType<typeof createWSClient>;
/**
* @see https://trpc.io/docs/v11/client/links/wsLink
* @deprecated
* 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
* See https://github.com/trpc/trpc/issues/6109
*/
export type WebSocketLinkOptions<TRouter extends AnyRouter> = {

@@ -75,3 +122,6 @@ client: TRPCWebSocketClient;

/**
* @link https://trpc.io/docs/v11/client/links/wsLink
* @see https://trpc.io/docs/v11/client/links/wsLink
* @deprecated
* 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
* See https://github.com/trpc/trpc/issues/6109
*/

@@ -78,0 +128,0 @@ export declare function wsLink<TRouter extends AnyRouter>(opts: WebSocketLinkOptions<TRouter>): TRPCLink<TRouter>;

@@ -15,4 +15,9 @@ 'use strict';

};
function createWSClient(opts) {
const { WebSocket: WebSocketImpl = WebSocket , retryDelayMs: retryDelayFn = exponentialBackoff , onOpen , onClose , } = opts;
/**
* @see https://trpc.io/docs/v11/client/links/wsLink
* @deprecated
* 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
* See https://github.com/trpc/trpc/issues/6109
*/ function createWSClient(opts) {
const { WebSocket: WebSocketImpl = WebSocket, retryDelayMs: retryDelayFn = exponentialBackoff } = opts;
const lazyOpts = {

@@ -34,2 +39,12 @@ ...lazyDefaults,

let activeConnection = lazyOpts.enabled ? null : createConnection();
const initState = activeConnection ? {
type: 'state',
state: 'connecting',
error: null
} : {
type: 'state',
state: 'idle',
error: null
};
const connectionState = observable.behaviorSubject(initState);
/**

@@ -39,3 +54,3 @@ * tries to send the list of messages

if (!activeConnection) {
activeConnection = createConnection();
reconnect(null);
return;

@@ -65,9 +80,8 @@ }

}
function tryReconnect(conn) {
function tryReconnect(cause) {
if (!!connectTimer) {
return;
}
conn.state = 'connecting';
const timeout = retryDelayFn(connectAttempt++);
reconnectInMs(timeout);
reconnectInMs(timeout, cause);
}

@@ -81,5 +95,5 @@ function hasPendingRequests(conn) {

}
function reconnect() {
function reconnect(cause) {
if (lazyOpts.enabled && !hasPendingRequests()) {
// Skip reconnecting if there are pending requests and we're in lazy mode
// Skip reconnecting if there aren't pending requests and we're in lazy mode
return;

@@ -89,9 +103,21 @@ }

activeConnection = createConnection();
oldConnection && closeIfNoPending(oldConnection);
if (oldConnection) {
closeIfNoPending(oldConnection);
}
const currentState = connectionState.get();
if (currentState.state !== 'connecting') {
connectionState.next({
type: 'state',
state: 'connecting',
error: cause ? TRPCClientError.TRPCClientError.from(cause) : null
});
}
}
function reconnectInMs(ms) {
function reconnectInMs(ms, cause) {
if (connectTimer) {
return;
}
connectTimer = setTimeout(reconnect, ms);
connectTimer = setTimeout(()=>{
reconnect(cause);
}, ms);
}

@@ -108,3 +134,7 @@ function closeIfNoPending(conn) {

}
request(req.op, req.callbacks);
request({
op: req.op,
callbacks: req.callbacks,
lastEventId: req.lastEventId
});
}

@@ -120,5 +150,10 @@ const startLazyDisconnectTimer = ()=>{

}
if (!hasPendingRequests(activeConnection)) {
if (!hasPendingRequests()) {
activeConnection.ws?.close();
activeConnection = null;
connectionState.next({
type: 'state',
state: 'idle',
error: null
});
}

@@ -128,2 +163,4 @@ }, lazyOpts.closeMs);

function createConnection() {
let pingTimeout = undefined;
let pongTimeout = undefined;
const self = {

@@ -134,10 +171,46 @@ id: ++connectionIndex,

clearTimeout(lazyDisconnectTimer);
const onError = ()=>{
function destroy() {
const noop = ()=>{
// no-op
};
const { ws } = self;
if (ws) {
ws.onclose = noop;
ws.onerror = noop;
ws.onmessage = noop;
ws.onopen = noop;
ws.close();
}
self.state = 'closed';
if (self === activeConnection) {
tryReconnect(self);
}
const onCloseOrError = (cause)=>{
clearTimeout(pingTimeout);
clearTimeout(pongTimeout);
self.state = 'closed';
if (activeConnection === self) {
// connection might have been replaced already
tryReconnect(cause);
}
for (const [key, req] of Object.entries(pendingRequests)){
if (req.connection !== self) {
continue;
}
// The connection was closed either unexpectedly or because of a reconnect
if (req.type === 'subscription') {
// Subscriptions will resume after we've reconnected
resumeSubscriptionOnReconnect(req);
} else {
// Queries and mutations will error if interrupted
delete pendingRequests[key];
req.callbacks.error?.(TRPCClientError.TRPCClientError.from(cause ?? new TRPCWebSocketClosedError()));
}
}
};
run(async ()=>{
let url = await urlWithConnectionParams.resultOf(opts.url);
const onError = (evt)=>{
onCloseOrError(new TRPCWebSocketClosedError({
cause: evt
}));
opts.onError?.(evt);
};
function connect(url) {
if (opts.connectionParams) {

@@ -152,3 +225,40 @@ // append `?connectionParams=1` when connection params are used

connectTimer = undefined;
ws.addEventListener('open', ()=>{
ws.onopen = ()=>{
async function sendConnectionParams() {
if (!opts.connectionParams) {
return;
}
const connectMsg = {
method: 'connectionParams',
data: await urlWithConnectionParams.resultOf(opts.connectionParams)
};
ws.send(JSON.stringify(connectMsg));
}
function handleKeepAlive() {
if (!opts.keepAlive?.enabled) {
return;
}
const { pongTimeoutMs = 1000, intervalMs = 5000 } = opts.keepAlive;
const schedulePing = ()=>{
const schedulePongTimeout = ()=>{
pongTimeout = setTimeout(()=>{
const wasOpen = self.state === 'open';
destroy();
if (wasOpen) {
opts.onClose?.();
}
}, pongTimeoutMs);
};
pingTimeout = setTimeout(()=>{
ws.send('PING');
schedulePongTimeout();
}, intervalMs);
};
ws.addEventListener('message', ()=>{
clearTimeout(pingTimeout);
clearTimeout(pongTimeout);
schedulePing();
});
schedulePing();
}
run(async ()=>{

@@ -158,20 +268,24 @@ /* istanbul ignore next -- @preserve */ if (activeConnection?.ws !== ws) {

}
if (opts.connectionParams) {
const connectMsg = {
method: 'connectionParams',
data: await urlWithConnectionParams.resultOf(opts.connectionParams)
};
ws.send(JSON.stringify(connectMsg));
}
handleKeepAlive();
await sendConnectionParams();
connectAttempt = 0;
self.state = 'open';
onOpen?.();
// Update connection state
connectionState.next({
type: 'state',
state: 'pending',
error: null
});
opts.onOpen?.();
dispatch();
}).catch((cause)=>{
ws.close(// "Status codes in the range 3000-3999 are reserved for use by libraries, frameworks, and applications"
3000, cause);
onError();
3000);
onCloseOrError(new TRPCWebSocketClosedError({
message: 'Initialization error',
cause
}));
});
});
ws.addEventListener('error', onError);
};
ws.onerror = onError;
const handleIncomingRequest = (req)=>{

@@ -182,3 +296,5 @@ if (self !== activeConnection) {

if (req.method === 'reconnect') {
reconnect();
reconnect(new TRPCWebSocketClosedError({
message: 'Server requested reconnect'
}));
// notify subscribers

@@ -200,7 +316,12 @@ for (const pendingReq of Object.values(pendingRequests)){

if (self === activeConnection && req.connection !== activeConnection) {
// gracefully replace old connection with this
const oldConn = req.connection;
// gracefully replace old connection with a new connection
req.connection = self;
oldConn && closeIfNoPending(oldConn);
}
if (req.connection !== self) {
// the connection has been replaced
return;
}
if ('result' in data && data.result.type === 'data' && typeof data.result.id === 'string') {
req.lastEventId = data.result.id;
}
if ('result' in data && data.result.type === 'stopped' && activeConnection === self) {

@@ -210,3 +331,11 @@ req.callbacks.complete();

};
ws.addEventListener('message', ({ data })=>{
ws.onmessage = (event)=>{
const { data } = event;
if (data === 'PONG') {
return;
}
if (data === 'PING') {
ws.send('PONG');
return;
}
startLazyDisconnectTimer();

@@ -223,40 +352,22 @@ const msg = JSON.parse(data);

}
});
ws.addEventListener('close', ({ code })=>{
if (self.state === 'open') {
onClose?.({
code
});
};
ws.onclose = (event)=>{
const wasOpen = self.state === 'open';
destroy();
onCloseOrError(new TRPCWebSocketClosedError({
cause: event
}));
if (wasOpen) {
opts.onClose?.(event);
}
self.state = 'closed';
if (activeConnection === self) {
// connection might have been replaced already
tryReconnect(self);
}
for (const [key, req] of Object.entries(pendingRequests)){
if (req.connection !== self) {
continue;
}
if (self.state === 'closed') {
// If the connection was closed, we just call `complete()` on the request
delete pendingRequests[key];
req.callbacks.complete?.();
continue;
}
// The connection was closed either unexpectedly or because of a reconnect
if (req.type === 'subscription') {
// Subscriptions will resume after we've reconnected
resumeSubscriptionOnReconnect(req);
} else {
// Queries and mutations will error if interrupted
delete pendingRequests[key];
req.callbacks.error?.(TRPCClientError.TRPCClientError.from(new TRPCWebSocketClosedError('WebSocket closed prematurely')));
}
}
});
}).catch(onError);
};
}
Promise.resolve(urlWithConnectionParams.resultOf(opts.url)).then(connect).catch(()=>{
onCloseOrError(new Error('Failed to resolve url'));
});
return self;
}
function request(op, callbacks) {
const { type , input , path , id } = op;
function request(opts) {
const { op, callbacks, lastEventId } = opts;
const { type, input, path, id } = op;
const envelope = {

@@ -267,3 +378,4 @@ id,

input,
path
path,
lastEventId
}

@@ -275,3 +387,4 @@ };

callbacks,
op
op,
lastEventId
};

@@ -304,6 +417,10 @@ // enqueue message

// close pending requests that aren't attached to a connection yet
req.callbacks.error(TRPCClientError.TRPCClientError.from(new Error('Closed before connection was established')));
req.callbacks.error(TRPCClientError.TRPCClientError.from(new TRPCWebSocketClosedError({
message: 'Closed before connection was established'
})));
}
}
activeConnection && closeIfNoPending(activeConnection);
if (activeConnection) {
closeIfNoPending(activeConnection);
}
clearTimeout(connectTimer);

@@ -319,8 +436,13 @@ connectTimer = undefined;

* Reconnect to the WebSocket server
*/ reconnect
*/ reconnect,
connectionState: connectionState
};
}
class TRPCWebSocketClosedError extends Error {
constructor(message){
super(message);
constructor(opts){
super(opts?.message ?? 'WebSocket closed', // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore https://github.com/tc39/proposal-error-cause
{
cause: opts?.cause
});
this.name = 'TRPCWebSocketClosedError';

@@ -331,44 +453,60 @@ Object.setPrototypeOf(this, TRPCWebSocketClosedError.prototype);

/**
* @link https://trpc.io/docs/v11/client/links/wsLink
* @see https://trpc.io/docs/v11/client/links/wsLink
* @deprecated
* 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
* See https://github.com/trpc/trpc/issues/6109
*/ function wsLink(opts) {
const transformer$1 = transformer.getTransformer(opts.transformer);
return ()=>{
const { client } = opts;
return ({ op })=>{
const { client } = opts;
return ({ op })=>{
return observable.observable((observer)=>{
const { type , path , id , context } = op;
const { type, path, id, context } = op;
const input = transformer$1.input.serialize(op.input);
const unsub = client.request({
type,
path,
input,
id,
context,
signal: null
}, {
error (err) {
observer.error(err);
unsub();
},
complete () {
observer.complete();
},
next (message) {
const transformed = unstableCoreDoNotImport.transformResult(message, transformer$1.output);
if (!transformed.ok) {
observer.error(TRPCClientError.TRPCClientError.from(transformed.error));
return;
}
const connState = type === 'subscription' ? client.connectionState.subscribe({
next (result) {
observer.next({
result: transformed.result
result,
context
});
if (op.type !== 'subscription') {
// if it isn't a subscription we don't care about next response
unsub();
}
}) : null;
const unsubscribeRequest = client.request({
op: {
type,
path,
input,
id,
context,
signal: null
},
callbacks: {
error (err) {
observer.error(err);
unsubscribeRequest();
},
complete () {
observer.complete();
},
next (event) {
const transformed = unstableCoreDoNotImport.transformResult(event, transformer$1.output);
if (!transformed.ok) {
observer.error(TRPCClientError.TRPCClientError.from(transformed.error));
return;
}
observer.next({
result: transformed.result
});
if (op.type !== 'subscription') {
// if it isn't a subscription we don't care about next response
unsubscribeRequest();
observer.complete();
}
}
}
},
lastEventId: undefined
});
return ()=>{
unsub();
unsubscribeRequest();
connState?.unsubscribe();
};

@@ -375,0 +513,0 @@ });

@@ -24,3 +24,3 @@ import type { inferClientTypes, InferrableClientTypes, Maybe, TRPCErrorResponse } from '@trpc/server/unstable-core-do-not-import';

});
static from<TRouterOrProcedure extends InferrableClientTypes>(_cause: Error | TRPCErrorResponse<any>, opts?: {
static from<TRouterOrProcedure extends InferrableClientTypes>(_cause: Error | TRPCErrorResponse<any> | object, opts?: {
meta?: Record<string, unknown>;

@@ -27,0 +27,0 @@ }): TRPCClientError<TRouterOrProcedure>;

@@ -5,2 +5,15 @@ 'use strict';

function _define_property(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function isTRPCClientError(cause) {

@@ -54,3 +67,8 @@ return cause instanceof TRPCClientError || /**

cause
});
}), // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore override doesn't work in all environments due to "This member cannot have an 'override' modifier because it is not declared in the base class 'Error'"
_define_property(this, "cause", void 0), _define_property(this, "shape", void 0), _define_property(this, "data", void 0), /**
* Additional meta data about the error
* In the case of HTTP-errors, we'll have `response` and potentially `responseJSON` here
*/ _define_property(this, "meta", void 0);
this.meta = opts?.meta;

@@ -57,0 +75,0 @@ this.cause = cause;

export * from './internals/transformer';
export * from './links/internals/subscriptions';
//# sourceMappingURL=unstable-internals.d.ts.map
{
"name": "@trpc/client",
"version": "11.0.0-alpha-tmp-issues-5851-take-two.499+02e677611",
"version": "11.0.0-alpha-tmp-issues-6374.694+ce45f1c48",
"description": "The tRPC client library",

@@ -28,3 +28,3 @@ "author": "KATT",

"codegen-entrypoints": "tsx entrypoints.script.ts",
"lint": "eslint --cache --ext \".js,.ts,.tsx\" --ignore-path ../../.gitignore src",
"lint": "eslint --cache src",
"ts-watch": "tsc --watch"

@@ -77,18 +77,21 @@ },

"unstable-internals",
"!**/*.test.*"
"!**/*.test.*",
"!**/__tests__"
],
"peerDependencies": {
"@trpc/server": "11.0.0-alpha-tmp-issues-5851-take-two.499+02e677611"
"@trpc/server": "11.0.0-alpha-tmp-issues-6374.694+ce45f1c48",
"typescript": ">=5.7.2"
},
"devDependencies": {
"@trpc/server": "11.0.0-alpha-tmp-issues-5851-take-two.499+02e677611",
"@trpc/server": "11.0.0-alpha-tmp-issues-6374.694+ce45f1c48",
"@types/isomorphic-fetch": "^0.0.39",
"@types/node": "^20.10.0",
"eslint": "^8.56.0",
"@types/node": "^22.9.0",
"eslint": "^9.13.0",
"isomorphic-fetch": "^3.0.0",
"node-fetch": "^3.3.0",
"rollup": "^4.9.5",
"tslib": "^2.5.0",
"rollup": "^4.24.4",
"tslib": "^2.8.1",
"tsx": "^4.0.0",
"undici": "^6.0.1"
"typescript": "^5.7.2",
"undici": "^7.0.0"
},

@@ -101,3 +104,3 @@ "publishConfig": {

],
"gitHead": "02e67761124670c5ff27d174bfec6cfa5410a5ac"
"gitHead": "ce45f1c48b24261580aabe1793a78876d77e5168"
}

@@ -23,3 +23,6 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */

} from './internals/TRPCUntypedClient';
import { TRPCUntypedClient } from './internals/TRPCUntypedClient';
import {
TRPCUntypedClient,
untypedClientSymbol,
} from './internals/TRPCUntypedClient';
import type { TRPCClientError } from './TRPCClientError';

@@ -29,5 +32,15 @@

* @public
*/
export type TRPCClient<TRouter extends AnyRouter> = DecoratedProcedureRecord<
TRouter,
TRouter['_def']['record']
> & {
[untypedClientSymbol]: TRPCUntypedClient<TRouter>;
};
/**
* @public
* @deprecated use {@link TRPCClient} instead
**/
export type inferRouterClient<TRouter extends AnyRouter> =
DecoratedProcedureRecord<TRouter, TRouter['_def']['record']>;
export type inferRouterClient<TRouter extends AnyRouter> = TRPCClient<TRouter>;

@@ -41,2 +54,7 @@ type ResolverDef = {

type coerceAsyncGeneratorToIterable<T> =
T extends AsyncGenerator<infer $T, infer $Return, infer $Next>
? AsyncIterable<$T, $Return, $Next>
: T;
/** @internal */

@@ -46,7 +64,7 @@ export type Resolver<TDef extends ResolverDef> = (

opts?: ProcedureOptions,
) => Promise<TDef['output']>;
) => Promise<coerceAsyncGeneratorToIterable<TDef['output']>>;
type SubscriptionResolver<TDef extends ResolverDef> = (
input: TDef['input'],
opts?: Partial<
opts: Partial<
TRPCSubscriptionObserver<TDef['output'], TRPCClientError<TDef>>

@@ -65,10 +83,10 @@ > &

: TType extends 'mutation'
? {
mutate: Resolver<TDef>;
}
: TType extends 'subscription'
? {
subscribe: SubscriptionResolver<TDef>;
}
: never;
? {
mutate: Resolver<TDef>;
}
: TType extends 'subscription'
? {
subscribe: SubscriptionResolver<TDef>;
}
: never;

@@ -86,15 +104,15 @@ /**

: $Value extends AnyProcedure
? DecorateProcedure<
$Value['_def']['type'],
{
input: inferProcedureInput<$Value>;
output: inferTransformedProcedureOutput<
inferClientTypes<TRouter>,
$Value
>;
errorShape: inferClientTypes<TRouter>['errorShape'];
transformer: inferClientTypes<TRouter>['transformer'];
}
>
: never
? DecorateProcedure<
$Value['_def']['type'],
{
input: inferProcedureInput<$Value>;
output: inferTransformedProcedureOutput<
inferClientTypes<TRouter>,
$Value
>;
errorShape: inferClientTypes<TRouter>['errorShape'];
transformer: inferClientTypes<TRouter>['transformer'];
}
>
: never
: never;

@@ -123,5 +141,5 @@ };

export type CreateTRPCClient<TRouter extends AnyRouter> =
inferRouterClient<TRouter> extends infer $Value
TRPCClient<TRouter> extends infer $Value
? UntypedClientProperties & keyof $Value extends never
? inferRouterClient<TRouter>
? TRPCClient<TRouter>
: IntersectionError<UntypedClientProperties & keyof $Value>

@@ -150,3 +168,3 @@ : never;

}
if (key === '__untypedClient') {
if (key === untypedClientSymbol) {
return client;

@@ -171,5 +189,5 @@ }

export function getUntypedClient<TRouter extends AnyRouter>(
client: inferRouterClient<TRouter>,
client: TRPCClient<TRouter>,
): TRPCUntypedClient<TRouter> {
return (client as any).__untypedClient;
return client[untypedClientSymbol];
}

@@ -20,3 +20,3 @@ import type {

* You must use the same transformer on the backend and frontend
* @link https://trpc.io/docs/v11/data-transformers
* @see https://trpc.io/docs/v11/data-transformers
**/

@@ -30,3 +30,3 @@ transformer: DataTransformerOptions;

* You must use the same transformer on the backend and frontend
* @link https://trpc.io/docs/v11/data-transformers
* @see https://trpc.io/docs/v11/data-transformers
**/

@@ -33,0 +33,0 @@ transformer?: TypeError<'You must define a transformer on your your `initTRPC`-object first'>;

@@ -8,2 +8,3 @@ import type {

AnyRouter,
inferAsyncIterableYield,
InferrableClientTypes,

@@ -14,2 +15,3 @@ Maybe,

import { createChain } from '../links/internals/createChain';
import type { TRPCConnectionState } from '../links/internals/subscriptions';
import type {

@@ -34,6 +36,7 @@ OperationContext,

onStarted: (opts: { context: OperationContext | undefined }) => void;
onData: (value: TValue) => void;
onData: (value: inferAsyncIterableYield<TValue>) => void;
onError: (err: TError) => void;
onStopped: () => void;
onComplete: () => void;
onConnectionStateChange: (state: TRPCConnectionState<TError>) => void;
}

@@ -138,15 +141,27 @@

input,
context: opts?.context,
signal: null,
context: opts.context,
signal: opts.signal,
});
return observable$.subscribe({
next(envelope) {
if (envelope.result.type === 'started') {
opts.onStarted?.({
context: envelope.context,
});
} else if (envelope.result.type === 'stopped') {
opts.onStopped?.();
} else {
opts.onData?.(envelope.result.data);
switch (envelope.result.type) {
case 'state': {
opts.onConnectionStateChange?.(envelope.result);
break;
}
case 'started': {
opts.onStarted?.({
context: envelope.context,
});
break;
}
case 'stopped': {
opts.onStopped?.();
break;
}
case 'data':
case undefined: {
opts.onData?.(envelope.result.data);
break;
}
}

@@ -163,1 +178,3 @@ },

}
export const untypedClientSymbol = Symbol('TRPCClient');

@@ -52,3 +52,3 @@ /**

*/
signal?: AbortSignal | null;
signal?: AbortSignal | undefined;
}

@@ -55,0 +55,0 @@

@@ -11,5 +11,5 @@ export * from './links/types';

export * from './links/httpSubscriptionLink';
export * from './links/retryLink';
// These are not public (yet) as we get this functionality from tanstack query
// export * from './links/internals/retryLink';
// export * from './links/internals/dedupeLink';

@@ -6,2 +6,3 @@ import type { AnyRouter, ProcedureType } from '@trpc/server';

import { dataLoader } from '../internals/dataLoader';
import { allAbortSignals } from '../internals/signals';
import type { NonEmptyArray } from '../internals/types';

@@ -14,3 +15,2 @@ import { TRPCClientError } from '../TRPCClientError';

jsonHttpRequester,
mergeAbortSignals,
resolveHTTPLinkOptions,

@@ -55,3 +55,3 @@ } from './internals/httpUtils';

const inputs = batchOps.map((op) => op.input);
const ac = mergeAbortSignals(batchOps);
const signal = allAbortSignals(...batchOps.map((op) => op.signal));

@@ -74,3 +74,3 @@ const res = await jsonHttpRequester({

},
signal: ac.signal,
signal,
});

@@ -77,0 +77,0 @@ const resJSON = Array.isArray(res.json)

@@ -11,3 +11,3 @@ import type { AnyClientTypes } from '@trpc/server/unstable-core-do-not-import';

* Headers to be set on outgoing requests or a callback that of said headers
* @link http://trpc.io/docs/client/headers
* @see http://trpc.io/docs/client/headers
*/

@@ -14,0 +14,0 @@ headers?:

@@ -8,2 +8,3 @@ import type { AnyRouter, ProcedureType } from '@trpc/server';

import { dataLoader } from '../internals/dataLoader';
import { allAbortSignals, raceAbortSignals } from '../internals/signals';
import type { NonEmptyArray } from '../internals/types';

@@ -17,3 +18,2 @@ import { TRPCClientError } from '../TRPCClientError';

getUrl,
mergeAbortSignals,
resolveHTTPLinkOptions,

@@ -72,7 +72,10 @@ } from './internals/httpUtils';

const ac = mergeAbortSignals(batchOps);
const batchSignals = allAbortSignals(
...batchOps.map((op) => op.signal),
);
const abortController = new AbortController();
const responsePromise = fetchHTTPResponse({
...resolvedOpts,
signal: ac.signal,
signal: raceAbortSignals(batchSignals, abortController.signal),
type,

@@ -112,3 +115,3 @@ contentTypeHeader: 'application/json',

},
abortController: ac,
abortController,
});

@@ -115,0 +118,0 @@ const promises = Object.keys(batchOps).map(

@@ -32,3 +32,3 @@ import { observable } from '@trpc/server/observable';

* Headers to be set on outgoing requests or a callback that of said headers
* @link http://trpc.io/docs/client/headers
* @see http://trpc.io/docs/client/headers
*/

@@ -74,3 +74,3 @@ headers?:

/**
* @link https://trpc.io/docs/client/links/httpLink
* @see https://trpc.io/docs/client/links/httpLink
*/

@@ -77,0 +77,0 @@ export function httpLink<TRouter extends AnyRouter = AnyRouter>(

@@ -1,7 +0,13 @@

import { observable } from '@trpc/server/observable';
import { behaviorSubject, observable } from '@trpc/server/observable';
import type {
TRPC_ERROR_CODE_NUMBER,
TRPCErrorShape,
TRPCResult,
} from '@trpc/server/rpc';
import { TRPC_ERROR_CODES_BY_KEY } from '@trpc/server/rpc';
import type {
AnyClientTypes,
EventSourceLike,
inferClientTypes,
InferrableClientTypes,
SSEMessage,
} from '@trpc/server/unstable-core-do-not-import';

@@ -12,3 +18,6 @@ import {

} from '@trpc/server/unstable-core-do-not-import';
import { inputWithTrackedEventId } from '../internals/inputWithTrackedEventId';
import { raceAbortSignals } from '../internals/signals';
import { TRPCClientError } from '../TRPCClientError';
import type { TRPCConnectionState } from '../unstable-internals';
import { getTransformer, type TransformerOptions } from '../unstable-internals';

@@ -20,3 +29,3 @@ import { getUrl } from './internals/httpUtils';

} from './internals/urlWithConnectionParams';
import type { TRPCLink } from './types';
import type { Operation, TRPCLink } from './types';

@@ -38,7 +47,20 @@ async function urlWithConnectionParams(

type HTTPSubscriptionLinkOptions<TRoot extends AnyClientTypes> = {
type HTTPSubscriptionLinkOptions<
TRoot extends AnyClientTypes,
TEventSource extends EventSourceLike.AnyConstructor = typeof EventSource,
> = {
/**
* EventSource options
* EventSource ponyfill
*/
eventSourceOptions?: EventSourceInit;
EventSource?: TEventSource;
/**
* EventSource options or a callback that returns them
*/
eventSourceOptions?:
| EventSourceLike.InitDictOf<TEventSource>
| ((opts: {
op: Operation;
}) =>
| EventSourceLike.InitDictOf<TEventSource>
| Promise<EventSourceLike.InitDictOf<TEventSource>>);
} & TransformerOptions<TRoot> &

@@ -48,2 +70,13 @@ UrlOptionsWithConnectionParams;

/**
* tRPC error codes that are considered retryable
* With out of the box SSE, the client will reconnect when these errors are encountered
*/
const codes5xx: TRPC_ERROR_CODE_NUMBER[] = [
TRPC_ERROR_CODES_BY_KEY.BAD_GATEWAY,
TRPC_ERROR_CODES_BY_KEY.SERVICE_UNAVAILABLE,
TRPC_ERROR_CODES_BY_KEY.GATEWAY_TIMEOUT,
TRPC_ERROR_CODES_BY_KEY.INTERNAL_SERVER_ERROR,
];
/**
* @see https://trpc.io/docs/client/links/httpSubscriptionLink

@@ -53,6 +86,11 @@ */

TInferrable extends InferrableClientTypes,
TEventSource extends EventSourceLike.AnyConstructor,
>(
opts: HTTPSubscriptionLinkOptions<inferClientTypes<TInferrable>>,
opts: HTTPSubscriptionLinkOptions<
inferClientTypes<TInferrable>,
TEventSource
>,
): TRPCLink<TInferrable> {
const transformer = getTransformer(opts.transformer);
return () => {

@@ -62,2 +100,3 @@ return ({ op }) => {

const { type, path, input } = op;
/* istanbul ignore if -- @preserve */

@@ -68,49 +107,132 @@ if (type !== 'subscription') {

let eventSource: EventSource | null = null;
let unsubscribed = false;
let lastEventId: string | undefined = undefined;
const ac = new AbortController();
const signal = raceAbortSignals(op.signal, ac.signal);
const eventSourceStream = sseStreamConsumer<{
EventSource: TEventSource;
data: Partial<{
id?: string;
data: unknown;
}>;
error: TRPCErrorShape;
}>({
url: async () =>
getUrl({
transformer,
url: await urlWithConnectionParams(opts),
input: inputWithTrackedEventId(input, lastEventId),
path,
type,
signal: null,
}),
init: () => resultOf(opts.eventSourceOptions, { op }),
signal,
deserialize: transformer.output.deserialize,
EventSource:
opts.EventSource ??
(globalThis.EventSource as never as TEventSource),
});
run(async () => {
const url = getUrl({
transformer,
url: await urlWithConnectionParams(opts),
input,
path,
type,
signal: null,
});
const connectionState = behaviorSubject<
TRPCConnectionState<TRPCClientError<any>>
>({
type: 'state',
state: 'connecting',
error: null,
});
/* istanbul ignore if -- @preserve */
if (unsubscribed) {
// already unsubscribed - rare race condition
return;
}
eventSource = new EventSource(url, opts.eventSourceOptions);
const onStarted = () => {
const connectionSub = connectionState.subscribe({
next(state) {
observer.next({
result: {
type: 'started',
},
context: {
eventSource,
},
result: state,
});
},
});
run(async () => {
for await (const chunk of eventSourceStream) {
switch (chunk.type) {
case 'ping':
// do nothing
break;
case 'data':
const chunkData = chunk.data;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
eventSource!.removeEventListener('open', onStarted);
};
// console.log('starting', new Date());
eventSource.addEventListener('open', onStarted);
const iterable = sseStreamConsumer<Partial<SSEMessage>>({
from: eventSource,
deserialize: transformer.input.deserialize,
});
let result: TRPCResult<unknown>;
if (chunkData.id) {
// if the `tracked()`-helper is used, we always have an `id` field
lastEventId = chunkData.id;
result = {
id: chunkData.id,
data: chunkData,
};
} else {
result = {
data: chunkData.data,
};
}
for await (const chunk of iterable) {
// if the `sse({})`-helper is used, we always have an `id` field
const data = 'id' in chunk ? chunk : chunk.data;
observer.next({
result: {
data,
},
});
observer.next({
result,
context: {
eventSource: chunk.eventSource,
},
});
break;
case 'connected': {
observer.next({
result: {
type: 'started',
},
context: {
eventSource: chunk.eventSource,
},
});
connectionState.next({
type: 'state',
state: 'pending',
error: null,
});
break;
}
case 'serialized-error': {
const error = TRPCClientError.from({ error: chunk.error });
if (codes5xx.includes(chunk.error.code)) {
//
connectionState.next({
type: 'state',
state: 'connecting',
error,
});
break;
}
//
// non-retryable error, cancel the subscription
throw error;
}
case 'connecting': {
const lastState = connectionState.get();
const error = chunk.event && TRPCClientError.from(chunk.event);
if (!error && lastState.state === 'connecting') {
break;
}
connectionState.next({
type: 'state',
state: 'connecting',
error,
});
break;
}
case 'timeout': {
connectionState.next({
type: 'state',
state: 'connecting',
error: new TRPCClientError(
`Timeout of ${chunk.ms}ms reached while waiting for a response`,
),
});
}
}
}

@@ -130,4 +252,4 @@

observer.complete();
eventSource?.close();
unsubscribed = true;
ac.abort();
connectionSub.unsubscribe();
};

@@ -134,0 +256,0 @@ });

@@ -33,3 +33,3 @@ import type {

* The HTTP handler must separately allow overriding the method. See:
* @link https://trpc.io/docs/rpc
* @see https://trpc.io/docs/rpc
*/

@@ -158,2 +158,14 @@ methodOverride?: 'POST';

/**
* Polyfill for DOMException with AbortError name
*/
class AbortError extends Error {
constructor() {
const name = 'AbortError';
super(name);
this.name = name;
this.message = name;
}
}
export type HTTPRequestOptions = ContentOptions &

@@ -164,4 +176,26 @@ HTTPBaseRequestOptions & {

/**
* Polyfill for `signal.throwIfAborted()`
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/throwIfAborted
*/
const throwIfAborted = (signal: Maybe<AbortSignal>) => {
if (!signal?.aborted) {
return;
}
// If available, use the native implementation
signal.throwIfAborted?.();
// If we have `DOMException`, use it
if (typeof DOMException !== 'undefined') {
throw new DOMException('AbortError', 'AbortError');
}
// Otherwise, use our own implementation
throw new AbortError();
};
export async function fetchHTTPResponse(opts: HTTPRequestOptions) {
opts.signal?.throwIfAborted();
throwIfAborted(opts.signal);
const url = opts.getUrl(opts);

@@ -212,41 +246,1 @@ const body = opts.getBody(opts);

}
/**
* Merges multiple abort signals into a single one
* - When all signals have been aborted, the merged signal will be aborted
*/
export function mergeAbortSignals(
opts: {
signal: Maybe<AbortSignal>;
}[],
): AbortController {
const ac = new AbortController();
if (opts.some((o) => !o.signal)) {
return ac;
}
const count = opts.length;
let abortedCount = 0;
const onAbort = () => {
if (++abortedCount === count) {
ac.abort();
}
};
for (const o of opts) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const signal = o.signal!;
if (signal.aborted) {
onAbort();
} else {
signal.addEventListener('abort', onAbort, {
once: true,
});
}
}
return ac;
}

@@ -5,5 +5,11 @@ import { type TRPCRequestInfo } from '@trpc/server/http';

* Get the result of a value or function that returns a value
* It also optionally accepts typesafe arguments for the function
*/
export const resultOf = <T>(value: T | (() => T)): T => {
return typeof value === 'function' ? (value as () => T)() : value;
export const resultOf = <T, TArgs extends any[]>(
value: T | ((...args: TArgs) => T),
...args: TArgs
): T => {
return typeof value === 'function'
? (value as (...args: TArgs) => T)(...args)
: value;
};

@@ -14,3 +20,3 @@

*/
type CallbackOrValue<T> = T | (() => T | Promise<T>);
export type CallbackOrValue<T> = T | (() => T | Promise<T>);

@@ -17,0 +23,0 @@ export interface UrlOptionsWithConnectionParams {

@@ -9,3 +9,6 @@ /// <reference lib="dom.iterable" />

import { observable, tap } from '@trpc/server/observable';
import type { AnyRouter } from '@trpc/server/unstable-core-do-not-import';
import type {
AnyRouter,
InferrableClientTypes,
} from '@trpc/server/unstable-core-do-not-import';
import type { TRPCClientError } from '../TRPCClientError';

@@ -19,6 +22,8 @@ import type { Operation, OperationResultEnvelope, TRPCLink } from './types';

type EnableFnOptions<TRouter extends AnyRouter> =
type EnableFnOptions<TRouter extends InferrableClientTypes> =
| {
direction: 'down';
result: OperationResultEnvelope<unknown> | TRPCClientError<TRouter>;
result:
| OperationResultEnvelope<unknown, TRPCClientError<TRouter>>
| TRPCClientError<TRouter>;
}

@@ -39,3 +44,5 @@ | (Operation & {

direction: 'down';
result: OperationResultEnvelope<unknown> | TRPCClientError<TRouter>;
result:
| OperationResultEnvelope<unknown, TRPCClientError<TRouter>>
| TRPCClientError<TRouter>;
elapsedMs: number;

@@ -199,3 +206,4 @@ }

props.result &&
(props.result instanceof Error || 'error' in props.result.result)
(props.result instanceof Error ||
('error' in props.result.result && props.result.result.error))
? 'error'

@@ -208,3 +216,3 @@ : 'log';

/**
* @link https://trpc.io/docs/v11/client/links/loggerLink
* @see https://trpc.io/docs/v11/client/links/loggerLink
*/

@@ -227,3 +235,3 @@ export function loggerLink<TRouter extends AnyRouter = AnyRouter>(

// ->
enabled({ ...op, direction: 'up' }) &&
if (enabled({ ...op, direction: 'up' })) {
logger({

@@ -233,9 +241,12 @@ ...op,

});
}
const requestStartTime = Date.now();
function logResult(
result: OperationResultEnvelope<unknown> | TRPCClientError<TRouter>,
result:
| OperationResultEnvelope<unknown, TRPCClientError<TRouter>>
| TRPCClientError<TRouter>,
) {
const elapsedMs = Date.now() - requestStartTime;
enabled({ ...op, direction: 'down', result }) &&
if (enabled({ ...op, direction: 'down', result })) {
logger({

@@ -247,2 +258,3 @@ ...op,

});
}
}

@@ -249,0 +261,0 @@ return next(op)

@@ -10,2 +10,3 @@ import type { Observable, Observer } from '@trpc/server/observable';

import type { TRPCClientError } from '../TRPCClientError';
import type { TRPCConnectionState } from './internals/subscriptions';

@@ -62,6 +63,7 @@ export {

*/
export interface OperationResultEnvelope<TOutput> {
export interface OperationResultEnvelope<TOutput, TError> {
result:
| TRPCResultMessage<TOutput>['result']
| TRPCSuccessResponse<TOutput>['result'];
| TRPCSuccessResponse<TOutput>['result']
| TRPCConnectionState<TError>;
context?: OperationContext;

@@ -76,3 +78,6 @@ }

TOutput,
> = Observable<OperationResultEnvelope<TOutput>, TRPCClientError<TInferrable>>;
> = Observable<
OperationResultEnvelope<TOutput, TRPCClientError<TInferrable>>,
TRPCClientError<TInferrable>
>;

@@ -85,3 +90,6 @@ /**

TOutput,
> = Observer<OperationResultEnvelope<TOutput>, TRPCClientError<TInferrable>>;
> = Observer<
OperationResultEnvelope<TOutput, TRPCClientError<TInferrable>>,
TRPCClientError<TInferrable>
>;

@@ -88,0 +96,0 @@ /**

import type { Observer, UnsubscribeFn } from '@trpc/server/observable';
import { observable } from '@trpc/server/observable';
import { behaviorSubject, observable } from '@trpc/server/observable';
import type { TRPCConnectionParamsMessage } from '@trpc/server/rpc';

@@ -19,2 +19,3 @@ import type {

import { getTransformer } from '../unstable-internals';
import type { TRPCConnectionState } from './internals/subscriptions';
import {

@@ -56,2 +57,6 @@ resultOf,

/**
* Triggered when a WebSocket connection encounters an error
*/
onError?: (evt?: Event) => void;
/**
* Triggered when a WebSocket connection is closed

@@ -75,2 +80,21 @@ */

};
/**
* Send ping messages to the server and kill the connection if no pong message is returned
*/
keepAlive?: {
/**
* @default false
*/
enabled: boolean;
/**
* Send a ping message every this many milliseconds
* @default 5_000
*/
intervalMs?: number;
/**
* Close the WebSocket after this many milliseconds if the server does not respond
* @default 1_000
*/
pongTimeoutMs?: number;
};
}

@@ -83,2 +107,9 @@

};
/**
* @see https://trpc.io/docs/v11/client/links/wsLink
* @deprecated
* 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
* See https://github.com/trpc/trpc/issues/6109
*/
export function createWSClient(opts: WebSocketClientOptions) {

@@ -88,4 +119,2 @@ const {

retryDelayMs: retryDelayFn = exponentialBackoff,
onOpen,
onClose,
} = opts;

@@ -111,3 +140,3 @@ const lazyOpts: LazyOptions = {

type TCallbacks = WSCallbackObserver<AnyRouter, unknown>;
type TRequest = {
type WsRequest = {
/**

@@ -120,4 +149,8 @@ * Reference to the WebSocket instance this request was made to

op: Operation;
/**
* The last event id that the client has received
*/
lastEventId: string | undefined;
};
const pendingRequests: Record<number | string, TRequest> =
const pendingRequests: Record<number | string, WsRequest> =
Object.create(null);

@@ -150,2 +183,17 @@ let connectAttempt = 0;

const initState: TRPCConnectionState<TRPCClientError<AnyRouter>> =
activeConnection
? {
type: 'state',
state: 'connecting',
error: null,
}
: {
type: 'state',
state: 'idle',
error: null,
};
const connectionState =
behaviorSubject<TRPCConnectionState<TRPCClientError<AnyRouter>>>(initState);
/**

@@ -156,3 +204,3 @@ * tries to send the list of messages

if (!activeConnection) {
activeConnection = createConnection();
reconnect(null);
return;

@@ -183,3 +231,3 @@ }

}
function tryReconnect(conn: Connection) {
function tryReconnect(cause: Error | null) {
if (!!connectTimer) {

@@ -189,5 +237,4 @@ return;

conn.state = 'connecting';
const timeout = retryDelayFn(connectAttempt++);
reconnectInMs(timeout);
reconnectInMs(timeout, cause);
}

@@ -202,5 +249,5 @@ function hasPendingRequests(conn?: Connection) {

function reconnect() {
function reconnect(cause: Error | null) {
if (lazyOpts.enabled && !hasPendingRequests()) {
// Skip reconnecting if there are pending requests and we're in lazy mode
// Skip reconnecting if there aren't pending requests and we're in lazy mode
return;

@@ -210,9 +257,22 @@ }

activeConnection = createConnection();
oldConnection && closeIfNoPending(oldConnection);
if (oldConnection) {
closeIfNoPending(oldConnection);
}
const currentState = connectionState.get();
if (currentState.state !== 'connecting') {
connectionState.next({
type: 'state',
state: 'connecting',
error: cause ? TRPCClientError.from(cause) : null,
});
}
}
function reconnectInMs(ms: number) {
function reconnectInMs(ms: number, cause: Error | null) {
if (connectTimer) {
return;
}
connectTimer = setTimeout(reconnect, ms);
connectTimer = setTimeout(() => {
reconnect(cause);
}, ms);
}

@@ -226,7 +286,11 @@

}
function resumeSubscriptionOnReconnect(req: TRequest) {
function resumeSubscriptionOnReconnect(req: WsRequest) {
if (outgoing.some((r) => r.id === req.op.id)) {
return;
}
request(req.op, req.callbacks);
request({
op: req.op,
callbacks: req.callbacks,
lastEventId: req.lastEventId,
});
}

@@ -245,5 +309,10 @@

if (!hasPendingRequests(activeConnection)) {
if (!hasPendingRequests()) {
activeConnection.ws?.close();
activeConnection = null;
connectionState.next({
type: 'state',
state: 'idle',
error: null,
});
}

@@ -254,2 +323,4 @@ }, lazyOpts.closeMs);

function createConnection(): Connection {
let pingTimeout: ReturnType<typeof setTimeout> | undefined = undefined;
let pongTimeout: ReturnType<typeof setTimeout> | undefined = undefined;
const self: Connection = {

@@ -262,10 +333,54 @@ id: ++connectionIndex,

const onError = () => {
function destroy() {
const noop = () => {
// no-op
};
const { ws } = self;
if (ws) {
ws.onclose = noop;
ws.onerror = noop;
ws.onmessage = noop;
ws.onopen = noop;
ws.close();
}
self.state = 'closed';
if (self === activeConnection) {
tryReconnect(self);
}
const onCloseOrError = (cause: Error | null) => {
clearTimeout(pingTimeout);
clearTimeout(pongTimeout);
self.state = 'closed';
if (activeConnection === self) {
// connection might have been replaced already
tryReconnect(cause);
}
for (const [key, req] of Object.entries(pendingRequests)) {
if (req.connection !== self) {
continue;
}
// The connection was closed either unexpectedly or because of a reconnect
if (req.type === 'subscription') {
// Subscriptions will resume after we've reconnected
resumeSubscriptionOnReconnect(req);
} else {
// Queries and mutations will error if interrupted
delete pendingRequests[key];
req.callbacks.error?.(
TRPCClientError.from(cause ?? new TRPCWebSocketClosedError()),
);
}
}
};
run(async () => {
let url = await resultOf(opts.url);
const onError = (evt?: Event) => {
onCloseOrError(new TRPCWebSocketClosedError({ cause: evt }));
opts.onError?.(evt);
};
function connect(url: string) {
if (opts.connectionParams) {

@@ -283,3 +398,44 @@ // append `?connectionParams=1` when connection params are used

ws.addEventListener('open', () => {
ws.onopen = () => {
async function sendConnectionParams() {
if (!opts.connectionParams) {
return;
}
const connectMsg: TRPCConnectionParamsMessage = {
method: 'connectionParams',
data: await resultOf(opts.connectionParams),
};
ws.send(JSON.stringify(connectMsg));
}
function handleKeepAlive() {
if (!opts.keepAlive?.enabled) {
return;
}
const { pongTimeoutMs = 1_000, intervalMs = 5_000 } = opts.keepAlive;
const schedulePing = () => {
const schedulePongTimeout = () => {
pongTimeout = setTimeout(() => {
const wasOpen = self.state === 'open';
destroy();
if (wasOpen) {
opts.onClose?.();
}
}, pongTimeoutMs);
};
pingTimeout = setTimeout(() => {
ws.send('PING');
schedulePongTimeout();
}, intervalMs);
};
ws.addEventListener('message', () => {
clearTimeout(pingTimeout);
clearTimeout(pongTimeout);
schedulePing();
});
schedulePing();
}
run(async () => {

@@ -290,10 +446,5 @@ /* istanbul ignore next -- @preserve */

}
if (opts.connectionParams) {
const connectMsg: TRPCConnectionParamsMessage = {
method: 'connectionParams',
data: await resultOf(opts.connectionParams),
};
handleKeepAlive();
ws.send(JSON.stringify(connectMsg));
}
await sendConnectionParams();

@@ -303,14 +454,25 @@ connectAttempt = 0;

onOpen?.();
// Update connection state
connectionState.next({
type: 'state',
state: 'pending',
error: null,
});
opts.onOpen?.();
dispatch();
}).catch((cause) => {
}).catch((cause: unknown) => {
ws.close(
// "Status codes in the range 3000-3999 are reserved for use by libraries, frameworks, and applications"
3000,
cause,
);
onError();
onCloseOrError(
new TRPCWebSocketClosedError({
message: 'Initialization error',
cause,
}),
);
});
});
ws.addEventListener('error', onError);
};
ws.onerror = onError;
const handleIncomingRequest = (req: TRPCClientIncomingRequest) => {

@@ -322,3 +484,7 @@ if (self !== activeConnection) {

if (req.method === 'reconnect') {
reconnect();
reconnect(
new TRPCWebSocketClosedError({
message: 'Server requested reconnect',
}),
);
// notify subscribers

@@ -341,10 +507,19 @@ for (const pendingReq of Object.values(pendingRequests)) {

if (self === activeConnection && req.connection !== activeConnection) {
// gracefully replace old connection with this
const oldConn = req.connection;
// gracefully replace old connection with a new connection
req.connection = self;
oldConn && closeIfNoPending(oldConn);
}
if (req.connection !== self) {
// the connection has been replaced
return;
}
if (
'result' in data &&
data.result.type === 'data' &&
typeof data.result.id === 'string'
) {
req.lastEventId = data.result.id;
}
if (
'result' in data &&
data.result.type === 'stopped' &&

@@ -356,3 +531,12 @@ activeConnection === self

};
ws.addEventListener('message', ({ data }) => {
ws.onmessage = (event) => {
const { data } = event;
if (data === 'PONG') {
return;
}
if (data === 'PING') {
ws.send('PONG');
return;
}
startLazyDisconnectTimer();

@@ -371,46 +555,30 @@

}
});
};
ws.addEventListener('close', ({ code }) => {
if (self.state === 'open') {
onClose?.({ code });
}
self.state = 'closed';
ws.onclose = (event) => {
const wasOpen = self.state === 'open';
if (activeConnection === self) {
// connection might have been replaced already
tryReconnect(self);
destroy();
onCloseOrError(new TRPCWebSocketClosedError({ cause: event }));
if (wasOpen) {
opts.onClose?.(event);
}
};
}
for (const [key, req] of Object.entries(pendingRequests)) {
if (req.connection !== self) {
continue;
}
if (self.state === 'closed') {
// If the connection was closed, we just call `complete()` on the request
delete pendingRequests[key];
req.callbacks.complete?.();
continue;
}
// The connection was closed either unexpectedly or because of a reconnect
if (req.type === 'subscription') {
// Subscriptions will resume after we've reconnected
resumeSubscriptionOnReconnect(req);
} else {
// Queries and mutations will error if interrupted
delete pendingRequests[key];
req.callbacks.error?.(
TRPCClientError.from(
new TRPCWebSocketClosedError('WebSocket closed prematurely'),
),
);
}
}
Promise.resolve(resultOf(opts.url))
.then(connect)
.catch(() => {
onCloseOrError(new Error('Failed to resolve url'));
});
}).catch(onError);
return self;
}
function request(op: Operation, callbacks: TCallbacks): UnsubscribeFn {
function request(opts: {
op: Operation;
callbacks: TCallbacks;
lastEventId: string | undefined;
}): UnsubscribeFn {
const { op, callbacks, lastEventId } = opts;
const { type, input, path, id } = op;

@@ -423,4 +591,6 @@ const envelope: TRPCRequestMessage = {

path,
lastEventId,
},
};
pendingRequests[id] = {

@@ -431,2 +601,3 @@ connection: null,

op,
lastEventId,
};

@@ -455,2 +626,3 @@

}
return {

@@ -467,3 +639,5 @@ close: () => {

TRPCClientError.from(
new Error('Closed before connection was established'),
new TRPCWebSocketClosedError({
message: 'Closed before connection was established',
}),
),

@@ -473,3 +647,5 @@ );

}
activeConnection && closeIfNoPending(activeConnection);
if (activeConnection) {
closeIfNoPending(activeConnection);
}
clearTimeout(connectTimer);

@@ -487,6 +663,20 @@ connectTimer = undefined;

reconnect,
connectionState: connectionState,
};
}
/**
* @see https://trpc.io/docs/v11/client/links/wsLink
* @deprecated
* 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
* See https://github.com/trpc/trpc/issues/6109
*/
export type TRPCWebSocketClient = ReturnType<typeof createWSClient>;
/**
* @see https://trpc.io/docs/v11/client/links/wsLink
* @deprecated
* 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
* See https://github.com/trpc/trpc/issues/6109
*/
export type WebSocketLinkOptions<TRouter extends AnyRouter> = {

@@ -496,4 +686,11 @@ client: TRPCWebSocketClient;

class TRPCWebSocketClosedError extends Error {
constructor(message: string) {
super(message);
constructor(opts?: { cause?: unknown; message?: string }) {
super(
opts?.message ?? 'WebSocket closed',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore https://github.com/tc39/proposal-error-cause
{
cause: opts?.cause,
},
);
this.name = 'TRPCWebSocketClosedError';

@@ -505,3 +702,6 @@ Object.setPrototypeOf(this, TRPCWebSocketClosedError.prototype);

/**
* @link https://trpc.io/docs/v11/client/links/wsLink
* @see https://trpc.io/docs/v11/client/links/wsLink
* @deprecated
* 🙋‍♂️ **Contributors needed** to continue supporting WebSockets!
* See https://github.com/trpc/trpc/issues/6109
*/

@@ -520,8 +720,19 @@ export function wsLink<TRouter extends AnyRouter>(

const unsub = client.request(
{ type, path, input, id, context, signal: null },
{
const connState =
type === 'subscription'
? client.connectionState.subscribe({
next(result) {
observer.next({
result,
context,
});
},
})
: null;
const unsubscribeRequest = client.request({
op: { type, path, input, id, context, signal: null },
callbacks: {
error(err) {
observer.error(err as TRPCClientError<any>);
unsub();
observer.error(err);
unsubscribeRequest();
},

@@ -531,4 +742,4 @@ complete() {

},
next(message) {
const transformed = transformResult(message, transformer.output);
next(event) {
const transformed = transformResult(event, transformer.output);

@@ -546,3 +757,3 @@ if (!transformed.ok) {

unsub();
unsubscribeRequest();
observer.complete();

@@ -552,5 +763,7 @@ }

},
);
lastEventId: undefined,
});
return () => {
unsub();
unsubscribeRequest();
connState?.unsubscribe();
};

@@ -557,0 +770,0 @@ });

@@ -93,3 +93,3 @@ import type {

public static from<TRouterOrProcedure extends InferrableClientTypes>(
_cause: Error | TRPCErrorResponse<any>,
_cause: Error | TRPCErrorResponse<any> | object,
opts: { meta?: Record<string, unknown> } = {},

@@ -96,0 +96,0 @@ ): TRPCClientError<TRouterOrProcedure> {

export * from './internals/transformer';
export * from './links/internals/subscriptions';

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc