gen-jhipster
Version:
Spring Boot + Angular/React/Vue in one handy generator
82 lines (67 loc) • 3.42 kB
TypeScript
import type { StringKeyOf, UnionToIntersection } from 'type-fest';
// Values<{ a: string, b: number }> = string | number
type Values<T> = T[keyof T];
// ArrayKeys<[string, string, string]> = 0 | 1 | 2
type ArrayKeys<T extends string[]> = Exclude<keyof T, keyof []>;
// ArrayToUnion<['foo', 'bar']> = 'foo' | 'bar
type ArrayToUnion<T extends string[]> = T[number];
type NoUndefined<T extends string | undefined> = T extends undefined ? never : T;
// BoxArrayByName<N, [1, 2]> = { 0: { [N]: 1 }, 1: { [N]: 2 } }
type DeterministicBoxArrayByName<N extends string, T extends string[], A extends any[]> = {
[K in ArrayKeys<T>]: Record<N, T[K] | undefined> &
(T[K] extends string ? Record<`${N}${Capitalize<T[K]>}`, true> : Record<string, never>) &
Record<`${N}${Capitalize<Exclude<ArrayToUnion<T>, T[K]>>}`, false> &
(K extends keyof A ? A[K] : Record<string, never>);
};
/*
* OptionWithDerivedProperties<'aProperty', ['foo', 'bar']> = { aProperty: 'foo'; aPropertyFoo: true; aPropertyBar: false; } | { aProperty: 'bar'; aPropertyFoo: false; aPropertyBar: true; }
* Deterministic derived options causes too much union and cannot be used as it is for the entire application.
* Can be used selectively
*/
export type DeterministicOptionWithDerivedProperties<N extends string, T extends string[], A extends any[] = []> = Values<
DeterministicBoxArrayByName<N, T, A>
>;
// OptionWithDerivedProperties<'aProperty', ['foo', 'bar']> = { aProperty: 'foo' | 'bar'; aPropertyFoo: boolean; aPropertyBar: boolean; }
export type OptionWithDerivedProperties<N extends string, T extends string[]> = Record<N, Exclude<ArrayToUnion<T>, 'any'> | undefined> &
Record<`${N}${Capitalize<ArrayToUnion<T>>}`, boolean> &
('no' extends ArrayToUnion<T> ? Record<`${N}Any`, boolean> : Record<string, never>);
// KeyToArray<{ foo: 1, foo2: 2 }> = [{foo: 1}, {foo2: 2}]
// type KeyToArray
// KeyToArray<{ foo: ['foo'], foo2: ['foo'] }> = [{foo: { foo: 'foo', fooFoo: true }}, {foo2: { foo2: 'foo', foo2Foo: true }}]
type OptionBoxByName<D extends Record<string, string[]>> = {
[K in StringKeyOf<D>]: OptionWithDerivedProperties<K, D[K]>;
};
// OptionsWithDerivedProperties<{ aProperty: ['foo', 'bar'], oProperty: ['foo', 'bar'] }> = { aProperty: 'foo' | 'bar'; aPropertyFoo: boolean; aPropertyBar: boolean; } & { oProperty: 'foo' | 'bar'; oPropertyFoo: boolean; oPropertyBar: boolean; }
export type OptionsWithDerivedProperties<D extends Record<string, string[]>> = UnionToIntersection<Values<OptionBoxByName<D>>>;
/*
some samples to test
type DatabaseTypeApplication = DeterministicOptionWithDerivedProperties<
'databaseType',
['sql', 'no', 'cassandra', 'couchbase', 'mongodb', 'neo4j'],
[{ foo: 'test ' }, { foo2: 'test ' }]
>;
const foo: DatabaseTypeApplication;
if (foo.databaseTypeCassandra) {
foo.foo;
}
if (foo.databaseTypeSql) {
// $ExpectType void
foo.databaseType
foo.foo;
}
type DatabaseTypeApplication2 = OptionWithDerivedProperties<'databaseType', ['sql', 'no', 'cassandra', 'couchbase', 'mongodb', 'neo4j']>;
const foo2: DatabaseTypeApplication2;
if (foo2.databaseTypeCassandra) {
foo2.databaseType;
foo2.
}
if (foo2.databaseTypeSql) {
// $ExpectType void
foo2.databaseType;
}
const foo3: OptionsWithDerivedProperties<{
databaseType: ['sql', 'no', 'cassandra', 'couchbase', 'mongodb', 'neo4j'],
messageBroker: ['no', 'kafka', 'pular'],
}>;
foo3.databaseTypeSql
*/