knifecycle
Manage your NodeJS processes's lifecycle automatically with an unobtrusive dependency injection implementation.
Most (maybe all) applications rely on two kinds of dependencies.
The code dependencies are fully covered by JavaScript modules in a testable
manner (with mockery
or System
directly). There is no need for another
dependency management system if those libraries are pure functions (involve no
global states at all).
Unfortunately, applications often rely on global states where the JavaScript
module system shows its limits. This is where knifecycle
enters the game.
It is largely inspired by the Angular service system except it should not
provide code but access to global states (time, filesystem, db). It also have an
important additional feature to shutdown processes which is really useful for
back-end servers and doesn't exists in Angular.
You may want to look at the architecture notes to better
handle the reasonning behind knifecycle
and its implementation.
At this point you may think that a DI system is useless. My advice is that it
depends. But at least, you should not make a definitive choice and allow both
approaches. See
this StackOverflow answer
for more context about this statement.
Features
- services management: start services taking their dependencies in count and
shut them down the same way for graceful exits (namely dependency injection
with inverted control);
- singleton: maintain singleton services across several running execution silos.
- easy end to end testing: just replace your services per your own mocks and
stubs while ensuring your application integrity between testing and
production;
- isolation: isolate processing in a clean manner, per concerns;
- functional programming ready: encapsulate global states allowing the rest of
your application to be purely functional;
- no circular dependencies for services: while circular dependencies are not a
problem within purely functional libraries (require allows it), it may be
harmful for your services,
knifecycle
impeach that while providing an
$injector
service à la Angular to allow accessing existing services
references if you really need to; - generate Mermaid graphs of the dependency tree;
- build raw initialization modules to avoid embedding Knifecycle in your builds;
- optionally autoload services dependencies with custom logic.
Usage
Using knifecycle
is all about declaring the services our application needs and
running your application over it.
Let's say we are building a CLI script. Here is how we would proceed with
Knifecycle:
First, we need to handle a configuration file so we are creating an initializer
to instanciate our CONFIG
service:
import fs from 'fs';
import YError from 'YError';
import Knifecycle, { initializer, constant, inject, name } from 'knifecycle';
const $ = new Knifecycle();
$.register(constant('ENV', process.env));
$.register(constant('ARGS', process.argv));
async function initConfig({ ENV = { CONFIG_PATH: '.' } }) {
return new Promise((resolve, reject) => {
fs.readFile(ENV.CONFIG_PATH, 'utf-8', (err, data) => {
if (err) {
reject(err);
return;
}
try {
resolve(JSON.parse(data));
} catch (err) {
reject(err);
}
});
});
}
$.register(
initializer(
{
name: 'CONFIG',
inject: ['?ENV'],
type: 'service',
options: { singleton: true },
},
initConfig,
),
);
const initDB = initializer(
{
name: 'db',
inject: ['CONFIG', 'DB_URI', '?log'],
type: 'provider',
options: { singleton: true },
},
async ({ CONFIG, DB_URI, log }) => {
const db = await MongoClient.connect(DB_URI, CONFIG.databaseOptions);
let fatalErrorPromise = new Promise((resolve, reject) => {
db.once('error', reject);
});
log && log('info', 'db service initialized!');
return {
service: db,
dispose: db.close.bind(db, true),
fatalErrorPromise,
};
},
);
$.register(constant('DB_URI', 'posgresql://xxxx'));
$.register(initDB);
$.register(constant('DB_URI2', 'posgresql://yyyy'));
$.register(
inject(
['CONFIG', 'DB_URI2>DB_URI', '?log'],
name('db2', initDB),
),
);
$.register(constant('now', Date.now.bind(Date)))
.register(constant('log', console.log.bind(console)))
.register(constant('exit', process.exit.bind(process)));
$.register(
initializer(
{
name: '$autoload',
type: 'service',
inject: ['CONFIG', 'ARGS'],
options: { singleton: true },
},
async ({ CONFIG, ARGS }) => async (serviceName) => {
if ('command' !== serviceName) {
throw new YError('E_UNMATCHED_DEPENDENCY', serviceName);
}
try {
const path = CONFIG.commands + '/' + ARGS[2];
return {
path,
initializer: require(path).default,
};
} catch (err) {
throw new Error(`Cannot load ${serviceName}: ${ARGS[2]}!`);
}
},
),
);
$.run(['command', '$instance', 'exit', 'log'])
.then(async ({ command, $instance, exit, log }) => {
try {
command();
log('It worked!');
} catch (err) {
log('It failed!', err);
} finally {
await $instance.destroy().catch((err) => {
console.error('Could not exit gracefully:', err);
exit(1);
});
}
})
.catch((err) => {
console.error('Could not launch the app:', err);
process.exit(1);
});
Running the following should make the magic happen:
cat "{ commands: './commands'}" > config.json
DEBUG=knifecycle CONFIG_PATH=./config.json node -r @babel/register bin.js mycommand test
// Prints: Could not launch the app: Error: Cannot load command: mycommand!
// (...stack trace)
Or at least, we still have to create commands, let's create the mycommand
one:
import { initializer } from './dist';
export default initializer(
{
name: 'command',
type: 'service',
inject: ['ARGS', 'log'],
},
async ({ ARGS, log }) => {
return () => log('Command args:', ARGS.slice(2));
},
);
So now, it works:
DEBUG=knifecycle CONFIG_PATH=./config.json node -r @babel/register bin.js mycommand test
// Prints: Command args: [ 'mycommand', 'test' ]
// It worked!
This is a very simple example but you can find a complexer CLI usage with
(metapak)[https://github.com/nfroidure/metapak/blob/master/bin/metapak.js]
.
Auto detection
Knifecycle also provide some utility function to automatically assign the
initializer property declarations, the following 3 ways to declare the getUser
service are equivalent:
import noop from 'noop';
import { autoInject, inject, initializer, autoService } from 'knifecycle';
initializer({
name: 'getUser',
inject: ['db', '?log'],
type: 'service',
}, getUser);
service('getUser', autoInject(getUser)));
autoService(getUser);
async function getUser({ db, log = noop}) {}
That said, if you need to build your code with webpack
/babel
you may have to
convert auto-detections to raw declarations with the
babel-plugin-knifecycle
plugin. You can also do this only for the performance improvements it brings.
Also, keep in mind that the auto-detection is based on a simple regular
expression so you should care to keep initializer signatures simple to avoid
having a E_AUTO_INJECTION_FAILURE
error. As a rule of thumb, avoid setting
complex default values.
autoInject(async ({ log = () => {} }) => {});
function noop() {}
autoInject(async ({ log = noop }) => {});
Debugging
Simply use the DEBUG environment variable by setting it to 'knifecycle':
DEBUG=knifecycle npm t
The output is very verbose but lead to a deep understanding of mechanisms that
take place under the hood.
Plans
The scope of this library won't change. However the plan is:
- improve performances;
- evolve with Node: I may not need to transpile this library at some point.
- track bugs ;).
I'll also share most of my own initializers and their stubs/mocks in order to
let you reuse it through your projects easily. Here are the current projects
that use this DI lib:
- common-services: contains the
services I use the most in my apps.
- swagger-http-router: a
complete HTTP router based on OpenAPI definitions with a few useful services
compatible with Knifecycle.
- memory-kv-store: a simple in
memory key-value store.
- whook: a framework to build REST web
services.
- postgresql-service: a
simple wrapper around the
pg
module. - jwt-service: a simple wrapper
around the
jwt
module to simplify its use.
Notice that those modules remains usable without using Knifecycle at all which
is maybe the best feature of this library ;).
API
Classes
- Knifecycle
Functions
- initInitializerBuilder(services) ⇒
Promise.<function()>
Instantiate the initializer builder service
- reuseSpecialProps(from, to, [amend]) ⇒
function
Apply special props to the given function from another one
- wrapInitializer(wrapper, baseInitializer) ⇒
function
Allows to wrap an initializer to add extra initialization steps
- inject(dependencies, initializer) ⇒
function
Decorator creating a new initializer with different
dependencies declarations set to it.
- useInject(from, to) ⇒
function
Apply injected dependencies from the given initializer to another one
- mergeInject(from, to) ⇒
function
Merge injected dependencies of the given initializer with another one
- autoInject(initializer) ⇒
function
Decorator creating a new initializer with different
dependencies declarations set to it according to the
given function signature.
- alsoInject(dependencies, initializer) ⇒
function
Decorator creating a new initializer with some
more dependencies declarations appended to it.
- extra(extraInformations, initializer, [merge]) ⇒
function
Decorator creating a new initializer with some
extra informations appended to it. It is just
a way for user to store some additional
informations but has no interaction with the
Knifecycle internals.
- options(options, initializer, [merge]) ⇒
function
Decorator to amend an initializer options.
- name(name, initializer) ⇒
function
Decorator to set an initializer name.
- autoName(initializer) ⇒
function
Decorator to set an initializer name from its function name.
- type(type, initializer) ⇒
function
Decorator to set an initializer type.
- initializer(properties, initializer) ⇒
function
Decorator to set an initializer properties.
- constant(name, initializer) ⇒
function
Decorator that creates an initializer for a constant value
- service(builder, [name], [dependencies], [options]) ⇒
function
Decorator that creates an initializer for a service
- autoService(initializer) ⇒
function
Decorator that auto creates a service
- provider(builder, [name], [dependencies], [options]) ⇒
function
Decorator that creates an initializer for a provider
- autoProvider(initializer) ⇒
function
Decorator that auto creates a provider
- handler(handlerFunction, [name], [dependencies], [options]) ⇒
function
Shortcut to create an initializer with a simple handler
- autoHandler(handlerFunction) ⇒
function
Allows to create an initializer with a simple handler automagically
- parseDependencyDeclaration(dependencyDeclaration) ⇒
Object
Explode a dependency declaration an returns its parts.
- stringifyDependencyDeclaration(dependencyDeclarationParts) ⇒
String
Stringify a dependency declaration from its parts.
Knifecycle
Kind: global class
new Knifecycle()
Create a new Knifecycle instance
Returns: Knifecycle
- The Knifecycle instance
Example
import Knifecycle from 'knifecycle'
const $ = new Knifecycle();
knifecycle.register(initializer) ⇒ Knifecycle
Register an initializer
Kind: instance method of Knifecycle
Returns: Knifecycle
- The Knifecycle instance (for chaining)
Param | Type | Description |
---|
initializer | function | An initializer |
knifecycle.toMermaidGraph(options) ⇒ String
Outputs a Mermaid compatible dependency graph of the declared services.
See Mermaid docs
Kind: instance method of Knifecycle
Returns: String
- Returns a string containing the Mermaid dependency graph
Param | Type | Description |
---|
options | Object | Options for generating the graph (destructured) |
options.shapes | Array.<Object> | Various shapes to apply |
options.styles | Array.<Object> | Various styles to apply |
options.classes | Object | A hash of various classes contents |
Example
import Knifecycle, { inject, constant, service } from 'knifecycle';
import appInitializer from './app';
const $ = new Knifecycle();
$.register(constant('ENV', process.env));
$.register(constant('OS', require('os')));
$.register(service('app', inject(['ENV', 'OS'], appInitializer)));
$.toMermaidGraph();
graph TD
app-->ENV
app-->OS
knifecycle.run(dependenciesDeclarations) ⇒ Promise
Creates a new execution silo
Kind: instance method of Knifecycle
Returns: Promise
- Service descriptor promise
Param | Type | Description |
---|
dependenciesDeclarations | Array.<String> | Service name. |
Example
import Knifecycle, { constant } from 'knifecycle'
const $ = new Knifecycle();
$.register(constant('ENV', process.env));
$.run(['ENV'])
.then(({ ENV }) => {
})
knifecycle.destroy() ⇒ Promise
Destroy the Knifecycle instance
Kind: instance method of Knifecycle
Returns: Promise
- Full destruction promise
Example
import Knifecycle, { constant } from 'knifecycle'
const $ = new Knifecycle();
$.register(constant('ENV', process.env));
$.run(['ENV'])
.then(({ ENV }) => {
$.destroy()
})
knifecycle._getServiceDescriptor(siloContext, serviceName, options, serviceProvider) ⇒ Promise
Initialize or return a service descriptor
Kind: instance method of Knifecycle
Returns: Promise
- Service dependencies hash promise.
Param | Type | Description |
---|
siloContext | Object | Current execution silo context |
serviceName | String | Service name. |
options | Object | Options for service retrieval |
options.injectorContext | Boolean | Flag indicating the injection were initiated by the $injector |
options.autoloading | Boolean | Flag to indicating $autoload dependencies on the fly loading |
serviceProvider | String | Service provider. |
knifecycle._initializeServiceDescriptor(siloContext, serviceName, options) ⇒ Promise
Initialize a service descriptor
Kind: instance method of Knifecycle
Returns: Promise
- Service dependencies hash promise.
Param | Type | Description |
---|
siloContext | Object | Current execution silo context |
serviceName | String | Service name. |
options | Object | Options for service retrieval |
options.injectorContext | Boolean | Flag indicating the injection were initiated by the $injector |
options.autoloading | Boolean | Flag to indicating $autoload dependendencies on the fly loading. |
knifecycle._initializeDependencies(siloContext, serviceName, servicesDeclarations, options) ⇒ Promise
Initialize a service dependencies
Kind: instance method of Knifecycle
Returns: Promise
- Service dependencies hash promise.
Param | Type | Description |
---|
siloContext | Object | Current execution silo siloContext |
serviceName | String | Service name. |
servicesDeclarations | String | Dependencies declarations. |
options | Object | Options for service retrieval |
options.injectorContext | Boolean | Flag indicating the injection were initiated by the $injector |
options.autoloading | Boolean | Flag to indicating $autoload dependendencies on the fly loading. |
initInitializerBuilder(services) ⇒ Promise.<function()>
Instantiate the initializer builder service
Kind: global function
Returns: Promise.<function()>
- A promise of the buildInitializer function
Param | Type | Description |
---|
services | Object | The services to inject |
services.$autoload | Object | The dependencies autoloader |
Example
import initInitializerBuilder from 'knifecycle/dist/build';
const buildInitializer = await initInitializerBuilder({
$autoload: async () => {},
});
initInitializerBuilder~buildInitializer(dependencies) ⇒ Promise.<String>
Create a JavaScript module that initialize
a set of dependencies with hardcoded
import/awaits.
Kind: inner method of initInitializerBuilder
Returns: Promise.<String>
- The JavaScript module content
Param | Type | Description |
---|
dependencies | Array.<String> | The main dependencies |
Example
import initInitializerBuilder from 'knifecycle/dist/build';
const buildInitializer = await initInitializerBuilder({
$autoload: async () => {},
});
const content = await buildInitializer(['entryPoint']);
reuseSpecialProps(from, to, [amend]) ⇒ function
Apply special props to the given function from another one
Kind: global function
Returns: function
- The newly built function
Param | Type | Default | Description |
---|
from | function | | The initialization function in which to pick the props |
to | function | | The initialization function from which to build the new one |
[amend] | Object | {} | Some properties to override |
wrapInitializer(wrapper, baseInitializer) ⇒ function
Allows to wrap an initializer to add extra initialization steps
Kind: global function
Returns: function
- The new initializer
Param | Type | Description |
---|
wrapper | function | A function taking dependencies and the base service in arguments |
baseInitializer | function | The initializer to decorate |
inject(dependencies, initializer) ⇒ function
Decorator creating a new initializer with different
dependencies declarations set to it.
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Description |
---|
dependencies | Array.<String> | List of dependencies declarations to declare which services the initializer needs to resolve its own service |
initializer | function | The initializer to tweak |
Example
import Knifecycle, { inject } from 'knifecycle'
import myServiceInitializer from './service';
new Knifecycle()
.register(
service(
inject(['ENV'], myServiceInitializer)
'myService',
)
)
);
useInject(from, to) ⇒ function
Apply injected dependencies from the given initializer to another one
Kind: global function
Returns: function
- The newly built initialization function
Param | Type | Description |
---|
from | function | The initialization function in which to pick the dependencies |
to | function | The destination initialization function |
mergeInject(from, to) ⇒ function
Merge injected dependencies of the given initializer with another one
Kind: global function
Returns: function
- The newly built initialization function
Param | Type | Description |
---|
from | function | The initialization function in which to pick the dependencies |
to | function | The destination initialization function |
autoInject(initializer) ⇒ function
Decorator creating a new initializer with different
dependencies declarations set to it according to the
given function signature.
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Description |
---|
initializer | function | The original initializer |
Example
import Knifecycle, { autoInject, name } from 'knifecycle'
new Knifecycle()
.register(
name(
'application',
autoInject(
async ({ NODE_ENV, mysql: db }) =>
async () => db.query('SELECT applicationId FROM applications WHERE environment=?', [NODE_ENV])
)
)
)
)
);
alsoInject(dependencies, initializer) ⇒ function
Decorator creating a new initializer with some
more dependencies declarations appended to it.
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Description |
---|
dependencies | Array.<String> | List of dependencies declarations to append |
initializer | function | The initializer to tweak |
Example
import Knifecycle, { alsoInject } from 'knifecycle'
import myServiceInitializer from './service';
new Knifecycle()
.register(service(
alsoInject(['ENV'], myServiceInitializer),
'myService',
));
Decorator creating a new initializer with some
extra informations appended to it. It is just
a way for user to store some additional
informations but has no interaction with the
Knifecycle internals.
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Default | Description |
---|
extraInformations | Object | | An object containing those extra informations. |
initializer | function | | The initializer to tweak |
[merge] | Boolean | false | Whether the extra object should be merged with the existing one or not |
Example
import Knifecycle, { extra } from 'knifecycle'
import myServiceInitializer from './service';
new Knifecycle()
.register(service(
extra({ httpHandler: true }, myServiceInitializer),
'myService',
));
options(options, initializer, [merge]) ⇒ function
Decorator to amend an initializer options.
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Default | Description |
---|
options | Object | | Options to set to the initializer |
options.singleton | Object | | Define the initializer service as a singleton (one instance for several runs) |
initializer | function | | The initializer to tweak |
[merge] | function | true | Whether options should be merged or not |
Example
import Knifecycle, { inject, options } from 'knifecycle';
import myServiceInitializer from './service';
new Knifecycle()
.register(service(
inject(['ENV'],
options({ singleton: true}, myServiceInitializer)
),
'myService',
));
name(name, initializer) ⇒ function
Decorator to set an initializer name.
Kind: global function
Returns: function
- Returns a new initializer with that name set
Param | Type | Description |
---|
name | String | The name of the service the initializer resolves to. |
initializer | function | The initializer to tweak |
Example
import Knifecycle, { name } from 'knifecycle';
import myServiceInitializer from './service';
new Knifecycle()
.register(name('myService', myServiceInitializer));
autoName(initializer) ⇒ function
Decorator to set an initializer name from its function name.
Kind: global function
Returns: function
- Returns a new initializer with that name set
Param | Type | Description |
---|
initializer | function | The initializer to name |
Example
import Knifecycle, { autoName } from 'knifecycle';
new Knifecycle()
.register(autoName(async function myService() {}));
type(type, initializer) ⇒ function
Decorator to set an initializer type.
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Description |
---|
type | String | The type to set to the initializer. |
initializer | function | The initializer to tweak |
Example
import Knifecycle, { name, type } from 'knifecycle';
import myServiceInitializer from './service';
new Knifecycle()
.register(
type('service',
name('myService',
myServiceInitializer
)
)
);
initializer(properties, initializer) ⇒ function
Decorator to set an initializer properties.
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Description |
---|
properties | Object | Properties to set to the service. |
initializer | function | The initializer to tweak |
Example
import Knifecycle, { initializer } from 'knifecycle';
import myServiceInitializer from './service';
new Knifecycle()
.register(initializer({
name: 'myService',
type: 'service',
inject: ['ENV'],
options: { singleton: true }
}, myServiceInitializer));
constant(name, initializer) ⇒ function
Decorator that creates an initializer for a constant value
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Description |
---|
name | String | The constant's name. |
initializer | any | The constant's value |
Example
import Knifecycle, { constant, service } from 'knifecycle';
const { printAnswer } = new Knifecycle()
.register(constant('THE_NUMBER', value))
.register(constant('log', console.log.bind(console)))
.register(service(
async ({ THE_NUMBER, log }) => () => log(THE_NUMBER),
'printAnswer',
['THE_NUMBER', 'log'],
))
.run(['printAnswer']);
printAnswer();
service(builder, [name], [dependencies], [options]) ⇒ function
Decorator that creates an initializer for a service
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Description |
---|
builder | function | An initializer returning the service promise |
[name] | String | The service's name |
[dependencies] | Array.<String> | The service's dependencies |
[options] | Object | Options attached to the built initializer |
Example
import Knifecycle, { constant, service } from 'knifecycle';
const { printAnswer } = new Knifecycle()
.register(constant('THE_NUMBER', value))
.register(constant('log', console.log.bind(console)))
.register(service(
async ({ THE_NUMBER, log }) => () => log(THE_NUMBER),
'printAnswer',
['THE_NUMBER', 'log'],
{ singleton: true }
))
.run(['printAnswer']);
printAnswer();
autoService(initializer) ⇒ function
Decorator that auto creates a service
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Description |
---|
initializer | function | An initializer returning the service promise |
provider(builder, [name], [dependencies], [options]) ⇒ function
Decorator that creates an initializer for a provider
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Description |
---|
builder | function | A builder returning the provider promise |
[name] | String | The service's name |
[dependencies] | Array.<String> | The service's dependencies |
[options] | Object | Options attached to the built initializer |
Example
import Knifecycle, { provider } from 'knifecycle'
import fs from 'fs';
const $ = new Knifecycle();
$.register(provider(configProvider, 'config'));
async function configProvider() {
return new Promise((resolve, reject) {
fs.readFile('config.js', function(err, data) {
let config;
if(err) {
reject(err);
return;
}
try {
config = JSON.parse(data.toString);
} catch (err) {
reject(err);
return;
}
resolve({
service: config,
});
});
});
}
autoProvider(initializer) ⇒ function
Decorator that auto creates a provider
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Description |
---|
initializer | function | An initializer returning the provider promise |
handler(handlerFunction, [name], [dependencies], [options]) ⇒ function
Shortcut to create an initializer with a simple handler
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Default | Description |
---|
handlerFunction | function | | The handler function |
[name] | String | | The name of the handler. Default to the DI prop if exists |
[dependencies] | Array.<String> | [] | The dependencies to inject in it |
[options] | Object | | Options attached to the built initializer |
Example
import Knifecycle, { handler } from 'knifecycle';
new Knifecycle()
.register(handler(getUser, 'getUser', ['db', '?log']));
const QUERY = `SELECT * FROM users WHERE id=$1`
async function getUser({ db }, userId) {
const [row] = await db.query(QUERY, userId);
return row;
}
autoHandler(handlerFunction) ⇒ function
Allows to create an initializer with a simple handler automagically
Kind: global function
Returns: function
- Returns a new initializer
Param | Type | Description |
---|
handlerFunction | function | The handler function |
Example
import Knifecycle, { autoHandler } from 'knifecycle';
new Knifecycle()
.register(autoHandler(getUser));
const QUERY = `SELECT * FROM users WHERE id=$1`
async function getUser({ db }, userId) {
const [row] = await db.query(QUERY, userId);
return row;
}
parseDependencyDeclaration(dependencyDeclaration) ⇒ Object
Explode a dependency declaration an returns its parts.
Kind: global function
Returns: Object
- The various parts of it
Param | Type | Description |
---|
dependencyDeclaration | String | A dependency declaration string |
Example
parseDependencyDeclaration('pgsql>db');
{
serviceName: 'pgsql',
mappedName: 'db',
optional: false,
}
stringifyDependencyDeclaration(dependencyDeclarationParts) ⇒ String
Stringify a dependency declaration from its parts.
Kind: global function
Returns: String
- The various parts of it
Param | Type | Description |
---|
dependencyDeclarationParts | Object | A dependency declaration string |
Example
stringifyDependencyDeclaration({
serviceName: 'pgsql',
mappedName: 'db',
optional: false,
});
'pgsql>db'
Authors
License
MIT