Skip to content

Commit 87f842f

Browse files
authored
Merge pull request #8495 from FloezeTv/feature/temporal-serialization
feat(core): add Temporal serialization support
2 parents 5c018ff + c322abe commit 87f842f

File tree

7 files changed

+172
-0
lines changed

7 files changed

+172
-0
lines changed

.changeset/lazy-knives-write.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/core': minor
3+
---
4+
5+
feat: add `Temporal` serialization support

packages/qwik/src/core/shared/serdes/allocate.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,22 @@ export const allocate = (container: DeserializeContainer, typeId: number, value:
7878
return new URL(value as string);
7979
case TypeIds.Date:
8080
return new Date(value as number);
81+
case TypeIds.TemporalDuration:
82+
return Temporal.Duration.from(value as string);
83+
case TypeIds.TemporalInstant:
84+
return Temporal.Instant.from(value as string);
85+
case TypeIds.TemporalPlainDate:
86+
return Temporal.PlainDate.from(value as string);
87+
case TypeIds.TemporalPlainDateTime:
88+
return Temporal.PlainDateTime.from(value as string);
89+
case TypeIds.TemporalPlainMonthDay:
90+
return Temporal.PlainMonthDay.from(value as string);
91+
case TypeIds.TemporalPlainTime:
92+
return Temporal.PlainTime.from(value as string);
93+
case TypeIds.TemporalPlainYearMonth:
94+
return Temporal.PlainYearMonth.from(value as string);
95+
case TypeIds.TemporalZonedDateTime:
96+
return Temporal.ZonedDateTime.from(value as string);
8197
case TypeIds.Regex:
8298
const idx = (value as string).lastIndexOf('/');
8399
return new RegExp((value as string).slice(1, idx), (value as string).slice(idx + 1));

packages/qwik/src/core/shared/serdes/can-serialize.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { isSerializerObj } from '../../reactive-primitives/utils';
1818
const getKeyVal = <T>(value: T, key: keyof T) => value[key];
1919

2020
export const canSerialize = (value: unknown, seen: WeakSet<any> = new WeakSet()): boolean => {
21+
const hasTemporal = typeof Temporal !== 'undefined';
2122
if (
2223
value == null ||
2324
typeof value === 'string' ||
@@ -71,6 +72,22 @@ export const canSerialize = (value: unknown, seen: WeakSet<any> = new WeakSet())
7172
return true;
7273
} else if (value instanceof Date) {
7374
return true;
75+
} else if (hasTemporal && value instanceof Temporal.Duration) {
76+
return true;
77+
} else if (hasTemporal && value instanceof Temporal.Instant) {
78+
return true;
79+
} else if (hasTemporal && value instanceof Temporal.PlainDate) {
80+
return true;
81+
} else if (hasTemporal && value instanceof Temporal.PlainDateTime) {
82+
return true;
83+
} else if (hasTemporal && value instanceof Temporal.PlainMonthDay) {
84+
return true;
85+
} else if (hasTemporal && value instanceof Temporal.PlainTime) {
86+
return true;
87+
} else if (hasTemporal && value instanceof Temporal.PlainYearMonth) {
88+
return true;
89+
} else if (hasTemporal && value instanceof Temporal.ZonedDateTime) {
90+
return true;
7491
} else if (value instanceof RegExp) {
7592
return true;
7693
} else if (value instanceof URLSearchParams) {

packages/qwik/src/core/shared/serdes/constants.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ export const enum TypeIds {
9292
BigInt,
9393
URLSearchParams,
9494
ForwardRefs,
95+
TemporalDuration,
96+
TemporalInstant,
97+
TemporalPlainDate,
98+
TemporalPlainDateTime,
99+
TemporalPlainMonthDay,
100+
TemporalPlainTime,
101+
TemporalPlainYearMonth,
102+
TemporalZonedDateTime,
95103
/// All types below will be inflate()d
96104
Error,
97105
Promise,
@@ -130,6 +138,14 @@ export const _typeIdNames = [
130138
'BigInt',
131139
'URLSearchParams',
132140
'ForwardRefs',
141+
'TemporalDuration',
142+
'TemporalInstant',
143+
'TemporalPlainDate',
144+
'TemporalPlainDateTime',
145+
'TemporalPlainMonthDay',
146+
'TemporalPlainTime',
147+
'TemporalPlainYearMonth',
148+
'TemporalZonedDateTime',
133149
'Error',
134150
'Promise',
135151
'Set',

packages/qwik/src/core/shared/serdes/serdes.unit.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,63 @@ describe('shared-serialization', () => {
186186
(6 chars)"
187187
`);
188188
});
189+
it.skipIf(typeof Temporal === 'undefined')(title(TypeIds.TemporalDuration), async () => {
190+
expect(await dump(Temporal.Duration.from('PT194972H22M2.783S'))).toMatchInlineSnapshot(`
191+
"
192+
0 TemporalDuration "PT194972H22M2.783S"
193+
(25 chars)"
194+
`);
195+
});
196+
it.skipIf(typeof Temporal === 'undefined')(title(TypeIds.TemporalInstant), async () => {
197+
expect(await dump(Temporal.Instant.from('2003-12-29T00:00:00Z'))).toMatchInlineSnapshot(`
198+
"
199+
0 TemporalInstant "2003-12-29T00:00:00Z"
200+
(27 chars)"
201+
`);
202+
});
203+
it.skipIf(typeof Temporal === 'undefined')(title(TypeIds.TemporalPlainDate), async () => {
204+
expect(await dump(Temporal.PlainDate.from('2003-12-29'))).toMatchInlineSnapshot(`
205+
"
206+
0 TemporalPlainDate "2003-12-29"
207+
(17 chars)"
208+
`);
209+
});
210+
it.skipIf(typeof Temporal === 'undefined')(title(TypeIds.TemporalPlainDateTime), async () => {
211+
expect(await dump(Temporal.PlainDateTime.from('2003-12-29T04:20:00'))).toMatchInlineSnapshot(`
212+
"
213+
0 TemporalPlainDateTime "2003-12-29T04:20:00"
214+
(26 chars)"
215+
`);
216+
});
217+
it.skipIf(typeof Temporal === 'undefined')(title(TypeIds.TemporalPlainMonthDay), async () => {
218+
expect(await dump(Temporal.PlainMonthDay.from('12-29'))).toMatchInlineSnapshot(`
219+
"
220+
0 TemporalPlainMonthDay "12-29"
221+
(12 chars)"
222+
`);
223+
});
224+
it.skipIf(typeof Temporal === 'undefined')(title(TypeIds.TemporalPlainTime), async () => {
225+
expect(await dump(Temporal.PlainTime.from('04:20:00'))).toMatchInlineSnapshot(`
226+
"
227+
0 TemporalPlainTime "04:20:00"
228+
(15 chars)"
229+
`);
230+
});
231+
it.skipIf(typeof Temporal === 'undefined')(title(TypeIds.TemporalPlainYearMonth), async () => {
232+
expect(await dump(Temporal.PlainYearMonth.from('2003-12'))).toMatchInlineSnapshot(`
233+
"
234+
0 TemporalPlainYearMonth "2003-12"
235+
(14 chars)"
236+
`);
237+
});
238+
it.skipIf(typeof Temporal === 'undefined')(title(TypeIds.TemporalZonedDateTime), async () => {
239+
expect(await dump(Temporal.ZonedDateTime.from('2003-12-29T04:20:00+01:00[Europe/Berlin]')))
240+
.toMatchInlineSnapshot(`
241+
"
242+
0 TemporalZonedDateTime "2003-12-29T04:20:00+01:00[Europe/Berlin]"
243+
(47 chars)"
244+
`);
245+
});
189246
it(title(TypeIds.Regex), async () => {
190247
expect(await dump(/abc/gm)).toMatchInlineSnapshot(`
191248
"
@@ -995,6 +1052,32 @@ describe('shared-serialization', () => {
9951052
expect(date).toBeInstanceOf(Date);
9961053
expect(date.toISOString()).toBe('2009-02-13T23:31:30.000Z');
9971054
});
1055+
const testTemporal = (id: TypeIds, T_: () => typeof __TemporalStub<unknown>, value: string) => {
1056+
it.skipIf(typeof Temporal === 'undefined')(title(id), async () => {
1057+
const T = T_();
1058+
const original = T.from(value);
1059+
const objs = await serialize(original);
1060+
const deserialized = deserialize(objs)[0];
1061+
expect(deserialized).toBeInstanceOf(T);
1062+
expect(original).toEqual(deserialized);
1063+
});
1064+
};
1065+
testTemporal(TypeIds.TemporalDuration, () => Temporal.Duration, 'PT194972H22M2.783S');
1066+
testTemporal(TypeIds.TemporalInstant, () => Temporal.Instant, '2003-12-29T00:00:00Z');
1067+
testTemporal(TypeIds.TemporalPlainDate, () => Temporal.PlainDate, '2003-12-29');
1068+
testTemporal(
1069+
TypeIds.TemporalPlainDateTime,
1070+
() => Temporal.PlainDateTime,
1071+
'2003-12-29T04:20:00'
1072+
);
1073+
testTemporal(TypeIds.TemporalPlainMonthDay, () => Temporal.PlainMonthDay, '12-29');
1074+
testTemporal(TypeIds.TemporalPlainTime, () => Temporal.PlainTime, '04:20:00');
1075+
testTemporal(TypeIds.TemporalPlainYearMonth, () => Temporal.PlainYearMonth, '2003-12');
1076+
testTemporal(
1077+
TypeIds.TemporalZonedDateTime,
1078+
() => Temporal.ZonedDateTime,
1079+
'2003-12-29T04:20:00+01:00[Europe/Berlin]'
1080+
);
9981081
it(title(TypeIds.Regex), async () => {
9991082
const objs = await serialize(/abc/gm);
10001083
const regex = deserialize(objs)[0] as RegExp;

packages/qwik/src/core/shared/serdes/serialize.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ export class Serializer {
6767
private $parent$: SeenRef | undefined;
6868
private $qrlMap$ = new Map<string, QRLInternal>();
6969
private $writer$: StreamWriter;
70+
/** We need to determine this at runtime because polyfills may not be loaded a module load time */
71+
private $hasTemporal$ = typeof Temporal !== 'undefined';
7072

7173
constructor(public $serializationContext$: SerializationContext) {
7274
this.$writer$ = $serializationContext$.$writer$;
@@ -510,6 +512,22 @@ export class Serializer {
510512
this.output(TypeIds.URL, value.href);
511513
} else if (value instanceof Date) {
512514
this.output(TypeIds.Date, Number.isNaN(value.valueOf()) ? '' : value.valueOf());
515+
} else if (this.$hasTemporal$ && value instanceof Temporal.Duration) {
516+
this.output(TypeIds.TemporalDuration, value.toJSON());
517+
} else if (this.$hasTemporal$ && value instanceof Temporal.Instant) {
518+
this.output(TypeIds.TemporalInstant, value.toJSON());
519+
} else if (this.$hasTemporal$ && value instanceof Temporal.PlainDate) {
520+
this.output(TypeIds.TemporalPlainDate, value.toJSON());
521+
} else if (this.$hasTemporal$ && value instanceof Temporal.PlainDateTime) {
522+
this.output(TypeIds.TemporalPlainDateTime, value.toJSON());
523+
} else if (this.$hasTemporal$ && value instanceof Temporal.PlainMonthDay) {
524+
this.output(TypeIds.TemporalPlainMonthDay, value.toJSON());
525+
} else if (this.$hasTemporal$ && value instanceof Temporal.PlainTime) {
526+
this.output(TypeIds.TemporalPlainTime, value.toJSON());
527+
} else if (this.$hasTemporal$ && value instanceof Temporal.PlainYearMonth) {
528+
this.output(TypeIds.TemporalPlainYearMonth, value.toJSON());
529+
} else if (this.$hasTemporal$ && value instanceof Temporal.ZonedDateTime) {
530+
this.output(TypeIds.TemporalZonedDateTime, value.toJSON());
513531
} else if (value instanceof RegExp) {
514532
this.output(TypeIds.Regex, value.toString());
515533
} else if (value instanceof Error) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/** Minimal type-stub for the types in `Temporal`. Contains methods required for (de-)serializing. */
2+
declare class __TemporalStub<T> {
3+
static from(item: string): T;
4+
toJSON(): string;
5+
}
6+
7+
/** Type-Stub for the types in `Temporal` */
8+
declare namespace Temporal {
9+
class Duration extends __TemporalStub<Duration> {}
10+
class Instant extends __TemporalStub<Instant> {}
11+
class PlainDate extends __TemporalStub<PlainDate> {}
12+
class PlainDateTime extends __TemporalStub<PlainDateTime> {}
13+
class PlainMonthDay extends __TemporalStub<PlainMonthDay> {}
14+
class PlainTime extends __TemporalStub<PlainTime> {}
15+
class PlainYearMonth extends __TemporalStub<PlainTime> {}
16+
class ZonedDateTime extends __TemporalStub<ZonedDateTime> {}
17+
}

0 commit comments

Comments
 (0)