Created
January 12, 2021 21:48
-
-
Save ryanflorence/84114f38083a973e006787b9879886eb to your computer and use it in GitHub Desktop.
generic generics?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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? |
Yeah, @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;
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I used
Thing
instead ofRecord
becauseRecord
is a type already in TS:And that I think has the result you want: