{"id":18552454,"url":"https://github.com/andrejewski/introspec","last_synced_at":"2025-05-15T11:12:55.038Z","repository":{"id":57275443,"uuid":"85251871","full_name":"andrejewski/introspec","owner":"andrejewski","description":"Dependencies and configuration described through data","archived":false,"fork":false,"pushed_at":"2020-06-01T01:20:57.000Z","size":3546,"stargazers_count":3,"open_issues_count":8,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-21T17:57:59.098Z","etag":null,"topics":["configuration","dependency","framework","javascript","service"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andrejewski.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-16T23:51:11.000Z","updated_at":"2019-04-25T15:28:01.000Z","dependencies_parsed_at":"2022-09-15T19:12:44.896Z","dependency_job_id":null,"html_url":"https://github.com/andrejewski/introspec","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrejewski%2Fintrospec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrejewski%2Fintrospec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrejewski%2Fintrospec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrejewski%2Fintrospec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andrejewski","download_url":"https://codeload.github.com/andrejewski/introspec/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254328389,"owners_count":22052633,"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":["configuration","dependency","framework","javascript","service"],"created_at":"2024-11-06T21:14:16.140Z","updated_at":"2025-05-15T11:12:50.029Z","avatar_url":"https://github.com/andrejewski.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Introspec\n\nDependencies and configuration described through data.\n\n```sh\nnpm install introspec\n```\n\n[![npm](https://img.shields.io/npm/v/introspec.svg)](https://www.npmjs.com/package/introspec)\n[![Build Status](https://travis-ci.org/andrejewski/introspec.svg?branch=master)](https://travis-ci.org/andrejewski/introspec)\n[![Coverage Status](https://coveralls.io/repos/github/andrejewski/introspec/badge.svg?branch=master)](https://coveralls.io/github/andrejewski/introspec?branch=master)\n[![Greenkeeper badge](https://badges.greenkeeper.io/andrejewski/introspec.svg)](https://greenkeeper.io/)\n\n## Why even?\nIntrospec is a variation of [Integrant](https://github.com/weavejester/integrant) for JavaScript. A [great talk](https://skillsmatter.com/skillscasts/9820-enter-integrant-a-micro-framework-for-data-driven-architecture-with-james-reeves) by the author of the framework goes into the details of why it simplifies system structure.\n\nRe-iterating one main point here:\n\n### Code obscures structure\nComponent systems often leave dependency injection to the code. For example, when component A needs component B to do its job.\n\n```js\nclass B {}\nclass A {}\n\nconst b = new B({ username: 'bender', password: 'antiquing' })\nconst a = new A({ bot: b, debug: false, bug: true })\na.job()\n```\n\nThe problem is that this dependency carries a lot of overhead configuring A. We know need to know the invocation of B, the instantiation order, what options both A and B need. Some of this is avoidable, but consider if we put this into a data structure instead.\n\n```js\nimport {ref} from 'introspec'\nexport default {\n  a: {\n    bot: ref('b'), // ref points to a top key in the same config\n    debug: false,\n    bug: true\n  },\n  b: {\n    username: 'bender',\n    password: 'antiquing'\n  }\n}\n```\n\nThe data structure isolates the dependencies and configuration. The invocation happens, unavoidably, in the code. We do stop caring *how* some B gets to some A. The other benefit is we can query our configuration in one place to get values like `b.username`.\n\n## Documentation\nIntrospec has a three function API surface.\n\n#### `ref`\n- `ref(key: string)` returns an internal symbol for pointing to a top-level configuration value. This is the function that applies to building configurations.\n\n#### `start`\n- `start(key: string, startFn: (options: map) -\u003e Promise\u003cservice: any\u003e)` assigns a startup hook to the top-level config property with the given `key` which calls with its instantiated `options` when that property resolves for usage.\n\n- `start(config: object, entryPoints: Array\u003cstring\u003e) completion: Promise\u003csystem\u003e` resolves the dependency graph for top-level `entryPoints`, applying the startup hooks for the respective dependencies, and returns a promise which resolves with a started `system` reference.\n\n#### `stop`\n- `stop(key: string, stopFn: (service: any) -\u003e Promise\u003c_: any\u003e)` assigns a shutdown hook to the top-level config property with the given `key` which calls with the startup result when that property resolves for destruction.\n\n- `stop(system) completion: Promise` shuts down the `system` starting with the top-level `entryPoints`, applying the shutdown hooks for the respective dependencies, and returns a promise which resolves on completion.\n\n### Non-global Introspecs\nIntrospec exports a default instance for convenience. Make Introspec instances with their own lifecycle hook registries and nest them at will.\n\n```js\nimport {Introspec} from 'introspec'\nconst myIntrospec = new Introspec()\n\nmyIntrospec.ref('b')\nmyIntrospec.start({ b: 'cake' }, ['b']).then(system =\u003e {\n  return myIntrospec.stop(system)\n})\n```\n\n## Introspec IRL\nFor larger projects, this data structure based dependency graph shines.\n\nImagine the following application:\n\n- Five services: a HTTP(S) webserver, Redis connection, Postgres connection, email client, and payments API.\n- The webserver uses the four other services handling requests.\n- The email client needs Redis for storing email link tokens.\n- The payments API needs both Redis and Postgres for persistence.\n\n```js\nimport {ref, start, stop} from 'introspec'\n\n/*\n  Environment variables and intermediate transformations are fine.\n  Configs are plain data structures after all.\n*/\nconst config = {\n  webserver: {\n    port: 8080,\n    cache: ref('redis'),\n    database: ref('postgres'),\n    email: ref('email'),\n    payments: ref('payments')\n  },\n  redis: 'redis://...',\n  postgres: 'postgres://...',\n  email: {\n    from: 'introspec@example.com',\n    redis: ref('redis')\n  },\n  payments: {\n    apiKey: 'xkcd12b4ucab',\n    cache: ref('redis'),\n    database: ref('postgres')\n  }\n}\n\n/*\n  Start/stop hooks can fit anywhere, preferably with\n  the relevant code (which I was too lazy to mock here).\n*/\nstart('webserver', ({port, cache, database, email, payments}) =\u003e {})\nstop('webserver', webserver =\u003e {})\n\nstart('redis', uri =\u003e {})\nstop('redis', connection =\u003e {})\n\nstart('postgres', uri =\u003e {})\nstop('postgres', connection =\u003e {})\n\nstart('email', ({from, redis}) =\u003e {})\nstop('email', client =\u003e {})\n\nstart('payments', ({apiKey, cache, database}) =\u003e {})\nstop('payments', service =\u003e {})\n\nstart(config, ['webserver']).then(app =\u003e {\n  return stop(app) // sometime later\n})\n```\n\n## Contributing\nContributions are incredibly welcome as long as they are standardly applicable and pass the tests (or break bad ones). Tests are in AVA.\n\n```bash\n# running tests\nnpm run test\n```\n\nFollow me on [Twitter](https://twitter.com/compooter) for updates or for the lolz and please check out my other [repositories](https://github.com/andrejewski) if I have earned it. I thank you for reading.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrejewski%2Fintrospec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandrejewski%2Fintrospec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrejewski%2Fintrospec/lists"}