@typespec/http-server-js 0.58.0-alpha.13-dev.6 → 0.58.0-alpha.13-dev.8

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.
@@ -143,6 +143,21 @@ const DURATION_BIGINT_ENCODING: Dependent<ScalarEncoding> = (_, module) => {
143
143
  };
144
144
  };
145
145
 
146
+ /**
147
+ * Resolves the encoding of Duration values to a BigDecimal number of seconds.
148
+ */
149
+ const DURATION_BIGDECIMAL_ENCODING: Dependent<ScalarEncoding> = (_, module) => {
150
+ module.imports.push(
151
+ { from: dateTimeModule, binder: ["Duration"] },
152
+ { from: "decimal.js", binder: ["Decimal"] },
153
+ );
154
+
155
+ return {
156
+ encodeTemplate: "new Decimal(Duration.totalSeconds({}).toString())",
157
+ decodeTemplate: "Duration.fromSeconds({}.toNumber())",
158
+ };
159
+ };
160
+
146
161
  const TYPESPEC_DURATION: ScalarInfo = {
147
162
  type: function importDuration(_, module) {
148
163
  module.imports.push({ from: dateTimeModule, binder: ["Duration"] });
@@ -163,7 +178,7 @@ const TYPESPEC_DURATION: ScalarInfo = {
163
178
  },
164
179
  },
165
180
  ...Object.fromEntries(
166
- ["int32", "uint32"].map((n) => [
181
+ ["int32", "uint32", "float32", "float64"].map((n) => [
167
182
  `TypeSpec.${n}`,
168
183
  {
169
184
  default: { via: "seconds" },
@@ -180,6 +195,10 @@ const TYPESPEC_DURATION: ScalarInfo = {
180
195
  },
181
196
  ]),
182
197
  ),
198
+ "TypeSpec.float": {
199
+ default: { via: "seconds" },
200
+ seconds: DURATION_BIGDECIMAL_ENCODING,
201
+ },
183
202
  },
184
203
  defaultEncodings: {
185
204
  byMimeType: {
@@ -202,6 +221,23 @@ const NUMBER: ScalarInfo = {
202
221
  isJsonCompatible: true,
203
222
  };
204
223
 
224
+ const BIGDECIMAL: ScalarInfo = {
225
+ type(_, module) {
226
+ module.imports.push({ from: "decimal.js", binder: ["Decimal"] });
227
+
228
+ return "Decimal";
229
+ },
230
+ encodings: {
231
+ "TypeSpec.string": {
232
+ default: {
233
+ encodeTemplate: "{}.toString()",
234
+ decodeTemplate: "new Decimal({})",
235
+ },
236
+ },
237
+ },
238
+ isJsonCompatible: false,
239
+ };
240
+
205
241
  /**
206
242
  * Declarative scalar table.
207
243
  *
@@ -287,6 +323,11 @@ const SCALARS = new Map<string, ScalarInfo>([
287
323
  ["TypeSpec.int8", NUMBER],
288
324
  ["TypeSpec.safeint", NUMBER],
289
325
 
326
+ ["TypeSpec.numeric", BIGDECIMAL],
327
+ ["TypeSpec.float", BIGDECIMAL],
328
+ ["TypeSpec.decimal", BIGDECIMAL],
329
+ ["TypeSpec.decimal128", BIGDECIMAL],
330
+
290
331
  [
291
332
  "TypeSpec.integer",
292
333
  {
@@ -3,6 +3,8 @@
3
3
  // Copyright (c) Microsoft Corporation.
4
4
  // Licensed under the MIT License.
5
5
 
6
+ import { hsjsDependencies } from "../../../generated-defs/package.json.js";
7
+
6
8
  import { compile, formatDiagnostic, NodeHost, OperationContainer } from "@typespec/compiler";
7
9
 
8
10
  import YAML from "yaml";
@@ -126,7 +128,7 @@ function parseScaffoldArguments(args: string[]): ScaffoldingOptions {
126
128
  } else if (arg === "--force") {
127
129
  options.force = true;
128
130
  } else {
129
- console.error(`[hsj] Unrecognized scaffolding argument: '${arg}'`);
131
+ console.error(`[hsjs] Unrecognized scaffolding argument: '${arg}'`);
130
132
  process.exit(1);
131
133
  }
132
134
 
@@ -146,7 +148,7 @@ async function confirmYesNo(message: string): Promise<void> {
146
148
  const response = await rl.question(`${message} [y/N] `);
147
149
 
148
150
  if (response.trim().toLowerCase() !== "y") {
149
- console.error("[hsj] Operation cancelled.");
151
+ console.error("[hsjs] Operation cancelled.");
150
152
  process.exit(0);
151
153
  }
152
154
  } finally {
@@ -157,7 +159,7 @@ async function confirmYesNo(message: string): Promise<void> {
157
159
  export async function scaffold(options: ScaffoldingOptions) {
158
160
  if (options.force) {
159
161
  await confirmYesNo(
160
- "[hsj] The `--force` flag is set and will overwrite existing files and settings that may have been modified. Continue?",
162
+ "[hsjs] The `--force` flag is set and will overwrite existing files and settings that may have been modified. Continue?",
161
163
  );
162
164
  }
163
165
 
@@ -166,9 +168,9 @@ export async function scaffold(options: ScaffoldingOptions) {
166
168
  const projectYamlPath = path.resolve(cwd, COMMON_PATHS.projectYaml);
167
169
  const mainTspPath = path.resolve(cwd, COMMON_PATHS.mainTsp);
168
170
 
169
- console.info("[hsj] Scaffolding TypeScript project...");
171
+ console.info("[hsjs] Scaffolding TypeScript project...");
170
172
  console.info(
171
- `[hsj] Using project file '${path.relative(cwd, projectYamlPath)}' and main file '${path.relative(cwd, mainTspPath)}'`,
173
+ `[hsjs] Using project file '${path.relative(cwd, projectYamlPath)}' and main file '${path.relative(cwd, mainTspPath)}'`,
172
174
  );
173
175
 
174
176
  let config: any;
@@ -179,7 +181,7 @@ export async function scaffold(options: ScaffoldingOptions) {
179
181
  config = YAML.parse(configText.toString("utf-8"));
180
182
  } catch {
181
183
  console.error(
182
- "[hsj] Failed to read project configuration file. Is the project initialized using `tsp init`?",
184
+ "[hsjs] Failed to read project configuration file. Is the project initialized using `tsp init`?",
183
185
  );
184
186
  process.exit(1);
185
187
  }
@@ -204,16 +206,16 @@ export async function scaffold(options: ScaffoldingOptions) {
204
206
  };
205
207
 
206
208
  console.info(
207
- `[hsj] Emitter options have 'express: ${expressOptions.isExpress}'. Generating server model: '${expressOptions.isExpress ? "Express" : "Node"}'.`,
209
+ `[hsjs] Emitter options have 'express: ${expressOptions.isExpress}'. Generating server model: '${expressOptions.isExpress ? "Express" : "Node"}'.`,
208
210
  );
209
211
 
210
212
  if (options["no-standalone"]) {
211
- console.info("[hsj] Standalone mode disabled, generating project in current directory.");
213
+ console.info("[hsjs] Standalone mode disabled, generating project in current directory.");
212
214
  } else {
213
- console.info("[hsj] Generating standalone project in output directory.");
215
+ console.info("[hsjs] Generating standalone project in output directory.");
214
216
  }
215
217
 
216
- console.info("[hsj] Compiling TypeSpec project...");
218
+ console.info("[hsjs] Compiling TypeSpec project...");
217
219
 
218
220
  const program = await compile(NodeHost, mainTspPath, {
219
221
  noEmit: true,
@@ -228,7 +230,7 @@ export async function scaffold(options: ScaffoldingOptions) {
228
230
  });
229
231
 
230
232
  if (!jsCtx) {
231
- console.error("[hsj] No services were found in the program. Exiting.");
233
+ console.error("[hsjs] No services were found in the program. Exiting.");
232
234
  process.exit(1);
233
235
  }
234
236
 
@@ -244,17 +246,17 @@ export async function scaffold(options: ScaffoldingOptions) {
244
246
  }
245
247
 
246
248
  if (program.hasError() || hadError) {
247
- console.error("[hsj] TypeScript compilation failed. See above error output.");
249
+ console.error("[hsjs] TypeScript compilation failed. See above error output.");
248
250
  process.exit(1);
249
251
  }
250
252
 
251
- console.info("[hsj] TypeSpec compiled successfully. Scaffolding implementation...");
253
+ console.info("[hsjs] TypeSpec compiled successfully. Scaffolding implementation...");
252
254
 
253
255
  const indexModule = jsCtx.srcModule;
254
256
 
255
257
  const routeControllers = await createRouteControllers(jsCtx, httpService, indexModule);
256
258
 
257
- console.info("[hsj] Generating server entry point...");
259
+ console.info("[hsjs] Generating server entry point...");
258
260
 
259
261
  const controllerModules = new Set<Module>();
260
262
 
@@ -370,7 +372,7 @@ export async function scaffold(options: ScaffoldingOptions) {
370
372
  ]);
371
373
  }
372
374
 
373
- console.info("[hsj] Writing files...");
375
+ console.info("[hsjs] Writing files...");
374
376
 
375
377
  const queue = createOnceQueue<Module>();
376
378
 
@@ -430,36 +432,39 @@ export async function scaffold(options: ScaffoldingOptions) {
430
432
  try {
431
433
  ownPackageJson = JSON.parse((await fs.readFile(ownPackageJsonPath)).toString("utf-8"));
432
434
  } catch {
433
- console.error("[hsj] Failed to read package.json of TypeSpec project. Exiting.");
435
+ console.error("[hsjs] Failed to read package.json of TypeSpec project. Exiting.");
434
436
  process.exit(1);
435
437
  }
436
438
 
439
+ // Accumulate all dependencies
440
+ const externalDependencies = getAllExternalDependencies(jsCtx);
441
+
437
442
  let packageJsonChanged = true;
438
443
 
439
444
  if (options["no-standalone"]) {
440
- console.info("[hsj] Checking package.json for changes...");
445
+ console.info("[hsjs] Checking package.json for changes...");
441
446
 
442
- packageJsonChanged = updatePackageJson(ownPackageJson, expressOptions, options.force);
447
+ packageJsonChanged = updatePackageJson(ownPackageJson, options.force, externalDependencies);
443
448
 
444
449
  if (packageJsonChanged) {
445
- console.info("[hsj] Writing updated package.json...");
450
+ console.info("[hsjs] Writing updated package.json...");
446
451
 
447
452
  try {
448
453
  await fs.writeFile(ownPackageJsonPath, JSON.stringify(ownPackageJson, null, 2) + "\n");
449
454
  } catch {
450
- console.error("[hsj] Failed to write package.json.");
455
+ console.error("[hsjs] Failed to write package.json.");
451
456
  process.exit(1);
452
457
  }
453
458
  } else {
454
- console.info("[hsj] No changes to package.json suggested.");
459
+ console.info("[hsjs] No changes to package.json suggested.");
455
460
  }
456
461
  } else {
457
462
  // Standalone mode, need to generate package.json from scratch
458
463
  const relativePathToSpec = path.relative(baseOutputDir, cwd);
459
464
  const packageJson = getPackageJsonForStandaloneProject(
460
465
  ownPackageJson,
461
- expressOptions,
462
466
  relativePathToSpec,
467
+ externalDependencies,
463
468
  );
464
469
 
465
470
  const packageJsonPath = path.resolve(baseOutputDir, COMMON_PATHS.packageJson);
@@ -469,7 +474,7 @@ export async function scaffold(options: ScaffoldingOptions) {
469
474
 
470
475
  if (packageJsonChanged) {
471
476
  // Run npm install to ensure dependencies are installed.
472
- console.info("[hsj] Running npm install...");
477
+ console.info("[hsjs] Running npm install...");
473
478
 
474
479
  try {
475
480
  await spawn("npm", ["install"], {
@@ -479,12 +484,12 @@ export async function scaffold(options: ScaffoldingOptions) {
479
484
  });
480
485
  } catch {
481
486
  console.warn(
482
- "[hsj] Failed to run npm install. Check the output above for errors and install dependencies manually.",
487
+ "[hsjs] Failed to run npm install. Check the output above for errors and install dependencies manually.",
483
488
  );
484
489
  }
485
490
  }
486
491
 
487
- console.info("[hsj] Project scaffolding complete. Building project...");
492
+ console.info("[hsjs] Project scaffolding complete. Building project...");
488
493
 
489
494
  try {
490
495
  await spawn("npm", ["run", "build"], {
@@ -493,21 +498,21 @@ export async function scaffold(options: ScaffoldingOptions) {
493
498
  shell: process.platform === "win32",
494
499
  });
495
500
  } catch {
496
- console.error("[hsj] Failed to build project. Check the output above for errors.");
501
+ console.error("[hsjs] Failed to build project. Check the output above for errors.");
497
502
  process.exit(1);
498
503
  }
499
504
 
500
505
  const codeDirectory = path.relative(cwd, options["no-standalone"] ? cwd : baseOutputDir);
501
506
 
502
- console.info("[hsj] Project is ready to run. Use `npm start` to launch the server.");
503
- console.info("[hsj] A debug configuration has been created for Visual Studio Code.");
507
+ console.info("[hsjs] Project is ready to run. Use `npm start` to launch the server.");
508
+ console.info("[hsjs] A debug configuration has been created for Visual Studio Code.");
504
509
  console.info(
505
- `[hsj] Try \`code ${codeDirectory}\` to open the project and press F5 to start debugging.`,
510
+ `[hsjs] Try \`code ${codeDirectory}\` to open the project and press F5 to start debugging.`,
506
511
  );
507
512
  console.info(
508
- `[hsj] The newly-generated route controllers in '${path.join(codeDirectory, "src", "controllers")}' are ready to be implemented.`,
513
+ `[hsjs] The newly-generated route controllers in '${path.join(codeDirectory, "src", "controllers")}' are ready to be implemented.`,
509
514
  );
510
- console.info("[hsj] Done.");
515
+ console.info("[hsjs] Done.");
511
516
 
512
517
  async function tryWrite(file: string, contents: string): Promise<void> {
513
518
  try {
@@ -519,20 +524,54 @@ export async function scaffold(options: ScaffoldingOptions) {
519
524
  .catch(() => false);
520
525
 
521
526
  if (exists && !options.force) {
522
- console.warn(`[hsj] File '${relative}' already exists and will not be overwritten.`);
523
- console.warn(`[hsj] Manually update the file or delete it and run scaffolding again.`);
527
+ console.warn(`[hsjs] File '${relative}' already exists and will not be overwritten.`);
528
+ console.warn(`[hsjs] Manually update the file or delete it and run scaffolding again.`);
524
529
 
525
530
  return;
526
531
  } else if (exists) {
527
- console.warn(`[hsj] Overwriting file '${relative}'...`);
532
+ console.warn(`[hsjs] Overwriting file '${relative}'...`);
528
533
  } else {
529
- console.info(`[hsj] Writing file '${relative}'...`);
534
+ console.info(`[hsjs] Writing file '${relative}'...`);
530
535
  }
531
536
 
532
537
  await fs.mkdir(path.dirname(file), { recursive: true });
533
538
  await fs.writeFile(file, contents);
534
539
  } catch (e: unknown) {
535
- console.error(`[hsj] Failed to write file: '${(e as Error).message}'`);
540
+ console.error(`[hsjs] Failed to write file: '${(e as Error).message}'`);
541
+ }
542
+ }
543
+ }
544
+
545
+ function getAllExternalDependencies(ctx: JsContext): Set<string> {
546
+ const externalDependencies = new Set<string>();
547
+
548
+ const visited = new Set<Module>();
549
+
550
+ addModule(ctx.rootModule);
551
+
552
+ return externalDependencies;
553
+
554
+ function addModule(module: Module) {
555
+ visited.add(module);
556
+
557
+ for (const declaration of module.declarations) {
558
+ if (isModule(declaration) && !visited.has(declaration)) {
559
+ addModule(declaration);
560
+ }
561
+ }
562
+
563
+ for (const _import of module.imports) {
564
+ if (
565
+ typeof _import.from === "string" &&
566
+ !_import.from.startsWith(".") &&
567
+ !_import.from.startsWith("/")
568
+ ) {
569
+ externalDependencies.add(_import.from);
570
+ } else if (typeof _import.from !== "string") {
571
+ if (!visited.has(_import.from)) {
572
+ addModule(_import.from);
573
+ }
574
+ }
536
575
  }
537
576
  }
538
577
  }
@@ -595,7 +634,7 @@ async function createRouteController(
595
634
 
596
635
  const controllerName = containerNameCase.pascalCase + "Impl";
597
636
 
598
- console.info(`[hsj] Generating controller '${controllerName}'...`);
637
+ console.info(`[hsjs] Generating controller '${controllerName}'...`);
599
638
 
600
639
  module.declarations.push([
601
640
  `export class ${controllerName} implements ${containerNameCase.pascalCase}<HttpContext> {`,
@@ -688,8 +727,8 @@ function* emitControllerOperationHandlers(
688
727
 
689
728
  function getPackageJsonForStandaloneProject(
690
729
  ownPackageJson: any,
691
- express: PackageJsonExpressOptions,
692
730
  relativePathToSpec: string,
731
+ externalDependencies: Set<string>,
693
732
  ): any {
694
733
  const packageJson = {
695
734
  name: (ownPackageJson.name ?? path.basename(process.cwd())) + "-server",
@@ -702,7 +741,7 @@ function getPackageJsonForStandaloneProject(
702
741
  packageJson.private = true;
703
742
  }
704
743
 
705
- updatePackageJson(packageJson, express, true, () => {});
744
+ updatePackageJson(packageJson, true, externalDependencies, () => {});
706
745
 
707
746
  delete packageJson.scripts["build:scaffold"];
708
747
  packageJson.scripts["build:typespec"] = 'tsp compile --output-dir=".." ' + relativePathToSpec;
@@ -719,8 +758,8 @@ interface PackageJsonExpressOptions {
719
758
 
720
759
  function updatePackageJson(
721
760
  packageJson: any,
722
- express: PackageJsonExpressOptions,
723
761
  force: boolean,
762
+ externalDependencies: Set<string>,
724
763
  info: (...args: any[]) => void = console.info,
725
764
  ): boolean {
726
765
  let changed = false;
@@ -733,17 +772,31 @@ function updatePackageJson(
733
772
  updateObjectPath(["devDependencies", "typescript"], "^5.7.3");
734
773
  updateObjectPath(["devDependencies", "@types/node"], "^22.13.1");
735
774
 
736
- if (express.isExpress) {
737
- updateObjectPath(["dependencies", "express"], "^5.0.1");
738
- updateObjectPath(["devDependencies", "@types/express"], "^5.0.0");
775
+ let hadError = false;
776
+
777
+ for (const dependency of externalDependencies) {
778
+ const dependencyVersion = hsjsDependencies[dependency];
739
779
 
740
- if (express.openApi3) {
741
- updateObjectPath(["dependencies", "swagger-ui-express"], "^5.0.1");
742
- updateObjectPath(["devDependencies", "@types/swagger-ui-express"], "^4.1.7");
780
+ if (!dependencyVersion) {
781
+ hadError = true;
782
+ console.error("[hsjs] Failed to find version for dependency:", dependency);
783
+ continue;
743
784
  }
744
785
 
745
- updateObjectPath(["dependencies", "morgan"], "^1.10.0");
746
- updateObjectPath(["devDependencies", "@types/morgan"], "^1.9.9");
786
+ updateObjectPath(["dependencies", dependency], dependencyVersion);
787
+
788
+ const typesDependency = `@types/${dependency}`;
789
+ const typesDependencyVersion = hsjsDependencies[typesDependency];
790
+ if (typesDependencyVersion) {
791
+ updateObjectPath(["devDependencies", typesDependency], typesDependencyVersion);
792
+ }
793
+ }
794
+
795
+ if (hadError) {
796
+ console.error(
797
+ "[hsjs] FATAL: Failed to find dependency versions. This is a bug. Please report this error to https://github.com/microsoft/typespec",
798
+ );
799
+ process.exit(1);
747
800
  }
748
801
 
749
802
  return changed;
@@ -769,9 +822,9 @@ function updatePackageJson(
769
822
 
770
823
  if (!existingValue || force) {
771
824
  if (!existingValue) {
772
- info(`[hsj] - Setting package.json property '${property}' to "${value}".`);
825
+ info(`[hsjs] - Setting package.json property '${property}' to "${value}".`);
773
826
  } else if (force) {
774
- info(`[hsj] - Overwriting package.json property '${property}' to "${value}".`);
827
+ info(`[hsjs] - Overwriting package.json property '${property}' to "${value}".`);
775
828
  }
776
829
 
777
830
  current[path[path.length - 1]] = value;
@@ -782,10 +835,10 @@ function updatePackageJson(
782
835
  }
783
836
 
784
837
  if (current[path[path.length - 1]] !== value) {
785
- info(`[hsj] - Skipping package.json property '${property}'.`);
786
- info(`[hsj] Scaffolding prefers "${value}", but it is already set to "${existingValue}".`);
838
+ info(`[hsjs] - Skipping package.json property '${property}'.`);
839
+ info(`[hsjs] Scaffolding prefers "${value}", but it is already set to "${existingValue}".`);
787
840
  info(
788
- "[hsj] Manually update the property or remove it and run scaffolding again if needed.",
841
+ "[hsjs] Manually update the property or remove it and run scaffolding again if needed.",
789
842
  );
790
843
  }
791
844
  }
@@ -190,13 +190,18 @@ function mockScalar(ctx: JsContext, module: Module, scalar: Scalar): string | un
190
190
  return JSON.stringify(true);
191
191
  }
192
192
  if ($.scalar.isNumeric(scalar) || $.scalar.extendsNumeric(scalar)) {
193
- switch ((scalar as Scalar).name) {
194
- case "integer":
195
- case "int64":
196
- case "uint64":
197
- return "42n";
198
- default:
199
- return "42";
193
+ if (
194
+ $.scalar.extendsSafeint(scalar) ||
195
+ $.scalar.extendsInt32(scalar) ||
196
+ $.scalar.extendsUint32(scalar) ||
197
+ $.scalar.extendsFloat64(scalar)
198
+ ) {
199
+ return "42";
200
+ } else if ($.scalar.extendsInteger(scalar)) {
201
+ return "42n";
202
+ } else {
203
+ module.imports.push({ from: "decimal.js", binder: ["Decimal"] });
204
+ return "new Decimal(42)";
200
205
  }
201
206
  }
202
207