Skip to content

Commit cba90c6

Browse files
authored
feat: Base.withPlugins() now accepts an array of plugin functions instead of multiple arguments. Export Plugin type
BREAKING CHANGE: `Base.withPlugins()` now accepts an array of plugin functions instead of multiple arguments BREAKING CHANGE: `.plugin()` is now `.withPlugins()`, `.defaults()` is now `.withDefaults()`, `.defaultOptions` is now `.defaults`
1 parent 3a30c56 commit cba90c6

File tree

7 files changed

+130
-83
lines changed

7 files changed

+130
-83
lines changed

README.md

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
[![@latest](https://img.shields.io/npm/v/javascript-plugin-architecture-with-typescript-definitions.svg)](https://www.npmjs.com/package/javascript-plugin-architecture-with-typescript-definitions)
66
[![Build Status](https://github.com/gr2m/javascript-plugin-architecture-with-typescript-definitions/workflows/Test/badge.svg)](https://github.com/gr2m/javascript-plugin-architecture-with-typescript-definitions/actions/workflows/test.yml)
77

8-
The goal of this repository is to provide a template of a simple plugin Architecture which allows plugins to created and authored as separate npm modules and shared as official or 3rd party plugins.
8+
The goal of this repository is to provide a template of a simple plugin Architecture which allows plugins to be created and authored as separate npm modules and shared as official or 3rd party plugins. It also permits the plugins to extend the types for the constructor options.
99

1010
## Usage
1111

12-
[Try it in TypeScript's playground editor](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgIQIYGcCmcC+cBmUEIcARAFaoBuGAxlMGDALRgA2ArgObAB2zqKLQAWwGJlowOUTMwDuY4cxgBPMJnT1GLACaZ8fMcAi90pANwAoS-g69Jx3nBAqAYhAgAFTj14AKPnQYVHtMAC4UDEwASkRLODgZKSgnBHiEgg8Iv1iAXgA+MnwPUgAadJwrHGtbexhHZxU0KG9uPgDTYNCItCxYtISk6VT0hIAjQWy8wtIJqDKKqpq7BxM4DCxYAGUYBl4uP3QOMfIJGAigva5+6staEyC4dwgAFQ14XMisADp2Nv8XM9Wr5olZ7p1Mq93nBPrxMHInh43kEclYNphtrs+AdilCgt9cTlQdZwY9ns1kR8vphfj52oCPMC+KVGs0mbxiaT4LiKdDYfDERBeSjiejMVc-DzBJSCR4iWj0JsYDsJVKoDK5vKgA)
12+
[Try it in TypeScript's playground editor](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgIQIYGcCmcC+cBmUEIcARAFaoBuGAxlMGDALRgA2ArgObAB2zqKLQAWwGJlowOUTMwDuY4cxgBPMJnT1GLACaZ8fMcAi90pANwAoS-g69Jx3nBAqAYhAgAFTj14AKPnQYVHtMAC4UDEwASkRLODgZKSgnBHiEgg8Iv1iAXgA+MnwPUgAadJwrHGtbexhHZxU0KG9uPgDTYNCItCxYtISk6VT0hIAjQWy8wtIJqDKKqpq7BxM4DCxYAGUYBl4uP3QOMfIJGAigva5+6staEyC4dwgAFQ14XMisADoFGGFWr50H4ANouZ6AvgAXWiVnunUyr3ecE+vEwcieHjeQRyVg2mG2uz4B2KSKC31JOVh1nhj2ezWxHy+mF+ikhplB4I87NKjWa7JhcIe8FJDORqPRmIgYpx1PxhKuflFgkZFI8VLx6E2MB2iuVUFVcw1QA)
1313

1414
```ts
1515
import { Base } from "javascript-plugin-architecture-with-typescript-definitions";
@@ -26,31 +26,56 @@ function myBarPlugin(instance: Base) {
2626
};
2727
}
2828

29-
const FooTest = Base.plugin(myFooPlugin);
29+
const FooTest = Base.withPlugins([myFooPlugin]);
3030
const fooTest = new FooTest();
3131
fooTest.foo(); // has full TypeScript intellisense
3232

33-
const FooBarTest = Base.plugin(myFooPlugin, myBarPlugin);
33+
const FooBarTest = Base.withPlugins([myFooPlugin, myBarPlugin]);
3434
const fooBarTest = new FooBarTest();
3535
fooBarTest.foo(); // has full TypeScript intellisense
3636
fooBarTest.bar(); // has full TypeScript intellisense
3737
```
3838

39-
The constructor accepts an optional `options` object which is passed to the plugins as second argument and stored in `instance.options`. Default options can be set using `Base.defaults(options)`
39+
The constructor accepts an optional `options` object which is passed to the plugins as second argument and stored in `instance.options`. Default options can be set using `Base.withDefaults(options)`.
4040

4141
```js
42-
const BaseWithOptions = Base.defaults({ foo: "bar" });
42+
const BaseWithOptions = Base.withDefaults({ foo: "bar" });
4343
const instance = new BaseWithOptions();
4444
instance.options; // {foo: 'bar'}
4545
```
4646

47+
Note that in for TypeScript to recognize the new option, you have to extend the `Base.Option` intererface.
48+
49+
```ts
50+
declare module "javascript-plugin-architecture-with-typescript-definitions" {
51+
namespace Base {
52+
interface Options {
53+
foo: string;
54+
}
55+
}
56+
}
57+
```
58+
59+
See also the [`required-options` example](examples/required-options).
60+
61+
The `Base` class also has two static properties
62+
63+
- `.defaults`: the default options for all instances
64+
- `.plugins`: the list of plugins applied to all instances
65+
66+
When creating a new class with `.withPlugins()` and `.defaults()`, the static properties of the returned class are set accordingly.
67+
68+
```js
69+
const MyBase = Base.withDefaults({ foo: "bar" });
70+
```
71+
4772
### Defaults
4873

49-
TypeScript will not complain when chaining `.defaults()` calls endlessly: the static `.defaultOptions` property will be set correctly. However, when instantiating from a class with 4+ chained `.defaults()` calls, then only the defaults from the first 3 calls are supported. See [#57](https://github.com/gr2m/javascript-plugin-architecture-with-typescript-definitions/pull/57) for details.
74+
TypeScript will not complain when chaining `.withDefaults()` calls endlessly: the static `.defaults` property will be set correctly. However, when instantiating from a class with 4+ chained `.withDefaults()` calls, then only the defaults from the first 3 calls are supported. See [#57](https://github.com/gr2m/javascript-plugin-architecture-with-typescript-definitions/pull/57) for details.
5075

5176
## Credit
5277

53-
This plugin architecture was extracted from [`@octokit/core`](https://github.com/octokit/core.js). The implementation was made possible by help from [@karol-majewski](https://github.com/karol-majewski), [@dragomirtitian](https://github.com/dragomirtitian), and [StackOverflow user "hackape"](https://stackoverflow.com/a/58706699/206879).
78+
This plugin architecture was extracted from [`@octokit/core`](https://github.com/octokit/core.js). The implementation was made possible by help from [@karol-majewski](https://github.com/karol-majewski), [@dragomirtitian](https://github.com/dragomirtitian), [StackOverflow user "hackape"](https://stackoverflow.com/a/58706699/206879), and [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg).
5479

5580
## LICENSE
5681

examples/required-options/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ function pluginRequiringOption(base, options) {
1010
}
1111
}
1212

13-
export const MyBase = Base.plugin(pluginRequiringOption);
13+
export const MyBase = Base.withPlugins([pluginRequiringOption]);

examples/required-options/index.test-d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ new MyBase({
1010
myRequiredUserOption: "",
1111
});
1212

13-
const MyBaseWithDefaults = MyBase.defaults({
13+
const MyBaseWithDefaults = MyBase.withDefaults({
1414
myRequiredUserOption: "",
1515
});
1616

index.d.ts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export declare namespace Base {
55
declare type ApiExtension = {
66
[key: string]: unknown;
77
};
8-
declare type Plugin = (
8+
export declare type Plugin = (
99
instance: Base,
1010
options: Base.Options
1111
) => ApiExtension | void;
@@ -51,11 +51,11 @@ type RequiredIfRemaining<PredefinedOptions, NowProvided> = NonOptionalKeys<
5151
NowProvided
5252
];
5353

54-
type ConstructorRequiringVersion<
54+
type ConstructorRequiringOptionsIfNeeded<
5555
Class extends ClassWithPlugins,
5656
PredefinedOptions
5757
> = {
58-
defaultOptions: PredefinedOptions;
58+
defaults: PredefinedOptions;
5959
} & {
6060
new <NowProvided>(
6161
...options: RequiredIfRemaining<PredefinedOptions, NowProvided>
@@ -65,8 +65,6 @@ type ConstructorRequiringVersion<
6565
};
6666

6767
export declare class Base<TOptions extends Base.Options = Base.Options> {
68-
static plugins: Plugin[];
69-
7068
/**
7169
* Pass one or multiple plugin functions to extend the `Base` class.
7270
* The instance of the new class will be extended with any keys returned by the passed plugins.
@@ -81,17 +79,17 @@ export declare class Base<TOptions extends Base.Options = Base.Options> {
8179
* };
8280
* }
8381
*
84-
* const MyBase = Base.plugin(helloWorld);
82+
* const MyBase = Base.withPlugins([helloWorld]);
8583
* const base = new MyBase();
8684
* base.helloWorld(); // `base.helloWorld` is typed as function
8785
* ```
8886
*/
89-
static plugin<
87+
static withPlugins<
9088
Class extends ClassWithPlugins,
9189
Plugins extends [Plugin, ...Plugin[]]
9290
>(
9391
this: Class,
94-
...plugins: Plugins
92+
plugins: Plugins
9593
): Class & {
9694
plugins: [...Class["plugins"], ...Plugins];
9795
} & Constructor<UnionToIntersection<ReturnTypeOf<Plugins>>>;
@@ -100,37 +98,37 @@ export declare class Base<TOptions extends Base.Options = Base.Options> {
10098
* Set defaults for the constructor
10199
*
102100
* ```js
103-
* const MyBase = Base.defaults({ version: '1.0.0', otherDefault: 'value' });
101+
* const MyBase = Base.withDefaults({ version: '1.0.0', otherDefault: 'value' });
104102
* const base = new MyBase({ option: 'value' }); // `version` option is not required
105103
* base.options // typed as `{ version: string, otherDefault: string, option: string }`
106104
* ```
107105
* @remarks
108106
* Ideally, we would want to make this infinitely recursive: allowing any number of
109-
* .defaults({ ... }).defaults({ ... }).defaults({ ... }).defaults({ ... })...
107+
* .withDefaults({ ... }).withDefaults({ ... }).withDefaults({ ... }).withDefaults({ ... })...
110108
* However, we don't see a clean way in today's TypeScript syntax to do so.
111109
* We instead artificially limit accurate type inference to just three levels,
112110
* since real users are not likely to go past that.
113111
* @see https://github.com/gr2m/javascript-plugin-architecture-with-typescript-definitions/pull/57
114112
*/
115-
static defaults<
113+
static withDefaults<
116114
PredefinedOptionsOne,
117115
ClassOne extends Constructor<Base<Base.Options & PredefinedOptionsOne>> &
118116
ClassWithPlugins
119117
>(
120118
this: ClassOne,
121119
defaults: PredefinedOptionsOne
122-
): ConstructorRequiringVersion<ClassOne, PredefinedOptionsOne> & {
123-
defaults<ClassTwo, PredefinedOptionsTwo>(
120+
): ConstructorRequiringOptionsIfNeeded<ClassOne, PredefinedOptionsOne> & {
121+
withDefaults<ClassTwo, PredefinedOptionsTwo>(
124122
this: ClassTwo,
125123
defaults: PredefinedOptionsTwo
126-
): ConstructorRequiringVersion<
124+
): ConstructorRequiringOptionsIfNeeded<
127125
ClassOne & ClassTwo,
128126
PredefinedOptionsOne & PredefinedOptionsTwo
129127
> & {
130-
defaults<ClassThree, PredefinedOptionsThree>(
128+
withDefaults<ClassThree, PredefinedOptionsThree>(
131129
this: ClassThree,
132130
defaults: PredefinedOptionsThree
133-
): ConstructorRequiringVersion<
131+
): ConstructorRequiringOptionsIfNeeded<
134132
ClassOne & ClassTwo & ClassThree,
135133
PredefinedOptionsOne & PredefinedOptionsTwo & PredefinedOptionsThree
136134
> &
@@ -141,7 +139,15 @@ export declare class Base<TOptions extends Base.Options = Base.Options> {
141139
ClassTwo;
142140
} & ClassOne;
143141

144-
static defaultOptions: {};
142+
/**
143+
* list of plugins that will be applied to all instances
144+
*/
145+
static plugins: Plugin[];
146+
147+
/**
148+
* list of default options that will be applied to all instances
149+
*/
150+
static defaults: {};
145151

146152
/**
147153
* options passed to the constructor as constructor defaults

index.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export class Base {
66
});
77
}
88

9-
static plugin(...newPlugins) {
9+
static withPlugins(newPlugins) {
1010
const currentPlugins = this.plugins;
1111
return class extends this {
1212
static plugins = currentPlugins.concat(
@@ -15,7 +15,7 @@ export class Base {
1515
};
1616
}
1717

18-
static defaults(defaults) {
18+
static withDefaults(defaults) {
1919
return class extends this {
2020
constructor(options) {
2121
super({
@@ -24,11 +24,10 @@ export class Base {
2424
});
2525
}
2626

27-
static defaultOptions = { ...defaults, ...this.defaultOptions };
27+
static defaults = { ...defaults, ...this.defaults };
2828
};
2929
}
3030

31-
static defaultOptions = {};
32-
3331
static plugins = [];
32+
static defaults = {};
3433
}

0 commit comments

Comments
 (0)