npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

xmemory-relations

v1.4.0

Published

Typed, equivalence-aware associations between Things for XMemory

Readme

xmemory-relations

Typed, equivalence-aware associations between Things for XMemory. Stores and queries directed and undirected edges with filters and pagination, using nx-mongo for persistence and xmemory-equal for thing identity and canonicalization.

Features

  • Upsert/delete directed and undirected associations (idempotent by unique identity)
  • Query with filters (type, direction, confidence, tags, source) and pagination
  • Canonical by default: write and read use canonical thing IDs so equivalent aliases do not create duplicate edges
  • Optional read modes: canonical, expand (all equivalents), or none
  • ThingRef: supports thingType (e.g. question, product_name) and optional externalIdentifier (e.g. jira:PROJ-123, github:issue:owner/repo#14) for “go back to source” pointers; passed through to xmemory-equal
  • Endpoints: getAssociations and getEdge return full ThingRecords for from/to, including thingType and externalIdentifier when provided by xmemory-equal (e.g. when it exposes getThingById)
  • Edge-centric retrieval: getEdgeById(edgeId), getEdgesByIds(edgeIds), getAssociationsBetween(a, b, query?), findEdges(query) (by type, direction, endpoint IDs, confidence, source, metadata), and getRelatedNodes(thing, query?) (deduped neighbor nodes + edges)
  • Scope-aware filtering: retrieval uses xEqual-assisted filtering (readEqualityMode: canonical / expand / none). Optional endpoint scope (scope: { db?, collection? }) and endpointThingTypes so Scoper can stay within scope; filter by sessionId / drivingFieldRelationId for mapper
  • Bulk upsert: bulkAssociate(items) for mapper and batch workloads; idempotent per edge
  • Mapper metadata: optional sessionId, drivingFieldRelationId on associations for mapper use; stored as top-level fields and filterable in findEdges
  • No Mongo client creation: you pass an initialized nx-mongo handle and an xmemory-equal client

Install

npm install xmemory-relations nx-mongo xmemory-equal

Requirements

  • nx-mongo: initialized (e.g. SimpleMongoHelper) with your MongoDB connection; relations use it for the {prefix}_associations collection.
  • xmemory-equal: EqualClient created with the same namespace and a compatible nxMongo provider (e.g. same DB). Used for ensureThing, resolve, and getEquivalents. If it exposes getThingById(id), relations use it to return full thing records (including externalIdentifier) for association endpoints; optional getThingByExternalIdentifier(externalId, namespace?) allows querying by external id in getAssociations(thing).

ThingRef and ThingRecord

Inputs use ThingRef (from xmemory-equal): namespace, thingType, kind, ref, and optional externalIdentifier. Thing identity is (namespace, thingType, kind, refNorm); externalIdentifier is a stable pointer to an external system (e.g. jira:PROJ-123, confluence:page:123, futurox:question:uuid). All association APIs accept ThingRef and pass it through to equal.

Returned ThingRecords (on AssociationRecord.from and .to) include id, namespace, thingType, kind, refRaw, refNorm, optional externalIdentifier, and timestamps. When xmemory-equal provides getThingById, relations use it so endpoints are full records from equal; otherwise a minimal record shape is returned.

Config and env

The client can read these from process.env (optional overrides in config):

| Env | Description | Default | |-----|-------------|--------| | XMEMORY_WRITE_EQUALITY_MODE | canonical or none | canonical | | XMEMORY_READ_EQUALITY_MODE | canonical, expand, or none | canonical | | XMEMORY_NAMESPACE | Resolved namespace (shared with equal) | — | | XMEMORY_COLLECTION_PREFIX | Collection name = {prefix}_associations | xmemory |

Important: This package does not read MONGO_URI or MONGO_DB. You configure and initialize nx-mongo yourself and pass it in.

Usage

import { SimpleMongoHelper } from "nx-mongo";
import { createEqualClient } from "xmemory-equal";
import { createRelationsClient } from "xmemory-relations";

const helper = new SimpleMongoHelper(process.env.MONGO_URI!);
await helper.initialize({ databaseName: process.env.MONGO_XMEMORY_DB });

const nxEqual = createEqualClient({
  nxMongo: { getDb: () => helper.getDatabaseByName(process.env.MONGO_XMEMORY_DB!), withTransaction: (cb) => helper.withTransaction(cb) },
  namespace: "my-namespace",
  defaultThingType: "generic",
});
await nxEqual.ensureIndexes();

const relations = createRelationsClient({
  nxMongo: helper,
  nxEqual,
  namespace: "my-namespace",
  database: process.env.MONGO_XMEMORY_DB,
});
await relations.ensureIndexes();

// Directed association (thingType and optional externalIdentifier)
await relations.associate(
  { namespace: "my-namespace", thingType: "person_name", kind: "literal", ref: "user-1" },
  { namespace: "my-namespace", thingType: "person_name", kind: "literal", ref: "user-2" },
  "follows",
  { direction: "directed", confidence: 0.9 }
);

// Undirected
await relations.associate(thingRef("a"), thingRef("b"), "related", { direction: "undirected" });

// Query
const result = await relations.getAssociations(thingRef("user-1"), {
  types: ["follows"],
  direction: "outgoing",
  limit: 50,
  offset: 0,
});

// Remove
const { deleted } = await relations.removeAssociation(thingRef("a"), thingRef("b"), "related", { direction: "undirected" });

// Single edge (record.id is the Mongo _id when loaded from store)
const edge = await relations.getEdge(thingRef("a"), thingRef("b"), "related", { direction: "undirected" });

// Edge-centric: by id, between two nodes, or find by type/metadata
const one = await relations.getEdgeById(edge!.id);
const between = await relations.getAssociationsBetween(thingRef("a"), thingRef("b"), { types: ["related"] });
const { edges } = await relations.findEdges({ types: ["follows"], limit: 20 });
const { nodes, edges: nodeEdges } = await relations.getRelatedNodes(thingRef("user-1"), { types: ["follows"] });

API summary

  • createRelationsClient(config)RelationsClient

    • Required: nxMongo, nxEqual, namespace
    • Optional: database, collectionPrefix, writeEqualityMode, readEqualityMode
  • associate(from, to, type, opts?)Promise<AssociationRecord>
    Upserts one association (directed or undirected). Optional: direction, confidence, weight, source, tags, reason, sessionId, drivingFieldRelationId.

  • bulkAssociate(items: BulkAssociateItem[])Promise<AssociationRecord[]>
    Bulk upsert (required for mapper). Each item: { from, to, type, opts? }. Idempotent per edge.

  • removeAssociation(from, to, type, opts?)Promise<{ deleted: number }>
    Deletes by identity (0 or 1).

  • getAssociations(thing, query?)Promise<AssociationQueryResult>
    Filters: types, direction (incoming/outgoing/both), confidenceMin/confidenceMax, tagsAny, source. Scope-aware: scope?: { db?, collection? }, endpointThingTypes?: string[] (only edges whose both endpoints match). Pagination: limit (default 50, max 500), offset. Optional readEqualityMode override. If thing has only externalIdentifier (and namespace), relations resolve it via equal’s getThingByExternalIdentifier when available. Returns { items, total, limit, offset } with full thing records for endpoints (including thingType and externalIdentifier when provided by equal).

  • getEdgeById(edgeId)Promise<AssociationRecord | null>
    Returns the association document by its Mongo _id (hex string). Returns null for invalid or missing id.

  • getEdgesByIds(edgeIds)Promise<AssociationRecord[]>
    Returns association records for the given edge ids (invalid ids are skipped).

  • getAssociationsBetween(a, b, query?)Promise<AssociationRecord[]>
    Returns all edges between two things (directed both ways + undirected). Optional query: types, limit, offset.

  • findEdges(query)Promise<FindEdgesResult>
    Edge-centric query: types, direction (directed/undirected/any), endpointThingIds, confidenceMin/confidenceMax, source, sessionId, drivingFieldRelationId, metadataEquals (key-value match on top-level fields). Scope-aware: scope?: { db?, collection? }, endpointThingTypes?: string[]. limit, offset. Returns { edges, total, limit, offset }. Required for mapper/scoper reporting.

  • getRelatedNodes(thing, query?)Promise<GetRelatedNodesResult>
    Same filters as getAssociations; returns { nodes, edges, total, limit, offset } with deduped neighbor nodes (useful for UI and mapper reports).

  • ensureIndexes()Promise<void>
    Ensures unique and query indexes on the associations collection (with partial filters for directed vs undirected).

  • getEdge(from, to, type, opts?)Promise<AssociationRecord | null>

Equality modes

  • Write

    • canonical (default): ensureThing then resolve to canonical IDs before storing → no duplicate edges across equivalent things.
    • none: store using the IDs returned by ensureThing (no resolve).
  • Read

    • canonical (default): resolve the input thing to one canonical ID and query that.
    • expand: resolve to all equivalent IDs, query all, dedupe (max group size 500; throws ExpandLimitExceededError if exceeded).
    • none: query the single thing ID only.

Scope-aware retrieval and multi-DB

  • Scope filter (scope: { db?, collection? }): When provided in getAssociations(thing, query) or findEdges(query), only edges whose both endpoints (Things) match the given db and/or collection are returned. Scope lives on Things in @xmemory/equal; relations resolve endpoints via getThingById and filter by optional db/collection on the returned ThingRecord. If equal does not store or return these fields, scope filtering has no effect.
  • Endpoint thing types (endpointThingTypes: string[]): Only edges whose both endpoints have thingType in this list are returned.
  • Multi-DB limitation: Relations store only Thing IDs; they do not store database or collection names. Scope (db/collection) is resolved from @xmemory/equal at read time. Cross-DB or multi-dB edge traversal is therefore limited to what equal can resolve per namespace; when using multiple MongoDB databases or collections for Things, ensure equal exposes db/collection on ThingRecords and that getThingById (or optional listThings) is consistent with that model.

Collection and indexes

  • Collection: {collectionPrefix}_associations (e.g. xmemory_associations).
  • Directed docs: namespace, direction: "directed", type, fromThingId, toThingId, plus optional metadata (confidence, weight, source, tags, reason, sessionId, drivingFieldRelationId) and timestamps.
  • Undirected docs: same but leftThingId / rightThingId (min/max of the two thing IDs).
  • Unique indexes (with partial filter by direction) and query indexes for outgoing/incoming/undirected lookups.
  • Each document has a Mongo _id; use getEdgeById(edgeId) with the hex string of _id (e.g. from getEdge(…).id).

See docs/specs.md for the full specification.

Errors

  • NamespaceMismatchError: config namespace does not match the one expected for the Equal client.
  • ExpandLimitExceededError: expand-mode read would exceed the max equivalence group size (500).

License

ISC