Skip to content

Commit 4f5207b

Browse files
committed
chore: move serializer signal impl to separate file
1 parent e010959 commit 4f5207b

File tree

10 files changed

+153
-136
lines changed

10 files changed

+153
-136
lines changed

packages/qwik/src/core/shared/shared-serialization.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { type DomContainer } from '../client/dom-container';
88
import type { VNode } from '../client/types';
99
import { vnode_getNode, vnode_isVNode, vnode_locate, vnode_toString } from '../client/vnode';
1010
import { _EFFECT_BACK_REF, NEEDS_COMPUTATION } from '../signal/flags';
11-
import { SerializerSignalImpl, isSerializerObj, type SerializerArg } from '../signal/signal';
11+
import { isSerializerObj } from '../signal/signal';
12+
import type { SerializerArg } from '../signal/types';
1213
import {
1314
getOrCreateStore,
1415
getStoreHandler,
@@ -55,6 +56,7 @@ import { SubscriptionData, type NodePropData } from '../signal/subscription-data
5556
import { SignalImpl } from '../signal/impl/signal-impl';
5657
import { ComputedSignalImpl } from '../signal/impl/computed-signal-impl';
5758
import { WrappedSignalImpl } from '../signal/impl/wrapped-signal-impl';
59+
import { SerializerSignalImpl } from '../signal/impl/serializer-signal-impl';
5860

5961
const deserializedProxyMap = new WeakMap<object, unknown[]>();
6062

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Signals
2+
3+
Signals come in two types:
4+
5+
1. `Signal` - A storage of data
6+
2. `ComputedSignal` - A signal which is computed from other signals.
7+
8+
## Why is `ComputedSignal` different?
9+
10+
- It needs to store a function which needs to re-run.
11+
- It is `Readonly` because it is computed.

packages/qwik/src/core/signal/impl/computed-signal-impl.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1-
import type { ComputeQRL, EffectSubscription } from '../types';
2-
import { EffectProperty } from '../types';
3-
import type { BackRef } from '../signal-cleanup';
4-
import { SignalFlags } from '../types';
5-
import { SignalImpl } from './signal-impl';
6-
import { _EFFECT_BACK_REF, NEEDS_COMPUTATION } from '../flags';
1+
import { qwikDebugToString } from '../../debug';
2+
import { assertFalse } from '../../shared/error/assert';
3+
import { QError, qError } from '../../shared/error/error';
74
import type { Container } from '../../shared/types';
85
import { ChoreType } from '../../shared/util-chore-type';
9-
import { assertFalse } from '../../shared/error/assert';
10-
import { throwIfQRLNotResolved } from '../signal';
6+
import { isPromise } from '../../shared/utils/promises';
117
import { tryGetInvokeContext } from '../../use/use-core';
8+
import { _EFFECT_BACK_REF, NEEDS_COMPUTATION } from '../flags';
9+
import { throwIfQRLNotResolved } from '../signal';
10+
import type { BackRef } from '../signal-cleanup';
1211
import { getSubscriber } from '../subscriber';
13-
import { isPromise } from '../../shared/utils/promises';
14-
import { QError, qError } from '../../shared/error/error';
15-
import { qwikDebugToString } from '../../debug';
12+
import type { ComputeQRL, EffectSubscription } from '../types';
13+
import { EffectProperty, SignalFlags } from '../types';
14+
import { SignalImpl } from './signal-impl';
1615

1716
const DEBUG = false;
1817
// eslint-disable-next-line no-console
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { qwikDebugToString } from '../../debug';
2+
import type { QRLInternal } from '../../shared/qrl/qrl-class';
3+
import type { Container } from '../../shared/types';
4+
import { trackSignal } from '../../use/use-core';
5+
import { NEEDS_COMPUTATION } from '../flags';
6+
import { throwIfQRLNotResolved } from '../signal';
7+
import type { SerializerArg } from '../types';
8+
import { EffectProperty, SignalFlags, type ComputeQRL } from '../types';
9+
import { ComputedSignalImpl } from './computed-signal-impl';
10+
11+
const DEBUG = false;
12+
// eslint-disable-next-line no-console
13+
const log = (...args: any[]) => console.log('SERIALIZER SIGNAL', ...args.map(qwikDebugToString));
14+
15+
/**
16+
* A signal which provides a non-serializable value. It works like a computed signal, but it is
17+
* handled slightly differently during serdes.
18+
*
19+
* @public
20+
*/
21+
export class SerializerSignalImpl<T, S> extends ComputedSignalImpl<T> {
22+
constructor(container: Container | null, argQrl: QRLInternal<SerializerArg<T, S>>) {
23+
super(container, argQrl as unknown as ComputeQRL<T>);
24+
}
25+
$didInitialize$: boolean = false;
26+
27+
$computeIfNeeded$(): boolean {
28+
if (!(this.$flags$ & SignalFlags.INVALID)) {
29+
return false;
30+
}
31+
throwIfQRLNotResolved(this.$computeQrl$);
32+
let arg = (this.$computeQrl$ as any as QRLInternal<SerializerArg<T, S>>).resolved!;
33+
if (typeof arg === 'function') {
34+
arg = arg();
35+
}
36+
const { deserialize, initial } = arg;
37+
const update = (arg as any).update as ((current: T) => T) | undefined;
38+
const currentValue =
39+
this.$untrackedValue$ === NEEDS_COMPUTATION ? initial : this.$untrackedValue$;
40+
const untrackedValue = trackSignal(
41+
() =>
42+
this.$didInitialize$
43+
? update?.(currentValue as T)
44+
: deserialize(currentValue as Awaited<S>),
45+
this,
46+
EffectProperty.VNODE,
47+
this.$container$!
48+
);
49+
DEBUG && log('SerializerSignal.$compute$', untrackedValue);
50+
const didChange =
51+
(this.$didInitialize$ && untrackedValue !== 'undefined') ||
52+
untrackedValue !== this.$untrackedValue$;
53+
this.$flags$ &= ~SignalFlags.INVALID;
54+
this.$didInitialize$ = true;
55+
if (didChange) {
56+
this.$untrackedValue$ = untrackedValue as T;
57+
}
58+
return didChange;
59+
}
60+
}

packages/qwik/src/core/signal/impl/signal-impl.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
import { qError, QError } from '../../shared/error/error';
1+
import { isDev } from '@qwik.dev/core/build';
2+
import { pad, qwikDebugToString } from '../../debug';
23
import { assertTrue } from '../../shared/error/assert';
4+
import { qError, QError } from '../../shared/error/error';
35
import type { Container } from '../../shared/types';
4-
import { addQrlToSerializationCtx, triggerEffects } from '../signal';
5-
import { ensureContainsBackRef } from '../signal';
6+
import { qDev } from '../../shared/utils/qdev';
67
import { tryGetInvokeContext } from '../../use/use-core';
7-
import { ensureContainsSubscription } from '../signal';
8+
import {
9+
addQrlToSerializationCtx,
10+
ensureContainsBackRef,
11+
ensureContainsSubscription,
12+
triggerEffects,
13+
} from '../signal';
814
import type { Signal } from '../signal.public';
915
import { SignalFlags, type EffectSubscription } from '../types';
10-
import { pad, qwikDebugToString } from '../../debug';
11-
import { qDev } from '../../shared/utils/qdev';
12-
import { isDev } from '@qwik.dev/core/build';
1316

1417
const DEBUG = false;
1518
// eslint-disable-next-line no-console

packages/qwik/src/core/signal/signal-api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import type { QRLInternal } from '../shared/qrl/qrl-class';
22
import type { QRL } from '../shared/qrl/qrl.public';
33
import { SignalImpl } from './impl/signal-impl';
44
import { ComputedSignalImpl } from './impl/computed-signal-impl';
5-
import { SerializerSignalImpl, throwIfQRLNotResolved, type SerializerArg } from './signal';
5+
import { throwIfQRLNotResolved } from './signal';
66
import type { Signal } from './signal.public';
7+
import type { SerializerArg } from './types';
8+
import { SerializerSignalImpl } from './impl/serializer-signal-impl';
79

810
/** @internal */
911
export const createSignal = <T>(value?: T): Signal<T> => {

packages/qwik/src/core/signal/signal.public.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { implicit$FirstArg } from '../shared/qrl/implicit_dollar';
2-
import type { SerializerArg } from './signal';
2+
import type { SerializerArg } from './types';
33
import {
44
createSignal as _createSignal,
55
createComputedSignal as createComputedQrl,

packages/qwik/src/core/signal/signal.ts

Lines changed: 2 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,3 @@
1-
/**
2-
* @file
3-
*
4-
* Signals come in two types:
5-
*
6-
* 1. `Signal` - A storage of data
7-
* 2. `ComputedSignal` - A signal which is computed from other signals.
8-
*
9-
* ## Why is `ComputedSignal` different?
10-
*
11-
* - It needs to store a function which needs to re-run.
12-
* - It is `Readonly` because it is computed.
13-
*/
141
import { isDomContainer } from '../client/dom-container';
152
import { pad, qwikDebugToString } from '../debug';
163
import type { OnRenderFn } from '../shared/component.public';
@@ -23,9 +10,8 @@ import { ChoreType } from '../shared/util-chore-type';
2310
import { ELEMENT_PROPS, OnRenderProp } from '../shared/utils/markers';
2411
import { SerializerSymbol } from '../shared/utils/serialize-utils';
2512
import type { ISsrNode, SSRContainer } from '../ssr/ssr-types';
26-
import { trackSignal } from '../use/use-core';
2713
import { TaskFlags, isTask } from '../use/use-task';
28-
import { NEEDS_COMPUTATION, _EFFECT_BACK_REF } from './flags';
14+
import { _EFFECT_BACK_REF } from './flags';
2915
import { ComputedSignalImpl } from './impl/computed-signal-impl';
3016
import { SignalImpl } from './impl/signal-impl';
3117
import type { WrappedSignalImpl } from './impl/wrapped-signal-impl';
@@ -35,8 +21,7 @@ import { SubscriptionData, type NodePropPayload } from './subscription-data';
3521
import {
3622
EffectProperty,
3723
EffectSubscriptionProp,
38-
SignalFlags,
39-
type ComputeQRL,
24+
type CustomSerializable,
4025
type EffectSubscription,
4126
} from './types';
4227

@@ -159,103 +144,6 @@ export const triggerEffects = (
159144
DEBUG && log('done scheduling');
160145
};
161146

162-
/** @public */
163-
export type SerializerArgObject<T, S> = {
164-
/**
165-
* This will be called with initial or serialized data to reconstruct an object. If no
166-
* `initialData` is provided, it will be called with `undefined`.
167-
*
168-
* This must not return a Promise.
169-
*/
170-
deserialize: (data: Awaited<S>) => T;
171-
/** The initial value to use when deserializing. */
172-
initial?: S | undefined;
173-
/**
174-
* This will be called with the object to get the serialized data. You can return a Promise if you
175-
* need to do async work.
176-
*
177-
* The result may be anything that Qwik can serialize.
178-
*
179-
* If you do not provide it, the object will be serialized as `undefined`. However, if the object
180-
* has a `[SerializerSymbol]` property, that will be used as the serializer instead.
181-
*/
182-
serialize?: (obj: T) => S;
183-
};
184-
185-
/**
186-
* Serialize and deserialize custom objects.
187-
*
188-
* If you need to use scoped state, you can pass a function instead of an object. The function will
189-
* be called with the current value, and you can return a new value.
190-
*
191-
* @public
192-
*/
193-
export type SerializerArg<T, S> =
194-
| SerializerArgObject<T, S>
195-
| (() => SerializerArgObject<T, S> & {
196-
/**
197-
* This gets called when reactive state used during `deserialize` changes. You may mutate the
198-
* current object, or return a new object.
199-
*
200-
* If it returns a value, that will be used as the new value, and listeners will be triggered.
201-
* If no change happened, don't return anything.
202-
*
203-
* If you mutate the current object, you must return it so that it will trigger listeners.
204-
*/
205-
update?: (current: T) => T | void;
206-
});
207-
208-
/**
209-
* A signal which provides a non-serializable value. It works like a computed signal, but it is
210-
* handled slightly differently during serdes.
211-
*
212-
* @public
213-
*/
214-
export class SerializerSignalImpl<T, S> extends ComputedSignalImpl<T> {
215-
constructor(container: Container | null, argQrl: QRLInternal<SerializerArg<T, S>>) {
216-
super(container, argQrl as unknown as ComputeQRL<T>);
217-
}
218-
$didInitialize$: boolean = false;
219-
220-
$computeIfNeeded$(): boolean {
221-
if (!(this.$flags$ & SignalFlags.INVALID)) {
222-
return false;
223-
}
224-
throwIfQRLNotResolved(this.$computeQrl$);
225-
let arg = (this.$computeQrl$ as any as QRLInternal<SerializerArg<T, S>>).resolved!;
226-
if (typeof arg === 'function') {
227-
arg = arg();
228-
}
229-
const { deserialize, initial } = arg;
230-
const update = (arg as any).update as ((current: T) => T) | undefined;
231-
const currentValue =
232-
this.$untrackedValue$ === NEEDS_COMPUTATION ? initial : this.$untrackedValue$;
233-
const untrackedValue = trackSignal(
234-
() =>
235-
this.$didInitialize$
236-
? update?.(currentValue as T)
237-
: deserialize(currentValue as Awaited<S>),
238-
this,
239-
EffectProperty.VNODE,
240-
this.$container$!
241-
);
242-
DEBUG && log('SerializerSignal.$compute$', untrackedValue);
243-
const didChange =
244-
(this.$didInitialize$ && untrackedValue !== 'undefined') ||
245-
untrackedValue !== this.$untrackedValue$;
246-
this.$flags$ &= ~SignalFlags.INVALID;
247-
this.$didInitialize$ = true;
248-
if (didChange) {
249-
this.$untrackedValue$ = untrackedValue as T;
250-
}
251-
return didChange;
252-
}
253-
}
254-
255-
// TODO move to serializer
256-
export type CustomSerializable<T extends { [SerializerSymbol]: (obj: any) => any }, S> = {
257-
[SerializerSymbol]: (obj: T) => S;
258-
};
259147
/** @internal */
260148
export const isSerializerObj = <T extends { [SerializerSymbol]: (obj: any) => any }, S>(
261149
obj: unknown

packages/qwik/src/core/signal/types.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { ReadonlySignal } from './signal.public';
66
import type { TargetType } from './store';
77
import type { SignalImpl } from './impl/signal-impl';
88
import type { QRLInternal } from '../shared/qrl/qrl-class';
9+
import type { SerializerSymbol } from '../shared/utils/serialize-utils';
910

1011
export interface InternalReadonlySignal<T = unknown> extends ReadonlySignal<T> {
1112
readonly untrackedValue: T;
@@ -94,3 +95,53 @@ export const enum EffectProperty {
9495
COMPONENT = ':',
9596
VNODE = '.',
9697
}
98+
99+
/** @public */
100+
export type SerializerArgObject<T, S> = {
101+
/**
102+
* This will be called with initial or serialized data to reconstruct an object. If no
103+
* `initialData` is provided, it will be called with `undefined`.
104+
*
105+
* This must not return a Promise.
106+
*/
107+
deserialize: (data: Awaited<S>) => T;
108+
/** The initial value to use when deserializing. */
109+
initial?: S | undefined;
110+
/**
111+
* This will be called with the object to get the serialized data. You can return a Promise if you
112+
* need to do async work.
113+
*
114+
* The result may be anything that Qwik can serialize.
115+
*
116+
* If you do not provide it, the object will be serialized as `undefined`. However, if the object
117+
* has a `[SerializerSymbol]` property, that will be used as the serializer instead.
118+
*/
119+
serialize?: (obj: T) => S;
120+
};
121+
122+
/**
123+
* Serialize and deserialize custom objects.
124+
*
125+
* If you need to use scoped state, you can pass a function instead of an object. The function will
126+
* be called with the current value, and you can return a new value.
127+
*
128+
* @public
129+
*/
130+
export type SerializerArg<T, S> =
131+
| SerializerArgObject<T, S>
132+
| (() => SerializerArgObject<T, S> & {
133+
/**
134+
* This gets called when reactive state used during `deserialize` changes. You may mutate the
135+
* current object, or return a new object.
136+
*
137+
* If it returns a value, that will be used as the new value, and listeners will be triggered.
138+
* If no change happened, don't return anything.
139+
*
140+
* If you mutate the current object, you must return it so that it will trigger listeners.
141+
*/
142+
update?: (current: T) => T | void;
143+
});
144+
145+
export type CustomSerializable<T extends { [SerializerSymbol]: (obj: any) => any }, S> = {
146+
[SerializerSymbol]: (obj: T) => S;
147+
};

packages/qwik/src/core/use/use-serializer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { implicit$FirstArg } from '../shared/qrl/implicit_dollar';
22
import type { QRL } from '../shared/qrl/qrl.public';
33
import type { ComputedSignalImpl } from '../signal/impl/computed-signal-impl';
4-
import { SerializerSignalImpl, type SerializerArg } from '../signal/signal';
4+
import { SerializerSignalImpl } from '../signal/impl/serializer-signal-impl';
5+
import type { SerializerArg } from '../signal/types';
56
import type { createSerializer$ } from '../signal/signal.public';
67
import { useComputedCommon } from './use-computed';
78

0 commit comments

Comments
 (0)