@typespec/http-server-js 0.58.0-alpha.13-dev.8 → 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 (70) hide show
  1. package/.testignore +0 -1
  2. package/README.md +6 -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 +62 -8
  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 +4 -3
  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 +68 -8
  69. package/temp/tsconfig.tsbuildinfo +1 -1
  70. 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,7 +143,7 @@ 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({}))",
143
147
  };
144
148
  };
145
149
 
@@ -154,11 +158,97 @@ const DURATION_BIGDECIMAL_ENCODING: Dependent<ScalarEncoding> = (_, module) => {
154
158
 
155
159
  return {
156
160
  encodeTemplate: "new Decimal(Duration.totalSeconds({}).toString())",
157
- decodeTemplate: "Duration.fromSeconds({}.toNumber())",
161
+ decodeTemplate: "Duration.fromSeconds(({}).toNumber())",
158
162
  };
159
163
  };
160
164
 
161
- const TYPESPEC_DURATION: ScalarInfo = {
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
+ },
248
+ };
249
+ };
250
+
251
+ const DURATION_CUSTOM: ScalarInfo = {
162
252
  type: function importDuration(_, module) {
163
253
  module.imports.push({ from: dateTimeModule, binder: ["Duration"] });
164
254
 
@@ -230,7 +320,7 @@ const BIGDECIMAL: ScalarInfo = {
230
320
  encodings: {
231
321
  "TypeSpec.string": {
232
322
  default: {
233
- encodeTemplate: "{}.toString()",
323
+ encodeTemplate: "({}).toString()",
234
324
  decodeTemplate: "new Decimal({})",
235
325
  },
236
326
  },
@@ -238,6 +328,22 @@ const BIGDECIMAL: ScalarInfo = {
238
328
  isJsonCompatible: false,
239
329
  };
240
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
+
241
347
  /**
242
348
  * Declarative scalar table.
243
349
  *
@@ -257,7 +363,7 @@ const BIGDECIMAL: ScalarInfo = {
257
363
  * `byMimeType` object maps MIME types to encoding pairs, and the `http` object maps HTTP metadata contexts to
258
364
  * encoding pairs.
259
365
  */
260
- const SCALARS = new Map<string, ScalarInfo>([
366
+ const SCALARS = new Map<string, MaybeDependent<ScalarInfo>>([
261
367
  [
262
368
  "TypeSpec.bytes",
263
369
  {
@@ -315,9 +421,11 @@ const SCALARS = new Map<string, ScalarInfo>([
315
421
 
316
422
  ["TypeSpec.float32", NUMBER],
317
423
  ["TypeSpec.float64", NUMBER],
424
+ ["TypeSpec.uint64", BIGINT],
318
425
  ["TypeSpec.uint32", NUMBER],
319
426
  ["TypeSpec.uint16", NUMBER],
320
427
  ["TypeSpec.uint8", NUMBER],
428
+ ["TypeSpec.int64", BIGINT],
321
429
  ["TypeSpec.int32", NUMBER],
322
430
  ["TypeSpec.int16", NUMBER],
323
431
  ["TypeSpec.int8", NUMBER],
@@ -328,29 +436,245 @@ const SCALARS = new Map<string, ScalarInfo>([
328
436
  ["TypeSpec.decimal", BIGDECIMAL],
329
437
  ["TypeSpec.decimal128", BIGDECIMAL],
330
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")],
331
444
  [
332
- "TypeSpec.integer",
445
+ "TypeSpec.unixTimestamp32",
333
446
  {
334
- type: "bigint",
447
+ type: "number",
335
448
  encodings: {
336
449
  "TypeSpec.string": {
337
450
  default: {
338
451
  encodeTemplate: "globalThis.String({})",
339
- 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({})",
340
467
  },
341
468
  },
342
469
  },
343
- isJsonCompatible: false,
470
+ isJsonCompatible: true,
344
471
  },
345
472
  ],
346
- ["TypeSpec.plainDate", { type: "Date", isJsonCompatible: false }],
347
- ["TypeSpec.plainTime", { type: "Date", isJsonCompatible: false }],
348
- ["TypeSpec.utcDateTime", { type: "Date", isJsonCompatible: false }],
349
- ["TypeSpec.offsetDateTime", { type: "Date", isJsonCompatible: false }],
350
- ["TypeSpec.unixTimestamp32", { type: "Date", isJsonCompatible: false }],
351
- ["TypeSpec.duration", TYPESPEC_DURATION],
473
+ ["TypeSpec.duration", DURATION],
352
474
  ]);
353
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
+
354
678
  /**
355
679
  * Emits a declaration for a scalar type.
356
680
  *
@@ -361,12 +685,14 @@ const SCALARS = new Map<string, ScalarInfo>([
361
685
  * @param scalar - The scalar to emit.
362
686
  * @returns a string that declares an alias to the scalar type in TypeScript.
363
687
  */
364
- export function emitScalar(ctx: JsContext, scalar: Scalar, module: Module): string {
688
+ export function* emitScalar(ctx: JsContext, scalar: Scalar, module: Module): Iterable<string> {
365
689
  const jsScalar = getJsScalar(ctx, module, scalar, scalar.node.id);
366
690
 
367
691
  const name = parseCase(scalar.name).pascalCase;
368
692
 
369
- return `type ${name} = ${jsScalar.type};`;
693
+ yield* emitDocumentation(ctx, scalar);
694
+
695
+ yield `export type ${name} = ${jsScalar.type};`;
370
696
  }
371
697
 
372
698
  /**
@@ -389,12 +715,12 @@ const __JS_SCALARS_MAP = new WeakMap<Program, ScalarStore>();
389
715
  /**
390
716
  * Gets the scalar store for a given program.
391
717
  */
392
- function getScalarStore(program: Program): ScalarStore {
393
- let scalars = __JS_SCALARS_MAP.get(program);
718
+ function getScalarStore(ctx: JsContext, module: Module): ScalarStore {
719
+ let scalars = __JS_SCALARS_MAP.get(ctx.program);
394
720
 
395
721
  if (scalars === undefined) {
396
- scalars = createScalarStore(program);
397
- __JS_SCALARS_MAP.set(program, scalars);
722
+ scalars = createScalarStore(ctx, module);
723
+ __JS_SCALARS_MAP.set(ctx.program, scalars);
398
724
  }
399
725
 
400
726
  return scalars;
@@ -403,17 +729,17 @@ function getScalarStore(program: Program): ScalarStore {
403
729
  /**
404
730
  * Initializes a scalar store for a given program.
405
731
  */
406
- function createScalarStore(program: Program): ScalarStore {
732
+ function createScalarStore(ctx: JsContext, module: Module): ScalarStore {
407
733
  const m = new Map<Scalar, Contextualized<JsScalar>>();
408
734
 
409
735
  for (const [scalarName, scalarInfo] of SCALARS) {
410
- const [scalar, diagnostics] = program.resolveTypeReference(scalarName);
736
+ const [scalar, diagnostics] = ctx.program.resolveTypeReference(scalarName);
411
737
 
412
738
  if (diagnostics.length > 0 || !scalar || scalar.kind !== "Scalar") {
413
739
  throw new UnreachableError(`Failed to resolve built-in scalar '${scalarName}'`);
414
740
  }
415
741
 
416
- m.set(scalar, createJsScalar(program, scalar, scalarInfo, m));
742
+ m.set(scalar, createJsScalar(ctx.program, scalar, scalarInfo, m));
417
743
  }
418
744
 
419
745
  return m;
@@ -424,17 +750,18 @@ function createScalarStore(program: Program): ScalarStore {
424
750
  *
425
751
  * @param program - The program that contains the scalar.
426
752
  * @param scalar - The scalar to bind.
427
- * @param scalarInfo - The scalar information spec to bind.
753
+ * @param _scalarInfo - The scalar information spec to bind.
428
754
  * @param store - The scalar store to use for the scalar.
429
755
  * @returns a function that takes a JsContext and Module and returns a JsScalar.
430
756
  */
431
757
  function createJsScalar(
432
758
  program: Program,
433
759
  scalar: Scalar,
434
- scalarInfo: ScalarInfo,
760
+ _scalarInfo: MaybeDependent<ScalarInfo>,
435
761
  store: ScalarStore,
436
762
  ): Contextualized<JsScalar> {
437
763
  return (ctx, module) => {
764
+ const scalarInfo = typeof _scalarInfo === "function" ? _scalarInfo(ctx, module) : _scalarInfo;
438
765
  const _http: { [K in HttpOperationParameter["type"]]?: Encoder } = {};
439
766
  let _type: string | undefined = undefined;
440
767
 
@@ -447,8 +774,8 @@ function createJsScalar(
447
774
  scalar,
448
775
 
449
776
  getEncoding(encoding: string, target: Scalar): Encoder | undefined {
450
- encoding = encoding.toLowerCase();
451
- let encodingSpec = scalarInfo.encodings?.[getFullyQualifiedTypeName(target)]?.[encoding];
777
+ const encodingTable = scalarInfo.encodings?.[getFullyQualifiedTypeName(target)];
778
+ let encodingSpec = encodingTable?.[encoding] ?? encodingTable?.[encoding.toLowerCase()];
452
779
 
453
780
  if (encodingSpec === undefined) {
454
781
  return undefined;
@@ -555,38 +882,39 @@ function createJsScalar(
555
882
  };
556
883
 
557
884
  return self;
558
- };
559
-
560
- /**
561
- * Helper to get the HTTP encoders for the scalar.
562
- */
563
- function getHttpEncoder(
564
- ctx: JsContext,
565
- module: Module,
566
- self: JsScalar,
567
- form: HttpOperationParameter["type"],
568
- ) {
569
- const [target, encoding] = scalarInfo.defaultEncodings?.http?.[form] ?? [
570
- "TypeSpec.string",
571
- "default",
572
- ];
573
-
574
- const [targetScalar, diagnostics] = program.resolveTypeReference(target);
575
-
576
- if (diagnostics.length > 0 || !targetScalar || targetScalar.kind !== "Scalar") {
577
- throw new UnreachableError(`Failed to resolve built-in scalar '${target}'`);
578
- }
579
-
580
- let encoder = self.getEncoding(encoding, targetScalar);
581
-
582
- if (encoder === undefined && scalarInfo.defaultEncodings?.http?.[form]) {
583
- 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;
584
916
  }
585
-
586
- encoder ??= getDefaultHttpStringEncoder(ctx, module, form);
587
-
588
- return encoder;
589
- }
917
+ };
590
918
  }
591
919
 
592
920
  /**
@@ -800,7 +1128,7 @@ export function getJsScalar(
800
1128
  scalar: Scalar,
801
1129
  diagnosticTarget: DiagnosticTarget | typeof NoTarget,
802
1130
  ): JsScalar {
803
- const scalars = getScalarStore(ctx.program);
1131
+ const scalars = getScalarStore(ctx, module);
804
1132
 
805
1133
  let _scalar: Scalar | undefined = scalar;
806
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)) {