Git Product home page Git Product logo

tsdi's Introduction

tsdi

Github Workflow npm GitHub license codecov renovate badge

Easy dependency injection for TypeScript.

Features

  • Type based dependency injection
  • Type auto registration
  • Lifecycle methods
  • Constructor parameters injection
  • Singletons and Instances
  • External components (components not managed by tsdi)
  • Scopes

Installation

Install as npm package:

$ yarn add tsdi

or

npm install tsdi --save

You need to enable decorator metadata in your tsconfig.json, which is done by adding the following line:

"emitDecoratorMetadata": true

Otherwise TSDI will not be able to infer the types of some factories and components.

Documentation

See https://tsdi.js.org/

Contributors โœจ

Thanks goes to these wonderful people (emoji key):


Markus Wolf

๐Ÿ’ป

Frederick Gnodtke

๐Ÿ’ป

This project follows the all-contributors specification. Contributions of any kind welcome!


Released under MIT license - (C) 2018 Markus Wolf

tsdi's People

Contributors

greenkeeper[bot] avatar greenkeeperio-bot avatar knisterpeter avatar prior99 avatar renovate-bot avatar renovate[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

tsdi's Issues

Asynchronous component initializers

Feature Request: Asynchronous Factories

I am using TSDI to inject a database across my application. For this I am using a factory, think:

@component
class DatabaseFactory {
    private databaseConnection: DatabaseConnection;

    @initialize
    connect() {
        this.databaseConnection = new DatabaseConnection();
    }

    @factory
    getDatabaseConnection(): DatabaseConnection {
        return this.databaseConnection;
    }
}

However, the database initialization is asynchronous, and that's where the problems start:

@component
class DatabaseFactory {
    private databaseConnection: DatabaseConnection;

    @initialize
    async connect() {
        this.databaseConnection = await connectToDatabase();
    }

    @factory
    getDatabaseConnection(): DatabaseConnection {
        return this.databaseConnection;
    }
}

@component({ eager: true })
class Api {
    @inject private db: DatabaseConnection;

    @initialize
    public initialize() {
        this.db.query(...); // `db` is now `undefined`.
    }
}

It'd be cool, if this could somehow be solved.

feat: add container hierarchies

It is common to have a hierarchy of containers.
For example on server-side create a base container with common resources like database connection pool and then a child container per request.
Or on the client side create a base container with authentication data and 'global' resources and child containers per application part like user-details page.

This follows #268, #270

Constructor injection should be lazy

The constructor injections could be created using proxies and with this change we can make them lazy.
This will break backward compatibility.

Auto Website Release fails

warning package.json: No license field
$ docusaurus-version refs/tags/v0.25.0
Invalid version number specified! Do not include slash (/). Try something like: 1.0.0
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Error: Process completed with exit code 1.

release issue

This is an umbrella ticket for the next release (testing the shipjs workflow)

Asynchronous initializers not working with externals

Externals are not waiting for asynchronous dependencies when using @initialize.

      @component
      class DeepNestedAsyncDependency {
        public value?: number;

        @initialize
        protected async init(): Promise<void> {
            // ...
        }
      }

      @component
      class SyncDependency {
        @inject public dependency!: DeepNestedAsyncDependency;
      }

      @external
      class Dependent {
        @inject public dependency!: SyncDependency;

        @initialize
        protected init(): void {
          assert.equal(this.dependency.dependency.value, 10);
          done();
        }
      }

      new Dependent();

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Location: package.json
Error type: The renovate configuration file contains some invalid settings
Message: Configuration option 'packageRules[0].node' should be a json object, Invalid configuration option: author, Invalid configuration option: commitlint, Invalid configuration option: files, Invalid configuration option: license, Invalid configuration option: lint-staged, Invalid configuration option: main, Invalid configuration option: module, Invalid configuration option: name, Invalid configuration option: nyc, Invalid configuration option: packageRules[1].@commitlint/cli, Invalid configuration option: packageRules[1].@commitlint/config-conventional, Invalid configuration option: packageRules[1].@istanbuljs/nyc-config-typescript, Invalid configuration option: packageRules[1].@knisterpeter/standard-tslint, Invalid configuration option: packageRules[1].@types/chai, Invalid configuration option: packageRules[1].@types/debug, Invalid configuration option: packageRules[1].@types/mocha, Invalid configuration option: packageRules[1].@types/node, Invalid configuration option: packageRules[1].@types/shelljs, Invalid configuration option: packageRules[1].chai, Invalid configuration option: packageRules[1].codecov, Invalid configuration option: packageRules[1].cypress, Invalid configuration option: packageRules[1].fkill, Invalid configuration option: packageRules[1].get-port, Invalid configuration option: packageRules[1].husky, Invalid configuration option: packageRules[1].lint-staged, Invalid configuration option: packageRules[1].microbundle, Invalid configuration option: packageRules[1].mocha, Invalid configuration option: packageRules[1].nyc, Invalid configuration option: packageRules[1].pinst, Invalid configuration option: packageRules[1].prettier, Invalid configuration option: packageRules[1].rimraf, Invalid configuration option: packageRules[1].shelljs, Invalid configuration option: packageRules[1].shipjs, Invalid configuration option: packageRules[1].tslint, Invalid configuration option: packageRules[1].typescript, Invalid configuration option: packageRules[1].wait-on, Invalid configuration option: packageRules[2].reflect-metadata, Invalid configuration option: prettier, Invalid configuration option: renovate, Invalid configuration option: scripts, Invalid configuration option: source, Invalid configuration option: types, Invalid configuration option: umd:main, Invalid configuration option: version, The "node" object can only be configured at the top level of a config but was found inside "packageRules[0]"

@destroy lifecycle not called on every instance of non-singleton component

Hi,

i found that the @destroy lifecycle is only called on the last instance that was created from a non-singleton scoped component. It's not documented anywhere but i assume the expected behavior would be that it's called on every instance.

I already created a unit test as a repro case but i'm currently not allowed to push branches to this project.

What's the expected behavior and would you accept a PR to fix this?

Cheers,

Tobias

Testing mocking doesn't work

How is possible to mock services, methods, etc?

I tried automock but not working, like this:

const tsdi: TSDI = new TSDI();
tsdi.enableAutomock(HemeraInstance);
tsdi.enableComponentScanner();
const main: Main = tsdi.get(Main);

main.startService();

But not working it still using old HemeraInstance class

There should be also some way to mock just methods inside class

Auto-update website

Use a github action to build and update the page if docs/ or website/ changes.

Injecting undefined type

TSDI complains an injected dependency would be undefined:

Uncaught Error: Injecting undefined type on Errors#api: Probably a cyclic dependency, switch to name based injection
    at TSDI.checkAndThrowDependencyError (tsdi.js:285)
    at TSDI.getInjectComponentMetadata (tsdi.js:260)
    at TSDI.getComponentDependency (tsdi.js:271)
    at Errors.get [as api] (tsdi.js:213)
    at Errors.render (errors.tsx:12)
    at Object.allowStateChanges (mobx.module.js:921)
    at index.js:645
    at trackDerivedFunction (mobx.module.js:2811)
    at Reaction.track (mobx.module.js:2979)
    at reactiveRender (index.js:640)

The component in question looks like this:

@component
export class ApiStore {
    @observable public authToken: string;
    @observable public requests: Requests = {};
    @observable public errors: ApiError[] = [];

    ...
}

And the store is injected like this:

@observer @external
export class Errors extends React.PureComponent<{}> {
    @inject private api: ApiStore;

    public render() {
        const { latestError, doDismiss } = this.api;
        if (!latestError) {
            return null; // tslint:disable-line
        }
        return (
            <Modal onClose={doDismiss} open>
                <Modal.Header>
                    An error occured
                </Modal.Header>
                <Modal.Content>
                    <Modal.Description>
                        {latestError.message}
                    </Modal.Description>
                </Modal.Content>
            </Modal>
        );
    }
}

property injection bug

import {Component, Inject, TSDI} from 'tsdi'

const myfunc = () => {
  return () => 'foo'
}

@Component()
class Foo {

  constructor (@Inject('func') public func: Function) {}
}

const c = new TSDI()
c.enableComponentScanner();
c.addProperty('foo', myfunc())

console.log(c.get(Foo).func())

throws

node_modules/tsdi/lib/decorators.ts:194
        const factoryMetadata = metadata as FactoryMetadata;
              ^
TypeError: Cannot read property 'rtti' of undefined
    at TSDI.getOrCreate (node_modules/tsdi/lib/decorators.ts:194:15)
    at node_modules/tsdi/lib/decorators.ts:180:28
    at Array.map (native)
    at TSDI.getConstructorParameters (node_modules/tsdi/lib/decorators.ts:180:10)
    at TSDI.getOrCreate (node_modules/tsdi/lib/decorators.ts:201:24)
    at TSDI.get (node_modules/tsdi/lib/decorators.ts:255:2)

its line 127 in the transpiled JS file...dont know whats wrong here...

its injected as property because the DIC lacks support for registering non-constructables.
but the real problem seems to be that its a ready instance.

i think this could be fixed if we have TSDI.addInstance(name : string, instance: Constructable|Function) or register accepts already setup instances of something.

State of this project

Hey @KnisterPeter

I just wanted to get a quick and honest assessment on the state of this project.
There haven't been any releases since roughly two years and barely any activity besides lots of dependency updates.

Is this something you would encourage to use in a production environment or rather for personal/hobby projects?

discussion: decorator-name suggestions

For me (or especially others) the keywords component or external might mean anything or nothing. So, when working with these, I found it useful, to give them more expressives names. For some time now, I am using the following syntax in my project:

component as injectable
external as canInject in connection with inject
initialize as constructor

What are your thoughts on this?

TSDI breaks with Factory returning interface

When creating a @factory which returns an interface type TSDI terminates with an exception

Uncaught TypeError: Cannot read property 'name' of undefined
    at TSDI.registerComponent (tsdi.js:82)
    at listener (tsdi.js:60)
    at global-state.js:24
    at Array.forEach (<anonymous>)
    at Object.addListener (global-state.js:24)
    at TSDI.enableComponentScanner (tsdi.js:64)
    at main (index.tsx:18)
    at Object.<anonymous> (index.tsx:33)
    at __webpack_require__ (bootstrap 2202acb43ec7175b6296:19)
    at Object.<anonymous> (bundle.js:43175)

The code for the factory in questions looks like this:

import { component, factory } from "tsdi";
import { createBrowserHistory, History } from "history";

@component
export class HistoryFactory {
    @factory({ name: "history" })
    public createHistory(): History {
        return createBrowserHistory();
    }
}

with History being of type interface.

Add componenent onReady lifecycle

Currently components have an onCreate and onDestroy lifecycle.
They should also have an onInitialized lifecycle to be notified whenever a component (maybe async initialized) is ready to be used.

Factories with Arguments

it should be possible to pass argument to factories:

@Component()
class Foo {
    
    @Factory()
    public static create(foo, bar){
        return new Foo(foo, bar);
    }
}

maybe it could be solved right now when foo and bar are Container properties?! But putting them into the container is not really cool i think

refactor: external components '__tsdi__' property

External components have a 'tsdi' property added during registration of the externals.
This is global to the external component and prevents tsdi from being able to have multiple containers or a hierarchy of containers.
This limitation should be removed.

Add container lifecycle

It would be more easy to have lifecycle on the containers (e.g. to have a 'ready' callback).
This should be resolved to be ready when all eager components are initialized and ready to be used.

Switch from cypress to playwright

Since we aren't using cypress as a dev tool but only as a test runner, playwright would be better, since then we could run the tests in chrome, firefox, edge and safari.

Feature request: defer eager component creation

When there are multiple components with dependencies and one of it is eager and gets registered first, then creation fails because the dependencies might not be known at that time.

Defering eager creation would solve this issue.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.