-
-
Save ryanflorence/84114f38083a973e006787b9879886eb to your computer and use it in GitHub Desktop.
| // My DB has multiple collections (tables), they can extend | |
| // this since the only difference is the data an collection, | |
| // they're passed in as generics | |
| export interface Record<TData, TCollection> { | |
| data: TData; | |
| ref: { | |
| collection: { | |
| id: TCollection; | |
| }; | |
| id: string; | |
| ts: number; | |
| }; | |
| } | |
| // for example, here are posts | |
| interface PostRecord | |
| extends Record< | |
| { | |
| title: string; | |
| published: boolean; | |
| body: string; | |
| }, | |
| "posts" | |
| > {} | |
| // and sessions | |
| interface SessionRecord | |
| extends Record< | |
| { | |
| id: string; | |
| values: { [key: string]: string }; | |
| }, | |
| "sessions" | |
| > {} | |
| // how do I tell this function it's getting a generic Record? | |
| export function serializeRecord< | |
| TRecord extends Record<???, ???> | |
| >(record: TRecord): TRecord { | |
| let { data, ref } = record; | |
| return { | |
| data, | |
| ref: { | |
| id: ref.id, | |
| ts: ref.ts, | |
| collection: { | |
| id: ref.collection.id, | |
| }, | |
| }, | |
| }; | |
| } | |
| // is there some way to infer or access `TRecord`'s generic TData and TCollection? |
Your serializeRecord function could define two generic type parameters itself, TData and TCollection, and pass those through to the Record type:
export function serializeRecord<TData, TCollection>(
record: Record<TData, TCollection>
): Record<TData, TCollection> {
let { data, ref } = record;
return {
data,
ref: {
id: ref.id,
ts: ref.ts,
collection: {
id: ref.collection.id,
},
},
};
}@mariusschulz That's what I wanted to avoid. I guess generics don't compose at all.
@ryanflorence I'm not sure I follow. Could you elaborate in which ways composition is limited here?
I used Thing instead of Record because Record is a type already in TS:
export interface Thing<Data = unknown, CollectionId = unknown> {
data: Data;
ref: {
collection: {
id: CollectionId;
};
id: string;
ts: number;
};
}
export const serializeRecord = <TRecord extends Thing>({
data,
ref
}: TRecord) =>
({
data,
ref: {
id: ref.id,
ts: ref.ts,
collection: {
id: ref.collection.id
}
}
} as TRecord);And that I think has the result you want:
type Post = Thing<
{
title: string;
published: boolean;
body: string;
},
"posts"
>;
let post: Post;
const test = serializeRecord(post); // test is of type PostYeah, @mariusschulz has nailed it
Would assigning a default value to the generics work here? Something like:
export interface Record<TData = unknown, TCollection = unknown> {
data: TData;
ref: {
collection: {
id: TCollection;
};
id: string;
ts: number;
};
}Which lets you write the function as:
export function serializeRecord<
TRecord extends Record
>(record: TRecord): TRecord {
let { data, ref } = record;
return {
data,
ref: {
id: ref.id,
ts: ref.ts,
collection: {
id: ref.collection.id,
},
},
};
}Check this out - it's a bit wild (it needs one cast, but it may be what you want if you care more about the consumer of that code)
export interface Envelope<TData = unknown, TCollection = unknown> {
data: TData;
ref: {
collection: {
id: TCollection;
};
id: string;
ts: number;
};
}
interface SessionRecord
extends Envelope<
{
id: string;
values: { [key: string]: string };
},
"sessions"
> {}
type Inferrer<X> = X extends Envelope<infer T1, infer T2> ? Envelope<T1,T2> : never;
export function serializeRecord<
TRecord extends Envelope
>(record: TRecord): Inferrer<TRecord> {
let { data, ref } = record;
return {
data,
ref: {
id: ref.id,
ts: ref.ts,
collection: {
id: ref.collection.id,
},
},
} as Inferrer<TRecord>;
}
const output = serializeRecord<SessionRecord>("foo" as any);The Inferrer helper will squeeze your 2 type arguments back into the return of the function without you specifying it (at least in my VS Code session :D)
I went with:
interface GenericRecord extends IRecord<any, string> {}
export function serializeRecord<TRecord>(record: GenericRecord): TRecord {
let { data, ref } = record;
return ({
data,
ref: {
id: ref.id,
ts: ref.ts,
collection: {
id: ref.collection.id,
},
},
} as unknown) as TRecord;
}Ya'll have given me a lot read up on! Thank you :)
Why not:
export function serializeRecord<
T extends Record<any, string>
>(record: T): T {
let { data, ref } = record;
return {
data,
ref: {
id: ref.id,
ts: ref.ts,
collection: {
id: ref.collection.id,
},
},
} as T;
}
Ok for one, there's already a inbuilt 'Record' type in Typescript for hash-style object collections, so that might screw you over.
More answers in coming