Skip to content

Instantly share code, notes, and snippets.

@colecrouter
Last active April 27, 2025 03:02
Show Gist options
  • Save colecrouter/f2660efcfb073dc35f6b439692b0beee to your computer and use it in GitHub Desktop.
Save colecrouter/f2660efcfb073dc35f6b439692b0beee to your computer and use it in GitHub Desktop.
SvelteKit transport hook deduplication pattern
import { DeduplicationContext } from "./Context";
import { App } from "../models/App";
export class AppContext extends DeduplicationContext<App, ConstructorParameters<typeof App>> {
// These functions can be substituted with however you want to serialize/deserialize your classes.
// For this, I simply implemented a `serialize()` method that returns `ConstructorParameters<App>`.
// I have the same `serialize()` method for `User` as well.
protected decodeValue(encoded: ConstructorParameters<typeof App>): App {
return new App(...encoded);
}
protected encodeValue(value: App): ConstructorParameters<typeof App> {
return value.serialize(); // This func just
}
}
type Key = keyof any;
// Notice the `id` prop, as well as `id` as args[0].
// This could be something else; we just need a unique key to match the params to the class, and vice versa.
interface Encodable {
id: Key;
}
/**
* A generic abstract class for deduplication of objects during
* serialization.
*/
export abstract class DeduplicationContext<Class extends Encodable, Params extends [Key, ...unknown[]]> {
protected encodeCache = new WeakMap<Class, Params>();
protected decodeCache = new Map<Key, Class>();
/**
* Called by the transport when it wishes to encode an instance.
* Returns an array containing the unique serial number.
*/
public encode(value: Class): Params {
const existing = this.encodeCache.get(value);
if (existing) {
return existing;
}
const encoded = this.encodeValue(value);
this.encodeCache.set(value, encoded);
return encoded;
}
/**
* Called by the transport to decode an instance based on the id.
*/
public decode(encoded: Params): Class {
const existing = this.decodeCache.get(encoded[0]);
if (existing) {
return existing;
}
const decoded = this.decodeValue(encoded);
this.decodeCache.set(decoded.id, decoded);
return decoded;
}
/**
* Clear the caches. Call at the start of each request, if appropriate.
*/
public reset(): void {
this.encodeCache = new WeakMap();
this.decodeCache = new Map();
}
/**
* Convert the value to its serialized form.
* Must be implemented by subclasses.
*/
protected abstract encodeValue(value: Class): Params;
/**
* Convert the serialized form back into an instance.
* Must be implemented by subclasses.
*/
protected abstract decodeValue(encoded: Params): Class;
}
import { App } from "$lib/models/App";
import { AppContext } from "$lib/transports/AppContext";
import { User } from "$lib/models/User";
import type { Transport } from "@sveltejs/kit";
const appTransport = new AppContext();
export const transport: Transport = {
App: {
encode: (data) => data instanceof App && appTransport.encode(data),
decode: (data: ConstructorParameters<typeof App>) => appTransport.decode(data),
},
User: {
encode: (data) => data instanceof User && data.serialize(),
decode: (data: ReturnType<User["serialize"]>) => new User(...data),
},
// If you have any classes that extend `User`, make sure to put them above ^ or else they'll all get converted to `Users`!
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment