microsoft / microsoft/typespec
[hsjs] Support or handle specifications that have multiple HTTP service declarations.
- Dominant language
- Java
- Stars
- 5.9k
- Forks
- 394
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 104
Description
### Describe the bug
I'm trying to update my tsp file (actually the todo in Allen's repo). I compiled and get this error.
```
> todo@1.0.0 tsp:compile
> tsp compile ./spec --config ./spec/tspconfig.yaml
TypeSpec compiler v1.0.0-rc.0
✔ Compiling
✔ @typespec/openapi3 generated-spec/openapi3/
✔ @typespec/http-server-csharp generated-dotnet-api-server/
⚠ @typespec/http-client-csharp generated-dotnet-api-client/
⚠ @typespec/http-client-js generated-js-api-client/
× Running @typespec/http-server-js...
Emitter "@typespec/http-server-js" crashed! This is a bug.
Please file an issue at https://github.com/microsoft/typespec/issues
Error: Unimplemented: multiple service definitions per program.
at createInitialContext (file:///workspaces/dina-dotnet/node_modules/@typespec/http-server-js/dist/src/ctx.js:42:15)
at Object.$onEmit [as emitFunction] (file:///workspaces/dina-dotnet/node_modules/@typespec/http-server-js/dist/src/index.js:13:25)
at runEmitter (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/program.js:668:23)
at file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/program.js:647:15
at trackAction (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/logger/console-sink.js:134:30)
at Object.trackAction (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/logger/console-sink.js:25:50)
at Object.trackAction (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/logger/logger.js:23:27)
at emit (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/program.js:646:18)
at compile (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/program.js:48:15)
at async compileOnce (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/cli/actions/compile/compile.js:39:25)
--------------------------------------------------
Library Version 0.58.0-alpha.12
TypeSpec Compiler Version 1.0.0-rc.0
--------------------------------------------------
npm error Lifecycle script `tsp:compile` failed with error:
npm error code 1
npm error path /workspaces/dina-dotnet/packages/todo
npm error workspace todo@1.0.0
npm error location /workspaces/dina-dotnet/packages/todo
npm error command failed
npm error command sh -c tsp compile ./spec --config ./spec/tspconfig.yaml
```
### Reproduction
Main.tsp
```
import "@typespec/http";
import "@typespec/rest";
import "@typespec/openapi";
import "@typespec/json-schema";
using Http;
using JsonSchema;
@service(#{
title: "Todo App",
})
@useAuth(BearerAuth)
@jsonSchema
namespace Todo;
@jsonSchema
model User {
/** An autogenerated unique id for the user */
@key
@visibility(Lifecycle.Read)
id: safeint;
/** The user's username */
@minLength(2)
@maxLength(50)
username: string;
/** The user's email address */
// @format("email") - crashes emitters for now
email: string;
/**
* The user's password, provided when creating a user
* but is otherwise not visible (and hashed by the backend)
*/
@visibility(Lifecycle.Create)
password: string;
/** Whether the user is validated. Never visible to the API. */
@invisible(Lifecycle) validated: boolean;
}
@jsonSchema
model TodoItem {
/** The item's unique id */
@visibility(Lifecycle.Read) @key id: safeint;
/** The item's title */
@maxLength(255)
title: string;
/** User that created the todo */
@visibility(Lifecycle.Read) createdBy: User.id;
/** User that the todo is assigned to */
assignedTo?: User.id;
/** A longer description of the todo item in markdown format */
description?: string;
/** The status of the todo item */
status: "NotStarted" | "InProgress" | "Completed";
/** When the todo item was created. */
@visibility(Lifecycle.Read) createdAt: utcDateTime;
/** When the todo item was last updated */
@visibility(Lifecycle.Read) updatedAt: utcDateTime;
/** When the todo item was makred as completed */
@visibility(Lifecycle.Read) completedAt?: utcDateTime;
// Want the read form to be normalized to TodoLabelRecord[], but can't
// https://github.com/microsoft/typespec/issues/2926
labels?: TodoLabels;
// hack to get a different schema for create
// (fastify glue doesn't support readonly)
@visibility(Lifecycle.Create) _dummy?: string;
}
model ToDoItemMultipartRequest {
item: HttpPart;
attachments?: HttpPart[];
}
model FileAttachmentMultipartRequest {
contents: HttpPart;
}
@jsonSchema
union TodoLabels {
string,
string[],
TodoLabelRecord,
TodoLabelRecord[],
}
@jsonSchema
model TodoLabelRecord {
name: string;
@pattern("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$")
color?: string;
}
@jsonSchema
model TodoAttachment {
/** The file name of the attachment */
@maxLength(255)
filename: string;
/** The media type of the attachment */
mediaType: string;
/** The contents of the file */
contents: bytes;
}
@jsonSchema
@error
model ApiError {
/** A machine readable error code */
code: string;
/** A human readable message */
// https://github.com/microsoft/OpenAPI/blob/main/extensions/x-ms-primary-error-message.md
@OpenAPI.extension("x-ms-primary-error-message", true)
message: string;
}
/**
* Something is wrong with you.
*/
model Standard4XXResponse extends ApiError {
@minValue(400)
@maxValue(499)
@statusCode
statusCode: int32;
}
/**
* Something is wrong with me.
*/
model Standard5XXResponse extends ApiError {
@minValue(500)
@maxValue(599)
@statusCode
statusCode: int32;
}
alias WithStandardErrors = T | Standard4XXResponse | Standard5XXResponse;
@useAuth(NoAuth)
@service(#{ title: "Todo API" })
@server("https://api.todo.com", "Production server")
namespace Users {
// would prefer to extend
// https://github.com/microsoft/typespec/issues/2922
model UserCreatedResponse {
...User;
...OkResponse;
/** The token to use to construct the validate email address url */
token: string;
}
/** The user already exists */
model UserExistsResponse extends ApiError {
...ConflictResponse;
code: "user-exists";
}
/** The user is invalid (e.g. forgot to enter email address) */
model InvalidUserResponse extends ApiError {
@statusCode statusCode: 422;
code: "invalid-user";
}
@route("/users")
@post
op create(
@body user: User,
): WithStandardErrors;
}
@route("items")
namespace TodoItems {
model PaginationControls {
/** The limit to the number of items */
@query limit?: int32 = 50;
/** The offset to start paginating at */
@query offset?: int32 = 0;
}
model TodoPage {
/** The items in the page */
@pageItems items: TodoItem[];
/** The number of items returned in this page */
pageSize: int32;
/** The total number of items */
totalSize: int32;
...PaginationControls;
/** A link to the previous page, if it exists */
@prevLink
prevLink?: url;
/** A link to the next page, if it exists */
@nextLink
nextLink?: url;
}
// deeply annoying that I have to copy/paste this...
model TodoItemPatch {
/** The item's title */
title?: TodoItem.title;
/** User that the todo is assigned to */
assignedTo?: TodoItem.assignedTo | null;
/** A longer description of the todo item in markdown format */
description?: TodoItem.description | null;
/** The status of the todo item */
status?: "NotStarted" | "InProgress" | "Completed";
}
model InvalidTodoItem extends ApiError {
@statusCode statusCode: 422;
}
@error
model NotFoundErrorResponse {
@statusCode statusCode: 404;
code: "not-found";
}
//@friendlyName("{name}List", T)
model Page {
@pageItems items: T[];
}
@list op list(...PaginationControls): WithStandardErrors;
@sharedRoute
@post
op createJson(
@header contentType: "application/json",
item: TodoItem,
attachments?: TodoAttachment[],
): WithStandardErrors;
@sharedRoute
@post
op createForm(
@header contentType: "multipart/form-data",
@multipartBody body: ToDoItemMultipartRequest,
): WithStandardErrors;
@get op get(@path id: TodoItem.id): TodoItem | NotFoundErrorResponse;
@patch op update(
@header contentType: "application/merge-patch+json",
@path id: TodoItem.id,
@body patch: TodoItemPatch,
): TodoItem;
@delete op delete(
@path id: TodoItem.id,
): WithStandardErrors;
@route("{itemId}/attachments")
namespace Attachments {
@list op list(
@path itemId: TodoItem.id,
): WithStandardErrors | NotFoundErrorResponse>;
@sharedRoute
@post
op createJsonAttachment(
@header contentType: "application/json",
@path itemId: TodoItem.id,
@body contents: TodoAttachment,
): WithStandardErrors;
@sharedRoute
@post
op createFileAttachment(
@header contentType: "multipart/form-data",
@path itemId: TodoItem.id,
@multipartBody body: FileAttachmentMultipartRequest,
): WithStandardErrors;
}
}
```
tspconfig.yaml
```
emit:
- "@typespec/openapi3"
- "@typespec/http-server-csharp"
- "@typespec/http-client-csharp"
- "@typespec/http-client-js"
- "@typespec/http-server-js"
options:
"@typespec/openapi3":
emitter-output-dir: "{project-root}/../generated-spec/openapi3"
"@typespec/http-client-csharp":
emitter-output-dir: "{project-root}/../generated-dotnet-api-client"
"@typespec/http-server-csharp":
emitter-output-dir: "{project-root}/../generated-dotnet-api-server"
"@typespec/http-client-js":
emitter-output-dir: "{project-root}/../generated-js-api-client"
"@typespec/http-server-js":
emitter-output-dir: "{project-root}/../generated-js-api-server"
```

### Checklist
- [x] Follow our [Code of Conduct](https://github.com/microsoft/typespec/blob/main/CODE_OF_CONDUCT.md)
- [x] Check that there isn't already an issue that request the same bug to avoid creating a duplicate.
- [x] Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/Microsoft/typespec/discussions).
- [x] The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.
Contributor guide
Assessment
This issue has not been assessed yet.