Accumulating type is a type that combines the types of declarations that occur in different parts of the code with the same name. Type merging is done according to the OR principle.
Principles and dogmas:
accumulate type, type system
Add the ability to declare accumulative types
// file: moduleAFolder/moduleA.ts
accumulate type ModuleNames = "ModuleA"; // type to be "ModuleA" | "ModuleB"
// file: moduleAFolder/moduleAHelper.ts
import {ModuleNames} from "moduleAFolder/moduleA";
// ^ powerful part of example: importing type ONLY from moduleA, NOT from ModuleB
// ^ accessible value for ModuleNames here are "ModuleA" and "ModuleB"
const a: ModuleNames = "ModuleA"; // it's ok
const b: ModuleNames = "ModuleB"; // it's ok.
const c: ModuleNames = "Unknown"; // it's error
// file: moduleBFolder/moduleB.ts
accumulate type ModuleNames = "ModuleB"; // type to be "ModuleA" | "ModuleB";
Example above is very simple but illustrates all principles good code. In more difficalt cases we have 100 modules and 1000 types. Some of them has types that need in another modules.
Yes, we can create folder with name 'shared' and move common types to this folder:
Yes, we can copy common types from one module to another:
// file: moduleAFolder/moduleA.ts
accumulate type CrossModuleTypes = {
"ModuleA": {
id: string;
data: {
value: string;
}
}
}
// file: moduleBFolder/moduleB.ts
accumulate type CrossModuleTypes = {
"ModuleB": {
id: string;
segment: {
part: string;
}
}
}
// file: moduleCFolder/moduleC.ts
// no imports here!
accumulate type CrossModuleTypes; // nothing merge, only usaging;
type UnionToIntersection<U> = (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type ModuleBType = UnionToIntersection<CrossModuleTypes>["ModuleB"];
const cachingData: ModuleBType = this.loadFromCache("ModuleB");
console.log("Caching segment from ModuleB is ", cachingData.segment.part); // no errors here.
My suggestion meets these guidelines:
I need this feature too
You can do this with already with interface merging, so long as you're OK with making up a unique fake property name for each union constituent:
interface Foo {
whatever: "moduleA";
}
interface Foo {
whateverwhatever: "moduleB";
}
type AccumulatedFoo = Foo[keyof Foo]; // "moduleA" | "moduleB"
powerful part of example: importing type ONLY from moduleA, NOT from ModuleB
This part sounds difficult. It means an accumulate type has an effectively global name, even if it's a module scoped type, because any other module anywhere could declare an accumulate type with the same name and they would merge.
Yes, 'accumulate type' in a sense, it can be read as 'global type'. This is the main point of the idea: merge types across root project.
You can do this with already with interface merging. Yes i can, but where Foo interface were placed?
Yes i can, but where Foo interface were placed?
I don't know all of the exact rules, but you're certainly allowed to have an interface merge across many different files.
Most helpful comment
You can do this with already with interface merging, so long as you're OK with making up a unique fake property name for each union constituent:
This part sounds difficult. It means an
accumulate typehas an effectively global name, even if it's a module scoped type, because any other module anywhere could declare anaccumulate typewith the same name and they would merge.