@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.
- package/CHANGELOG.md +122 -0
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/core/index.cjs +3191 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +648 -0
- package/dist/core/index.d.ts +648 -0
- package/dist/core/index.js +3168 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.cjs +1479 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +11165 -0
- package/dist/index.d.ts +11165 -0
- package/dist/index.js +1456 -0
- package/dist/index.js.map +1 -0
- package/package.json +98 -0
|
@@ -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 };
|