-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
22 changed files
with
15,056 additions
and
2,061 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// see [ESLint Configuration](https://eslint.org/docs/user-guide/configuring) | ||
{ | ||
// libs that run in both envs, browser & node, don't access global variables that are browser or node specific | ||
"env": { | ||
"browser": false, // enables Browser global variables, like localStorage | ||
"es6": true, // enables new ES6 global variables, such as Set | ||
"node": true // enables Node global variables, like process | ||
}, | ||
"extends": [ | ||
"eslint:recommended", | ||
"plugin:@typescript-eslint/eslint-recommended" | ||
], | ||
"parser": "@typescript-eslint/parser", | ||
"parserOptions": { // [typescript-eslint parser configuration](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser#configuration) | ||
"project": "tsconfig.eslint.json", // mandatory when using types | ||
"sourceType": "module" // needed in order to use import declarations | ||
}, | ||
"plugins": [ | ||
"@typescript-eslint", | ||
"typescript-enum", | ||
"unused-imports" | ||
], | ||
"rules": { | ||
|
||
// List of [ESLint rules](https://eslint.org/docs/rules/) | ||
"arrow-parens": ["off", "as-needed"], // do not force arrow function parentheses | ||
"constructor-super": "error", // checks the correct use of super() in sub-classes | ||
"curly": "error", // if statement needs curly braces | ||
"dot-notation": "error", // obj.a instead of obj['a'] when possible | ||
"eqeqeq": "error", // ban '==', don't use 'smart' option! | ||
"guard-for-in": "error", // needs obj.hasOwnProperty(key) checks | ||
"new-parens": "error", // new Error() instead of new Error | ||
"no-bitwise": "error", // bitwise operators &, | can be confused with &&, || | ||
"no-caller": "error", // ECMAScript deprecated arguments.caller and arguments.callee | ||
"no-cond-assign": "error", // assignments if (a = '1') are error-prone | ||
"no-debugger": "error", // disallow debugger; statements | ||
"no-eval": "error", // eval is considered unsafe | ||
"no-inner-declarations": "off", // we need to have 'namespace' functions when using TS 'export =' | ||
"no-labels": "error", // GOTO is only used in BASIC ;) | ||
"no-multiple-empty-lines": ["error", {"max": 1}], // two or more empty lines need to be fused to one | ||
"no-new-wrappers": "error", // there is no reason to wrap primitve values | ||
"no-throw-literal": "error", // only throw Error but no objects {} | ||
"no-trailing-spaces": "error", // trim end of lines | ||
"no-unsafe-finally": "error", // safe try/catch/finally behavior | ||
"no-unused-vars": "off", // we need unused vars for proper typing | ||
"no-var": "error", // use const and let instead of var | ||
"prefer-const": "error", // use const when possible | ||
"quote-props": ["error", "as-needed", { // defines how object-keys are quoted | ||
"keywords": false, | ||
"unnecessary": true, | ||
"numbers": false | ||
}], | ||
"space-before-function-paren": ["error", { // space in function decl: f() vs async () => {} | ||
"anonymous": "never", | ||
"asyncArrow": "always", | ||
"named": "never" | ||
}], | ||
"unused-imports/no-unused-imports": "error", // no unsused imports | ||
"use-isnan": "error", // isNaN(i) Number.isNaN(i) instead of i === NaN | ||
|
||
// List of [typescript-enum rules](https://github.com/shian15810/eslint-plugin-typescript-enum) | ||
"typescript-enum/no-enum": "error", // disallow enums, see https://2ality.com/2020/02/enum-alternatives-typescript.html | ||
|
||
// List of [@typescript-eslint rules](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules) | ||
"@typescript-eslint/adjacent-overload-signatures": "error", // grouping same method names | ||
"@typescript-eslint/array-type": ["error", { // string[] instead of Array<string> | ||
"default": "array" | ||
}], | ||
"@typescript-eslint/indent": "error", // consistent indentation | ||
"@typescript-eslint/consistent-type-assertions": "error", // needed for .tsx, bad = <Foo>bar, good = bar as Foo | ||
"@typescript-eslint/no-misused-new": "error", // no constructors for interfaces or new for classes | ||
"@typescript-eslint/no-parameter-properties": "error", // no property definitions in class constructors | ||
"@typescript-eslint/no-var-requires": "error", // use import instead of require | ||
"@typescript-eslint/prefer-for-of": "error", // prefer for-of loop over arrays | ||
"@typescript-eslint/prefer-namespace-keyword": "error", // prefer namespace over module in TypeScript | ||
"@typescript-eslint/triple-slash-reference": "error", // ban /// <reference />, prefer imports | ||
"@typescript-eslint/type-annotation-spacing": "error" // consistent space around colon ':' | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
--- | ||
name: "Bug report" | ||
about: Report a reproducible bug or regression. | ||
labels: 'bug' | ||
|
||
--- | ||
|
||
# Bug Report | ||
|
||
Ginject version: | ||
|
||
<!-- | ||
Please provide a clear and concise description of what the bug is. Include | ||
screenshots if needed. Please test using the latest version of Ginject to | ||
make sure your issue has not already been fixed. | ||
--> | ||
|
||
## Steps To Reproduce | ||
|
||
1. | ||
2. | ||
|
||
<!-- | ||
Your bug will get fixed much faster if we can run your code and it doesn't | ||
have dependencies other than Ginject. Issues without reproduction steps or | ||
code examples may be immediately closed as not actionable. | ||
--> | ||
|
||
Link to code example: | ||
|
||
<!-- | ||
Please provide a link to a repository on GitHub or provide a minimal code | ||
example that reproduces the problem. You may provide a screenshot of some | ||
application if you think it is relevant to your bug report. Here are some | ||
tips for providing a minimal example: https://stackoverflow.com/help/mcve. | ||
--> | ||
|
||
## The current behavior | ||
|
||
## The expected behavior |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
blank_issues_enabled: false | ||
contact_links: | ||
- name: Question | ||
url: https://github.com/langium/ginject/discussions | ||
about: Please ask questions here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
name: Feature request | ||
about: Suggest an idea for this project | ||
labels: 'feature-request' | ||
|
||
--- | ||
<!-- Please search existing issues to avoid creating duplicates. --> | ||
<!-- Describe the feature you'd like. --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
name: General improvement | ||
about: Suggest an improvement for this project | ||
|
||
--- | ||
<!-- Please search existing issues to avoid creating duplicates. --> | ||
<!-- Describe the improvement you'd like to see in the project. --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
name: Build | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
build: | ||
name: Ginject | ||
runs-on: ubuntu-latest | ||
timeout-minutes: 20 | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v2 | ||
- name: Use Node.js | ||
uses: actions/setup-node@v2 | ||
with: | ||
node-version: '16' | ||
- name: Build | ||
shell: bash | ||
run: | | ||
npm ci | ||
npm test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
.DS_Store | ||
*.tgz | ||
coverage/ | ||
dist/ | ||
lib/ | ||
node_modules/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
<div id="ginject-logo" align="center"> | ||
<a href="https://github.com/langium/ginject"> | ||
<img alt="Ginject Logo" width="450" src="https://user-images.githubusercontent.com/743833/193610222-cf9a7feb-b1d9-4d5c-88de-6ce9fbca8299.png"> | ||
</a> | ||
<h3> | ||
Dependency injection done right. | ||
</h3> | ||
</div> | ||
|
||
<div id="badges" align="center"> | ||
|
||
[](https://www.npmjs.com/package/ginject/) | ||
[](https://github.com/langium/ginject/actions/workflows/build.yml) | ||
[](https://gitpod.io/#https://github.com/langium/ginject) | ||
|
||
</div> | ||
|
||
<br> | ||
|
||
**Ginject** [ʤɪnject] is a non-intrusive and typesafe dependency injection library for Node.js and JavaScript, powered by TypeScript. | ||
|
||
**Ginject** empowers developers designing decoupled applications and frameworks. **Ginject**'s main goal is increasing the developer experience by offering a tiny, yet powerful API, keeping dependencies in central module definitions and by using TypeScript's type system to restrain runtime challenges. | ||
|
||
The concept of **ginject**'s central module definition is inspired by [Google Guice](https://github.com/google/guice). However, **ginject** is going further by lifting the API to the functional level. | ||
|
||
Despite its simplicity, **ginject** is powerful enough to cover all features provided by [Inversify](https://github.com/inversify/InversifyJS). Direct support for classes and constructors, property injection, rebinding dependencies and dependency cycle detection are just a few of the features worth mentioning. | ||
|
||
<br> | ||
|
||
<div id="ginject vs inversify" align="center"> | ||
|
||
| | ginject | inversify | | ||
|------------------|:----------:|:-----------:| | ||
| minified | [](https://bundlephobia.com/result?p=ginject@latest) | [](https://bundlephobia.com/result?p=inversify@latest) | | ||
| minzipped | [](https://bundlephobia.com/result?p=ginject@latest) | [](https://bundlephobia.com/result?p=inversify@latest) | | ||
| typesafe | ✅ | ❌ | | ||
| requirements | none | decorators | | ||
| style | functional | imperative | | ||
| API surface area | tiny | non-trivial | | ||
|
||
</div> | ||
|
||
<br> | ||
|
||
## Quickstart | ||
|
||
The first step is to add **ginject** to your application. | ||
|
||
```sh | ||
npm i ginject | ||
``` | ||
|
||
Bascially, the only thing needed is to define **modules** of **factories** and finally call **inject**. The resulting **container** provides concrete **instances**. | ||
|
||
```ts | ||
import { inject } from 'ginject'; | ||
|
||
// create an inversion of control container | ||
const container = inject({ | ||
hi: () => 'Hi', | ||
sayHi: () => (name: string) => `${container.hi} ${name}!` | ||
}); | ||
|
||
// prints 'Hi Ginject!' | ||
console.log(container.sayHi('Ginject')); | ||
``` | ||
|
||
## API | ||
|
||
### Terminology | ||
|
||
The **inject** function is turning **modules** into a **container**. A **module** is a plain vanilla JS object, composed of nested **groups** and **dependency factories**. Factories may return any JS value, e.g. constants, singletons and providers. Unlike [Inversify](https://github.com/inversify/InversifyJS), there is no need to decorate classes. | ||
|
||
```ts | ||
import { inject, Module } from 'ginject'; | ||
|
||
// Defining a _context_ of dependencies | ||
type Context = { | ||
group: { | ||
value: Value // any JS type, here a class | ||
} | ||
} | ||
|
||
// A _module_ contains nested _groups_ (optional) and _factories_ | ||
const module: Module<Context> = { | ||
group: { | ||
// a factory of type Factory<Context, Value> | ||
value: (ctx: Context) => new Value(ctx) | ||
} | ||
}; | ||
|
||
// A _container_ of type Container<Module<Context>> = Context | ||
const container = inject(module); | ||
|
||
// Values can be obtained from the container | ||
const value = container.group.value; | ||
``` | ||
|
||
### Context | ||
|
||
A **container** provides each **factory** with a parameter called **context**. | ||
|
||
```ts | ||
type C = { | ||
value: string | ||
} | ||
|
||
const container = inject({ | ||
factory: (ctx: C) => () => ctx.value | ||
}); | ||
``` | ||
|
||
The **context** of type **C** provides a **value** that can't be resolved. The **inject** call is type-checked by TS the way that the completeness of the arguments is checked. | ||
|
||
Such **missing dependencies** need to be provided by adding additional **modules** to the **inject** call. | ||
|
||
```ts | ||
const container = inject({ | ||
factory: (ctx: C) => () => ctx.value | ||
}, { | ||
value: () => '🍸' | ||
}); | ||
``` | ||
|
||
Now the compiler is satisfied and we can start using the **container**. | ||
|
||
```ts | ||
// prints 🍸 | ||
console.log(container.factory()); | ||
``` | ||
|
||
You might have noticed that the **container** automatically calls the **factory** and **injects** itself as the **context**. The use-site receives the **value**. | ||
|
||
### Eager vs lazy initialization | ||
|
||
A dependency **container.group.value** is **lazily** initialized when first accessed on the container. Turn a factory **eager** to initialize the dependency at the time of the **inject** call. | ||
|
||
A use case for **eager initialization** would be to ensure that **side effects** take place during the initialization of the **container**. | ||
|
||
```ts | ||
import { eager, inject, Module } from 'ginject'; | ||
|
||
type C = { | ||
gin: string | ||
} | ||
|
||
const module: Module<C> = { | ||
gin: eager(() => { | ||
const gin = '🍸'; | ||
console.log('Gin mixed'); | ||
return gin; | ||
}) | ||
} | ||
|
||
const ctr = inject(module); | ||
|
||
console.log('App started'); | ||
|
||
ctr.gin | ||
``` | ||
|
||
In the **eager** case, the output is | ||
|
||
``` | ||
Gin mixed | ||
App started | ||
``` | ||
|
||
In the **lazy** case, the output is | ||
|
||
``` | ||
App started | ||
Gin mixed | ||
``` | ||
|
||
Please note that **eager factories** overwrite **lazy factories** vice versa when **rebinding** them. | ||
|
||
### Rebinding dependencies | ||
|
||
The main advantage of **dependency injection** arises from the fact that an application is able to **rebind dependencies**. That way the **structure** of a system can be fixated while the **behavior** can be changed. | ||
|
||
The main vehicle for **rebinding dependencies** is the **inject** function which receives a variable amount of **modules**. | ||
|
||
The behavior of an application can be enhanced by overwriting existing functionality using additional modules. | ||
|
||
```ts | ||
type C = { | ||
readonly print: () => void | ||
eval: (a: number, b: number) => number | ||
} | ||
|
||
const module_0: Module<C> = { | ||
print: (ctx) => () => { | ||
console.log(ctx.eval(1, 1)); | ||
}, | ||
eval: () => (a, b) => a + b | ||
}; | ||
|
||
const ctr = inject(module_0, { | ||
eval: () => (a: number, b: number) => a * b | ||
}); | ||
|
||
// = 1 | ||
ctr.print(); | ||
``` | ||
|
||
### Cyclic Dependencies | ||
|
||
### Asynchronous Factories |
Oops, something went wrong.