Skip to content

Commit 0289d45

Browse files
committed
added support for ports, baseUrl and ctx object
Signed-off-by: Noorain Panjwani <[email protected]>
1 parent 623a23e commit 0289d45

File tree

9 files changed

+193
-57
lines changed

9 files changed

+193
-57
lines changed

README.md

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,33 @@
1-
# space-api-ts
1+
# Space SDK (Typescript)
22
Typescript API for Space Cloud
3+
4+
## Worker API
5+
6+
Sample usage
7+
```ts
8+
import { Server } from "@spacecloud-io/worker";
9+
import { z } from "zod";
10+
11+
// We first create the server object
12+
const server = Server.create({
13+
name: "myServer",
14+
baseUrl: "/v2", // Optional. Defaults to `/v1`.
15+
port: 8080 // Optional. Defaults to `3000`.
16+
});
17+
18+
// Create a router object. All operations are registered on this router.
19+
const router = server.router();
20+
21+
// Register a query object.
22+
router.query("operate") // `opId` is the name of the operation
23+
.method("GET") // Defaults to `GET` for queries and `POST` for mutations
24+
.url("/v1/operate") // Defaults to `${baseURL}/${opId}`
25+
.input(z.object({ name: z.string() }))
26+
.output(z.object({ greeting: z.string() }))
27+
.fn(async (_ctx, req) => {
28+
return { greeting: `Hi ${req.name}` };
29+
});
30+
31+
// Start the express http server.
32+
server.start();
33+
```

apps/basic/src/index.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { Server } from "@spacecloud-io/worker";
22
import { z } from "zod";
33
import * as schemas from "./schema";
44

5-
const server = Server.create("myServer");
5+
const server = Server.create({ name: "myServer" });
66
const router = server.router();
77

8-
router.mutation("operate")
9-
.input(z.object({ name: z.string()}))
8+
router.query("operate")
9+
.input(z.object({ name: z.string() }))
1010
.output(z.object({ greeting: z.string() }))
11-
.fn(async (req) => {
11+
.fn(async (_ctx, req) => {
1212
return { greeting: `Hi ${req.name}` };
1313
});
1414

@@ -17,7 +17,7 @@ router.mutation("operate")
1717
router.mutation("addTodo")
1818
.input(schemas.addTodoRequestSchema)
1919
.output(schemas.addTodoResponseSchema)
20-
.fn(async (req) => {
20+
.fn(async (_ctx, req) => {
2121
console.log("Adding todo:", req);
2222
return { id: "myid" };
2323
});

packages/worker/package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@spacecloud-io/worker",
3-
"version": "0.1.0",
3+
"version": "0.1.2",
44
"description": "",
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",
@@ -10,16 +10,16 @@
1010
"lint": "eslint ./"
1111
},
1212
"keywords": [],
13-
"author": "",
14-
"license": "ISC",
13+
"author": "Noorain Panjwani <[email protected]>",
14+
"license": "Apache-2.0",
1515
"dependencies": {
1616
"express": "^4.18.2",
1717
"openapi3-ts": "^4.1.2",
18-
"tsconfig": "workspace:^",
1918
"zod": "^3.21.4",
2019
"zod-to-json-schema": "^3.21.1"
2120
},
2221
"devDependencies": {
23-
"@types/express": "^4.17.17"
22+
"@types/express": "^4.17.17",
23+
"tsconfig": "workspace:^"
2424
}
2525
}

packages/worker/src/context.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Request, Response } from "express";
2+
import { extractHeadersFromRequest } from "./helpers";
3+
4+
export class Context {
5+
private req: Request;
6+
private res: Response;
7+
8+
public metadata: { [key: string]: string };
9+
10+
constructor(req: Request, res: Response) {
11+
this.req = req;
12+
this.res = res;
13+
this.metadata = extractHeadersFromRequest(req);
14+
}
15+
16+
/**
17+
* Returns the request object provided by express. Helpful for accessing
18+
* underlying request properties like hostname & cookies. For headers, use metadata.
19+
* @returns
20+
*/
21+
public getRawRequestObject(): Request {
22+
return this.req;
23+
}
24+
25+
/**
26+
* Returns the response object provided by express. Useful for setting properties in response
27+
* object like headers.
28+
* @returns
29+
*/
30+
public getRawResponseObject(): Response {
31+
return this.res;
32+
}
33+
}

packages/worker/src/helpers.ts

+11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Request } from "express";
12
import openapi3 from "openapi3-ts/oas30";
23

34
export const getPayloadFromParams = (query: any, inputSchema: any) => {
@@ -71,3 +72,13 @@ export const getErrorResponseSchema = () => {
7172

7273
return responseObject;
7374
};
75+
76+
export const extractHeadersFromRequest = (req: Request) => {
77+
const headers: { [key: string]: string } = {};
78+
for (const [key, value] of Object.entries(req.headers)) {
79+
if (typeof value === "string") headers[key] = value;
80+
else if (Array.isArray(value)) headers[key] = value[0];
81+
}
82+
83+
return headers;
84+
};

packages/worker/src/route.ts

+38-23
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import { zodToJsonSchema } from "zod-to-json-schema";
44
import openapi3 from "openapi3-ts/oas30";
55

66
import { getErrorResponseSchema, getPayloadFromParams, isZodTypeNull, processException } from "./helpers";
7+
import { Context } from "./context";
78

89
export type RouteUpdater = (r: Route<any, any>) => void;
910

1011
export interface RouteConfig {
1112
opId: string;
1213
opType: string;
1314
method: string;
14-
url?: string;
15+
url: string;
1516
jsonSchema: {
1617
input: any;
1718
output: any;
@@ -27,7 +28,7 @@ export interface ZodSchemas {
2728
output?: z.ZodType;
2829
}
2930

30-
export type Query<I, O> = (input: I) => Promise<O>;
31+
export type Query<I, O> = (ctx: Context, input: I) => Promise<O>;
3132

3233
export class Route<I, O> {
3334
private config: RouteConfig;
@@ -39,23 +40,36 @@ export class Route<I, O> {
3940
constructor(config: RouteConfig, updater: RouteUpdater) {
4041
this.config = config;
4142
this.updater = updater;
42-
43-
// Autogenerate a url if it wasn't already provided by the user
44-
if (this.config.url === undefined) {
45-
this.config.url = `/v1/${this.config.opId}`;
46-
}
4743
}
4844

45+
/**
46+
* Method configures the HTTP method to use for this operation
47+
* @param method
48+
* @returns
49+
*/
4950
public method(method: string) {
5051
this.config.method = method;
5152
return this;
5253
}
5354

55+
/**
56+
* Url configures the URL to use for this operation
57+
* @param url
58+
* @returns
59+
*/
5460
public url(url: string) {
5561
this.config.url = url;
5662
return this;
5763
}
5864

65+
/**
66+
* Input configures the data type of the request object to pass in the handler function
67+
* registered by the user. It also performs a JSONSchema check before calling the handler function.
68+
* The proeprties specified by the input may be accepted via the body or as query parameters based
69+
* on the method being used for the operation
70+
* @param schema ZOD schema object
71+
* @returns
72+
*/
5973
public input<T>(schema: z.ZodType<T>) {
6074
// Create json schema from zod schema
6175
this.config.zodSchemas.input = schema;
@@ -71,6 +85,13 @@ export class Route<I, O> {
7185
return r;
7286
}
7387

88+
/**
89+
* Output configures the data type of the response object to that the handler function registered
90+
* by the user will return.
91+
* The returned object will always be sent as a JSON payload in the response body.
92+
* @param schema ZOD schema object
93+
* @returns
94+
*/
7495
public output<T>(schema: z.ZodType<T>) {
7596
// Create json schema from zod schema
7697
this.config.zodSchemas.output = schema;
@@ -86,12 +107,17 @@ export class Route<I, O> {
86107
return r;
87108
}
88109

89-
public fn(query: Query<I, O>) {
90-
this.handler = query;
110+
/**
111+
* fn accepts the procedure that needs to be called when this operation is invoked
112+
* @param handlerFn
113+
* @returns
114+
*/
115+
public fn(handlerFn: Query<I, O>) {
116+
this.handler = handlerFn;
91117
return this;
92118
}
93119

94-
public getOpenAPIOperation() {
120+
public _getOpenAPIOperation() {
95121
// Process request schema
96122
const isRequestNull = isZodTypeNull(this.config.zodSchemas.input);
97123
const requestBodyObject: openapi3.RequestBodyObject = {
@@ -131,12 +157,6 @@ export class Route<I, O> {
131157
["x-request-op-type"]: this.config.opType,
132158
};
133159

134-
// Unnecessary check to remove linting error. We don't really require this
135-
// since we are already performing this step in the constructor.
136-
if (!this.config.url) {
137-
this.config.url = `/v1/${this.config.opId}`;
138-
}
139-
140160
return {
141161
path: this.config.url,
142162
method: this.config.method,
@@ -173,20 +193,15 @@ export class Route<I, O> {
173193

174194
// Simply return back the response
175195
try {
176-
const output = await this.handler(payload);
196+
const ctx = new Context(req, res);
197+
const output = await this.handler(ctx, payload);
177198
res.json(output);
178199
} catch (e: any) {
179200
// Return status code 500 if we catch an exception
180201
res.status(500).json({ message: processException(e) });
181202
}
182203
};
183204

184-
// Unnecessary check to remove linting error. We don't really require this
185-
// since we are already performing this step in the constructor.
186-
if (!this.config.url) {
187-
this.config.url = `/v1/${this.config.opId}`;
188-
}
189-
190205
// Register the handler for the appropriate method
191206
switch (this.config.method) {
192207
case "get":

0 commit comments

Comments
 (0)