Migration guide: original DFC connectors → LinkML connectors¶
Swap the original DFC connectors for the LinkML generated ones with only mechanical changes. Direction is one-way: LinkML replaces the original connector. Perfect parity is not achieved: but everything either works unchanged or rewrites by rule.
These migrations were verified against original TypeScript 2.0.0-beta.2 and Ruby
2.0.0.pre.beta8. Per-class method tables live in
api-gaps-typescript.md and
api-gaps-ruby.md (generated — see
tests/cross_connector/codeplane_inventory.py).
The worked flows below are executed as tests —
typescript-connector/test/migration-example.test.ts and
ruby-gem/spec/migration_example_spec.rb. Keep guide and tests in sync.
What works unchanged¶
- Package install aside, reads need no changes: import our JSON-LD with
the original connector and vice versa (verified by
run_matrix.py --verify-drop-in, original→LinkML fully green). - Predicates on the wire are identical (
dfc-b:VATnumber, …). - Class inventory: LinkML is a superset (89 classes).
Enterprisestill exists in LinkML (deprecated subclass ofOrganization); v2 originals removed it.
TypeScript: before → after¶
Before (original):
import { Connector } from "@datafoodconsortium/connector";
const c = new Connector();
const org = c.createOrganization({ semanticId: "http://example.com/org1" });
org.setName("Farm Org");
org.setVatNumber("FR12345678901");
const tomato = c.createSuppliedProduct({ semanticId: "http://example.com/tomato" });
tomato.setName("Tomato");
tomato.setDescription("Fresh tomato");
const line = c.createOrderLine({ semanticId: "http://example.com/line1" });
line.setQuantity(5);
line.setDescription("Line 1");
const order = c.createOrder({ semanticId: "http://example.com/order1" });
order.setNumber("ORD-001");
order.setClient(org);
order.addLine(line);
const jsonld = await c.export([org, tomato, line, order], {
outputContext: "https://w3id.org/dfc/ontology/v2.0.0/context/context_2.0.0.json",
});
After (LinkML — same construction shape, fields instead of methods):
import { Connector } from "@siol-data/linkml-connector";
const c = new Connector();
const org = c.createOrganization({
semanticId: "http://example.com/org1",
name: "Farm Org",
vatNumber: "FR12345678901",
});
const tomato = c.createSuppliedProduct({
semanticId: "http://example.com/tomato",
name: "Tomato",
description: "Fresh tomato",
});
const line = c.createOrderLine({
semanticId: "http://example.com/line1",
name: "Line 1",
quantity: 5,
concerns: [tomato.semanticId],
});
const order = c.createOrder({
semanticId: "http://example.com/order1",
orderNumber: "ORD-001",
orderedBy: org,
hasPart: line,
});
const doc = JSON.parse(await c.export(org, tomato, line, order));
Rewrite rules (mechanical, no behavior change):
| Original | LinkML | Note |
|---|---|---|
createX({ semanticId, ... }) |
same (supported) or createX(semanticId, params) |
object form accepted |
setName(x) / getName() |
o.name = x / o.name |
fields, not methods |
setNumber / addLine / setClient / setOffer / setOfferedProduct / setOffers / setPrice |
orderNumber / hasPart / orderedBy / concerns / references / offeredThrough / price |
schema-literal names; see gap doc for the full list |
await c.export([...], { outputContext }) |
await c.export(...) |
context is always the versioned URL string |
| single-object import result | import() always returns an array |
take [0] if you know it is single |
Ruby: before → after¶
Before (original):
c = DataFoodConsortium::Connector::Connector.instance
org = DataFoodConsortium::Connector::Organization.new("http://example.com/org1")
org.name = "Farm Org"
org.vatNumber = "FR12345678901"
tomato = DataFoodConsortium::Connector::SuppliedProduct.new("http://example.com/tomato")
tomato.name = "Tomato"
order = DataFoodConsortium::Connector::Order.new("http://example.com/order1")
order.number = "ORD-001"
order.client = org
jsonld = c.export(org, tomato, order)
After (LinkML — original setter names work through aliases, kwargs optional):
connector = DfcLinkmlConnector::Core::Connector.new
org = DfcLinkmlConnector::Models::Organization.new(
"http://example.com/org1", name: "Farm Org", vatNumber: "FR12345678901"
)
org.vatNumber = "FR12345678901" # alias, like original
tomato = DfcLinkmlConnector::Models::SuppliedProduct.new(
"http://example.com/tomato", name: "Tomato", description: "Fresh tomato"
)
line = DfcLinkmlConnector::Models::OrderLine.new(
"http://example.com/line1", name: "Line 1", quantity: 5,
concerns: tomato.semanticId
)
order = DfcLinkmlConnector::Models::Order.new(
"http://example.com/order1", orderNumber: "ORD-001"
)
order.number = "ORD-001" # alias for order_number
order.client = org # alias for ordered_by
order.lines = line # alias for part
doc = JSON.parse(connector.export(org, tomato, line, order))
Notes:
Connector.instanceexists (default-instance shim); preferConnector.new.- Writers differing only by name are
alias_methods generated fromconfig/dfc-original-api.yaml(number=→order_number=,lines=→part=,client=→ordered_by=,offer=→concerns=,product=→references=,offers=→offered_through=,vatNumber=→vat_number=). Identical names need nothing. - Constructor kwargs are camelCase (
vatNumber:,orderNumber:) just like original setters without=.
Deliberate differences (not migrated, by design)¶
import()always returns an array, even for a single@graphentry.- Export
@contextis a URL string, never an inline object. - LinkML ships 89 classes vs ~50 original factories; extra classes are inert.
- TypeScript has no
supplyProduct-style domain methods — use fields. dfc-b:Enterprisedocuments import asdfc-b:Organizationin LinkML; original v2 dropped the type entirely.
v1 vs v2 original notes¶
- v1 (TS
1.0.0-beta.2, Ruby1.3.0) usesEnterpriseanddfc-b:hasDescription; v2 usesOrganizationand keepsdfc-b:hasDescriptionwhile LinkML registersdfc-b:description. The matrix treats these as expected drops in both directions. - The LinkML
Enterpriseclass +TYPE_ALIASESkeep v1 documents readable.