{"id":16241591,"url":"https://github.com/getlarge/loopback-pubsub-component","last_synced_at":"2026-05-12T23:35:41.148Z","repository":{"id":114684886,"uuid":"236175856","full_name":"getlarge/loopback-pubsub-component","owner":"getlarge","description":"Generic pubsub component for loopback 4","archived":false,"fork":false,"pushed_at":"2020-01-29T10:01:33.000Z","size":159,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-14T15:16:09.684Z","etag":null,"topics":["amqp","bridge","component","loopback4","mqtt-client","pubsub","redis"],"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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-01-25T13:47:19.000Z","updated_at":"2020-05-16T19:25:50.000Z","dependencies_parsed_at":null,"dependency_job_id":"45a15a5c-2700-412c-b5de-c2fa18d047a9","html_url":"https://github.com/getlarge/loopback-pubsub-component","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Floopback-pubsub-component","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Floopback-pubsub-component/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Floopback-pubsub-component/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Floopback-pubsub-component/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/getlarge","download_url":"https://codeload.github.com/getlarge/loopback-pubsub-component/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247819946,"owners_count":21001394,"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","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":["amqp","bridge","component","loopback4","mqtt-client","pubsub","redis"],"created_at":"2024-10-10T14:08:04.667Z","updated_at":"2026-05-12T23:35:41.119Z","avatar_url":"https://github.com/getlarge.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# loopback-pubsub-component\n\nA PubSub component for LoopBack 4, trying to follow [GraphQL specs](https://github.com/graphql/graphql-spec/blob/master/rfcs/Subscriptions.md) and inspired by [graphql-subscriptions](https://github.com/apollographql/graphql-subscriptions)\n\nThis is a generic component that can wraps \"any\" PubSub client/broker in your own repository and strategy provider.\n\n## Installation\n\nRun the following command to install `loopback-pubsub-component`:\n\n```npm\nnpm i -s loopback-pubsub-component\n```\n\n## Usage\n\n### Import component \n\nWhen the `loopback-pubsub-component` package is installed, bind it to your application with `app.component()`\n\n```typescript\nimport {RestApplication} from '@loopback/rest';\nimport {PubSubBindings, PubSubComponent, PubSubStrategyProvider} from 'loopback-pubsub-component';\n\nconst app = new RestApplication();\n\napp.bind(PubSubBindings.CONFIG).to({\n  eventEmitter: new EventEmitter(),\n  // client: mqttClient\n});\napp.component(PubSubComponent);\napp.bind(PubSubBindings.PUBSUB_STRATEGY).toProvider(PubSubStrategyProvider);\n\n```\n\n### Create a repository\n\nCreate a repository that implements your client/broker logic, here an example for a simple EventEmitter.\nYou could create several repositories, with MQTT client or other kinds of PubSub clients.\n\n```typescript\nimport {inject} from '@loopback/core';\nimport {\n  PubSubEngine,\n  PubSubBindings,\n  PubSubConfig,\n  PubSubAsyncIterator,\n} from 'loopback-pubsub-component';\nimport {EventEmitter} from 'events';\n\nexport class PubSubEERepository extends PubSubEngine {\n  protected ee: EventEmitter;\n  public subscriptions: {[subId: number]: [string, (...args: any[]) =\u003e void]};\n  public subIdCounter: number;\n\n  constructor(@inject(PubSubBindings.CONFIG) options: PubSubConfig) {\n    super();\n    if (options.eventEmitter) {\n      this.ee = options.eventEmitter;\n    } else {\n      this.ee = new EventEmitter();\n    }\n    this.subscriptions = {};\n    this.subIdCounter = 0;\n  }\n\n  public publish(triggerName: string, payload: any): Promise\u003cvoid\u003e {\n    this.ee.emit(triggerName, payload);\n    return Promise.resolve();\n  }\n\n  public subscribe(\n    triggerName: string,\n    onMessage: (...args: any[]) =\u003e void,\n    options?: Object,\n  ): Promise\u003cnumber\u003e {\n    this.ee.addListener(triggerName, onMessage);\n    this.subIdCounter = this.subIdCounter + 1;\n    this.subscriptions[this.subIdCounter] = [triggerName, onMessage];\n    return Promise.resolve(this.subIdCounter);\n  }\n\n  public unsubscribe(subIdOrTriggerName: number | string) {\n    if (typeof subIdOrTriggerName === 'string') {\n      return this.unsubscribeByName(subIdOrTriggerName);\n    }\n    return this.unsubscribeById(subIdOrTriggerName);\n  }\n\n  public asyncIterator\u003cT\u003e(triggers: string | string[]): AsyncIterator\u003cT\u003e {\n    return new PubSubAsyncIterator\u003cT\u003e(this, triggers);\n  }\n\n  private unsubscribeById(subId: number) {\n    const [triggerName, onMessage] = this.subscriptions[subId];\n    delete this.subscriptions[subId];\n    this.ee.removeListener(triggerName, onMessage);\n    return Promise.resolve();\n  }\n\n  private unsubscribeByName(triggerName: string) {\n    const subIds: number[] = Object.keys(this.subscriptions).map(Number);\n    for (const subId of subIds) {\n      if (this.subscriptions[subId][0] === triggerName) {\n        const onMessage = this.subscriptions[subId][1];\n        delete this.subscriptions[subId];\n        this.ee.removeListener(triggerName, onMessage);\n        break;\n      }\n    }\n    return Promise.resolve();\n  }\n}\n```\n\n### Strategy provider\n\nCreate a strategy provider that implements your custom logic.\nIf you have several repositories, inject them and create a function to switch between repositories with trigerName filtering.\n\n```typescript\nimport {inject, Provider, ValueOrPromise} from '@loopback/core';\nimport {repository} from '@loopback/repository';\nimport {\n  PubSubBindings,\n  PubSubConfig,\n  PubSubStrategy,\n  PubSubMetadata,\n} from 'loopback-pubsub-component';\nimport {PubSubEERepository, PubSubMQTTRepository} from '../repositories';\n\nexport class PubSubStrategyProvider implements Provider\u003cPubSubStrategy | undefined\u003e {\n  private engines: (PubSubEERepository | PubSubMQTTRepository)[];\n\n  constructor(\n    @inject(PubSubBindings.METADATA) private metadata: PubSubMetadata,\n    @repository(PubSubEERepository) protected pubsubEERepo: PubSubEERepository,\n    @repository(PubSubMQTTRepository) protected pubsubMQTTRepo: PubSubMQTTRepository,\n  ) {\n    this.engines = [this.pubsubEERepo, this.pubsubMQTTRepo];\n  }\n\n  selectRepository(triggerNames: string | string[]): PubSubEERepository | PubSubMQTTRepository {\n    // filter triggerName or triggerNames[0]\n    return this.engines[0];\n  }\n\n  value(): ValueOrPromise\u003cPubSubStrategy | undefined\u003e {\n    const self = this;\n\n    return {\n      setConfig: async (config?: Object) =\u003e {\n        const pubsubConf: PubSubConfig = {};\n        return pubsubConf;\n      },\n\n      publish: async (triggerName: string, payload: any) =\u003e {\n        const engine = this.selectRepository(triggerName);\n        await engine.publish(triggerName, JSON.stringify(payload));\n      },\n\n      subscribe: (triggerName: string, onMessage: (...args: any[]) =\u003e void, options?: Object) =\u003e {\n        const engine = this.selectRepository(triggerName);\n        // return Promise.all([engines.map( engine =\u003e engine.subscribe(triggerName, onMessage, options))])\n        return engine.subscribe(triggerName, onMessage, options);\n      },\n\n      unsubscribe: async (triggerName: string) =\u003e {\n        const engine = this.selectRepository(triggerName);\n        await engine.unsubscribe(triggerName);\n      },\n\n      asyncIterator(triggers: string | string[]) {\n        const engine = self.selectRepository(triggers);\n        return engine.asyncIterator(triggers);\n      },\n\n    };\n  }\n}\n\n```\n\n### Use in a controller\n\nInject the bindings, to make available PubSubStrategy provider functions.\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 {PubSubBindings} from 'loopback-pubsub-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    @inject(PubSubBindings.publish) public publish: PubSubPublishFn,\n  ) {}\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  })\n  async create(@requestBody() device: Device): Promise\u003cDevice\u003e {\n    const token = getToken(this.request);\n    const result = await this.deviceApi.create(token, device);\n    await this.publish(this.request.path, result)\n    return result;\n  }\n\n}\n\n```\n\n## TODO \n\n- Adding decorator to use it like a router in a Controller ( @publish, @subscribe ... ) and control access ( when using broker )\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-pubsub-component","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgetlarge%2Floopback-pubsub-component","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetlarge%2Floopback-pubsub-component/lists"}