@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.
- package/.testignore +0 -1
- package/README.md +6 -0
- package/build-helpers.ts +22 -9
- package/dist/generated-defs/helpers/index.d.ts.map +1 -1
- package/dist/generated-defs/helpers/index.js +1 -0
- package/dist/generated-defs/helpers/index.js.map +1 -1
- package/dist/generated-defs/helpers/temporal/index.d.ts +4 -0
- package/dist/generated-defs/helpers/temporal/index.d.ts.map +1 -0
- package/dist/generated-defs/helpers/temporal/index.js +19 -0
- package/dist/generated-defs/helpers/temporal/index.js.map +1 -0
- package/dist/generated-defs/helpers/temporal/native.d.ts +4 -0
- package/dist/generated-defs/helpers/temporal/native.d.ts.map +1 -0
- package/dist/generated-defs/helpers/temporal/native.js +125 -0
- package/dist/generated-defs/helpers/temporal/native.js.map +1 -0
- package/dist/generated-defs/helpers/temporal/polyfill.d.ts +4 -0
- package/dist/generated-defs/helpers/temporal/polyfill.d.ts.map +1 -0
- package/dist/generated-defs/helpers/temporal/polyfill.js +124 -0
- package/dist/generated-defs/helpers/temporal/polyfill.js.map +1 -0
- package/dist/generated-defs/package.json.d.ts.map +1 -1
- package/dist/generated-defs/package.json.js +1 -0
- package/dist/generated-defs/package.json.js.map +1 -1
- package/dist/src/common/declaration.js +1 -1
- package/dist/src/common/declaration.js.map +1 -1
- package/dist/src/common/scalar.d.ts +1 -1
- package/dist/src/common/scalar.d.ts.map +1 -1
- package/dist/src/common/scalar.js +341 -49
- package/dist/src/common/scalar.js.map +1 -1
- package/dist/src/common/serialization/index.d.ts +2 -2
- package/dist/src/common/serialization/index.d.ts.map +1 -1
- package/dist/src/common/serialization/index.js +3 -9
- package/dist/src/common/serialization/index.js.map +1 -1
- package/dist/src/common/serialization/json.d.ts +2 -0
- package/dist/src/common/serialization/json.d.ts.map +1 -1
- package/dist/src/common/serialization/json.js +10 -19
- package/dist/src/common/serialization/json.js.map +1 -1
- package/dist/src/helpers/temporal/native.d.ts +40 -0
- package/dist/src/helpers/temporal/native.d.ts.map +1 -0
- package/dist/src/helpers/temporal/native.js +81 -0
- package/dist/src/helpers/temporal/native.js.map +1 -0
- package/dist/src/helpers/temporal/polyfill.d.ts +41 -0
- package/dist/src/helpers/temporal/polyfill.d.ts.map +1 -0
- package/dist/src/helpers/temporal/polyfill.js +80 -0
- package/dist/src/helpers/temporal/polyfill.js.map +1 -0
- package/dist/src/http/server/index.d.ts.map +1 -1
- package/dist/src/http/server/index.js +4 -1
- package/dist/src/http/server/index.js.map +1 -1
- package/dist/src/lib.d.ts +9 -0
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/src/lib.js +7 -0
- package/dist/src/lib.js.map +1 -1
- package/dist/src/scripts/scaffold/data-mocks.d.ts.map +1 -1
- package/dist/src/scripts/scaffold/data-mocks.js +62 -8
- package/dist/src/scripts/scaffold/data-mocks.js.map +1 -1
- package/generated-defs/helpers/index.ts +1 -0
- package/generated-defs/helpers/temporal/index.ts +25 -0
- package/generated-defs/helpers/temporal/native.ts +132 -0
- package/generated-defs/helpers/temporal/polyfill.ts +131 -0
- package/generated-defs/package.json.ts +1 -0
- package/package.json +4 -3
- package/src/common/declaration.ts +1 -1
- package/src/common/scalar.ts +390 -62
- package/src/common/serialization/index.ts +4 -13
- package/src/common/serialization/json.ts +22 -25
- package/src/helpers/temporal/native.ts +104 -0
- package/src/helpers/temporal/polyfill.ts +103 -0
- package/src/http/server/index.ts +6 -1
- package/src/lib.ts +17 -0
- package/src/scripts/scaffold/data-mocks.ts +68 -8
- package/temp/tsconfig.tsbuildinfo +1 -1
- package/test/scalar.test.ts +547 -97
package/src/common/scalar.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
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.
|
|
445
|
+
"TypeSpec.unixTimestamp32",
|
|
333
446
|
{
|
|
334
|
-
type: "
|
|
447
|
+
type: "number",
|
|
335
448
|
encodings: {
|
|
336
449
|
"TypeSpec.string": {
|
|
337
450
|
default: {
|
|
338
451
|
encodeTemplate: "globalThis.String({})",
|
|
339
|
-
decodeTemplate: "globalThis.
|
|
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:
|
|
470
|
+
isJsonCompatible: true,
|
|
344
471
|
},
|
|
345
472
|
],
|
|
346
|
-
["TypeSpec.
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
451
|
-
let encodingSpec =
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
|
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,
|
|
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 |
|
|
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)) {
|