janus/entities
The core data model for Janus: entity identifiers, self-annotated document trees, routes, redirects, URIs, and Rama path navigators.
Everything is accessible from a single namespace — janus.entities.api.
Setup
Add the dependency:
;; deps.edn
{io.forward-publishing.janus/entities {:mvn/version "..."}}
Require the API namespace:
(require '[janus.entities.api :as ent])
Entities
An entity is the essential object that Janus operates on.
It is a map carrying at least :janus/id and :janus/tag.
By design, Janus overlays its information on top of input-system data.
All information that Janus encodes is therefore present under namespaced keys (:janus/, :janus.node/, :janus.link/*, etc.), leaving the source system’s own keys untouched.
{:janus/id #janus/id [:ld/document 42]
:janus/tag :janus.tag/document
:janus/version 3
;; source-system keys are passed through as-is
:title "Hello World"
:author "Jane Doe"}
| Key | Description |
|---|---|
|
An |
|
A qualified keyword denoting the entity type (e.g. |
|
Optional integer version counter. |
|
Optional. |
EntityId
EntityId is a tagged-literal identifier that Janus uses to reference entities from external source systems.
It is a Clojure record with two fields:
-
tag— a qualified keyword whose namespace identifies the source system and whose name identifies the entity type within that system (e.g.:ld/document— a document from the Livingdocs system). -
id— the source system’s own identifier for the entity (a string or long). Whenidisnilthe entity-id is called an alias — a well-known name pointing to an entity (e.g. a homepage).
;; Full entity id
(ent/->id :ld/document 42)
;; => #janus/id [:ld/document 42]
;; Alias (id is nil)
(ent/->alias :homepage/zuz)
;; => #janus/id :homepage/zuz
IntoEntityId coercion
The single-arity (ent/→id x) coerces common types via the IntoEntityId protocol:
| Input type | Behaviour |
|---|---|
|
Returned as-is. |
|
Creates an alias: |
|
1- or 2-element tuple: |
|
Requires a |
|
Parsed via |
Predicates
| Function | Description |
|---|---|
|
True if the value is an |
|
True if the value is an |
|
True if the map has both |
|
True when |
|
True when |
Documents and Nodes
A document is an entity whose content is structured as a self-annotated tree of nodes. Every node in the tree carries its own traversal instructions — no external schema is needed to walk or transform it.
A node is any map with :janus.node/tag.
The root entity node uses :janus/tag as its implicit node tag (:janus/root).
Child segments
The key :janus.node/children is a map of segment name → keypath pointing to where the child data lives in the same map:
{:janus/tag :janus.tag/document
:janus/id #janus/id [:ld/document 1]
;; Self-describing structure:
:janus.node/children {:header :header
:body [:containers :body]}
;; Actual data at the described locations:
:header {:janus.node/tag :header
:title "Hello"}
:containers
{:body [{:janus.node/tag :paragraph
:text "World"}]}}
The keypath can be a single keyword (:header) or a vector of keywords ([:containers :body]).
The value at that path is either a single node or a vector of nodes.
Links
Nodes can reference other entities through link sets — sets of EntityId values stored under special keys.
| Key | Navigator | Semantics |
|---|---|---|
|
|
The referred entity’s summary is needed to render this node (e.g. an author name). |
|
|
The referred entity’s full document is needed. |
|
|
A reference without content dependency (e.g. a teaser image link). |
Routes
A route map describes how an entity maps to URIs across different origins (publishing targets):
{:zug-ch #janus/uri "/artikel/hello-world"
:luzerner-zeitung #janus/uri "/news/hello-world"
:* #janus/uri "/hello-world" ;; fallback
:janus.route/canonical :zug-ch ;; or an absolute URI
:janus.route/external #janus/uri "https://example.com/x"} ;; optional override
Route resolution
| Function | Description |
|---|---|
|
|
|
|
(ent/uri-for [:zug-ch (ent/uri "https://www.zug.ch")] routes)
;; => #janus/uri "https://www.zug.ch/artikel/hello-world"
Redirects
A redirect is a map with the following keys:
| Key | Description |
|---|---|
|
Simple keyword identifying the publishing origin. |
|
The path being redirected (must start with |
|
A URI (absolute or relative) to redirect to. |
|
HTTP status code: |
|
Boolean flag. |
redirect? returns true when a map contains :active, :target, and :type.
URIs
Utility functions wrapping java.net.URI:
| Function | Description |
|---|---|
|
Coerces to |
|
Creates a relative URI from an unencoded path string (encodes it). |
|
Like |
|
Predicates on |
|
|
|
Expands German umlauts for URI-friendliness: ae, oe, ue, ss. |
Entity-URI conversion
Entity IDs have a URI representation used for serialisation and data readers:
janus:tag/name:id -- local entity id (string id) janus:tag/name:42:i -- local entity id (integer id, :i suffix) janusg:system/id:tag/name:id:i -- global entity id (with system prefix) janus:tag/name -- alias (no id part)
| Function | Description |
|---|---|
|
|
|
Parses a |
|
Like |
Data Readers
Register ent/data-readers with your EDN reader to enable the tagged literals below.
| Reader tag | Input form | Example |
|---|---|---|
|
Keyword (alias) or |
|
|
String |
|
|
ISO-8601 string |
|
Navigating Documents with Rama Paths
The janus/entities package provides a rich set of Specter-compatible navigators for traversing and transforming document trees.
These work with Rama’s path infrastructure (com.rpl.rama.path).
Tree traversal
| Navigator | Description |
|---|---|
|
Navigates to a view of the node’s child segments as a map of segment-name → child node(s). Supports both selection and transformation (adding/removing/replacing segments). |
|
|
|
Recursively traverses all sub-nodes depth-first, post-order. |
(require '[com.rpl.rama.path :as sp])
;; Select all nodes in a document
(sp/select [NODES] document)
;; Select all paragraph nodes
(sp/select [NODES (node-tag= :paragraph)] document)
;; Add a child segment
(sp/multi-transform [CHILD-SEGMENTS :footer
(termval-under {:janus.node/tag :footer} [:containers :footer])]
document)
Filter navigators
| Navigator | Description |
|---|---|
|
|
|
|
|
|
|
|
|
|
Simple navigators
| Navigator | Description |
|---|---|
|
Navigates to the value of |
|
Navigates to the value of the node tag ( |
Link navigators
| Navigator | Description |
|---|---|
|
Navigates to the |
|
Navigates to the |
|
Navigates to the |
|
Enumerates each include link as |
|
Enumerates each entity-include link as |
|
Enumerates each ref link as |
|
Enumerates all links as |
|
|
;; Get all links from all nodes in a document
(sp/select [NODES ALL-LINKS] document)
;; Get only include links from the root
(sp/select [INCLUDE-LINKS] document)
;; Filter nodes that reference a specific entity
(sp/select [NODES (sp/selected? REFS sp/ALL (id= target-eid))] document)