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

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 (70) hide show
  1. package/.testignore +0 -1
  2. package/README.md +13 -0
  3. package/build-helpers.ts +22 -9
  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.map +1 -1
  20. package/dist/generated-defs/package.json.js +1 -0
  21. package/dist/generated-defs/package.json.js.map +1 -1
  22. package/dist/src/common/declaration.js +1 -1
  23. package/dist/src/common/declaration.js.map +1 -1
  24. package/dist/src/common/scalar.d.ts +1 -1
  25. package/dist/src/common/scalar.d.ts.map +1 -1
  26. package/dist/src/common/scalar.js +341 -49
  27. package/dist/src/common/scalar.js.map +1 -1
  28. package/dist/src/common/serialization/index.d.ts +2 -2
  29. package/dist/src/common/serialization/index.d.ts.map +1 -1
  30. package/dist/src/common/serialization/index.js +3 -9
  31. package/dist/src/common/serialization/index.js.map +1 -1
  32. package/dist/src/common/serialization/json.d.ts +2 -0
  33. package/dist/src/common/serialization/json.d.ts.map +1 -1
  34. package/dist/src/common/serialization/json.js +10 -19
  35. package/dist/src/common/serialization/json.js.map +1 -1
  36. package/dist/src/helpers/temporal/native.d.ts +40 -0
  37. package/dist/src/helpers/temporal/native.d.ts.map +1 -0
  38. package/dist/src/helpers/temporal/native.js +81 -0
  39. package/dist/src/helpers/temporal/native.js.map +1 -0
  40. package/dist/src/helpers/temporal/polyfill.d.ts +41 -0
  41. package/dist/src/helpers/temporal/polyfill.d.ts.map +1 -0
  42. package/dist/src/helpers/temporal/polyfill.js +80 -0
  43. package/dist/src/helpers/temporal/polyfill.js.map +1 -0
  44. package/dist/src/http/server/index.d.ts.map +1 -1
  45. package/dist/src/http/server/index.js +4 -1
  46. package/dist/src/http/server/index.js.map +1 -1
  47. package/dist/src/lib.d.ts +9 -0
  48. package/dist/src/lib.d.ts.map +1 -1
  49. package/dist/src/lib.js +7 -0
  50. package/dist/src/lib.js.map +1 -1
  51. package/dist/src/scripts/scaffold/data-mocks.d.ts.map +1 -1
  52. package/dist/src/scripts/scaffold/data-mocks.js +91 -32
  53. package/dist/src/scripts/scaffold/data-mocks.js.map +1 -1
  54. package/generated-defs/helpers/index.ts +1 -0
  55. package/generated-defs/helpers/temporal/index.ts +25 -0
  56. package/generated-defs/helpers/temporal/native.ts +132 -0
  57. package/generated-defs/helpers/temporal/polyfill.ts +131 -0
  58. package/generated-defs/package.json.ts +1 -0
  59. package/package.json +6 -5
  60. package/src/common/declaration.ts +1 -1
  61. package/src/common/scalar.ts +390 -62
  62. package/src/common/serialization/index.ts +4 -13
  63. package/src/common/serialization/json.ts +22 -25
  64. package/src/helpers/temporal/native.ts +104 -0
  65. package/src/helpers/temporal/polyfill.ts +103 -0
  66. package/src/http/server/index.ts +6 -1
  67. package/src/lib.ts +17 -0
  68. package/src/scripts/scaffold/data-mocks.ts +97 -32
  69. package/temp/tsconfig.tsbuildinfo +1 -1
  70. 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,
@@ -12,8 +12,10 @@ import { $ } from "@typespec/compiler/experimental/typekit";
12
12
  import { JsContext, Module } from "../../ctx.js";
13
13
  import { isUnspeakable, parseCase } from "../../util/case.js";
14
14
 
15
- import { module as dateTimeHelper } from "../../../generated-defs/helpers/datetime.js";
16
15
  import { KEYWORDS } from "../../util/keywords.js";
16
+ import { getFullyQualifiedTypeName } from "../../util/name.js";
17
+
18
+ import { module as dateTimeHelper } from "../../../generated-defs/helpers/datetime.js";
17
19
 
18
20
  /**
19
21
  * Generates a mock value for a TypeSpec Model.
@@ -24,16 +26,17 @@ import { KEYWORDS } from "../../util/keywords.js";
24
26
  * @throws Error if a property cannot be mocked
25
27
  */
26
28
  function mockModel(ctx: JsContext, module: Module, type: Model): string {
27
- if ($.array.is(type)) {
29
+ const tk = $(ctx.program);
30
+ if (tk.array.is(type)) {
28
31
  return mockArray(ctx, module, type);
29
32
  }
30
33
 
31
- if ($.record.is(type)) {
34
+ if (tk.record.is(type)) {
32
35
  return mockRecord(ctx, module, type);
33
36
  }
34
37
 
35
38
  const mock: string[][] = [];
36
- const properties = $.model.getProperties(type, { includeExtended: true });
39
+ const properties = tk.model.getProperties(type, { includeExtended: true });
37
40
 
38
41
  // If no properties exist, return an empty object
39
42
  if (properties.size === 0) {
@@ -72,7 +75,8 @@ function mockModel(ctx: JsContext, module: Module, type: Model): string {
72
75
  * @returns A JavaScript string representation of the mock array
73
76
  */
74
77
  function mockArray(ctx: JsContext, module: Module, type: Model): string {
75
- const elementType = $.array.getElementType(type);
78
+ const tk = $(ctx.program);
79
+ const elementType = tk.array.getElementType(type);
76
80
  const mockedType = mockType(ctx, module, elementType);
77
81
 
78
82
  // If we can't mock the element type, return an empty array
@@ -90,7 +94,8 @@ function mockArray(ctx: JsContext, module: Module, type: Model): string {
90
94
  * @returns A JavaScript string representation of the mock record
91
95
  */
92
96
  function mockRecord(ctx: JsContext, module: Module, type: Model): string {
93
- const elementType = $.record.getElementType(type);
97
+ const tk = $(ctx.program);
98
+ const elementType = tk.record.getElementType(type);
94
99
  const mockedType = mockType(ctx, module, elementType);
95
100
 
96
101
  if (mockedType === undefined) {
@@ -134,23 +139,24 @@ function mockLiteral(type: LiteralType): string {
134
139
  * @returns A JavaScript string representation of the mock data, or undefined if the type cannot be mocked
135
140
  */
136
141
  export function mockType(ctx: JsContext, module: Module, type: Type): string | undefined {
137
- if ($.model.is(type)) {
142
+ const tk = $(ctx.program);
143
+ if (tk.model.is(type)) {
138
144
  return mockModel(ctx, module, type);
139
145
  }
140
146
 
141
- if ($.literal.is(type)) {
147
+ if (tk.literal.is(type)) {
142
148
  return mockLiteral(type);
143
149
  }
144
150
 
145
- if ($.modelProperty.is(type)) {
151
+ if (tk.modelProperty.is(type)) {
146
152
  return mockModelProperty(ctx, module, type);
147
153
  }
148
154
 
149
- if ($.scalar.is(type)) {
155
+ if (tk.scalar.is(type)) {
150
156
  return mockScalar(ctx, module, type);
151
157
  }
152
158
 
153
- if ($.union.is(type)) {
159
+ if (tk.union.is(type)) {
154
160
  return mockUnion(ctx, module, type);
155
161
  }
156
162
 
@@ -186,18 +192,21 @@ function mockUnion(ctx: JsContext, module: Module, union: Union): string | undef
186
192
  * @returns A JavaScript string representation of a suitable mock value for the scalar type
187
193
  */
188
194
  function mockScalar(ctx: JsContext, module: Module, scalar: Scalar): string | undefined {
189
- if ($.scalar.isBoolean(scalar) || $.scalar.extendsBoolean(scalar)) {
195
+ const tk = $(ctx.program);
196
+ const dateTimeMode = ctx.options.datetime ?? "temporal-polyfill";
197
+
198
+ if (tk.scalar.isBoolean(scalar) || tk.scalar.extendsBoolean(scalar)) {
190
199
  return JSON.stringify(true);
191
200
  }
192
- if ($.scalar.isNumeric(scalar) || $.scalar.extendsNumeric(scalar)) {
201
+ if (tk.scalar.isNumeric(scalar) || tk.scalar.extendsNumeric(scalar)) {
193
202
  if (
194
- $.scalar.extendsSafeint(scalar) ||
195
- $.scalar.extendsInt32(scalar) ||
196
- $.scalar.extendsUint32(scalar) ||
197
- $.scalar.extendsFloat64(scalar)
203
+ tk.scalar.extendsSafeint(scalar) ||
204
+ tk.scalar.extendsInt32(scalar) ||
205
+ tk.scalar.extendsUint32(scalar) ||
206
+ tk.scalar.extendsFloat64(scalar)
198
207
  ) {
199
208
  return "42";
200
- } else if ($.scalar.extendsInteger(scalar)) {
209
+ } else if (tk.scalar.extendsInteger(scalar)) {
201
210
  return "42n";
202
211
  } else {
203
212
  module.imports.push({ from: "decimal.js", binder: ["Decimal"] });
@@ -205,40 +214,96 @@ function mockScalar(ctx: JsContext, module: Module, scalar: Scalar): string | un
205
214
  }
206
215
  }
207
216
 
208
- if ($.scalar.isUtcDateTime(scalar) || $.scalar.extendsUtcDateTime(scalar)) {
209
- return "new Date()";
217
+ if (tk.scalar.isUtcDateTime(scalar)) {
218
+ if (dateTimeMode === "date-duration") {
219
+ return "new Date()";
220
+ } else {
221
+ if (dateTimeMode === "temporal-polyfill") {
222
+ module.imports.push({
223
+ from: "temporal-polyfill",
224
+ binder: ["Temporal"],
225
+ });
226
+ }
227
+
228
+ // Current instant
229
+ return "Temporal.Now.instant()";
230
+ }
231
+ }
232
+
233
+ if (
234
+ tk.scalar.isOffsetDateTime(scalar) ||
235
+ tk.scalar.isPlainDate(scalar) ||
236
+ tk.scalar.isPlainTime(scalar)
237
+ ) {
238
+ if (dateTimeMode === "date-duration") {
239
+ return "new Date()";
240
+ } else {
241
+ if (dateTimeMode === "temporal-polyfill") {
242
+ module.imports.push({
243
+ from: "temporal-polyfill",
244
+ binder: ["Temporal"],
245
+ });
246
+ }
247
+
248
+ // Current instant
249
+ switch ((scalar as Scalar).name) {
250
+ case "offsetDateTime":
251
+ return "Temporal.Now.zonedDateTimeISO()";
252
+ case "plainDate":
253
+ return "Temporal.Now.zonedDateTimeISO().toPlainDate()";
254
+ case "plainTime":
255
+ return "Temporal.Now.zonedDateTimeISO().toPlainTime()";
256
+ default:
257
+ throw new Error("Unexpected scalar type: " + (scalar as Scalar).name);
258
+ }
259
+ }
210
260
  }
211
261
 
212
- if ($.scalar.isBytes(scalar) || $.scalar.extendsBytes(scalar)) {
262
+ if (getFullyQualifiedTypeName(scalar) === "TypeSpec.unixTimestamp32") {
263
+ return "Math.floor(new Date().getTime() / 1000)";
264
+ }
265
+
266
+ if (tk.scalar.isBytes(scalar) || tk.scalar.extendsBytes(scalar)) {
213
267
  return "new Uint8Array()";
214
268
  }
215
269
 
216
- if ($.scalar.isDuration(scalar) || $.scalar.extendsDuration(scalar)) {
217
- module.imports.push({
218
- from: dateTimeHelper,
219
- binder: ["Duration"],
220
- });
270
+ if (tk.scalar.isDuration(scalar) || tk.scalar.extendsDuration(scalar)) {
271
+ if (dateTimeMode === "date-duration") {
272
+ module.imports.push({
273
+ from: dateTimeHelper,
274
+ binder: ["Duration"],
275
+ });
221
276
 
222
- return 'Duration.parseISO8601("P1Y2M3DT4H5M6S")';
277
+ return 'Duration.parseISO8601("P1Y2M3DT4H5M6S")';
278
+ } else {
279
+ if (dateTimeMode === "temporal-polyfill") {
280
+ module.imports.push({
281
+ from: "temporal-polyfill",
282
+ binder: ["Temporal"],
283
+ });
284
+ }
285
+
286
+ return `Temporal.Duration.from({ years: 1, months: 2, days: 3, hours: 4, minutes: 5, seconds: 6 })`;
287
+ }
223
288
  }
224
289
 
225
- if ($.scalar.isOffsetDateTime(scalar) || $.scalar.extendsOffsetDateTime(scalar)) {
290
+ if (tk.scalar.isOffsetDateTime(scalar) || tk.scalar.extendsOffsetDateTime(scalar)) {
226
291
  return JSON.stringify("2022-01-01T00:00:00Z");
227
292
  }
228
293
 
229
- if ($.scalar.isPlainDate(scalar) || $.scalar.extendsPlainDate(scalar)) {
294
+ if (tk.scalar.isPlainDate(scalar) || tk.scalar.extendsPlainDate(scalar)) {
230
295
  return JSON.stringify("2022-01-01");
231
296
  }
232
297
 
233
- if ($.scalar.isPlainTime(scalar) || $.scalar.extendsPlainTime(scalar)) {
298
+ if (tk.scalar.isPlainTime(scalar) || tk.scalar.extendsPlainTime(scalar)) {
234
299
  return JSON.stringify("00:00:00");
235
300
  }
236
301
 
237
- if ($.scalar.isUrl(scalar) || $.scalar.extendsUrl(scalar)) {
302
+ if (tk.scalar.isUrl(scalar) || tk.scalar.extendsUrl(scalar)) {
238
303
  return JSON.stringify("https://example.com");
239
304
  }
240
305
 
241
- if ($.scalar.isString(scalar) || $.scalar.extendsString(scalar)) {
306
+ if (tk.scalar.isString(scalar) || tk.scalar.extendsString(scalar)) {
242
307
  return JSON.stringify("mock-string");
243
308
  }
244
309