Skip to content

Latest commit

 

History

History
110 lines (85 loc) · 4.02 KB

File metadata and controls

110 lines (85 loc) · 4.02 KB

TypeDB GLAV Mapper

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}

Two jobs, one mechanism

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.

Install

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 --help

Use

glav_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.

The counter that matters

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.

Documentation

Requirements

TypeDB 3.x, Python 3.11–3.13, uv.

License

Apache-2.0