@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
@@ -8,9 +8,13 @@ import { parseCase } from "../util/case.js";
8
8
  import { getFullyQualifiedTypeName } from "../util/name.js";
9
9
 
10
10
  import { HttpOperationParameter } from "@typespec/http";
11
- import { module as dateTimeModule } from "../../generated-defs/helpers/datetime.js";
12
11
  import { UnreachableError } from "../util/error.js";
13
12
 
13
+ import { module as dateTimeModule } from "../../generated-defs/helpers/datetime.js";
14
+ import { module as temporalNativeHelpers } from "../../generated-defs/helpers/temporal/native.js";
15
+ import { module as temporalPolyfillHelpers } from "../../generated-defs/helpers/temporal/polyfill.js";
16
+ import { emitDocumentation } from "./documentation.js";
17
+
14
18
  /**
15
19
  * A specification of a TypeSpec scalar type.
16
20
  */
@@ -127,7 +131,7 @@ const DURATION_NUMBER_ENCODING: Dependent<ScalarEncoding> = (_, module) => {
127
131
 
128
132
  return {
129
133
  encodeTemplate: "Duration.totalSeconds({})",
130
- decodeTemplate: "Duration.fromSeconds({})",
134
+ decodeTemplate: "Duration.fromTotalSeconds({})",
131
135
  };
132
136
  };
133
137
 
@@ -139,11 +143,112 @@ const DURATION_BIGINT_ENCODING: Dependent<ScalarEncoding> = (_, module) => {
139
143
 
140
144
  return {
141
145
  encodeTemplate: "Duration.totalSecondsBigInt({})",
142
- decodeTemplate: "Duration.fromSeconds(globalThis.Number({}))",
146
+ decodeTemplate: "Duration.fromTotalSeconds(globalThis.Number({}))",
147
+ };
148
+ };
149
+
150
+ /**
151
+ * Resolves the encoding of Duration values to a BigDecimal number of seconds.
152
+ */
153
+ const DURATION_BIGDECIMAL_ENCODING: Dependent<ScalarEncoding> = (_, module) => {
154
+ module.imports.push(
155
+ { from: dateTimeModule, binder: ["Duration"] },
156
+ { from: "decimal.js", binder: ["Decimal"] },
157
+ );
158
+
159
+ return {
160
+ encodeTemplate: "new Decimal(Duration.totalSeconds({}).toString())",
161
+ decodeTemplate: "Duration.fromSeconds(({}).toNumber())",
162
+ };
163
+ };
164
+
165
+ const DURATION: Dependent<ScalarInfo> = (ctx, module) => {
166
+ const mode = ctx.options.datetime ?? "temporal-polyfill";
167
+
168
+ if (mode === "temporal-polyfill") {
169
+ module.imports.push({ from: "temporal-polyfill", binder: ["Temporal"] });
170
+ }
171
+
172
+ const isTemporal = mode === "temporal" || mode === "temporal-polyfill";
173
+ const temporalRef = mode === "temporal-polyfill" ? "Temporal" : "globalThis.Temporal";
174
+
175
+ if (!isTemporal) return DURATION_CUSTOM;
176
+
177
+ const isPolyfill = mode === "temporal-polyfill";
178
+
179
+ return {
180
+ type: "Temporal.Duration",
181
+ isJsonCompatible: false,
182
+ encodings: {
183
+ "TypeSpec.string": {
184
+ default: { via: "iso8601" },
185
+ iso8601: {
186
+ encodeTemplate: `({}).toString()`,
187
+ decodeTemplate: `${temporalRef}.Duration.from({})`,
188
+ },
189
+ },
190
+ ...Object.fromEntries(
191
+ ["int32", "uint32", "float32", "float64"].map((n) => [
192
+ `TypeSpec.${n}`,
193
+ {
194
+ default: { via: "seconds" },
195
+ seconds: {
196
+ encodeTemplate: (_, module) => {
197
+ module.imports.push({
198
+ from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
199
+ binder: [`durationTotalSeconds`],
200
+ });
201
+
202
+ return `durationTotalSeconds({})`;
203
+ },
204
+ decodeTemplate: `${temporalRef}.Duration.from({ seconds: {} })`,
205
+ },
206
+ },
207
+ ]),
208
+ ),
209
+ ...Object.fromEntries(
210
+ ["int64", "uint64", "integer"].map((n) => [
211
+ `TypeSpec.${n}`,
212
+ {
213
+ default: { via: "seconds" },
214
+ seconds: {
215
+ encodeTemplate: (_, module) => {
216
+ module.imports.push({
217
+ from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
218
+ binder: [`durationTotalSecondsBigInt`],
219
+ });
220
+
221
+ return `durationTotalSecondsBigInt({})`;
222
+ },
223
+ decodeTemplate: `${temporalRef}.Duration.from({ seconds: globalThis.Number({}) })`,
224
+ },
225
+ },
226
+ ]),
227
+ ),
228
+ "TypeSpec.float": {
229
+ default: { via: "seconds" },
230
+ seconds: {
231
+ encodeTemplate: (_, module) => {
232
+ module.imports.push({
233
+ from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
234
+ binder: [`durationTotalSecondsBigInt`],
235
+ });
236
+
237
+ return `new Decimal(durationTotalSecondsBigInt({}).toString())`;
238
+ },
239
+ decodeTemplate: `${temporalRef}.Duration.from({ seconds: ({}).toNumber() })`,
240
+ },
241
+ },
242
+ },
243
+ defaultEncodings: {
244
+ byMimeType: {
245
+ "application/json": ["TypeSpec.string", "iso8601"],
246
+ },
247
+ },
143
248
  };
144
249
  };
145
250
 
146
- const TYPESPEC_DURATION: ScalarInfo = {
251
+ const DURATION_CUSTOM: ScalarInfo = {
147
252
  type: function importDuration(_, module) {
148
253
  module.imports.push({ from: dateTimeModule, binder: ["Duration"] });
149
254
 
@@ -163,7 +268,7 @@ const TYPESPEC_DURATION: ScalarInfo = {
163
268
  },
164
269
  },
165
270
  ...Object.fromEntries(
166
- ["int32", "uint32"].map((n) => [
271
+ ["int32", "uint32", "float32", "float64"].map((n) => [
167
272
  `TypeSpec.${n}`,
168
273
  {
169
274
  default: { via: "seconds" },
@@ -180,6 +285,10 @@ const TYPESPEC_DURATION: ScalarInfo = {
180
285
  },
181
286
  ]),
182
287
  ),
288
+ "TypeSpec.float": {
289
+ default: { via: "seconds" },
290
+ seconds: DURATION_BIGDECIMAL_ENCODING,
291
+ },
183
292
  },
184
293
  defaultEncodings: {
185
294
  byMimeType: {
@@ -202,6 +311,39 @@ const NUMBER: ScalarInfo = {
202
311
  isJsonCompatible: true,
203
312
  };
204
313
 
314
+ const BIGDECIMAL: ScalarInfo = {
315
+ type(_, module) {
316
+ module.imports.push({ from: "decimal.js", binder: ["Decimal"] });
317
+
318
+ return "Decimal";
319
+ },
320
+ encodings: {
321
+ "TypeSpec.string": {
322
+ default: {
323
+ encodeTemplate: "({}).toString()",
324
+ decodeTemplate: "new Decimal({})",
325
+ },
326
+ },
327
+ },
328
+ isJsonCompatible: false,
329
+ };
330
+
331
+ const BIGINT: ScalarInfo = {
332
+ type: "bigint",
333
+ encodings: {
334
+ "TypeSpec.string": {
335
+ default: {
336
+ encodeTemplate: "globalThis.String({})",
337
+ decodeTemplate: "globalThis.BigInt({})",
338
+ },
339
+ },
340
+ },
341
+ defaultEncodings: {
342
+ byMimeType: { "application/json": ["TypeSpec.string", "default"] },
343
+ },
344
+ isJsonCompatible: false,
345
+ };
346
+
205
347
  /**
206
348
  * Declarative scalar table.
207
349
  *
@@ -221,7 +363,7 @@ const NUMBER: ScalarInfo = {
221
363
  * `byMimeType` object maps MIME types to encoding pairs, and the `http` object maps HTTP metadata contexts to
222
364
  * encoding pairs.
223
365
  */
224
- const SCALARS = new Map<string, ScalarInfo>([
366
+ const SCALARS = new Map<string, MaybeDependent<ScalarInfo>>([
225
367
  [
226
368
  "TypeSpec.bytes",
227
369
  {
@@ -279,37 +421,260 @@ const SCALARS = new Map<string, ScalarInfo>([
279
421
 
280
422
  ["TypeSpec.float32", NUMBER],
281
423
  ["TypeSpec.float64", NUMBER],
424
+ ["TypeSpec.uint64", BIGINT],
282
425
  ["TypeSpec.uint32", NUMBER],
283
426
  ["TypeSpec.uint16", NUMBER],
284
427
  ["TypeSpec.uint8", NUMBER],
428
+ ["TypeSpec.int64", BIGINT],
285
429
  ["TypeSpec.int32", NUMBER],
286
430
  ["TypeSpec.int16", NUMBER],
287
431
  ["TypeSpec.int8", NUMBER],
288
432
  ["TypeSpec.safeint", NUMBER],
289
433
 
434
+ ["TypeSpec.numeric", BIGDECIMAL],
435
+ ["TypeSpec.float", BIGDECIMAL],
436
+ ["TypeSpec.decimal", BIGDECIMAL],
437
+ ["TypeSpec.decimal128", BIGDECIMAL],
438
+
439
+ ["TypeSpec.integer", BIGINT],
440
+ ["TypeSpec.plainDate", dateTime("plainDate")],
441
+ ["TypeSpec.plainTime", dateTime("plainTime")],
442
+ ["TypeSpec.utcDateTime", dateTime("utcDateTime")],
443
+ ["TypeSpec.offsetDateTime", dateTime("offsetDateTime")],
290
444
  [
291
- "TypeSpec.integer",
445
+ "TypeSpec.unixTimestamp32",
292
446
  {
293
- type: "bigint",
447
+ type: "number",
294
448
  encodings: {
295
449
  "TypeSpec.string": {
296
450
  default: {
297
451
  encodeTemplate: "globalThis.String({})",
298
- decodeTemplate: "globalThis.BigInt({})",
452
+ decodeTemplate: "globalThis.Number({})",
453
+ },
454
+ },
455
+ "TypeSpec.int32": {
456
+ default: { via: "unixTimestamp" },
457
+ unixTimestamp: {
458
+ encodeTemplate: "{}",
459
+ decodeTemplate: "{}",
460
+ },
461
+ },
462
+ "TypeSpec.int64": {
463
+ default: { via: "unixTimestamp" },
464
+ unixTimestamp: {
465
+ encodeTemplate: "globalThis.BigInt({})",
466
+ decodeTemplate: "globalThis.Number({})",
299
467
  },
300
468
  },
301
469
  },
302
- isJsonCompatible: false,
470
+ isJsonCompatible: true,
303
471
  },
304
472
  ],
305
- ["TypeSpec.plainDate", { type: "Date", isJsonCompatible: false }],
306
- ["TypeSpec.plainTime", { type: "Date", isJsonCompatible: false }],
307
- ["TypeSpec.utcDateTime", { type: "Date", isJsonCompatible: false }],
308
- ["TypeSpec.offsetDateTime", { type: "Date", isJsonCompatible: false }],
309
- ["TypeSpec.unixTimestamp32", { type: "Date", isJsonCompatible: false }],
310
- ["TypeSpec.duration", TYPESPEC_DURATION],
473
+ ["TypeSpec.duration", DURATION],
311
474
  ]);
312
475
 
476
+ /**
477
+ * Datetime types that support dynamic construction.
478
+ */
479
+ type DateTimeType = "plainDate" | "plainTime" | "utcDateTime" | "offsetDateTime";
480
+
481
+ /**
482
+ * Gets the DateTime Scalar specification for a given date time type.
483
+ */
484
+ function dateTime(t: DateTimeType): Dependent<ScalarInfo> {
485
+ return (ctx, module): ScalarInfo => {
486
+ const mode = ctx.options.datetime ?? "temporal-polyfill";
487
+
488
+ if (mode === "temporal-polyfill") {
489
+ module.imports.push({ from: "temporal-polyfill", binder: ["Temporal"] });
490
+ }
491
+
492
+ const isTemporal = mode === "temporal" || mode === "temporal-polyfill";
493
+ const temporalRef = mode === "temporal-polyfill" ? "Temporal" : "globalThis.Temporal";
494
+
495
+ let type: string;
496
+
497
+ switch (t) {
498
+ case "plainDate":
499
+ type = isTemporal ? "Temporal.PlainDate" : "Date";
500
+ break;
501
+ case "plainTime":
502
+ type = isTemporal ? "Temporal.PlainTime" : "Date";
503
+ break;
504
+ case "utcDateTime":
505
+ type = isTemporal ? "Temporal.Instant" : "Date";
506
+ break;
507
+ case "offsetDateTime":
508
+ type = isTemporal ? "Temporal.ZonedDateTime" : "Date";
509
+ break;
510
+ default:
511
+ void (t satisfies never);
512
+ throw new UnreachableError(`Unknown datetime type: ${t}`);
513
+ }
514
+
515
+ return {
516
+ type,
517
+ isJsonCompatible: false,
518
+ encodings: isTemporal
519
+ ? TEMPORAL_ENCODERS(temporalRef, mode === "temporal-polyfill")[t]
520
+ : LEGACY_DATETIME_ENCODER,
521
+ defaultEncodings: {
522
+ byMimeType: {
523
+ "application/json": ["TypeSpec.string", "rfc3339"],
524
+ },
525
+ http: {
526
+ header: ["TypeSpec.string", "rfc7231"],
527
+ query: ["TypeSpec.string", "rfc3339"],
528
+ cookie: ["TypeSpec.string", "rfc7231"],
529
+ path: ["TypeSpec.string", "rfc3339"],
530
+ },
531
+ },
532
+ };
533
+ };
534
+ }
535
+
536
+ const TEMPORAL_ENCODERS = (
537
+ temporal: string,
538
+ isPolyfill: boolean,
539
+ ): Record<DateTimeType, ScalarInfo["encodings"]> => {
540
+ return {
541
+ plainDate: {
542
+ "TypeSpec.string": {
543
+ default: { via: "iso8601" },
544
+ rfc3339: { via: "iso8601" },
545
+ iso8601: {
546
+ encodeTemplate: "({}).toString()",
547
+ decodeTemplate: `${temporal}.PlainDate.from({})`,
548
+ },
549
+ },
550
+ },
551
+ plainTime: {
552
+ "TypeSpec.string": {
553
+ default: { via: "iso8601" },
554
+ rfc3339: { via: "iso8601" },
555
+ iso8601: {
556
+ encodeTemplate: "({}).toString()",
557
+ decodeTemplate: `${temporal}.PlainTime.from({})`,
558
+ },
559
+ },
560
+ },
561
+ // Temporal.Instant
562
+ utcDateTime: {
563
+ "TypeSpec.string": {
564
+ default: { via: "iso8601" },
565
+ rfc3339: { via: "iso8601" },
566
+ iso8601: {
567
+ encodeTemplate: "({}).toString()",
568
+ decodeTemplate: `${temporal}.Instant.from({})`,
569
+ },
570
+ "http-date": { via: "rfc7231" },
571
+ rfc7231: {
572
+ encodeTemplate: (ctx, module) => {
573
+ module.imports.push({
574
+ from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
575
+ binder: [`formatHttpDate`],
576
+ });
577
+
578
+ return `formatHttpDate({})`;
579
+ },
580
+ decodeTemplate: (ctx, module) => {
581
+ module.imports.push({
582
+ from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
583
+ binder: [`parseHttpDate`],
584
+ });
585
+
586
+ return `parseHttpDate({})`;
587
+ },
588
+ },
589
+ },
590
+ "TypeSpec.int32": {
591
+ default: { via: "unixTimestamp" },
592
+ unixTimestamp: {
593
+ encodeTemplate: "globalThis.Math.floor(({}).epochMilliseconds / 1000)",
594
+ decodeTemplate: `${temporal}.Instant.fromEpochMilliseconds({} * 1000)`,
595
+ },
596
+ },
597
+ "TypeSpec.int64": {
598
+ default: { via: "unixTimestamp" },
599
+ unixTimestamp: {
600
+ encodeTemplate: "({}).epochNanoseconds / 1_000_000_000n",
601
+ decodeTemplate: `${temporal}.Instant.fromEpochNanoseconds({} * 1_000_000_000n)`,
602
+ },
603
+ },
604
+ },
605
+ // Temporal.ZonedDateTime
606
+ offsetDateTime: {
607
+ "TypeSpec.string": {
608
+ default: { via: "iso8601" },
609
+ rfc3339: { via: "iso8601" },
610
+ iso8601: {
611
+ encodeTemplate: "({}).toString()",
612
+ decodeTemplate: `${temporal}.ZonedDateTime.from({})`,
613
+ },
614
+ "http-date": { via: "rfc7231" },
615
+ rfc7231: {
616
+ encodeTemplate: (ctx, module) => {
617
+ module.imports.push({
618
+ from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
619
+ binder: [`formatHttpDate`],
620
+ });
621
+
622
+ return `formatHttpDate(({}).toInstant())`;
623
+ },
624
+ decodeTemplate: (ctx, module) => {
625
+ module.imports.push({
626
+ from: isPolyfill ? temporalPolyfillHelpers : temporalNativeHelpers,
627
+ binder: [`parseHttpDate`],
628
+ });
629
+
630
+ // HTTP dates are always GMT a.k.a. UTC
631
+ return `parseHttpDate({}).toZonedDateTimeISO("UTC")`;
632
+ },
633
+ },
634
+ },
635
+ },
636
+ };
637
+ };
638
+
639
+ /**
640
+ * Encoding and decoding for legacy JS Date.
641
+ */
642
+ const LEGACY_DATETIME_ENCODER: ScalarInfo["encodings"] = {
643
+ "TypeSpec.string": {
644
+ default: {
645
+ via: "iso8601",
646
+ },
647
+ iso8601: {
648
+ encodeTemplate: "({}).toISOString()",
649
+ decodeTemplate: "new globalThis.Date({})",
650
+ },
651
+ rfc3339: {
652
+ via: "iso8601",
653
+ },
654
+ rfc7231: {
655
+ encodeTemplate: "({}).toUTCString()",
656
+ decodeTemplate: "new globalThis.Date({})",
657
+ },
658
+ "http-date": {
659
+ via: "rfc7231",
660
+ },
661
+ },
662
+ "TypeSpec.int32": {
663
+ default: { via: "unixTimestamp" },
664
+ unixTimestamp: {
665
+ encodeTemplate: "globalThis.Math.floor(({}).getTime() / 1000)",
666
+ decodeTemplate: `new globalThis.Date({} * 1000)`,
667
+ },
668
+ },
669
+ "TypeSpec.int64": {
670
+ default: { via: "unixTimestamp" },
671
+ unixTimestamp: {
672
+ encodeTemplate: "globalThis.BigInt(({}).getTime()) / 1000n",
673
+ decodeTemplate: `new globalThis.Date(globalThis.Number({}) * 1000)`,
674
+ },
675
+ },
676
+ };
677
+
313
678
  /**
314
679
  * Emits a declaration for a scalar type.
315
680
  *
@@ -320,12 +685,14 @@ const SCALARS = new Map<string, ScalarInfo>([
320
685
  * @param scalar - The scalar to emit.
321
686
  * @returns a string that declares an alias to the scalar type in TypeScript.
322
687
  */
323
- export function emitScalar(ctx: JsContext, scalar: Scalar, module: Module): string {
688
+ export function* emitScalar(ctx: JsContext, scalar: Scalar, module: Module): Iterable<string> {
324
689
  const jsScalar = getJsScalar(ctx, module, scalar, scalar.node.id);
325
690
 
326
691
  const name = parseCase(scalar.name).pascalCase;
327
692
 
328
- return `type ${name} = ${jsScalar.type};`;
693
+ yield* emitDocumentation(ctx, scalar);
694
+
695
+ yield `export type ${name} = ${jsScalar.type};`;
329
696
  }
330
697
 
331
698
  /**
@@ -348,12 +715,12 @@ const __JS_SCALARS_MAP = new WeakMap<Program, ScalarStore>();
348
715
  /**
349
716
  * Gets the scalar store for a given program.
350
717
  */
351
- function getScalarStore(program: Program): ScalarStore {
352
- let scalars = __JS_SCALARS_MAP.get(program);
718
+ function getScalarStore(ctx: JsContext, module: Module): ScalarStore {
719
+ let scalars = __JS_SCALARS_MAP.get(ctx.program);
353
720
 
354
721
  if (scalars === undefined) {
355
- scalars = createScalarStore(program);
356
- __JS_SCALARS_MAP.set(program, scalars);
722
+ scalars = createScalarStore(ctx, module);
723
+ __JS_SCALARS_MAP.set(ctx.program, scalars);
357
724
  }
358
725
 
359
726
  return scalars;
@@ -362,17 +729,17 @@ function getScalarStore(program: Program): ScalarStore {
362
729
  /**
363
730
  * Initializes a scalar store for a given program.
364
731
  */
365
- function createScalarStore(program: Program): ScalarStore {
732
+ function createScalarStore(ctx: JsContext, module: Module): ScalarStore {
366
733
  const m = new Map<Scalar, Contextualized<JsScalar>>();
367
734
 
368
735
  for (const [scalarName, scalarInfo] of SCALARS) {
369
- const [scalar, diagnostics] = program.resolveTypeReference(scalarName);
736
+ const [scalar, diagnostics] = ctx.program.resolveTypeReference(scalarName);
370
737
 
371
738
  if (diagnostics.length > 0 || !scalar || scalar.kind !== "Scalar") {
372
739
  throw new UnreachableError(`Failed to resolve built-in scalar '${scalarName}'`);
373
740
  }
374
741
 
375
- m.set(scalar, createJsScalar(program, scalar, scalarInfo, m));
742
+ m.set(scalar, createJsScalar(ctx.program, scalar, scalarInfo, m));
376
743
  }
377
744
 
378
745
  return m;
@@ -383,17 +750,18 @@ function createScalarStore(program: Program): ScalarStore {
383
750
  *
384
751
  * @param program - The program that contains the scalar.
385
752
  * @param scalar - The scalar to bind.
386
- * @param scalarInfo - The scalar information spec to bind.
753
+ * @param _scalarInfo - The scalar information spec to bind.
387
754
  * @param store - The scalar store to use for the scalar.
388
755
  * @returns a function that takes a JsContext and Module and returns a JsScalar.
389
756
  */
390
757
  function createJsScalar(
391
758
  program: Program,
392
759
  scalar: Scalar,
393
- scalarInfo: ScalarInfo,
760
+ _scalarInfo: MaybeDependent<ScalarInfo>,
394
761
  store: ScalarStore,
395
762
  ): Contextualized<JsScalar> {
396
763
  return (ctx, module) => {
764
+ const scalarInfo = typeof _scalarInfo === "function" ? _scalarInfo(ctx, module) : _scalarInfo;
397
765
  const _http: { [K in HttpOperationParameter["type"]]?: Encoder } = {};
398
766
  let _type: string | undefined = undefined;
399
767
 
@@ -406,8 +774,8 @@ function createJsScalar(
406
774
  scalar,
407
775
 
408
776
  getEncoding(encoding: string, target: Scalar): Encoder | undefined {
409
- encoding = encoding.toLowerCase();
410
- let encodingSpec = scalarInfo.encodings?.[getFullyQualifiedTypeName(target)]?.[encoding];
777
+ const encodingTable = scalarInfo.encodings?.[getFullyQualifiedTypeName(target)];
778
+ let encodingSpec = encodingTable?.[encoding] ?? encodingTable?.[encoding.toLowerCase()];
411
779
 
412
780
  if (encodingSpec === undefined) {
413
781
  return undefined;
@@ -514,38 +882,39 @@ function createJsScalar(
514
882
  };
515
883
 
516
884
  return self;
517
- };
518
-
519
- /**
520
- * Helper to get the HTTP encoders for the scalar.
521
- */
522
- function getHttpEncoder(
523
- ctx: JsContext,
524
- module: Module,
525
- self: JsScalar,
526
- form: HttpOperationParameter["type"],
527
- ) {
528
- const [target, encoding] = scalarInfo.defaultEncodings?.http?.[form] ?? [
529
- "TypeSpec.string",
530
- "default",
531
- ];
532
-
533
- const [targetScalar, diagnostics] = program.resolveTypeReference(target);
534
-
535
- if (diagnostics.length > 0 || !targetScalar || targetScalar.kind !== "Scalar") {
536
- throw new UnreachableError(`Failed to resolve built-in scalar '${target}'`);
537
- }
538
-
539
- let encoder = self.getEncoding(encoding, targetScalar);
540
-
541
- if (encoder === undefined && scalarInfo.defaultEncodings?.http?.[form]) {
542
- throw new UnreachableError(`Default HTTP ${form} encoding specified but failed to resolve.`);
885
+ /**
886
+ * Helper to get the HTTP encoders for the scalar.
887
+ */
888
+ function getHttpEncoder(
889
+ ctx: JsContext,
890
+ module: Module,
891
+ self: JsScalar,
892
+ form: HttpOperationParameter["type"],
893
+ ) {
894
+ const [target, encoding] = scalarInfo.defaultEncodings?.http?.[form] ?? [
895
+ "TypeSpec.string",
896
+ "default",
897
+ ];
898
+
899
+ const [targetScalar, diagnostics] = program.resolveTypeReference(target);
900
+
901
+ if (diagnostics.length > 0 || !targetScalar || targetScalar.kind !== "Scalar") {
902
+ throw new UnreachableError(`Failed to resolve built-in scalar '${target}'`);
903
+ }
904
+
905
+ let encoder = self.getEncoding(encoding, targetScalar);
906
+
907
+ if (encoder === undefined && scalarInfo.defaultEncodings?.http?.[form]) {
908
+ throw new UnreachableError(
909
+ `Default HTTP ${form} encoding specified but failed to resolve.`,
910
+ );
911
+ }
912
+
913
+ encoder ??= getDefaultHttpStringEncoder(ctx, module, form);
914
+
915
+ return encoder;
543
916
  }
544
-
545
- encoder ??= getDefaultHttpStringEncoder(ctx, module, form);
546
-
547
- return encoder;
548
- }
917
+ };
549
918
  }
550
919
 
551
920
  /**
@@ -759,7 +1128,7 @@ export function getJsScalar(
759
1128
  scalar: Scalar,
760
1129
  diagnosticTarget: DiagnosticTarget | typeof NoTarget,
761
1130
  ): JsScalar {
762
- const scalars = getScalarStore(ctx.program);
1131
+ const scalars = getScalarStore(ctx, module);
763
1132
 
764
1133
  let _scalar: Scalar | undefined = scalar;
765
1134
 
@@ -1,25 +1,18 @@
1
1
  // Copyright (c) Microsoft Corporation
2
2
  // Licensed under the MIT license.
3
3
 
4
- import { Enum, Model, NoTarget, Scalar, Type, Union } from "@typespec/compiler";
4
+ import { Enum, Model, NoTarget, Type, Union } from "@typespec/compiler";
5
5
  import { $ } from "@typespec/compiler/experimental/typekit";
6
6
  import { JsContext, Module, completePendingDeclarations } from "../../ctx.js";
7
- import { UnimplementedError } from "../../util/error.js";
8
7
  import { indent } from "../../util/iter.js";
9
8
  import { createOrGetModuleForNamespace } from "../namespace.js";
10
9
  import { emitTypeReference } from "../reference.js";
11
10
  import { emitJsonSerialization, requiresJsonSerialization } from "./json.js";
12
11
 
13
- export type SerializableType = Model | Scalar | Union | Enum;
12
+ export type SerializableType = Model | Union | Enum;
14
13
 
15
14
  export function isSerializableType(t: Type): t is SerializableType {
16
- return (
17
- t.kind === "Model" ||
18
- t.kind === "Scalar" ||
19
- t.kind === "Union" ||
20
- t.kind === "Intrinsic" ||
21
- t.kind === "Enum"
22
- );
15
+ return t.kind === "Model" || t.kind === "Union" || t.kind === "Intrinsic" || t.kind === "Enum";
23
16
  }
24
17
 
25
18
  export type SerializationContentType = "application/json";
@@ -31,9 +24,7 @@ export function requireSerialization(
31
24
  type: Type,
32
25
  contentType: SerializationContentType,
33
26
  ): void {
34
- if (!isSerializableType(type)) {
35
- throw new UnimplementedError(`no implementation of JSON serialization for type '${type.kind}'`);
36
- }
27
+ if (!isSerializableType(type)) return;
37
28
 
38
29
  // Ignore array and record types
39
30
  if ($(ctx.program).array.is(type) || $(ctx.program).record.is(type)) {