-
-
Notifications
You must be signed in to change notification settings - Fork 9
feat: user-defined required constructor options #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4df5e57
e1aebc5
61036c4
8c777a4
74bdd51
ba85870
32744ac
b9bfbc3
2ec3e41
526b802
8ee38c8
5c4fdde
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# required options Example | ||
|
||
`Base` has no required options by default, so the following code has no type errors. | ||
|
||
```js | ||
import { Base } from "javascript-plugin-architecture-with-typescript-definitions"; | ||
|
||
const base1 = new Base(); | ||
const base2 = new Base({}); | ||
``` | ||
|
||
But required options can be added by extending the `Base.Options` interface. | ||
|
||
```ts | ||
declare module "javascript-plugin-architecture-with-typescript-definitions" { | ||
namespace Base { | ||
interface Options { | ||
myRequiredUserOption: string; | ||
} | ||
} | ||
} | ||
``` | ||
|
||
With that extension, the same code will have a type error | ||
|
||
```ts | ||
// TS Error: Property 'myRequiredUserOption' is missing in type '{}' but required in type 'Options' | ||
const base = new Base({}); | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Base } from "../../index.js"; | ||
|
||
declare module "../.." { | ||
namespace Base { | ||
interface Options { | ||
myRequiredUserOption: string; | ||
} | ||
} | ||
} | ||
|
||
export class MyBase extends Base {} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Base } from "../../index.js"; | ||
|
||
/** | ||
* @param {Base} base | ||
* @param {Base.Options} options | ||
*/ | ||
function pluginRequiringOption(base, options) { | ||
if (typeof options.myRequiredUserOption !== "string") { | ||
throw new Error('Required option "myRequiredUserOption" missing'); | ||
} | ||
} | ||
|
||
export const MyBase = Base.plugin(pluginRequiringOption); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { MyBase } from "./index.js"; | ||
|
||
// @ts-expect-error - An argument for 'options' was not provided | ||
new MyBase(); | ||
|
||
// @ts-expect-error - Type '{}' is missing the following properties from type 'Options': myRequiredUserOption | ||
new MyBase({}); | ||
|
||
new MyBase({ | ||
myRequiredUserOption: "", | ||
}); | ||
gr2m marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const MyBaseWithDefaults = MyBase.defaults({ | ||
myRequiredUserOption: "", | ||
}); | ||
|
||
new MyBaseWithDefaults(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { test } from "uvu"; | ||
import * as assert from "uvu/assert"; | ||
|
||
import { MyBase } from "./index.js"; | ||
|
||
test("new MyBase()", () => { | ||
assert.throws(() => new MyBase()); | ||
}); | ||
|
||
test("new MyBase({})", () => { | ||
assert.throws(() => new MyBase({})); | ||
}); | ||
|
||
test('new MyBase({ myRequiredUserOption: ""})', () => { | ||
assert.not.throws(() => new MyBase({ myRequiredUserOption: "" })); | ||
}); | ||
|
||
test.run(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,5 @@ | ||
export declare namespace Base { | ||
interface Options { | ||
version: string; | ||
[key: string]: unknown; | ||
} | ||
interface Options { } | ||
} | ||
|
||
declare type ApiExtension = { | ||
|
@@ -26,28 +23,36 @@ declare type UnionToIntersection<Union> = ( | |
declare type AnyFunction = (...args: any) => any; | ||
declare type ReturnTypeOf<T extends AnyFunction | AnyFunction[]> = | ||
T extends AnyFunction | ||
? ReturnType<T> | ||
: T extends AnyFunction[] | ||
? UnionToIntersection<Exclude<ReturnType<T[number]>, void>> | ||
: never; | ||
? ReturnType<T> | ||
: T extends AnyFunction[] | ||
? UnionToIntersection<Exclude<ReturnType<T[number]>, void>> | ||
: never; | ||
|
||
type ClassWithPlugins = Constructor<any> & { | ||
plugins: any[]; | ||
plugins: Plugin[]; | ||
}; | ||
|
||
type RemainingRequirements<PredefinedOptions> = | ||
keyof PredefinedOptions extends never | ||
? Base.Options | ||
: Omit<Base.Options, keyof PredefinedOptions> | ||
|
||
type NonOptionalKeys<Obj> = { | ||
[K in keyof Obj]: {} extends Pick<Obj, K> ? undefined : K; | ||
}[keyof Obj]; | ||
|
||
type RequiredIfRemaining<PredefinedOptions, NowProvided> = | ||
NonOptionalKeys<RemainingRequirements<PredefinedOptions>> extends undefined | ||
Comment on lines
+41
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was an odd little issue. I debugged by adding a ...which showed itself to be type expectType<{ intentionallyFailAlways: true }>(new BaseLevelOne().debugKeys) I switched from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
just fyi, here is
https://github.com/SamVerschueren/tsd#custom-typescript-config |
||
? [(Partial<Base.Options> & NowProvided)?] | ||
: [Partial<Base.Options> & RemainingRequirements<PredefinedOptions> & NowProvided]; | ||
|
||
type ConstructorRequiringVersion<Class extends ClassWithPlugins, PredefinedOptions> = { | ||
defaultOptions: PredefinedOptions; | ||
} & (PredefinedOptions extends { version: string } | ||
? { | ||
new <NowProvided>(options?: NowProvided): Class & { | ||
options: NowProvided & PredefinedOptions; | ||
}; | ||
} | ||
: { | ||
new <NowProvided>(options: Base.Options & NowProvided): Class & { | ||
options: NowProvided & PredefinedOptions; | ||
}; | ||
}); | ||
} & { | ||
new <NowProvided>(...options: RequiredIfRemaining<PredefinedOptions, NowProvided>): Class & { | ||
options: NowProvided & PredefinedOptions; | ||
}; | ||
}; | ||
|
||
export declare class Base<TOptions extends Base.Options = Base.Options> { | ||
static plugins: Plugin[]; | ||
|
@@ -74,9 +79,9 @@ export declare class Base<TOptions extends Base.Options = Base.Options> { | |
static plugin< | ||
Class extends ClassWithPlugins, | ||
Plugins extends [Plugin, ...Plugin[]], | ||
>( | ||
this: Class, | ||
...plugins: Plugins, | ||
>( | ||
this: Class, | ||
...plugins: Plugins, | ||
): Class & { | ||
plugins: [...Class['plugins'], ...Plugins]; | ||
} & Constructor<UnionToIntersection<ReturnTypeOf<Plugins>>>; | ||
|
@@ -130,4 +135,4 @@ export declare class Base<TOptions extends Base.Options = Base.Options> { | |
|
||
constructor(options: TOptions); | ||
} | ||
export {}; | ||
export { }; |
Uh oh!
There was an error while loading. Please reload this page.