@typespec/http-server-js 0.58.0-alpha.10-dev.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -0
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/build-helpers.ts +170 -0
- package/dist/generated-defs/helpers/header.d.ts +4 -0
- package/dist/generated-defs/helpers/header.d.ts.map +1 -0
- package/dist/generated-defs/helpers/header.js +76 -0
- package/dist/generated-defs/helpers/header.js.map +1 -0
- package/dist/generated-defs/helpers/http.d.ts +4 -0
- package/dist/generated-defs/helpers/http.d.ts.map +1 -0
- package/dist/generated-defs/helpers/http.js +134 -0
- package/dist/generated-defs/helpers/http.js.map +1 -0
- package/dist/generated-defs/helpers/index.d.ts +4 -0
- package/dist/generated-defs/helpers/index.d.ts.map +1 -0
- package/dist/generated-defs/helpers/index.js +21 -0
- package/dist/generated-defs/helpers/index.js.map +1 -0
- package/dist/generated-defs/helpers/multipart.d.ts +4 -0
- package/dist/generated-defs/helpers/multipart.d.ts.map +1 -0
- package/dist/generated-defs/helpers/multipart.js +249 -0
- package/dist/generated-defs/helpers/multipart.js.map +1 -0
- package/dist/generated-defs/helpers/router.d.ts +4 -0
- package/dist/generated-defs/helpers/router.d.ts.map +1 -0
- package/dist/generated-defs/helpers/router.js +259 -0
- package/dist/generated-defs/helpers/router.js.map +1 -0
- package/dist/src/common/declaration.d.ts +13 -0
- package/dist/src/common/declaration.d.ts.map +1 -0
- package/dist/src/common/declaration.js +45 -0
- package/dist/src/common/declaration.js.map +1 -0
- package/dist/src/common/documentation.d.ts +12 -0
- package/dist/src/common/documentation.d.ts.map +1 -0
- package/dist/src/common/documentation.js +21 -0
- package/dist/src/common/documentation.js.map +1 -0
- package/dist/src/common/enum.d.ts +10 -0
- package/dist/src/common/enum.d.ts.map +1 -0
- package/dist/src/common/enum.js +21 -0
- package/dist/src/common/enum.js.map +1 -0
- package/dist/src/common/interface.d.ts +50 -0
- package/dist/src/common/interface.d.ts.map +1 -0
- package/dist/src/common/interface.js +194 -0
- package/dist/src/common/interface.js.map +1 -0
- package/dist/src/common/model.d.ts +26 -0
- package/dist/src/common/model.d.ts.map +1 -0
- package/dist/src/common/model.js +115 -0
- package/dist/src/common/model.js.map +1 -0
- package/dist/src/common/namespace.d.ts +38 -0
- package/dist/src/common/namespace.d.ts.map +1 -0
- package/dist/src/common/namespace.js +184 -0
- package/dist/src/common/namespace.js.map +1 -0
- package/dist/src/common/reference.d.ts +46 -0
- package/dist/src/common/reference.d.ts.map +1 -0
- package/dist/src/common/reference.js +243 -0
- package/dist/src/common/reference.js.map +1 -0
- package/dist/src/common/scalar.d.ts +50 -0
- package/dist/src/common/scalar.d.ts.map +1 -0
- package/dist/src/common/scalar.js +144 -0
- package/dist/src/common/scalar.js.map +1 -0
- package/dist/src/common/serialization/index.d.ts +11 -0
- package/dist/src/common/serialization/index.d.ts.map +1 -0
- package/dist/src/common/serialization/index.js +72 -0
- package/dist/src/common/serialization/index.js.map +1 -0
- package/dist/src/common/serialization/json.d.ts +6 -0
- package/dist/src/common/serialization/json.d.ts.map +1 -0
- package/dist/src/common/serialization/json.js +341 -0
- package/dist/src/common/serialization/json.js.map +1 -0
- package/dist/src/common/union.d.ts +23 -0
- package/dist/src/common/union.d.ts.map +1 -0
- package/dist/src/common/union.js +57 -0
- package/dist/src/common/union.js.map +1 -0
- package/dist/src/ctx.d.ts +242 -0
- package/dist/src/ctx.d.ts.map +1 -0
- package/dist/src/ctx.js +211 -0
- package/dist/src/ctx.js.map +1 -0
- package/dist/src/helpers/header.d.ts +14 -0
- package/dist/src/helpers/header.d.ts.map +1 -0
- package/dist/src/helpers/header.js +38 -0
- package/dist/src/helpers/header.js.map +1 -0
- package/dist/src/helpers/http.d.ts +70 -0
- package/dist/src/helpers/http.d.ts.map +1 -0
- package/dist/src/helpers/http.js +86 -0
- package/dist/src/helpers/http.js.map +1 -0
- package/dist/src/helpers/multipart.d.ts +26 -0
- package/dist/src/helpers/multipart.d.ts.map +1 -0
- package/dist/src/helpers/multipart.js +182 -0
- package/dist/src/helpers/multipart.js.map +1 -0
- package/dist/src/helpers/router.d.ts +176 -0
- package/dist/src/helpers/router.d.ts.map +1 -0
- package/dist/src/helpers/router.js +55 -0
- package/dist/src/helpers/router.js.map +1 -0
- package/dist/src/http/index.d.ts +24 -0
- package/dist/src/http/index.d.ts.map +1 -0
- package/dist/src/http/index.js +52 -0
- package/dist/src/http/index.js.map +1 -0
- package/dist/src/http/server/index.d.ts +11 -0
- package/dist/src/http/server/index.d.ts.map +1 -0
- package/dist/src/http/server/index.js +413 -0
- package/dist/src/http/server/index.js.map +1 -0
- package/dist/src/http/server/multipart.d.ts +16 -0
- package/dist/src/http/server/multipart.d.ts.map +1 -0
- package/dist/src/http/server/multipart.js +214 -0
- package/dist/src/http/server/multipart.js.map +1 -0
- package/dist/src/http/server/router.d.ts +15 -0
- package/dist/src/http/server/router.d.ts.map +1 -0
- package/dist/src/http/server/router.js +459 -0
- package/dist/src/http/server/router.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +38 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib.d.ts +141 -0
- package/dist/src/lib.d.ts.map +1 -0
- package/dist/src/lib.js +116 -0
- package/dist/src/lib.js.map +1 -0
- package/dist/src/scripts/scaffold/bin.d.mts +14 -0
- package/dist/src/scripts/scaffold/bin.d.mts.map +1 -0
- package/dist/src/scripts/scaffold/bin.mjs +559 -0
- package/dist/src/scripts/scaffold/bin.mjs.map +1 -0
- package/dist/src/testing/index.d.ts +3 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +6 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/util/case.d.ts +81 -0
- package/dist/src/util/case.d.ts.map +1 -0
- package/dist/src/util/case.js +111 -0
- package/dist/src/util/case.js.map +1 -0
- package/dist/src/util/differentiate.d.ts +251 -0
- package/dist/src/util/differentiate.d.ts.map +1 -0
- package/dist/src/util/differentiate.js +580 -0
- package/dist/src/util/differentiate.js.map +1 -0
- package/dist/src/util/error.d.ts +13 -0
- package/dist/src/util/error.d.ts.map +1 -0
- package/dist/src/util/error.js +25 -0
- package/dist/src/util/error.js.map +1 -0
- package/dist/src/util/extends.d.ts +10 -0
- package/dist/src/util/extends.d.ts.map +1 -0
- package/dist/src/util/extends.js +31 -0
- package/dist/src/util/extends.js.map +1 -0
- package/dist/src/util/iter.d.ts +39 -0
- package/dist/src/util/iter.d.ts.map +1 -0
- package/dist/src/util/iter.js +72 -0
- package/dist/src/util/iter.js.map +1 -0
- package/dist/src/util/keywords.d.ts +10 -0
- package/dist/src/util/keywords.d.ts.map +1 -0
- package/dist/src/util/keywords.js +85 -0
- package/dist/src/util/keywords.js.map +1 -0
- package/dist/src/util/name.d.ts +12 -0
- package/dist/src/util/name.d.ts.map +1 -0
- package/dist/src/util/name.js +26 -0
- package/dist/src/util/name.js.map +1 -0
- package/dist/src/util/once-queue.d.ts +24 -0
- package/dist/src/util/once-queue.d.ts.map +1 -0
- package/dist/src/util/once-queue.js +34 -0
- package/dist/src/util/once-queue.js.map +1 -0
- package/dist/src/util/openapi3.d.ts +23 -0
- package/dist/src/util/openapi3.d.ts.map +1 -0
- package/dist/src/util/openapi3.js +40 -0
- package/dist/src/util/openapi3.js.map +1 -0
- package/dist/src/util/pluralism.d.ts +23 -0
- package/dist/src/util/pluralism.d.ts.map +1 -0
- package/dist/src/util/pluralism.js +36 -0
- package/dist/src/util/pluralism.js.map +1 -0
- package/dist/src/util/scope.d.ts +85 -0
- package/dist/src/util/scope.d.ts.map +1 -0
- package/dist/src/util/scope.js +111 -0
- package/dist/src/util/scope.js.map +1 -0
- package/dist/src/write.d.ts +23 -0
- package/dist/src/write.d.ts.map +1 -0
- package/dist/src/write.js +62 -0
- package/dist/src/write.js.map +1 -0
- package/generated-defs/helpers/header.ts +83 -0
- package/generated-defs/helpers/http.ts +141 -0
- package/generated-defs/helpers/index.ts +27 -0
- package/generated-defs/helpers/multipart.ts +256 -0
- package/generated-defs/helpers/router.ts +266 -0
- package/package.json +71 -0
- package/src/common/declaration.ts +52 -0
- package/src/common/documentation.ts +26 -0
- package/src/common/enum.ts +28 -0
- package/src/common/interface.ts +264 -0
- package/src/common/model.ts +160 -0
- package/src/common/namespace.ts +243 -0
- package/src/common/reference.ts +319 -0
- package/src/common/scalar.ts +173 -0
- package/src/common/serialization/index.ts +124 -0
- package/src/common/serialization/json.ts +444 -0
- package/src/common/union.ts +76 -0
- package/src/ctx.ts +497 -0
- package/src/helpers/header.ts +55 -0
- package/src/helpers/http.ts +113 -0
- package/src/helpers/multipart.ts +228 -0
- package/src/helpers/router.ts +238 -0
- package/src/http/index.ts +81 -0
- package/src/http/server/index.ts +548 -0
- package/src/http/server/multipart.ts +272 -0
- package/src/http/server/router.ts +686 -0
- package/src/index.ts +56 -0
- package/src/lib.ts +130 -0
- package/src/scripts/scaffold/bin.mts +781 -0
- package/src/testing/index.ts +10 -0
- package/src/util/case.ts +182 -0
- package/src/util/differentiate.ts +957 -0
- package/src/util/error.ts +28 -0
- package/src/util/extends.ts +43 -0
- package/src/util/iter.ts +85 -0
- package/src/util/keywords.ts +90 -0
- package/src/util/name.ts +33 -0
- package/src/util/once-queue.ts +55 -0
- package/src/util/openapi3.ts +53 -0
- package/src/util/pluralism.ts +37 -0
- package/src/util/scope.ts +211 -0
- package/src/write.ts +88 -0
- package/temp/tsconfig.tsbuildinfo +1 -0
- package/test/header.test.ts +26 -0
- package/test/multipart.test.ts +169 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +4 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation
|
|
2
|
+
// Licensed under the MIT license.
|
|
3
|
+
|
|
4
|
+
import { Enum } from "@typespec/compiler";
|
|
5
|
+
import { JsContext } from "../ctx.js";
|
|
6
|
+
import { parseCase } from "../util/case.js";
|
|
7
|
+
import { emitDocumentation } from "./documentation.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Emit an enum declaration.
|
|
11
|
+
*
|
|
12
|
+
* @param ctx - The emitter context.
|
|
13
|
+
* @param enum_ - The enum to emit.
|
|
14
|
+
*/
|
|
15
|
+
export function* emitEnum(ctx: JsContext, enum_: Enum): Iterable<string> {
|
|
16
|
+
yield* emitDocumentation(ctx, enum_);
|
|
17
|
+
|
|
18
|
+
const name = parseCase(enum_.name);
|
|
19
|
+
|
|
20
|
+
yield `export enum ${name.pascalCase} {`;
|
|
21
|
+
|
|
22
|
+
for (const member of enum_.members.values()) {
|
|
23
|
+
const nameCase = parseCase(member.name);
|
|
24
|
+
yield ` ${nameCase.pascalCase} = ${JSON.stringify(member.value)},`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
yield `}`;
|
|
28
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation
|
|
2
|
+
// Licensed under the MIT license.
|
|
3
|
+
|
|
4
|
+
import { Interface, Operation, Type, UnionVariant, isErrorModel } from "@typespec/compiler";
|
|
5
|
+
import { JsContext, Module, PathCursor } from "../ctx.js";
|
|
6
|
+
import { parseCase } from "../util/case.js";
|
|
7
|
+
import { getAllProperties } from "../util/extends.js";
|
|
8
|
+
import { bifilter, indent } from "../util/iter.js";
|
|
9
|
+
import { emitDocumentation } from "./documentation.js";
|
|
10
|
+
import { emitTypeReference, isValueLiteralType } from "./reference.js";
|
|
11
|
+
import { emitUnionType } from "./union.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Emit an interface declaration.
|
|
15
|
+
*
|
|
16
|
+
* @param ctx - The emitter context.
|
|
17
|
+
* @param iface - The interface to emit.
|
|
18
|
+
* @param module - The module that this interface is written into.
|
|
19
|
+
*/
|
|
20
|
+
export function* emitInterface(ctx: JsContext, iface: Interface, module: Module): Iterable<string> {
|
|
21
|
+
const name = parseCase(iface.name).pascalCase;
|
|
22
|
+
|
|
23
|
+
yield* emitDocumentation(ctx, iface);
|
|
24
|
+
yield `export interface ${name}<Context = unknown> {`;
|
|
25
|
+
yield* indent(emitOperationGroup(ctx, iface.operations.values(), module));
|
|
26
|
+
yield "}";
|
|
27
|
+
yield "";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Emit a list of operation signatures.
|
|
32
|
+
*
|
|
33
|
+
* @param ctx - The emitter context.
|
|
34
|
+
* @param operations - The operations to emit.
|
|
35
|
+
* @param module - The module that the operations are written into.
|
|
36
|
+
*/
|
|
37
|
+
export function* emitOperationGroup(
|
|
38
|
+
ctx: JsContext,
|
|
39
|
+
operations: Iterable<Operation>,
|
|
40
|
+
module: Module,
|
|
41
|
+
): Iterable<string> {
|
|
42
|
+
for (const op of operations) {
|
|
43
|
+
yield* emitOperation(ctx, op, module);
|
|
44
|
+
yield "";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Emit a single operation signature.
|
|
50
|
+
*
|
|
51
|
+
* @param ctx - The emitter context.
|
|
52
|
+
* @param op - The operation to emit.
|
|
53
|
+
* @param module - The module that the operation is written into.
|
|
54
|
+
*/
|
|
55
|
+
export function* emitOperation(ctx: JsContext, op: Operation, module: Module): Iterable<string> {
|
|
56
|
+
const opNameCase = parseCase(op.name);
|
|
57
|
+
|
|
58
|
+
const opName = opNameCase.camelCase;
|
|
59
|
+
|
|
60
|
+
const allParameters = getAllProperties(op.parameters);
|
|
61
|
+
|
|
62
|
+
const hasOptions = allParameters.some((p) => p.optional);
|
|
63
|
+
|
|
64
|
+
const returnTypeReference = emitTypeReference(ctx, op.returnType, op, module, {
|
|
65
|
+
altName: opNameCase.pascalCase + "Result",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const returnType = `Promise<${returnTypeReference}>`;
|
|
69
|
+
|
|
70
|
+
const params: string[] = [];
|
|
71
|
+
|
|
72
|
+
for (const param of allParameters) {
|
|
73
|
+
// If the type is a value literal, then we consider it a _setting_ and not a parameter.
|
|
74
|
+
// This allows us to exclude metadata parameters (such as contentType) from the generated interface.
|
|
75
|
+
if (param.optional || isValueLiteralType(param.type)) continue;
|
|
76
|
+
|
|
77
|
+
const paramNameCase = parseCase(param.name);
|
|
78
|
+
const paramName = paramNameCase.camelCase;
|
|
79
|
+
|
|
80
|
+
const outputTypeReference = emitTypeReference(ctx, param.type, param, module, {
|
|
81
|
+
altName: opNameCase.pascalCase + paramNameCase.pascalCase,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
params.push(`${paramName}: ${outputTypeReference}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const paramsDeclarationLine = params.join(", ");
|
|
88
|
+
|
|
89
|
+
yield* emitDocumentation(ctx, op);
|
|
90
|
+
|
|
91
|
+
if (hasOptions) {
|
|
92
|
+
const optionsTypeName = opNameCase.pascalCase + "Options";
|
|
93
|
+
|
|
94
|
+
emitOptionsType(ctx, op, module, optionsTypeName);
|
|
95
|
+
|
|
96
|
+
const paramsFragment = params.length > 0 ? `${paramsDeclarationLine}, ` : "";
|
|
97
|
+
|
|
98
|
+
// prettier-ignore
|
|
99
|
+
yield `${opName}(ctx: Context, ${paramsFragment}options?: ${optionsTypeName}): ${returnType};`;
|
|
100
|
+
yield "";
|
|
101
|
+
} else {
|
|
102
|
+
// prettier-ignore
|
|
103
|
+
yield `${opName}(ctx: Context, ${paramsDeclarationLine}): ${returnType};`;
|
|
104
|
+
yield "";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Emit a declaration for an options type including the optional parameters of an operation.
|
|
110
|
+
*
|
|
111
|
+
* @param ctx - The emitter context.
|
|
112
|
+
* @param operation - The operation to emit the options type for.
|
|
113
|
+
* @param module - The module that the options type is written into.
|
|
114
|
+
* @param optionsTypeName - The name of the options type.
|
|
115
|
+
*/
|
|
116
|
+
export function emitOptionsType(
|
|
117
|
+
ctx: JsContext,
|
|
118
|
+
operation: Operation,
|
|
119
|
+
module: Module,
|
|
120
|
+
optionsTypeName: string,
|
|
121
|
+
) {
|
|
122
|
+
module.imports.push({
|
|
123
|
+
binder: [optionsTypeName],
|
|
124
|
+
from: ctx.syntheticModule,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const options = [...operation.parameters.properties.values()].filter((p) => p.optional);
|
|
128
|
+
|
|
129
|
+
ctx.syntheticModule.declarations.push([
|
|
130
|
+
`export interface ${optionsTypeName} {`,
|
|
131
|
+
...options.flatMap((p) => [
|
|
132
|
+
` ${parseCase(p.name).camelCase}?: ${emitTypeReference(ctx, p.type, p, module, {
|
|
133
|
+
altName: optionsTypeName + parseCase(p.name).pascalCase,
|
|
134
|
+
})};`,
|
|
135
|
+
]),
|
|
136
|
+
"}",
|
|
137
|
+
"",
|
|
138
|
+
]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface SplitReturnTypeCommon {
|
|
142
|
+
typeReference: string;
|
|
143
|
+
target: Type | [PathCursor, string] | undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface OrdinarySplitReturnType extends SplitReturnTypeCommon {
|
|
147
|
+
kind: "ordinary";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface UnionSplitReturnType extends SplitReturnTypeCommon {
|
|
151
|
+
kind: "union";
|
|
152
|
+
variants: UnionVariant[];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export type SplitReturnType = OrdinarySplitReturnType | UnionSplitReturnType;
|
|
156
|
+
|
|
157
|
+
const DEFAULT_NO_VARIANT_RETURN_TYPE = "never";
|
|
158
|
+
const DEFAULT_NO_VARIANT_SPLIT: SplitReturnType = {
|
|
159
|
+
kind: "ordinary",
|
|
160
|
+
typeReference: DEFAULT_NO_VARIANT_RETURN_TYPE,
|
|
161
|
+
target: undefined,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export function isInfallible(split: SplitReturnType): boolean {
|
|
165
|
+
return (
|
|
166
|
+
(split.kind === "ordinary" && split.typeReference === "never") ||
|
|
167
|
+
(split.kind === "union" && split.variants.length === 0)
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function splitReturnType(
|
|
172
|
+
ctx: JsContext,
|
|
173
|
+
type: Type,
|
|
174
|
+
module: Module,
|
|
175
|
+
altBaseName: string,
|
|
176
|
+
): [SplitReturnType, SplitReturnType] {
|
|
177
|
+
const successAltName = altBaseName + "Response";
|
|
178
|
+
const errorAltName = altBaseName + "ErrorResponse";
|
|
179
|
+
|
|
180
|
+
if (type.kind === "Union") {
|
|
181
|
+
const [successVariants, errorVariants] = bifilter(
|
|
182
|
+
type.variants.values(),
|
|
183
|
+
(v) => !isErrorModel(ctx.program, v.type),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const successTypeReference =
|
|
187
|
+
successVariants.length === 0
|
|
188
|
+
? DEFAULT_NO_VARIANT_RETURN_TYPE
|
|
189
|
+
: successVariants.length === 1
|
|
190
|
+
? emitTypeReference(ctx, successVariants[0].type, successVariants[0], module, {
|
|
191
|
+
altName: successAltName,
|
|
192
|
+
})
|
|
193
|
+
: emitUnionType(ctx, successVariants, module);
|
|
194
|
+
|
|
195
|
+
const errorTypeReference =
|
|
196
|
+
errorVariants.length === 0
|
|
197
|
+
? DEFAULT_NO_VARIANT_RETURN_TYPE
|
|
198
|
+
: errorVariants.length === 1
|
|
199
|
+
? emitTypeReference(ctx, errorVariants[0].type, errorVariants[0], module, {
|
|
200
|
+
altName: errorAltName,
|
|
201
|
+
})
|
|
202
|
+
: emitUnionType(ctx, errorVariants, module);
|
|
203
|
+
|
|
204
|
+
const successSplit: SplitReturnType =
|
|
205
|
+
successVariants.length > 1
|
|
206
|
+
? {
|
|
207
|
+
kind: "union",
|
|
208
|
+
variants: successVariants,
|
|
209
|
+
typeReference: successTypeReference,
|
|
210
|
+
target: undefined,
|
|
211
|
+
}
|
|
212
|
+
: {
|
|
213
|
+
kind: "ordinary",
|
|
214
|
+
typeReference: successTypeReference,
|
|
215
|
+
target: successVariants[0]?.type,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const errorSplit: SplitReturnType =
|
|
219
|
+
errorVariants.length > 1
|
|
220
|
+
? {
|
|
221
|
+
kind: "union",
|
|
222
|
+
variants: errorVariants,
|
|
223
|
+
typeReference: errorTypeReference,
|
|
224
|
+
// target: module.cursor.resolveRelativeItemPath(errorTypeReference),
|
|
225
|
+
target: undefined,
|
|
226
|
+
}
|
|
227
|
+
: {
|
|
228
|
+
kind: "ordinary",
|
|
229
|
+
typeReference: errorTypeReference,
|
|
230
|
+
target: errorVariants[0]?.type,
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
return [successSplit, errorSplit];
|
|
234
|
+
} else {
|
|
235
|
+
// No splitting, just figure out if the type is an error type or not and make the other infallible.
|
|
236
|
+
|
|
237
|
+
if (isErrorModel(ctx.program, type)) {
|
|
238
|
+
const typeReference = emitTypeReference(ctx, type, type, module, {
|
|
239
|
+
altName: altBaseName + "ErrorResponse",
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return [
|
|
243
|
+
DEFAULT_NO_VARIANT_SPLIT,
|
|
244
|
+
{
|
|
245
|
+
kind: "ordinary",
|
|
246
|
+
typeReference,
|
|
247
|
+
target: type,
|
|
248
|
+
},
|
|
249
|
+
];
|
|
250
|
+
} else {
|
|
251
|
+
const typeReference = emitTypeReference(ctx, type, type, module, {
|
|
252
|
+
altName: altBaseName + "SuccessResponse",
|
|
253
|
+
});
|
|
254
|
+
return [
|
|
255
|
+
{
|
|
256
|
+
kind: "ordinary",
|
|
257
|
+
typeReference,
|
|
258
|
+
target: type,
|
|
259
|
+
},
|
|
260
|
+
DEFAULT_NO_VARIANT_SPLIT,
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation
|
|
2
|
+
// Licensed under the MIT license.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
Model,
|
|
6
|
+
getFriendlyName,
|
|
7
|
+
isTemplateDeclaration,
|
|
8
|
+
isTemplateInstance,
|
|
9
|
+
} from "@typespec/compiler";
|
|
10
|
+
import { JsContext, Module } from "../ctx.js";
|
|
11
|
+
import { isUnspeakable, parseCase } from "../util/case.js";
|
|
12
|
+
import { indent } from "../util/iter.js";
|
|
13
|
+
import { KEYWORDS } from "../util/keywords.js";
|
|
14
|
+
import { getFullyQualifiedTypeName } from "../util/name.js";
|
|
15
|
+
import { asArrayType, getArrayElementName, getRecordValueName } from "../util/pluralism.js";
|
|
16
|
+
import { emitDocumentation } from "./documentation.js";
|
|
17
|
+
import { emitTypeReference } from "./reference.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Emit a model declaration.
|
|
21
|
+
*
|
|
22
|
+
* @param ctx - The emitter context.
|
|
23
|
+
* @param model - The model to emit.
|
|
24
|
+
* @param module - The module that this model is written into.
|
|
25
|
+
* @param altName - An alternative name to use for the model if it is not named.
|
|
26
|
+
*/
|
|
27
|
+
export function* emitModel(
|
|
28
|
+
ctx: JsContext,
|
|
29
|
+
model: Model,
|
|
30
|
+
module: Module,
|
|
31
|
+
altName?: string,
|
|
32
|
+
): Iterable<string> {
|
|
33
|
+
const isTemplate = isTemplateInstance(model);
|
|
34
|
+
const friendlyName = getFriendlyName(ctx.program, model);
|
|
35
|
+
|
|
36
|
+
if (isTemplateDeclaration(model)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const modelNameCase = parseCase(
|
|
41
|
+
friendlyName
|
|
42
|
+
? friendlyName
|
|
43
|
+
: isTemplate
|
|
44
|
+
? model.templateMapper!.args.map((a) => ("name" in a ? String(a.name) : "")).join("_") +
|
|
45
|
+
model.name
|
|
46
|
+
: model.name,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (model.name === "" && !altName) {
|
|
50
|
+
throw new Error("UNREACHABLE: Anonymous model with no altName");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
yield* emitDocumentation(ctx, model);
|
|
54
|
+
|
|
55
|
+
const ifaceName = model.name === "" ? altName! : modelNameCase.pascalCase;
|
|
56
|
+
|
|
57
|
+
const extendsClause = model.baseModel
|
|
58
|
+
? `extends ${emitTypeReference(ctx, model.baseModel, model, module)} `
|
|
59
|
+
: "";
|
|
60
|
+
|
|
61
|
+
yield `export interface ${ifaceName} ${extendsClause}{`;
|
|
62
|
+
|
|
63
|
+
for (const field of model.properties.values()) {
|
|
64
|
+
// Skip properties with unspeakable names.
|
|
65
|
+
if (isUnspeakable(field.name)) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const nameCase = parseCase(field.name);
|
|
70
|
+
const basicName = nameCase.camelCase;
|
|
71
|
+
|
|
72
|
+
const typeReference = emitTypeReference(ctx, field.type, field, module, {
|
|
73
|
+
altName: modelNameCase.pascalCase + nameCase.pascalCase,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const name = KEYWORDS.has(basicName) ? `_${basicName}` : basicName;
|
|
77
|
+
|
|
78
|
+
yield* indent(emitDocumentation(ctx, field));
|
|
79
|
+
|
|
80
|
+
const questionMark = field.optional ? "?" : "";
|
|
81
|
+
|
|
82
|
+
yield ` ${name}${questionMark}: ${typeReference};`;
|
|
83
|
+
yield "";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
yield "}";
|
|
87
|
+
yield "";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function emitModelLiteral(ctx: JsContext, model: Model, module: Module): string {
|
|
91
|
+
const properties = [...model.properties.values()]
|
|
92
|
+
.map((prop) => {
|
|
93
|
+
if (isUnspeakable(prop.name)) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const nameCase = parseCase(prop.name);
|
|
98
|
+
const questionMark = prop.optional ? "?" : "";
|
|
99
|
+
|
|
100
|
+
const name = KEYWORDS.has(nameCase.camelCase) ? `_${nameCase.camelCase}` : nameCase.camelCase;
|
|
101
|
+
|
|
102
|
+
return `${name}${questionMark}: ${emitTypeReference(ctx, prop.type, prop, module)}`;
|
|
103
|
+
})
|
|
104
|
+
.filter((p) => !!p);
|
|
105
|
+
|
|
106
|
+
return `{ ${properties.join("; ")} }`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Determines whether a model is an instance of a well-known model, such as TypeSpec.Record or TypeSpec.Array.
|
|
111
|
+
*/
|
|
112
|
+
export function isWellKnownModel(ctx: JsContext, type: Model): boolean {
|
|
113
|
+
const fullName = getFullyQualifiedTypeName(type);
|
|
114
|
+
return ["TypeSpec.Record", "TypeSpec.Array", "TypeSpec.Http.HttpPart"].includes(fullName);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Emits a well-known model, such as TypeSpec.Record or TypeSpec.Array.
|
|
119
|
+
*
|
|
120
|
+
* @param ctx - The emitter context.
|
|
121
|
+
* @param type - The model to emit.
|
|
122
|
+
* @param module - The module that this model is written into.
|
|
123
|
+
* @param preferredAlternativeName - An alternative name to use for the model if it is not named.
|
|
124
|
+
*/
|
|
125
|
+
export function emitWellKnownModel(
|
|
126
|
+
ctx: JsContext,
|
|
127
|
+
type: Model,
|
|
128
|
+
module: Module,
|
|
129
|
+
preferredAlternativeName?: string,
|
|
130
|
+
): string {
|
|
131
|
+
switch (type.name) {
|
|
132
|
+
case "Record": {
|
|
133
|
+
const arg = type.indexer!.value;
|
|
134
|
+
return `{ [k: string]: ${emitTypeReference(ctx, arg, type, module, {
|
|
135
|
+
altName: preferredAlternativeName && getRecordValueName(preferredAlternativeName),
|
|
136
|
+
})} }`;
|
|
137
|
+
}
|
|
138
|
+
case "Array": {
|
|
139
|
+
const arg = type.indexer!.value;
|
|
140
|
+
return asArrayType(
|
|
141
|
+
emitTypeReference(ctx, arg, type, module, {
|
|
142
|
+
altName: preferredAlternativeName && getArrayElementName(preferredAlternativeName),
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
case "HttpPart": {
|
|
147
|
+
const argument = type.templateMapper!.args[0];
|
|
148
|
+
|
|
149
|
+
if (!(argument.entityKind === "Type" && argument.kind === "Model")) {
|
|
150
|
+
throw new Error("UNREACHABLE: HttpPart must have a Model argument");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return emitTypeReference(ctx, argument, type, module, {
|
|
154
|
+
altName: preferredAlternativeName && `${preferredAlternativeName}HttpPart`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
default:
|
|
158
|
+
throw new Error(`UNREACHABLE: ${type.name}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation
|
|
2
|
+
// Licensed under the MIT license.
|
|
3
|
+
|
|
4
|
+
import { Namespace, getNamespaceFullName } from "@typespec/compiler";
|
|
5
|
+
import {
|
|
6
|
+
DeclarationType,
|
|
7
|
+
JsContext,
|
|
8
|
+
Module,
|
|
9
|
+
ModuleBodyDeclaration,
|
|
10
|
+
createModule,
|
|
11
|
+
isModule,
|
|
12
|
+
} from "../ctx.js";
|
|
13
|
+
import { parseCase } from "../util/case.js";
|
|
14
|
+
import { UnimplementedError } from "../util/error.js";
|
|
15
|
+
import { cat, indent, isIterable } from "../util/iter.js";
|
|
16
|
+
import { OnceQueue } from "../util/once-queue.js";
|
|
17
|
+
import { emitOperationGroup } from "./interface.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Enqueue all declarations in the namespace to be included in the emit, recursively.
|
|
21
|
+
*
|
|
22
|
+
* @param ctx - The emitter context.
|
|
23
|
+
* @param namespace - The root namespace to begin traversing.
|
|
24
|
+
*/
|
|
25
|
+
export function visitAllTypes(ctx: JsContext, namespace: Namespace) {
|
|
26
|
+
const { enums, interfaces, models, unions, namespaces, scalars, operations } = namespace;
|
|
27
|
+
|
|
28
|
+
for (const type of cat<DeclarationType>(
|
|
29
|
+
enums.values(),
|
|
30
|
+
interfaces.values(),
|
|
31
|
+
models.values(),
|
|
32
|
+
unions.values(),
|
|
33
|
+
scalars.values(),
|
|
34
|
+
)) {
|
|
35
|
+
ctx.typeQueue.add(type);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const ns of namespaces.values()) {
|
|
39
|
+
visitAllTypes(ctx, ns);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (operations.size > 0) {
|
|
43
|
+
// If the operation has any floating operations in it, we will synthesize an interface for them in the parent module.
|
|
44
|
+
// This requires some special handling by other parts of the emitter to ensure that the interface for a namespace's
|
|
45
|
+
// own operations is properly imported.
|
|
46
|
+
if (!namespace.namespace) {
|
|
47
|
+
throw new UnimplementedError("no parent namespace in visitAllTypes");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const parentModule = createOrGetModuleForNamespace(ctx, namespace.namespace);
|
|
51
|
+
|
|
52
|
+
parentModule.declarations.push([
|
|
53
|
+
// prettier-ignore
|
|
54
|
+
`/** An interface representing the operations defined in the '${getNamespaceFullName(namespace)}' namespace. */`,
|
|
55
|
+
`export interface ${parseCase(namespace.name).pascalCase}<Context = unknown> {`,
|
|
56
|
+
...indent(emitOperationGroup(ctx, operations.values(), parentModule)),
|
|
57
|
+
"}",
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create a module for a namespace, or get an existing module if one has already been created.
|
|
64
|
+
*
|
|
65
|
+
* @param ctx - The emitter context.
|
|
66
|
+
* @param namespace - The namespace to create a module for.
|
|
67
|
+
* @returns the module for the namespace.
|
|
68
|
+
*/
|
|
69
|
+
export function createOrGetModuleForNamespace(
|
|
70
|
+
ctx: JsContext,
|
|
71
|
+
namespace: Namespace,
|
|
72
|
+
root: Module = ctx.globalNamespaceModule,
|
|
73
|
+
): Module {
|
|
74
|
+
if (ctx.namespaceModules.has(namespace)) {
|
|
75
|
+
return ctx.namespaceModules.get(namespace)!;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!namespace.namespace) {
|
|
79
|
+
throw new Error("UNREACHABLE: no parent namespace in createOrGetModuleForNamespace");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const parent =
|
|
83
|
+
namespace.namespace === ctx.globalNamespace
|
|
84
|
+
? root
|
|
85
|
+
: createOrGetModuleForNamespace(ctx, namespace.namespace);
|
|
86
|
+
const name = namespace.name === "TypeSpec" ? "typespec" : parseCase(namespace.name).kebabCase;
|
|
87
|
+
|
|
88
|
+
const module: Module = createModule(name, parent, namespace);
|
|
89
|
+
|
|
90
|
+
ctx.namespaceModules.set(namespace, module);
|
|
91
|
+
|
|
92
|
+
return module;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get a reference to the interface representing the namespace's floating operations.
|
|
97
|
+
*
|
|
98
|
+
* This does not check that such an interface actually exists, so it should only be called in situations where it is
|
|
99
|
+
* known to exist (for example, if an operation comes from the namespace).
|
|
100
|
+
*
|
|
101
|
+
* @param ctx - The emitter context.
|
|
102
|
+
* @param namespace - The namespace to get the interface reference for.
|
|
103
|
+
* @param module - The module the the reference will be written to.
|
|
104
|
+
*/
|
|
105
|
+
export function emitNamespaceInterfaceReference(
|
|
106
|
+
ctx: JsContext,
|
|
107
|
+
namespace: Namespace,
|
|
108
|
+
module: Module,
|
|
109
|
+
): string {
|
|
110
|
+
if (!namespace.namespace) {
|
|
111
|
+
throw new Error("UNREACHABLE: no parent namespace in emitNamespaceInterfaceReference");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const namespaceName = parseCase(namespace.name).pascalCase;
|
|
115
|
+
|
|
116
|
+
module.imports.push({
|
|
117
|
+
binder: [namespaceName],
|
|
118
|
+
from: createOrGetModuleForNamespace(ctx, namespace.namespace),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return namespaceName;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Emits a single declaration within a module. If the declaration is a module, it is enqueued for later processing.
|
|
126
|
+
*
|
|
127
|
+
* @param ctx - The emitter context.
|
|
128
|
+
* @param decl - The declaration to emit.
|
|
129
|
+
* @param queue - The queue to add the declaration to if it is a module.
|
|
130
|
+
*/
|
|
131
|
+
function* emitModuleBodyDeclaration(
|
|
132
|
+
ctx: JsContext,
|
|
133
|
+
decl: ModuleBodyDeclaration,
|
|
134
|
+
queue: OnceQueue<Module>,
|
|
135
|
+
): Iterable<string> {
|
|
136
|
+
if (isIterable(decl)) {
|
|
137
|
+
yield* decl;
|
|
138
|
+
} else if (typeof decl === "string") {
|
|
139
|
+
yield* decl.split(/\r?\n/);
|
|
140
|
+
} else {
|
|
141
|
+
if (decl.declarations.length > 0) {
|
|
142
|
+
queue.add(decl);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Gets a file path from a given module to another module.
|
|
149
|
+
*/
|
|
150
|
+
function computeRelativeFilePath(from: Module, to: Module): string {
|
|
151
|
+
const fromIsIndex = from.declarations.some((d) => isModule(d));
|
|
152
|
+
const toIsIndex = to.declarations.some((d) => isModule(d));
|
|
153
|
+
|
|
154
|
+
const relativePath = (fromIsIndex ? from.cursor : from.cursor.parent!).relativePath(to.cursor);
|
|
155
|
+
|
|
156
|
+
if (relativePath.length === 0 && !toIsIndex)
|
|
157
|
+
throw new Error("UNREACHABLE: relativePath returned no fragments");
|
|
158
|
+
|
|
159
|
+
if (relativePath.length === 0) return "./index.js";
|
|
160
|
+
|
|
161
|
+
const prefix = relativePath[0] === ".." ? "" : "./";
|
|
162
|
+
|
|
163
|
+
const suffix = toIsIndex ? "/index.js" : ".js";
|
|
164
|
+
|
|
165
|
+
return prefix + relativePath.join("/") + suffix;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Deduplicates, consolidates, and writes the import statements for a module.
|
|
170
|
+
*/
|
|
171
|
+
function* writeImportsNormalized(ctx: JsContext, module: Module): Iterable<string> {
|
|
172
|
+
const allTargets = new Set<string>();
|
|
173
|
+
const importMap = new Map<string, Set<string>>();
|
|
174
|
+
const starAsMap = new Map<string, string>();
|
|
175
|
+
const extraStarAs: [string, string][] = [];
|
|
176
|
+
|
|
177
|
+
for (const _import of module.imports) {
|
|
178
|
+
// check for same module and continue
|
|
179
|
+
if (_import.from === module) continue;
|
|
180
|
+
|
|
181
|
+
const target =
|
|
182
|
+
typeof _import.from === "string"
|
|
183
|
+
? _import.from
|
|
184
|
+
: computeRelativeFilePath(module, _import.from);
|
|
185
|
+
|
|
186
|
+
allTargets.add(target);
|
|
187
|
+
|
|
188
|
+
if (typeof _import.binder === "string") {
|
|
189
|
+
if (starAsMap.has(target)) {
|
|
190
|
+
extraStarAs.push([_import.binder, target]);
|
|
191
|
+
} else {
|
|
192
|
+
starAsMap.set(target, _import.binder);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
const binders = importMap.get(target) ?? new Set<string>();
|
|
196
|
+
for (const binder of _import.binder) {
|
|
197
|
+
binders.add(binder);
|
|
198
|
+
}
|
|
199
|
+
importMap.set(target, binders);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
for (const target of allTargets) {
|
|
204
|
+
const binders = importMap.get(target);
|
|
205
|
+
const starAs = starAsMap.get(target);
|
|
206
|
+
|
|
207
|
+
if (binders && starAs) {
|
|
208
|
+
yield `import ${starAs}, { ${[...binders].join(", ")} } from "${target}";`;
|
|
209
|
+
} else if (binders) {
|
|
210
|
+
yield `import { ${[...binders].join(", ")} } from "${target}";`;
|
|
211
|
+
} else if (starAs) {
|
|
212
|
+
yield `import ${starAs} from "${target}";`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
yield "";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
for (const [binder, target] of extraStarAs) {
|
|
219
|
+
yield `import ${binder} from "${target}";`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Emits the body of a module file.
|
|
225
|
+
*
|
|
226
|
+
* @param ctx - The emitter context.
|
|
227
|
+
* @param module - The module to emit.
|
|
228
|
+
* @param queue - The queue to add any submodules to for later processing.
|
|
229
|
+
*/
|
|
230
|
+
export function* emitModuleBody(
|
|
231
|
+
ctx: JsContext,
|
|
232
|
+
module: Module,
|
|
233
|
+
queue: OnceQueue<Module>,
|
|
234
|
+
): Iterable<string> {
|
|
235
|
+
yield* writeImportsNormalized(ctx, module);
|
|
236
|
+
|
|
237
|
+
if (module.imports.length > 0) yield "";
|
|
238
|
+
|
|
239
|
+
for (const decl of module.declarations) {
|
|
240
|
+
yield* emitModuleBodyDeclaration(ctx, decl, queue);
|
|
241
|
+
yield "";
|
|
242
|
+
}
|
|
243
|
+
}
|