Papaya
Papaya is a dependency injection container. It's a way to organize your JavaScript application to take advantage of the dependency inversion principle.
Installation
Papaya is available as a npm package.
npm
npm install --save papaya
Getting Started
The examples here use modern JavaScript syntax, but Papaya is compatible back to ES5.
To start, create a new instance of Papaya:
const Papaya = const app =
The methods you will use most often in Papaya are the get
, constant
, and
service
methods. These allow you to create and access services and
attributes.
// setting a constantapp // setting a serviceapp // Now we access the api service// This would typically be done in a controllerapp
In this example, we set up and use an api service.
- First, we use the
constant
method to create an attribute calledapi.url
. There is no special meaning to the.
in the service name. It's used purely for clarity. - Then, we create a service called
api
. This time we use theservice
method. This allows the service to be instantiated asynchronously. TheRestApi
instance won't be created until we use it on the final line. - On the last line, we use the
get
method to retrieve the instance ofRestApi
and call arequest
method on it. Because of the way we defined theapi
service, theapi.url
parameter will be passed into theRestApi
constructor when it is created.
Once it's constructed, the api
service will be cached, so if we call it again,
Papaya will use the same instance of RestApi
.
Organizing Your Project
Feel free to manage your containers however you like, but this is the pattern I typically use. To make it easier to reuse your container, you may want to extend the Papaya class.
# Appjs const env = const db = moduleexports = { super this this }
Then split up your services into logical groups and move them into separate provider files.
# providers/envjs module { app app}
# providers/dbjsconst Database = module { app}
Now when you want to boot your app, just create a new instance of your custom class.
const App = const app = app
TypeScript
Papaya fully supports both JavaScript and TypeScript. To use types with your container, you should define interfaces for each service.
# app.ts
# providers/env.ts
# providers/db.ts // for providers with dependencies, define the types of the dependencies
If you strictly define your service interfaces this way, the TypeScript compiler will be able to do compile-time type checking on your services.
# The TypeScript compiler knows that get returns a "Database"
If you want to turn off type checking, simply set the Papaya type to any
.
// to extend Papaya // or to create a one-off instance // typescript will allow this// but doesn't gurantee that get returns a "Database"
API
constant(name, constant)
Creates a simple named value service from constant
. The most common use of
attributes is to provide parameters for other services.
// Create an attributeappconst prefix = app // http://example.com
service(name, service)
Creates a singleton service.
Creates a singleton service. This is a service that will only be constructed
once. Services are lazy, so it will not be created until it is used. When the
service is requested with get
, the function will be called to create the
service.
app app
Notice that
this
is used to access the Papaya instance. This is a matter of preference. Papaya setsthis
to itself when calling service functions. In this example,this
,app
, and thecontainer
argument are the same thing.
get(name)
Gets a service or attribute by name. get
returns the value of a service
regardless of the function that was used to create a service.
appappapp app // 'abc'app // '123'app // 'xyz'
factory(name, factory)
Creates a service that will be reconstructed every time it is used. factory
is similar to service
, but it does not cache the return value of your service
function.
app // Calls the factory function aboveconst request = app // Calls the factory function againconst otherRequest = app // request !== otherRequest
extend(name, extender(extended, this))
The extend
method can be used to modify existing services.
app app app // Creates RestApi with MyPlugin added
In this example, we use extend
to add a plugin to the api service. Because we
defined api
as a singleton service above, it will remain a singleton. It will
also remain lazy, meaning the api
service and the extender will not be called
until the api
service is used.
When extending a factory, it will remain a factory service, otherwise it will be
converted to a singleton service (like when using the service
method).
Services can also be extended multiple times.
The extender
function is passed 2 arguments, the previous value of the service
and the container. If the service does not exist when it is extended, extend
will throw an error. The extender
function should return the new value for the
service.
register(provider)
Registers a provider
function, a convenient way to organize services into
groups.
{ container container} app
The register
method itself does not create services, but it allows you to
register functions that create related services. In this example, we use
register
to group api related services together.
The provider
function will be called immediately when it is registered. Its
this
and first argument will be set to the Papaya instance, just like in
service
.
keys()
Get an array of all the registered service names with keys
.
appapp app // ['foo', 'bar']
has(name)
Check if a given service is registered with has
.
app app // trueapp // false
Changes
Version 3
- Support strict TypeScript types
Version 2
- Add typescript support
- Split the
set
method intoservice
andconstant
. - Remove the
protect
method (replaced byconstant
) - Constants are no longer allowed in place of service functions.
- All service functions are now passed the container as an argument
Credits
Created by Justin Howard
Thank you to Fabien Potencier, the creator of Pimple for PHP for the inspiration for Papaya.