Prerequisites
- A Wazoo private beta account. Sign up at wazoo.dev/beta.
- A world token (
wzw_) with thedata:readanddata:writescopes, generated on the Console tokens page, exported asWORLDS_TOKEN. - A world ID, exported as
WORLD_ID. curlandjqfor the API calls, and Node.js 22.18 or later, Deno, or Bun to run the SHACL validator.
Load the example data
The examples use two people. Ethan has an email address on record and Sandra does not. Save the following aspeople.ttl:
people.ttl
Response
.rq file sent with the same pattern:
Is a missing fact false?
No. A SQL table makes a closed-world assumption: if a row or column value is absent, the answer is no. RDF makes an open-world assumption: if a fact is absent, the answer is unknown. A world records what you asserted and nothing about what you did not. That difference matters for agents. An agent that reads a missing email address as “has no email” invents a fact. An agent that reads it as “no email on record” reports what it knows. Ask for every person and their email address, if any:emails.rq
Response
email binding. Read it as “not recorded”. When your
application needs a definite negative, assert it as its own fact so a query can
tell it apart from a gap.
Will a schemaless graph turn into a mess?
Not if you add constraints once the model settles. A world accepts any well-formed RDF, so you can start without a schema, the way you start a JavaScript project without types. When a shape stabilizes, describe it with SHACL shapes and validate data against them before import, the way you add TypeScript types to code that already runs. The example data above has no schema. The next section adds one without rewriting the existing facts. Worlds does not validate writes against SHACL shapes yet, so run validation in your pipeline before data reaches the world. Validation on every write, on by default with a per-world opt-out, is planned (worlds-sdk-ts#151).How does a schema change work?
In SQL, splitting aname column into first and last name means a migration:
alter the table, backfill every row, and deploy readers and writers in step. In
a world, you add the new properties beside the old one and move readers and
writers over at their own pace.
1
Add the new properties beside the old one
Assert A SPARQL update requires the
schema:givenName and schema:familyName for Ethan. His schema:name
stays in place, so existing readers keep working.split-name.rq
Response
data:write scope.2
Read either shape
Update readers to prefer the new properties and fall back to the old one. The
Ethan’s display name now comes from the new properties and Sandra’s from
CONCAT is unbound when either part is missing, so COALESCE falls back to
schema:name.display-names.rq
Response
schema:name. Neither record had to change for the query to work.3
Require the new shape for new data
Save a shape that requires both new properties on every A new record written in the old shape fails validation. Save it as
Validate it with
rdf-validate-shacl, the SHACL
library behind the Wiki CLI. Save the following as
Install the dependencies and run it with Node.js 22.18 or later, Deno, or Bun:The script exits with status 1, so a CI step or import script stops here. SHACL
is a W3C standard, so any conforming processor, such as
pySHACL, accepts the same shapes. Rewrite
the record in the new shape:Import it with the same request as
schema:Person as
shapes.ttl:shapes.ttl
gregory.ttl:gregory.ttl
validate.ts:validate.ts
Output
gregory.ttl
Output
people.ttl. The display-name query returns
“Gregory Hale” from the new properties.4
Backfill and retire the old property when you choose
Records written before the change, like Sandra’s, still carry only
schema:name. Validate only new data until you backfill them. Once every reader
uses the new properties, you can stop writing schema:name. Nothing sets a
deadline for that step.Related pages
- Update a world covers imports and patches.
- Query a world covers SPARQL against a world.
- Comparison with relational databases compares the two models side by side.