@ai-matrx/associations 0.3.0 → 0.5.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 +116 -0
- package/README.md +3 -3
- package/dist/core/index.cjs +315 -3
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +85 -3
- package/dist/core/index.d.ts +85 -3
- package/dist/core/index.js +315 -3
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +11 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +156 -3
- package/dist/index.d.ts +156 -3
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +1270 -39
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +336 -5
- package/dist/react/index.d.ts +336 -5
- package/dist/react/index.js +1273 -5
- package/dist/react/index.js.map +1 -1
- package/package.json +3 -3
package/dist/core/index.js
CHANGED
|
@@ -423,6 +423,7 @@ var ENTITY_TYPE_METADATA = {
|
|
|
423
423
|
"platform_actor_session": { token: "platform_actor_session", schema: "platform", table: "actor_session", label: "Actor session", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: false, titleColumn: null, contentRole: null, referenceCategory: null },
|
|
424
424
|
"platform_actor_token": { token: "platform_actor_token", schema: "platform", table: "actor_token", label: "Actor token", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: false, titleColumn: null, contentRole: null, referenceCategory: null },
|
|
425
425
|
"platform_actor_token_event": { token: "platform_actor_token_event", schema: "platform", table: "actor_token_event", label: "Actor token event", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: false, titleColumn: null, contentRole: null, referenceCategory: null },
|
|
426
|
+
"platform_continued_access": { token: "platform_continued_access", schema: "platform", table: "continued_access", label: "Continued Access", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: false, titleColumn: null, contentRole: null, referenceCategory: null },
|
|
426
427
|
"platform_outcome_event": { token: "platform_outcome_event", schema: "platform", table: "outcome_event", label: "Outcome Event", baseTier: 1, isComponent: false, isModule: false, isListed: true, scopeable: true, category: null, referencePickable: false, titleColumn: null, contentRole: null, referenceCategory: null },
|
|
427
428
|
"platform_outsider_consumer": { token: "platform_outsider_consumer", schema: "platform", table: "outsider_consumer", label: "Outsider consumer", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: false, titleColumn: null, contentRole: null, referenceCategory: null },
|
|
428
429
|
"platform_saved_view": { token: "platform_saved_view", schema: "platform", table: "saved_view", label: "Saved view", baseTier: 1, isComponent: false, isModule: false, isListed: true, scopeable: true, category: null, referencePickable: false, titleColumn: null, contentRole: null, referenceCategory: null },
|
|
@@ -451,6 +452,7 @@ var ENTITY_TYPE_METADATA = {
|
|
|
451
452
|
"research_tag": { token: "research_tag", schema: "research", table: "rs_tag", label: "Research Tag", baseTier: 1, isComponent: true, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: false, titleColumn: "name", contentRole: null, referenceCategory: null },
|
|
452
453
|
"research_template": { token: "research_template", schema: "research", table: "rs_template", label: "Research Template", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: true, titleColumn: "name", contentRole: null, referenceCategory: null },
|
|
453
454
|
"research_topic": { token: "research_topic", schema: "research", table: "rs_topic", label: "Research Topic", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: true, titleColumn: "name", contentRole: null, referenceCategory: null },
|
|
455
|
+
"route_manifest_entry": { token: "route_manifest_entry", schema: "platform", table: "route_manifest", label: "Route Manifest Entry", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: false, titleColumn: null, contentRole: null, referenceCategory: null },
|
|
454
456
|
"rulebook": { token: "rulebook", schema: "platform", table: "rulebook", label: "Rulebook", baseTier: 1, isComponent: false, isModule: false, isListed: true, scopeable: true, category: "Knowledge", referencePickable: true, titleColumn: "name", contentRole: "source", referenceCategory: null },
|
|
455
457
|
"sandbox_instance": { token: "sandbox_instance", schema: "public", table: "sandbox_instances", label: "Sandbox Instance", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: false, titleColumn: null, contentRole: null, referenceCategory: null },
|
|
456
458
|
"sch_agent_task": { token: "sch_agent_task", schema: "scheduler", table: "sch_agent_task", label: "Agent Task Config", baseTier: 1, isComponent: true, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: false, titleColumn: null, contentRole: null, referenceCategory: null },
|
|
@@ -529,6 +531,7 @@ var ENTITY_TYPE_METADATA = {
|
|
|
529
531
|
"seo_topic": { token: "seo_topic", schema: "seo", table: "topic", label: "SEO Topic", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: "seo", referencePickable: true, titleColumn: "name", contentRole: null, referenceCategory: null },
|
|
530
532
|
"seo_web_analytics_daily": { token: "seo_web_analytics_daily", schema: "seo", table: "web_analytics_daily", label: "Web Analytics Daily", baseTier: 1, isComponent: true, isModule: false, isListed: false, scopeable: true, category: "marketing", referencePickable: false, titleColumn: null, contentRole: null, referenceCategory: null },
|
|
531
533
|
"shared_canvas_item": { token: "shared_canvas_item", schema: "canvas", table: "shared_canvas_items", label: "Shared Canvas Item", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: true, titleColumn: "title", contentRole: null, referenceCategory: null },
|
|
534
|
+
"short_link": { token: "short_link", schema: "platform", table: "short_links", label: "Short Link", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: false, titleColumn: null, contentRole: null, referenceCategory: null },
|
|
532
535
|
"skill": { token: "skill", schema: "skill", table: "definition", label: "Skill", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: "Skills", referencePickable: true, titleColumn: "label", contentRole: "utility", referenceCategory: null },
|
|
533
536
|
"skill_render_definition": { token: "skill_render_definition", schema: "skill", table: "render_definition", label: "Skill Render Definition", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: "Skills", referencePickable: true, titleColumn: "label", contentRole: null, referenceCategory: null },
|
|
534
537
|
"sms_consent": { token: "sms_consent", schema: "communication", table: "sms_consent", label: "SMS Consent", baseTier: 1, isComponent: false, isModule: false, isListed: false, scopeable: true, category: null, referencePickable: false, titleColumn: null, contentRole: null, referenceCategory: null },
|
|
@@ -1076,6 +1079,7 @@ var ENTITY_TYPE_TOKENS = [
|
|
|
1076
1079
|
"platform_actor_session",
|
|
1077
1080
|
"platform_actor_token",
|
|
1078
1081
|
"platform_actor_token_event",
|
|
1082
|
+
"platform_continued_access",
|
|
1079
1083
|
"platform_outcome_event",
|
|
1080
1084
|
"platform_outsider_consumer",
|
|
1081
1085
|
"platform_saved_view",
|
|
@@ -1104,6 +1108,7 @@ var ENTITY_TYPE_TOKENS = [
|
|
|
1104
1108
|
"research_tag",
|
|
1105
1109
|
"research_template",
|
|
1106
1110
|
"research_topic",
|
|
1111
|
+
"route_manifest_entry",
|
|
1107
1112
|
"rulebook",
|
|
1108
1113
|
"sandbox_instance",
|
|
1109
1114
|
"sch_agent_task",
|
|
@@ -1182,6 +1187,7 @@ var ENTITY_TYPE_TOKENS = [
|
|
|
1182
1187
|
"seo_topic",
|
|
1183
1188
|
"seo_web_analytics_daily",
|
|
1184
1189
|
"shared_canvas_item",
|
|
1190
|
+
"short_link",
|
|
1185
1191
|
"skill",
|
|
1186
1192
|
"skill_render_definition",
|
|
1187
1193
|
"sms_consent",
|
|
@@ -2655,6 +2661,122 @@ function createAssociationHelpers(service) {
|
|
|
2655
2661
|
};
|
|
2656
2662
|
}
|
|
2657
2663
|
|
|
2664
|
+
// src/core/commentsService.ts
|
|
2665
|
+
function toComment(row) {
|
|
2666
|
+
return {
|
|
2667
|
+
id: row.id,
|
|
2668
|
+
orgId: row.organization_id ?? null,
|
|
2669
|
+
entityType: row.entity_type,
|
|
2670
|
+
entityId: row.entity_id,
|
|
2671
|
+
parentId: row.parent_id ?? null,
|
|
2672
|
+
body: row.body,
|
|
2673
|
+
createdAt: row.created_at,
|
|
2674
|
+
updatedAt: row.updated_at,
|
|
2675
|
+
createdBy: row.created_by ?? null,
|
|
2676
|
+
author: {
|
|
2677
|
+
email: row.author_email ?? null,
|
|
2678
|
+
displayName: row.author_display_name ?? null,
|
|
2679
|
+
avatarUrl: row.author_avatar_url ?? null
|
|
2680
|
+
}
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
function createCommentsService(deps) {
|
|
2684
|
+
const { dataSource, identity, guards } = deps;
|
|
2685
|
+
const { ok: ok2, err: err2, mapPgError, mapPgErrorPair } = deps.rpc;
|
|
2686
|
+
return {
|
|
2687
|
+
/**
|
|
2688
|
+
* All comments on `${entityType}:${entityId}`, org-filtered by RLS
|
|
2689
|
+
* inside the RPC, ordered oldest→newest. Build the thread from each
|
|
2690
|
+
* row's `parentId` (top-level comments have `parentId === null`).
|
|
2691
|
+
*/
|
|
2692
|
+
async listForEntity(entityType, entityId) {
|
|
2693
|
+
try {
|
|
2694
|
+
identity.requireUserId();
|
|
2695
|
+
const token = guards.normalizeEntityToken(entityType);
|
|
2696
|
+
const invalid = firstError(
|
|
2697
|
+
guards.checkToken("entityType", token),
|
|
2698
|
+
guards.checkUuid("entityId", entityId)
|
|
2699
|
+
);
|
|
2700
|
+
if (invalid) return { ok: false, error: invalid };
|
|
2701
|
+
const { data, error } = await dataSource.rpc("cmt_list", {
|
|
2702
|
+
p_entity_type: token,
|
|
2703
|
+
p_entity_id: entityId
|
|
2704
|
+
});
|
|
2705
|
+
if (error) return err2(...mapPgErrorPair(error));
|
|
2706
|
+
const rows = Array.isArray(data) ? data : [];
|
|
2707
|
+
return ok2({ comments: rows.map(toComment) });
|
|
2708
|
+
} catch (e) {
|
|
2709
|
+
return { ok: false, error: mapPgError(e) };
|
|
2710
|
+
}
|
|
2711
|
+
},
|
|
2712
|
+
/**
|
|
2713
|
+
* Post `body` on `${entityType}:${entityId}`; pass `parentId` to reply.
|
|
2714
|
+
* Returns the new comment id.
|
|
2715
|
+
*/
|
|
2716
|
+
async add(args) {
|
|
2717
|
+
try {
|
|
2718
|
+
identity.requireUserId();
|
|
2719
|
+
const token = guards.normalizeEntityToken(args.entityType);
|
|
2720
|
+
const invalid = firstError(
|
|
2721
|
+
guards.checkToken("entityType", token),
|
|
2722
|
+
guards.checkUuid("entityId", args.entityId),
|
|
2723
|
+
args.parentId != null ? guards.checkUuid("parentId", args.parentId) : null
|
|
2724
|
+
);
|
|
2725
|
+
if (invalid) return { ok: false, error: invalid };
|
|
2726
|
+
if (!args.body || args.body.trim().length === 0) {
|
|
2727
|
+
return err2("invalid_argument", "comment body must be non-empty");
|
|
2728
|
+
}
|
|
2729
|
+
const { data, error } = await dataSource.rpc("cmt_add", {
|
|
2730
|
+
p_entity_type: token,
|
|
2731
|
+
p_entity_id: args.entityId,
|
|
2732
|
+
p_body: args.body,
|
|
2733
|
+
p_parent_id: args.parentId ?? void 0,
|
|
2734
|
+
p_org_id: args.orgId ?? void 0
|
|
2735
|
+
});
|
|
2736
|
+
if (error) return err2(...mapPgErrorPair(error));
|
|
2737
|
+
if (!data || typeof data !== "string") {
|
|
2738
|
+
return err2("internal", "cmt_add returned no comment id");
|
|
2739
|
+
}
|
|
2740
|
+
return ok2({ id: data });
|
|
2741
|
+
} catch (e) {
|
|
2742
|
+
return { ok: false, error: mapPgError(e) };
|
|
2743
|
+
}
|
|
2744
|
+
},
|
|
2745
|
+
/** Replace a comment's body. Author-only; the RPC rejects everyone else. */
|
|
2746
|
+
async edit(id, body) {
|
|
2747
|
+
try {
|
|
2748
|
+
identity.requireUserId();
|
|
2749
|
+
const invalid = guards.checkUuid("commentId", id);
|
|
2750
|
+
if (invalid) return { ok: false, error: invalid };
|
|
2751
|
+
if (!body || body.trim().length === 0) {
|
|
2752
|
+
return err2("invalid_argument", "comment body must be non-empty");
|
|
2753
|
+
}
|
|
2754
|
+
const { error } = await dataSource.rpc("cmt_edit", {
|
|
2755
|
+
p_id: id,
|
|
2756
|
+
p_body: body
|
|
2757
|
+
});
|
|
2758
|
+
if (error) return err2(...mapPgErrorPair(error));
|
|
2759
|
+
return ok2(null);
|
|
2760
|
+
} catch (e) {
|
|
2761
|
+
return { ok: false, error: mapPgError(e) };
|
|
2762
|
+
}
|
|
2763
|
+
},
|
|
2764
|
+
/** Soft-delete a comment. Author or org member; enforced server-side. */
|
|
2765
|
+
async remove(id) {
|
|
2766
|
+
try {
|
|
2767
|
+
identity.requireUserId();
|
|
2768
|
+
const invalid = guards.checkUuid("commentId", id);
|
|
2769
|
+
if (invalid) return { ok: false, error: invalid };
|
|
2770
|
+
const { error } = await dataSource.rpc("cmt_delete", { p_id: id });
|
|
2771
|
+
if (error) return err2(...mapPgErrorPair(error));
|
|
2772
|
+
return ok2(null);
|
|
2773
|
+
} catch (e) {
|
|
2774
|
+
return { ok: false, error: mapPgError(e) };
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
};
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2658
2780
|
// src/core/store.ts
|
|
2659
2781
|
function associationsKey(type, id) {
|
|
2660
2782
|
return `${type}:${id}`;
|
|
@@ -2671,6 +2793,12 @@ var IDLE_CATEGORIES = Object.freeze({
|
|
|
2671
2793
|
fetchedAt: null,
|
|
2672
2794
|
error: null
|
|
2673
2795
|
});
|
|
2796
|
+
var IDLE_COMMENTS = Object.freeze({
|
|
2797
|
+
status: "idle",
|
|
2798
|
+
comments: Object.freeze([]),
|
|
2799
|
+
fetchedAt: null,
|
|
2800
|
+
error: null
|
|
2801
|
+
});
|
|
2674
2802
|
function createAssociationsStore(config) {
|
|
2675
2803
|
if (!config || typeof config.dataSource?.rpc !== "function") {
|
|
2676
2804
|
throw new Error(
|
|
@@ -2704,12 +2832,16 @@ function createAssociationsStore(config) {
|
|
|
2704
2832
|
const titles = createTitlesService(deps, candidates);
|
|
2705
2833
|
const entityRows = createEntityRowsService(deps, titles);
|
|
2706
2834
|
const helpers = createAssociationHelpers(associations);
|
|
2835
|
+
const comments = createCommentsService(deps);
|
|
2707
2836
|
const assocByKey = /* @__PURE__ */ new Map();
|
|
2708
2837
|
const assocSubs = /* @__PURE__ */ new Map();
|
|
2709
2838
|
const assocInFlight = /* @__PURE__ */ new Map();
|
|
2710
2839
|
const catByDim = /* @__PURE__ */ new Map();
|
|
2711
2840
|
const catSubs = /* @__PURE__ */ new Map();
|
|
2712
2841
|
const catInFlight = /* @__PURE__ */ new Map();
|
|
2842
|
+
const cmtByKey = /* @__PURE__ */ new Map();
|
|
2843
|
+
const cmtSubs = /* @__PURE__ */ new Map();
|
|
2844
|
+
const cmtInFlight = /* @__PURE__ */ new Map();
|
|
2713
2845
|
function notifyAssoc(key) {
|
|
2714
2846
|
const subs = assocSubs.get(key);
|
|
2715
2847
|
if (subs) for (const cb of [...subs]) cb();
|
|
@@ -2726,6 +2858,14 @@ function createAssociationsStore(config) {
|
|
|
2726
2858
|
catByDim.set(dimension, entry);
|
|
2727
2859
|
notifyCat(dimension);
|
|
2728
2860
|
}
|
|
2861
|
+
function notifyCmt(key) {
|
|
2862
|
+
const subs = cmtSubs.get(key);
|
|
2863
|
+
if (subs) for (const cb of [...subs]) cb();
|
|
2864
|
+
}
|
|
2865
|
+
function setCmt(key, entry) {
|
|
2866
|
+
cmtByKey.set(key, entry);
|
|
2867
|
+
notifyCmt(key);
|
|
2868
|
+
}
|
|
2729
2869
|
async function load(type, id, opts = {}) {
|
|
2730
2870
|
const force = opts.force ?? false;
|
|
2731
2871
|
if (!type || !id) return;
|
|
@@ -2815,6 +2955,49 @@ function createAssociationsStore(config) {
|
|
|
2815
2955
|
catInFlight.set(dimension, promise);
|
|
2816
2956
|
return promise;
|
|
2817
2957
|
}
|
|
2958
|
+
async function loadComments(type, id, opts = {}) {
|
|
2959
|
+
const force = opts.force ?? false;
|
|
2960
|
+
if (!type || !id) return;
|
|
2961
|
+
const key = associationsKey(type, id);
|
|
2962
|
+
const entry = cmtByKey.get(key);
|
|
2963
|
+
if (!force && entry?.status === "ready") return;
|
|
2964
|
+
const pending = cmtInFlight.get(key);
|
|
2965
|
+
if (!force && entry?.status === "loading" && pending) return pending;
|
|
2966
|
+
const prev = cmtByKey.get(key);
|
|
2967
|
+
setCmt(key, {
|
|
2968
|
+
status: "loading",
|
|
2969
|
+
comments: prev?.comments ?? [],
|
|
2970
|
+
fetchedAt: prev?.fetchedAt ?? null,
|
|
2971
|
+
// Retain the visible failure while a retry is pending (same contract
|
|
2972
|
+
// as the association cache).
|
|
2973
|
+
error: prev?.error ?? null
|
|
2974
|
+
});
|
|
2975
|
+
const promise = (async () => {
|
|
2976
|
+
try {
|
|
2977
|
+
const res = await comments.listForEntity(type, id);
|
|
2978
|
+
if (isAssociationsRpcErr(res)) {
|
|
2979
|
+
const before = cmtByKey.get(key);
|
|
2980
|
+
setCmt(key, {
|
|
2981
|
+
status: "error",
|
|
2982
|
+
comments: before?.comments ?? [],
|
|
2983
|
+
fetchedAt: before?.fetchedAt ?? null,
|
|
2984
|
+
error: res.error.message
|
|
2985
|
+
});
|
|
2986
|
+
} else {
|
|
2987
|
+
setCmt(key, {
|
|
2988
|
+
status: "ready",
|
|
2989
|
+
comments: res.data.comments,
|
|
2990
|
+
fetchedAt: Date.now(),
|
|
2991
|
+
error: null
|
|
2992
|
+
});
|
|
2993
|
+
}
|
|
2994
|
+
} finally {
|
|
2995
|
+
cmtInFlight.delete(key);
|
|
2996
|
+
}
|
|
2997
|
+
})();
|
|
2998
|
+
cmtInFlight.set(key, promise);
|
|
2999
|
+
return promise;
|
|
3000
|
+
}
|
|
2818
3001
|
return {
|
|
2819
3002
|
// ── associations ─────────────────────────────────────────────────────
|
|
2820
3003
|
getEdges(type, id) {
|
|
@@ -2885,6 +3068,9 @@ function createAssociationsStore(config) {
|
|
|
2885
3068
|
const dims = [...catByDim.keys()];
|
|
2886
3069
|
catByDim.clear();
|
|
2887
3070
|
for (const dim of dims) notifyCat(dim);
|
|
3071
|
+
const cmtKeys = [...cmtByKey.keys()];
|
|
3072
|
+
cmtByKey.clear();
|
|
3073
|
+
for (const key of cmtKeys) notifyCmt(key);
|
|
2888
3074
|
},
|
|
2889
3075
|
// ── categories ───────────────────────────────────────────────────────
|
|
2890
3076
|
getCategories(dimension) {
|
|
@@ -2959,6 +3145,52 @@ function createAssociationsStore(config) {
|
|
|
2959
3145
|
await loadCategories(args.dimension, { force: true });
|
|
2960
3146
|
return { ok: true, id: res.data.id };
|
|
2961
3147
|
},
|
|
3148
|
+
// ── comments (W6) ────────────────────────────────────────────────────
|
|
3149
|
+
getComments(type, id) {
|
|
3150
|
+
if (!type || !id) return IDLE_COMMENTS;
|
|
3151
|
+
return cmtByKey.get(associationsKey(type, id)) ?? IDLE_COMMENTS;
|
|
3152
|
+
},
|
|
3153
|
+
loadComments,
|
|
3154
|
+
subscribeComments(key, cb) {
|
|
3155
|
+
let subs = cmtSubs.get(key);
|
|
3156
|
+
if (!subs) {
|
|
3157
|
+
subs = /* @__PURE__ */ new Set();
|
|
3158
|
+
cmtSubs.set(key, subs);
|
|
3159
|
+
}
|
|
3160
|
+
subs.add(cb);
|
|
3161
|
+
return () => {
|
|
3162
|
+
subs.delete(cb);
|
|
3163
|
+
if (subs.size === 0) cmtSubs.delete(key);
|
|
3164
|
+
};
|
|
3165
|
+
},
|
|
3166
|
+
async addComment(args) {
|
|
3167
|
+
const res = await comments.add(args);
|
|
3168
|
+
if (isAssociationsRpcErr(res)) {
|
|
3169
|
+
return { ok: false, error: res.error.message };
|
|
3170
|
+
}
|
|
3171
|
+
await loadComments(args.entityType, args.entityId, { force: true });
|
|
3172
|
+
return { ok: true, id: res.data.id };
|
|
3173
|
+
},
|
|
3174
|
+
async editComment(args) {
|
|
3175
|
+
const res = await comments.edit(args.id, args.body);
|
|
3176
|
+
if (isAssociationsRpcErr(res)) {
|
|
3177
|
+
return { ok: false, error: res.error.message };
|
|
3178
|
+
}
|
|
3179
|
+
await loadComments(args.entityType, args.entityId, { force: true });
|
|
3180
|
+
return { ok: true, id: args.id };
|
|
3181
|
+
},
|
|
3182
|
+
async deleteComment(args) {
|
|
3183
|
+
const res = await comments.remove(args.id);
|
|
3184
|
+
if (isAssociationsRpcErr(res)) {
|
|
3185
|
+
return { ok: false, error: res.error.message };
|
|
3186
|
+
}
|
|
3187
|
+
await loadComments(args.entityType, args.entityId, { force: true });
|
|
3188
|
+
return { ok: true, id: args.id };
|
|
3189
|
+
},
|
|
3190
|
+
invalidateComments(type, id) {
|
|
3191
|
+
const key = associationsKey(type, id);
|
|
3192
|
+
if (cmtByKey.delete(key)) notifyCmt(key);
|
|
3193
|
+
},
|
|
2962
3194
|
// ── seams ────────────────────────────────────────────────────────────
|
|
2963
3195
|
titles,
|
|
2964
3196
|
favorites,
|
|
@@ -2966,7 +3198,7 @@ function createAssociationsStore(config) {
|
|
|
2966
3198
|
entityRows,
|
|
2967
3199
|
helpers,
|
|
2968
3200
|
registry,
|
|
2969
|
-
services: { associations, categories },
|
|
3201
|
+
services: { associations, categories, comments },
|
|
2970
3202
|
registerEntityOverlay: registry.registerEntityOverlay,
|
|
2971
3203
|
errorSink,
|
|
2972
3204
|
identity: config.identity
|
|
@@ -2997,7 +3229,11 @@ var DEMANDED_RPC_NAMES = [
|
|
|
2997
3229
|
"ues_list",
|
|
2998
3230
|
"ues_get_bulk",
|
|
2999
3231
|
"ues_touch",
|
|
3000
|
-
"reference_search_candidates"
|
|
3232
|
+
"reference_search_candidates",
|
|
3233
|
+
"cmt_list",
|
|
3234
|
+
"cmt_add",
|
|
3235
|
+
"cmt_edit",
|
|
3236
|
+
"cmt_delete"
|
|
3001
3237
|
];
|
|
3002
3238
|
|
|
3003
3239
|
// src/core/assertDemandedSchema.ts
|
|
@@ -3062,7 +3298,17 @@ var PROBE_ARGS = {
|
|
|
3062
3298
|
ues_get_bulk: { p_entity_type: "__probe__", p_entity_ids: [BAD_UUID] },
|
|
3063
3299
|
ues_touch: { p_entity_type: "__probe__", p_entity_id: BAD_UUID },
|
|
3064
3300
|
// candidates
|
|
3065
|
-
reference_search_candidates: { p_token: "__probe__", p_limit: 1 }
|
|
3301
|
+
reference_search_candidates: { p_token: "__probe__", p_limit: 1 },
|
|
3302
|
+
// comments (W6) — every fn takes a uuid arg, so the unparseable sentinel
|
|
3303
|
+
// guarantees 22P02 before any SECURITY DEFINER body runs (no write occurs).
|
|
3304
|
+
cmt_list: { p_entity_type: "__probe__", p_entity_id: BAD_UUID },
|
|
3305
|
+
cmt_add: {
|
|
3306
|
+
p_entity_type: "__probe__",
|
|
3307
|
+
p_entity_id: BAD_UUID,
|
|
3308
|
+
p_body: "__probe__"
|
|
3309
|
+
},
|
|
3310
|
+
cmt_edit: { p_id: BAD_UUID, p_body: "__probe__" },
|
|
3311
|
+
cmt_delete: { p_id: BAD_UUID }
|
|
3066
3312
|
};
|
|
3067
3313
|
function isMissingFunctionError(error) {
|
|
3068
3314
|
if (!error || typeof error !== "object") return false;
|
|
@@ -3138,6 +3384,70 @@ function isContentSourceEdge(sourceType, metadata) {
|
|
|
3138
3384
|
if (isMembershipMetadata(metadata)) return false;
|
|
3139
3385
|
return true;
|
|
3140
3386
|
}
|
|
3387
|
+
|
|
3388
|
+
// src/core/categoryHierarchy.ts
|
|
3389
|
+
function buildCategoryHierarchy(categories) {
|
|
3390
|
+
const byId = new Map(categories.map((category) => [category.id, category]));
|
|
3391
|
+
const hasHierarchy = categories.some(
|
|
3392
|
+
(category) => category.parentId !== null && byId.has(category.parentId)
|
|
3393
|
+
);
|
|
3394
|
+
if (!hasHierarchy) {
|
|
3395
|
+
return {
|
|
3396
|
+
items: categories.map((category) => ({
|
|
3397
|
+
category,
|
|
3398
|
+
depth: 0,
|
|
3399
|
+
parent: null,
|
|
3400
|
+
displayName: category.name
|
|
3401
|
+
})),
|
|
3402
|
+
roots: categories.filter((category) => category.parentId === null),
|
|
3403
|
+
hasHierarchy: false
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
const roots = categories.filter(
|
|
3407
|
+
(category) => category.parentId === null || !byId.has(category.parentId)
|
|
3408
|
+
);
|
|
3409
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
3410
|
+
for (const category of categories) {
|
|
3411
|
+
if (!category.parentId || !byId.has(category.parentId)) continue;
|
|
3412
|
+
const children = childrenByParent.get(category.parentId) ?? [];
|
|
3413
|
+
children.push(category);
|
|
3414
|
+
childrenByParent.set(category.parentId, children);
|
|
3415
|
+
}
|
|
3416
|
+
const items = [];
|
|
3417
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3418
|
+
for (const root of roots) {
|
|
3419
|
+
items.push({
|
|
3420
|
+
category: root,
|
|
3421
|
+
depth: 0,
|
|
3422
|
+
parent: null,
|
|
3423
|
+
displayName: root.name
|
|
3424
|
+
});
|
|
3425
|
+
seen.add(root.id);
|
|
3426
|
+
for (const child of childrenByParent.get(root.id) ?? []) {
|
|
3427
|
+
items.push({
|
|
3428
|
+
category: child,
|
|
3429
|
+
depth: 1,
|
|
3430
|
+
parent: root,
|
|
3431
|
+
displayName: `${root.name} / ${child.name}`
|
|
3432
|
+
});
|
|
3433
|
+
seen.add(child.id);
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
for (const category of categories) {
|
|
3437
|
+
if (seen.has(category.id)) continue;
|
|
3438
|
+
items.push({
|
|
3439
|
+
category,
|
|
3440
|
+
depth: 0,
|
|
3441
|
+
parent: null,
|
|
3442
|
+
displayName: category.name
|
|
3443
|
+
});
|
|
3444
|
+
}
|
|
3445
|
+
return {
|
|
3446
|
+
items,
|
|
3447
|
+
roots: categories.filter((category) => category.parentId === null),
|
|
3448
|
+
hasHierarchy: true
|
|
3449
|
+
};
|
|
3450
|
+
}
|
|
3141
3451
|
export {
|
|
3142
3452
|
DEFAULT_ORG_COLUMN,
|
|
3143
3453
|
DEFAULT_OWNER_COLUMN,
|
|
@@ -3145,12 +3455,14 @@ export {
|
|
|
3145
3455
|
SELF_TEST_MISSING_RPC,
|
|
3146
3456
|
assertDemandedSchema,
|
|
3147
3457
|
associationsKey,
|
|
3458
|
+
buildCategoryHierarchy,
|
|
3148
3459
|
createAssociationGuards,
|
|
3149
3460
|
createAssociationHelpers,
|
|
3150
3461
|
createAssociationsService,
|
|
3151
3462
|
createAssociationsStore,
|
|
3152
3463
|
createCandidatesService,
|
|
3153
3464
|
createCategoriesService,
|
|
3465
|
+
createCommentsService,
|
|
3154
3466
|
createEntityRegistry,
|
|
3155
3467
|
createEntityRowsService,
|
|
3156
3468
|
createFavoritesService,
|