# atom.io/foundations/junction

Source: docs/source/pages/docs/foundations/junction.mdx
URL: /docs/foundations/junction

# <low-emphasis>atom.io</low-emphasis>/foundations/junction

`Junction` models bidirectional relations between two string-keyed sides.

Use it when the relationship itself deserves structure: both directions should stay in
sync, cardinality matters, and relation content may need to travel with the edge.

### basic relation
Source: docs/source/exhibits/foundations/junction/basic-relation.ts

```ts
import { Junction } from "atom.io/foundations/junction"

type PlaylistKey = `playlist::${string}`
type TrackKey = `track::${string}`

const playlistTracks = new Junction<`playlist`, PlaylistKey, `track`, TrackKey>({
	between: [`playlist`, `track`],
	cardinality: `n:n`,
})

playlistTracks.set({
	playlist: `playlist::road-trip`,
	track: `track::dreams`,
})

playlistTracks.getRelatedKeys(`playlist::road-trip`) // Set { "track::dreams" }
playlistTracks.getRelatedKey(`track::dreams`) // "playlist::road-trip"
```

## package contents

<table-wrapper>

| Export | Description |
| --- | --- |
| `Junction` | A bidirectional relation helper. |
| `JunctionSchema` | The `between` and `cardinality` schema. |
| `JunctionEntries` | Serialized relation and content entries. |
| `JunctionJSON` | The JSON shape returned by `toJSON()`. |
| `JunctionOverlay` | The overlay type returned by `overlay()`. |
| `RelationsOverlay` | A source-backed relation map used by junction overlays. |
| `Refinement` | A runtime type guard type used by advanced configuration. |

</table-wrapper>

## schema

`between` names the two sides of the relation. Those names power object-style calls.

`cardinality` controls replacement behavior:

- `1:1`: each side may have one relation.
- `1:n`: the first side may have many relations; the second side may have one.
- `n:n`: both sides may have many relations.

## set and delete

Use either positional arguments or object-style arguments.

### set and delete
Source: docs/source/exhibits/foundations/junction/set-and-delete.ts

```ts
import { Junction } from "atom.io/foundations/junction"

const playlistTracks = new Junction({
	between: [`playlist`, `track`],
	cardinality: `n:n`,
})

playlistTracks.set(`road-trip`, `dreams`)
playlistTracks.set({ playlist: `road-trip`, track: `ventura-highway` })

playlistTracks.delete({ playlist: `road-trip`, track: `dreams` })
playlistTracks.delete({ playlist: `road-trip` })
```

Deleting one side removes all relations for that side. Deleting both sides removes that
one relation.

## reading relations

`getRelatedKeys(key)` returns the related keys for the exact stored key as a
`Set`, or `undefined`.

Because this call accepts a bare string key, it does not know which side you
meant beyond the key itself. Its TypeScript overloads are clearest when the two
key types are distinct, and its runtime behavior is clearest when the two key
spaces cannot overlap. If the same string might appear on both sides, prefer
non-overlapping keys such as `playlist::road-trip` and `track::dreams` before
relying on positional reads like `getRelatedKeys(key)`, `getRelatedKey(key)`,
`has(key)`, or `replaceRelations(key, relations)`.

Object-style calls avoid side ambiguity where the API accepts them, such as
`set`, `delete`, and `getRelationEntries`. The advanced `isAType` and `isBType`
refinements help `Junction` infer a side from a bare key during hydration and
safe replacement, but they do not add a side parameter to `getRelatedKeys`.

`getRelatedKey(key)` returns the first related key, or `undefined`. If more than one
relation exists, `warn` is called when a warning function was provided.

`has(key)` checks whether any relation exists for a key. `has(a, b)` checks one
specific relation.

## relation content

Junctions can store JSON-object content on relations.

### relation content
Source: docs/source/exhibits/foundations/junction/relation-content.ts

```ts
import { Junction } from "atom.io/foundations/junction"

const credits = new Junction<
	`album`,
	string,
	`artist`,
	string,
	{ role: string }
>({
	between: [`album`, `artist`],
	cardinality: `n:n`,
})

credits.set({ album: `rumours`, artist: `fleetwood-mac` }, { role: `band` })
credits.getContent(`rumours`, `fleetwood-mac`) // { role: "band" }
```

`getRelationEntries({ album: "rumours" })` returns related keys paired with content.

## replaceRelations

Use `replaceRelations(key, relations)` to replace all relations for one side.

For content-bearing junctions, pass an object whose keys are related IDs and whose values
are relation content.

### replace relations
Source: docs/source/exhibits/foundations/junction/replace-relations.ts

```ts
import { Junction } from "atom.io/foundations/junction"

const credits = new Junction<
	`album`,
	string,
	`artist`,
	string,
	{ role: string }
>({
	between: [`album`, `artist`],
	cardinality: `n:n`,
})

credits.replaceRelations(`rumours`, {
	"fleetwood-mac": { role: `band` },
	"ken-caillat": { role: `producer` },
})
```

By default, replacement updates reverse relations safely. Passing `{ reckless: true }`
uses the faster replacement path and can leave old reverse relations in place.

## JSON

`toJSON()` returns the schema, relation entries, and content entries.

Pass that shape back to the constructor to hydrate a junction.

### json
Source: docs/source/exhibits/foundations/junction/json.ts

```ts
import { Junction } from "atom.io/foundations/junction"

const playlistTracks = new Junction({
	between: [`playlist`, `track`],
	cardinality: `n:n`,
})

playlistTracks.set({ playlist: `road-trip`, track: `dreams` })

const json = playlistTracks.toJSON()
const restored = new Junction(json)
```

## overlays

`overlay()` creates a staged junction over the current one.

`incorporate(overlay)` applies the staged relation and content changes back to the
source junction.

`RelationsOverlay` backs junction relation maps whose values are sets. When it reads a
set from the source map, it stores and returns a `SetOverlay` for that set so relation
members can be staged without mutating the source relation set.
