-
-
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? |
@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 Post
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;
}
Your
serializeRecord
function could define two generic type parameters itself,TData
andTCollection
, and pass those through to theRecord
type: