README
@graphile/smart-tags-table
This plugin allows you to store smart tags into a database table rather than using database comments or a dedicated file.
In general, using the other approaches are preferred. This plugin is primarily intended for applications which are allowing users to manipulate the GraphQL and maybe even database schema at run time.
Crowd-funded open-source software
We rely on the community's support to keep producing and maintaining OSS; if you find this plugin helpful, please click here to find out more about sponsors and sponsorship.
Usage
Please note you have to create a smart_tags
table as described
below; we do not do this for you.
From the CLI you can install this plugin and run using command line
postgraphile
:
yarn add postgraphile @graphile/smart-tags-table
yarn postgraphile --append-plugins @graphile/smart-tags-table -c postgres://localhost/my_db --watch
In library mode you can use appendPlugins
to install the plugin, and you can
also choose the name of the table via the smartTagsTable
option within
graphileBuildOptions
(should you wish to override it):
app.use(
postgraphile(process.env.DATABASE_URL, process.env.SCHEMA_NAME, {
appendPlugins: [require("@graphile/smart-tags-table")],
watchPg: true,
graphileBuildOptions: {
smartTagsTable: "public.smart_tags",
},
}),
);
NOTE: watch mode consumes (very inefficiently) an entire PostgreSQL client just for this plugin, so your pg pool needs to be at least size 3 if you are using watch mode (default is 10). Addressing this will require changes to the way that watch mode works throughout PostGraphile/Graphile Engine; the changes have not been made yet.
Smart tags table
You must add a table to your database to store the smart tags (that's the entire point of this plugin :wink:). The table follows a similar pattern to the entries in a JSON smart tags file, namely:
kind
- one of:class
- for tables, views, materialized views, compound types and other table-like entities; things you'd find in thepg_class
PostgreSQL system table.attribute
- for columns/attributes of aclass
; things you'd find in thepg_attribute
PostgreSQL system table.constraint
- for constraints; things you'd find in thepg_constraint
PostgreSQL system table.procedure
- for functions and procedures; things you'd find in thepg_proc
PostgreSQL system table
identifier
- the textual representation of the entity to apply the tags to, this will differ based on thekind
:class
-schema_name.table_name
attribute
-schema_name.table_name.column_name
constraint
-schema_name.table_name.constraint_name
procedure
-schema_name.function_name
- NOTE: since PostGraphile doesn't support function overloading, function parameters are not factored into the identifier.
- NOTE: you may omit from the left until and including a period (
.
), this will make the matching fuzzier which may result in applying the tags to multiple identically named entities in different schemas/tables/etc; for example theid
column in a tableapp_public.users
could be referred to asapp_public.users.id
orusers.id
or justid
.
description
- optionally override the documentation for this entity (rather than pulling from the relevant PostgreSQL comment).tags
- a JSON object containing the tags to apply to the entity; the values within this object must be the booleantrue
, a string, or an array of strings. All other values are invalid and may have unexpected consequences.
A minimal implementation of the smart tags table would be:
create table public.smart_tags (
kind text not null,
identifier text not null,
description text,
tags json not null default '{}',
unique (kind, identifier)
);
A fuller implementation with validation rules and support for watch mode could be something like:
-- This is an optional validation function used in the `check` constraint
-- below; you don't need it, but you do need to adhere to these rules.
create function public.is_valid_smart_tags_json(tags json)
returns boolean as $
-- Must be an object
select json_typeof(tags) = 'object'
and not exists(
select 1
-- And each value in the object...
from json_each(tags)
-- Must be 'true':
where value::text <> 'true'
-- Or a string:
and json_typeof(value) <> 'string'
-- Or an array of strings:
and (
json_typeof(value) <> 'array'
or exists(
select 1
from json_array_elements(value) v2
where json_typeof(v2) <> 'string'
)
)
);
$ language sql immutable;
-- Your smart_tags table; you may rename this if you wish but you must tell
-- PostGraphile what it's called via `graphileBuildOptions.smartTagsTable` as
-- shown in usage above.
create table public.smart_tags (
-- We don't care what kind of primary key you use, nor what you call it.
id serial primary key,
-- These columns are required to have the names and types as stated, the
-- check constraints are optional.
kind text not null check(kind in ('class', 'attribute', 'constraint', 'procedure')),
identifier text not null,
description text,
tags json not null default '{}' check(public.is_valid_smart_tags_json(tags)),
-- We require there's a unique index/constraint (or primary key
-- constraint) on these columns.
unique (kind, identifier)
);
-- This trigger function is used to notify PostGraphile that something has
-- changed within the table; you only need this if you intend to support watch
-- mode.
create function public.tg_smart_tags__notify() returns trigger as $
begin
perform pg_notify('smart_tags_table'::text, ''::text);
return null;
end;
$ language plpgsql;
-- This trigger is for watch mode, calling the function above.
create trigger smart_tags_changed
after insert or update or delete on public.smart_tags
for each statement
execute procedure public.tg_smart_tags__notify();
Thanks 🙏
This plugin was originally sponsored by Surge 🙌