-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: useSerialized$/createSerialized$ + SerializeSymbol #204
Comments
I think this is a great addition to Qwik. On a devX point of view I find it a little difficult to understand because serialize & deserialize don't have the same mecanism (serialize -> SerializeSymbol / deserialize -> useSerialized$). const custom = useSerialized(new MyCustomClass(), {
serialize: $((instance) => instance.toJSON()),
deserialize: $((json) => MyCustomClass.fromJSON(json))
}); Also scoping it into a import { MemoryDB } from 'third-party';
export const db = serialize(new MemoryDB(), {
serialize: (instance) => instance.getState(),
deserialize: (state) => new MemoryDB(state)
});
// Create API
export const addFoo = $(async (foo: Foo) => {
await db.store('foo').add(foo);
// Do additionaly logic here
}) |
The first interface can work (the serializer doesn't need to be qrl but can be). I think I'd still like to keep the symbol interface for cleanup before serialization etc. For the |
Oh but the creation needs to be a function and the deserializer does the same, so it could be like const createSerialized$((data) => new MyCustomClass(data), (obj) => obj.toJSON()) , with the serializer optional. EDIT: actually this would mean keeping a a WeakMap for the serialization functions, instead of the single prototype object with the serializer. So I'm not a fan. |
I'm not sure to understand how the import { MemoryDB } from 'third-party';
// Create global instance that I can access in both server & client
export const db = ... ;
// Use instance in either server or client
export const addFoo = $(async (foo: Foo) => {
// db.add(...)
}) |
That won't work, the db connection won't be initialized in the client. addFoo would be a method of an object you create, and you'd use it like signal.value.addFoo, and it would create its db connection if needed. |
Let's say class MemoryDB {
state = {};
add(store, value) {
const id = randomUUID();
this.state[store] ||= {};
this.state[store][id] = value;
}
get(store, id) {
return this.state[store][id];
}
...
} How can I use |
@GrandSchtroumpf something like class MemoryDB {
constructor(public state = {}) {}
add(store, value) {
const id = randomUUID();
this.state[store] ||= {};
this.state[store][id] = value;
}
get(store, id) {
return this.state[store][id];
}
[SerializeSymbol]() { return this.state }
...
}
const dbSig = createSerialized$((data) => new MemoryDB(data)) |
In a previous comment you mentioned we can use
How can I use it ? Like that ? import { MemoryDB } from 'third-party';
// Create global instance that I can access in both server & client
export const dbSig = createSerialized$((data) => {
const instance = new MemoryDB(data);
instance[SerializeSymbol] = () => instance.state;
return instance
});
// Use instance in either server or client
export const addFoo = $((foo: Foo) => {
dbSig.value.add('foo', foo);
}) |
Yes correct, I edited the example, I used |
The use case of useSerialized$ is especially valuable for integrating vanilla JS libraries with Qwik. The current challenge with libraries like TanStack Table is that they have a vanilla JS core that creates complex objects with methods. These objects often become non-serializable in Qwik's resumable architecture, leading to runtime errors when trying to pause/resume the application state. (it does not recover the right info, at least in v1) For example, with TanStack Table, they create table instances with methods like: const table = createTable({
data: [],
columns: []
})
// Methods on the table object can cause serialization issues
table.getRowModel()
table.getSortedRowModel() The proposed useSerialized$ would let us properly serialize/deserialize these instances by:
const tableSignal = useSerialized$((data) => {
const table = createTable(data)
table[SerializeSymbol] = () => table.getState() // Serialize core state
return table
}) This would be a huge improvement over current workarounds like:
|
// existing propsal
const custom = useSerialized$(new MyCustomClass(), {
serialize: $((instance) => instance.toJSON()),
deserialize: $((json) => MyCustomClass.fromJSON(json))
});
// mhevery proposal
class MyClass {
static serialize: ((instance) => instance.toJSON())
static deserialize: ((json) => MyCustomClass.fromJSON(json))
}
const custom = useSerialized$(MyClass, optionalInitialSerializableValue);
// non class syntax
const custom = useSerialized$({
serialize: ((instance) => instance.toJSON()),
deserialize: ((json) => MyCustomClass.fromJSON(json))
}, optionalInitialSerializableValue);
I think the above syntax is cleaner and does not require that the object has to be a class. |
@mhevery ok, I like it, how about this: type SerializationOptions<T, S> = {
// No need for `serialize` in the browser
serialize?: false | (obj: T) => S | Promise<S>,
// If you rerun due to scope capture, the previous will be in the second argument
deserialize: (...args: [data: S | undefined, previous: undefined] | [data: undefined, previous: T]) => T
// put the initial data inside the segment too
initialData?: S | () => S
})
declare const createSerialized$: (opts: SerializationOptions) => Signal<T>
const mySig = createSerialized$({
serialize: isServer && o => o.toJson(),
deserialize: d => new MyObj(d)
})
// This will optimize to:
const mySig = createSerializedQrl(qrl(() => import('./opts.js'), 'opts'))
// ...and ./opts.js in client:
export default opts = {
serialize: false,
deserialize: d => new MyObj(d)
} We could even special-case it in the optimizer to strip the The So, 1), is this API ok? That said, I'd still like to keep the |
Champion
@wmertens
What's the motivation for this proposal?
Problems you are trying to solve:
Proposed Solution / Feature
What do you propose?
SerializerSymbol
, you can attach to an object that will serialize the objectCode examples
In detail
[SerializeSymbol]: (obj) => serialize(obj)
to any object that needs custom serializationuseSerialized$(constructorFn)
takes a function that will be executed on the server as well as the browser to create the object, and it returns a SignalComputedSignal
, except that it will always be marked to-calculate in the browser, and it passes the serialized value to the compute functionPRs/ Links / References
QwikDev/qwik#7223
Proposed changes
useSerialized$(deser, [ser])
call. This would mean using a WeakMap likenoSerialize()
does, but it is better DX.The text was updated successfully, but these errors were encountered: