feat(graphile-history): PostGraphile v5 history plugin (version reads + restore) - #1531
Merged
Conversation
Discovers @history tables (the constructive-db DataHistory companions) and adds: - a `history` field on each source row type (version stream, recorded_at DESC) - `versionAt(at: Datetime!)` point-in-time reads - a `restore<Table>Version(input: { <pk>, recordedAt, reinsert })` mutation that rewrites the live row from a historical version (optionally reinserting a deleted row); the restore writes through the source table so the source trigger records it as a new version All reads/writes go through withPgClient + pgSettings so RLS and mutation policies are enforced. Wired into graphile-settings via an enableHistory flag (off by default). Integration tests cover history ordering, versionAt, restore update, and reinsert.
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Replace the accidental full lockfile reserialization with a surgical addition of only the graphile-history importer and the graphile-settings dependency edge, so the diff is limited to the new package. Verified with pnpm install --frozen-lockfile (pnpm 10).
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Summary
New
graphile/graphile-historyPostGraphile v5 plugin — the GraphQL half of the table-history feature. It surfaces the<table>_historycompanions produced by the constructive-dbDataHistorymodule (companion DB PR: constructive-io/constructive-db#2577) so clients can read a row's version stream, do point-in-time and time-window reads, and roll a row back.The plugin discovers source tables via the
@historysmart tag (same mechanism graphile-i18n uses for@i18n) and, on each source row type, adds:Key design point — everything runs through the request's
withPgClient+pgSettings(the pattern graphile-presigned-url / graphile-i18n use), so RLS and mutation policies are enforced identically to any other operation; there is no elevated path.restoresemantics:recorded_at <= recordedAt);UPDATEit to the version's values, skipping the PK andimmutableColumns(defaultcreated_at/updated_at);reinsert: true→ re-INSERTit; otherwise no-op (returns the version,restored: null);DataHistorytrigger records the restore itself as a fresh version — no special-casing.Composite primary keys are fully supported — the source PK comes from the resource's
uniquesand every field (history,versionAt,versionsBetween,restorepredicates + the restore input args) iterates all PK columns, producingWHERE k1 = $1 AND k2 = $2 …. Tables with no PK are skipped (no history fields emitted).versionsBetweenfiltersrecorded_at BETWEEN from AND to(inclusive) on the same PK-keyed path, ordered newest-first — an index range scan against the companion(pk…, recorded_at)index added in the DB PR.Discovery is resilient to how the smart tag surfaces:
@historymay be an explicit history-table name, a JSON object ({ "history_table": ... }), or a bare marker (then the history table is derived byhistorySuffix, default_history). Copied columns are computed ashistory_attrs ∩ source_attrsminus the reservedrecorded_at/history_op. ThePostHistoryVersionoutput type maps each copied column's pg codec to the matching GraphQL scalar (uuid→UUID, jsonb→JSON, timestamptz→Datetime, …), all nullable to mirror the zero-constraint history table.Wiring
graphile-settingscreateConstructivePresetgains anenableHistoryflag (off by default, mirroringenableI18n/enableBulk) that pushesHistoryPreset().HistoryPlugin,createHistoryPlugin,HistoryPreset, and the option/info types.Tests
graphile/graphile-history/src/__tests__(graphile-test + real Postgres, all green):@historydiscovery,historyordering (newest-first, 3 versions),versionAtpoint-in-time resolution,versionsBetweentime-window (narrow → 1 version, wide → all 3 newest-first), restore-to-earlier-version (and verifying the restore appended a 4th version via the trigger), and reinsert of a deleted row. The package runs in thepg-graphqlCI batch.Link to Devin session: https://app.devin.ai/sessions/42ba449f34cb4a1687a53756f860af76
Requested by: @pyramation