just-the-type 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Kneip
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.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # just-the-type
2
+
3
+ Emitter to create TypeScript types from TypeSpec. No clients, no runtime, no
4
+ serializers — just the types, built with the TypeScript compiler factory API.
5
+
6
+ ## Usage
7
+
8
+ ```sh
9
+ pnpm add just-the-type
10
+ tsp compile . --emit just-the-type
11
+ ```
12
+
13
+ The emitter writes a single `types.ts` to `tsp-output/just-the-type/`.
14
+
15
+ ## What gets emitted
16
+
17
+ | TypeSpec | TypeScript |
18
+ | ---------------------------------------- | ---------------------------------------------- |
19
+ | `model` | `export interface` (with `extends`) |
20
+ | `model Wrapper<T>` (constraints/defaults) | generic `export interface Wrapper<T>` |
21
+ | `model Pets is Pet[]` | `export type Pets = Pet[]` |
22
+ | `...Record<T>` spread | index signature `[key: string]: T` |
23
+ | `enum` (incl. spread) | `export enum` |
24
+ | named `union` (incl. generic) | `export type` alias |
25
+ | `op` | `export type` function alias |
26
+ | `interface` | `export interface` with method signatures |
27
+ | custom `scalar` | `export type` alias to its base primitive |
28
+ | doc comments / `@doc` | JSDoc comments |
29
+ | string templates `"a-${string}"` | template literal types |
30
+ | anonymous models, unions, intersections | inlined structurally |
31
+ | templates with `valueof` parameters | declaration skipped, instantiations inlined |
32
+ | `int64` / `uint64` | `bigint` |
33
+ | other numerics | `number` |
34
+ | `bytes` | `Uint8Array` |
35
+ | dates, times, `duration`, `url` | `string` |
36
+
37
+ Not represented in the output: `alias` declarations (dissolved by the TypeSpec
38
+ checker, their targets are inlined), values/`const`, and API-metadata decorators
39
+ such as `@visibility`, `@format`, or `@encode` that do not change the type shape.
40
+
41
+ ## Decorators
42
+
43
+ The library ships decorators for TypeScript features that TypeSpec cannot
44
+ express. Import the library and bring them into scope:
45
+
46
+ ```typespec
47
+ import "just-the-type";
48
+ using JustTheType;
49
+ ```
50
+
51
+ | Decorator | Target | Effect |
52
+ | ------------------------------- | -------------------------- | ------------------------------------------------------------ |
53
+ | `@promise` | `op`, `interface` | Wraps return types in `Promise<T>` |
54
+ | `@readonly` | property, `model` | Emits the `readonly` modifier |
55
+ | `@tsType("Date")` | `scalar`, property | Replaces the emitted type with raw TypeScript |
56
+ | `@tsType("Dayjs", "dayjs")` | `scalar`, property | Same, plus `import type { Dayjs } from "dayjs";` |
57
+ | `@literalUnion` | `enum` | Emits `type Color = "red" \| "blue"` instead of a TS enum |
58
+
59
+ `@tsType` also works on built-in scalars via augment decorators, e.g. map all
60
+ `utcDateTime` to real `Date` objects:
61
+
62
+ ```typespec
63
+ @@tsType(utcDateTime, "Date");
64
+ ```
65
+
66
+ For qualified overrides such as `@tsType("Temporal.Instant", "@js-temporal/polyfill")`
67
+ the root identifier (`Temporal`) is imported.
68
+
69
+ ## Development
70
+
71
+ ```sh
72
+ pnpm install
73
+ pnpm test # vitest
74
+ pnpm build # tsc -> dist/
75
+ ```
76
+
77
+ Try it on the sample:
78
+
79
+ ```sh
80
+ pnpm sample # tsp compile sample -> sample/tsp-output/
81
+ ```
82
+
83
+ ## License
84
+
85
+ [MIT](LICENSE)
@@ -0,0 +1,22 @@
1
+ import { DecoratorContext, Enum, Interface, Model, ModelProperty, Operation, Program, Scalar, Type } from "@typespec/compiler";
2
+ export interface TsTypeOverride {
3
+ type: string;
4
+ from?: string;
5
+ }
6
+ declare function $promise(context: DecoratorContext, target: Operation | Interface): void;
7
+ declare function $readonly(context: DecoratorContext, target: ModelProperty | Model): void;
8
+ declare function $tsType(context: DecoratorContext, target: Scalar | ModelProperty, type: string, importFrom?: string): void;
9
+ declare function $literalUnion(context: DecoratorContext, target: Enum): void;
10
+ export declare const $decorators: {
11
+ JustTheType: {
12
+ promise: typeof $promise;
13
+ readonly: typeof $readonly;
14
+ tsType: typeof $tsType;
15
+ literalUnion: typeof $literalUnion;
16
+ };
17
+ };
18
+ export declare function isAsync(program: Program, type: Type): boolean;
19
+ export declare function isReadonly(program: Program, type: Type): boolean;
20
+ export declare function getTsType(program: Program, type: Type): TsTypeOverride | undefined;
21
+ export declare function isLiteralUnion(program: Program, type: Type): boolean;
22
+ export {};
@@ -0,0 +1,42 @@
1
+ import { $lib } from "./lib.js";
2
+ const keys = $lib.stateKeys;
3
+ // Decorator arguments declared as `valueof string` arrive as plain strings;
4
+ // without the extern declaration (e.g. in tests) they arrive as StringLiteral types.
5
+ function asString(value) {
6
+ return typeof value === "object" ? value.value : value;
7
+ }
8
+ function $promise(context, target) {
9
+ context.program.stateSet(keys.async).add(target);
10
+ }
11
+ function $readonly(context, target) {
12
+ context.program.stateSet(keys.readonly).add(target);
13
+ }
14
+ function $tsType(context, target, type, importFrom) {
15
+ context.program.stateMap(keys.tsType).set(target, {
16
+ type: asString(type),
17
+ from: asString(importFrom),
18
+ });
19
+ }
20
+ function $literalUnion(context, target) {
21
+ context.program.stateSet(keys.literalUnion).add(target);
22
+ }
23
+ export const $decorators = {
24
+ JustTheType: {
25
+ promise: $promise,
26
+ readonly: $readonly,
27
+ tsType: $tsType,
28
+ literalUnion: $literalUnion,
29
+ },
30
+ };
31
+ export function isAsync(program, type) {
32
+ return program.stateSet(keys.async).has(type);
33
+ }
34
+ export function isReadonly(program, type) {
35
+ return program.stateSet(keys.readonly).has(type);
36
+ }
37
+ export function getTsType(program, type) {
38
+ return program.stateMap(keys.tsType).get(type);
39
+ }
40
+ export function isLiteralUnion(program, type) {
41
+ return program.stateSet(keys.literalUnion).has(type);
42
+ }
@@ -0,0 +1,2 @@
1
+ import { Program } from "@typespec/compiler";
2
+ export declare function emitTypes(program: Program): string;
@@ -0,0 +1,324 @@
1
+ import { getDoc, isArrayModelType, isRecordModelType, isTemplateDeclaration, } from "@typespec/compiler";
2
+ import ts from "typescript";
3
+ import { getTsType, isAsync, isLiteralUnion, isReadonly } from "./decorators.js";
4
+ const f = ts.factory;
5
+ const stringType = () => f.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
6
+ const numberType = () => f.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
7
+ const bigintType = () => f.createKeywordTypeNode(ts.SyntaxKind.BigIntKeyword);
8
+ const unknownType = () => f.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
9
+ const scalarMap = {
10
+ string: stringType,
11
+ boolean: () => f.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword),
12
+ bytes: () => f.createTypeReferenceNode("Uint8Array"),
13
+ int64: bigintType,
14
+ uint64: bigintType,
15
+ numeric: numberType,
16
+ integer: numberType,
17
+ float: numberType,
18
+ decimal: numberType,
19
+ decimal128: numberType,
20
+ float32: numberType,
21
+ float64: numberType,
22
+ int32: numberType,
23
+ int16: numberType,
24
+ int8: numberType,
25
+ safeint: numberType,
26
+ uint32: numberType,
27
+ uint16: numberType,
28
+ uint8: numberType,
29
+ url: stringType,
30
+ plainDate: stringType,
31
+ plainTime: stringType,
32
+ utcDateTime: stringType,
33
+ offsetDateTime: stringType,
34
+ duration: stringType,
35
+ };
36
+ const exportModifier = () => [f.createModifier(ts.SyntaxKind.ExportKeyword)];
37
+ export function emitTypes(program) {
38
+ const ctx = { program, imports: new Map() };
39
+ const statements = [];
40
+ collectNamespace(ctx, program.getGlobalNamespaceType(), statements);
41
+ statements.unshift(...importDeclarations(ctx));
42
+ const file = ts.createSourceFile("types.ts", "", ts.ScriptTarget.ES2022, false, ts.ScriptKind.TS);
43
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
44
+ return printer.printList(ts.ListFormat.MultiLine, f.createNodeArray(statements), file);
45
+ }
46
+ function importDeclarations(ctx) {
47
+ return [...ctx.imports].map(([module, names]) => f.createImportDeclaration(undefined, f.createImportClause(ts.SyntaxKind.TypeKeyword, undefined, f.createNamedImports([...names].sort().map((name) => f.createImportSpecifier(false, undefined, f.createIdentifier(name))))), f.createStringLiteral(module)));
48
+ }
49
+ function collectNamespace(ctx, ns, statements) {
50
+ for (const model of ns.models.values()) {
51
+ if (isTemplateDeclaration(model) && !canEmitGeneric(ctx, model))
52
+ continue;
53
+ statements.push(modelDeclaration(ctx, model));
54
+ }
55
+ for (const scalar of ns.scalars.values()) {
56
+ if (!isTemplateDeclaration(scalar))
57
+ statements.push(scalarDeclaration(ctx, scalar));
58
+ }
59
+ for (const en of ns.enums.values())
60
+ statements.push(enumDeclaration(ctx, en));
61
+ for (const union of ns.unions.values()) {
62
+ if (!union.name)
63
+ continue;
64
+ if (isTemplateDeclaration(union) && !canEmitGeneric(ctx, union))
65
+ continue;
66
+ statements.push(withDoc(ctx, union, f.createTypeAliasDeclaration(exportModifier(), union.name, typeParameterDeclarations(ctx, union), unionToNode(ctx, union))));
67
+ }
68
+ for (const op of ns.operations.values()) {
69
+ if (isTemplateDeclaration(op))
70
+ continue;
71
+ statements.push(withDoc(ctx, op, f.createTypeAliasDeclaration(exportModifier(), op.name, undefined, f.createFunctionTypeNode(undefined, operationParameters(ctx, op), returnTypeNode(ctx, op)))));
72
+ }
73
+ for (const iface of ns.interfaces.values()) {
74
+ if (!isTemplateDeclaration(iface))
75
+ statements.push(interfaceDeclaration(ctx, iface));
76
+ }
77
+ for (const child of ns.namespaces.values()) {
78
+ if (child.name === "TypeSpec" && !child.namespace?.namespace)
79
+ continue;
80
+ collectNamespace(ctx, child, statements);
81
+ }
82
+ }
83
+ function modelDeclaration(ctx, model) {
84
+ const typeParameters = typeParameterDeclarations(ctx, model);
85
+ if (isArrayModelType(model)) {
86
+ return withDoc(ctx, model, f.createTypeAliasDeclaration(exportModifier(), model.name, typeParameters, f.createArrayTypeNode(typeToNode(ctx, model.indexer.value))));
87
+ }
88
+ const heritage = model.baseModel && namedReference(ctx, model.baseModel);
89
+ return withDoc(ctx, model, f.createInterfaceDeclaration(exportModifier(), model.name, typeParameters, heritage
90
+ ? [
91
+ f.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
92
+ f.createExpressionWithTypeArguments(f.createIdentifier(heritage.name), heritage.args),
93
+ ]),
94
+ ]
95
+ : undefined, modelMembers(ctx, model)));
96
+ }
97
+ function modelMembers(ctx, model) {
98
+ const members = [...model.properties.values()].map((prop) => withDoc(ctx, prop, f.createPropertySignature(isReadonly(ctx.program, prop) || isReadonly(ctx.program, model)
99
+ ? [f.createModifier(ts.SyntaxKind.ReadonlyKeyword)]
100
+ : undefined, propertyName(prop.name), prop.optional ? f.createToken(ts.SyntaxKind.QuestionToken) : undefined, propertyTypeNode(ctx, prop))));
101
+ if (model.indexer && model.indexer.key.name === "string") {
102
+ members.push(f.createIndexSignature(undefined, [f.createParameterDeclaration(undefined, undefined, "key", undefined, stringType())], typeToNode(ctx, model.indexer.value)));
103
+ }
104
+ return members;
105
+ }
106
+ function propertyTypeNode(ctx, prop) {
107
+ const override = getTsType(ctx.program, prop);
108
+ return override ? overrideNode(ctx, override) : typeToNode(ctx, prop.type);
109
+ }
110
+ function overrideNode(ctx, override) {
111
+ if (override.from) {
112
+ const root = override.type.split(/[.<]/)[0];
113
+ const names = ctx.imports.get(override.from) ?? new Set();
114
+ names.add(root);
115
+ ctx.imports.set(override.from, names);
116
+ }
117
+ return f.createTypeReferenceNode(override.type);
118
+ }
119
+ function propertyName(name) {
120
+ return /^[A-Za-z_$][\w$]*$/.test(name) ? f.createIdentifier(name) : f.createStringLiteral(name);
121
+ }
122
+ function enumDeclaration(ctx, en) {
123
+ if (isLiteralUnion(ctx.program, en)) {
124
+ return withDoc(ctx, en, f.createTypeAliasDeclaration(exportModifier(), en.name, undefined, f.createUnionTypeNode([...en.members.values()].map((member) => literalNode(member.value ?? member.name)))));
125
+ }
126
+ const members = [...en.members.values()].map((member) => {
127
+ const value = member.value ?? member.name;
128
+ return withDoc(ctx, member, f.createEnumMember(propertyName(member.name), typeof value === "number" ? f.createNumericLiteral(value) : f.createStringLiteral(value)));
129
+ });
130
+ return withDoc(ctx, en, f.createEnumDeclaration(exportModifier(), en.name, members));
131
+ }
132
+ function literalNode(value) {
133
+ return f.createLiteralTypeNode(typeof value === "number" ? f.createNumericLiteral(value) : f.createStringLiteral(value));
134
+ }
135
+ function scalarDeclaration(ctx, scalar) {
136
+ const override = getTsType(ctx.program, scalar);
137
+ const base = scalar.baseScalar;
138
+ const node = override
139
+ ? overrideNode(ctx, override)
140
+ : !base
141
+ ? unknownType()
142
+ : inStdNamespace(base)
143
+ ? stdScalarNode(ctx, base)
144
+ : f.createTypeReferenceNode(base.name);
145
+ return withDoc(ctx, scalar, f.createTypeAliasDeclaration(exportModifier(), scalar.name, undefined, node));
146
+ }
147
+ function returnTypeNode(ctx, op, container) {
148
+ const node = typeToNode(ctx, op.returnType);
149
+ const async = isAsync(ctx.program, op) || (container !== undefined && isAsync(ctx.program, container));
150
+ return async ? f.createTypeReferenceNode("Promise", [node]) : node;
151
+ }
152
+ function operationParameters(ctx, op) {
153
+ return [...op.parameters.properties.values()].map((prop) => f.createParameterDeclaration(undefined, undefined, prop.name, prop.optional ? f.createToken(ts.SyntaxKind.QuestionToken) : undefined, propertyTypeNode(ctx, prop)));
154
+ }
155
+ function interfaceDeclaration(ctx, iface) {
156
+ const members = [...iface.operations.values()].map((op) => withDoc(ctx, op, f.createMethodSignature(undefined, op.name, undefined, undefined, operationParameters(ctx, op), returnTypeNode(ctx, op, iface))));
157
+ return withDoc(ctx, iface, f.createInterfaceDeclaration(exportModifier(), iface.name, undefined, undefined, members));
158
+ }
159
+ function unionToNode(ctx, union) {
160
+ return f.createUnionTypeNode([...union.variants.values()].map((variant) => typeToNode(ctx, variant.type)));
161
+ }
162
+ function typeToNode(ctx, type) {
163
+ switch (type.kind) {
164
+ case "Model": {
165
+ // Inside template declarations Array<T>/Record<T> instances carry no
166
+ // indexer yet, so resolve them via their template argument instead.
167
+ const element = (type.name === "Array" || type.name === "Record") && inStdNamespace(type)
168
+ ? (type.indexer?.value ?? typeArgument(type))
169
+ : undefined;
170
+ if (element) {
171
+ return type.name === "Array"
172
+ ? f.createArrayTypeNode(typeToNode(ctx, element))
173
+ : f.createTypeReferenceNode("Record", [stringType(), typeToNode(ctx, element)]);
174
+ }
175
+ if (isArrayModelType(type)) {
176
+ return f.createArrayTypeNode(typeToNode(ctx, type.indexer.value));
177
+ }
178
+ if (isRecordModelType(type)) {
179
+ return f.createTypeReferenceNode("Record", [stringType(), typeToNode(ctx, type.indexer.value)]);
180
+ }
181
+ const ref = namedReference(ctx, type);
182
+ return ref
183
+ ? f.createTypeReferenceNode(ref.name, ref.args)
184
+ : f.createTypeLiteralNode(modelMembers(ctx, type));
185
+ }
186
+ case "Scalar":
187
+ // User scalars are declared as aliases, so a @tsType override applies
188
+ // at the declaration site and references keep using the scalar's name.
189
+ return inStdNamespace(type) ? stdScalarNode(ctx, type) : f.createTypeReferenceNode(type.name);
190
+ case "Enum":
191
+ return f.createTypeReferenceNode(type.name);
192
+ case "EnumMember":
193
+ if (isLiteralUnion(ctx.program, type.enum)) {
194
+ return literalNode(type.value ?? type.name);
195
+ }
196
+ return f.createTypeReferenceNode(f.createQualifiedName(f.createIdentifier(type.enum.name), type.name));
197
+ case "Union": {
198
+ const ref = type.name ? namedReference(ctx, type) : undefined;
199
+ return ref ? f.createTypeReferenceNode(ref.name, ref.args) : unionToNode(ctx, type);
200
+ }
201
+ case "Tuple": {
202
+ const tuple = f.createTupleTypeNode(type.values.map((value) => typeToNode(ctx, value)));
203
+ return ts.setEmitFlags(tuple, ts.EmitFlags.SingleLine);
204
+ }
205
+ case "TemplateParameter":
206
+ return f.createTypeReferenceNode(type.node.id.sv);
207
+ case "String":
208
+ return f.createLiteralTypeNode(f.createStringLiteral(type.value));
209
+ case "Number":
210
+ return f.createLiteralTypeNode(f.createNumericLiteral(type.value));
211
+ case "Boolean":
212
+ return f.createLiteralTypeNode(type.value ? f.createTrue() : f.createFalse());
213
+ case "StringTemplate":
214
+ return stringTemplateToNode(ctx, type);
215
+ case "Intrinsic":
216
+ switch (type.name) {
217
+ case "null":
218
+ return f.createLiteralTypeNode(f.createNull());
219
+ case "void":
220
+ return f.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
221
+ case "never":
222
+ return f.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword);
223
+ default:
224
+ return unknownType();
225
+ }
226
+ default:
227
+ return unknownType();
228
+ }
229
+ }
230
+ function stringTemplateToNode(ctx, template) {
231
+ if (template.stringValue !== undefined) {
232
+ return f.createLiteralTypeNode(f.createStringLiteral(template.stringValue));
233
+ }
234
+ const spans = [...template.spans];
235
+ let i = 0;
236
+ let head = "";
237
+ while (i < spans.length && !spans[i].isInterpolated) {
238
+ head += spans[i].type.value;
239
+ i++;
240
+ }
241
+ const tsSpans = [];
242
+ while (i < spans.length) {
243
+ const inner = typeToNode(ctx, spans[i].type);
244
+ i++;
245
+ let text = "";
246
+ while (i < spans.length && !spans[i].isInterpolated) {
247
+ text += spans[i].type.value;
248
+ i++;
249
+ }
250
+ tsSpans.push(f.createTemplateLiteralTypeSpan(inner, i >= spans.length ? f.createTemplateTail(text) : f.createTemplateMiddle(text)));
251
+ }
252
+ return f.createTemplateLiteralType(f.createTemplateHead(head), tsSpans);
253
+ }
254
+ function typeArgument(type) {
255
+ const arg = type.templateMapper?.args[0];
256
+ return arg?.entityKind === "Type" ? arg : undefined;
257
+ }
258
+ function namedReference(ctx, type) {
259
+ if (!type.name || inStdNamespace(type))
260
+ return undefined;
261
+ if (!type.templateMapper)
262
+ return { name: type.name };
263
+ if (!canEmitGeneric(ctx, type))
264
+ return undefined;
265
+ const args = [];
266
+ for (const arg of type.templateMapper.args) {
267
+ if (arg.entityKind !== "Type")
268
+ return undefined;
269
+ args.push(typeToNode(ctx, arg));
270
+ }
271
+ return { name: type.name, args };
272
+ }
273
+ function templateParamNodes(type) {
274
+ const node = type.node;
275
+ return node?.templateParameters ?? [];
276
+ }
277
+ // `valueof` constraints have no type-level equivalent; the checker resolves
278
+ // them to ErrorType, which marks the template as not emittable as a generic.
279
+ function resolveExpression(ctx, expr) {
280
+ const type = ctx.program.checker.getTypeForNode(expr);
281
+ return type.kind === "Intrinsic" && type.name === "ErrorType" ? undefined : type;
282
+ }
283
+ function canEmitGeneric(ctx, type) {
284
+ return templateParamNodes(type).every((param) => !param.constraint || resolveExpression(ctx, param.constraint));
285
+ }
286
+ function typeParameterDeclarations(ctx, type) {
287
+ const params = templateParamNodes(type);
288
+ if (params.length === 0)
289
+ return undefined;
290
+ return params.map((param) => {
291
+ const constraint = param.constraint && resolveExpression(ctx, param.constraint);
292
+ const defaultType = param.default && resolveExpression(ctx, param.default);
293
+ return f.createTypeParameterDeclaration(undefined, param.id.sv, constraint && typeToNode(ctx, constraint), defaultType && typeToNode(ctx, defaultType));
294
+ });
295
+ }
296
+ function stdScalarNode(ctx, scalar) {
297
+ let current = scalar;
298
+ while (current) {
299
+ const override = getTsType(ctx.program, current);
300
+ if (override)
301
+ return overrideNode(ctx, override);
302
+ const mapped = scalarMap[current.name];
303
+ if (mapped)
304
+ return mapped();
305
+ current = current.baseScalar;
306
+ }
307
+ return unknownType();
308
+ }
309
+ function inStdNamespace(type) {
310
+ let ns = type.namespace;
311
+ while (ns?.namespace) {
312
+ if (!ns.namespace.name && !ns.namespace.namespace)
313
+ return ns.name === "TypeSpec";
314
+ ns = ns.namespace;
315
+ }
316
+ return false;
317
+ }
318
+ function withDoc(ctx, type, node) {
319
+ const doc = getDoc(ctx.program, type);
320
+ if (doc) {
321
+ ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, `* ${doc.replace(/\n/g, "\n * ")} `, true);
322
+ }
323
+ return node;
324
+ }
@@ -0,0 +1,5 @@
1
+ import { EmitContext } from "@typespec/compiler";
2
+ export { $decorators } from "./decorators.js";
3
+ export { emitTypes } from "./emitter.js";
4
+ export { $lib } from "./lib.js";
5
+ export declare function $onEmit(context: EmitContext): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ import { emitFile, resolvePath } from "@typespec/compiler";
2
+ import { emitTypes } from "./emitter.js";
3
+ export { $decorators } from "./decorators.js";
4
+ export { emitTypes } from "./emitter.js";
5
+ export { $lib } from "./lib.js";
6
+ export async function $onEmit(context) {
7
+ if (context.program.compilerOptions.noEmit)
8
+ return;
9
+ await emitFile(context.program, {
10
+ path: resolvePath(context.emitterOutputDir, "types.ts"),
11
+ content: emitTypes(context.program),
12
+ });
13
+ }
package/dist/lib.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare const $lib: import("@typespec/compiler").TypeSpecLibrary<{
2
+ [code: string]: import("@typespec/compiler").DiagnosticMessages;
3
+ }, Record<string, any>, "async" | "readonly" | "tsType" | "literalUnion">;
package/dist/lib.js ADDED
@@ -0,0 +1,11 @@
1
+ import { createTypeSpecLibrary } from "@typespec/compiler";
2
+ export const $lib = createTypeSpecLibrary({
3
+ name: "just-the-type",
4
+ diagnostics: {},
5
+ state: {
6
+ async: { description: "Operations and interfaces whose return types are wrapped in Promise" },
7
+ readonly: { description: "Properties and models emitted with the readonly modifier" },
8
+ tsType: { description: "Raw TypeScript type overrides with optional import source" },
9
+ literalUnion: { description: "Enums emitted as unions of literals instead of TS enums" },
10
+ },
11
+ });
package/lib/main.tsp ADDED
@@ -0,0 +1,21 @@
1
+ import "../dist/index.js";
2
+
3
+ using TypeSpec.Reflection;
4
+
5
+ namespace JustTheType;
6
+
7
+ /** Wrap the return type of the operation (or all operations of an interface) in Promise. */
8
+ extern dec promise(target: Operation | Interface);
9
+
10
+ /** Emit the property (or all properties of the model) with the readonly modifier. */
11
+ extern dec readonly(target: ModelProperty | Model);
12
+
13
+ /**
14
+ * Replace the emitted type with raw TypeScript.
15
+ * @param type TypeScript type text, e.g. "Date" or "Temporal.Instant".
16
+ * @param importFrom Optional module to type-import the type's root identifier from.
17
+ */
18
+ extern dec tsType(target: Scalar | ModelProperty, type: valueof string, importFrom?: valueof string);
19
+
20
+ /** Emit the enum as a union of its literal values instead of a TS enum. */
21
+ extern dec literalUnion(target: Enum);
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "just-the-type",
3
+ "version": "0.1.0",
4
+ "description": "TypeSpec emitter that emits plain TypeScript types",
5
+ "keywords": [
6
+ "typespec",
7
+ "typespec-emitter",
8
+ "typescript",
9
+ "codegen",
10
+ "types"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Daniel Kneip",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/daniel-kneip/just-the-type.git"
17
+ },
18
+ "homepage": "https://github.com/daniel-kneip/just-the-type#readme",
19
+ "bugs": "https://github.com/daniel-kneip/just-the-type/issues",
20
+ "type": "module",
21
+ "main": "dist/index.js",
22
+ "exports": {
23
+ ".": {
24
+ "typespec": "./lib/main.tsp",
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "lib"
32
+ ],
33
+ "engines": {
34
+ "node": ">=20.0.0"
35
+ },
36
+ "peerDependencies": {
37
+ "@typespec/compiler": ">=1.0.0"
38
+ },
39
+ "dependencies": {
40
+ "typescript": "^5.9.3"
41
+ },
42
+ "devDependencies": {
43
+ "@typespec/compiler": "1.13.0",
44
+ "vitest": "4.1.8"
45
+ },
46
+ "scripts": {
47
+ "build": "tsc",
48
+ "test": "vitest run",
49
+ "sample": "tsp compile sample"
50
+ }
51
+ }