Skip to content

Commit f0bfd49

Browse files
committed
Get working again
1 parent 1714d72 commit f0bfd49

File tree

9 files changed

+203
-182
lines changed

9 files changed

+203
-182
lines changed

example-app/server-sdk.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { createTypedSDK, QueryClient } from "create-typed-sdk";
1+
import { createTypedReactSDK } from "create-typed-sdk";
22
import axios from "axios";
33
import type { ApiType } from "./server-api";
4+
import { QueryClient } from "react-query";
45

56
export function createServerSDK(queryClient: QueryClient) {
6-
return createTypedSDK<ApiType>({
7+
return createTypedReactSDK<ApiType>({
78
queryClient,
89
doFetch: async ({ argument, path }) => {
910
return axios

example-app/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { api } from "./server-api";
2-
import { collectEndpoints } from "create-typed-sdk";
2+
import { collectEndpoints } from "create-typed-sdk/core";
33
import fastify from "fastify";
44
import cors from "fastify-cors";
55

example-app/src/App.tsx

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,8 @@ import { QueryClient, QueryClientProvider, useQueryClient } from "react-query";
55

66
const queryClient = new QueryClient();
77

8-
const sdkProm = createServerSDK(queryClient);
9-
let ServerSDK: Awaited<typeof sdkProm>;
8+
const ServerSDK = createServerSDK(queryClient);
109
function App() {
11-
const [hasBootstraped, setHasBootstraped] = useState(false);
12-
useEffect(() => {
13-
sdkProm.then((sdk) => {
14-
ServerSDK = sdk;
15-
setHasBootstraped(true);
16-
});
17-
}, []);
18-
19-
if (!hasBootstraped) {
20-
return null;
21-
}
22-
2310
return (
2411
<QueryClientProvider client={queryClient}>
2512
<AppInner />

package/build.mjs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import yargs from "yargs-parser";
33
const { _, ...argv } = yargs(process.argv.slice(2)) || {};
44

55
const conf = {
6-
entryPoints: ["./src/index.ts"],
6+
entryPoints: ["./src/reactSDK.ts", "./src/coreSDK.ts"],
77
bundle: true,
88
format: "cjs",
99
minify: false,
@@ -12,5 +12,4 @@ const conf = {
1212
...argv,
1313
};
1414

15-
console.log(conf);
1615
esbuild.build(conf);

package/core/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"internal": true,
3+
"main": "../dist/cjs/coreSDK.js",
4+
"module": "../dist/esm/coreSDK.js",
5+
"types": "../dist/esm/coreSDK.d.ts"
6+
}

package/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
"name": "create-typed-sdk",
33
"version": "0.0.3",
44
"description": "A library for quickly created SDKs for your Node.js Typescript backends. No build step, no risk of injecting your server code into your frontend, just sweet, sweet DX goodness.",
5-
"main": "./dist/cjs/index.js",
6-
"module": "./dist/esm/index.js",
7-
"types": "./dist/cjs/index.d.ts",
5+
"main": "./dist/cjs/reactSDK.js",
6+
"module": "./dist/esm/reactSDK.js",
7+
"types": "./dist/esm/reactSDK.d.ts",
88
"repository": {
99
"type": "git",
1010
"url": "https://github.com/scottmas/create-typed-sdk.git"
@@ -20,8 +20,8 @@
2020
"scripts": {
2121
"dev": "yarn build && run-p -l 'build:* --watch'",
2222
"build": "run-p build:*",
23-
"build:cjs": "node build.mjs --format cjs --outdir dist/cjs --platform node ",
24-
"build:esm": "node build.mjs --format esm --outdir dist/esm --platform node",
23+
"build:cjs": "node build.mjs --format cjs --outdir dist/cjs --platform node",
24+
"build:esm": "node build.mjs --format esm --outdir dist/esm --platform browser",
2525
"build:cjs-ts": "tsc --outDir dist/cjs",
2626
"build:esm-ts": "tsc --outDir dist/esm"
2727
},

package/src/coreSDK.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import {
2+
Opts,
3+
DeepAsyncFnRecord,
4+
DoFetch,
5+
getTypedSDKInstance,
6+
TypedSDK,
7+
getFetchFn,
8+
} from "./internal";
9+
10+
export function createTypedSDK<T extends DeepAsyncFnRecord<any>>(
11+
opts: Opts
12+
): TypedSDK<T> {
13+
let doFetch: DoFetch;
14+
if ("doFetch" in opts) {
15+
doFetch = getFetchFn({ userSuppliedDoFetch: opts.doFetch });
16+
} else {
17+
doFetch = getFetchFn({ defaultUrl: opts.url });
18+
}
19+
20+
return getTypedSDKInstance({
21+
doFetch,
22+
queryClient: opts.queryClient,
23+
});
24+
}
25+
26+
export function collectEndpoints<T extends DeepAsyncFnRecord<T>>(api: T) {
27+
function collectLeafFunctions(value: any, path = [] as string[]) {
28+
const fns = [];
29+
if (isPlainObject(value) || Array.isArray(value)) {
30+
Object.keys(value).forEach((key) => {
31+
fns.push(...collectLeafFunctions(value[key], path.concat(key)));
32+
});
33+
} else {
34+
if (typeof value === "function") {
35+
fns.push({
36+
path,
37+
fn: value,
38+
});
39+
}
40+
}
41+
return fns;
42+
}
43+
return collectLeafFunctions(api);
44+
}
45+
46+
export function attachApiToAppWithDefault<T extends DeepAsyncFnRecord<T>>(
47+
api: T,
48+
app: {
49+
post: (
50+
path: string,
51+
handler: (
52+
req: { body: any },
53+
resp: { send: (v: any) => any } | { json: (v: any) => any }
54+
) => void
55+
) => any;
56+
}
57+
) {
58+
collectEndpoints(api).forEach(({ fn, path }) => {
59+
if (!app.post) {
60+
throw new Error(
61+
"No post method found on app! Ensure you are using a nodejs library like express or fastify"
62+
);
63+
}
64+
65+
app.post("/" + path.join("/"), async (req, resp) => {
66+
if (!req.body) {
67+
throw new Error(
68+
"Unable to find post body! Ensure your server parses the request body and attaches it to the request"
69+
);
70+
}
71+
72+
const val = await fn(req.body.argument);
73+
if ("send" in resp) {
74+
resp.send(val);
75+
} else if ("json" in resp) {
76+
resp.json(val);
77+
} else {
78+
throw new Error(
79+
"Unable to find method to send response! Ensure you are using a nodejs library like express or fastify"
80+
);
81+
}
82+
});
83+
});
84+
}
85+
86+
function isPlainObject(value: unknown) {
87+
if (!value || typeof value !== "object") {
88+
return false;
89+
}
90+
const proto = Object.getPrototypeOf(value);
91+
if (proto === null && value !== Object.prototype) {
92+
return true;
93+
}
94+
if (proto && Object.getPrototypeOf(proto) === null) {
95+
return true;
96+
}
97+
return false;
98+
}

package/src/types.ts renamed to package/src/internal.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import axios from "axios";
2+
import safeStringify from "fast-safe-stringify";
13
import type {
24
QueryFunctionContext,
35
QueryKey,
@@ -99,3 +101,64 @@ export type TypedUseSDKMutation<T extends DeepAsyncFnRecord<T>> = {
99101
};
100102

101103
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
104+
105+
export function getTypedSDKInstance(p: {
106+
doFetch: DoFetch;
107+
queryClient?: QueryClient;
108+
}) {
109+
const getNextQuery = (path: string[]): any => {
110+
return new Proxy(
111+
() => {}, //use function as base, so that it can be called...
112+
{
113+
apply: (__, ___, args) => {
114+
const argument = args[0];
115+
116+
const prom = p.doFetch({ argument, path });
117+
118+
if (p.queryClient) {
119+
prom.then((resp: any) => {
120+
p.queryClient?.setQueryData(getQueryKey(path, argument), resp);
121+
});
122+
}
123+
124+
return prom;
125+
},
126+
get(__, prop) {
127+
return getNextQuery(path.concat(prop.toString()));
128+
},
129+
}
130+
);
131+
};
132+
133+
return getNextQuery([]);
134+
}
135+
136+
export function getQueryKey(path: string[], argument: unknown) {
137+
const queryKey = [...path];
138+
if (argument !== "undefined") {
139+
queryKey.push(safeStringify.stableStringify(argument));
140+
}
141+
return queryKey;
142+
}
143+
144+
export function getFetchFn(
145+
opts: { userSuppliedDoFetch: DoFetch } | { defaultUrl: string }
146+
): DoFetch {
147+
return (p) => {
148+
if ("userSuppliedDoFetch" in opts) {
149+
return opts.userSuppliedDoFetch(p);
150+
} else {
151+
if (!opts.defaultUrl) {
152+
throw new Error(
153+
"url must be supplied to SDK constructor if no doFetch function is provided"
154+
);
155+
}
156+
157+
return axios
158+
.post(`${opts.defaultUrl}/${p.path.join("/")}`, {
159+
argument: p.argument,
160+
})
161+
.then((resp) => resp.data);
162+
}
163+
};
164+
}

0 commit comments

Comments
 (0)