Skip to content

Commit

Permalink
Improve version typing (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcospassos authored Feb 6, 2023
1 parent 58c78a1 commit 26e13fb
Show file tree
Hide file tree
Showing 5 changed files with 22 additions and 36 deletions.
2 changes: 1 addition & 1 deletion src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface VersionedComponentMap extends LatestComponentVersionMap {

export type ComponentId = keyof VersionedComponentMap extends never ? string : keyof VersionedComponentMap;

export type ComponentVersion<I extends ComponentId = ComponentId> = Version<VersionedComponentMap, I>;
export type ComponentVersion<I extends ComponentId> = Version<VersionedComponentMap, I>;

export type ComponentVersionId<I extends ComponentId = ComponentId> = CanonicalVersionId<I, VersionedComponentMap>;

Expand Down
2 changes: 1 addition & 1 deletion src/slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export type DynamicSlotId = any;

export type SlotId = keyof VersionedSlotMap extends never ? string : keyof VersionedSlotMap;

export type SlotVersion<I extends SlotId = SlotId> = Version<VersionedSlotMap, I>;
export type SlotVersion<I extends SlotId> = Version<VersionedSlotMap, I>;

export type SlotVersionId<I extends SlotId = SlotId> = CanonicalVersionId<I, VersionedSlotMap>;

Expand Down
27 changes: 18 additions & 9 deletions src/versioning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ import {JsonObject} from '@croct/json';

export type LatestAlias = 'latest';

export type ExtractVersion<I extends string> = I extends `${string}@${infer V}`
? (LatestAlias extends V ? LatestAlias : (V extends `${number}` ? V : never))
: LatestAlias;

export type CanonicalVersionId<I extends string, M> = {
[K in I]: `${K}@${Extract<Version<M, K>, `${number}`>}`
}[I];
Expand All @@ -17,13 +13,26 @@ type CastString<T extends string> = T extends `${infer V}` ? V : string;

export type VersionedId<I extends string, M> = CastString<I> | {[K in I]: `${K}@${Version<M, K> & string}`}[I];

export type ExtractId<I extends string> = I extends `${infer V}@${string}` ? V : CastString<I>;

export type Version<M, I extends string> = LatestAlias | (I extends keyof M ? keyof M[I] : never);

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;

type Expand<T> = {[K in keyof T]: T[K]};

type FlattenVersionMap<T> = Expand<
UnionToIntersection<
{
[K in Extract<keyof T, string>]: {
[K2 in Extract<keyof T[K], string> as (`${K}@${K2}` | (LatestAlias extends K2 ? K : never))]: T[K][K2];
}
}[Extract<keyof T, string>]
>
>;

export type Versioned<I extends string, M, C extends JsonObject = JsonObject> =
ExtractId<I> extends keyof M
? ExtractVersion<I> extends keyof M[ExtractId<I>]
? M[ExtractId<I>][ExtractVersion<I>]
// Ensure T is string
I extends `${infer K}`
? K extends keyof FlattenVersionMap<M>
? FlattenVersionMap<M>[K]
: C
: C;
14 changes: 1 addition & 13 deletions test/component.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,23 +132,11 @@ describe('Component typing', () => {
);
});

it('should export a ComponentVersion type that resolves to "latest" when no component mapping exists', () => {
const code: CodeOptions = {
imports: ['ComponentVersion'],
mapping: false,
type: 'ComponentVersion',
};

expect(() => compileCode(code)).not.toThrow();

expect(getTypeName(code)).toBe('"latest"');
});

it('should export a ComponentVersion that resolves to a union of all component versions', () => {
const code: CodeOptions = {
imports: ['ComponentVersion'],
mapping: true,
type: 'ComponentVersion',
type: 'ComponentVersion<"banner">',
};

expect(() => compileCode(code)).not.toThrow();
Expand Down
13 changes: 1 addition & 12 deletions test/slot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,24 +178,13 @@ describe('Slot typing', () => {
);
});

it('should export a SlotVersion type that resolves to "latest" when no slot mapping exists', () => {
const code: CodeOptions = {
imports: ['SlotVersion'],
type: 'SlotVersion',
};

expect(() => compileCode(code)).not.toThrow();

expect(getTypeName(code)).toBe('"latest"');
});

it('should export a SlotVersion that resolves to a union of all slot versions', () => {
const code: CodeOptions = {
imports: ['SlotVersion'],
mapping: {
slot: true,
},
type: 'SlotVersion',
type: 'SlotVersion<"home-banner">',
};

expect(() => compileCode(code)).not.toThrow();
Expand Down

0 comments on commit 26e13fb

Please sign in to comment.