Skip to content

Commit 4d5a0c5

Browse files
authored
Merge pull request #7223 from QwikDev/custom-serialize
- custom serialization - adds eslint rules - renames signal classes to have ...Impl suffix so they don't clash with the abstract types
2 parents 8da72e2 + 54acbc0 commit 4d5a0c5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1892
-181
lines changed

.changeset/tricky-peaches-buy.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@qwik.dev/core': minor
3+
---
4+
5+
FEAT: `useSerializer$`, `createSerializer$`: Create a Signal holding a custom serializable value. See {@link useSerializer$} for more details.
6+
7+
FEAT: `NoSerializeSymbol`: objects that have this symbol will not be serialized.
8+
9+
FEAT: `SerializerSymbol`: When defined on an object, this function will get called with the object and is expected to returned a serializable object literal representing this object. Use this to remove data cached data, consolidate things, integrate with other libraries, etc.

packages/docs/src/repl/worker/app-bundle-client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const appBundleClient = async (
5555
const loc = warning.loc;
5656
if (loc && loc.file) {
5757
diagnostic.file = loc.file;
58-
diagnostic.highlights.push({
58+
diagnostic.highlights!.push({
5959
startCol: loc.column,
6060
endCol: loc.column + 1,
6161
startLine: loc.line,

packages/docs/src/repl/worker/app-bundle-ssr.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export const appBundleSsr = async (options: ReplInputOptions, result: ReplResult
4545
const loc = warning.loc;
4646
if (loc && loc.file) {
4747
diagnostic.file = loc.file;
48-
diagnostic.highlights.push({
48+
diagnostic.highlights!.push({
4949
startCol: loc.column,
5050
endCol: loc.column + 1,
5151
startLine: loc.line,

packages/docs/src/routes/api/qwik-optimizer/api.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
}
5858
],
5959
"kind": "Interface",
60-
"content": "```typescript\nexport interface Diagnostic \n```\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\n[category](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[DiagnosticCategory](#diagnosticcategory)\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[code](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring \\| null\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[file](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[highlights](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[SourceLocation](#sourcelocation)<!-- -->\\[\\]\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[message](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[scope](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[suggestions](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\\[\\] \\| null\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>",
60+
"content": "```typescript\nexport interface Diagnostic \n```\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\n[category](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[DiagnosticCategory](#diagnosticcategory)\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[code](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring \\| null\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[file](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[highlights](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[SourceLocation](#sourcelocation)<!-- -->\\[\\] \\| null\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[message](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[scope](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[suggestions](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\\[\\] \\| null\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>",
6161
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts",
6262
"mdFile": "core.diagnostic.md"
6363
},

packages/docs/src/routes/api/qwik-optimizer/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ string
216216

217217
</td><td>
218218

219-
[SourceLocation](#sourcelocation)[]
219+
[SourceLocation](#sourcelocation)[] \| null
220220

221221
</td><td>
222222

packages/docs/src/routes/api/qwik/api.json

+60-4
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@
390390
}
391391
],
392392
"kind": "Function",
393-
"content": "Create a computed signal which is calculated from the given QRL. A computed signal is a signal which is calculated from other signals. When the signals change, the computed signal is recalculated.\n\nThe QRL must be a function which returns the value of the signal. The function must not have side effects, and it mus be synchronous.\n\nIf you need the function to be async, use `useSignal` and `useTask$` instead.\n\n\n```typescript\ncreateComputed$: <T>(qrl: () => T) => T extends Promise<any> ? never : ComputedSignal<T>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nqrl\n\n\n</td><td>\n\n() =&gt; T\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n**Returns:**\n\nT extends Promise&lt;any&gt; ? never : [ComputedSignal](#computedsignal)<!-- -->&lt;T&gt;",
393+
"content": "Create a computed signal which is calculated from the given QRL. A computed signal is a signal which is calculated from other signals. When the signals change, the computed signal is recalculated.\n\nThe QRL must be a function which returns the value of the signal. The function must not have side effects, and it must be synchronous.\n\nIf you need the function to be async, use `useSignal` and `useTask$` instead.\n\n\n```typescript\ncreateComputed$: <T>(qrl: () => T) => T extends Promise<any> ? never : ComputedSignal<T>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nqrl\n\n\n</td><td>\n\n() =&gt; T\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n**Returns:**\n\nT extends Promise&lt;any&gt; ? never : [ComputedSignal](#computedsignal)<!-- -->&lt;T&gt;",
394394
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/signal/signal.public.ts",
395395
"mdFile": "core.createcomputed_.md"
396396
},
@@ -408,6 +408,20 @@
408408
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-context.ts",
409409
"mdFile": "core.createcontextid.md"
410410
},
411+
{
412+
"name": "createSerializer$",
413+
"id": "createserializer_",
414+
"hierarchy": [
415+
{
416+
"name": "createSerializer$",
417+
"id": "createserializer_"
418+
}
419+
],
420+
"kind": "Function",
421+
"content": "Create a signal that holds a custom serializable value. See [useSerializer$](#useserializer_) for more details.\n\n\n```typescript\ncreateSerializer$: <T, S>(arg: SerializerArg<T, S>) => T extends Promise<any> ? never : SerializerSignal<T>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\narg\n\n\n</td><td>\n\nSerializerArg&lt;T, S&gt;\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n**Returns:**\n\nT extends Promise&lt;any&gt; ? never : SerializerSignal&lt;T&gt;",
422+
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/signal/signal.public.ts",
423+
"mdFile": "core.createserializer_.md"
424+
},
411425
{
412426
"name": "createSignal",
413427
"id": "createsignal",
@@ -716,7 +730,7 @@
716730
}
717731
],
718732
"kind": "Function",
719-
"content": "```typescript\nisSignal: (value: any) => value is ISignal<unknown>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nvalue\n\n\n</td><td>\n\nany\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n**Returns:**\n\nvalue is [ISignal](#signal)<!-- -->&lt;unknown&gt;",
733+
"content": "```typescript\nisSignal: (value: any) => value is Signal<unknown>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nvalue\n\n\n</td><td>\n\nany\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n**Returns:**\n\nvalue is [Signal](#signal)<!-- -->&lt;unknown&gt;",
720734
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/signal/signal.ts",
721735
"mdFile": "core.issignal.md"
722736
},
@@ -1014,6 +1028,20 @@
10141028
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/utils/serialize-utils.ts",
10151029
"mdFile": "core.noserialize.md"
10161030
},
1031+
{
1032+
"name": "NoSerializeSymbol",
1033+
"id": "noserializesymbol",
1034+
"hierarchy": [
1035+
{
1036+
"name": "NoSerializeSymbol",
1037+
"id": "noserializesymbol"
1038+
}
1039+
],
1040+
"kind": "Variable",
1041+
"content": "If an object has this property, it will not be serialized. Use this on prototypes to avoid having to call `noSerialize()` on every object.\n\n\n```typescript\nNoSerializeSymbol: unique symbol\n```",
1042+
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/utils/serialize-utils.ts",
1043+
"mdFile": "core.noserializesymbol.md"
1044+
},
10171045
{
10181046
"name": "OnRenderFn",
10191047
"id": "onrenderfn",
@@ -1714,6 +1742,20 @@
17141742
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-resource.ts",
17151743
"mdFile": "core.resourcereturn.md"
17161744
},
1745+
{
1746+
"name": "SerializerSymbol",
1747+
"id": "serializersymbol",
1748+
"hierarchy": [
1749+
{
1750+
"name": "SerializerSymbol",
1751+
"id": "serializersymbol"
1752+
}
1753+
],
1754+
"kind": "Variable",
1755+
"content": "If an object has this property as a function, it will be called with the object and should return a serializable value.\n\nThis can be used to clean up, integrate with other libraries, etc.\n\nThe type your object should conform to is:\n\n```ts\n{\n [SerializerSymbol]: (this: YourType, toSerialize: YourType) => YourSerializableType;\n}\n```\n\n\n```typescript\nSerializerSymbol: unique symbol\n```",
1756+
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/utils/serialize-utils.ts",
1757+
"mdFile": "core.serializersymbol.md"
1758+
},
17171759
{
17181760
"name": "setPlatform",
17191761
"id": "setplatform",
@@ -2060,8 +2102,8 @@
20602102
}
20612103
],
20622104
"kind": "Function",
2063-
"content": "Creates a computed signal which is calculated from the given function. A computed signal is a signal which is calculated from other signals. When the signals change, the computed signal is recalculated, and if the result changed, all tasks which are tracking the signal will be re-run and all components that read the signal will be re-rendered.\n\nThe function must be synchronous and must not have any side effects.\n\n\n```typescript\nuseComputed$: <T>(qrl: import(\"./use-computed\").ComputedFn<T>) => T extends Promise<any> ? never : import(\"..\").ReadonlySignal<T>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nqrl\n\n\n</td><td>\n\nimport(\"./use-computed\").[ComputedFn](#computedfn)<!-- -->&lt;T&gt;\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n**Returns:**\n\nT extends Promise&lt;any&gt; ? never : import(\"..\").[ReadonlySignal](#readonlysignal)<!-- -->&lt;T&gt;",
2064-
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-computed-dollar.ts",
2105+
"content": "Creates a computed signal which is calculated from the given function. A computed signal is a signal which is calculated from other signals. When the signals change, the computed signal is recalculated, and if the result changed, all tasks which are tracking the signal will be re-run and all components that read the signal will be re-rendered.\n\nThe function must be synchronous and must not have any side effects.\n\n\n```typescript\nuseComputed$: <T>(qrl: ComputedFn<T>) => T extends Promise<any> ? never : ReadonlySignal<T>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nqrl\n\n\n</td><td>\n\n[ComputedFn](#computedfn)<!-- -->&lt;T&gt;\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n**Returns:**\n\nT extends Promise&lt;any&gt; ? never : [ReadonlySignal](#readonlysignal)<!-- -->&lt;T&gt;",
2106+
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-computed.ts",
20652107
"mdFile": "core.usecomputed_.md"
20662108
},
20672109
{
@@ -2190,6 +2232,20 @@
21902232
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-resource-dollar.ts",
21912233
"mdFile": "core.useresource_.md"
21922234
},
2235+
{
2236+
"name": "useSerializer$",
2237+
"id": "useserializer_",
2238+
"hierarchy": [
2239+
{
2240+
"name": "useSerializer$",
2241+
"id": "useserializer_"
2242+
}
2243+
],
2244+
"kind": "Variable",
2245+
"content": "Creates a signal which holds a custom serializable value. It requires that the value implements the `CustomSerializable` type, which means having a function under the `[SerializeSymbol]` property that returns a serializable value when called.\n\nThe `fn` you pass is called with the result of the serialization (in the browser, only when the value is needed), or `undefined` when not yet initialized. If you refer to other signals, `fn` will be called when those change just like computed signals, and then the argument will be the previous output, not the serialized result.\n\nThis is useful when using third party libraries that use custom objects that are not serializable.\n\nNote that the `fn` is called lazily, so it won't impact container resume.\n\n\n```typescript\nuseSerializer$: typeof createSerializer$\n```\n\n\n\n```tsx\nclass MyCustomSerializable {\n constructor(public n: number) {}\n inc() {\n this.n++;\n }\n}\nconst Cmp = component$(() => {\n const custom = useSerializer$({\n deserialize: (data) => new MyCustomSerializable(data),\n serialize: (data) => data.n,\n initial: 2,\n });\n return <div onClick$={() => custom.value.inc()}>{custom.value.n}</div>;\n});\n```\n\n\nWhen using a Signal as the data to create the object, you need to pass the configuration as a function, and you can then also provide the `update` function to update the object when the signal changes.\n\nBy returning an object from `update`<!-- -->, you signal that the listeners have to be notified. You can mutate the current object but you should return it so that it will trigger listeners.\n\n```tsx\nconst Cmp = component$(() => {\n const n = useSignal(2);\n const custom = useSerializer$(() =>\n ({\n deserialize: () => new MyCustomSerializable(n.value),\n update: (current) => {\n current.n = n.value;\n return current;\n }\n })\n );\n return <div onClick$={() => n.value++}>{custom.value.n}</div>;\n});\n```",
2246+
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-serializer.ts",
2247+
"mdFile": "core.useserializer_.md"
2248+
},
21932249
{
21942250
"name": "useServerData",
21952251
"id": "useserverdata",

0 commit comments

Comments
 (0)