{"id":20555879,"url":"https://github.com/conjurelabs/pg-dir","last_synced_at":"2026-05-09T03:34:40.967Z","repository":{"id":57103692,"uuid":"235209164","full_name":"ConjureLabs/pg-dir","owner":"ConjureLabs","description":"query postgres using directories of templatized sql flat files","archived":false,"fork":false,"pushed_at":"2023-10-03T14:03:19.000Z","size":74,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-09T19:11:25.611Z","etag":null,"topics":["flat-file","node","postgres","templatized"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ConjureLabs.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-01-20T22:15:35.000Z","updated_at":"2023-10-03T14:03:22.000Z","dependencies_parsed_at":"2024-11-15T12:45:59.341Z","dependency_job_id":null,"html_url":"https://github.com/ConjureLabs/pg-dir","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/ConjureLabs/pg-dir","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ConjureLabs%2Fpg-dir","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ConjureLabs%2Fpg-dir/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ConjureLabs%2Fpg-dir/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ConjureLabs%2Fpg-dir/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ConjureLabs","download_url":"https://codeload.github.com/ConjureLabs/pg-dir/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ConjureLabs%2Fpg-dir/sbom","scorecard":{"id":32779,"data":{"date":"2025-08-11","repo":{"name":"github.com/ConjureLabs/pg-dir","commit":"edfb800b0791fda635580b5219ceded8d3d5bf9f"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/26 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 5 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-14T19:33:33.378Z","repository_id":57103692,"created_at":"2025-08-14T19:33:33.378Z","updated_at":"2025-08-14T19:33:33.378Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274580448,"owners_count":25311177,"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-09-11T02:00:13.660Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","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":["flat-file","node","postgres","templatized"],"created_at":"2024-11-16T03:21:40.208Z","updated_at":"2026-05-09T03:34:40.923Z","avatar_url":"https://github.com/ConjureLabs.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pg-dir\n\nthis module exports a class that uses [pg-dot-template](https://github.com/ConjureLabs/pg-dot-template/) to fill in templatized sql files, and provides some nicities to query and use data.\n\n## install\n\n```sh\n# peer dependency\nnpm install pg\n\n# this module\nnpm install @conjurelabs/pg-dir\n```\n\n## use\n\nsee [the node postgres docs](https://node-postgres.com/) on setting up your database connection.\n\nonce `pg` is connected, then store you `.sql` files in a directory and initialize `PgDir` to start querying.\n\n### pg pool config\n\nyou can set [the config](https://node-postgres.com/api/pool) which will be used for all pool connections\n\n```js\nconst { usingPoolConfig } = require('@conjurelabs/pg-dir')\n\nusingPoolConfig({\n  idleTimeoutMillis: 10000\n})\n```\n\nthis should be set before any use of sql\n\n### directory setup\n\n_./sql/accounts/get-account.sql_\n```sql\nselect *\nfrom accounts\nwhere id = $PG{id}\nlimit 1;\n```\n\n_./sql/accounts/create-account.sql_\n```sql\ninsert into accounts (first_name, last_name, type, email, added)\nvalues (!PG{firstName}, !PG{lastName}, $PG{type}, !PG{email}, now())\nreturning *;\n```\n\n_./sql/accounts/index.js_\n```js\nconst PgDir = require('@conjurelabs/pg-dir')\n\nmodule.exports = new PgDir(__dirname)\n````\n\n### normal queries\n\nan instance of `PgDir` will expose camel-cased filenames, allowing you to query each easily\n\n__index.js__\n```js\nconst accountsSql = require('./sql/accounts')\n\nasync function main() {\n  const accounts = await accountsSql.getAccount({\n    id: 123\n  })\n\n  console.log(accounts[0])\n  // row keys are camel-cased\n  /*\n    {\n      id: 123,\n      firstName: 'Timo',\n      lastName: 'Mars',\n      type: 'admin',\n      email: 'timo@mars.somesite',\n      added: '2020-01-20T23:04:00.250Z'\n    }\n   */\n  \n  // `firstName`, `lastName` and `email`\n  // will log '\u003cREDACTED\u003e' to console\n  // but will pass actual values to postgres\n  // (due to using `!PG{...}`)\n  //\n  // `type` will show 'user' in console\n  // and will pass 'user' to postgres\n  // (due to using `$PG{...}`)\n  await accountsSql.createAccount({\n    firstName: 'timoteo',\n    lastName: 'marshall',\n    type: 'user',\n    email: 'timoteo@marshall.museum'\n  })\n}\nawait main()\n````\n\n### .one()\n\noften you will only want a single row\n\n```js\nconst account = await accountsSql.getAccount.one({\n  id: 123\n})\n```\n\n### .hash(key)\n\na common pattern is to pull rows and have them stored in a lookup hash, by specific key\n\n```js\nconst accounts = await accountsSql.getAllAccounts.hash('email')({\n  limit: 10\n})\n// returns { [email]: \u003crow\u003e }\n```\n\n### .fullResponse()\n\nif you need to access the full postgres response object, you can use `.fullResponse`\n\n```js\nconst accountsResponse = await accountsSql.getAccount.fullResponse({\n  id: 123\n})\nconst account = accountsResponse.rows[0]\n```\n\n### custom template handlers\n\n`$PG{name}` can be used to replace values, and `!PG{name}` can be used to replace while redacting values from console logs.\n\nthese will only work in a postgres `where` clause\n\nsee [the pg-dot-template docs' section on expression handlers](https://github.com/ConjureLabs/pg-dot-template#postgres-expression-handlers) to see more.\n\n### transactions\n\n`pg-dir` adds utility methods for dealing with `begin`, `commit` and `rollback` (transaction blocks)\n\n```js\nconst transaction = await accountsSql.transaction\n\ntry {\n  // triggers `begin` query\n  await transaction.begin()\n\n  const newAccountRow = await transaction.createAccount.one({\n    firstName: 'timoteo',\n    lastName: 'marshall',\n    type: 'user',\n    email: 'timoteo@marshall.museum'\n  })\n\n  await transaction.createAccountLogin({\n    accountId: newAccountRow.id\n  })\n\n  // triggers `commit` query\n  // then attempts connection.release()\n  await transaction.commit()\n} catch(err) {\n  // triggers `rollback` query\n  // then connection.release()\n  await transaction.rollback()\n\n  console.error(err)\n}\n```\n\nyou can also create savepoints, and rollback to these savepoints\n\nnote that if you rollback to a savepoint the sql pool connection will not be released\n\n```js\nawait transaction.savepoint('some_point')\n\n// do something else\n\nawait transaction.rollback('some_point')\n```\n\n### console logging\n\nlogging is built in - this library uses the [debug](https://www.npmjs.com/package/debug) module\n\n```sh\nDEBUG=\"pg-dir:query\" node ./\n```\n\nthis will log all queries _before_ they are executed, and these logs will be sanitized on non-development environments\n\n```sh\nDEBUG=\"pg-dir:executed\" node ./\n```\n\nthis will log queries as they are executed, with **no sanitization**\n\nthe exact query being passed to `pg`, along with an array of arguments for placeholder values, will be logged\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconjurelabs%2Fpg-dir","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconjurelabs%2Fpg-dir","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconjurelabs%2Fpg-dir/lists"}