{"id":15046238,"url":"https://github.com/getlarge/loopback-callback-component","last_synced_at":"2026-02-06T23:32:12.744Z","repository":{"id":37874591,"uuid":"236705364","full_name":"getlarge/loopback-callback-component","owner":"getlarge","description":"Generate callbacks described like OpenAPI in loopback4","archived":false,"fork":false,"pushed_at":"2023-01-24T01:14:32.000Z","size":959,"stargazers_count":0,"open_issues_count":12,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-04T22:46:57.823Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/getlarge.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-01-28T10:05:45.000Z","updated_at":"2020-03-14T10:50:22.000Z","dependencies_parsed_at":"2023-02-13T08:01:08.104Z","dependency_job_id":null,"html_url":"https://github.com/getlarge/loopback-callback-component","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/getlarge/loopback-callback-component","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Floopback-callback-component","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Floopback-callback-component/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Floopback-callback-component/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Floopback-callback-component/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/getlarge","download_url":"https://codeload.github.com/getlarge/loopback-callback-component/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Floopback-callback-component/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266510687,"owners_count":23940696,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-09-24T20:52:53.811Z","updated_at":"2026-02-06T23:32:07.706Z","avatar_url":"https://github.com/getlarge.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# loopback-callback-component\n\nA 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)\n\n\n## Installation\n\nRun the following command to install `loopback-callback-component`:\n\n```npm\nnpm i -s loopback-callback-component\n```\n\n## Usage\n\n### Import component \n\nWhen the `loopback-callback-component` package is installed, bind it to your application with `app.component()`\n\n```typescript\nimport {RestApplication} from '@loopback/rest';\nimport {CallbackComponent, CallbackBindings, CallbackStrategyProvider} from 'loopback-callback-component';\n\nconst app = new RestApplication();\n\napp.component(CallbackComponent);\napp.bind(CallbackBindings.CALLBACK_STRATEGY).toProvider(CallbackStrategyProvider);\n\n```\n\n### Strategy provider\n\nCreate a strategy provider that implements your custom logic :\n\n```typescript\nimport {inject, Provider, ValueOrPromise} from '@loopback/core';\nimport {Request, Response} from '@loopback/rest';\nimport {\n  CallbackBindings,\n  CallbackStrategy,\n  CallbackMetadata,\n  CallbackObject,\n  isRuntimeExpression,\n  resolveTriggerName,\n  resolvePayload,\n} from 'loopback-callback-component';\n\nexport class CallbackStrategyProvider implements Provider\u003cCallbackStrategy | undefined\u003e {\n  constructor(@inject(CallbackBindings.METADATA) private metadata: CallbackMetadata) {}\n\n  value(): ValueOrPromise\u003cCallbackStrategy | undefined\u003e {\n    return {\n      checkCallback: async (request: Request, response: Response, options?: Object) =\u003e {\n        if (!this.metadata) {\n          return Promise.resolve(undefined);\n        }\n\n        // use this.metadata.parent to retrieve Oas path ?\n        const callbackObject: CallbackObject = {\n          [this.metadata.expression]: {\n            [this.metadata.method]: {\n              operationId: `${this.metadata.name}`,\n              description: `${this.metadata.name} callback`,\n              requestBody: request.body,\n              parameters: request.params,\n              // url: request.url,\n              // method: request.method,\n            },\n          },\n        };\n        return Promise.resolve(callbackObject);\n      },\n\n      resolveCallback: async (callback: CallbackObject, result: any) =\u003e {\n        if (!this.metadata) {\n          throw new Error('Callback metadata are mandatory to resolve the callback');\n        }\n        let value = this.metadata.expression;\n        const method = this.metadata.method;\n        const cbName = this.metadata.name;\n        const resolveData = {\n          usedPayload: callback[value][method].requestBody || {},\n          usedParams: callback[value][method].parameters,\n          usedRequestOptions: {method},\n        };\n\n        // Replace callback expression with appropriate values\n        let topic: string;\n        if (value.search(/{|}/) === -1) {\n          topic = isRuntimeExpression(value)\n            ? resolveTriggerName(cbName, value, resolveData, result)\n            : value;\n        } else {\n          const cbParams = value.match(/{([^}]*)}/g) ?? [];\n          cbParams.forEach(cbParam =\u003e {\n            value = value.replace(\n              cbParam,\n              resolveTriggerName(\n                cbName,\n                cbParam.substring(1, cbParam.length - 1),\n                resolveData,\n                result,\n              ),\n            );\n          });\n          topic = value;\n        }\n\n        const payload = resolvePayload(cbName, result, 'string');\n        // console.log('resolved callback', topic, payload);\n        return {topic, payload};\n      },\n    };\n  }\n}\n\n\n```\n### Custom sequence \n\nTo create some callback to specific endpoint you first need to add this kind of logic in a loopback sequence :\n\n```typescript\nimport {inject} from '@loopback/context';\nimport {\n  FindRoute,\n  InvokeMethod,\n  ParseParams,\n  Reject,\n  RequestContext,\n  RestBindings,\n  Send,\n  SequenceHandler,\n} from '@loopback/rest';\nimport {\n  CallbackBindings,\n  CallbackCheckFn,\n  CallbackResolveFn\n} from 'loopback-callback-component';\n\nconst SequenceActions = RestBindings.SequenceActions;\n\nexport class MySequence implements SequenceHandler {\n  constructor(\n    @inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,\n    @inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,\n    @inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,\n    @inject(SequenceActions.SEND) public send: Send,\n    @inject(SequenceActions.REJECT) public reject: Reject,\n    @inject(CallbackBindings.CALLBACK_RESOLVE) protected resolveCallback: CallbackResolveFn,\n    @inject(CallbackBindings.CALLBACK_CHECK) protected checkCallback: CallbackCheckFn,\n  ) {}\n\n\n  async handle(context: RequestContext) {\n    try {\n      const {request, response} = context;\n      const route = this.findRoute(request);\n      const args = await this.parseParams(request, route);\n\n      const result = await this.invoke(route, args);\n\n      let callback = await this.checkCallback(request, response);\n      if (callback) {\n        // compose route and payload from CallbackObject with response, request object ...\n        const {topic, payload} = await this.resolveCallback(callback, request, result);\n        // then you can publish { topic, payload } via a PubSub system\n      }\n\n      this.send(response, result);\n    } catch (err) {\n      this.reject(context, err);\n    }\n  }\n}\n\n```\n### Use in a controller\n\nAdd 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.\n\n```typescript\nimport {inject} from '@loopback/context';\nimport {repository} from '@loopback/repository';\nimport {\n  post,\n  param,\n  get,\n  patch,\n  put,\n  Request,\n  requestBody,\n  RestBindings,\n} from '@loopback/rest';\nimport {callback} from 'loopback-callback-component';\nimport {Device} from '../models';\nimport {DeviceApi, devicesApiEndPoint} from '../services';\nimport {getToken} from '../utils';\n\nconst security = [\n  {\n    Authorization: [],\n  },\n];\n\nexport class DeviceController {\n  constructor(\n    @inject('services.DeviceApi') protected deviceApi: DeviceApi,\n    @inject(RestBindings.Http.REQUEST) public request: Request,\n  ) {}\n\n  // Adding callback decorator\n  @callback(\n    'deviceWatcher',\n    '/api/{$response.body#/ownerId}/devices/{$method}/{$response.body#/id}',\n    'post',\n    {path: `/${devicesApiEndPoint}`, method: 'post'},\n  )\n  @post(`/${devicesApiEndPoint}`, {\n    operationId: 'createDevice',\n    security,\n    responses: {\n      '200': {\n        description: 'Device instance',\n        content: {'application/json': {schema: {'x-ts-type': Device}}},\n      },\n    },\n    // todo add callbacks definition via decorator \n  })\n  async create(@requestBody() device: Device): Promise\u003cDevice\u003e {\n    const token = getToken(this.request);\n    return this.deviceApi.create(token, device);\n  }\n\n}\n\n```\n\n## TODO \n\n- Improve CallbackObject to mimic OpenAPI CallbackObject\n\n- Use decorator to customize OpenAPI schema [example](https://loopback.io/doc/en/lb4/Extending-OpenAPI-specification.html)\n\n\n## License\n\n[![LoopBack](\u003chttps://github.com/strongloop/loopback-next/raw/master/docs/site/imgs/branding/Powered-by-LoopBack-Badge-(blue)-@2x.png\u003e)](http://loopback.io/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetlarge%2Floopback-callback-component","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgetlarge%2Floopback-callback-component","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetlarge%2Floopback-callback-component/lists"}