@ai-matrx/associations

中文文档
0.14.103发布于 2026/10/30MITTypeScriptESM + CJS近 30 天下载 9.2万

THE one way to relate two entities on the AI Matrx platform: the association edge vocabulary, the generated 650-token entity-type registry, the demanded RPC contract (assoc_/cat_/ues_ families), and the typed ports the headless core and React faces bind t

原始使用文档
文档目录6 节
  1. The demanded schema — read this first
  2. The token vocabulary — writes strict, reads tolerant
  3. The ports — two required, the rest DEFAULTED
  4. Code splitting — the package owns it
  5. Versioning
  6. License

@ai-matrx/associations

THE one way to relate two entities on the AI Matrx platform: association edges, categories, favorites/recents, and the generated entity-type vocabulary — shipped as one package a Matrx client or enterprise app installs.

The package ships the types root, the headless /core, the React binding /react, and the package-owned lazy React entry /react/lazy: the service chokepoint, guards, never-throw result funnel, registry merge engine, subscribable cache store, hooks, and the association faces (cards, list, pickers, attached-items sheet, capture toolbar). Everything below about the schema is binding — it is the package's product.

import {
  type EntityTypeToken,        // generated from platform.entity_types
  ENTITY_TYPE_METADATA,        // per-token schema/table/label/titleColumn/contentRole
  isEntityTypeToken,           // runtime guard, narrows to the union
  type AssociationEdge,        // one assoc_for_entity row (both directions)
  type AssociationsRpcResult,  // the never-throw result envelope
  type DemandedRpcName,        // the 27-function demanded RPC surface
  type AssociationsConfig,     // the seven ports a host binds
} from "@ai-matrx/associations";
import {
  createAssociationsStore, // the ONE runtime object a host constructs
} from "@ai-matrx/associations/core";

const store = createAssociationsStore({
  dataSource: supabase,                       // structural subset of SupabaseClient
  identity: { requireUserId },                // throws when unauthenticated
  errorSink: (e) => report(e),                // OPTIONAL — default screams to console
  entityOverlay: { file: { hrefFor: (id) => `/files/f/${id}` } }, // optional
});

await store.load("task", taskId);             // lazy, deduped, subscribable
store.getEdges("task", taskId);               // sync, stable idle default
await store.add({ sourceType: "note", sourceId, targetType: "task", targetId });
store.titles.prime("task", taskId, "Renamed");
await store.favorites.setFavorite("note", noteId, true);

// There is NO boot check to call (0.9.0). A database that cannot answer a
// demanded function says so at the first real call: PostgREST returns
// PGRST202 and the package screams `demanded_schema_violation` with a remedy.

useContainerLinks({ containerType, containerId, orgId }) is the canonical container-side React API. Its attach, detach, and setPinned operations all target the exact role-qualified edge; setPinned preserves the edge's other metadata fields. AssociationCardGrid and the default AssociationList adapter expose those same operations without host wiring.

// /react — the banner'd React binding (peer: react >=18;
// @ai-matrx/design-system is a real dependency — the faces' chrome
// installs automatically). Bind ONCE:
import {
  AssociationsProvider,
  useAssociations,              // the seven hooks keep the app originals' exact signatures
  AssociationCardGrid,          // + AssociationCard / AssociationList / pickers / capture toolbar
  PrimaryEntityProvider,
} from "@ai-matrx/associations/react";

<AssociationsProvider
  store={store}                 // the /core store from createAssociationsStore
  notifier={toastAdapter}       // optional — absent warns once, failures still hit errorSink
  windowShell={windowAdapter}   // optional — absent uses the package's OWN draggable window
  capture={{ uploadFile }}      // optional — bind ONE primitive, get the whole upload flow
  pickerOverrides={{ file: FilePickerWindowAdapter }} // optional — the in-package picker is the default
  entityDoors={{ EntityRef, DoorControls, UnresolvedRef }} // optional — hrefFor anchors by default
>
  <PrimaryEntityProvider value={{ type: "organization", id, orgId, label }}>
    <AssociationCardGrid />
  </PrimaryEntityProvider>
</AssociationsProvider>;

The faces style with Tailwind semantic tokens (bg-card, text-muted-foreground, …): register this package as a Tailwind v4 source in the consuming app (@source "../node_modules/@ai-matrx/associations/dist") the same way @ai-matrx/design-system documents.

The demanded schema — read this first

This package is opinionated like the Python matrx-* packages: it does not adapt to your database — it DEMANDS ours. A consumer who won't run our schema is not this package's consumer. It ships no DDL (the platform DB owns that; managed consumers run on our infrastructure); it ships the demand three ways — documented (here), typed (DemandedRpcSignatures, generated from the live DB), and screamed at the first real call (PGRST202 → demanded_schema_violation, with a remedy). Every violation screams, never degrades.

  1. The edge. platform.associations(source_type, source_id, target_type, target_id, organization_id, label, metadata, role, position, created_by, created_at, deleted_at, …) — one polymorphic table, unique on the 5-tuple (source_type, source_id, target_type, target_id, role) NULLS NOT DISTINCT. Type columns are FK-validated against platform.entity_types.token.
  2. RPC-only. The client holds no grant on the table. Every operation goes through the 27 SECURITY DEFINER functions in DEMANDED_RPC_NAMES (assoc_* ×8, conversation_file* ×3, agent_resource_* ×2, cat_* ×5, ues_* ×4, reference_search_candidates, and the W6 comments family cmt_* ×4). Those readers consume the live-only associations_live view, so tombstone semantics (a trashed entity soft-removes its edges; restore revives them) hold by construction — this package never writes a .is("deleted_at", null) filter. The DB's legacy assoc_link/assoc_list/assoc_unlink trio is deliberately NOT demanded.
  3. Direction is registry-owned: little→big. The resource is the SOURCE, the container the TARGET (task → organization, file → scope). The DB trigger trg_associations_auto_orient rejects reversed writes — this package surfaces that error verbatim and never flips an edge for you.
  4. A link is never a grant. Access lives in iam.permissions / iam.memberships. The ONLY sanctioned association→access bridge is DB-side conveyance (platform.association_types.conveys_max → reachability). This package makes zero access decisions.
  5. A missing function screams. A PostgREST PGRST202 on any demanded RPC maps to the demanded_schema_violation result code and always reaches your errorSink: a database without our schema produces one unambiguous scream, not a mystery toast.

The token vocabulary — writes strict, reads tolerant

EntityTypeToken mirrors platform.entity_types 1:1 (651 active tokens), regenerated by pnpm gen:entity-types inside this package. Skew policy:

  • Writes are STRICT against the installed vocabulary: a token this version doesn't know fails at the guard, in code, before Postgres — exactly like a missing DB type. The fix is pnpm up @ai-matrx/associations (registry changes ship as patch releases), never a raw-string widening.
  • Reads are TOLERANT: an edge arriving with a token this version doesn't know renders through the fallback (default icon, raw label, no door) instead of crashing.

The ports — two required, the rest DEFAULTED

One AssociationsConfig, bound once per host. Only dataSource (a structural subset of SupabaseClient answering the demanded RPCs) and identity are required — construction throws without them, because they ARE your app's identity and no package can invent them.

Everything else ships a working default (C23 — you override ports, you never have to fill them):

PortDefault when unbound
errorSinkcreateDefaultErrorSink() — tagged console.error per event; announces ONCE that no sink is bound and names the remedy.
windowShellDefaultWindowShell — a real draggable / resizable / maximizable / viewport-clamped / portalled / non-modal window; full-width bottom card under 640px. Bind this port only for a real window MANAGER (tray docking, workspace persistence).
captureBind uploadFile(file, { folderPath, visibility }) alone and the package ships the file input, drag-and-drop, batching, the stall watchdog and every loud outcome. Bind requestUpload instead when you own a richer pipeline. Chips with no path behind them are never rendered.
pickerOverridesThe in-package AssociationPicker serves every token (candidate list, search, attach/detach, create-then-associate footer).
entityDoorsDoorControlsSlot renders a real labeled target="_blank" anchor from the overlay's hrefFor; a token with no route renders nothing rather than a door to nowhere.
entityOverlayConvention columns, default icon, ${label}s, no door.
notifierWarn-once through the errorSink, and every user-facing failure still reaches the errorSink. (No toast stack ships: a toast surface needs a host-mounted root, so a "default" would be the silent no-op this package exists to prevent.)
authorDisplaycmt_list's denormalized author fields, then "Unknown user" — never blank.

Code splitting — the package owns it

Import the heavy faces from @ai-matrx/associations/react/lazy and each one arrives in its own chunk on first render. No next/dynamic, no ssr: false, no host glue:

import { AssociationCardGrid } from "@ai-matrx/associations/react/lazy";

For a HOST component you bind to a port, use the same seam:

windowShell: {
  Window: lazyWindowShell(() => import("./hostWindow").then((m) => m.HostWindow)),
},
pickerOverrides: {
  file: lazyPickerOverride(() => import("./hostPicker").then((m) => m.HostPicker)),
},

A per-token picker override should bind useAssociationPickerBridge(props) for its attach/detach writes — it carries the loud partial-outcome, refusal and created-but-unattached reporting so an override never re-implements them.

Versioning

Consumed by Matrx repos as latest (workspace:* inside the aidream monorepo) per THE LATEST LAW — never pinned. Registry token changes → regenerate → patch. Contract changes → minor/major per semver.

License

MIT

版本与发布记录

@ai-matrx/associations 共发布 387 个版本,首次发布于 2026/08/30;过去 12 个月发布了 387 个版本。

版本发布日期距今
0.14.103latest2026/10/306 天前
0.14.1022026/10/289 天前
0.14.1012026/10/289 天前
0.14.1002026/10/2710 天前
0.14.992026/10/2611 天前
0.14.982026/10/2611 天前

其他发布通道:backfill → 0.13.5。

关于 @ai-matrx/associations 的常见问题

@ai-matrx/associations 是什么?

THE one way to relate two entities on the AI Matrx platform: the association edge vocabulary, the generated 650-token entity-type registry, the demanded RPC contract (assoc_/cat_/ues_ families), and the typed ports the headless core and React faces bind t

如何安装 @ai-matrx/associations?

在项目目录运行 npm install @ai-matrx/associations,或使用 pnpm add / yarn add / bun add。

@ai-matrx/associations 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@ai-matrx/associations 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@ai-matrx/associations 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@ai-matrx/associations 可以商用吗?

@ai-matrx/associations 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@ai-matrx/associations 的下载量有多少?

根据 npm 官方下载统计,@ai-matrx/associations 在 2026/10/05 – 2026/11/03 这 30 天内被下载 92,092 次,日均约 3,070 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@ai-matrx/associations 还在维护吗?

最新版本 0.14.103 发布于 2026/10/30(6 天前);至今共发布 387 个版本,其中过去 12 个月发布了 387 个;首次发布于 2026/08/30。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。