-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(strongly-typed): initial commit
Add an initial offering for `@inversifyjs/strongly-typed`, which will let consumers optionally apply strong typing to their `Container`.
- Loading branch information
1 parent
935565e
commit 9200964
Showing
16 changed files
with
549 additions
and
0 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,19 @@ | ||
# Typescript compiled files | ||
/lib/** | ||
|
||
/tsconfig.tsbuildinfo | ||
/tsconfig.cjs.tsbuildinfo | ||
/tsconfig.esm.tsbuildinfo | ||
|
||
# Test coverage report | ||
/coverage | ||
|
||
# Test mutation report | ||
/reports | ||
|
||
# node modules | ||
/node_modules/ | ||
|
||
# Turborepo files | ||
.turbo/ | ||
|
9 changes: 9 additions & 0 deletions
9
packages/container/libraries/strongly-typed/.lintstagedrc.json
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,9 @@ | ||
{ | ||
"*.js": [ | ||
"prettier --write" | ||
], | ||
"*.ts": [ | ||
"prettier --write", | ||
"eslint" | ||
] | ||
} |
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,17 @@ | ||
**/*.spec.js | ||
**/*.spec.js.map | ||
**/*.ts | ||
!lib/**/*.d.ts | ||
lib/**/*.spec.d.ts | ||
|
||
.lintstagedrc.json | ||
eslint.config.mjs | ||
jest.config.mjs | ||
jest.config.stryker.mjs | ||
jest.js.config.mjs | ||
prettier.config.mjs | ||
stryker.config.mjs | ||
tsconfig.json | ||
tsconfig.cjs.json | ||
tsconfig.esm.json | ||
tsconfig.tsbuildinfo |
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 @@ | ||
# @inversifyjs/strongly-typed |
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,38 @@ | ||
[![Test coverage](https://codecov.io/gh/inversify/monorepo/branch/main/graph/badge.svg?flag=%40inversifyjs%2Fstrongly-typed)](https://codecov.io/gh/inversify/monorepo/branch/main/graph/badge.svg?flag=%40inversifyjs%2Fstrongly-typed) | ||
[![npm version](https://img.shields.io/github/package-json/v/inversify/monorepo?filename=packages%2Fcontainer%2Flibraries%2Fstrongly-typed%2Fpackage.json&style=plastic)](https://www.npmjs.com/package/@inversifyjs/strongly-typed) | ||
|
||
# @inversifyjs/strongly-typed | ||
|
||
Type definitions for adding strong typing to the `Container` and `@inject()` decorator. | ||
|
||
## Usage | ||
|
||
This library contains **no functionality**, and only exposes type definitions to augment the core `inversify` implementation. | ||
|
||
These type definitions rely on creating a binding interface along the lines of: | ||
|
||
It can be used in conjunction with the core library simply by casting with a type assertion: | ||
|
||
```ts | ||
import { Container } from 'inversify'; | ||
import type { TypedContainer } from '@inversifyjs/strongly-typed'; | ||
|
||
interface Foo { foo: string } | ||
interface Bar { bar: string } | ||
|
||
interface BindingMap { | ||
foo: Foo; | ||
bar: Bar; | ||
} | ||
|
||
export const container = new Container() as TypedContainer<BindingMap>; | ||
|
||
// Bindings are now strongly typed: | ||
|
||
container.bind('foo').toConstantValue({foo: 'abc'}); // ok | ||
container.rebind('foo').toConstantValue({unknown: 'uh-oh'}) // compilation error | ||
|
||
let foo: Foo = container.get('foo') // ok | ||
foo = container.get('bar') // compilation error | ||
foo = container.get('unknown-identifier') // compilation error | ||
``` |
3 changes: 3 additions & 0 deletions
3
packages/container/libraries/strongly-typed/eslint.config.mjs
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,3 @@ | ||
import myconfig from '@inversifyjs/foundation-eslint-config'; | ||
|
||
export default [...myconfig]; |
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,3 @@ | ||
import { tsGlobalConfig } from '@inversifyjs/foundation-jest-config'; | ||
|
||
export default tsGlobalConfig; |
3 changes: 3 additions & 0 deletions
3
packages/container/libraries/strongly-typed/jest.js.config.mjs
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,3 @@ | ||
import { jsGlobalConfig } from '@inversifyjs/foundation-jest-config'; | ||
|
||
export default jsGlobalConfig; |
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,73 @@ | ||
{ | ||
"author": "Alec Gibson", | ||
"bugs": { | ||
"url": "https://github.com/inversify/monorepo/issues" | ||
}, | ||
"description": "InversifyJs strong type definitions", | ||
"devDependencies": { | ||
"@eslint/js": "9.13.0", | ||
"@jest/globals": "29.7.0", | ||
"@types/node": "20.17.4", | ||
"@typescript-eslint/eslint-plugin": "8.12.2", | ||
"@typescript-eslint/parser": "8.12.2", | ||
"eslint": "9.13.0", | ||
"jest": "29.7.0", | ||
"prettier": "3.3.3", | ||
"ts-jest": "29.2.5", | ||
"typescript": "5.6.3" | ||
}, | ||
"devEngines": { | ||
"node": "^20.18.0", | ||
"pnpm": "^9.12.1" | ||
}, | ||
"homepage": "https://inversify.io", | ||
"keywords": [ | ||
"dependency injection", | ||
"dependency inversion", | ||
"di", | ||
"inversion of control container", | ||
"ioc", | ||
"javascript", | ||
"node", | ||
"typescript" | ||
], | ||
"license": "MIT", | ||
"main": "lib/cjs/index.js", | ||
"module": "lib/esm/index.js", | ||
"exports": { | ||
".": { | ||
"import": "./lib/esm/index.js", | ||
"require": "./lib/cjs/index.js" | ||
} | ||
}, | ||
"name": "@inversifyjs/strongly-typed", | ||
"os": [ | ||
"darwin", | ||
"linux" | ||
], | ||
"peerDependencies": { | ||
"inversify": ">=6.0.3" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/inversify/monorepo.git" | ||
}, | ||
"scripts": { | ||
"build": "pnpm run build:cjs && pnpm run build:esm", | ||
"build:cjs": "tsc --build tsconfig.cjs.json && pnpm exec foundation-ts-package-cjs ./lib/cjs", | ||
"build:esm": "tsc --build tsconfig.esm.json && pnpm exec foundation-ts-package-esm ./lib/esm", | ||
"build:clean": "rimraf lib", | ||
"format": "prettier --write ./src/**/*.ts", | ||
"lint": "eslint ./src", | ||
"test": "jest --config=jest.config.mjs --runInBand", | ||
"test:js": "jest --config=jest.js.config.mjs --runInBand", | ||
"test:js:coverage": "pnpm run test:unit:js --coverage", | ||
"test:uncommitted": "pnpm run test --changedSince=HEAD", | ||
"test:unit:js": "pnpm run test:js --selectProjects Unit" | ||
}, | ||
"sideEffects": false, | ||
"version": "1.0.0" | ||
} |
3 changes: 3 additions & 0 deletions
3
packages/container/libraries/strongly-typed/prettier.config.mjs
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,3 @@ | ||
import config from '@inversifyjs/foundation-prettier-config'; | ||
|
||
export default config; |
158 changes: 158 additions & 0 deletions
158
packages/container/libraries/strongly-typed/src/container.spec.ts
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,158 @@ | ||
/* eslint-disable @typescript-eslint/no-non-null-assertion */ | ||
/* eslint-disable jest/expect-expect */ | ||
import { beforeEach, describe, expect, it } from '@jest/globals'; | ||
|
||
import 'reflect-metadata'; | ||
|
||
import { Container, injectable } from 'inversify'; | ||
|
||
import { TypedContainer } from './container'; | ||
|
||
describe('interfaces', () => { | ||
@injectable() | ||
class Foo { | ||
public foo: string = ''; | ||
} | ||
|
||
@injectable() | ||
class Bar { | ||
public bar: string = ''; | ||
} | ||
|
||
describe('Container', () => { | ||
let foo: Foo; | ||
let foos: Foo[]; | ||
|
||
beforeEach(() => { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions | ||
foo; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions | ||
foos; | ||
}); | ||
|
||
describe('no binding map', () => { | ||
let container: TypedContainer; | ||
|
||
beforeEach(() => { | ||
container = new Container() as TypedContainer; | ||
}); | ||
|
||
describe('bind()', () => { | ||
it('binds without a type argument', () => { | ||
container.bind('foo').to(Foo); | ||
container.bind(Foo).to(Foo); | ||
}); | ||
|
||
it('checks bindings with an explicit type argument', () => { | ||
container.bind<Foo>('foo').to(Foo); | ||
// @ts-expect-error :: can't bind Bar to Foo | ||
container.bind<Foo>('foo').to(Bar); | ||
}); | ||
|
||
it('binds a class as a service identifier', () => { | ||
container.bind(Foo).to(Foo); | ||
// @ts-expect-error :: can't bind Bar to Foo | ||
container.bind(Foo).to(Bar); | ||
}); | ||
}); | ||
|
||
describe('get()', () => { | ||
beforeEach(() => { | ||
container.bind('foo').to(Foo); | ||
container.bind('bar').to(Bar); | ||
container.bind(Foo).to(Foo); | ||
container.bind(Bar).to(Bar); | ||
}); | ||
|
||
it('gets an anonymous binding', () => { | ||
foo = container.get('foo'); | ||
}); | ||
|
||
it('enforces type arguments', () => { | ||
foo = container.get<Foo>('foo'); | ||
// @ts-expect-error :: can't assign Bar to Foo | ||
foo = container.get<Bar>('bar'); | ||
}); | ||
|
||
it('gets a class identifier', () => { | ||
foo = container.get(Foo); | ||
// @ts-expect-error :: can't assign Bar to Foo | ||
foo = container.get(Bar); | ||
}); | ||
|
||
it('gets all', () => { | ||
foos = container.getAll<Foo>('foo'); | ||
// @ts-expect-error :: can't assign Bar to Foo | ||
foos = container.getAll<Bar>('bar'); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('binding map', () => { | ||
let container: TypedContainer<{ foo: Foo; bar: Bar }>; | ||
|
||
beforeEach(() => { | ||
container = new Container() as TypedContainer<{ foo: Foo; bar: Bar }>; | ||
}); | ||
|
||
describe('bind()', () => { | ||
it('enforces strict bindings', () => { | ||
container.bind('foo').to(Foo); | ||
// @ts-expect-error :: can't bind Bar to Foo | ||
container.bind('foo').to(Bar); | ||
// @ts-expect-error :: unknown service identifier | ||
container.bind('unknown').to(Foo); | ||
}); | ||
}); | ||
|
||
describe('get()', () => { | ||
beforeEach(() => { | ||
container.bind('foo').to(Foo); | ||
container.bind('bar').to(Bar); | ||
}); | ||
|
||
it('enforces strict bindings', () => { | ||
foo = container.get('foo'); | ||
// @ts-expect-error :: can't assign Bar to Foo | ||
foo = container.get('bar'); | ||
// @ts-expect-error :: unknown service identifier | ||
expect(() => container.get('unknown')).toThrow( | ||
'No matching bindings', | ||
); | ||
}); | ||
|
||
it('gets all', () => { | ||
foos = container.getAll('foo'); | ||
// @ts-expect-error :: can't assign Bar to Foo | ||
foos = container.getAll('bar'); | ||
}); | ||
}); | ||
|
||
describe('ancestry', () => { | ||
beforeEach(() => { | ||
container.bind('foo').to(Foo); | ||
container.bind('bar').to(Bar); | ||
}); | ||
|
||
it('tracks the types of ancestors', () => { | ||
// eslint-disable-next-line @typescript-eslint/typedef | ||
const child = container.createChild<{ lorem: string }>(); | ||
child.bind('lorem').toConstantValue('lorem'); | ||
foo = child.parent!.get('foo'); | ||
// @ts-expect-error :: can't assign Bar to Foo | ||
foo = child.parent!.get('bar'); | ||
|
||
// eslint-disable-next-line @typescript-eslint/typedef | ||
const grandchild = child.createChild<{ ipsum: string }>(); | ||
const lorem: string = grandchild.parent!.get('lorem'); | ||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions | ||
lorem; | ||
|
||
foo = grandchild.parent!.parent!.get('foo'); | ||
// @ts-expect-error :: can't assign Bar to Foo | ||
foo = grandchild.parent!.parent!.get('bar'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.