ForrestJS API » Create Extension

ForrestJS API » Create Extension


Create Extension

Services and Features interact with the App's Lifecycle and with each others by creating new Extensions.

CLASSIC EXAMPLE:

  1. Our App uses a Service to establish a connection to Postgres
  2. A Feature needs to run a SQL query as soon the connection exists

👉 We solved this situation in the My First REST API / Integrate Services and Features tutorial. Go check it out!


It's Just a Regular Function

It's important to understand that an Extension is just a programmable function that we call within a registered Action:

const myService = {
  target: '$INIT_SERVICE',
  handler: ({ createExtension }) => {
    console.log('myService::init()');

    // Create an Extension:
    createExtension.sync('foobar');

    console.log('myService::afterInit');
  },
};

const myFeature = {
  target: 'foobar',
  handler: () => console.log('myFeature, on myService::init'),
};

forrest.run({
  services: [myService],
  features: [myFeature],
});

This simple App defines a custom Service that:

  1. registers an Action into the App's Lifecyle
  2. log something
  3. creates an Extension allowing other Features to inject their logic
  4. log something else

The custom Feature registers an Action into that Extension, injecting new logic into it.


💻 Live on CodeSandbox:
https://codesandbox.io/s/create-hook-sync-04yy4?file=/src/index.js


👉 The baseline is that creating an Extension is just like running a regular function, but this function behaves like a black-box, and we can't know in advance who will register Actions into it.

As with functions:

  • an Extension has a unique name, that is simply a string
  • you can pass arguments into an Extension
  • you can collect the returning values from each registered Action

The Extension Targets Registry

So far we used simple strings as Extensions' names.

That makes it for a very fast development cycle, but it opens up the Gates of Hell for typos and any kind of nasty mistakes.

Don't worry: there is a way around this issue that solves most of the typos scenario, while preserving coding speed 😎.

The first step is to build an _Extensions Targets Registry for your Service. It is just a key/value store where you will list all the extension points that your Service will use:

const myServiceExtensions = {
  MY_SERVICE_INIT: 'myService/init',
  MY_SERVICE_START: 'myService/start',
};

Then you jump into your Service Manifest's function and register it:

const myService = ({ registerAction, registerTargets }) => {
  // Register the Extension Targets
  registerTargets(myServiceExtensions);

  // ... register your actions
};

This will build an internal dictionary of all the Extensions available to the Application.

😎 From now on, it's easy.

First, you can formally refer to the Registry when you create your Extensions:

// Create your Extensions using symbols from the manifest:
createExtension.sync('$MY_SERVICE_INIT');

And then you can reference back when you register your Actions:

registerAction({
  target: '$MY_SERVICE_INIT',
  handler: () => console.log('firstFeature, on myService::init'),
});

💻 Live on CodeSandbox:
https://codesandbox.io/s/create-hook-reference-ewqkn?file=/src/index.js


By using the $ sign at the beginning of your Target's name you ask ForrestJS to formally validate its existance against the internal Extension Targets Registry.

If the Extension does not exists,ForrestJS will throw an error and your App will fail to start. So you will be able to catch on all those silly mistakes in real time while building your Features.


Passing Arguments to the Action Handlers

When you create an Extension you can pass arguments to it as a single object:

createExtension.sync('extensionName', {
  foo: 'bar',
});

registerAction({
  target: 'extensionName',
  handler: (params) => console.log(`foo: ${params.foo}`),
});

This mechanic is often used to pass down APIs and/or utility functions.


💻 Live on CodeSandbox:
https://codesandbox.io/s/create-hook-arguments-pgmwz?file=/src/index.js


Collecting Action Handlers Returing Values

It is also possible to collect and manipulate each registered Action's Handler returning values:

// Register some Actions:
registerAction({ target: 'foobar', handler: () => 5 });
registerAction({ target: 'foobar', handler: () => 10 });

// Run the Extension and collect the results:
const data = createExtension.sync('foobar');
console.log(data);

💻 Live on CodeSandbox:
https://codesandbox.io/s/create-hook-returning-values-66xq3?file=/src/index.js


Each Action Handler's returning value is represented by a positional array with:

  • [0] raw returning value
  • [1] Action's details
  • [2] Extension's details

[[TO BE EXPANDED]]

createExtension.sync()

The sync method is the most common and used type of Extension. It runs its registered Actions in serie, sorted by priority, and it expects only synchronous functions.

Use this method to provide ways to enrich or modify configuration at boot time. A good example is how we register routes in the Fastify Service.

const results = createExtension.sync('extensionName', { arg1: 'x', arg2: 'y' });

createExtension.serie()

Use the serie method when you want asynchronous Action Handlers to execute one after the other, by the registration or priority order.

The returning values will reflect the execution order.


💻 Live on CodeSandbox:
https://codesandbox.io/s/create-hook-serie-e6670?file=/src/index.js


createExtension.parallel()

Use the parallel method when you want asynchronous Action Handlers to execute at the same time, triggered by by the registration or priority order.

The returning values order will reflect the trigger order.

const results = createExtension.parallel('extensionName', {
  arg1: 'x',
  arg2: 'y',
});

💻 Live on CodeSandbox:
https://codesandbox.io/s/create-hook-parallel-qylzn?file=/src/index.js


createExtension.waterfall()

Use the waterfall method if you want to let synchronous Action Handlers to transform a received input by returning a new version of it.

The Action handlers are executed by the registration or priority order.

In the end, the returning value will yield the value that was returned by the last handler.

const result = createExtension.watefall('extensionName', 10);
console.log(result.value);

💻 Live on CodeSandbox:
https://codesandbox.io/s/create-hook-waterfall-j4hnc?file=/src/index.js


results matching ""

    No results matching ""