@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.
- package/.testignore +0 -2
- package/README.md +6 -0
- package/build-helpers.ts +57 -12
- 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 +2 -0
- package/dist/generated-defs/package.json.d.ts.map +1 -0
- package/dist/generated-defs/package.json.js +37 -0
- package/dist/generated-defs/package.json.js.map +1 -0
- 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 +373 -48
- 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/bin.d.mts.map +1 -1
- package/dist/src/scripts/scaffold/bin.mjs +91 -53
- package/dist/src/scripts/scaffold/bin.mjs.map +1 -1
- package/dist/src/scripts/scaffold/data-mocks.d.ts.map +1 -1
- package/dist/src/scripts/scaffold/data-mocks.js +74 -15
- 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 +38 -0
- package/package.json +11 -3
- package/src/common/declaration.ts +1 -1
- package/src/common/scalar.ts +430 -61
- 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/bin.mts +106 -53
- package/src/scripts/scaffold/data-mocks.ts +81 -16
- 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,11 +143,112 @@ 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({}))",
|
|
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
|
|
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.
|
|
445
|
+
"TypeSpec.unixTimestamp32",
|
|
292
446
|
{
|
|
293
|
-
type: "
|
|
447
|
+
type: "number",
|
|
294
448
|
encodings: {
|
|
295
449
|
"TypeSpec.string": {
|
|
296
450
|
default: {
|
|
297
451
|
encodeTemplate: "globalThis.String({})",
|
|
298
|
-
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({})",
|
|
299
467
|
},
|
|
300
468
|
},
|
|
301
469
|
},
|
|
302
|
-
isJsonCompatible:
|
|
470
|
+
isJsonCompatible: true,
|
|
303
471
|
},
|
|
304
472
|
],
|
|
305
|
-
["TypeSpec.
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
410
|
-
let encodingSpec =
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
|
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,
|
|
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)) {
|