@ai-matrx/associations 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,648 @@
1
+ import { EntityOverlayMap, EntityTypeToken, EntityOverlayEntry, ErrorSink, AssociationsRpcError, AssociationsDataSource, AssociationsIdentity, AssociationTargetType, AssociationsRpcResult, AssociationEdge, AssociationTargetEdge, AssociationSourceEdge, CategoryDimension, PlatformCategory, UserStateKind, UserEntityState, AssociationsEntry, CategoriesEntry, AssociationsConfig } from '../index.cjs';
2
+ import 'react';
3
+
4
+ /** The universal ownership column post-2026-reorg. */
5
+ declare const DEFAULT_OWNER_COLUMN = "created_by";
6
+ /** The universal org-scoping column. */
7
+ declare const DEFAULT_ORG_COLUMN = "organization_id";
8
+ type ContentRole = "utility" | "source" | "destination" | "hybrid" | "container";
9
+ /** Runtime guard for the DB's free-text `content_role` column. */
10
+ declare function isContentRole(value: string | null): value is ContentRole;
11
+ /**
12
+ * Normalise a raw `kind`/`type` string onto its canonical entity token.
13
+ * Returns the input unchanged when it is already canonical or unknown — the
14
+ * caller's existing "no registry entry → plain text" path still applies.
15
+ */
16
+ declare function resolveEntityToken(raw: string): string;
17
+ /**
18
+ * Fully-resolved entity descriptor — generated metadata + host overlay, with
19
+ * safe fallbacks so an un-overlaid token still renders (derived plural, no
20
+ * door, `Icon: null` → the consumer's default) even if it can't be queried
21
+ * for candidates yet.
22
+ */
23
+ interface EntityInfo {
24
+ token: EntityTypeToken;
25
+ label: string;
26
+ labelPlural: string;
27
+ /** Postgres schema of the backing table (from the generated registry). */
28
+ schema: string;
29
+ /** Backing table name (from the generated registry). */
30
+ table: string;
31
+ /** Title column for the picker, or null when none is registered. */
32
+ titleColumn: string | null;
33
+ /** Ownership column to scope candidate reads to the current user. */
34
+ ownerColumn: string;
35
+ /** Org-scoping column. */
36
+ orgColumn: string;
37
+ /** Host-supplied icon component, or null (consumer renders its default). */
38
+ Icon: EntityOverlayEntry["Icon"] | null;
39
+ hrefFor: ((id: string) => string) | null;
40
+ scopeable: boolean;
41
+ category: string | null;
42
+ /** Knowledge-model grouping bucket (resource surfaces group by this). */
43
+ contentRole: ContentRole;
44
+ /** Candidate-source override (rag-backed tokens etc.); null = generic read. */
45
+ listCandidates: EntityOverlayEntry["listCandidates"] | null;
46
+ /** True when a picker can list real candidates (title column OR override). */
47
+ canListCandidates: boolean;
48
+ }
49
+ interface EntityRegistry {
50
+ /** Merge more overlay entries in (host binding, W4 pickers, tests). */
51
+ registerEntityOverlay(overlay: EntityOverlayMap): void;
52
+ /** Resolve a KNOWN token to its full descriptor. */
53
+ getEntityInfo(token: EntityTypeToken): EntityInfo;
54
+ /** Safe variant for raw strings (e.g. an edge's `otherType`). */
55
+ tryGetEntityInfo(token: string): EntityInfo | null;
56
+ /** Resolve from a live `(schema, table)` pair, or null. */
57
+ tryGetEntityInfoByTable(schema: string, table: string): EntityInfo | null;
58
+ /** Resolve a raw table name only when it maps to exactly one entity. */
59
+ tryGetEntityInfoByUniqueTableName(table: string): EntityInfo | null;
60
+ /**
61
+ * Tokens the DB classifies as knowledge resources via a valid
62
+ * `content_role` AND that can list candidates. The default set for card
63
+ * grids, resource sections, and attach pickers.
64
+ */
65
+ curatedTokens(): EntityTypeToken[];
66
+ /**
67
+ * Tokens offered as reference "Allowed types" — DB-driven via
68
+ * `platform.entity_types.reference_pickable`. A pickable token with no
69
+ * title column and no candidate override is a config defect: excluded and
70
+ * screamed to the errorSink, never silently shown broken.
71
+ */
72
+ listableTokens(): EntityTypeToken[];
73
+ }
74
+ /** Build the merge engine over the host overlay (empty overlay is legal). */
75
+ declare function createEntityRegistry(errorSink: ErrorSink, initialOverlay?: EntityOverlayMap): EntityRegistry;
76
+
77
+ /** True when `value` is a syntactically valid UUID string. */
78
+ declare function isUuid(value: unknown): value is string;
79
+ /** Return the first failing check, or null if all pass. Pure. */
80
+ declare function firstError(...checks: (AssociationsRpcError | null)[]): AssociationsRpcError | null;
81
+ interface AssociationGuards {
82
+ /** A single id must be a real UUID. Kills the "cute text id" bug class. */
83
+ checkUuid(field: string, value: unknown): AssociationsRpcError | null;
84
+ /** Every id in an array must be a real UUID. */
85
+ checkUuidArray(field: string, values: readonly unknown[]): AssociationsRpcError | null;
86
+ /** A type token must be registered in `platform.entity_types`. */
87
+ checkToken(field: string, value: unknown): AssociationsRpcError | null;
88
+ /**
89
+ * Map a legacy/phantom token to its registered canonical token.
90
+ * Pass-through for anything not in the alias table. Loud by doctrine — the
91
+ * alias firing is a bug at the callsite, recovered here, never silent.
92
+ */
93
+ normalizeEntityToken(value: string): string;
94
+ }
95
+ /** Bind the guard set to the host's errorSink (the loud seam). */
96
+ declare function createAssociationGuards(errorSink: ErrorSink): AssociationGuards;
97
+
98
+ declare function err(code: AssociationsRpcError["code"], message: string, detail?: unknown, hint?: string): {
99
+ ok: false;
100
+ error: AssociationsRpcError;
101
+ };
102
+ declare function ok<T>(data: T): {
103
+ ok: true;
104
+ data: T;
105
+ };
106
+ /**
107
+ * True when a failure is the TRANSPORT dying, not the server answering: the
108
+ * browser is offline, asleep, mid-wifi-handoff, or DNS/TLS failed; also the
109
+ * bounded Envoy/Supabase gateway shape emitted when the upstream connection
110
+ * dies before response headers. Use it to decide how LOUD a failure is,
111
+ * never whether to handle it.
112
+ */
113
+ declare function isTransportFailure(e: unknown): boolean;
114
+ type PgErrorMapper = (e: unknown) => AssociationsRpcError;
115
+ type PgErrorPairMapper = (e: unknown) => [AssociationsRpcError["code"], string, unknown, string | undefined];
116
+ interface RpcResultHelpers {
117
+ ok: typeof ok;
118
+ err: typeof err;
119
+ mapPgError: PgErrorMapper;
120
+ mapPgErrorPair: PgErrorPairMapper;
121
+ }
122
+ /**
123
+ * Bind the loud funnel to the host's errorSink. Every failure in this
124
+ * package's services passes through the returned `mapPgError` exactly once.
125
+ */
126
+ declare function createRpcResultHelpers(errorSink: ErrorSink): RpcResultHelpers;
127
+
128
+ interface CoreDeps {
129
+ dataSource: AssociationsDataSource;
130
+ identity: AssociationsIdentity;
131
+ errorSink: ErrorSink;
132
+ guards: AssociationGuards;
133
+ rpc: RpcResultHelpers;
134
+ registry: EntityRegistry;
135
+ }
136
+
137
+ interface ConversationFileLink {
138
+ fileId: string;
139
+ label: string | null;
140
+ metadata: unknown;
141
+ createdAt: string;
142
+ }
143
+ interface AddAssociationArgs {
144
+ sourceType: string;
145
+ sourceId: string;
146
+ targetType: AssociationTargetType;
147
+ targetId: string;
148
+ orgId?: string;
149
+ label?: string;
150
+ metadata?: unknown;
151
+ /**
152
+ * Exact file → conversation updates preserve existing metadata unless the
153
+ * caller explicitly opts into replacement. Invalid for every other pair.
154
+ */
155
+ replaceMetadata?: boolean;
156
+ role?: string;
157
+ position?: number;
158
+ /**
159
+ * Typed edge payload (Edge Payload System). `payloadKind` must be a
160
+ * registered `platform.edge_payload_kind`; the DB trigger validates
161
+ * `payload` against its JSON Schema and hard-fails on any mismatch. Real
162
+ * logic an edge carries goes HERE — `metadata` is for loose annotations.
163
+ */
164
+ payloadKind?: string;
165
+ payload?: unknown;
166
+ }
167
+ interface AssociationsServiceApi {
168
+ listForEntity(rawType: string, id: string): Promise<AssociationsRpcResult<{
169
+ edges: AssociationEdge[];
170
+ }>>;
171
+ listConversationFiles(conversationId: string): Promise<AssociationsRpcResult<{
172
+ files: ConversationFileLink[];
173
+ }>>;
174
+ listForTargets(rawTargetType: string, targetIds: string[]): Promise<AssociationsRpcResult<{
175
+ edges: AssociationTargetEdge[];
176
+ }>>;
177
+ listForTargetsVisible(rawTargetType: string, targetIds: string[]): Promise<AssociationsRpcResult<{
178
+ edges: AssociationTargetEdge[];
179
+ }>>;
180
+ listForSources(rawSourceType: string, sourceIds: string[], rawTargetType?: string): Promise<AssociationsRpcResult<{
181
+ edges: AssociationSourceEdge[];
182
+ }>>;
183
+ add(args: AddAssociationArgs): Promise<AssociationsRpcResult<{
184
+ id: string;
185
+ }>>;
186
+ remove(args: {
187
+ sourceType: string;
188
+ sourceId: string;
189
+ targetType: string;
190
+ targetId: string;
191
+ role?: string;
192
+ }): Promise<AssociationsRpcResult<null>>;
193
+ setTargets(args: {
194
+ sourceType: string;
195
+ sourceId: string;
196
+ targetType: AssociationTargetType;
197
+ targetIds: string[];
198
+ orgId?: string;
199
+ role?: string;
200
+ }): Promise<AssociationsRpcResult<null>>;
201
+ addAgentResource(args: {
202
+ agentId: string;
203
+ sourceType: string;
204
+ sourceId: string;
205
+ label?: string;
206
+ metadata?: unknown;
207
+ }): Promise<AssociationsRpcResult<{
208
+ id: string;
209
+ }>>;
210
+ removeAgentResource(args: {
211
+ agentId: string;
212
+ sourceType: string;
213
+ sourceId: string;
214
+ }): Promise<AssociationsRpcResult<null>>;
215
+ removeForEntity(rawType: string, id: string): Promise<AssociationsRpcResult<null>>;
216
+ }
217
+ declare function createAssociationsService(deps: CoreDeps): AssociationsServiceApi;
218
+
219
+ interface CategoriesServiceApi {
220
+ list(dimension?: CategoryDimension): Promise<AssociationsRpcResult<{
221
+ categories: PlatformCategory[];
222
+ }>>;
223
+ create(args: {
224
+ dimension: CategoryDimension;
225
+ name: string;
226
+ orgId: string;
227
+ parentId?: string | null;
228
+ color?: string | null;
229
+ icon?: string | null;
230
+ slug?: string | null;
231
+ }): Promise<AssociationsRpcResult<{
232
+ id: string;
233
+ }>>;
234
+ update(args: {
235
+ id: string;
236
+ name: string;
237
+ slug: string | null;
238
+ color: string | null;
239
+ icon: string | null;
240
+ position: number | null;
241
+ }): Promise<AssociationsRpcResult<{
242
+ id: string;
243
+ }>>;
244
+ reparent(args: {
245
+ id: string;
246
+ parentId: string | null;
247
+ }): Promise<AssociationsRpcResult<{
248
+ id: string;
249
+ }>>;
250
+ delete(id: string): Promise<AssociationsRpcResult<{
251
+ id: string;
252
+ }>>;
253
+ }
254
+ declare function createCategoriesService(deps: CoreDeps): CategoriesServiceApi;
255
+
256
+ interface FavoritesServiceApi {
257
+ setState(args: {
258
+ entityType: string;
259
+ entityId: string;
260
+ isFavorite?: boolean;
261
+ isPinned?: boolean;
262
+ isHidden?: boolean;
263
+ }): Promise<AssociationsRpcResult<null>>;
264
+ setFavorite(entityType: string, entityId: string, isFavorite: boolean): Promise<AssociationsRpcResult<null>>;
265
+ setPinned(entityType: string, entityId: string, isPinned: boolean): Promise<AssociationsRpcResult<null>>;
266
+ setHidden(entityType: string, entityId: string, isHidden: boolean): Promise<AssociationsRpcResult<null>>;
267
+ list(kind?: UserStateKind): Promise<AssociationsRpcResult<{
268
+ items: UserEntityState[];
269
+ }>>;
270
+ getBulk(entityType: string, entityIds: string[]): Promise<AssociationsRpcResult<{
271
+ items: UserEntityState[];
272
+ }>>;
273
+ touch(entityType: string, entityId: string): Promise<AssociationsRpcResult<null>>;
274
+ }
275
+ declare function createFavoritesService(deps: CoreDeps): FavoritesServiceApi;
276
+
277
+ interface CandidateRecord {
278
+ id: string;
279
+ title: string;
280
+ }
281
+ interface ListCandidatesArgs {
282
+ token: EntityTypeToken;
283
+ /** Scope to this owner's rows when the entity declares an owner column. */
284
+ ownerId?: string | null;
285
+ /** Case-insensitive title filter. */
286
+ search?: string;
287
+ limit?: number;
288
+ }
289
+ type CandidatesResult = {
290
+ ok: true;
291
+ data: CandidateRecord[];
292
+ } | {
293
+ ok: false;
294
+ error: string;
295
+ };
296
+ interface UniversalCandidate extends CandidateRecord {
297
+ token: EntityTypeToken;
298
+ }
299
+ interface SearchAcrossTokensArgs {
300
+ /** Tokens to search. Defaults to every curated token in the registry. */
301
+ tokens?: EntityTypeToken[];
302
+ search: string;
303
+ ownerId?: string | null;
304
+ /** Cap per token — a universal search shows a few of everything. */
305
+ perTokenLimit?: number;
306
+ /** Max in-flight token queries. */
307
+ concurrency?: number;
308
+ }
309
+ interface ReferenceSearchCandidatesFn {
310
+ (args: {
311
+ p_token: string;
312
+ p_search?: string;
313
+ p_limit?: number;
314
+ p_ids?: string[];
315
+ }): Promise<{
316
+ data: Array<{
317
+ id: string;
318
+ title: string | null;
319
+ }>;
320
+ error: null;
321
+ } | {
322
+ data: null;
323
+ error: {
324
+ message: string;
325
+ };
326
+ }>;
327
+ }
328
+ interface CandidatesServiceApi {
329
+ /**
330
+ * The universal candidate reader RPC (`public.reference_search_candidates`)
331
+ * — shared by candidate search here and title resolution in titles.ts.
332
+ */
333
+ callReferenceSearchCandidates: ReferenceSearchCandidatesFn;
334
+ listAssociationCandidates(args: ListCandidatesArgs): Promise<CandidatesResult>;
335
+ /**
336
+ * ONE search over EVERY listable entity type — the "attach anything from
337
+ * anywhere" primitive. Registry-driven client fan-out, capped and
338
+ * concurrency-limited. Per-token failures are screamed and skipped — one
339
+ * broken table never blanks the search.
340
+ */
341
+ searchCandidatesAcrossTokens(args: SearchAcrossTokensArgs): Promise<UniversalCandidate[]>;
342
+ }
343
+ declare function createCandidatesService(deps: CoreDeps): CandidatesServiceApi;
344
+
345
+ declare function entityTitleCacheKey(token: string, id: string): string;
346
+ interface TitlesServiceApi {
347
+ /** Read the cache synchronously (for render paths after a fetch settled). */
348
+ get(token: string, id: string): string | null;
349
+ /**
350
+ * Push a known-fresh title into the cache (after a rename or a create), so
351
+ * surfaces that resolve through this service never show a stale name.
352
+ */
353
+ prime(token: string, id: string, title: string): void;
354
+ /** Display fallback when no label and no fetched title exist. */
355
+ fallback(token: string): string;
356
+ /**
357
+ * Resolve titles for `ids` of one token. Returns ONLY the resolved entries
358
+ * (cache + fresh reads); missing rows (deleted, no access) stay absent.
359
+ */
360
+ fetch(token: string, ids: string[]): Promise<Map<string, string>>;
361
+ }
362
+ declare function createTitlesService(deps: CoreDeps, candidates: CandidatesServiceApi): TitlesServiceApi;
363
+
364
+ type EntityRowResult = {
365
+ ok: true;
366
+ data: {
367
+ id: string;
368
+ title: string;
369
+ };
370
+ } | {
371
+ ok: false;
372
+ error: string;
373
+ };
374
+ interface CreateEntityRowArgs {
375
+ /** The new row's human name — written to the registry `titleColumn`. */
376
+ title: string;
377
+ /** Org to stamp (skipped when the token has no org column). */
378
+ orgId?: string | null;
379
+ /** Required NOT NULL columns the registry conventions can't know about. */
380
+ extraColumns?: Record<string, unknown>;
381
+ }
382
+ interface EntityRowsServiceApi {
383
+ createEntityRow(token: EntityTypeToken, args: CreateEntityRowArgs): Promise<EntityRowResult>;
384
+ renameEntityRow(token: EntityTypeToken, id: string, title: string): Promise<{
385
+ ok: true;
386
+ } | {
387
+ ok: false;
388
+ error: string;
389
+ }>;
390
+ }
391
+ declare function createEntityRowsService(deps: CoreDeps, titles: TitlesServiceApi): EntityRowsServiceApi;
392
+
393
+ /** A typed reference to a single row — `{ type, id }`. */
394
+ interface EntityRef {
395
+ type: EntityTypeToken;
396
+ id: string;
397
+ }
398
+ /** A container reference — the deliberate set an edge may point at. */
399
+ interface ContainerRef {
400
+ type: AssociationTargetType;
401
+ id: string;
402
+ }
403
+ /** Per-edge attributes shared by the link helpers. */
404
+ interface EdgeAttrs {
405
+ orgId?: string;
406
+ label?: string;
407
+ metadata?: unknown;
408
+ role?: string;
409
+ position?: number;
410
+ }
411
+ /** One fully-specified edge for {@link AssociationHelpersApi.linkEdges}. */
412
+ interface EdgeSpec extends EdgeAttrs {
413
+ source: EntityRef;
414
+ target: ContainerRef;
415
+ }
416
+ interface AssociationHelpersApi {
417
+ /**
418
+ * Add an arbitrary batch of edges in one call (idempotent per edge).
419
+ * Best-effort: every edge is attempted; the result is `ok` with the
420
+ * created ids only when ALL succeed, otherwise `err` whose `detail` lists
421
+ * every failure (the edges that DID succeed remain applied — re-running is
422
+ * safe via ON CONFLICT).
423
+ */
424
+ linkEdges(edges: EdgeSpec[]): Promise<AssociationsRpcResult<{
425
+ ids: string[];
426
+ }>>;
427
+ /** Attach ONE source to MANY containers in a single shot. */
428
+ linkOneToMany(args: {
429
+ source: EntityRef;
430
+ targets: ContainerRef[];
431
+ attrs?: EdgeAttrs;
432
+ }): Promise<AssociationsRpcResult<{
433
+ ids: string[];
434
+ }>>;
435
+ /** Attach MANY sources to ONE container in a single shot. */
436
+ linkManyToOne(args: {
437
+ sources: EntityRef[];
438
+ target: ContainerRef;
439
+ attrs?: EdgeAttrs;
440
+ }): Promise<AssociationsRpcResult<{
441
+ ids: string[];
442
+ }>>;
443
+ /**
444
+ * REPLACE the full set of a source's edges of one container type (adds
445
+ * missing, removes extras) in one DB transaction — the set-semantics
446
+ * one-shot. Thin, named pass-through to `assoc_set_targets`.
447
+ */
448
+ replaceTargets(args: {
449
+ source: EntityRef;
450
+ targetType: AssociationTargetType;
451
+ targetIds: string[];
452
+ orgId?: string;
453
+ role?: string;
454
+ }): Promise<AssociationsRpcResult<null>>;
455
+ /**
456
+ * Wire edges onto a JUST-CREATED entity. The caller inserts its own row
457
+ * (entity tables are feature-owned), then passes the new id here to attach
458
+ * it to any number of containers (orphan-on-partial-fail is harmless and
459
+ * re-runnable). `created` is the new entity; it becomes the edges' SOURCE.
460
+ */
461
+ linkCreated(args: {
462
+ created: EntityRef;
463
+ targets: ContainerRef[];
464
+ attrs?: EdgeAttrs;
465
+ }): Promise<AssociationsRpcResult<{
466
+ ids: string[];
467
+ }>>;
468
+ /** Remove an arbitrary batch of edges (best-effort; no-op per missing edge). */
469
+ unlinkEdges(edges: {
470
+ source: EntityRef;
471
+ target: ContainerRef;
472
+ role?: string;
473
+ }[]): Promise<AssociationsRpcResult<null>>;
474
+ }
475
+ declare function createAssociationHelpers(service: AssociationsServiceApi): AssociationHelpersApi;
476
+
477
+ /** Cache key for one association endpoint. */
478
+ declare function associationsKey(type: string, id: string): string;
479
+ interface AssociationWriteResult {
480
+ ok: boolean;
481
+ /** Set on a successful single-edge `add`. */
482
+ id?: string;
483
+ error?: string;
484
+ }
485
+ interface CategoryMutationResult {
486
+ ok: boolean;
487
+ /** Set on a successful mutation. */
488
+ id?: string;
489
+ error?: string;
490
+ }
491
+ interface AssociationsStore {
492
+ /** Stable, sync read of one endpoint's entry (idle default when uncached). */
493
+ getEdges(type: string, id: string): AssociationsEntry;
494
+ /**
495
+ * Lazy load of every edge touching `${type}:${id}` (both directions).
496
+ * Deduped per key; `status === "ready"` short-circuits unless `force`.
497
+ */
498
+ load(type: string, id: string, opts?: {
499
+ force?: boolean;
500
+ }): Promise<void>;
501
+ /** External-store contract: notified whenever the key's entry changes. */
502
+ subscribe(key: string, cb: () => void): () => void;
503
+ /** Attach source → target; on success reloads BOTH endpoints. */
504
+ add(args: {
505
+ sourceType: string;
506
+ sourceId: string;
507
+ targetType: AssociationTargetType;
508
+ targetId: string;
509
+ orgId?: string;
510
+ label?: string;
511
+ role?: string;
512
+ metadata?: unknown;
513
+ replaceMetadata?: boolean;
514
+ }): Promise<AssociationWriteResult>;
515
+ /** Detach source → target; on success reloads BOTH endpoints. */
516
+ remove(args: {
517
+ sourceType: string;
518
+ sourceId: string;
519
+ targetType: string;
520
+ targetId: string;
521
+ role?: string;
522
+ }): Promise<AssociationWriteResult>;
523
+ /** Replace the source's `targetType` edge set; reloads the SOURCE. */
524
+ setTargets(args: {
525
+ sourceType: string;
526
+ sourceId: string;
527
+ targetType: AssociationTargetType;
528
+ targetIds: string[];
529
+ orgId?: string;
530
+ role?: string;
531
+ }): Promise<AssociationWriteResult>;
532
+ /** Reset one endpoint to idle and notify its subscribers. */
533
+ invalidate(type: string, id: string): void;
534
+ /** Reset every association endpoint AND every category facet. */
535
+ invalidateAll(): void;
536
+ getCategories(dimension: CategoryDimension): CategoriesEntry;
537
+ loadCategories(dimension: CategoryDimension, opts?: {
538
+ force?: boolean;
539
+ }): Promise<void>;
540
+ subscribeCategories(dimension: CategoryDimension, cb: () => void): () => void;
541
+ /** Create + echo-insert + authoritative force reload. Returns the new id. */
542
+ createCategory(args: {
543
+ dimension: CategoryDimension;
544
+ name: string;
545
+ orgId: string;
546
+ parentId?: string | null;
547
+ color?: string | null;
548
+ icon?: string | null;
549
+ slug?: string | null;
550
+ }): Promise<CategoryMutationResult>;
551
+ updateCategory(args: {
552
+ dimension: CategoryDimension;
553
+ id: string;
554
+ name: string;
555
+ slug: string | null;
556
+ color: string | null;
557
+ icon: string | null;
558
+ position: number | null;
559
+ }): Promise<CategoryMutationResult>;
560
+ reparentCategory(args: {
561
+ dimension: CategoryDimension;
562
+ id: string;
563
+ parentId: string | null;
564
+ }): Promise<CategoryMutationResult>;
565
+ deleteCategory(args: {
566
+ dimension: CategoryDimension;
567
+ id: string;
568
+ }): Promise<CategoryMutationResult>;
569
+ /** Batched title resolution + the session cache + prime. */
570
+ titles: TitlesServiceApi;
571
+ /** Per-user favorite/pinned/hidden state (`ues_*`). */
572
+ favorites: FavoritesServiceApi;
573
+ /** Candidate reads for the pickers. */
574
+ candidates: CandidatesServiceApi;
575
+ /** Generic registry-convention row create/rename. */
576
+ entityRows: EntityRowsServiceApi;
577
+ /** Composite link helpers over the chokepoint. */
578
+ helpers: AssociationHelpersApi;
579
+ /** The registry merge engine (generated metadata + host overlay). */
580
+ registry: EntityRegistry;
581
+ /**
582
+ * The raw chokepoint services — every RPC family, results-enveloped.
583
+ * `associations` writes here do NOT touch the cache; use the store-level
584
+ * `add`/`remove`/`setTargets` when a cached surface must stay fresh.
585
+ */
586
+ services: {
587
+ associations: AssociationsServiceApi;
588
+ categories: CategoriesServiceApi;
589
+ };
590
+ /** Merge more host overlay entries in (icons/routes/candidate loaders). */
591
+ registerEntityOverlay: EntityRegistry["registerEntityOverlay"];
592
+ }
593
+ /**
594
+ * Build the one store a host binds. REQUIRED ports (dataSource / identity /
595
+ * errorSink) throw at construction when absent — never degraded. Every
596
+ * optional port degrades per its documented contract.
597
+ */
598
+ declare function createAssociationsStore(config: AssociationsConfig): AssociationsStore;
599
+
600
+ /** The name the self-test probes; MUST NOT exist in any schema. */
601
+ declare const SELF_TEST_MISSING_RPC = "assoc_probe_self_test_missing_fn";
602
+ interface DemandedSchemaReport {
603
+ ok: boolean;
604
+ /** Demanded functions the database cannot answer (PGRST202). */
605
+ missing: string[];
606
+ /** Functions the probe could not reach (transport/unknown failure). */
607
+ unreachable: {
608
+ fn: string;
609
+ error: unknown;
610
+ }[];
611
+ /** Functions that answered (success or any non-PGRST202 refusal). */
612
+ answered: string[];
613
+ }
614
+ interface AssertDemandedSchemaOptions {
615
+ /**
616
+ * Also probe a fabricated function name and REQUIRE it to come back
617
+ * missing — proves the probe can fail (the falsifiability rule).
618
+ */
619
+ selfTest?: boolean;
620
+ /** Max in-flight probe calls (default 6). */
621
+ concurrency?: number;
622
+ /**
623
+ * Return the report instead of throwing on violation (for surfaces that
624
+ * render the report). The self-test failure ALWAYS throws.
625
+ */
626
+ throwOnViolation?: boolean;
627
+ }
628
+ /**
629
+ * Probe every demanded RPC against `dataSource`. Throws an `Error` naming
630
+ * every missing function when the database violates the demanded schema
631
+ * (unless `throwOnViolation: false`), and ALWAYS throws when the probe
632
+ * itself cannot be trusted (unreachable database, failed self-test).
633
+ */
634
+ declare function assertDemandedSchema(dataSource: AssociationsDataSource, options?: AssertDemandedSchemaOptions): Promise<DemandedSchemaReport>;
635
+
636
+ /** Source tokens that are structure, never container content. */
637
+ declare const NON_CONTENT_SOURCE_TYPES: ReadonlySet<string>;
638
+ /** True when edge metadata marks a container-membership edge. */
639
+ declare function isMembershipMetadata(metadata: unknown): boolean;
640
+ /**
641
+ * True when an edge into a container represents CONTENT the container holds.
642
+ * `sourceType` is the edge's raw `source_type` token; `metadata` the edge's
643
+ * jsonb (pass it when available so membership edges are excluded even if a
644
+ * structural token is ever missed).
645
+ */
646
+ declare function isContentSourceEdge(sourceType: string, metadata?: unknown): boolean;
647
+
648
+ export { type AddAssociationArgs, type AssertDemandedSchemaOptions, type AssociationGuards, type AssociationHelpersApi, type AssociationWriteResult, type AssociationsServiceApi, type AssociationsStore, type CandidateRecord, type CandidatesResult, type CandidatesServiceApi, type CategoriesServiceApi, type CategoryMutationResult, type ContainerRef, type ContentRole, type ConversationFileLink, type CoreDeps, type CreateEntityRowArgs, DEFAULT_ORG_COLUMN, DEFAULT_OWNER_COLUMN, type DemandedSchemaReport, type EdgeAttrs, type EdgeSpec, type EntityInfo, type EntityRef, type EntityRegistry, type EntityRowResult, type EntityRowsServiceApi, type FavoritesServiceApi, type ListCandidatesArgs, NON_CONTENT_SOURCE_TYPES, type PgErrorMapper, type RpcResultHelpers, SELF_TEST_MISSING_RPC, type SearchAcrossTokensArgs, type TitlesServiceApi, type UniversalCandidate, assertDemandedSchema, associationsKey, createAssociationGuards, createAssociationHelpers, createAssociationsService, createAssociationsStore, createCandidatesService, createCategoriesService, createEntityRegistry, createEntityRowsService, createFavoritesService, createRpcResultHelpers, createTitlesService, entityTitleCacheKey, err, firstError, isContentRole, isContentSourceEdge, isMembershipMetadata, isTransportFailure, isUuid, ok, resolveEntityToken };