@typespec/http-server-js 0.58.0-alpha.13-dev.7 → 0.58.0-alpha.13-dev.9

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.
Files changed (75) hide show
  1. package/.testignore +0 -2
  2. package/README.md +6 -0
  3. package/build-helpers.ts +57 -12
  4. package/dist/generated-defs/helpers/index.d.ts.map +1 -1
  5. package/dist/generated-defs/helpers/index.js +1 -0
  6. package/dist/generated-defs/helpers/index.js.map +1 -1
  7. package/dist/generated-defs/helpers/temporal/index.d.ts +4 -0
  8. package/dist/generated-defs/helpers/temporal/index.d.ts.map +1 -0
  9. package/dist/generated-defs/helpers/temporal/index.js +19 -0
  10. package/dist/generated-defs/helpers/temporal/index.js.map +1 -0
  11. package/dist/generated-defs/helpers/temporal/native.d.ts +4 -0
  12. package/dist/generated-defs/helpers/temporal/native.d.ts.map +1 -0
  13. package/dist/generated-defs/helpers/temporal/native.js +125 -0
  14. package/dist/generated-defs/helpers/temporal/native.js.map +1 -0
  15. package/dist/generated-defs/helpers/temporal/polyfill.d.ts +4 -0
  16. package/dist/generated-defs/helpers/temporal/polyfill.d.ts.map +1 -0
  17. package/dist/generated-defs/helpers/temporal/polyfill.js +124 -0
  18. package/dist/generated-defs/helpers/temporal/polyfill.js.map +1 -0
  19. package/dist/generated-defs/package.json.d.ts +2 -0
  20. package/dist/generated-defs/package.json.d.ts.map +1 -0
  21. package/dist/generated-defs/package.json.js +37 -0
  22. package/dist/generated-defs/package.json.js.map +1 -0
  23. package/dist/src/common/declaration.js +1 -1
  24. package/dist/src/common/declaration.js.map +1 -1
  25. package/dist/src/common/scalar.d.ts +1 -1
  26. package/dist/src/common/scalar.d.ts.map +1 -1
  27. package/dist/src/common/scalar.js +373 -48
  28. package/dist/src/common/scalar.js.map +1 -1
  29. package/dist/src/common/serialization/index.d.ts +2 -2
  30. package/dist/src/common/serialization/index.d.ts.map +1 -1
  31. package/dist/src/common/serialization/index.js +3 -9
  32. package/dist/src/common/serialization/index.js.map +1 -1
  33. package/dist/src/common/serialization/json.d.ts +2 -0
  34. package/dist/src/common/serialization/json.d.ts.map +1 -1
  35. package/dist/src/common/serialization/json.js +10 -19
  36. package/dist/src/common/serialization/json.js.map +1 -1
  37. package/dist/src/helpers/temporal/native.d.ts +40 -0
  38. package/dist/src/helpers/temporal/native.d.ts.map +1 -0
  39. package/dist/src/helpers/temporal/native.js +81 -0
  40. package/dist/src/helpers/temporal/native.js.map +1 -0
  41. package/dist/src/helpers/temporal/polyfill.d.ts +41 -0
  42. package/dist/src/helpers/temporal/polyfill.d.ts.map +1 -0
  43. package/dist/src/helpers/temporal/polyfill.js +80 -0
  44. package/dist/src/helpers/temporal/polyfill.js.map +1 -0
  45. package/dist/src/http/server/index.d.ts.map +1 -1
  46. package/dist/src/http/server/index.js +4 -1
  47. package/dist/src/http/server/index.js.map +1 -1
  48. package/dist/src/lib.d.ts +9 -0
  49. package/dist/src/lib.d.ts.map +1 -1
  50. package/dist/src/lib.js +7 -0
  51. package/dist/src/lib.js.map +1 -1
  52. package/dist/src/scripts/scaffold/bin.d.mts.map +1 -1
  53. package/dist/src/scripts/scaffold/bin.mjs +91 -53
  54. package/dist/src/scripts/scaffold/bin.mjs.map +1 -1
  55. package/dist/src/scripts/scaffold/data-mocks.d.ts.map +1 -1
  56. package/dist/src/scripts/scaffold/data-mocks.js +74 -15
  57. package/dist/src/scripts/scaffold/data-mocks.js.map +1 -1
  58. package/generated-defs/helpers/index.ts +1 -0
  59. package/generated-defs/helpers/temporal/index.ts +25 -0
  60. package/generated-defs/helpers/temporal/native.ts +132 -0
  61. package/generated-defs/helpers/temporal/polyfill.ts +131 -0
  62. package/generated-defs/package.json.ts +38 -0
  63. package/package.json +11 -3
  64. package/src/common/declaration.ts +1 -1
  65. package/src/common/scalar.ts +430 -61
  66. package/src/common/serialization/index.ts +4 -13
  67. package/src/common/serialization/json.ts +22 -25
  68. package/src/helpers/temporal/native.ts +104 -0
  69. package/src/helpers/temporal/polyfill.ts +103 -0
  70. package/src/http/server/index.ts +6 -1
  71. package/src/lib.ts +17 -0
  72. package/src/scripts/scaffold/bin.mts +106 -53
  73. package/src/scripts/scaffold/data-mocks.ts +81 -16
  74. package/temp/tsconfig.tsbuildinfo +1 -1
  75. package/test/scalar.test.ts +547 -97
@@ -38,7 +38,10 @@ import {
38
38
  /**
39
39
  * Memoization cache for requiresJsonSerialization.
40
40
  */
41
- const _REQUIRES_JSON_SERIALIZATION = new WeakMap<SerializableType | ModelProperty, boolean>();
41
+ const _REQUIRES_JSON_SERIALIZATION = new WeakMap<
42
+ SerializableType | Scalar | ModelProperty,
43
+ boolean
44
+ >();
42
45
 
43
46
  export function requiresJsonSerialization(
44
47
  ctx: JsContext,
@@ -46,7 +49,7 @@ export function requiresJsonSerialization(
46
49
  type: Type,
47
50
  diagnosticTarget: DiagnosticTarget | typeof NoTarget = NoTarget,
48
51
  ): boolean {
49
- if (!isSerializable(type)) return false;
52
+ if (!isJsonSerializable(type)) return false;
50
53
 
51
54
  if (_REQUIRES_JSON_SERIALIZATION.has(type)) {
52
55
  return _REQUIRES_JSON_SERIALIZATION.get(type)!;
@@ -107,7 +110,7 @@ function propertyRequiresJsonSerialization(
107
110
  isHttpMetadata(ctx, property) ||
108
111
  getEncode(ctx.program, property) ||
109
112
  resolveEncodedName(ctx.program, property, "application/json") !== property.name ||
110
- (isSerializable(property.type) &&
113
+ (isJsonSerializable(property.type) &&
111
114
  requiresJsonSerialization(ctx, module, property.type, property))
112
115
  );
113
116
  }
@@ -120,8 +123,8 @@ function isHttpMetadata(ctx: JsContext, property: ModelProperty): boolean {
120
123
  );
121
124
  }
122
125
 
123
- function isSerializable(type: Type): type is SerializableType | ModelProperty {
124
- return type.kind === "ModelProperty" || isSerializableType(type);
126
+ function isJsonSerializable(type: Type): type is SerializableType | Scalar | ModelProperty {
127
+ return type.kind === "ModelProperty" || type.kind === "Scalar" || isSerializableType(type);
125
128
  }
126
129
 
127
130
  export function* emitJsonSerialization(
@@ -194,10 +197,6 @@ function* emitToJson(
194
197
 
195
198
  return;
196
199
  }
197
- case "Scalar": {
198
- yield `throw new Error("Unimplemented: scalar JSON serialization");`;
199
- return;
200
- }
201
200
  case "Union": {
202
201
  const codeTree = differentiateUnion(ctx, module, type);
203
202
 
@@ -224,7 +223,7 @@ function* emitToJson(
224
223
  }
225
224
  }
226
225
 
227
- function transposeExpressionToJson(
226
+ export function transposeExpressionToJson(
228
227
  ctx: SerializationContext,
229
228
  type: Type,
230
229
  expr: string,
@@ -400,12 +399,14 @@ function* emitFromJson(
400
399
  const scalarEncoder = scalar.getEncoding(encoding.encoding ?? "default", encoding.type);
401
400
 
402
401
  if (scalarEncoder) {
403
- expr = transposeExpressionFromJson(
404
- ctx,
405
- // Assertion: scalarEncoder.target.scalar is defined because we resolved an encoder.
406
- scalarEncoder.target.scalar as Scalar,
407
- scalarEncoder.decode(expr),
408
- module,
402
+ expr = scalarEncoder.decode(
403
+ transposeExpressionFromJson(
404
+ ctx,
405
+ // Assertion: scalarEncoder.target.scalar is defined because we resolved an encoder.
406
+ scalarEncoder.target.scalar as Scalar,
407
+ expr,
408
+ module,
409
+ ),
409
410
  );
410
411
  } else {
411
412
  reportDiagnostic(ctx.program, {
@@ -433,10 +434,6 @@ function* emitFromJson(
433
434
 
434
435
  return;
435
436
  }
436
- case "Scalar": {
437
- yield `throw new Error("Unimplemented: scalar JSON serialization");`;
438
- return;
439
- }
440
437
  case "Union": {
441
438
  const codeTree = differentiateUnion(ctx, module, type);
442
439
 
@@ -464,7 +461,7 @@ function* emitFromJson(
464
461
  }
465
462
  }
466
463
 
467
- function transposeExpressionFromJson(
464
+ export function transposeExpressionFromJson(
468
465
  ctx: SerializationContext,
469
466
  type: Type,
470
467
  expr: string,
@@ -507,13 +504,13 @@ function transposeExpressionFromJson(
507
504
 
508
505
  const encoder = getScalarEncoder(ctx, type, scalar);
509
506
 
510
- const decoded = encoder.decode(expr);
511
-
512
507
  if (encoder.target.isJsonCompatible || !encoder.target.scalar) {
513
- return decoded;
508
+ return encoder.decode(expr);
514
509
  } else {
515
510
  // Assertion: encoder.target.scalar is a scalar because "unknown" is JSON compatible.
516
- return transposeExpressionFromJson(ctx, encoder.target.scalar as Scalar, decoded, module);
511
+ return encoder.decode(
512
+ transposeExpressionFromJson(ctx, encoder.target.scalar as Scalar, expr, module),
513
+ );
517
514
  }
518
515
  case "Union":
519
516
  if (!requiresJsonSerialization(ctx, module, type)) {
@@ -0,0 +1,104 @@
1
+ // Copyright (c) Microsoft Corporation
2
+ // Licensed under the MIT license.
3
+
4
+ // Due to the lack of generally-available global type definitions for Temporal, we use `any` throughout this module.
5
+ // When global.d.ts eventually has definitions for Temporal, we can unify this module with `polyfill.ts`.
6
+
7
+ /**
8
+ * Parses an HTTP date string (e.g. `Wed, 21 Oct 2015 07:28:00 GMT`) into a `Temporal.Instant`.
9
+ * The date string must be in the format specified by RFC 7231.
10
+ *
11
+ * @param httpDate - The HTTP date string to parse.
12
+ * @throws {RangeError} If the date string is invalid or cannot be parsed.
13
+ * @returns The parsed `Temporal.Instant`.
14
+ */
15
+ export function parseHttpDate(httpDate: string): any {
16
+ const timestamp = globalThis.Date.parse(httpDate);
17
+
18
+ if (isNaN(timestamp)) {
19
+ throw new RangeError(`Invalid HTTP date: ${httpDate}`);
20
+ }
21
+
22
+ return (globalThis as any).Temporal.Instant.fromEpochMilliseconds(timestamp);
23
+ }
24
+
25
+ /**
26
+ * Formats a `Temporal.Instant` into an HTTP date string (e.g. `Wed, 21 Oct 2015 07:28:00 GMT`).
27
+ * The date string is formatted according to RFC 7231.
28
+ *
29
+ * @param instant - The `Temporal.Instant` to format.
30
+ * @returns The formatted HTTP date string.
31
+ */
32
+ export function formatHttpDate(instant: any) {
33
+ const date = new Date(instant.epochMilliseconds);
34
+ return date.toUTCString();
35
+ }
36
+
37
+ /**
38
+ * Converts a `Temporal.Duration` to a number of seconds.
39
+ * This method will throw an Error if the duration contains any years, months, weeks, or days, as those require a reference
40
+ * point to calculate the total number of seconds.
41
+ *
42
+ * WARNING: If the total number of seconds is larger than the maximum safe integer in JavaScript, this method will
43
+ * lose precision. @see durationTotalSecondsBigInt for a BigInt alternative.
44
+ *
45
+ * @param duration - the duration to calculate the total number of seconds for
46
+ * @returns the total number of seconds in the duration
47
+ */
48
+ export function durationTotalSeconds(duration: any): number {
49
+ if (
50
+ duration.years !== 0 ||
51
+ duration.months !== 0 ||
52
+ duration.weeks !== 0 ||
53
+ duration.days !== 0
54
+ ) {
55
+ throw new Error(
56
+ "Cannot calculate total seconds for a duration with years, months, weeks, or days.",
57
+ );
58
+ }
59
+
60
+ return (
61
+ duration.seconds +
62
+ duration.minutes * 60 +
63
+ duration.hours * 60 * 60 +
64
+ duration.days * 24 * 60 * 60
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Gets the total number of seconds in a duration.
70
+ *
71
+ * This method will throw an Error if the duration contains any years, months, weeks, or days, as those require a reference
72
+ * point to calculate the total number of seconds. It will also throw an error if any of the components are not integers.
73
+ *
74
+ * @param duration - the duration to calculate the total number of seconds for
75
+ * @returns the total number of seconds in the duration
76
+ */
77
+ export function durationTotalSecondsBigInt(duration: any): bigint {
78
+ if (
79
+ duration.years !== 0 ||
80
+ duration.months !== 0 ||
81
+ duration.weeks !== 0 ||
82
+ duration.days !== 0
83
+ ) {
84
+ throw new Error(
85
+ "Cannot calculate total seconds for a duration with years, months, weeks, or days.",
86
+ );
87
+ }
88
+
89
+ if (
90
+ !Number.isInteger(duration.seconds) ||
91
+ !Number.isInteger(duration.minutes) ||
92
+ !Number.isInteger(duration.hours) ||
93
+ !Number.isInteger(duration.days)
94
+ ) {
95
+ throw new Error("Duration components must be integers.");
96
+ }
97
+
98
+ return (
99
+ BigInt(duration.seconds) +
100
+ BigInt(duration.minutes) * 60n +
101
+ BigInt(duration.hours) * 60n * 60n +
102
+ BigInt(duration.days) * 24n * 60n * 60n
103
+ );
104
+ }
@@ -0,0 +1,103 @@
1
+ // Copyright (c) Microsoft Corporation
2
+ // Licensed under the MIT license.
3
+
4
+ import { Temporal } from "temporal-polyfill";
5
+
6
+ /**
7
+ * Parses an HTTP date string (e.g. `Wed, 21 Oct 2015 07:28:00 GMT`) into a `Temporal.Instant`.
8
+ * The date string must be in the format specified by RFC 7231.
9
+ *
10
+ * @param httpDate - The HTTP date string to parse.
11
+ * @throws {RangeError} If the date string is invalid or cannot be parsed.
12
+ * @returns The parsed `Temporal.Instant`.
13
+ */
14
+ export function parseHttpDate(httpDate: string): Temporal.Instant {
15
+ const timestamp = globalThis.Date.parse(httpDate);
16
+
17
+ if (isNaN(timestamp)) {
18
+ throw new RangeError(`Invalid HTTP date: ${httpDate}`);
19
+ }
20
+
21
+ return Temporal.Instant.fromEpochMilliseconds(timestamp);
22
+ }
23
+
24
+ /**
25
+ * Formats a `Temporal.Instant` into an HTTP date string (e.g. `Wed, 21 Oct 2015 07:28:00 GMT`).
26
+ * The date string is formatted according to RFC 7231.
27
+ *
28
+ * @param instant - The `Temporal.Instant` to format.
29
+ * @returns The formatted HTTP date string.
30
+ */
31
+ export function formatHttpDate(instant: Temporal.Instant) {
32
+ const date = new Date(instant.epochMilliseconds);
33
+ return date.toUTCString();
34
+ }
35
+
36
+ /**
37
+ * Converts a `Temporal.Duration` to a number of seconds.
38
+ * This method will throw an Error if the duration contains any years, months, weeks, or days, as those require a reference
39
+ * point to calculate the total number of seconds.
40
+ *
41
+ * WARNING: If the total number of seconds is larger than the maximum safe integer in JavaScript, this method will
42
+ * lose precision. @see durationTotalSecondsBigInt for a BigInt alternative.
43
+ *
44
+ * @param duration - the duration to calculate the total number of seconds for
45
+ * @returns the total number of seconds in the duration
46
+ */
47
+ export function durationTotalSeconds(duration: Temporal.Duration): number {
48
+ if (
49
+ duration.years !== 0 ||
50
+ duration.months !== 0 ||
51
+ duration.weeks !== 0 ||
52
+ duration.days !== 0
53
+ ) {
54
+ throw new Error(
55
+ "Cannot calculate total seconds for a duration with years, months, weeks, or days.",
56
+ );
57
+ }
58
+
59
+ return (
60
+ duration.seconds +
61
+ duration.minutes * 60 +
62
+ duration.hours * 60 * 60 +
63
+ duration.days * 24 * 60 * 60
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Gets the total number of seconds in a duration.
69
+ *
70
+ * This method will throw an Error if the duration contains any years, months, weeks, or days, as those require a reference
71
+ * point to calculate the total number of seconds. It will also throw an error if any of the components are not integers.
72
+ *
73
+ * @param duration - the duration to calculate the total number of seconds for
74
+ * @returns the total number of seconds in the duration
75
+ */
76
+ export function durationTotalSecondsBigInt(duration: Temporal.Duration): bigint {
77
+ if (
78
+ duration.years !== 0 ||
79
+ duration.months !== 0 ||
80
+ duration.weeks !== 0 ||
81
+ duration.days !== 0
82
+ ) {
83
+ throw new Error(
84
+ "Cannot calculate total seconds for a duration with years, months, weeks, or days.",
85
+ );
86
+ }
87
+
88
+ if (
89
+ !Number.isInteger(duration.seconds) ||
90
+ !Number.isInteger(duration.minutes) ||
91
+ !Number.isInteger(duration.hours) ||
92
+ !Number.isInteger(duration.days)
93
+ ) {
94
+ throw new Error("Duration components must be integers.");
95
+ }
96
+
97
+ return (
98
+ BigInt(duration.seconds) +
99
+ BigInt(duration.minutes) * 60n +
100
+ BigInt(duration.hours) * 60n * 60n +
101
+ BigInt(duration.days) * 24n * 60n * 60n
102
+ );
103
+ }
@@ -40,7 +40,10 @@ import { emitMultipart, emitMultipartLegacy } from "./multipart.js";
40
40
  import { module as headerHelpers } from "../../../generated-defs/helpers/header.js";
41
41
  import { module as httpHelpers } from "../../../generated-defs/helpers/http.js";
42
42
  import { getJsScalar } from "../../common/scalar.js";
43
- import { requiresJsonSerialization } from "../../common/serialization/json.js";
43
+ import {
44
+ requiresJsonSerialization,
45
+ transposeExpressionFromJson,
46
+ } from "../../common/serialization/json.js";
44
47
  import { getFullyQualifiedTypeName } from "../../util/name.js";
45
48
 
46
49
  const DEFAULT_CONTENT_TYPE = "application/json";
@@ -274,6 +277,8 @@ function* emitRawServerOperation(
274
277
  yield ` return reject();`;
275
278
  yield ` }`;
276
279
  value = `Object.fromEntries(Object.entries(__recordBody).map(([key, value]) => [key, ${innerTypeName}.fromJsonObject(value)]))`;
280
+ } else if (body.type.kind === "Scalar") {
281
+ value = transposeExpressionFromJson(ctx, body.type, `JSON.parse(body)`, module);
277
282
  } else {
278
283
  value = `${bodyTypeName}.fromJsonObject(JSON.parse(body))`;
279
284
  }
package/src/lib.ts CHANGED
@@ -14,6 +14,16 @@ to `true`, the emitter will only emit those types that are reachable from an HTT
14
14
  "omit-unreachable-types": boolean;
15
15
  /** If set to `true`, the emitter will not format the generated code using Prettier. */
16
16
  "no-format": boolean;
17
+
18
+ /**
19
+ * The type of datetime models to use for TypeSpecs DateTime and Duration types.
20
+ *
21
+ * Options:
22
+ * - `temporal-polyfill`: (Default) Uses the Temporal API from the `temporal-polyfill` package.
23
+ * - `temporal`: Uses the native Temporal API, requires that your target environment supports it. This will become the default setting in the future.
24
+ * - `date-duration`: Uses the built-in `Date` and a custom `Duration` type. Not recommended.
25
+ */
26
+ datetime?: "temporal-polyfill" | "temporal" | "date-duration";
17
27
  }
18
28
 
19
29
  const EmitterOptionsSchema: JSONSchemaType<JsEmitterOptions> = {
@@ -27,6 +37,13 @@ const EmitterOptionsSchema: JSONSchemaType<JsEmitterOptions> = {
27
37
  description:
28
38
  "If set to `true`, the emitter will generate a router that exposes an Express.js middleware function in addition to the ordinary Node.js HTTP server router.\n\nIf this option is not set to `true`, the `expressMiddleware` property will not be present on the generated router.",
29
39
  },
40
+ datetime: {
41
+ type: "string",
42
+ enum: ["temporal-polyfill", "temporal", "date-duration"],
43
+ default: "temporal-polyfill",
44
+ nullable: true,
45
+ description: "The type of datetime models to use for TypeSpecs DateTime and Duration types.",
46
+ },
30
47
  "omit-unreachable-types": {
31
48
  type: "boolean",
32
49
  default: false,