npm package: @semantic-namespace/atlas
Description: Atlas semantic registry for JavaScript — query, analyze, and reason about software architecture
Latest version: 0.1.0-alpha

# Atlas JS — Semantic Registry for JavaScript

Atlas JS compiles the [Atlas](../README.md) Clojure(Script) library to an ES module. Load an EDN registry snapshot or define entities from scratch, then query, analyze, and reason about your software architecture — all locally in the browser or Node.js.

**No server required.** The same `.cljc` source that runs on the JVM is compiled to JS. Every function behaves identically in both runtimes.

## Two Builds

| Build | Bundle | Gzipped | Includes |
|-------|--------|---------|----------|
| **Full** (`dist/atlas.js`) | 546 KB | 125 KB | Registry + Query + Datalog + Invariants |
| **Slim** (`dist-slim/atlas.js`) | 291 KB | 72 KB | Registry + Query + Ontology (no Datascript) |

Use **slim** when you only need registration, querying, and analytics.
Use **full** when you also need datalog graph traversal (upstream/downstream closure, blast radius).

## Install & Build

```bash
cd js/
npm install
npm run build        # full  → dist/atlas.js
npm run build:slim   # slim  → dist-slim/atlas.js
npm run build:all    # both
npm run dev          # full, watch mode (source maps)
npm run dev:slim     # slim, watch mode (source maps)
npm run clean
```

---

## Quick Start — Load an EDN Snapshot

```javascript
import { loadRegistry, summary, findByAspect } from "./dist/atlas.js";
// or: from "./dist-slim/atlas.js"  (same API, minus datalog)

// Node.js
import { readFileSync } from "fs";
const edn = readFileSync("my-registry.edn", "utf-8");

// Browser
// const edn = await fetch("/registries/my-registry.edn").then(r => r.text());

const count = loadRegistry(edn);
console.log(`Loaded ${count} entities`);
console.log(summary());
// => { total: 489, "unique-aspects": 269, ... }

const auth = findByAspect("domain/auth");
// => { "#{...}": { "atlas/dev-id": "fn/validate-token", ... }, ... }
```

### Exporting from CLJ

From a running Clojure REPL:

```clojure
(spit "my-registry.edn" (pr-str @atlas.registry/registry))
```

Or fetch from the Atlas UI server:

```javascript
const edn = await fetch("http://localhost:8082/api/atlas/registry").then(r => r.text());
loadRegistry(edn);
```

---

## Quick Start — Define Entities from JS

You can build a full registry from scratch without any EDN file.

```javascript
import { resetRegistry, register, findByAspect, summary } from "./dist/atlas.js";

resetRegistry();

// Structure component (infrastructure)
register("component/postgres", "atlas/structure-component",
  ["domain/persistence", "tier/foundation", "protocol/sql"],
  { "structure-component/deps": [] }
);

// Execution function (business logic)
register("fn/authenticate-user", "atlas/execution-function",
  ["domain/auth", "tier/service", "operation/validate"],
  {
    "execution-function/context": ["auth/email", "auth/password"],
    "execution-function/response": ["auth/session-token", "auth/user-id"],
    "execution-function/deps": ["component/postgres"]
  }
);

// Interface endpoint (API layer)
register("endpoint/login", "atlas/interface-endpoint",
  ["domain/auth", "tier/api"],
  {
    "interface-endpoint/context": ["auth/email", "auth/password"],
    "interface-endpoint/response": ["auth/session-token"],
    "interface-endpoint/deps": ["fn/authenticate-user"]
  }
);

console.log(summary());
// => { total: 3, "unique-aspects": 7, ... }
```

---

## Full Example — Calendar App

This example defines a complete calendar application with 18 entities across 3 tiers.

```javascript
import {
  resetRegistry, register,
  findByAspect, findByDevId, findDevIdsWithAspect,
  where, semanticSimilarity, queryAlgebra,
  aspectFrequency, relatedAspects,
  byTier, dependencyGraph, domainCoupling, impactOfChange,
  traceDataFlow, summary
} from "./dist/atlas.js";

resetRegistry();

// ═══════════════════════════════════════════════════════════════════
// TIER: FOUNDATION — Infrastructure components
// ═══════════════════════════════════════════════════════════════════

register("component/postgres", "atlas/structure-component",
  ["domain/persistence", "tier/foundation", "protocol/sql"],
  { "structure-component/deps": [] }
);

register("component/redis", "atlas/structure-component",
  ["domain/cache", "tier/foundation", "protocol/redis"],
  { "structure-component/deps": [] }
);

register("component/google-oauth", "atlas/structure-component",
  ["domain/auth", "tier/foundation", "protocol/oauth", "integration/external"],
  { "structure-component/deps": [] }
);

register("component/google-calendar", "atlas/structure-component",
  ["domain/calendar", "tier/foundation", "protocol/rest", "integration/external"],
  { "structure-component/deps": [] }
);

register("component/smtp", "atlas/structure-component",
  ["domain/notifications", "tier/foundation", "protocol/smtp", "integration/external"],
  { "structure-component/deps": [] }
);

// ═══════════════════════════════════════════════════════════════════
// TIER: SERVICE — Business logic functions
// ═══════════════════════════════════════════════════════════════════

register("fn/authenticate-user", "atlas/execution-function",
  ["domain/auth", "tier/service", "operation/validate"],
  {
    "execution-function/context": ["auth/email", "auth/password"],
    "execution-function/response": ["auth/session-token", "auth/user-id"],
    "execution-function/deps": ["component/postgres", "component/google-oauth"]
  }
);

register("fn/refresh-oauth-token", "atlas/execution-function",
  ["domain/auth", "tier/service", "operation/refresh"],
  {
    "execution-function/context": ["auth/user-id"],
    "execution-function/response": ["auth/oauth-token"],
    "execution-function/deps": ["component/google-oauth", "component/redis"]
  }
);

register("fn/list-calendars", "atlas/execution-function",
  ["domain/calendar", "tier/service", "operation/read", "scope/collection"],
  {
    "execution-function/context": ["auth/user-id", "auth/oauth-token"],
    "execution-function/response": ["calendar/list"],
    "execution-function/deps": ["component/google-calendar"]
  }
);

register("fn/fetch-events", "atlas/execution-function",
  ["domain/calendar", "tier/service", "operation/read", "scope/detail"],
  {
    "execution-function/context": ["calendar/id", "calendar/date-range"],
    "execution-function/response": ["calendar/events"],
    "execution-function/deps": ["component/google-calendar", "component/redis"]
  }
);

register("fn/create-event", "atlas/execution-function",
  ["domain/calendar", "tier/service", "operation/write", "effect/write"],
  {
    "execution-function/context": ["calendar/id", "calendar/event-data"],
    "execution-function/response": ["calendar/event-id"],
    "execution-function/deps": ["component/google-calendar"]
  }
);

register("fn/check-availability", "atlas/execution-function",
  ["domain/calendar", "domain/scheduling", "tier/service", "operation/read"],
  {
    "execution-function/context": ["calendar/user-ids", "calendar/date-range"],
    "execution-function/response": ["scheduling/available-slots"],
    "execution-function/deps": ["component/google-calendar", "component/redis"]
  }
);

register("fn/send-invite", "atlas/execution-function",
  ["domain/notifications", "domain/scheduling", "tier/service", "effect/write"],
  {
    "execution-function/context": ["scheduling/event-id", "scheduling/invitees"],
    "execution-function/response": ["notification/sent-count"],
    "execution-function/deps": ["component/smtp", "component/postgres"]
  }
);

register("fn/cache-events", "atlas/execution-function",
  ["domain/cache", "domain/calendar", "tier/service", "operation/write"],
  {
    "execution-function/context": ["calendar/events", "auth/user-id"],
    "execution-function/response": ["cache/stored"],
    "execution-function/deps": ["component/redis"]
  }
);

// ═══════════════════════════════════════════════════════════════════
// TIER: API — Interface endpoints
// ═══════════════════════════════════════════════════════════════════

register("endpoint/login", "atlas/interface-endpoint",
  ["domain/auth", "tier/api"],
  {
    "interface-endpoint/context": ["auth/email", "auth/password"],
    "interface-endpoint/response": ["auth/session-token"],
    "interface-endpoint/deps": ["fn/authenticate-user"]
  }
);

register("endpoint/calendars", "atlas/interface-endpoint",
  ["domain/calendar", "tier/api", "operation/read", "scope/collection"],
  {
    "interface-endpoint/context": ["auth/session-token"],
    "interface-endpoint/response": ["calendar/list"],
    "interface-endpoint/deps": ["fn/refresh-oauth-token", "fn/list-calendars"]
  }
);

register("endpoint/events", "atlas/interface-endpoint",
  ["domain/calendar", "tier/api", "operation/read", "scope/detail"],
  {
    "interface-endpoint/context": ["auth/session-token", "calendar/id", "calendar/date-range"],
    "interface-endpoint/response": ["calendar/events"],
    "interface-endpoint/deps": ["fn/refresh-oauth-token", "fn/fetch-events", "fn/cache-events"]
  }
);

register("endpoint/create-event", "atlas/interface-endpoint",
  ["domain/calendar", "tier/api", "operation/write", "effect/write"],
  {
    "interface-endpoint/context": ["auth/session-token", "calendar/id", "calendar/event-data"],
    "interface-endpoint/response": ["calendar/event-id"],
    "interface-endpoint/deps": ["fn/refresh-oauth-token", "fn/create-event"]
  }
);

register("endpoint/schedule-meeting", "atlas/interface-endpoint",
  ["domain/scheduling", "tier/api", "operation/write", "effect/write"],
  {
    "interface-endpoint/context": ["auth/session-token", "calendar/user-ids",
                                    "calendar/date-range", "calendar/event-data"],
    "interface-endpoint/response": ["calendar/event-id", "notification/sent-count"],
    "interface-endpoint/deps": ["fn/check-availability", "fn/create-event", "fn/send-invite"]
  }
);

// Data schemas
register("schema/user", "atlas/data-schema",
  ["domain/auth"],
  { "data-schema/fields": ["auth/user-id", "auth/email", "auth/oauth-token"] }
);

register("schema/calendar-event", "atlas/data-schema",
  ["domain/calendar"],
  { "data-schema/fields": ["calendar/event-id", "calendar/id",
                             "calendar/event-data", "calendar/date-range"] }
);

// ═══════════════════════════════════════════════════════════════════
// QUERYING — Everything below works on the registry we just built
// ═══════════════════════════════════════════════════════════════════

console.log("=== summary() ===");
console.log(summary());
// { total: 20, "unique-aspects": 26, ... }


// ─── findByAspect ──────────────────────────────────────────────────

console.log("\n=== findByAspect('domain/auth') ===");
const auth = findByAspect("domain/auth");
Object.values(auth).forEach(v => console.log(" ", v["atlas/dev-id"]));
// component/google-oauth, fn/authenticate-user, fn/refresh-oauth-token,
// endpoint/login, schema/user

console.log("\n=== findByAspect(['domain/calendar', 'tier/service']) ===");
const calSvc = findByAspect(["domain/calendar", "tier/service"]);
Object.values(calSvc).forEach(v => console.log(" ", v["atlas/dev-id"]));
// fn/list-calendars, fn/fetch-events, fn/create-event,
// fn/check-availability, fn/cache-events
// (5 functions — all calendar-related service tier)


// ─── findByDevId ───────────────────────────────────────────────────

console.log("\n=== findByDevId('fn/authenticate-user') ===");
const [compoundId, entity] = findByDevId("fn/authenticate-user");
console.log("compound-id:", compoundId);
// ["domain/auth", "operation/validate", "tier/service", "atlas/execution-function"]
console.log("context:", entity["execution-function/context"]);
// ["auth/email", "auth/password"]
console.log("response:", entity["execution-function/response"]);
// ["auth/session-token", "auth/user-id"]
console.log("deps:", entity["execution-function/deps"]);
// ["component/postgres", "component/google-oauth"]


// ─── findDevIdsWithAspect ──────────────────────────────────────────

console.log("\n=== findDevIdsWithAspect('effect/write') ===");
console.log(findDevIdsWithAspect("effect/write"));
// ["fn/create-event", "fn/send-invite", "endpoint/create-event",
//  "endpoint/schedule-meeting"]


// ─── where (predicate filtering) ──────────────────────────────────

console.log("\n=== where: functions with >1 dependency ===");
const multiDep = where((id, val) => {
  const deps = val["execution-function/deps"];
  return deps && deps.length > 1;
});
Object.values(multiDep).forEach(v =>
  console.log(" ", v["atlas/dev-id"], "→", v["execution-function/deps"])
);
// fn/authenticate-user → [component/postgres, component/google-oauth]
// fn/refresh-oauth-token → [component/google-oauth, component/redis]
// fn/check-availability → [component/google-calendar, component/redis]
// fn/fetch-events → [component/google-calendar, component/redis]
// fn/send-invite → [component/smtp, component/postgres]

console.log("\n=== where: entities touching external integrations ===");
const external = where((id, val) => id.includes("integration/external"));
Object.values(external).forEach(v => console.log(" ", v["atlas/dev-id"]));
// component/google-oauth, component/google-calendar, component/smtp


// ─── semanticSimilarity ───────────────────────────────────────────

console.log("\n=== semanticSimilarity of endpoint/schedule-meeting ===");
const schedId = findByDevId("endpoint/schedule-meeting")[0];
const similar = semanticSimilarity(schedId, 0.3);
similar.forEach(s =>
  console.log(
    "  sim=" + s.similarity.toFixed(2),
    "shared:", s.shared.join(", ")
  )
);
// sim=0.67 shared: tier/api, operation/write, effect/write, atlas/interface-endpoint
// sim=0.33 shared: tier/api, atlas/interface-endpoint


// ─── aspectFrequency ──────────────────────────────────────────────

console.log("\n=== aspectFrequency() ===");
const freq = aspectFrequency();
Object.entries(freq).forEach(([k, v]) => console.log(`  ${k} = ${v}`));
// domain/calendar = 8, tier/service = 7, atlas/execution-function = 7,
// domain/auth = 5, tier/foundation = 5, ...


// ─── relatedAspects ───────────────────────────────────────────────

console.log("\n=== relatedAspects('domain/calendar') ===");
const rel = relatedAspects("domain/calendar");
Object.entries(rel).forEach(([k, v]) => console.log(`  ${k} = ${v}`));
// tier/service = 4, operation/read = 3, operation/write = 3,
// effect/write = 2, tier/api = 2, ...


// ─── byTier ───────────────────────────────────────────────────────

console.log("\n=== byTier('atlas/dev-id') ===");
const tiers = byTier("atlas/dev-id");
Object.entries(tiers).forEach(([tier, devIds]) =>
  console.log(`  ${tier}: ${devIds.join(", ")}`)
);
// tier/foundation: component/redis, component/postgres, ... (5)
// tier/service: fn/authenticate-user, fn/refresh-oauth-token, ... (8)
// tier/api: endpoint/login, endpoint/calendars, ... (5)


// ─── dependencyGraph ──────────────────────────────────────────────

console.log("\n=== dependencyGraph('atlas/dev-id', 'execution-function/deps') ===");
const graph = dependencyGraph("atlas/dev-id", "execution-function/deps");
graph.forEach(g => console.log(`  ${g.id} → ${JSON.stringify(g.deps)}`));
// fn/authenticate-user → ["component/postgres","component/google-oauth"]
// fn/refresh-oauth-token → ["component/google-oauth","component/redis"]
// fn/list-calendars → ["component/google-calendar"]
// fn/fetch-events → ["component/google-calendar","component/redis"]
// fn/create-event → ["component/google-calendar"]
// fn/check-availability → ["component/google-calendar","component/redis"]
// fn/send-invite → ["component/smtp","component/postgres"]
// fn/cache-events → ["component/redis"]


// ─── impactOfChange ──────────────────────────────────────────────

console.log("\n=== impactOfChange('fn/refresh-oauth-token') ===");
const impact = impactOfChange(
  "fn/refresh-oauth-token",
  "atlas/dev-id",
  "interface-endpoint/deps",
  "execution-function/response"
);
console.log("produces:", impact["entity/produces"]);
// ["auth/oauth-token"]
console.log("dependents:", impact["direct-dependents"]);
// ["endpoint/calendars", "endpoint/events", "endpoint/create-event"]


// ─── traceDataFlow ───────────────────────────────────────────────

console.log("\n=== traceDataFlow('auth/session-token') ===");
const flow = traceDataFlow(
  "auth/session-token",
  "execution-function/response",
  "interface-endpoint/context"
);
console.log("producers:", Object.values(flow.producers).map(v => v["atlas/dev-id"]));
// ["fn/authenticate-user"]
console.log("consumers:", Object.values(flow.consumers).map(v => v["atlas/dev-id"]));
// ["endpoint/calendars", "endpoint/events", "endpoint/create-event",
//  "endpoint/schedule-meeting"]
console.log("connected:", flow["connected?"]);
// true


// ─── queryAlgebra ────────────────────────────────────────────────

console.log("\n=== queryAlgebra: calendar ∩ write ===");
const inter = queryAlgebra({
  intersection: [["domain/calendar"], ["effect/write"]]
});
console.log("count:", inter.length);
// 2 (fn/create-event, endpoint/create-event)

console.log("\n=== queryAlgebra: service \\ write (read-only services) ===");
const readOnly = queryAlgebra({
  difference: [["tier/service"], ["effect/write"]]
});
console.log("count:", readOnly.length);
// 5 (fn/authenticate-user, fn/refresh-oauth-token, fn/list-calendars,
//    fn/fetch-events, fn/check-availability)
```

---

## API Reference

### Convention: Qualified Keywords as Strings

Atlas uses Clojure's qualified keywords (`:namespace/name`) as the fundamental identifier. In JS, these become strings with the same `"namespace/name"` format:

| Clojure | JavaScript |
|---------|-----------|
| `:domain/auth` | `"domain/auth"` |
| `:atlas/execution-function` | `"atlas/execution-function"` |
| `#{:domain/auth :tier/service}` | `["domain/auth", "tier/service"]` |
| `{:atlas/dev-id :fn/foo}` | `{ "atlas/dev-id": "fn/foo" }` |

### Registry Lifecycle

| Function | Signature | Description |
|----------|-----------|-------------|
| `loadRegistry` | `(ednString) → count` | Parse EDN string, replace registry, return entity count |
| `getRegistry` | `() → object` | Return entire registry as JS object |
| `resetRegistry` | `() → null` | Clear the registry |
| `register` | `(devId, type, aspects, value) → compoundId` | Register with explicit dev-id |
| `register` | `(type, aspects, value) → compoundId` | Register with auto-generated dev-id |

#### `register` Parameters

| Parameter | Type | Example |
|-----------|------|---------|
| `devId` | `string` | `"fn/validate-token"` |
| `type` | `string` | `"atlas/execution-function"` |
| `aspects` | `string[]` | `["domain/auth", "tier/service"]` |
| `value` | `object` | `{ "execution-function/context": ["auth/token"] }` |

Returns the compound identity (as an array of strings).

### Query Functions

| Function | Signature | Description |
|----------|-----------|-------------|
| `findByAspect` | `(aspect) → {compoundId: entity}` | Find entities containing aspect(s). Pass a string for single, array for AND. |
| `findByDevId` | `(devId) → [compoundId, entity]` | Lookup by dev-id. Returns `null` if not found. |
| `findDevIdsWithAspect` | `(aspect) → string[]` | All dev-ids containing the aspect. |
| `findExact` | `(identityArray) → entity` | Exact compound identity match. |
| `where` | `(predFn) → {compoundId: entity}` | Filter by predicate `(compoundIdArray, valueObject) → boolean`. |
| `allIdentities` | `() → string[][]` | All compound identities as array of arrays. |

### Scoring & Matching

| Function | Signature | Description |
|----------|-----------|-------------|
| `matchScore` | `(identity, aspects, mode) → number` | Score 0.0-1.0. Mode: `"and"`, `"or"`, `"count"`. |
| `queryMatches` | `(identity, queryMap) → boolean` | Match with `selected`, `negated`, `mode`, `"min-score"`. |
| `semanticSimilarity` | `(identity, minSim?) → [{identity, shared, similarity}]` | Jaccard similarity, sorted descending. |

### Analytics

| Function | Signature | Description |
|----------|-----------|-------------|
| `aspectFrequency` | `() → {aspect: count}` | How often each aspect appears across all entities. |
| `relatedAspects` | `(aspect) → {aspect: count}` | Aspects that co-occur with the given aspect. |
| `identityStats` | `() → [{identity, size, value}]` | All identities sorted by size descending. |

### Architecture Analysis

| Function | Signature | Description |
|----------|-----------|-------------|
| `dependencyGraph` | `(idKey, depsKey) → [{id, deps, identity}]` | Build dependency graph from a property key. |
| `byTier` | `(idKey) → {tier: devIds[]}` | Group entities by `tier/*` aspect. |
| `domainCoupling` | `(idKey, depsKey) → [{domain, "depends-on", "entity-count"}]` | Cross-domain dependency analysis. |
| `impactOfChange` | `(entityId, idKey, depsKey, responseKey) → {entity, "entity/produces", "direct-dependents"}` | What breaks if this entity changes. |

The `idKey` and `depsKey` parameters tell Atlas which properties to use:

```javascript
// For execution functions:
dependencyGraph("atlas/dev-id", "execution-function/deps");

// For interface endpoints:
dependencyGraph("atlas/dev-id", "interface-endpoint/deps");

// impactOfChange — who lists this entity in their deps?
impactOfChange("fn/refresh-oauth-token",
  "atlas/dev-id",             // which property holds the entity id
  "interface-endpoint/deps",  // which property holds dependency lists
  "execution-function/response" // which property holds outputs
);
```

### Data Flow

| Function | Signature | Description |
|----------|-----------|-------------|
| `findProducers` | `(dataKey, propertyKey) → {compoundId: entity}` | Entities whose `propertyKey` contains `dataKey`. |
| `findConsumers` | `(dataKey, propertyKey) → {compoundId: entity}` | Same, for consumers. |
| `traceDataFlow` | `(dataKey, producerKey, consumerKey) → {producers, consumers, "connected?"}` | Full trace for a data key. |

```javascript
// Who produces auth/session-token? (it's in their response)
findProducers("auth/session-token", "execution-function/response");

// Who consumes it? (it's in their context)
findConsumers("auth/session-token", "interface-endpoint/context");

// Full trace
traceDataFlow("auth/session-token",
  "execution-function/response",   // producer property
  "interface-endpoint/context"     // consumer property
);
```

### Set Algebra

| Function | Signature | Description |
|----------|-----------|-------------|
| `queryAlgebra` | `({intersection, union, or difference}) → compoundId[][]` | Set operations on compound identities. |

```javascript
// Entities in BOTH domain/calendar AND effect/write
queryAlgebra({ intersection: [["domain/calendar"], ["effect/write"]] });

// Entities in domain/calendar OR domain/auth
queryAlgebra({ union: [["domain/calendar"], ["domain/auth"]] });

// Entities in tier/service but NOT effect/write (read-only services)
queryAlgebra({ difference: [["tier/service"], ["effect/write"]] });
```

### Registry Introspection

| Function | Signature | Description |
|----------|-----------|-------------|
| `registeredTypes` | `() → string[]` | All registered entity types. |
| `entityType` | `(identity) → string` | Extract the entity type from a compound identity. |
| `aspects` | `(identity) → string[]` | Extract non-type aspects from a compound identity. |
| `summary` | `() → object` | Registry stats: total, unique aspects, top aspects, largest identities. |
| `validateTypes` | `() → {valid, "registered-types"}` | Check all entities use known types. |

### Ontology

| Function | Signature | Description |
|----------|-----------|-------------|
| `allOntologies` | `() → {compoundId: ontology}` | All registered ontologies. |
| `ontologyFor` | `(entityType) → object` | Ontology definition for an entity type. |

### Invariants (full build only)

| Function | Signature | Description |
|----------|-----------|-------------|
| `checkInvariants` | `() → {valid, violations}` | Run all registered invariant checks. |

### Datalog Queries (full build only)

Datascript runs in the browser. These functions query a datalog database built from the registry. Call `rebuildDatalog()` after loading or modifying the registry.

| Function | Signature | Description |
|----------|-----------|-------------|
| `rebuildDatalog` | `() → null` | Rebuild the datalog DB (cached after first call). |
| `queryEntitiesWithAspect` | `(aspect) → string[]` | Find dev-ids by aspect via datalog. |
| `queryDependencies` | `(devId) → string[]` | Direct dependencies. |
| `queryReverseDependencies` | `(devId) → string[]` | Who depends on this entity. |
| `queryUpstreamClosure` | `(devIdOrArray, maxHops) → [{entity, hops}]` | Transitive dependencies with hop count. |
| `queryDownstreamClosure` | `(devIdOrArray, maxHops) → [{entity, hops}]` | Transitive dependents with hop count. |
| `queryProducersOf` | `(dataKey) → string[]` | Entities producing a data key (via datalog). |
| `queryConsumersOf` | `(dataKey) → string[]` | Entities consuming a data key (via datalog). |

> **Note:** Datalog dependency/data-flow queries require **extractors** registered in the registry (`:atlas/datalog-extractor` entities). Without extractors, the datalog DB only contains aspect facts. The set-based query functions (`dependencyGraph`, `traceDataFlow`, `impactOfChange`) read properties directly and always work.

### Conversion Helpers

| Function | Signature | Description |
|----------|-----------|-------------|
| `toJS` | `(cljData) → jsData` | Keywords → strings, sets → arrays, maps → objects. |
| `toClj` | `(jsData) → cljData` | Strings with `"/"` → keywords. |

---

## Entity Types

Atlas has built-in entity types, each with its own set of property keys:

### `atlas/execution-function` — Business logic

```javascript
register("fn/validate-token", "atlas/execution-function",
  ["domain/auth", "tier/service", "operation/validate"],
  {
    "execution-function/context": ["auth/token"],         // inputs
    "execution-function/response": ["auth/valid?"],       // outputs
    "execution-function/deps": ["component/oauth"]        // infrastructure deps
  }
);
```

### `atlas/structure-component` — Infrastructure

```javascript
register("component/postgres", "atlas/structure-component",
  ["domain/persistence", "tier/foundation", "protocol/sql"],
  {
    "structure-component/deps": []
  }
);
```

### `atlas/interface-endpoint` — API endpoints

```javascript
register("endpoint/login", "atlas/interface-endpoint",
  ["domain/auth", "tier/api"],
  {
    "interface-endpoint/context": ["auth/email", "auth/password"],   // request inputs
    "interface-endpoint/response": ["auth/session-token"],           // response outputs
    "interface-endpoint/deps": ["fn/authenticate-user"]              // functions called
  }
);
```

### `atlas/data-schema` — Data structures

```javascript
register("schema/user", "atlas/data-schema",
  ["domain/auth"],
  {
    "data-schema/fields": ["auth/user-id", "auth/email"]
  }
);
```

### Custom entity types

You can register any entity type — just use a qualified `"namespace/name"` string:

```javascript
register("flow/checkout", "myapp/user-flow",
  ["domain/cart", "domain/payment"],
  {
    "user-flow/steps": ["cart/review", "payment/enter", "payment/confirm"],
    "user-flow/happy-path": true
  }
);
```

---

## Common Aspect Namespaces

Aspects are the reusable semantic tags that form compound identities. Common namespaces:

| Namespace | Purpose | Examples |
|-----------|---------|---------|
| `domain/*` | Business domain | `domain/auth`, `domain/cart`, `domain/calendar` |
| `tier/*` | Architecture layer | `tier/foundation`, `tier/service`, `tier/api` |
| `operation/*` | What it does | `operation/read`, `operation/write`, `operation/validate` |
| `effect/*` | Side effects | `effect/write`, `effect/send`, `effect/delete` |
| `protocol/*` | Communication protocol | `protocol/sql`, `protocol/rest`, `protocol/oauth` |
| `integration/*` | Boundary type | `integration/external`, `integration/internal` |
| `scope/*` | Granularity | `scope/inbox`, `scope/panel`, `scope/widget` |

You can invent any namespace — these are conventions, not restrictions.

---

## EDN Registry Format

An Atlas EDN file is a Clojure map: `{compound-identity-set → value-map}`.

```edn
{#{:atlas/execution-function :domain/auth :operation/validate :tier/service}
 {:atlas/dev-id :fn/validate-token
  :atlas/type :atlas/execution-function
  :execution-function/context [:auth/token]
  :execution-function/response [:auth/valid?]
  :execution-function/deps #{:component/oauth}}

 #{:atlas/structure-component :domain/auth :protocol/oauth :tier/foundation}
 {:atlas/dev-id :component/oauth
  :atlas/type :atlas/structure-component
  :structure-component/deps #{}}

 #{:atlas/interface-endpoint :domain/auth :tier/api}
 {:atlas/dev-id :endpoint/login
  :atlas/type :atlas/interface-endpoint
  :interface-endpoint/context [:auth/email :auth/password]
  :interface-endpoint/response [:auth/session-token]
  :interface-endpoint/deps #{:fn/validate-token}}}
```

---

## How It Works

Atlas JS is **not a reimplementation**. It compiles the same `.cljc` source files that run on the JVM to JavaScript via ClojureScript + shadow-cljs:

```
core/src/atlas/
├── registry.cljc    ─┐
├── query.cljc        │  Identical source
├── datalog.cljc      ├─ compiled to both
├── invariant.cljc    │  JVM (Clojure) and
├── ontology.cljc    ─┘  JS (ClojureScript)

js/src/atlas_js/
├── core.cljs        ← Full: thin interop layer (keyword ↔ string)
└── core_slim.cljs   ← Slim: same, without datascript
```

## Project Structure

```
js/
├── shadow-cljs.edn         # Build configuration (4 targets)
├── package.json             # npm package
├── src/
│   └── atlas_js/
│       ├── core.cljs        # Full JS API (registry + query + datalog + invariants)
│       └── core_slim.cljs   # Slim JS API (registry + query + ontology, no datascript)
├── dist/                    # Full production build
│   └── atlas.js             #   546 KB / 125 KB gzip
├── dist-slim/               # Slim production build
│   └── atlas.js             #   291 KB / 72 KB gzip
├── dist-dev/                # Full dev build (source maps)
└── dist-slim-dev/           # Slim dev build (source maps)
```