{"id":14974152,"url":"https://github.com/mmkal/plv8-git","last_synced_at":"2025-08-17T01:06:08.053Z","repository":{"id":38972360,"uuid":"306447316","full_name":"mmkal/plv8-git","owner":"mmkal","description":"Tracks history of rows in postgresql database tables, using git","archived":false,"fork":false,"pushed_at":"2025-08-12T14:26:59.000Z","size":447,"stargazers_count":29,"open_issues_count":5,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-12T16:23:44.961Z","etag":null,"topics":["git","plv8","plv8-git","postgres","postgresql","versioning"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mmkal.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,"zenodo":null}},"created_at":"2020-10-22T20:08:07.000Z","updated_at":"2025-06-04T17:09:12.000Z","dependencies_parsed_at":"2024-04-20T03:27:46.269Z","dependency_job_id":"c3ec1709-b720-466c-9724-2cb176698949","html_url":"https://github.com/mmkal/plv8-git","commit_stats":{"total_commits":139,"total_committers":3,"mean_commits":"46.333333333333336","dds":"0.41007194244604317","last_synced_commit":"d05b6db2e2af5deaba5cf56bf1985602164887b4"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/mmkal/plv8-git","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmkal%2Fplv8-git","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmkal%2Fplv8-git/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmkal%2Fplv8-git/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmkal%2Fplv8-git/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mmkal","download_url":"https://codeload.github.com/mmkal/plv8-git/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmkal%2Fplv8-git/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270791794,"owners_count":24645833,"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-08-16T02:00:11.002Z","response_time":91,"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":["git","plv8","plv8-git","postgres","postgresql","versioning"],"created_at":"2024-09-24T13:50:03.490Z","updated_at":"2025-08-17T01:06:08.028Z","avatar_url":"https://github.com/mmkal.png","language":"TypeScript","readme":"# plv8-git\n\nAn experimental helper which tracks the modification history of rows in postgres database tables, using git, based on the idea in [this tweet](https://twitter.com/mayfer/status/1308606131426582528).\n\nThe implementation uses [plv8](https://github.com/plv8/plv8) to run JavaScript in postgres, with [isomorphic-git](https://npmjs.com/package/isomorphic-git) and [memfs](https://npmjs.com/package) to perform git operations in-memory.\n\n\u003c!-- codegen:start {preset: markdownTOC, minDepth: 2} --\u003e\n- [Motivation](#motivation)\n- [Usage](#usage)\n   - [Tracking history](#tracking-history)\n   - [Deletions](#deletions)\n   - [Options](#options)\n      - [Commit messages](#commit-messages)\n      - [Git config](#git-config)\n      - [Log depth](#log-depth)\n      - [Tags](#tags)\n   - [Restoring previous versions](#restoring-previous-versions)\n   - [Column name clashes](#column-name-clashes)\n- [Caveat](#caveat)\n- [Implementation](#implementation)\n\u003c!-- codegen:end --\u003e\n\n## Motivation\n\nTo paraphrase [@mayfer's twitter thread](https://twitter.com/mayfer/status/1308606131426582528):\n\n\n- never have to worry about building edit/delete/undo/backup/recover type features, one generic git-backed [column] is enough\n\n- removes the need to keep additional SQL tables which keep logs of all edit histories.\n\n- makes event sourcing a lot more modular. instead of tons of tables storing custom events, every SQL update on a column also updates its git bundle, saved into a separate binary column\n\n- with just 1 extra column, you \ncan add multiuser versioning to *any* indexed column!\n\n- how cool this will be for large JSON or other text blob c get overwritten a lot duringall commits are controlled by the main app, it's trivial to integrate commit authors directly into any regular application's user auth system\n\n- due to the git standard, this repo then can easily be fed into any generic git UI for all sorts of diffing, logging \u0026 visualizing\n\n## Usage\n\nThe easiest way to get started is to use the pre-baked sql files exported with the package:\n\n```bash\nnpm install plv8-git\n\npsql -c \"\n  create extension if not exists plv8;\n  select plv8_version();\n\"\npsql -f node_modules/plv8-git/queries/create-git-functions.sql\n```\n\nOr from javascript:\n\n```js\nconst sqlClient = getSqlClient()\n\nconst sql = require('plv8-git/queries').getGitFunctionsSql()\nawait sqlClient.runRawSql(sql)\n```\n\nNote: for `create extension plv8` to work the plv8.control file must exist on your database system. You can use [the postgres-plv8 docker image](https://github.com/clkao/docker-postgres-plv8/tree/master/11-2) for development (or production, if you really want to deploy a containerised database to production). Amazon RDS instances [have the extension available](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html), as does [Azure Postgres 11](https://docs.microsoft.com/en-us/azure/postgresql/concepts-extensions#postgres-11-extensions).\n\nThis will have created three postgres functions: `git_track`, `git_log` and `git_resolve`.\n\n\u003c!-- codegen:start {preset: custom, source: scripts/docs.js} --\u003e\n### Tracking history\n\n`git_track` is a trigger function that can be added to any table, with a `json` column, default-named `git`:\n\n```sql\ncreate table test_table(\n  id int,\n  text text,\n  git json\n);\n\ncreate trigger test_table_git_track_trigger\n  before insert or update\n  on test_table for each row\n  execute procedure git_track();\n```\n\nNow, whenever rows are inserted or updated into the `test_table` table, the `git` column will automatically be managed as a serialisation of the `.git` folder of an ephemeral git repo. All you need to do is `insert`/`update` as normal:\n\n```sql\ninsert into test_table(id, text)\nvalues(1, 'item 1 old content');\n\nupdate test_table\nset text = 'item 1 new content'\nwhere id = 1;\n```\n\nThere's still just a single row in the `test_table` table, but the full history of it is tracked in the `git` column. The `git_log` function can be used to access the change history:\n\n```sql\nselect git_log(git)\nfrom test_table\nwhere id = 1\n```\n\nThis query will return:\n\n```json\n{\n  \"git_log\": [\n    {\n      \"message\": \"test_table_git_track_trigger: BEFORE UPDATE ROW on public.test_table\",\n      \"author\": \"pguser (pguser@pg.com)\",\n      \"timestamp\": \"2000-12-25T12:00:00.000Z\",\n      \"oid\": \"[oid]\",\n      \"tags\": [],\n      \"changes\": [\n        {\n          \"field\": \"text\",\n          \"new\": \"item 1 new content\",\n          \"old\": \"item 1 old content\"\n        }\n      ]\n    },\n    {\n      \"message\": \"test_table_git_track_trigger: BEFORE INSERT ROW on public.test_table\",\n      \"author\": \"pguser (pguser@pg.com)\",\n      \"timestamp\": \"2000-12-25T12:00:00.000Z\",\n      \"oid\": \"[oid]\",\n      \"tags\": [],\n      \"changes\": [\n        {\n          \"field\": \"id\",\n          \"new\": 1\n        },\n        {\n          \"field\": \"text\",\n          \"new\": \"item 1 old content\"\n        }\n      ]\n    }\n  ]\n}\n```\n\ni.e. you can see the row's full history, in human- and machine-readable form, straight from the table.\n\nTo use existing git clients to get rich visual diffs, etc., you can simply pull the `git` field for a given row, and convert it into real files:\n\n```sql\nselect git from test_table where id = 1\n```\n\n```json\n{\n  \"git\": {\n    \"/repo/.git/objects/8a/ed642bf5118b9d3c859bd4be35ecac75b6e873\": \"[byte array]\",\n    \"/repo/.git/objects/d0/ff5974b6aa52cf562bea5921840c032a860a91\": \"[byte array]\",\n    \"/repo/.git/objects/d8/4bdb34d4eeef4034d77e5403f850e35bc4a51b\": \"[byte array]\",\n    \"/repo/.git/objects/a4/16ea84421fa7e1351582da48235bac88380a33\": \"[byte array]\",\n    \"/repo/.git/objects/fb/d04e1aae9ce0b11a8946e2c9ac2619f7428a64\": \"[byte array]\",\n    \"/repo/.git/objects/a1/9a1584344c1f3783bff51524a5a4b86f2cc093\": \"[byte array]\",\n    \"/repo/.git/objects/8a/b31b5afaea56114427e1f01b81d001b079a0f5\": \"[byte array]\",\n    \"/repo/.git/refs/heads/main\": \"[byte array]\",\n    \"/repo/.git/config\": \"[byte array]\",\n    \"/repo/.git/HEAD\": \"[byte array]\",\n    \"/repo/.git/index\": \"[byte array]\"\n  }\n}\n```\n\nThis will return a json-formatted object, with keys corresponding to file system paths, and byte-array values as contents. Write them to disk using the CLI tool provided with this package:\n\n```bash\nGIT=$(psql -qAt -c \"select git from test_table where id = 1\")\nnode_modules/.bin/plv8-git write --input \"$GIT\" --output path/to/git/dir\n```\n\n`path/to/git/dir` will now be a valid git repository, with one file corresponding to each column in `test_table`. You can `cd` into it, and run commands like `git log`, or use your favourite git UI to inspect the history in as much detail as you'd like.\n\n### Deletions\n\nYou can also take advantage of the `git` column to track deletions, by adding a delete hook:\n\n```sql\ncreate table deleted_history(\n  schemaname name,\n  tablename name,\n  identifier jsonb,\n  deleted_at timestamptz,\n  git json\n);\n\ncreate function test_table_track_deletion() returns trigger as\n$$\n  begin\n    insert into deleted_history(schemaname, tablename, identifier, deleted_at, git)\n    values ('public', 'test_table', jsonb_build_object('id', OLD.id), now(), OLD.git);\n\n    return OLD;\n  end\n$$\nlanguage plpgsql;\n\ncreate trigger test_table_track_deletion_trigger\n  before delete\n  on test_table for each row\n  execute procedure test_table_track_deletion();\n```\n\nYou can now perform deletions as normal and they'll be automatically tracked in `deleted_history`:\n\n```sql\ndelete from test_table\nwhere id = 1\n```\n\nThe `deleted_history` table can be queried in the same was as the other tables:\n\n```sql\nselect *\nfrom deleted_history\nwhere identifier-\u003e\u003e'id' = '1'\n```\n\nThis will return something like:\n\n```json\n{\n  \"schemaname\": \"public\",\n  \"tablename\": \"test_table\",\n  \"identifier\": {\n    \"id\": 1\n  },\n  \"deleted_at\": \"2000-12-25T12:00:00.000Z\",\n  \"git\": {\n    \"/repo/.git/objects/8a/ed642bf5118b9d3c859bd4be35ecac75b6e873\": \"[byte array]\",\n    \"/repo/.git/objects/d0/ff5974b6aa52cf562bea5921840c032a860a91\": \"[byte array]\",\n    \"/repo/.git/objects/d8/4bdb34d4eeef4034d77e5403f850e35bc4a51b\": \"[byte array]\",\n    \"/repo/.git/objects/a4/16ea84421fa7e1351582da48235bac88380a33\": \"[byte array]\",\n    \"/repo/.git/objects/fb/d04e1aae9ce0b11a8946e2c9ac2619f7428a64\": \"[byte array]\",\n    \"/repo/.git/objects/a1/9a1584344c1f3783bff51524a5a4b86f2cc093\": \"[byte array]\",\n    \"/repo/.git/objects/8a/b31b5afaea56114427e1f01b81d001b079a0f5\": \"[byte array]\",\n    \"/repo/.git/refs/heads/main\": \"[byte array]\",\n    \"/repo/.git/config\": \"[byte array]\",\n    \"/repo/.git/HEAD\": \"[byte array]\",\n    \"/repo/.git/index\": \"[byte array]\"\n  }\n}\n```\n\nYou can use `git_log` again to get a readable history:\n\n```sql\nselect git_log(git)\nfrom deleted_history\nwhere identifier-\u003e\u003e'id' = '1'\n```\n\n```json\n{\n  \"git_log\": [\n    {\n      \"message\": \"test_table_git_track_trigger: BEFORE UPDATE ROW on public.test_table\",\n      \"author\": \"pguser (pguser@pg.com)\",\n      \"timestamp\": \"2000-12-25T12:00:00.000Z\",\n      \"oid\": \"[oid]\",\n      \"tags\": [],\n      \"changes\": [\n        {\n          \"field\": \"text\",\n          \"new\": \"item 1 new content\",\n          \"old\": \"item 1 old content\"\n        }\n      ]\n    },\n    {\n      \"message\": \"test_table_git_track_trigger: BEFORE INSERT ROW on public.test_table\",\n      \"author\": \"pguser (pguser@pg.com)\",\n      \"timestamp\": \"2000-12-25T12:00:00.000Z\",\n      \"oid\": \"[oid]\",\n      \"tags\": [],\n      \"changes\": [\n        {\n          \"field\": \"id\",\n          \"new\": 1\n        },\n        {\n          \"field\": \"text\",\n          \"new\": \"item 1 old content\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nIn this example, `deleted_history` is generic enough that it could be the \"history\" table for several other relations, since it uses columns `schemaname` and `tablename`, and `identifier` as the flexible `JSONB` data type to allow for different types of primary key. This avoids the overhead of needing a new `_history` table for every relation created - all the data, including history, is captured in the `git` column. The `identifier` column is only used for lookups.\n\n### Options\n\n#### Commit messages\n\nYou can pass a custom commit message and author by pre-loading the `git` property with `commit` details, which can include a commit message and user info:\n\n```sql\ninsert into test_table(\n  id,\n  text,\n  git\n)\nvalues(\n  2,\n  'original value set by alice',\n  '{ \"commit\": { \"message\": \"some custom message\", \"author\": { \"name\": \"Alice\", \"email\": \"alice@gmail.com\" } } }'\n)\n```\n\n```sql\nselect git_log(git)\nfrom test_table\nwhere id = 2\n```\n\n```json\n{\n  \"git_log\": [\n    {\n      \"message\": \"some custom message\\\\n\\\\ntest_table_git_track_trigger: BEFORE INSERT ROW on public.test_table\",\n      \"author\": \"Alice (alice@gmail.com)\",\n      \"timestamp\": \"2000-12-25T12:00:00.000Z\",\n      \"oid\": \"[oid]\",\n      \"tags\": [],\n      \"changes\": [\n        {\n          \"field\": \"id\",\n          \"new\": 2\n        },\n        {\n          \"field\": \"text\",\n          \"new\": \"original value set by alice\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n#### Git config\n\nYou can configure git using `git_set_local_config` or `git_set_global_config`:\n\n```sql\nselect git_set_local_config('user.name', 'Bob');\nselect git_set_local_config('user.email', 'bobby@company.com');\n\ninsert into test_table(id, text)\nvalues(201, 'value set by bob')\n```\n\n```sql\nselect git_log(git)\nfrom test_table\nwhere id = 201\n```\n\n```json\n{\n  \"git_log\": [\n    {\n      \"message\": \"test_table_git_track_trigger: BEFORE INSERT ROW on public.test_table\",\n      \"author\": \"Bob (bobby@company.com)\",\n      \"timestamp\": \"2000-12-25T12:00:00.000Z\",\n      \"oid\": \"[oid]\",\n      \"tags\": [],\n      \"changes\": [\n        {\n          \"field\": \"id\",\n          \"new\": 201\n        },\n        {\n          \"field\": \"text\",\n          \"new\": \"value set by bob\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nUnder the hood these use `set_config` with the `is_local` parameter respectively true/false for the local/global variants.\n\n#### Log depth\n\n`git_log` also accepts a `depth` parameter to limit the amount of history that is fetched:\n\n```sql\nupdate test_table\nset text = 'a new value set by admin',\n    git = '{ \"commit\": { \"message\": \"Changed because the previous value was out-of-date\"  } }'\nwhere id = 2\n```\n\n```sql\nselect git_log(git, depth := 1)\nfrom test_table\nwhere id = 2\n```\n\n```json\n{\n  \"git_log\": [\n    {\n      \"message\": \"Changed because the previous value was out-of-date\\\\n\\\\ntest_table_git_track_trigger: BEFORE UPDATE ROW on public.test_table\",\n      \"author\": \"pguser (pguser@pg.com)\",\n      \"timestamp\": \"2000-12-25T12:00:00.000Z\",\n      \"oid\": \"[oid]\",\n      \"tags\": [],\n      \"changes\": [\n        {\n          \"field\": \"text\",\n          \"new\": \"a new value set by admin\",\n          \"old\": \"original value set by alice\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nBy setting `depth := 1`, only the most recent change is returned.\n\n#### Tags\n\nYou can pass `tags` to the git object. The below example uses a convention of tagging with the day, month, and year so it will later be easy to restore to previous versions:\n\n```sql\ninsert into test_table(id, text, git)\nvalues (3, 'item 3 xmas day value', '{ \"git\": { \"tags\": [\"2000-12-25\", \"2000-12\", \"2000\"] } }');\n\nupdate test_table\nset\n  text = 'item 3 boxing day value',\n  git = '{ \"tags\": [\"2000-12-26\", \"2000-12\", \"2000\"] }'\nwhere id = 3;\n\nupdate test_table\nset\n  text = 'item 3 new year value',\n  git = '{ \"tags\": [\"2001-01-01\", \"2001-01\", \"2001\"] }'\nwhere id = 3;\n```\n\nOr, set them in git config as a colon-separated list:\n\n```sql\nselect git_set_local_config('tags', 'your_app_request_id=1234:your_app_trace_id=5678');\n\nupdate test_table\nset text = 'item 3 yet another value'\nwhere id = 3;\n```\n\n### Restoring previous versions\n\n`git_resolve` gives you a json representation of a prior version of a row, which can be used for backup and restore. The first argument is a `git` json value, the second value is a valid git ref string (e.g. a git oid returned by `git_log`, or `HEAD`, or `main`. Note that an issue with [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git/issues/1238) means that you can't currently pass values like `HEAD~1` here).\n\nCombine it with `git_log` to get a previous version - the below query uses `-\u003e1-\u003e\u003e'oid'` to get the oid from the second item in the log array:\n\n```sql\nselect git_resolve(git, ref := git_log(git)-\u003e1-\u003e\u003e'oid')\nfrom test_table\nwhere id = 2\n```\n\n```json\n{\n  \"git_resolve\": {\n    \"id\": 2,\n    \"text\": \"original value set by alice\"\n  }\n}\n```\n\nThis can be used in an update query to revert a change:\n\n```sql\nupdate test_table set (id, text) =\n(\n  select id, text\n  from json_populate_record(\n    null::test_table,\n    git_resolve(git, ref := git_log(git)-\u003e1-\u003e\u003e'oid')\n  )\n)\nwhere id = 2\nreturning id, text\n```\n\n```json\n{\n  \"id\": 2,\n  \"text\": \"original value set by alice\"\n}\n```\n\nIf you used `tags` as described above, you can take advantage of them to restore to a known-good state easily:\n\n```sql\nselect git_log(git)\nfrom test_table\nwhere id = 3\n```\n\n```json\n[\n  {\n    \"git_log\": [\n      {\n        \"message\": \"test_table_git_track_trigger: BEFORE UPDATE ROW on public.test_table\",\n        \"author\": \"pguser (pguser@pg.com)\",\n        \"timestamp\": \"2000-12-25T12:00:00.000Z\",\n        \"oid\": \"[oid]\",\n        \"tags\": [\n          \"your_app_request_id=1234\",\n          \"your_app_trace_id=5678\"\n        ],\n        \"changes\": [\n          {\n            \"field\": \"text\",\n            \"new\": \"item 3 yet another value\",\n            \"old\": \"item 3 new year value\"\n          }\n        ]\n      },\n      {\n        \"message\": \"test_table_git_track_trigger: BEFORE UPDATE ROW on public.test_table\",\n        \"author\": \"pguser (pguser@pg.com)\",\n        \"timestamp\": \"2000-12-25T12:00:00.000Z\",\n        \"oid\": \"[oid]\",\n        \"tags\": [\n          \"2001\",\n          \"2001-01\",\n          \"2001-01-01\"\n        ],\n        \"changes\": [\n          {\n            \"field\": \"text\",\n            \"new\": \"item 3 new year value\",\n            \"old\": \"item 3 boxing day value\"\n          }\n        ]\n      },\n      {\n        \"message\": \"test_table_git_track_trigger: BEFORE UPDATE ROW on public.test_table\",\n        \"author\": \"pguser (pguser@pg.com)\",\n        \"timestamp\": \"2000-12-25T12:00:00.000Z\",\n        \"oid\": \"[oid]\",\n        \"tags\": [\n          \"2000\",\n          \"2000-12\",\n          \"2000-12-26\"\n        ],\n        \"changes\": [\n          {\n            \"field\": \"text\",\n            \"new\": \"item 3 boxing day value\",\n            \"old\": \"item 3 xmas day value\"\n          }\n        ]\n      },\n      {\n        \"message\": \"test_table_git_track_trigger: BEFORE INSERT ROW on public.test_table\",\n        \"author\": \"pguser (pguser@pg.com)\",\n        \"timestamp\": \"2000-12-25T12:00:00.000Z\",\n        \"oid\": \"[oid]\",\n        \"tags\": [],\n        \"changes\": [\n          {\n            \"field\": \"id\",\n            \"new\": 3\n          },\n          {\n            \"field\": \"text\",\n            \"new\": \"item 3 xmas day value\"\n          }\n        ]\n      }\n    ]\n  }\n]\n```\n\n```sql\nupdate test_table set (id, text) =\n(\n  select id, text\n  from json_populate_record(\n    null::test_table,\n    git_resolve(git, ref := '2000-12')\n  )\n)\nwhere id = 3\nreturning id, text\n```\n\n```json\n{\n  \"id\": 3,\n  \"text\": \"item 3 boxing day value\"\n}\n```\n\n```sql\nupdate test_table set (id, text) =\n(\n  select id, text\n  from json_populate_record(\n    null::test_table,\n    git_resolve(git, ref := 'your_app_request_id=1234')\n  )\n)\nwhere id = 3\nreturning id, text\n```\n\n```json\n{\n  \"id\": 3,\n  \"text\": \"item 3 yet another value\"\n}\n```\n\nA similar technique can restore a deleted item:\n\n```sql\ninsert into test_table\nselect * from json_populate_record(\n  null::test_table,\n  (\n    select git_resolve(git, ref := 'HEAD')\n    from deleted_history\n    where tablename = 'test_table' and identifier-\u003e\u003e'id' = '1'\n  )\n)\nreturning id, text\n```\n\n```json\n{\n  \"id\": 1,\n  \"text\": \"item 1 new content\"\n}\n```\n\n### Column name clashes\n\nHistory can be tracked even on pre-existing tables which already have a `git` column used for something else:\n\n```sql\ncreate table repos(\n  id int,\n  name text,\n  git text -- the repo clone url\n);\n```\n\nAny column with type `json` can be used, by passing the column name when creating a trigger:\n\n```sql\nalter table repos\nadd column my_custom_plv8_git_column json;\n\ncreate trigger repos_git_track_trigger\n  before insert or update\n  on repos for each row\n  execute procedure git_track('my_custom_plv8_git_column');\n\ninsert into repos(id, name, git)\nvalues (1, 'plv8-git', 'https://github.com/mmkal/plv8-git.git');\n```\n\n```sql\nselect git_log(my_custom_plv8_git_column)\nfrom repos\nwhere git = 'https://github.com/mmkal/plv8-git.git'\n```\n\n```json\n{\n  \"git_log\": [\n    {\n      \"message\": \"repos_git_track_trigger: BEFORE INSERT ROW on public.repos\",\n      \"author\": \"pguser (pguser@pg.com)\",\n      \"timestamp\": \"2000-12-25T12:00:00.000Z\",\n      \"oid\": \"[oid]\",\n      \"tags\": [],\n      \"changes\": [\n        {\n          \"field\": \"git\",\n          \"new\": \"https://github.com/mmkal/plv8-git.git\"\n        },\n        {\n          \"field\": \"id\",\n          \"new\": 1\n        },\n        {\n          \"field\": \"name\",\n          \"new\": \"plv8-git\"\n        }\n      ]\n    }\n  ]\n}\n```\n\u003c!-- codegen:end --\u003e\n\n## Caveat\n\n- This library is experimental, and hasn't been pressure-tested. There may well be edge-cases where it falls down.\n- It hasn't been performance-tested yet. It works well for rows with small, easily-json-stringifiable data. Large, frequently updated rows may hit issues.\n- It currently uses the `JSON` data type to store a serialised copy of the `.git` repo folder. This can likely be optimised to use `BYTEA` or another data type.\n- It uses several tools that were _not_ built with each other in mind (although each is well-designed and flexible enough for them to play nice without too many problems). See the [implementation section](#implementation)\n- It's still in v0, so breaking changes may occur.\n\n## Implementation\n\nAt its core, this library bundles [isomorphic-git](https://npmjs.com/package/isomorphic-git) and [memfs](https://npmjs.com/package/memfs) to produce an entirely in-memory, synchronous git implementation which can run inside postgres's plv8 engine. A few modifications are applied to each:\n\nSince plv8 triggers need to return values synchronously, but isomorphic-git uses promises extensively, a shim of the global `Promise` object was created called [`SyncPromise`](./src/sync-promise.ts). This has the same API as `Promise`, but its callbacks are executed immediately.\n\nTo avoid the event-loop, all async-await code in isomorphic-git is transformed to `.then`, `.catch` etc. by [babel-plugin-transform-async-to-promises](https://npmjs.com/package/babel-plugin-transform-async-to-promises). `async-lock`, which is a dependency of isomorphic-git, is also [shimmed](./webpack/async-lock-shim.js) to bypass its locking mechanism which relies on timers - it's not necessary anyway, since all git operations take place on an ephemeral, in-memory, synchronous filesystem.\n\n`memfs` is also shimmed before being passed to isomorphic-git to [replace its promise-based operations with sync ones](./src/fs.ts).\n\nThese libraries are bundled using webpack into a standalone module with no dependencies. The source code for this bundle is copied into a sql file by [generate-queries](./scripts/generate-queries.ts), so that it can be used to define a postgres function with plv8.\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmkal%2Fplv8-git","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmmkal%2Fplv8-git","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmkal%2Fplv8-git/lists"}