@typespec/http-server-js
Version:
TypeSpec HTTP server code generator for JavaScript
813 lines • 31.1 kB
JavaScript
// Copyright (c) Microsoft Corporation
// Licensed under the MIT license.
import { NoTarget } from "@typespec/compiler";
import { reportDiagnostic } from "../lib.js";
import { parseCase } from "../util/case.js";
import { getFullyQualifiedTypeName } from "../util/name.js";
import { UnreachableError } from "../util/error.js";
import { module as dateTimeModule } from "../../generated-defs/helpers/datetime.js";
import { module as temporalNativeHelpers } from "../../generated-defs/helpers/temporal/native.js";
import { module as temporalPolyfillHelpers } from "../../generated-defs/helpers/temporal/polyfill.js";
import { emitDocumentation } from "./documentation.js";
/**
* Resolves the encoding of Duration values to a number of seconds.
*/
const DURATION_NUMBER_ENCODING = (_, module) => {
module.imports.push({ from: dateTimeModule, binder: ["Duration"] });
return {
encodeTemplate: "Duration.totalSeconds({})",
decodeTemplate: "Duration.fromTotalSeconds({})",
};
};
/**
* Resolves the encoding of Duration values to a BigInt number of seconds.
*/
const DURATION_BIGINT_ENCODING = (_, module) => {
module.imports.push({ from: dateTimeModule, binder: ["Duration"] });
return {
encodeTemplate: "Duration.totalSecondsBigInt({})",
decodeTemplate: "Duration.fromTotalSeconds(globalThis.Number({}))",
};
};
/**
* Resolves the encoding of Duration values to a BigDecimal number of seconds.
*/
const DURATION_BIGDECIMAL_ENCODING = (_, module) => {
module.imports.push({ from: dateTimeModule, binder: ["Duration"] }, { from: "decimal.js", binder: ["Decimal"] });
return {
encodeTemplate: "new Decimal(Duration.totalSeconds({}).toString())",
decodeTemplate: "Duration.fromSeconds(({}).toNumber())",
};
};
const DURATION = (ctx, module) => {
const mode = ctx.options.datetime ?? "temporal-polyfill";
if (mode === "temporal-polyfill") {
module.imports.push({ from: "temporal-polyfill", binder: ["Temporal"] });
}
const isTemporal = mode === "temporal" || mode === "temporal-polyfill";
const temporalRef = mode === "temporal-polyfill" ? "Temporal" : "globalThis.Temporal";
if (!isTemporal)
return DURATION_CUSTOM;
const isPolyfill = mode === "temporal-polyfill";
return {
type: "Temporal.Duration",
isJsonCompatible: false,
encodings: {
"TypeSpec.string": {
default: { via: "iso8601" },
iso8601: {
encodeTemplate: `({}).toString()`,
decodeTemplate: `${temporalRef}.Duration.from({})`,
},
},
...Object.fromEntries(["int32", "uint32", "float32", "float64"].map((n) => [
`TypeSpec.${n}`,
{
default: { via: "seconds" },
seconds: {
encodeTemplate: (_, module) => {
module.imports.push({
from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
binder: [`durationTotalSeconds`],
});
return `durationTotalSeconds({})`;
},
decodeTemplate: `${temporalRef}.Duration.from({ seconds: {} })`,
},
},
])),
...Object.fromEntries(["int64", "uint64", "integer"].map((n) => [
`TypeSpec.${n}`,
{
default: { via: "seconds" },
seconds: {
encodeTemplate: (_, module) => {
module.imports.push({
from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
binder: [`durationTotalSecondsBigInt`],
});
return `durationTotalSecondsBigInt({})`;
},
decodeTemplate: `${temporalRef}.Duration.from({ seconds: globalThis.Number({}) })`,
},
},
])),
"TypeSpec.float": {
default: { via: "seconds" },
seconds: {
encodeTemplate: (_, module) => {
module.imports.push({
from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
binder: [`durationTotalSecondsBigInt`],
});
return `new Decimal(durationTotalSecondsBigInt({}).toString())`;
},
decodeTemplate: `${temporalRef}.Duration.from({ seconds: ({}).toNumber() })`,
},
},
},
defaultEncodings: {
byMimeType: {
"application/json": ["TypeSpec.string", "iso8601"],
},
},
};
};
const DURATION_CUSTOM = {
type: function importDuration(_, module) {
module.imports.push({ from: dateTimeModule, binder: ["Duration"] });
return "Duration";
},
encodings: {
"TypeSpec.string": {
default: {
via: "iso8601",
},
iso8601: function importDurationForEncode(_, module) {
module.imports.push({ from: dateTimeModule, binder: ["Duration"] });
return {
encodeTemplate: "Duration.toISO8601({})",
decodeTemplate: "Duration.parseISO8601({})",
};
},
},
...Object.fromEntries(["int32", "uint32", "float32", "float64"].map((n) => [
`TypeSpec.${n}`,
{
default: { via: "seconds" },
seconds: DURATION_NUMBER_ENCODING,
},
])),
...Object.fromEntries(["int64", "uint64"].map((n) => [
`TypeSpec.${n}`,
{
default: { via: "seconds" },
seconds: DURATION_BIGINT_ENCODING,
},
])),
"TypeSpec.float": {
default: { via: "seconds" },
seconds: DURATION_BIGDECIMAL_ENCODING,
},
},
defaultEncodings: {
byMimeType: {
"application/json": ["TypeSpec.string", "iso8601"],
},
},
isJsonCompatible: false,
};
const NUMBER = {
type: "number",
encodings: {
"TypeSpec.string": {
default: {
encodeTemplate: "globalThis.String({})",
decodeTemplate: "globalThis.Number({})",
},
},
},
isJsonCompatible: true,
};
const BIGDECIMAL = {
type(_, module) {
module.imports.push({ from: "decimal.js", binder: ["Decimal"] });
return "Decimal";
},
encodings: {
"TypeSpec.string": {
default: {
encodeTemplate: "({}).toString()",
decodeTemplate: "new Decimal({})",
},
},
},
isJsonCompatible: false,
};
const BIGINT = {
type: "bigint",
encodings: {
"TypeSpec.string": {
default: {
encodeTemplate: "globalThis.String({})",
decodeTemplate: "globalThis.BigInt({})",
},
},
"TypeSpec.safeint": {
lossy: {
encodeTemplate: "globalThis.Number({})",
decodeTemplate: "globalThis.BigInt({})",
},
},
},
defaultEncodings: {
byMimeType: {
"application/json": ["TypeSpec.safeint", "lossy"],
},
},
isJsonCompatible: false,
};
/**
* Declarative scalar table.
*
* This table defines how TypeSpec scalars are represented in JS/TS.
*
* The entries are the fully-qualified names of scalars, and the values are objects that describe how the scalar
* is represented.
*
* Each representation has a `type`, indicating the TypeScript type that represents the scalar at runtime.
*
* The `encodings` object describes how the scalar can be encoded/decoded to/from other types. Encodings
* are named, and each encoding has an `encodeTemplate` and `decodeTemplate` that describe how to encode and decode
* the scalar to/from the target type using the encoding. Encodings can also optionally have a `via` field that
* indicates that the encoding is a modification of the data yielded by another encoding.
*
* The `defaultEncodings` object describes the default encodings to use for the scalar in various contexts. The
* `byMimeType` object maps MIME types to encoding pairs, and the `http` object maps HTTP metadata contexts to
* encoding pairs.
*/
const SCALARS = new Map([
[
"TypeSpec.bytes",
{
type: "Uint8Array",
encodings: {
"TypeSpec.string": {
base64: {
encodeTemplate: "({} instanceof globalThis.Buffer ? {} : globalThis.Buffer.from({})).toString('base64')",
decodeTemplate: "globalThis.Buffer.from({}, 'base64')",
},
base64url: {
via: "base64",
encodeTemplate: "globalThis.encodeURIComponent({})",
decodeTemplate: "globalThis.decodeURIComponent({})",
},
},
},
defaultEncodings: {
byMimeType: { "application/json": ["TypeSpec.string", "base64"] },
},
isJsonCompatible: false,
},
],
[
"TypeSpec.boolean",
{
type: "boolean",
encodings: {
"TypeSpec.string": {
default: {
encodeTemplate: "globalThis.String({})",
decodeTemplate: '({} === "false" ? false : globalThis.Boolean({}))',
},
},
},
isJsonCompatible: true,
},
],
[
"TypeSpec.string",
{
type: "string",
// This little no-op encoding makes it so that we can attempt to encode string to itself infallibly and it will
// do nothing. We therefore don't need to redundantly describe HTTP encodings for query, header, etc. because
// they rely on the ["TypeSpec.string", "default"] encoding in the absence of a more specific encoding.
encodings: {
"TypeSpec.string": {
default: { encodeTemplate: "{}", decodeTemplate: "{}" },
},
},
isJsonCompatible: true,
},
],
["TypeSpec.float32", NUMBER],
["TypeSpec.float64", NUMBER],
["TypeSpec.uint64", BIGINT],
["TypeSpec.uint32", NUMBER],
["TypeSpec.uint16", NUMBER],
["TypeSpec.uint8", NUMBER],
["TypeSpec.int64", BIGINT],
["TypeSpec.int32", NUMBER],
["TypeSpec.int16", NUMBER],
["TypeSpec.int8", NUMBER],
["TypeSpec.safeint", NUMBER],
["TypeSpec.numeric", BIGDECIMAL],
["TypeSpec.float", BIGDECIMAL],
["TypeSpec.decimal", BIGDECIMAL],
["TypeSpec.decimal128", BIGDECIMAL],
["TypeSpec.integer", BIGINT],
["TypeSpec.plainDate", dateTime("plainDate")],
["TypeSpec.plainTime", dateTime("plainTime")],
["TypeSpec.utcDateTime", dateTime("utcDateTime")],
["TypeSpec.offsetDateTime", dateTime("offsetDateTime")],
[
"TypeSpec.unixTimestamp32",
{
type: "number",
encodings: {
"TypeSpec.string": {
default: {
encodeTemplate: "globalThis.String({})",
decodeTemplate: "globalThis.Number({})",
},
},
"TypeSpec.int32": {
default: { via: "unixTimestamp" },
unixTimestamp: {
encodeTemplate: "{}",
decodeTemplate: "{}",
},
},
"TypeSpec.int64": {
default: { via: "unixTimestamp" },
unixTimestamp: {
encodeTemplate: "globalThis.BigInt({})",
decodeTemplate: "globalThis.Number({})",
},
},
},
isJsonCompatible: true,
},
],
["TypeSpec.duration", DURATION],
]);
/**
* Gets the DateTime Scalar specification for a given date time type.
*/
function dateTime(t) {
return (ctx, module) => {
const mode = ctx.options.datetime ?? "temporal-polyfill";
if (mode === "temporal-polyfill") {
module.imports.push({ from: "temporal-polyfill", binder: ["Temporal"] });
}
const isTemporal = mode === "temporal" || mode === "temporal-polyfill";
const temporalRef = mode === "temporal-polyfill" ? "Temporal" : "globalThis.Temporal";
let type;
switch (t) {
case "plainDate":
type = isTemporal ? "Temporal.PlainDate" : "Date";
break;
case "plainTime":
type = isTemporal ? "Temporal.PlainTime" : "Date";
break;
case "utcDateTime":
type = isTemporal ? "Temporal.Instant" : "Date";
break;
case "offsetDateTime":
type = isTemporal ? "Temporal.ZonedDateTime" : "Date";
break;
default:
void t;
throw new UnreachableError(`Unknown datetime type: ${t}`);
}
return {
type,
isJsonCompatible: false,
encodings: isTemporal
? TEMPORAL_ENCODERS(temporalRef, mode === "temporal-polyfill")[t]
: LEGACY_DATETIME_ENCODER,
defaultEncodings: {
byMimeType: {
"application/json": ["TypeSpec.string", "rfc3339"],
},
http: {
header: ["TypeSpec.string", "rfc7231"],
query: ["TypeSpec.string", "rfc3339"],
cookie: ["TypeSpec.string", "rfc7231"],
path: ["TypeSpec.string", "rfc3339"],
},
},
};
};
}
const TEMPORAL_ENCODERS = (temporal, isPolyfill) => {
return {
plainDate: {
"TypeSpec.string": {
default: { via: "iso8601" },
rfc3339: { via: "iso8601" },
iso8601: {
encodeTemplate: "({}).toString()",
decodeTemplate: `${temporal}.PlainDate.from({})`,
},
},
},
plainTime: {
"TypeSpec.string": {
default: { via: "iso8601" },
rfc3339: { via: "iso8601" },
iso8601: {
encodeTemplate: "({}).toString()",
decodeTemplate: `${temporal}.PlainTime.from({})`,
},
},
},
// Temporal.Instant
utcDateTime: {
"TypeSpec.string": {
default: { via: "iso8601" },
rfc3339: { via: "iso8601" },
iso8601: {
encodeTemplate: "({}).toString()",
decodeTemplate: `${temporal}.Instant.from({})`,
},
"http-date": { via: "rfc7231" },
rfc7231: {
encodeTemplate: (ctx, module) => {
module.imports.push({
from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
binder: [`formatHttpDate`],
});
return `formatHttpDate({})`;
},
decodeTemplate: (ctx, module) => {
module.imports.push({
from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
binder: [`parseHttpDate`],
});
return `parseHttpDate({})`;
},
},
},
"TypeSpec.int32": {
default: { via: "unixTimestamp" },
unixTimestamp: {
encodeTemplate: "globalThis.Math.floor(({}).epochMilliseconds / 1000)",
decodeTemplate: `${temporal}.Instant.fromEpochMilliseconds({} * 1000)`,
},
},
"TypeSpec.int64": {
default: { via: "unixTimestamp" },
unixTimestamp: {
encodeTemplate: "({}).epochNanoseconds / 1_000_000_000n",
decodeTemplate: `${temporal}.Instant.fromEpochNanoseconds({} * 1_000_000_000n)`,
},
},
},
// Temporal.ZonedDateTime
offsetDateTime: {
"TypeSpec.string": {
default: { via: "iso8601" },
rfc3339: { via: "iso8601" },
iso8601: {
encodeTemplate: "({}).toString()",
decodeTemplate: `${temporal}.ZonedDateTime.from({})`,
},
"http-date": { via: "rfc7231" },
rfc7231: {
encodeTemplate: (ctx, module) => {
module.imports.push({
from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
binder: [`formatHttpDate`],
});
return `formatHttpDate(({}).toInstant())`;
},
decodeTemplate: (ctx, module) => {
module.imports.push({
from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
binder: [`parseHttpDate`],
});
// HTTP dates are always GMT a.k.a. UTC
return `parseHttpDate({}).toZonedDateTimeISO("UTC")`;
},
},
},
},
};
};
/**
* Encoding and decoding for legacy JS Date.
*/
const LEGACY_DATETIME_ENCODER = {
"TypeSpec.string": {
default: {
via: "iso8601",
},
iso8601: {
encodeTemplate: "({}).toISOString()",
decodeTemplate: "new globalThis.Date({})",
},
rfc3339: {
via: "iso8601",
},
rfc7231: {
encodeTemplate: "({}).toUTCString()",
decodeTemplate: "new globalThis.Date({})",
},
"http-date": {
via: "rfc7231",
},
},
"TypeSpec.int32": {
default: { via: "unixTimestamp" },
unixTimestamp: {
encodeTemplate: "globalThis.Math.floor(({}).getTime() / 1000)",
decodeTemplate: `new globalThis.Date({} * 1000)`,
},
},
"TypeSpec.int64": {
default: { via: "unixTimestamp" },
unixTimestamp: {
encodeTemplate: "globalThis.BigInt(({}).getTime()) / 1000n",
decodeTemplate: `new globalThis.Date(globalThis.Number({}) * 1000)`,
},
},
};
/**
* Emits a declaration for a scalar type.
*
* This is rare in TypeScript, as the scalar will ordinarily be used inline, but may be desirable in some cases.
*
* @param ctx - The emitter context.
* @param module - The module that the scalar is being emitted in.
* @param scalar - The scalar to emit.
* @returns a string that declares an alias to the scalar type in TypeScript.
*/
export function* emitScalar(ctx, scalar, module) {
const jsScalar = getJsScalar(ctx, module, scalar, scalar);
const name = parseCase(scalar.name).pascalCase;
yield* emitDocumentation(ctx, scalar);
yield `export type ${name} = ${jsScalar.type};`;
}
/**
* The store of all scalars known to the emitter in all active Programs.
*/
const __JS_SCALARS_MAP = new WeakMap();
/**
* Gets the scalar store for a given program.
*/
function getScalarStore(ctx) {
let scalars = __JS_SCALARS_MAP.get(ctx.program);
if (scalars === undefined) {
scalars = createScalarStore(ctx);
__JS_SCALARS_MAP.set(ctx.program, scalars);
}
return scalars;
}
/**
* Initializes a scalar store for a given program.
*/
function createScalarStore(ctx) {
const m = new Map();
for (const [scalarName, scalarInfo] of SCALARS) {
const [scalar, diagnostics] = ctx.program.resolveTypeReference(scalarName);
if (diagnostics.length > 0 || !scalar || scalar.kind !== "Scalar") {
throw new UnreachableError(`Failed to resolve built-in scalar '${scalarName}'`);
}
m.set(scalar, createJsScalar(ctx.program, scalar, scalarInfo, m));
}
return m;
}
/**
* Binds a ScalarInfo specification to a JsScalar.
*
* @param program - The program that contains the scalar.
* @param scalar - The scalar to bind.
* @param _scalarInfo - The scalar information spec to bind.
* @param store - The scalar store to use for the scalar.
* @returns a function that takes a JsContext and Module and returns a JsScalar.
*/
function createJsScalar(program, scalar, _scalarInfo, store) {
return (ctx, module) => {
const scalarInfo = typeof _scalarInfo === "function" ? _scalarInfo(ctx, module) : _scalarInfo;
const _http = {};
let _type = undefined;
const self = {
get type() {
return (_type ??=
typeof scalarInfo.type === "function" ? scalarInfo.type(ctx, module) : scalarInfo.type);
},
scalar,
getEncoding(encodeDataOrString, target) {
let encoding = "default";
if (typeof encodeDataOrString === "string") {
encoding = encodeDataOrString;
target = target;
}
else {
encoding = encodeDataOrString.encoding ?? "default";
target = encodeDataOrString.type;
}
const encodingTable = scalarInfo.encodings?.[getFullyQualifiedTypeName(target)];
let encodingSpec = encodingTable?.[encoding] ?? encodingTable?.[encoding.toLowerCase()];
if (encodingSpec === undefined) {
return undefined;
}
encodingSpec =
typeof encodingSpec === "function" ? encodingSpec(ctx, module) : encodingSpec;
let _target = undefined;
let _decodeTemplate = undefined;
let _encodeTemplate = undefined;
return {
get target() {
return (_target ??= store.get(target)(ctx, module));
},
decode(subject) {
_decodeTemplate ??=
typeof encodingSpec.decodeTemplate === "function"
? encodingSpec.decodeTemplate(ctx, module)
: (encodingSpec.decodeTemplate ?? "{}");
subject = `(${subject})`;
// If we have a via, decode it last
subject = _decodeTemplate.replaceAll("{}", subject);
if (isVia(encodingSpec)) {
const via = self.getEncoding(encodingSpec.via, target);
if (via === undefined) {
return subject;
}
subject = via.decode(subject);
}
return subject;
},
encode(subject) {
_encodeTemplate ??=
typeof encodingSpec.encodeTemplate === "function"
? encodingSpec.encodeTemplate(ctx, module)
: (encodingSpec.encodeTemplate ?? "{}");
subject = `(${subject})`;
// If we have a via, encode to it first
if (isVia(encodingSpec)) {
const via = self.getEncoding(encodingSpec.via, target);
if (via === undefined) {
return subject;
}
subject = via.encode(subject);
}
subject = _encodeTemplate.replaceAll("{}", subject);
return subject;
},
};
},
getDefaultMimeEncoding(target) {
const encoding = scalarInfo.defaultEncodings?.byMimeType?.[target];
if (encoding === undefined) {
return undefined;
}
const [encodingType, encodingName] = encoding;
const [encodingScalar, diagnostics] = program.resolveTypeReference(encodingType);
if (diagnostics.length > 0 || !encodingScalar || encodingScalar.kind !== "Scalar") {
throw new UnreachableError(`Failed to resolve built-in scalar '${encodingType}'`);
}
return self.getEncoding(encodingName, encodingScalar);
},
http: {
get header() {
return (_http.header ??= getHttpEncoder(ctx, module, self, "header"));
},
get query() {
return (_http.query ??= getHttpEncoder(ctx, module, self, "query"));
},
get cookie() {
return (_http.cookie ??= getHttpEncoder(ctx, module, self, "cookie"));
},
get path() {
return (_http.path ??= getHttpEncoder(ctx, module, self, "path"));
},
},
isJsonCompatible: scalarInfo.isJsonCompatible,
};
return self;
/**
* Helper to get the HTTP encoders for the scalar.
*/
function getHttpEncoder(ctx, module, self, form) {
const [target, encoding] = scalarInfo.defaultEncodings?.http?.[form] ?? [
"TypeSpec.string",
"default",
];
const [targetScalar, diagnostics] = program.resolveTypeReference(target);
if (diagnostics.length > 0 || !targetScalar || targetScalar.kind !== "Scalar") {
throw new UnreachableError(`Failed to resolve built-in scalar '${target}'`);
}
let encoder = self.getEncoding(encoding, targetScalar);
if (encoder === undefined && scalarInfo.defaultEncodings?.http?.[form]) {
throw new UnreachableError(`Default HTTP ${form} encoding specified but failed to resolve.`);
}
encoder ??= getDefaultHttpStringEncoder(ctx, module, form);
return encoder;
}
};
}
/**
* Returns `true` if the encoding is provided `via` another encoding. False otherwise.
*/
function isVia(encoding) {
return "via" in encoding;
}
/** Map to ensure we don't report the same unrecognized scalar many times. */
const REPORTED_UNRECOGNIZED_SCALARS = new WeakMap();
/**
* Reports a scalar as unrecognized, so that the spec author knows it is treated as `unknown`.
*
* @param ctx - The emitter context.
* @param scalar - The scalar that was not recognized.
* @param target - The diagnostic target to report the error on.
*/
export function reportUnrecognizedScalar(ctx, scalar, target) {
let reported = REPORTED_UNRECOGNIZED_SCALARS.get(ctx.program);
if (reported === undefined) {
reported = new Set();
REPORTED_UNRECOGNIZED_SCALARS.set(ctx.program, reported);
}
if (reported.has(scalar)) {
return;
}
reportDiagnostic(ctx.program, {
code: "unrecognized-scalar",
target: target,
format: {
scalar: getFullyQualifiedTypeName(scalar),
},
});
reported.add(scalar);
}
/**
* Gets the default string encoder for HTTP metadata.
*/
function getDefaultHttpStringEncoder(ctx, module, form) {
const string = ctx.program.checker.getStdType("string");
const scalar = getJsScalar(ctx, module, string, NoTarget);
return {
target: scalar,
encode: HTTP_ENCODE_STRING,
decode: HTTP_DECODE_STRING,
};
}
// Encoders for HTTP metadata.
const HTTP_ENCODE_STRING = (subject) => `JSON.stringify(${subject})`;
const HTTP_DECODE_STRING = (subject) => `JSON.parse(${subject})`;
/**
* A dummy encoder that just converts the value to a string and does not decode it.
*
* This is used for "unknown" scalars.
*/
const DEFAULT_STRING_ENCODER_RAW = {
encode(subject) {
return `String(${subject})`;
},
decode(subject) {
return `${subject}`;
},
};
/**
* A JsScalar value that represents an unknown scalar.
*/
export const JS_SCALAR_UNKNOWN = {
type: "unknown",
scalar: "unknown",
getEncoding: () => undefined,
getDefaultMimeEncoding: () => undefined,
http: {
get header() {
return {
target: JS_SCALAR_UNKNOWN,
...DEFAULT_STRING_ENCODER_RAW,
};
},
get query() {
return {
target: JS_SCALAR_UNKNOWN,
...DEFAULT_STRING_ENCODER_RAW,
};
},
get cookie() {
return {
target: JS_SCALAR_UNKNOWN,
...DEFAULT_STRING_ENCODER_RAW,
};
},
get path() {
return {
target: JS_SCALAR_UNKNOWN,
...DEFAULT_STRING_ENCODER_RAW,
};
},
},
isJsonCompatible: true,
};
/**
* Gets a TypeScript type that can represent a given TypeSpec scalar.
*
* Scalar recognition is recursive. If a scalar is not recognized, we will treat it as its parent scalar and try again.
*
* If no scalar in the chain is recognized, it will be treated as `unknown` and a warning will be issued.
*
* @param program - The program that contains the scalar
* @param scalar - The scalar to get the TypeScript type for
* @param diagnosticTarget - Where to report a diagnostic if the scalar is not recognized.
* @returns a string containing a TypeScript type that can represent the scalar
*/
export function getJsScalar(ctx, module, scalar, diagnosticTarget) {
const scalars = getScalarStore(ctx);
let _scalar = scalar;
while (_scalar !== undefined) {
const jsScalar = scalars.get(_scalar);
if (jsScalar !== undefined) {
return jsScalar(ctx, module);
}
_scalar = _scalar.baseScalar;
}
reportUnrecognizedScalar(ctx, scalar, diagnosticTarget);
return JS_SCALAR_UNKNOWN;
}
//# sourceMappingURL=scalar.js.map