Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/getlarge/loopback-callback-component
Generate callbacks described like OpenAPI in loopback4
https://github.com/getlarge/loopback-callback-component
Last synced: 11 days ago
JSON representation
Generate callbacks described like OpenAPI in loopback4
- Host: GitHub
- URL: https://github.com/getlarge/loopback-callback-component
- Owner: getlarge
- Created: 2020-01-28T10:05:45.000Z (almost 5 years ago)
- Default Branch: master
- Last Pushed: 2023-01-24T01:14:32.000Z (almost 2 years ago)
- Last Synced: 2024-10-11T04:41:10.559Z (about 1 month ago)
- Language: TypeScript
- Size: 937 KB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 12
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# loopback-callback-component
A Callback component for LoopBack 4, trying to follow [GraphQL specs](https://github.com/graphql/graphql-spec/blob/master/rfcs/Subscriptions.md) and [OpenAPI Callback Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#callbackObject)
## Installation
Run the following command to install `loopback-callback-component`:
```npm
npm i -s loopback-callback-component
```## Usage
### Import component
When the `loopback-callback-component` package is installed, bind it to your application with `app.component()`
```typescript
import {RestApplication} from '@loopback/rest';
import {CallbackComponent, CallbackBindings, CallbackStrategyProvider} from 'loopback-callback-component';const app = new RestApplication();
app.component(CallbackComponent);
app.bind(CallbackBindings.CALLBACK_STRATEGY).toProvider(CallbackStrategyProvider);```
### Strategy provider
Create a strategy provider that implements your custom logic :
```typescript
import {inject, Provider, ValueOrPromise} from '@loopback/core';
import {Request, Response} from '@loopback/rest';
import {
CallbackBindings,
CallbackStrategy,
CallbackMetadata,
CallbackObject,
isRuntimeExpression,
resolveTriggerName,
resolvePayload,
} from 'loopback-callback-component';export class CallbackStrategyProvider implements Provider {
constructor(@inject(CallbackBindings.METADATA) private metadata: CallbackMetadata) {}value(): ValueOrPromise {
return {
checkCallback: async (request: Request, response: Response, options?: Object) => {
if (!this.metadata) {
return Promise.resolve(undefined);
}// use this.metadata.parent to retrieve Oas path ?
const callbackObject: CallbackObject = {
[this.metadata.expression]: {
[this.metadata.method]: {
operationId: `${this.metadata.name}`,
description: `${this.metadata.name} callback`,
requestBody: request.body,
parameters: request.params,
// url: request.url,
// method: request.method,
},
},
};
return Promise.resolve(callbackObject);
},resolveCallback: async (callback: CallbackObject, result: any) => {
if (!this.metadata) {
throw new Error('Callback metadata are mandatory to resolve the callback');
}
let value = this.metadata.expression;
const method = this.metadata.method;
const cbName = this.metadata.name;
const resolveData = {
usedPayload: callback[value][method].requestBody || {},
usedParams: callback[value][method].parameters,
usedRequestOptions: {method},
};// Replace callback expression with appropriate values
let topic: string;
if (value.search(/{|}/) === -1) {
topic = isRuntimeExpression(value)
? resolveTriggerName(cbName, value, resolveData, result)
: value;
} else {
const cbParams = value.match(/{([^}]*)}/g) ?? [];
cbParams.forEach(cbParam => {
value = value.replace(
cbParam,
resolveTriggerName(
cbName,
cbParam.substring(1, cbParam.length - 1),
resolveData,
result,
),
);
});
topic = value;
}const payload = resolvePayload(cbName, result, 'string');
// console.log('resolved callback', topic, payload);
return {topic, payload};
},
};
}
}```
### Custom sequenceTo create some callback to specific endpoint you first need to add this kind of logic in a loopback sequence :
```typescript
import {inject} from '@loopback/context';
import {
FindRoute,
InvokeMethod,
ParseParams,
Reject,
RequestContext,
RestBindings,
Send,
SequenceHandler,
} from '@loopback/rest';
import {
CallbackBindings,
CallbackCheckFn,
CallbackResolveFn
} from 'loopback-callback-component';const SequenceActions = RestBindings.SequenceActions;
export class MySequence implements SequenceHandler {
constructor(
@inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
@inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,
@inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
@inject(SequenceActions.SEND) public send: Send,
@inject(SequenceActions.REJECT) public reject: Reject,
@inject(CallbackBindings.CALLBACK_RESOLVE) protected resolveCallback: CallbackResolveFn,
@inject(CallbackBindings.CALLBACK_CHECK) protected checkCallback: CallbackCheckFn,
) {}async handle(context: RequestContext) {
try {
const {request, response} = context;
const route = this.findRoute(request);
const args = await this.parseParams(request, route);const result = await this.invoke(route, args);
let callback = await this.checkCallback(request, response);
if (callback) {
// compose route and payload from CallbackObject with response, request object ...
const {topic, payload} = await this.resolveCallback(callback, request, result);
// then you can publish { topic, payload } via a PubSub system
}this.send(response, result);
} catch (err) {
this.reject(context, err);
}
}
}```
### Use in a controllerAdd a decorator on specific endpoint, to trigger a callback with parameters that will be used to compose topic and payload, from the response/request in the custom sequence.
```typescript
import {inject} from '@loopback/context';
import {repository} from '@loopback/repository';
import {
post,
param,
get,
patch,
put,
Request,
requestBody,
RestBindings,
} from '@loopback/rest';
import {callback} from 'loopback-callback-component';
import {Device} from '../models';
import {DeviceApi, devicesApiEndPoint} from '../services';
import {getToken} from '../utils';const security = [
{
Authorization: [],
},
];export class DeviceController {
constructor(
@inject('services.DeviceApi') protected deviceApi: DeviceApi,
@inject(RestBindings.Http.REQUEST) public request: Request,
) {}// Adding callback decorator
@callback(
'deviceWatcher',
'/api/{$response.body#/ownerId}/devices/{$method}/{$response.body#/id}',
'post',
{path: `/${devicesApiEndPoint}`, method: 'post'},
)
@post(`/${devicesApiEndPoint}`, {
operationId: 'createDevice',
security,
responses: {
'200': {
description: 'Device instance',
content: {'application/json': {schema: {'x-ts-type': Device}}},
},
},
// todo add callbacks definition via decorator
})
async create(@requestBody() device: Device): Promise {
const token = getToken(this.request);
return this.deviceApi.create(token, device);
}}
```
## TODO
- Improve CallbackObject to mimic OpenAPI CallbackObject
- Use decorator to customize OpenAPI schema [example](https://loopback.io/doc/en/lb4/Extending-OpenAPI-specification.html)
## License
[![LoopBack]()](http://loopback.io/)