Map data between two TypeDB databases using declarative rules.
GLAV — global-local-as-view — writes the correspondence between two schemas
down as data rather than as code. Each rule is a pair of TypeQL fragments:
a source_match that reads rows out of one database and a target_insert that
writes them into another. The orchestrator is mechanical and holds no domain
knowledge; everything specific to a mapping lives in its rules directory, where
it can be read, reviewed and argued with.
name: character
description: Characters, re-parented onto the new base hierarchy
depends_on: [campaign]
notes: |
legacy-only `annotation` is carried into `description`; 3 rows had one.
source_match: |
match $c isa old-character, has id $i, has name $n;
fetch { "id": $i, "name": $n, "notes": $c.annotation };
target_insert: |
insert $c isa new-character,
has id $id,
has name $name,
has description ?notes;$ glav_mapper.py run --source-db old --target-db new --rules-dir rules/
campaign source 4 inserted 4 no-match 0 failed 0
character source 114 inserted 114 no-match 0 failed 0
campaign_membership source 473 inserted 473 no-match 0 failed 0
...
totals: {'source_rows': 1589, 'inserted': 1589, 'no_match': 0, 'failed': 0}
Cross-schema migration. A supertype changed, a type was renamed, a base
hierarchy moved — anything TypeDB will not accept as a define, which means
the database cannot be upgraded in place and its own exporter may no longer be
able to read it. Identity is the existing id, preserved: the rows on both
sides are the same rows, and minting new ones would break every reference held
outside the database.
Cross-domain mapping. Two schemas modelling different things, where the target has no natural identifier for what the source describes. Identity is skolemised — a deterministic id hashed from chosen key values, so a second run recognises the first run's rows instead of duplicating them.
Choosing wrongly is the most expensive mistake available here, and it is silent in both directions.
As a Claude Code plugin:
/plugin marketplace add sciknow-io/TypeDB-GLAV-mapper
/plugin install glav-mapper
Or standalone — it is one file with two dependencies:
uv run --project skills/glav-mapper python skills/glav-mapper/glav_mapper.py --helpglav_mapper.py plan --rules-dir RULES
glav_mapper.py run --source-db A --target-db B --rules-dir RULES --dry-run
glav_mapper.py run --source-db A --target-db B --rules-dir RULES
glav_mapper.py verify --source-db A --target-db B --rules-dir RULES--source-port and --target-port may differ: mapping across two servers is
the same operation as mapping across two databases. The target must already
exist and already carry its schema — this tool moves data, it does not define
types.
Watch three, not one. inserted is the good news and failed is loud, but
no_match is the quiet one: an insert whose match matches nothing writes
nothing and raises nothing. A mapper that does not count that will report
complete success over an empty database. This one counts it.
skills/glav-mapper/SKILL.md— when to reach for this, and the order of workskills/glav-mapper/USAGE.md— the full rule format, substitution forms and countersexamples/schema-migration/— 16 rules that moved a live database onto a new schema, reproduced unedited: optional attributes, a polymorphic relation,verify_match, and aderivetransformation. 1589 of 1589 rows, nothing lost.examples/cross-domain/— skolemised identity, and why one rule keys on a single field and the next on two
TypeDB 3.x, Python 3.11–3.13, uv.
Apache-2.0