Skip to content

Commit b5d766c

Browse files
[http-server-javascript] Merge JavaScript Server Generator to Main (#3231)
This work-in-progress PR tracks merging the JavaScript server code generator to the TypeSpec repository. The JavaScript server code generator creates HTTP bindings for TypeSpec HTTP services and exposes them for binding either to the Node.js http server directly, or to an Express.js app as middleware. Closes #3215 --------- Co-authored-by: Will Temple <[email protected]>
1 parent 86a0c56 commit b5d766c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+7089
-684
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: internal
3+
packages:
4+
- "@typespec/http-server-javascript"
5+
---
6+
7+
Added the experimental HTTP server generator for JavaScript.

cspell.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ words:
1212
- azsdkengsys
1313
- azurecr
1414
- azuresdk
15+
- bifilter
1516
- blockful
1617
- blockless
1718
- cadl
@@ -28,6 +29,7 @@ words:
2829
- CRUDL
2930
- dbaeumer
3031
- debouncer
32+
- destructures
3133
- devdiv
3234
- Diagnoser
3335
- dogfood
@@ -84,6 +86,7 @@ words:
8486
- protoc
8587
- psscriptanalyzer
8688
- pwsh
89+
- recase
8790
- regen
8891
- respecify
8992
- rpaas
@@ -109,11 +112,14 @@ words:
109112
- uitestresults
110113
- unassignable
111114
- Uncapitalize
115+
- undifferentiable
112116
- uncollapsed
113117
- uninstantiated
114118
- unioned
119+
- unparented
115120
- unprefixed
116121
- unprojected
122+
- unrepresentable
117123
- unsourced
118124
- unversioned
119125
- VITE
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
title: "Emitter usage"
3+
toc_min_heading_level: 2
4+
toc_max_heading_level: 3
5+
---
6+
7+
# Emitter
8+
9+
## Usage
10+
11+
1. Via the command line
12+
13+
```bash
14+
tsp compile . --emit=@typespec/http-server-javascript
15+
```
16+
17+
2. Via the config
18+
19+
```yaml
20+
emit:
21+
- "@typespec/http-server-javascript"
22+
```
23+
24+
## Emitter options
25+
26+
### `features`
27+
28+
**Type:** `object`
29+
30+
### `omit-unreachable-types`
31+
32+
**Type:** `boolean`
33+
34+
### `no-format`
35+
36+
**Type:** `boolean`
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
title: Overview
3+
sidebar_position: 0
4+
toc_min_heading_level: 2
5+
toc_max_heading_level: 3
6+
---
7+
8+
import Tabs from '@theme/Tabs';
9+
import TabItem from '@theme/TabItem';
10+
11+
# Overview
12+
13+
TypeSpec HTTP server code generator for JavaScript
14+
15+
## Install
16+
17+
<Tabs>
18+
<TabItem value="spec" label="In a spec" default>
19+
20+
```bash
21+
npm install @typespec/http-server-javascript
22+
```
23+
24+
</TabItem>
25+
<TabItem value="library" label="In a library" default>
26+
27+
```bash
28+
npm install --save-peer @typespec/http-server-javascript
29+
```
30+
31+
</TabItem>
32+
</Tabs>
33+
34+
## Emitter usage
35+
36+
[See documentation](./emitter.md)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) Microsoft Corporation. All rights reserved.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# @typespec/http-server-javascript
2+
3+
:warning: **This package is highly experimental and may be subject to breaking changes and bugs.** Please expect that your code may need to be updated as this package evolves, and please report any issues you encounter.
4+
5+
TypeSpec HTTP server code generator for JavaScript and TypeScript.
6+
7+
This package generates an implementation of an HTTP server layer for a TypeSpec API. It supports binding directly to a
8+
Node.js HTTP server or Express.js application.
9+
10+
## Install
11+
12+
```bash
13+
npm install @typespec/http-server-javascript
14+
```
15+
16+
## Emitter
17+
18+
### Usage
19+
20+
1. Via the command line
21+
22+
```bash
23+
tsp compile . --emit=@typespec/http-server-javascript
24+
```
25+
26+
2. Via the config
27+
28+
```yaml
29+
emit:
30+
- "@typespec/http-server-javascript"
31+
```
32+
33+
### Emitter options
34+
35+
#### `express`
36+
37+
**Type:** `boolean`
38+
39+
If set to `true`, the emitter will generate a router that exposes an Express.js middleware function in addition to the
40+
ordinary Node.js HTTP server router.
41+
42+
If this option is not set to `true`, the `expressMiddleware` property will not be present on the generated router.
43+
44+
#### `omit-unreachable-types`
45+
46+
**Type:** `boolean`
47+
48+
By default, the emitter will create interfaces that represent all models in the service namespace. If this option is set
49+
to `true`, the emitter will only emit those types that are reachable from an HTTP operation.
50+
51+
#### `no-format`
52+
53+
**Type:** `boolean`
54+
55+
If set to `true`, the emitter will not format the generated code using Prettier.
56+
57+
## Functionality and generated code
58+
59+
The emitter generates a few major components:
60+
61+
### Router
62+
63+
The highest-level component that your code interacts with directly is the router implementation.
64+
`@typespec/http-server-javascript` generates a static router that you can bind to an implementation of an HTTP server.
65+
66+
The router is generated in the `http/router.js` module within the output directory. Each service will have its own
67+
router implementation named after the service. For example, given a service namespace named `Todo`, the router module
68+
will export a function `createTodoRouter`. This function creates an instance of a router that dispatches methods within
69+
the `Todo` service.
70+
71+
```ts
72+
import { createTodoRouter } from "../tsp-output/@typespec/http-server-javascript/http/router.js";
73+
74+
const router = createTodoRouter(users, todoItems, attachments);
75+
```
76+
77+
As arguments, the `createTodoRouter` function expects implementations of the underlying service interfaces. These
78+
interfaces are explained further in the next section.
79+
80+
Once the router is created, it is bound to an instance of the HTTP server. The router's `dispatch` method implements the
81+
Node.js event handler signature for the `request` event on a Node.js HTTP server.
82+
83+
```ts
84+
const server = http.createServer();
85+
86+
server.on("request", router.dispatch);
87+
88+
server.listen(8080, () => {
89+
console.log("Server listening on http://localhost:8080");
90+
});
91+
```
92+
93+
Alternatively, the router can be used with Express.js instead of the Node.js HTTP server directly. If the `express`
94+
feature is enabled in the emitter options, the router will expose an `expressMiddleware` property that implements the
95+
Express.js middleware interface.
96+
97+
```ts
98+
import express from "express";
99+
100+
const app = express();
101+
102+
app.use(router.expressMiddleware);
103+
104+
app.listen(8080, () => {
105+
console.log("Server listening on http://localhost:8080");
106+
});
107+
```
108+
109+
### Service interfaces
110+
111+
The emitter generates interfaces for each collection of service methods that exists in the service namespace.
112+
Implementations of these interfaces are required to instantiate the router. When the router processes an HTTP request,
113+
it will call the appropriate method on the service implementation after determining the route and method.
114+
115+
For example, given the following TypeSpec namespace `Users` within the `Todo` service:
116+
117+
```tsp
118+
namespace Users {
119+
@route("/users")
120+
@post
121+
op create(
122+
user: User,
123+
): WithStandardErrors<UserCreatedResponse | UserExistsResponse | InvalidUserResponse>;
124+
}
125+
```
126+
127+
The emitter will generate a corresponding interface `Users` within the module `models/all/todo/index.js` in the output
128+
directory.
129+
130+
```ts
131+
/** An interface representing the operations defined in the 'Todo.Users' namespace. */
132+
export interface Users<Context = unknown> {
133+
create(
134+
ctx: Context,
135+
user: User
136+
): Promise<
137+
| UserCreatedResponse
138+
| UserExistsResponse
139+
| InvalidUserResponse
140+
| Standard4XxResponse
141+
| Standard5XxResponse
142+
>;
143+
}
144+
```
145+
146+
An object implementing this `Users` interface must be passed to the router when it is created. The `Context` type
147+
parameter represents the underlying protocol or framework-specific context that the service implementation may inspect.
148+
If you need to access the HTTP request or response objects directly in the implementation of the service methods, you
149+
must use the `HttpContext` type as the `Context` argument when implementing the service interface. Otherwise, it is safe
150+
to use the default `unknown` argument.
151+
152+
```ts
153+
import { HttpContext } from "../tsp-output/@typespec/http-server-javascript/helpers/router.js";
154+
import { Users } from "../tsp-output/@typespec/http-server-javascript/models/all/todo/index.js";
155+
156+
export const users: Users<HttpContext> = {
157+
async create(ctx, user) {
158+
// Implementation
159+
},
160+
};
161+
```
162+
163+
### Models
164+
165+
The emitter generates TypeScript interfaces that represent the model types used in the service operations. This allows
166+
the service implementation to interact with the data structures carried over the HTTP protocol in a type-safe manner.
167+
168+
### Operation functions
169+
170+
While your code should never need to interact with these functions directly, the emitter generates a function per HTTP
171+
operation that handles the parsing and validation of the request contents. This allows the service implementation to be
172+
written in terms of ordinary TypeScript types and values rather than raw HTTP request and response objects. In general:
173+
174+
- The Node.js HTTP server or Express.js application (your code) calls the router (generated code), which determines
175+
which service operation function (generated code) to call based on the route, method, and other HTTP metadata in the
176+
case of shared routes.
177+
- The operation function (generated code) deserializes the request body, query parameters, and headers into TypeScript
178+
types, and may perform request validation.
179+
- The operation function (generated code) calls the service implementation (your code) with the deserialized request
180+
data.
181+
- The service implementation (your code) returns a result or throws an error.
182+
- The operation function (generated code) responds to the HTTP request on your behalf, converting the result or error
183+
into HTTP response data.

0 commit comments

Comments
 (0)