Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/start/server/server-functions/babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ function transformServer({ types: t, template }) {
source: serverFn.node
})
);
path.replaceWith(
t.identifier(
`server$.createServerResultHandler($$server_module${serverIndex})`
)
);
} else {
statement.insertBefore(
template(
Expand All @@ -203,8 +208,8 @@ function transformServer({ types: t, template }) {
: {}
)
);
path.replaceWith(t.identifier(`$$server_module${serverIndex}`));
}
path.replaceWith(t.identifier(`$$server_module${serverIndex}`));
}
},
FunctionDeclaration: markFunction,
Expand Down
77 changes: 77 additions & 0 deletions packages/start/server/server-functions/serialize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*!
* Original code by Alex ("KATT") Johansson for Trpc
* MIT Licensed, Copyright(c) 2021 Trpc, see LICENSE.remix.md for details
*
* Credits to the Trpc team for the solving typescript json serialization typings:
* https://github.com/remix-run/remix/blob/main/packages/remix-react/components.tsx#L865
*/

export type FilterKeys<TObj extends object, TFilter> = {
[TKey in keyof TObj]: TObj[TKey] extends TFilter ? TKey : never;
}[keyof TObj];

/**
* @link https://github.com/remix-run/remix/blob/2248669ed59fd716e267ea41df5d665d4781f4a9/packages/remix-server-runtime/serialize.ts
*/
type JsonPrimitive =
| string
| number
| boolean
// eslint-disable-next-line @typescript-eslint/ban-types
| String
// eslint-disable-next-line @typescript-eslint/ban-types
| Number
// eslint-disable-next-line @typescript-eslint/ban-types
| Boolean
| null;
// eslint-disable-next-line @typescript-eslint/ban-types
type NonJsonPrimitive = undefined | Function | symbol;

/*
* `any` is the only type that can let you equate `0` with `1`
* See https://stackoverflow.com/a/49928360/1490091
*/
type IsAny<T> = 0 extends 1 & T ? true : false;

// prettier-ignore
export type Serialize<T> =
IsAny<T> extends true ? any :
T extends JsonPrimitive ? T :
T extends Map<any,any> | Set<any> ? object :
T extends NonJsonPrimitive ? never :
T extends { toJSON(): infer U } ? U :
T extends [] ? [] :
T extends [unknown, ...unknown[]] ? SerializeTuple<T> :
T extends ReadonlyArray<infer U> ? (U extends NonJsonPrimitive ? null : Serialize<U>)[] :
T extends object ? SerializeObject<UndefinedToOptional<T>> :
never;

/** JSON serialize [tuples](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) */
type SerializeTuple<T extends [unknown, ...unknown[]]> = {
[k in keyof T]: T[k] extends NonJsonPrimitive ? null : Serialize<T[k]>;
};

/** JSON serialize objects (not including arrays) and classes */
type SerializeObject<T extends object> = {
[k in keyof Omit<T, FilterKeys<T, NonJsonPrimitive>>]: Serialize<T[k]>;
};

type FilterDefinedKeys<TObj extends object> = Exclude<
{
[TKey in keyof TObj]: undefined extends TObj[TKey] ? never : TKey;
}[keyof TObj],
undefined
>;

/*
* For an object T, if it has any properties that are a union with `undefined`,
* make those into optional properties instead.
*
* Example: { a: string | undefined} --> { a?: string}
*/
type UndefinedToOptional<T extends object> =
// Property is not a union with `undefined`, keep as-is
Pick<T, FilterDefinedKeys<T>> & {
// Property _is_ a union with `defined`. Set as optional (via `?`) and remove `undefined` from the union
[k in keyof Omit<T, FilterDefinedKeys<T>>]?: Exclude<T[k], undefined>;
};
26 changes: 24 additions & 2 deletions packages/start/server/server-functions/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { FormError } from "../../data";
import { ServerError } from "../../data/FormError";
import {
ContentTypeHeader,
isRedirectResponse,
JSONResponseType,
LocationHeader,
ResponseError,
XSolidStartContentTypeHeader,
XSolidStartLocationHeader,
XSolidStartOrigin,
XSolidStartResponseTypeHeader,
isRedirectResponse
XSolidStartResponseTypeHeader
} from "../responses";
import { PageEvent, ServerFunctionEvent } from "../types";
import { CreateServerFunction } from "./types";
Expand Down Expand Up @@ -279,6 +279,28 @@ server$.hasHandler = function (route) {
return handlers.has(route);
};

server$.createServerResultHandler = function (handler) {
return (...args) => {
const handleResponse = (data: any) => {
if (data instanceof ResponseError) {
return data.clone();
} else if (data instanceof Response) {
return data;
} else if (
typeof data === "object" ||
typeof data === "string" ||
typeof data === "number" ||
typeof data === "boolean"
) {
return JSON.stringify(data);
Comment on lines +289 to +295
Copy link
Copy Markdown
Contributor Author

@arbassett arbassett Mar 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be optimized to only stringify on object but to make the check consistent with repondWith I left it as is

}
};
const data = handler(...args);
data.then(handleResponse);
return data;
};
};

// used to fetch from an API route on the server or client, without falling into
// fetch problems on the server
server$.fetch = internalFetch;
4 changes: 3 additions & 1 deletion packages/start/server/server-functions/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { FetchEvent } from "../types";
import { Serialize } from "./serialize";

export type ServerFunction<E extends any[], T extends (...args: [...E]) => void> = ((
...p: Parameters<T>
) => Promise<Awaited<ReturnType<T>>>) & { url: string };
) => Promise<Serialize<Awaited<ReturnType<T>>>>) & { url: string };

export type CreateServerFunction = (<E extends any[], T extends (...args: [...E]) => void>(
fn: T
Expand All @@ -13,4 +14,5 @@ export type CreateServerFunction = (<E extends any[], T extends (...args: [...E]
hasHandler: (route: string) => boolean;
createFetcher(route: string, serverResource: boolean): ServerFunction<any, any>;
fetch(route: string, init?: RequestInit): Promise<Response>;
createServerResultHandler: (handler: any) => (...args: any[]) => any;
} & FetchEvent;