@tanstack/start-client-core 1.132.0-alpha.3 → 1.132.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/constants.d.ts +3 -1
- package/dist/esm/constants.js +6 -2
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/createClientRpc.d.ts +2 -0
- package/dist/esm/createClientRpc.js +3 -1
- package/dist/esm/createClientRpc.js.map +1 -1
- package/dist/esm/envOnly.d.ts +2 -2
- package/dist/esm/envOnly.js +4 -4
- package/dist/esm/envOnly.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +8 -6
- package/dist/esm/serializer/ServerFunctionSerializationAdapter.js +2 -1
- package/dist/esm/serializer/ServerFunctionSerializationAdapter.js.map +1 -1
- package/dist/esm/serializer/getDefaultSerovalPlugins.js +1 -1
- package/dist/esm/serializer/getDefaultSerovalPlugins.js.map +1 -1
- package/dist/esm/serverFnFetcher.js +6 -6
- package/dist/esm/serverFnFetcher.js.map +1 -1
- package/package.json +3 -3
- package/src/constants.ts +4 -1
- package/src/createClientRpc.ts +2 -0
- package/src/envOnly.ts +2 -2
- package/src/index.tsx +6 -2
- package/src/serializer/ServerFunctionSerializationAdapter.ts +2 -2
- package/src/serializer/getDefaultSerovalPlugins.ts +1 -1
- package/src/serverFnFetcher.ts +7 -7
- package/src/tests/envOnly.test-d.ts +9 -9
package/dist/esm/constants.d.ts
CHANGED
package/dist/esm/constants.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
const
|
|
1
|
+
const TSS_FORMDATA_CONTEXT = "__TSS_CONTEXT";
|
|
2
|
+
const TSS_SERVER_FUNCTION = Symbol.for("TSS_SERVER_FUNCTION");
|
|
3
|
+
const X_TSS_SERIALIZED = "x-tss-serialized";
|
|
2
4
|
export {
|
|
3
|
-
|
|
5
|
+
TSS_FORMDATA_CONTEXT,
|
|
6
|
+
TSS_SERVER_FUNCTION,
|
|
7
|
+
X_TSS_SERIALIZED
|
|
4
8
|
};
|
|
5
9
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sources":["../../src/constants.ts"],"sourcesContent":["export const
|
|
1
|
+
{"version":3,"file":"constants.js","sources":["../../src/constants.ts"],"sourcesContent":["export const TSS_FORMDATA_CONTEXT = '__TSS_CONTEXT'\nexport const TSS_SERVER_FUNCTION = Symbol.for('TSS_SERVER_FUNCTION')\n\nexport const X_TSS_SERIALIZED = 'x-tss-serialized'\nexport {}\n"],"names":[],"mappings":"AAAO,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB,OAAO,IAAI,qBAAqB;AAE5D,MAAM,mBAAmB;"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TSS_SERVER_FUNCTION } from "./constants.js";
|
|
1
2
|
import { serverFnFetcher } from "./serverFnFetcher.js";
|
|
2
3
|
let baseUrl;
|
|
3
4
|
function sanitizeBase(base) {
|
|
@@ -15,7 +16,8 @@ const createClientRpc = (functionId) => {
|
|
|
15
16
|
};
|
|
16
17
|
return Object.assign(clientFn, {
|
|
17
18
|
url,
|
|
18
|
-
functionId
|
|
19
|
+
functionId,
|
|
20
|
+
[TSS_SERVER_FUNCTION]: true
|
|
19
21
|
});
|
|
20
22
|
};
|
|
21
23
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createClientRpc.js","sources":["../../src/createClientRpc.ts"],"sourcesContent":["import { serverFnFetcher } from './serverFnFetcher'\n\nlet baseUrl: string\nfunction sanitizeBase(base: string) {\n return base.replace(/^\\/|\\/$/g, '')\n}\n\nexport const createClientRpc = (functionId: string) => {\n if (!baseUrl) {\n const sanitizedAppBase = sanitizeBase(process.env.TSS_APP_BASE || '/')\n const sanitizedServerBase = sanitizeBase(process.env.TSS_SERVER_FN_BASE!)\n baseUrl = `${sanitizedAppBase ? `/${sanitizedAppBase}` : ''}/${sanitizedServerBase}/`\n }\n const url = baseUrl + functionId\n\n const clientFn = (...args: Array<any>) => {\n return serverFnFetcher(url, args, fetch)\n }\n\n return Object.assign(clientFn, {\n url,\n functionId,\n })\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"createClientRpc.js","sources":["../../src/createClientRpc.ts"],"sourcesContent":["import { TSS_SERVER_FUNCTION } from './constants'\nimport { serverFnFetcher } from './serverFnFetcher'\n\nlet baseUrl: string\nfunction sanitizeBase(base: string) {\n return base.replace(/^\\/|\\/$/g, '')\n}\n\nexport const createClientRpc = (functionId: string) => {\n if (!baseUrl) {\n const sanitizedAppBase = sanitizeBase(process.env.TSS_APP_BASE || '/')\n const sanitizedServerBase = sanitizeBase(process.env.TSS_SERVER_FN_BASE!)\n baseUrl = `${sanitizedAppBase ? `/${sanitizedAppBase}` : ''}/${sanitizedServerBase}/`\n }\n const url = baseUrl + functionId\n\n const clientFn = (...args: Array<any>) => {\n return serverFnFetcher(url, args, fetch)\n }\n\n return Object.assign(clientFn, {\n url,\n functionId,\n [TSS_SERVER_FUNCTION]: true,\n })\n}\n"],"names":[],"mappings":";;AAGA,IAAI;AACJ,SAAS,aAAa,MAAc;AAClC,SAAO,KAAK,QAAQ,YAAY,EAAE;AACpC;AAEO,MAAM,kBAAkB,CAAC,eAAuB;AACrD,MAAI,CAAC,SAAS;AACZ,UAAM,mBAAmB,aAAa,QAAQ,IAAI,gBAAgB,GAAG;AACrE,UAAM,sBAAsB,aAAa,QAAQ,IAAI,kBAAmB;AACxE,cAAU,GAAG,mBAAmB,IAAI,gBAAgB,KAAK,EAAE,IAAI,mBAAmB;AAAA,EACpF;AACA,QAAM,MAAM,UAAU;AAEtB,QAAM,WAAW,IAAI,SAAqB;AACxC,WAAO,gBAAgB,KAAK,MAAM,KAAK;AAAA,EACzC;AAEA,SAAO,OAAO,OAAO,UAAU;AAAA,IAC7B;AAAA,IACA;AAAA,IACA,CAAC,mBAAmB,GAAG;AAAA,EAAA,CACxB;AACH;"}
|
package/dist/esm/envOnly.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
type EnvOnlyFn = <TFn extends (...args: Array<any>) => any>(fn: TFn) => TFn;
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
2
|
+
export declare const createServerOnlyFn: EnvOnlyFn;
|
|
3
|
+
export declare const createClientOnlyFn: EnvOnlyFn;
|
|
4
4
|
export {};
|
package/dist/esm/envOnly.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
1
|
+
const createServerOnlyFn = (fn) => fn;
|
|
2
|
+
const createClientOnlyFn = (fn) => fn;
|
|
3
3
|
export {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
createClientOnlyFn,
|
|
5
|
+
createServerOnlyFn
|
|
6
6
|
};
|
|
7
7
|
//# sourceMappingURL=envOnly.js.map
|
package/dist/esm/envOnly.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envOnly.js","sources":["../../src/envOnly.ts"],"sourcesContent":["type EnvOnlyFn = <TFn extends (...args: Array<any>) => any>(fn: TFn) => TFn\n\n// A function that will only be available in the server build\n// If called on the client, it will throw an error\nexport const
|
|
1
|
+
{"version":3,"file":"envOnly.js","sources":["../../src/envOnly.ts"],"sourcesContent":["type EnvOnlyFn = <TFn extends (...args: Array<any>) => any>(fn: TFn) => TFn\n\n// A function that will only be available in the server build\n// If called on the client, it will throw an error\nexport const createServerOnlyFn: EnvOnlyFn = (fn) => fn\n\n// A function that will only be available in the client build\n// If called on the server, it will throw an error\nexport const createClientOnlyFn: EnvOnlyFn = (fn) => fn\n"],"names":[],"mappings":"AAIO,MAAM,qBAAgC,CAAC,OAAO;AAI9C,MAAM,qBAAgC,CAAC,OAAO;"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export type { DehydratedRouter, JsonResponse, } from '@tanstack/router-core/ssr/
|
|
|
2
2
|
export { hydrate, json, mergeHeaders } from '@tanstack/router-core/ssr/client';
|
|
3
3
|
export type { Serializable, SerializerParse, SerializerParseBy, SerializerStringify, SerializerStringifyBy, SerializerExtensions, } from './serializer.js';
|
|
4
4
|
export { createIsomorphicFn, type IsomorphicFn, type ServerOnlyFn, type ClientOnlyFn, type IsomorphicFnBase, } from './createIsomorphicFn.js';
|
|
5
|
-
export {
|
|
5
|
+
export { createServerOnlyFn, createClientOnlyFn } from './envOnly.js';
|
|
6
6
|
export { createServerFn } from './createServerFn.js';
|
|
7
7
|
export { createMiddleware, type IntersectAllValidatorInputs, type IntersectAllValidatorOutputs, type FunctionMiddlewareServerFn, type AnyFunctionMiddleware, type FunctionMiddlewareOptions, type FunctionMiddlewareWithTypes, type FunctionMiddlewareValidator, type FunctionMiddlewareServer, type FunctionMiddlewareAfterClient, type FunctionMiddlewareAfterServer, type FunctionMiddleware, type FunctionMiddlewareClientFnOptions, type FunctionMiddlewareClientFnResult, type FunctionMiddlewareClientNextFn, type FunctionClientResultWithContext, type AssignAllClientContextBeforeNext, type AssignAllMiddleware, type AssignAllServerContext, type FunctionMiddlewareAfterValidator, type FunctionMiddlewareClientFn, type FunctionMiddlewareServerFnResult, type FunctionMiddlewareClient, type FunctionMiddlewareServerFnOptions, type FunctionMiddlewareServerNextFn, type FunctionServerResultWithContext, type AnyRequestMiddleware, } from './createMiddleware.js';
|
|
8
8
|
export { registerGlobalMiddleware, globalMiddleware, } from './registerGlobalMiddleware.js';
|
|
@@ -10,4 +10,4 @@ export type { CompiledFetcherFnOptions, CompiledFetcherFn, Fetcher, RscStream, F
|
|
|
10
10
|
export { applyMiddleware, execValidator, serverFnBaseToMiddleware, flattenMiddlewares, executeMiddleware, } from './createServerFn.js';
|
|
11
11
|
export { createClientRpc } from './createClientRpc.js';
|
|
12
12
|
export { getDefaultSerovalPlugins } from './serializer/getDefaultSerovalPlugins.js';
|
|
13
|
-
export {
|
|
13
|
+
export { TSS_FORMDATA_CONTEXT, TSS_SERVER_FUNCTION, X_TSS_SERIALIZED, } from './constants.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { hydrate, json, mergeHeaders } from "@tanstack/router-core/ssr/client";
|
|
2
2
|
import { createIsomorphicFn } from "./createIsomorphicFn.js";
|
|
3
|
-
import {
|
|
3
|
+
import { createClientOnlyFn, createServerOnlyFn } from "./envOnly.js";
|
|
4
4
|
import { applyMiddleware, createServerFn, execValidator, executeMiddleware, flattenMiddlewares, serverFnBaseToMiddleware } from "./createServerFn.js";
|
|
5
5
|
import { createMiddleware } from "./createMiddleware.js";
|
|
6
6
|
import { globalMiddleware, registerGlobalMiddleware } from "./registerGlobalMiddleware.js";
|
|
7
7
|
import { createClientRpc } from "./createClientRpc.js";
|
|
8
8
|
import { getDefaultSerovalPlugins } from "./serializer/getDefaultSerovalPlugins.js";
|
|
9
|
-
import {
|
|
9
|
+
import { TSS_FORMDATA_CONTEXT, TSS_SERVER_FUNCTION, X_TSS_SERIALIZED } from "./constants.js";
|
|
10
10
|
export {
|
|
11
|
-
|
|
11
|
+
TSS_FORMDATA_CONTEXT,
|
|
12
|
+
TSS_SERVER_FUNCTION,
|
|
13
|
+
X_TSS_SERIALIZED,
|
|
12
14
|
applyMiddleware,
|
|
13
|
-
|
|
15
|
+
createClientOnlyFn,
|
|
14
16
|
createClientRpc,
|
|
15
17
|
createIsomorphicFn,
|
|
16
18
|
createMiddleware,
|
|
17
19
|
createServerFn,
|
|
20
|
+
createServerOnlyFn,
|
|
18
21
|
execValidator,
|
|
19
22
|
executeMiddleware,
|
|
20
23
|
flattenMiddlewares,
|
|
@@ -24,7 +27,6 @@ export {
|
|
|
24
27
|
json,
|
|
25
28
|
mergeHeaders,
|
|
26
29
|
registerGlobalMiddleware,
|
|
27
|
-
serverFnBaseToMiddleware
|
|
28
|
-
serverOnly
|
|
30
|
+
serverFnBaseToMiddleware
|
|
29
31
|
};
|
|
30
32
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createSerializationAdapter } from "@tanstack/router-core";
|
|
2
2
|
import { createClientRpc } from "../createClientRpc.js";
|
|
3
|
+
import { TSS_SERVER_FUNCTION } from "../constants.js";
|
|
3
4
|
const ServerFunctionSerializationAdapter = createSerializationAdapter({
|
|
4
5
|
key: "$TSS/serverfn",
|
|
5
|
-
test: (v) =>
|
|
6
|
+
test: (v) => v[TSS_SERVER_FUNCTION],
|
|
6
7
|
toSerializable: ({ functionId }) => ({ functionId }),
|
|
7
8
|
fromSerializable: ({ functionId }) => createClientRpc(functionId)
|
|
8
9
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerFunctionSerializationAdapter.js","sources":["../../../src/serializer/ServerFunctionSerializationAdapter.ts"],"sourcesContent":["import { createSerializationAdapter } from '@tanstack/router-core'\nimport { createClientRpc } from '../createClientRpc'\n\nexport const ServerFunctionSerializationAdapter = createSerializationAdapter({\n key: '$TSS/serverfn',\n test: (v): v is { functionId: string }
|
|
1
|
+
{"version":3,"file":"ServerFunctionSerializationAdapter.js","sources":["../../../src/serializer/ServerFunctionSerializationAdapter.ts"],"sourcesContent":["import { createSerializationAdapter } from '@tanstack/router-core'\nimport { createClientRpc } from '../createClientRpc'\nimport { TSS_SERVER_FUNCTION } from '../constants'\n\nexport const ServerFunctionSerializationAdapter = createSerializationAdapter({\n key: '$TSS/serverfn',\n test: (v): v is { functionId: string } => v[TSS_SERVER_FUNCTION],\n toSerializable: ({ functionId }) => ({ functionId }),\n fromSerializable: ({ functionId }) => createClientRpc(functionId),\n})\n"],"names":[],"mappings":";;;AAIO,MAAM,qCAAqC,2BAA2B;AAAA,EAC3E,KAAK;AAAA,EACL,MAAM,CAAC,MAAmC,EAAE,mBAAmB;AAAA,EAC/D,gBAAgB,CAAC,EAAE,kBAAkB,EAAE,WAAA;AAAA,EACvC,kBAAkB,CAAC,EAAE,WAAA,MAAiB,gBAAgB,UAAU;AAClE,CAAC;"}
|
|
@@ -10,7 +10,7 @@ function getDefaultSerovalPlugins() {
|
|
|
10
10
|
const router = getRouterInstance();
|
|
11
11
|
invariant(router, "Expected router instance to be available");
|
|
12
12
|
const adapters = router.options.serializationAdapters;
|
|
13
|
-
return [...
|
|
13
|
+
return [...adapters?.map(makeSerovalPlugin) ?? [], ...defaultSerovalPlugins];
|
|
14
14
|
}
|
|
15
15
|
export {
|
|
16
16
|
defaultSerovalPlugins,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getDefaultSerovalPlugins.js","sources":["../../../src/serializer/getDefaultSerovalPlugins.ts"],"sourcesContent":["import invariant from 'tiny-invariant'\nimport {\n makeSerovalPlugin,\n defaultSerovalPlugins as routerDefaultSerovalPlugins,\n} from '@tanstack/router-core'\nimport { FormDataPlugin } from 'seroval-plugins/web'\nimport { getRouterInstance } from '../getRouterInstance'\nimport type { AnyTransformer } from '@tanstack/router-core'\n\nimport type { Plugin } from 'seroval'\n\nexport const defaultSerovalPlugins = [\n ...routerDefaultSerovalPlugins,\n FormDataPlugin as Plugin<FormData, any>,\n]\n\nexport function getDefaultSerovalPlugins() {\n const router = getRouterInstance()\n invariant(router, 'Expected router instance to be available')\n const adapters = router.options.serializationAdapters as\n | Array<AnyTransformer>\n | undefined\n return [...
|
|
1
|
+
{"version":3,"file":"getDefaultSerovalPlugins.js","sources":["../../../src/serializer/getDefaultSerovalPlugins.ts"],"sourcesContent":["import invariant from 'tiny-invariant'\nimport {\n makeSerovalPlugin,\n defaultSerovalPlugins as routerDefaultSerovalPlugins,\n} from '@tanstack/router-core'\nimport { FormDataPlugin } from 'seroval-plugins/web'\nimport { getRouterInstance } from '../getRouterInstance'\nimport type { AnyTransformer } from '@tanstack/router-core'\n\nimport type { Plugin } from 'seroval'\n\nexport const defaultSerovalPlugins = [\n ...routerDefaultSerovalPlugins,\n FormDataPlugin as Plugin<FormData, any>,\n]\n\nexport function getDefaultSerovalPlugins() {\n const router = getRouterInstance()\n invariant(router, 'Expected router instance to be available')\n const adapters = router.options.serializationAdapters as\n | Array<AnyTransformer>\n | undefined\n return [...(adapters?.map(makeSerovalPlugin) ?? []), ...defaultSerovalPlugins]\n}\n"],"names":["routerDefaultSerovalPlugins"],"mappings":";;;;AAWO,MAAM,wBAAwB;AAAA,EACnC,GAAGA;AAAAA,EACH;AACF;AAEO,SAAS,2BAA2B;AACzC,QAAM,SAAS,kBAAA;AACf,YAAU,QAAQ,0CAA0C;AAC5D,QAAM,WAAW,OAAO,QAAQ;AAGhC,SAAO,CAAC,GAAI,UAAU,IAAI,iBAAiB,KAAK,CAAA,GAAK,GAAG,qBAAqB;AAC/E;"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { isPlainObject, encode, parseRedirect, isNotFound } from "@tanstack/router-core";
|
|
2
|
-
import {
|
|
2
|
+
import { fromCrossJSON, toJSONAsync } from "seroval";
|
|
3
3
|
import invariant from "tiny-invariant";
|
|
4
4
|
import { getClientSerovalPlugins } from "./serializer/getClientSerovalPlugins.js";
|
|
5
|
-
import {
|
|
5
|
+
import { X_TSS_SERIALIZED, TSS_FORMDATA_CONTEXT } from "./constants.js";
|
|
6
6
|
let serovalPlugins = null;
|
|
7
7
|
async function serverFnFetcher(url, args, handler) {
|
|
8
8
|
if (!serovalPlugins) {
|
|
@@ -78,7 +78,7 @@ async function serialize(data) {
|
|
|
78
78
|
async function getFetcherRequestOptions(opts) {
|
|
79
79
|
if (opts.method === "POST") {
|
|
80
80
|
if (opts.data instanceof FormData) {
|
|
81
|
-
opts.data.set(
|
|
81
|
+
opts.data.set(TSS_FORMDATA_CONTEXT, await serialize(opts.context));
|
|
82
82
|
return {
|
|
83
83
|
body: opts.data
|
|
84
84
|
};
|
|
@@ -102,11 +102,11 @@ async function getResponse(fn) {
|
|
|
102
102
|
})();
|
|
103
103
|
const contentType = response.headers.get("content-type");
|
|
104
104
|
invariant(contentType, "expected content-type header to be set");
|
|
105
|
-
const serializedByStart = !!response.headers.get(
|
|
105
|
+
const serializedByStart = !!response.headers.get(X_TSS_SERIALIZED);
|
|
106
106
|
if (!response.ok) {
|
|
107
107
|
if (serializedByStart && contentType.includes("application/json")) {
|
|
108
108
|
const jsonPayload = await response.json();
|
|
109
|
-
const result =
|
|
109
|
+
const result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins });
|
|
110
110
|
throw result;
|
|
111
111
|
}
|
|
112
112
|
throw new Error(await response.text());
|
|
@@ -125,7 +125,7 @@ async function getResponse(fn) {
|
|
|
125
125
|
}
|
|
126
126
|
if (contentType.includes("application/json")) {
|
|
127
127
|
const jsonPayload = await response.json();
|
|
128
|
-
result =
|
|
128
|
+
result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins });
|
|
129
129
|
}
|
|
130
130
|
invariant(result, "expected result to be resolved");
|
|
131
131
|
if (result instanceof Error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverFnFetcher.js","sources":["../../src/serverFnFetcher.ts"],"sourcesContent":["import {\n encode,\n isNotFound,\n isPlainObject,\n parseRedirect,\n} from '@tanstack/router-core'\nimport { fromCrossJSON, fromJSON, toJSONAsync } from 'seroval'\nimport invariant from 'tiny-invariant'\nimport { getClientSerovalPlugins } from './serializer/getClientSerovalPlugins'\nimport { TSR_FORMDATA_CONTEXT } from './constants'\nimport type { FunctionMiddlewareClientFnOptions } from './createMiddleware'\nimport type { Plugin as SerovalPlugin } from 'seroval'\n\nlet serovalPlugins: Array<SerovalPlugin<any, any>> | null = null\n\nexport async function serverFnFetcher(\n url: string,\n args: Array<any>,\n handler: (url: string, requestInit: RequestInit) => Promise<Response>,\n) {\n if (!serovalPlugins) {\n serovalPlugins = getClientSerovalPlugins()\n }\n const _first = args[0]\n\n // If createServerFn was used to wrap the fetcher,\n // We need to handle the arguments differently\n if (isPlainObject(_first) && _first.method) {\n const first = _first as FunctionMiddlewareClientFnOptions<\n any,\n any,\n any,\n any\n > & {\n headers: HeadersInit\n }\n const type = first.data instanceof FormData ? 'formData' : 'payload'\n\n // Arrange the headers\n const headers = new Headers({\n 'x-tsr-redirect': 'manual',\n ...(type === 'payload'\n ? {\n 'content-type': 'application/json',\n accept: 'application/x-ndjson, application/json',\n }\n : {}),\n ...(first.headers instanceof Headers\n ? Object.fromEntries(first.headers.entries())\n : first.headers),\n })\n\n // If the method is GET, we need to move the payload to the query string\n if (first.method === 'GET') {\n const encodedPayload = encode({\n payload: await serializePayload(first),\n })\n\n if (encodedPayload) {\n if (url.includes('?')) {\n url += `&${encodedPayload}`\n } else {\n url += `?${encodedPayload}`\n }\n }\n }\n\n if (url.includes('?')) {\n url += `&createServerFn`\n } else {\n url += `?createServerFn`\n }\n if (first.response === 'raw') {\n url += `&raw`\n }\n\n return await getResponse(async () =>\n handler(url, {\n method: first.method,\n headers,\n signal: first.signal,\n ...(await getFetcherRequestOptions(first)),\n }),\n )\n }\n\n // If not a custom fetcher, it was probably\n // a `use server` function, so just proxy the arguments\n // through as a POST request\n return await getResponse(() =>\n handler(url, {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(args),\n }),\n )\n}\n\nasync function serializePayload(\n opts: FunctionMiddlewareClientFnOptions<any, any, any, any>,\n) {\n const payloadToSerialize: any = {}\n if (opts.data) {\n payloadToSerialize['data'] = opts.data\n }\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (opts.context && Object.keys(opts.context).length > 0) {\n payloadToSerialize['context'] = opts.context\n }\n\n return serialize(payloadToSerialize)\n}\n\nasync function serialize(data: any) {\n return JSON.stringify(\n await Promise.resolve(toJSONAsync(data, { plugins: serovalPlugins! })),\n )\n}\n\nasync function getFetcherRequestOptions(\n opts: FunctionMiddlewareClientFnOptions<any, any, any, any>,\n) {\n if (opts.method === 'POST') {\n if (opts.data instanceof FormData) {\n opts.data.set(TSR_FORMDATA_CONTEXT, await serialize(opts.context))\n return {\n body: opts.data,\n }\n }\n\n return {\n body: await serializePayload(opts),\n }\n }\n\n return {}\n}\n\n/**\n * Retrieves a response from a given function and manages potential errors\n * and special response types including redirects and not found errors.\n *\n * @param fn - The function to execute for obtaining the response.\n * @returns The processed response from the function.\n * @throws If the response is invalid or an error occurs during processing.\n */\nasync function getResponse(fn: () => Promise<Response>) {\n const response = await (async () => {\n try {\n return await fn()\n } catch (error) {\n if (error instanceof Response) {\n return error\n }\n\n throw error\n }\n })()\n\n const contentType = response.headers.get('content-type')\n invariant(contentType, 'expected content-type header to be set')\n const serializedByStart = !!response.headers.get('x-tss-serialized')\n // If the response is not ok, throw an error\n if (!response.ok) {\n if (serializedByStart && contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n const result = fromJSON(jsonPayload, { plugins: serovalPlugins! })\n throw result\n }\n\n throw new Error(await response.text())\n }\n\n if (serializedByStart) {\n let result\n if (contentType.includes('application/x-ndjson')) {\n const refs = new Map()\n result = await processServerFnResponse({\n response,\n onMessage: (msg) =>\n fromCrossJSON(msg, { refs, plugins: serovalPlugins! }),\n onError(msg, error) {\n // TODO how could we notify consumer that an error occured?\n console.error(msg, error)\n },\n })\n }\n if (contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n result = fromJSON(jsonPayload, { plugins: serovalPlugins! })\n }\n invariant(result, 'expected result to be resolved')\n if (result instanceof Error) {\n throw result\n }\n return result\n }\n\n if (contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n const redirect = parseRedirect(jsonPayload)\n if (redirect) {\n throw redirect\n }\n if (isNotFound(jsonPayload)) {\n throw jsonPayload\n }\n return jsonPayload\n }\n\n return response\n}\n\nasync function processServerFnResponse({\n response,\n onMessage,\n onError,\n}: {\n response: Response\n onMessage: (msg: any) => any\n onError?: (msg: string, error?: any) => void\n}) {\n if (!response.body) {\n throw new Error('No response body')\n }\n\n const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()\n\n let buffer = ''\n let firstRead = false\n let firstObject\n\n while (!firstRead) {\n const { value, done } = await reader.read()\n if (value) buffer += value\n\n if (buffer.length === 0 && done) {\n throw new Error('Stream ended before first object')\n }\n\n // common case: buffer ends with newline\n if (buffer.endsWith('\\n')) {\n const lines = buffer.split('\\n').filter(Boolean)\n const firstLine = lines[0]\n if (!firstLine) throw new Error('No JSON line in the first chunk')\n firstObject = JSON.parse(firstLine)\n firstRead = true\n buffer = lines.slice(1).join('\\n')\n } else {\n // fallback: wait for a newline to parse first object safely\n const newlineIndex = buffer.indexOf('\\n')\n if (newlineIndex >= 0) {\n const line = buffer.slice(0, newlineIndex).trim()\n buffer = buffer.slice(newlineIndex + 1)\n if (line.length > 0) {\n firstObject = JSON.parse(line)\n firstRead = true\n }\n }\n }\n }\n\n // process rest of the stream asynchronously\n ;(async () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { value, done } = await reader.read()\n if (value) buffer += value\n\n const lastNewline = buffer.lastIndexOf('\\n')\n if (lastNewline >= 0) {\n const chunk = buffer.slice(0, lastNewline)\n buffer = buffer.slice(lastNewline + 1)\n const lines = chunk.split('\\n').filter(Boolean)\n\n for (const line of lines) {\n try {\n onMessage(JSON.parse(line))\n } catch (e) {\n onError?.(`Invalid JSON line: ${line}`, e)\n }\n }\n }\n\n if (done) {\n break\n }\n }\n } catch (err) {\n onError?.('Stream processing error:', err)\n }\n })()\n\n return onMessage(firstObject)\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"serverFnFetcher.js","sources":["../../src/serverFnFetcher.ts"],"sourcesContent":["import {\n encode,\n isNotFound,\n isPlainObject,\n parseRedirect,\n} from '@tanstack/router-core'\nimport { fromCrossJSON, toJSONAsync } from 'seroval'\nimport invariant from 'tiny-invariant'\nimport { getClientSerovalPlugins } from './serializer/getClientSerovalPlugins'\nimport { TSS_FORMDATA_CONTEXT, X_TSS_SERIALIZED } from './constants'\nimport type { FunctionMiddlewareClientFnOptions } from './createMiddleware'\nimport type { Plugin as SerovalPlugin } from 'seroval'\n\nlet serovalPlugins: Array<SerovalPlugin<any, any>> | null = null\n\nexport async function serverFnFetcher(\n url: string,\n args: Array<any>,\n handler: (url: string, requestInit: RequestInit) => Promise<Response>,\n) {\n if (!serovalPlugins) {\n serovalPlugins = getClientSerovalPlugins()\n }\n const _first = args[0]\n\n // If createServerFn was used to wrap the fetcher,\n // We need to handle the arguments differently\n if (isPlainObject(_first) && _first.method) {\n const first = _first as FunctionMiddlewareClientFnOptions<\n any,\n any,\n any,\n any\n > & {\n headers: HeadersInit\n }\n const type = first.data instanceof FormData ? 'formData' : 'payload'\n\n // Arrange the headers\n const headers = new Headers({\n 'x-tsr-redirect': 'manual',\n ...(type === 'payload'\n ? {\n 'content-type': 'application/json',\n accept: 'application/x-ndjson, application/json',\n }\n : {}),\n ...(first.headers instanceof Headers\n ? Object.fromEntries(first.headers.entries())\n : first.headers),\n })\n\n // If the method is GET, we need to move the payload to the query string\n if (first.method === 'GET') {\n const encodedPayload = encode({\n payload: await serializePayload(first),\n })\n\n if (encodedPayload) {\n if (url.includes('?')) {\n url += `&${encodedPayload}`\n } else {\n url += `?${encodedPayload}`\n }\n }\n }\n\n if (url.includes('?')) {\n url += `&createServerFn`\n } else {\n url += `?createServerFn`\n }\n if (first.response === 'raw') {\n url += `&raw`\n }\n\n return await getResponse(async () =>\n handler(url, {\n method: first.method,\n headers,\n signal: first.signal,\n ...(await getFetcherRequestOptions(first)),\n }),\n )\n }\n\n // If not a custom fetcher, it was probably\n // a `use server` function, so just proxy the arguments\n // through as a POST request\n return await getResponse(() =>\n handler(url, {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(args),\n }),\n )\n}\n\nasync function serializePayload(\n opts: FunctionMiddlewareClientFnOptions<any, any, any, any>,\n) {\n const payloadToSerialize: any = {}\n if (opts.data) {\n payloadToSerialize['data'] = opts.data\n }\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (opts.context && Object.keys(opts.context).length > 0) {\n payloadToSerialize['context'] = opts.context\n }\n\n return serialize(payloadToSerialize)\n}\n\nasync function serialize(data: any) {\n return JSON.stringify(\n await Promise.resolve(toJSONAsync(data, { plugins: serovalPlugins! })),\n )\n}\n\nasync function getFetcherRequestOptions(\n opts: FunctionMiddlewareClientFnOptions<any, any, any, any>,\n) {\n if (opts.method === 'POST') {\n if (opts.data instanceof FormData) {\n opts.data.set(TSS_FORMDATA_CONTEXT, await serialize(opts.context))\n return {\n body: opts.data,\n }\n }\n\n return {\n body: await serializePayload(opts),\n }\n }\n\n return {}\n}\n\n/**\n * Retrieves a response from a given function and manages potential errors\n * and special response types including redirects and not found errors.\n *\n * @param fn - The function to execute for obtaining the response.\n * @returns The processed response from the function.\n * @throws If the response is invalid or an error occurs during processing.\n */\nasync function getResponse(fn: () => Promise<Response>) {\n const response = await (async () => {\n try {\n return await fn()\n } catch (error) {\n if (error instanceof Response) {\n return error\n }\n\n throw error\n }\n })()\n\n const contentType = response.headers.get('content-type')\n invariant(contentType, 'expected content-type header to be set')\n const serializedByStart = !!response.headers.get(X_TSS_SERIALIZED)\n // If the response is not ok, throw an error\n if (!response.ok) {\n if (serializedByStart && contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n const result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })\n throw result\n }\n\n throw new Error(await response.text())\n }\n\n if (serializedByStart) {\n let result\n if (contentType.includes('application/x-ndjson')) {\n const refs = new Map()\n result = await processServerFnResponse({\n response,\n onMessage: (msg) =>\n fromCrossJSON(msg, { refs, plugins: serovalPlugins! }),\n onError(msg, error) {\n // TODO how could we notify consumer that an error occurred?\n console.error(msg, error)\n },\n })\n }\n if (contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })\n }\n invariant(result, 'expected result to be resolved')\n if (result instanceof Error) {\n throw result\n }\n return result\n }\n\n if (contentType.includes('application/json')) {\n const jsonPayload = await response.json()\n const redirect = parseRedirect(jsonPayload)\n if (redirect) {\n throw redirect\n }\n if (isNotFound(jsonPayload)) {\n throw jsonPayload\n }\n return jsonPayload\n }\n\n return response\n}\n\nasync function processServerFnResponse({\n response,\n onMessage,\n onError,\n}: {\n response: Response\n onMessage: (msg: any) => any\n onError?: (msg: string, error?: any) => void\n}) {\n if (!response.body) {\n throw new Error('No response body')\n }\n\n const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()\n\n let buffer = ''\n let firstRead = false\n let firstObject\n\n while (!firstRead) {\n const { value, done } = await reader.read()\n if (value) buffer += value\n\n if (buffer.length === 0 && done) {\n throw new Error('Stream ended before first object')\n }\n\n // common case: buffer ends with newline\n if (buffer.endsWith('\\n')) {\n const lines = buffer.split('\\n').filter(Boolean)\n const firstLine = lines[0]\n if (!firstLine) throw new Error('No JSON line in the first chunk')\n firstObject = JSON.parse(firstLine)\n firstRead = true\n buffer = lines.slice(1).join('\\n')\n } else {\n // fallback: wait for a newline to parse first object safely\n const newlineIndex = buffer.indexOf('\\n')\n if (newlineIndex >= 0) {\n const line = buffer.slice(0, newlineIndex).trim()\n buffer = buffer.slice(newlineIndex + 1)\n if (line.length > 0) {\n firstObject = JSON.parse(line)\n firstRead = true\n }\n }\n }\n }\n\n // process rest of the stream asynchronously\n ;(async () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { value, done } = await reader.read()\n if (value) buffer += value\n\n const lastNewline = buffer.lastIndexOf('\\n')\n if (lastNewline >= 0) {\n const chunk = buffer.slice(0, lastNewline)\n buffer = buffer.slice(lastNewline + 1)\n const lines = chunk.split('\\n').filter(Boolean)\n\n for (const line of lines) {\n try {\n onMessage(JSON.parse(line))\n } catch (e) {\n onError?.(`Invalid JSON line: ${line}`, e)\n }\n }\n }\n\n if (done) {\n break\n }\n }\n } catch (err) {\n onError?.('Stream processing error:', err)\n }\n })()\n\n return onMessage(firstObject)\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/start-client-core",
|
|
3
|
-
"version": "1.132.0-alpha.
|
|
3
|
+
"version": "1.132.0-alpha.7",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"seroval-plugins": "^1.3.2",
|
|
49
49
|
"tiny-invariant": "^1.3.3",
|
|
50
50
|
"tiny-warning": "^1.0.3",
|
|
51
|
-
"@tanstack/
|
|
52
|
-
"@tanstack/
|
|
51
|
+
"@tanstack/start-storage-context": "1.132.0-alpha.4",
|
|
52
|
+
"@tanstack/router-core": "1.132.0-alpha.4"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {}
|
|
55
55
|
}
|
package/src/constants.ts
CHANGED
package/src/createClientRpc.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TSS_SERVER_FUNCTION } from './constants'
|
|
1
2
|
import { serverFnFetcher } from './serverFnFetcher'
|
|
2
3
|
|
|
3
4
|
let baseUrl: string
|
|
@@ -20,5 +21,6 @@ export const createClientRpc = (functionId: string) => {
|
|
|
20
21
|
return Object.assign(clientFn, {
|
|
21
22
|
url,
|
|
22
23
|
functionId,
|
|
24
|
+
[TSS_SERVER_FUNCTION]: true,
|
|
23
25
|
})
|
|
24
26
|
}
|
package/src/envOnly.ts
CHANGED
|
@@ -2,8 +2,8 @@ type EnvOnlyFn = <TFn extends (...args: Array<any>) => any>(fn: TFn) => TFn
|
|
|
2
2
|
|
|
3
3
|
// A function that will only be available in the server build
|
|
4
4
|
// If called on the client, it will throw an error
|
|
5
|
-
export const
|
|
5
|
+
export const createServerOnlyFn: EnvOnlyFn = (fn) => fn
|
|
6
6
|
|
|
7
7
|
// A function that will only be available in the client build
|
|
8
8
|
// If called on the server, it will throw an error
|
|
9
|
-
export const
|
|
9
|
+
export const createClientOnlyFn: EnvOnlyFn = (fn) => fn
|
package/src/index.tsx
CHANGED
|
@@ -21,7 +21,7 @@ export {
|
|
|
21
21
|
type ClientOnlyFn,
|
|
22
22
|
type IsomorphicFnBase,
|
|
23
23
|
} from './createIsomorphicFn'
|
|
24
|
-
export {
|
|
24
|
+
export { createServerOnlyFn, createClientOnlyFn } from './envOnly'
|
|
25
25
|
export { createServerFn } from './createServerFn'
|
|
26
26
|
export {
|
|
27
27
|
createMiddleware,
|
|
@@ -88,4 +88,8 @@ export { createClientRpc } from './createClientRpc'
|
|
|
88
88
|
|
|
89
89
|
export { getDefaultSerovalPlugins } from './serializer/getDefaultSerovalPlugins'
|
|
90
90
|
|
|
91
|
-
export {
|
|
91
|
+
export {
|
|
92
|
+
TSS_FORMDATA_CONTEXT,
|
|
93
|
+
TSS_SERVER_FUNCTION,
|
|
94
|
+
X_TSS_SERIALIZED,
|
|
95
|
+
} from './constants'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createSerializationAdapter } from '@tanstack/router-core'
|
|
2
2
|
import { createClientRpc } from '../createClientRpc'
|
|
3
|
+
import { TSS_SERVER_FUNCTION } from '../constants'
|
|
3
4
|
|
|
4
5
|
export const ServerFunctionSerializationAdapter = createSerializationAdapter({
|
|
5
6
|
key: '$TSS/serverfn',
|
|
6
|
-
test: (v): v is { functionId: string } =>
|
|
7
|
-
typeof v == 'function' && 'functionId' in v,
|
|
7
|
+
test: (v): v is { functionId: string } => v[TSS_SERVER_FUNCTION],
|
|
8
8
|
toSerializable: ({ functionId }) => ({ functionId }),
|
|
9
9
|
fromSerializable: ({ functionId }) => createClientRpc(functionId),
|
|
10
10
|
})
|
|
@@ -20,5 +20,5 @@ export function getDefaultSerovalPlugins() {
|
|
|
20
20
|
const adapters = router.options.serializationAdapters as
|
|
21
21
|
| Array<AnyTransformer>
|
|
22
22
|
| undefined
|
|
23
|
-
return [...
|
|
23
|
+
return [...(adapters?.map(makeSerovalPlugin) ?? []), ...defaultSerovalPlugins]
|
|
24
24
|
}
|
package/src/serverFnFetcher.ts
CHANGED
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
isPlainObject,
|
|
5
5
|
parseRedirect,
|
|
6
6
|
} from '@tanstack/router-core'
|
|
7
|
-
import { fromCrossJSON,
|
|
7
|
+
import { fromCrossJSON, toJSONAsync } from 'seroval'
|
|
8
8
|
import invariant from 'tiny-invariant'
|
|
9
9
|
import { getClientSerovalPlugins } from './serializer/getClientSerovalPlugins'
|
|
10
|
-
import {
|
|
10
|
+
import { TSS_FORMDATA_CONTEXT, X_TSS_SERIALIZED } from './constants'
|
|
11
11
|
import type { FunctionMiddlewareClientFnOptions } from './createMiddleware'
|
|
12
12
|
import type { Plugin as SerovalPlugin } from 'seroval'
|
|
13
13
|
|
|
@@ -125,7 +125,7 @@ async function getFetcherRequestOptions(
|
|
|
125
125
|
) {
|
|
126
126
|
if (opts.method === 'POST') {
|
|
127
127
|
if (opts.data instanceof FormData) {
|
|
128
|
-
opts.data.set(
|
|
128
|
+
opts.data.set(TSS_FORMDATA_CONTEXT, await serialize(opts.context))
|
|
129
129
|
return {
|
|
130
130
|
body: opts.data,
|
|
131
131
|
}
|
|
@@ -162,12 +162,12 @@ async function getResponse(fn: () => Promise<Response>) {
|
|
|
162
162
|
|
|
163
163
|
const contentType = response.headers.get('content-type')
|
|
164
164
|
invariant(contentType, 'expected content-type header to be set')
|
|
165
|
-
const serializedByStart = !!response.headers.get(
|
|
165
|
+
const serializedByStart = !!response.headers.get(X_TSS_SERIALIZED)
|
|
166
166
|
// If the response is not ok, throw an error
|
|
167
167
|
if (!response.ok) {
|
|
168
168
|
if (serializedByStart && contentType.includes('application/json')) {
|
|
169
169
|
const jsonPayload = await response.json()
|
|
170
|
-
const result =
|
|
170
|
+
const result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })
|
|
171
171
|
throw result
|
|
172
172
|
}
|
|
173
173
|
|
|
@@ -183,14 +183,14 @@ async function getResponse(fn: () => Promise<Response>) {
|
|
|
183
183
|
onMessage: (msg) =>
|
|
184
184
|
fromCrossJSON(msg, { refs, plugins: serovalPlugins! }),
|
|
185
185
|
onError(msg, error) {
|
|
186
|
-
// TODO how could we notify consumer that an error
|
|
186
|
+
// TODO how could we notify consumer that an error occurred?
|
|
187
187
|
console.error(msg, error)
|
|
188
188
|
},
|
|
189
189
|
})
|
|
190
190
|
}
|
|
191
191
|
if (contentType.includes('application/json')) {
|
|
192
192
|
const jsonPayload = await response.json()
|
|
193
|
-
result =
|
|
193
|
+
result = fromCrossJSON(jsonPayload, { plugins: serovalPlugins! })
|
|
194
194
|
}
|
|
195
195
|
invariant(result, 'expected result to be resolved')
|
|
196
196
|
if (result instanceof Error) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expectTypeOf, test } from 'vitest'
|
|
2
|
-
import {
|
|
2
|
+
import { createClientOnlyFn, createServerOnlyFn } from '../envOnly'
|
|
3
3
|
|
|
4
4
|
const inputFn = () => 'output'
|
|
5
5
|
|
|
@@ -11,24 +11,24 @@ function overloadedFn(input: any) {
|
|
|
11
11
|
return input
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
test("
|
|
15
|
-
const outputFn =
|
|
14
|
+
test("createClientOnlyFn returns the function it's given", () => {
|
|
15
|
+
const outputFn = createClientOnlyFn(inputFn)
|
|
16
16
|
expectTypeOf(outputFn).toEqualTypeOf<typeof inputFn>()
|
|
17
17
|
|
|
18
|
-
const genericOutputFn =
|
|
18
|
+
const genericOutputFn = createClientOnlyFn(genericInputFn)
|
|
19
19
|
expectTypeOf(genericOutputFn).toEqualTypeOf<typeof genericInputFn>()
|
|
20
20
|
|
|
21
|
-
const overloadedOutputFn =
|
|
21
|
+
const overloadedOutputFn = createClientOnlyFn(overloadedFn)
|
|
22
22
|
expectTypeOf(overloadedOutputFn).toEqualTypeOf<typeof overloadedFn>()
|
|
23
23
|
})
|
|
24
24
|
|
|
25
|
-
test("
|
|
26
|
-
const outputFn =
|
|
25
|
+
test("createServerOnlyFn returns the function it's given", () => {
|
|
26
|
+
const outputFn = createServerOnlyFn(inputFn)
|
|
27
27
|
expectTypeOf(outputFn).toEqualTypeOf<typeof inputFn>()
|
|
28
28
|
|
|
29
|
-
const genericOutputFn =
|
|
29
|
+
const genericOutputFn = createServerOnlyFn(genericInputFn)
|
|
30
30
|
expectTypeOf(genericOutputFn).toEqualTypeOf<typeof genericInputFn>()
|
|
31
31
|
|
|
32
|
-
const overloadedOutputFn =
|
|
32
|
+
const overloadedOutputFn = createServerOnlyFn(overloadedFn)
|
|
33
33
|
expectTypeOf(overloadedOutputFn).toEqualTypeOf<typeof overloadedFn>()
|
|
34
34
|
})
|