@burdenoff/be-sdk 2026.510.79
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/README.md +173 -0
- package/configs/eslint/index.js +492 -0
- package/configs/tsconfig/tsconfig.json +53 -0
- package/dist/EventQueueFactory-6NMSGECR.js +8 -0
- package/dist/EventQueueFactory-6NMSGECR.js.map +1 -0
- package/dist/auth/index.d.ts +66 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/cache/index.d.ts +2 -0
- package/dist/cache/index.js +6 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/captcha/index.d.ts +1 -0
- package/dist/captcha/index.js +4 -0
- package/dist/captcha/index.js.map +1 -0
- package/dist/chunk-2HANKZZM.js +245 -0
- package/dist/chunk-2HANKZZM.js.map +1 -0
- package/dist/chunk-2SD5L2F2.js +816 -0
- package/dist/chunk-2SD5L2F2.js.map +1 -0
- package/dist/chunk-3O2RQOPV.js +1068 -0
- package/dist/chunk-3O2RQOPV.js.map +1 -0
- package/dist/chunk-4B43DZIS.js +560 -0
- package/dist/chunk-4B43DZIS.js.map +1 -0
- package/dist/chunk-4XAEES25.js +2444 -0
- package/dist/chunk-4XAEES25.js.map +1 -0
- package/dist/chunk-4XGGWW36.js +1405 -0
- package/dist/chunk-4XGGWW36.js.map +1 -0
- package/dist/chunk-5LXDPGDH.js +245 -0
- package/dist/chunk-5LXDPGDH.js.map +1 -0
- package/dist/chunk-AI7L22ZJ.js +472 -0
- package/dist/chunk-AI7L22ZJ.js.map +1 -0
- package/dist/chunk-CBZNP6RF.js +286 -0
- package/dist/chunk-CBZNP6RF.js.map +1 -0
- package/dist/chunk-ELLX3K54.js +613 -0
- package/dist/chunk-ELLX3K54.js.map +1 -0
- package/dist/chunk-F5XEAGHQ.js +1875 -0
- package/dist/chunk-F5XEAGHQ.js.map +1 -0
- package/dist/chunk-FNZEQOJY.js +628 -0
- package/dist/chunk-FNZEQOJY.js.map +1 -0
- package/dist/chunk-GTEMQXCA.js +516 -0
- package/dist/chunk-GTEMQXCA.js.map +1 -0
- package/dist/chunk-HIWTWIGQ.js +226 -0
- package/dist/chunk-HIWTWIGQ.js.map +1 -0
- package/dist/chunk-IYE2NTRX.js +764 -0
- package/dist/chunk-IYE2NTRX.js.map +1 -0
- package/dist/chunk-J26YGR2U.js +1484 -0
- package/dist/chunk-J26YGR2U.js.map +1 -0
- package/dist/chunk-JG2FTZPI.js +509 -0
- package/dist/chunk-JG2FTZPI.js.map +1 -0
- package/dist/chunk-JVBTNSPG.js +1551 -0
- package/dist/chunk-JVBTNSPG.js.map +1 -0
- package/dist/chunk-L7ZMN2FJ.js +12 -0
- package/dist/chunk-L7ZMN2FJ.js.map +1 -0
- package/dist/chunk-M5IMU3US.js +473 -0
- package/dist/chunk-M5IMU3US.js.map +1 -0
- package/dist/chunk-MLW2HPS5.js +1267 -0
- package/dist/chunk-MLW2HPS5.js.map +1 -0
- package/dist/chunk-MNCGUYLJ.js +269 -0
- package/dist/chunk-MNCGUYLJ.js.map +1 -0
- package/dist/chunk-MOCUBSTW.js +373 -0
- package/dist/chunk-MOCUBSTW.js.map +1 -0
- package/dist/chunk-NGMEI42S.js +103 -0
- package/dist/chunk-NGMEI42S.js.map +1 -0
- package/dist/chunk-NUB5GQTU.js +270 -0
- package/dist/chunk-NUB5GQTU.js.map +1 -0
- package/dist/chunk-OVSYMNCI.js +145 -0
- package/dist/chunk-OVSYMNCI.js.map +1 -0
- package/dist/chunk-QLVKMWFA.js +94 -0
- package/dist/chunk-QLVKMWFA.js.map +1 -0
- package/dist/chunk-SAG6ZYWQ.js +89 -0
- package/dist/chunk-SAG6ZYWQ.js.map +1 -0
- package/dist/chunk-SAK2JCA7.js +198 -0
- package/dist/chunk-SAK2JCA7.js.map +1 -0
- package/dist/chunk-SBS4S2PJ.js +100 -0
- package/dist/chunk-SBS4S2PJ.js.map +1 -0
- package/dist/chunk-SNEOLWXT.js +148 -0
- package/dist/chunk-SNEOLWXT.js.map +1 -0
- package/dist/chunk-TKBD2ONF.js +564 -0
- package/dist/chunk-TKBD2ONF.js.map +1 -0
- package/dist/chunk-UBXFIIAW.js +127 -0
- package/dist/chunk-UBXFIIAW.js.map +1 -0
- package/dist/chunk-VI4VSNTI.js +57 -0
- package/dist/chunk-VI4VSNTI.js.map +1 -0
- package/dist/chunk-VWCN5PMD.js +268 -0
- package/dist/chunk-VWCN5PMD.js.map +1 -0
- package/dist/chunk-XC2YD6CF.js +913 -0
- package/dist/chunk-XC2YD6CF.js.map +1 -0
- package/dist/chunk-XE4OXN2W.js +10 -0
- package/dist/chunk-XE4OXN2W.js.map +1 -0
- package/dist/chunk-YMZEUTD2.js +110 -0
- package/dist/chunk-YMZEUTD2.js.map +1 -0
- package/dist/chunk-Z4IRLBWO.js +758 -0
- package/dist/chunk-Z4IRLBWO.js.map +1 -0
- package/dist/client/internal-client.d.ts +7 -0
- package/dist/client/internal-client.js +5 -0
- package/dist/client/internal-client.js.map +1 -0
- package/dist/client-CSjIrYKS.d.ts +77 -0
- package/dist/config-UKE2E3BW.js +4 -0
- package/dist/config-UKE2E3BW.js.map +1 -0
- package/dist/crypto/index.d.ts +1 -0
- package/dist/crypto/index.js +4 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/database/index.d.ts +117 -0
- package/dist/database/index.js +7 -0
- package/dist/database/index.js.map +1 -0
- package/dist/dataloader/index.d.ts +1 -0
- package/dist/dataloader/index.js +4 -0
- package/dist/dataloader/index.js.map +1 -0
- package/dist/esm-K6H7EIWK.js +1450 -0
- package/dist/esm-K6H7EIWK.js.map +1 -0
- package/dist/event-queue/index.d.ts +6 -0
- package/dist/event-queue/index.js +10 -0
- package/dist/event-queue/index.js.map +1 -0
- package/dist/export/index.d.ts +2 -0
- package/dist/export/index.js +9 -0
- package/dist/export/index.js.map +1 -0
- package/dist/feature-flags/index.d.ts +1 -0
- package/dist/feature-flags/index.js +4 -0
- package/dist/feature-flags/index.js.map +1 -0
- package/dist/gateway/index.d.ts +4 -0
- package/dist/gateway/index.js +8 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/gateway/security/index.d.ts +2 -0
- package/dist/gateway/security/index.js +5 -0
- package/dist/gateway/security/index.js.map +1 -0
- package/dist/graph/index.d.ts +9 -0
- package/dist/graph/index.js +13 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph-emitter-Bk0FqD53.d.ts +47 -0
- package/dist/graphql/index.d.ts +2 -0
- package/dist/graphql/index.js +7 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/services/index.d.ts +24 -0
- package/dist/graphql/services/index.js +5 -0
- package/dist/graphql/services/index.js.map +1 -0
- package/dist/graphql-BAVrUgBF.d.ts +22 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/index.js +5 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n-DA6IPnYy.d.ts +101 -0
- package/dist/index-4gKQKZ2B.d.ts +71 -0
- package/dist/index-B18rrFru.d.ts +42 -0
- package/dist/index-B70b5mYS.d.ts +33 -0
- package/dist/index-BMq3lFjj.d.ts +247 -0
- package/dist/index-BODgvRtL.d.ts +316 -0
- package/dist/index-BS2idbnr.d.ts +874 -0
- package/dist/index-BV3G48cH.d.ts +124 -0
- package/dist/index-CHbVw5M7.d.ts +73 -0
- package/dist/index-Cq_xNjcv.d.ts +602 -0
- package/dist/index-CrQgidpx.d.ts +77 -0
- package/dist/index-CyVEg86m.d.ts +309 -0
- package/dist/index-DOcBnmMe.d.ts +166 -0
- package/dist/index-DSOpj8DI.d.ts +17 -0
- package/dist/index-DW9q22GH.d.ts +75 -0
- package/dist/index-DlFDFyWD.d.ts +55 -0
- package/dist/index-DoK0bjhH.d.ts +607 -0
- package/dist/index-Dtf6FBaD.d.ts +199 -0
- package/dist/index-KVWGHMbS.d.ts +415 -0
- package/dist/index-PaojfSdd.d.ts +71 -0
- package/dist/index-RU_M5c74.d.ts +163 -0
- package/dist/index-UhkH2eaB.d.ts +192 -0
- package/dist/index-e_18DzAg.d.ts +18 -0
- package/dist/index-jOuh_TKn.d.ts +59 -0
- package/dist/index.d.ts +584 -0
- package/dist/index.js +2626 -0
- package/dist/index.js.map +1 -0
- package/dist/init/index.d.ts +2 -0
- package/dist/init/index.js +12 -0
- package/dist/init/index.js.map +1 -0
- package/dist/instrumentation/index.d.ts +3 -0
- package/dist/instrumentation/index.js +5 -0
- package/dist/instrumentation/index.js.map +1 -0
- package/dist/layer/index.d.ts +2 -0
- package/dist/layer/index.js +6 -0
- package/dist/layer/index.js.map +1 -0
- package/dist/layer-DDip18KY.d.ts +67 -0
- package/dist/logger/index.d.ts +2 -0
- package/dist/logger/index.js +4 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.js +5 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/notifications/index.d.ts +4 -0
- package/dist/notifications/index.js +7 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/observability/index.d.ts +3 -0
- package/dist/observability/index.js +5 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/pino-logger-RYLGXPHZ.js +5 -0
- package/dist/pino-logger-RYLGXPHZ.js.map +1 -0
- package/dist/prisma/generator/index.d.ts +1 -0
- package/dist/prisma/generator/index.js +228 -0
- package/dist/prisma/generator/index.js.map +1 -0
- package/dist/prisma/index.d.ts +5 -0
- package/dist/prisma/index.js +12 -0
- package/dist/prisma/index.js.map +1 -0
- package/dist/prisma-extension-krXnYnrN.d.ts +30 -0
- package/dist/rbac/index.d.ts +116 -0
- package/dist/rbac/index.js +7 -0
- package/dist/rbac/index.js.map +1 -0
- package/dist/sdk-THO3IWEE.js +136 -0
- package/dist/sdk-THO3IWEE.js.map +1 -0
- package/dist/server/index.d.ts +359 -0
- package/dist/server/index.js +884 -0
- package/dist/server/index.js.map +1 -0
- package/dist/telemetry/index.d.ts +83 -0
- package/dist/telemetry/index.js +209 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/temporal/index.d.ts +148 -0
- package/dist/temporal/index.js +6 -0
- package/dist/temporal/index.js.map +1 -0
- package/dist/tracker/index.d.ts +5 -0
- package/dist/tracker/index.js +9 -0
- package/dist/tracker/index.js.map +1 -0
- package/dist/types-Bo2wkjSP.d.ts +35 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/workspace-guard-LHBNK4TG.js +5 -0
- package/dist/workspace-guard-LHBNK4TG.js.map +1 -0
- package/package.json +241 -0
|
@@ -0,0 +1,1875 @@
|
|
|
1
|
+
import { shouldSyncRelationship, shouldSyncEntity, parseRelationship, getCollectionForEntity, extractMinimalFields, extractEntityTitle, createRelationshipEvent, createEntityEvent, addGraphExtension, GraphRegistry } from './chunk-TKBD2ONF.js';
|
|
2
|
+
import { getGraphEventEmitter, NatsGraphEventEmitter } from './chunk-ELLX3K54.js';
|
|
3
|
+
import { ValidationError } from './chunk-SAG6ZYWQ.js';
|
|
4
|
+
import { logger } from './chunk-CBZNP6RF.js';
|
|
5
|
+
import { __export, __name } from './chunk-XE4OXN2W.js';
|
|
6
|
+
|
|
7
|
+
// src/graph/index.ts
|
|
8
|
+
var graph_exports = {};
|
|
9
|
+
__export(graph_exports, {
|
|
10
|
+
AUDIT_FIELDS: () => AUDIT_FIELDS,
|
|
11
|
+
GraphEventsPublisher: () => GraphEventsPublisher,
|
|
12
|
+
GraphExportService: () => GraphExportService,
|
|
13
|
+
GraphModule: () => GraphModule,
|
|
14
|
+
GraphRegistry: () => GraphRegistry,
|
|
15
|
+
SOFT_DELETE_FIELDS: () => SOFT_DELETE_FIELDS,
|
|
16
|
+
STANDARD_FIELDS: () => STANDARD_FIELDS,
|
|
17
|
+
TENANT_FIELDS: () => TENANT_FIELDS,
|
|
18
|
+
addGraphExtension: () => addGraphExtension,
|
|
19
|
+
createEntityEvent: () => createEntityEvent,
|
|
20
|
+
createGraphEventsHelper: () => createGraphEventsHelper,
|
|
21
|
+
createGraphExportService: () => createGraphExportService,
|
|
22
|
+
createPrismaGraphIntegration: () => createPrismaGraphIntegration,
|
|
23
|
+
createRelationshipEvent: () => createRelationshipEvent,
|
|
24
|
+
createRelationshipFromEntity: () => createRelationshipFromEntity,
|
|
25
|
+
defineEntity: () => defineEntity,
|
|
26
|
+
defineGraphConfig: () => defineGraphConfig,
|
|
27
|
+
defineModuleGraph: () => defineModuleGraph,
|
|
28
|
+
defineRelationship: () => defineRelationship,
|
|
29
|
+
extractEntityTitle: () => extractEntityTitle,
|
|
30
|
+
extractMinimalFields: () => extractMinimalFields,
|
|
31
|
+
getCollectionForEntity: () => getCollectionForEntity,
|
|
32
|
+
getGraphEventsPublisher: () => getGraphEventsPublisher,
|
|
33
|
+
getGraphStats: () => getGraphStats,
|
|
34
|
+
initializeGraph: () => initializeGraph,
|
|
35
|
+
isGraphInitialized: () => isGraphInitialized,
|
|
36
|
+
parseRelationship: () => parseRelationship,
|
|
37
|
+
shouldSyncEntity: () => shouldSyncEntity,
|
|
38
|
+
shouldSyncRelationship: () => shouldSyncRelationship
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// src/graph/utils/prisma-helpers.ts
|
|
42
|
+
function createPrismaGraphIntegration(options) {
|
|
43
|
+
const { serviceName, graphConfig, productName = "workspaces", validateRuntime = process.env.NODE_ENV !== "production", autoInit = true, createPrismaClient } = options;
|
|
44
|
+
const state = {
|
|
45
|
+
graphEmitter: null,
|
|
46
|
+
extendedPrismaClient: null,
|
|
47
|
+
emitterInitialized: false,
|
|
48
|
+
initializationPromise: null
|
|
49
|
+
};
|
|
50
|
+
async function initializeGraphEmitter() {
|
|
51
|
+
if (state.initializationPromise) {
|
|
52
|
+
return state.initializationPromise;
|
|
53
|
+
}
|
|
54
|
+
if (state.emitterInitialized) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
state.initializationPromise = (async () => {
|
|
58
|
+
if (!process.env["GRAPH_EVENTS_ENABLED"] || process.env["GRAPH_EVENTS_ENABLED"] !== "true") {
|
|
59
|
+
logger.info(`[${serviceName}] Graph events disabled`);
|
|
60
|
+
state.emitterInitialized = true;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
if (!process.env["NATS_SERVERS"] && process.env["NATS_URL"]) {
|
|
65
|
+
process.env["NATS_SERVERS"] = process.env["NATS_URL"];
|
|
66
|
+
}
|
|
67
|
+
state.graphEmitter = new NatsGraphEventEmitter();
|
|
68
|
+
logger.info(`[${serviceName}] Graph event emitter initialized`);
|
|
69
|
+
state.emitterInitialized = true;
|
|
70
|
+
const basePrisma = createPrismaClient();
|
|
71
|
+
state.extendedPrismaClient = initializeGraph(basePrisma, graphConfig, {
|
|
72
|
+
productName,
|
|
73
|
+
eventEmitter: state.graphEmitter,
|
|
74
|
+
enabled: true,
|
|
75
|
+
validateRuntime
|
|
76
|
+
});
|
|
77
|
+
logger.info(`[${serviceName}] Graph integration initialized successfully`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger.error(`[${serviceName}] Failed to initialize graph integration:`, error);
|
|
80
|
+
state.graphEmitter = null;
|
|
81
|
+
state.extendedPrismaClient = null;
|
|
82
|
+
state.emitterInitialized = true;
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
return state.initializationPromise;
|
|
86
|
+
}
|
|
87
|
+
__name(initializeGraphEmitter, "initializeGraphEmitter");
|
|
88
|
+
function db() {
|
|
89
|
+
if (autoInit && !state.emitterInitialized && !state.initializationPromise) {
|
|
90
|
+
logger.warn(`[${serviceName}] Graph emitter not initialized before first database access. Auto-initializing now. Consider calling 'await initializeGraphEmitter()' explicitly during server startup for better control.`);
|
|
91
|
+
void initializeGraphEmitter();
|
|
92
|
+
}
|
|
93
|
+
if (!process.env["GRAPH_EVENTS_ENABLED"] || process.env["GRAPH_EVENTS_ENABLED"] !== "true" || !state.extendedPrismaClient) {
|
|
94
|
+
const baseClient = createPrismaClient();
|
|
95
|
+
return {
|
|
96
|
+
client: baseClient
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
client: state.extendedPrismaClient
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
__name(db, "db");
|
|
104
|
+
const prisma = new Proxy({}, {
|
|
105
|
+
get(_, prop) {
|
|
106
|
+
const { client } = db();
|
|
107
|
+
const value = client[prop];
|
|
108
|
+
if (typeof value === "function") {
|
|
109
|
+
return value.bind(client);
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
async function cleanup() {
|
|
115
|
+
if (state.graphEmitter) {
|
|
116
|
+
await state.graphEmitter.disconnect();
|
|
117
|
+
logger.info(`[${serviceName}] Graph event emitter disconnected`);
|
|
118
|
+
}
|
|
119
|
+
if (state.extendedPrismaClient) {
|
|
120
|
+
await state.extendedPrismaClient.$disconnect();
|
|
121
|
+
state.extendedPrismaClient = null;
|
|
122
|
+
logger.info(`[${serviceName}] Prisma client disconnected`);
|
|
123
|
+
}
|
|
124
|
+
state.emitterInitialized = false;
|
|
125
|
+
}
|
|
126
|
+
__name(cleanup, "cleanup");
|
|
127
|
+
return {
|
|
128
|
+
/**
|
|
129
|
+
* Initialize graph event emitter
|
|
130
|
+
*
|
|
131
|
+
* With autoInit=true (default), this is called automatically on first database access.
|
|
132
|
+
* You can still call it manually for explicit initialization timing.
|
|
133
|
+
*
|
|
134
|
+
* With autoInit=false, you must call this during application startup.
|
|
135
|
+
*/
|
|
136
|
+
initializeGraphEmitter,
|
|
137
|
+
/**
|
|
138
|
+
* Get Prisma client with graph integration
|
|
139
|
+
* Returns { client: PrismaClient }
|
|
140
|
+
*
|
|
141
|
+
* If autoInit is enabled, triggers initialization on first call.
|
|
142
|
+
*/
|
|
143
|
+
db,
|
|
144
|
+
/**
|
|
145
|
+
* Direct Prisma client accessor (Proxy-based)
|
|
146
|
+
* Use this for most database operations
|
|
147
|
+
*
|
|
148
|
+
* If autoInit is enabled, initialization happens automatically.
|
|
149
|
+
*/
|
|
150
|
+
prisma,
|
|
151
|
+
/**
|
|
152
|
+
* Cleanup function for graceful shutdown
|
|
153
|
+
*/
|
|
154
|
+
cleanup,
|
|
155
|
+
/**
|
|
156
|
+
* Get the raw graph emitter instance (for advanced use cases)
|
|
157
|
+
*/
|
|
158
|
+
getGraphEmitter: /* @__PURE__ */ __name(() => state.graphEmitter, "getGraphEmitter"),
|
|
159
|
+
/**
|
|
160
|
+
* Check if emitter is initialized
|
|
161
|
+
*/
|
|
162
|
+
isInitialized: /* @__PURE__ */ __name(() => state.emitterInitialized, "isInitialized")
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
__name(createPrismaGraphIntegration, "createPrismaGraphIntegration");
|
|
166
|
+
|
|
167
|
+
// src/graph/events-helper.ts
|
|
168
|
+
var GraphEventsPublisher = class _GraphEventsPublisher {
|
|
169
|
+
static {
|
|
170
|
+
__name(this, "GraphEventsPublisher");
|
|
171
|
+
}
|
|
172
|
+
static instance = null;
|
|
173
|
+
productName;
|
|
174
|
+
initialized = false;
|
|
175
|
+
emitter = null;
|
|
176
|
+
constructor(productName = "unknown") {
|
|
177
|
+
this.productName = productName;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get singleton instance
|
|
181
|
+
*/
|
|
182
|
+
static getInstance(productName) {
|
|
183
|
+
if (!_GraphEventsPublisher.instance) {
|
|
184
|
+
_GraphEventsPublisher.instance = new _GraphEventsPublisher(productName);
|
|
185
|
+
}
|
|
186
|
+
return _GraphEventsPublisher.instance;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Set the product name (useful when using singleton)
|
|
190
|
+
*/
|
|
191
|
+
setProductName(name) {
|
|
192
|
+
this.productName = name;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Initialize the publisher
|
|
196
|
+
*/
|
|
197
|
+
async initialize() {
|
|
198
|
+
if (this.initialized) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
this.emitter = getGraphEventEmitter();
|
|
203
|
+
logger.info("[GraphEventsPublisher] Graph event emitter initialized");
|
|
204
|
+
this.initialized = true;
|
|
205
|
+
const natsUrl = process.env["NATS_URL"] || process.env["NATS_SERVERS"];
|
|
206
|
+
if (natsUrl) {
|
|
207
|
+
logger.info(`[GraphEventsPublisher] NATS configured: ${natsUrl}`);
|
|
208
|
+
} else {
|
|
209
|
+
logger.warn("[GraphEventsPublisher] NATS not configured, using in-memory fallback");
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
logger.error("[GraphEventsPublisher] Failed to initialize:", error);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Ensure initialized before publishing
|
|
217
|
+
*/
|
|
218
|
+
async ensureInitialized() {
|
|
219
|
+
if (!this.initialized) {
|
|
220
|
+
await this.initialize();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Publish entity created event
|
|
225
|
+
*/
|
|
226
|
+
async publishEntityCreated(entity, options) {
|
|
227
|
+
await this.publishEntityEvent("entity.created", entity, options);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Publish entity updated event
|
|
231
|
+
*/
|
|
232
|
+
async publishEntityUpdated(entity, options) {
|
|
233
|
+
await this.publishEntityEvent("entity.updated", entity, options);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Publish entity deleted event
|
|
237
|
+
*/
|
|
238
|
+
async publishEntityDeleted(entity, options) {
|
|
239
|
+
await this.publishEntityEvent("entity.deleted", entity, options);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Publish relationship created event
|
|
243
|
+
*/
|
|
244
|
+
async publishRelationshipCreated(relationship, organizationId, options) {
|
|
245
|
+
await this.publishRelationshipEvent("relationship.created", relationship, organizationId, options);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Publish relationship deleted event
|
|
249
|
+
*/
|
|
250
|
+
async publishRelationshipDeleted(relationship, organizationId, options) {
|
|
251
|
+
await this.publishRelationshipEvent("relationship.deleted", relationship, organizationId, options);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Internal method to publish entity events
|
|
255
|
+
*/
|
|
256
|
+
async publishEntityEvent(eventType, entity, options) {
|
|
257
|
+
await this.ensureInitialized();
|
|
258
|
+
if (!this.emitter) {
|
|
259
|
+
logger.warn("[GraphEventsPublisher] Emitter not initialized, skipping event");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
await this.emitter.publishEntityEvent(eventType, {
|
|
264
|
+
id: entity.id,
|
|
265
|
+
type: entity.type,
|
|
266
|
+
data: entity.data
|
|
267
|
+
}, {
|
|
268
|
+
productName: this.productName,
|
|
269
|
+
organizationId: entity.organizationId || "unknown",
|
|
270
|
+
workspaceId: entity.workspaceId,
|
|
271
|
+
actorId: options?.userId
|
|
272
|
+
});
|
|
273
|
+
logger.debug(`[GraphEventsPublisher] Published ${eventType} for ${entity.type}:${entity.id}`);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
logger.error(`[GraphEventsPublisher] Failed to publish ${eventType} for ${entity.type}:${entity.id}:`, error);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Internal method to publish relationship events
|
|
280
|
+
*/
|
|
281
|
+
async publishRelationshipEvent(eventType, relationship, organizationId, options) {
|
|
282
|
+
await this.ensureInitialized();
|
|
283
|
+
if (!this.emitter) {
|
|
284
|
+
logger.warn("[GraphEventsPublisher] Emitter not initialized, skipping event");
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
await this.emitter.publishEntityEvent(eventType, {
|
|
289
|
+
id: `${relationship.from.id}_${relationship.to.id}`,
|
|
290
|
+
type: relationship.relationType,
|
|
291
|
+
data: {
|
|
292
|
+
from: relationship.from,
|
|
293
|
+
to: relationship.to,
|
|
294
|
+
metadata: relationship.metadata
|
|
295
|
+
}
|
|
296
|
+
}, {
|
|
297
|
+
productName: this.productName,
|
|
298
|
+
organizationId,
|
|
299
|
+
actorId: options?.userId
|
|
300
|
+
});
|
|
301
|
+
logger.debug(`[GraphEventsPublisher] Published ${eventType} for ${relationship.relationType}`);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
logger.error(`[GraphEventsPublisher] Failed to publish ${eventType} for ${relationship.relationType}:`, error);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Shutdown the publisher
|
|
308
|
+
*/
|
|
309
|
+
async shutdown() {
|
|
310
|
+
this.initialized = false;
|
|
311
|
+
this.emitter = null;
|
|
312
|
+
logger.info("[GraphEventsPublisher] Shutdown complete");
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Reset singleton (for testing)
|
|
316
|
+
*/
|
|
317
|
+
static reset() {
|
|
318
|
+
_GraphEventsPublisher.instance = null;
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
function getGraphEventsPublisher(productName) {
|
|
322
|
+
return GraphEventsPublisher.getInstance(productName);
|
|
323
|
+
}
|
|
324
|
+
__name(getGraphEventsPublisher, "getGraphEventsPublisher");
|
|
325
|
+
function createGraphEventsHelper(config) {
|
|
326
|
+
const publisher = new GraphEventsPublisher(config.moduleName);
|
|
327
|
+
const helper = {};
|
|
328
|
+
for (const [name, entityConfig] of Object.entries(config.entities)) {
|
|
329
|
+
helper[name] = {
|
|
330
|
+
created: /* @__PURE__ */ __name(async (data, options) => {
|
|
331
|
+
await publisher.publishEntityCreated({
|
|
332
|
+
id: data["id"],
|
|
333
|
+
type: entityConfig.type,
|
|
334
|
+
data,
|
|
335
|
+
organizationId: entityConfig.getOrgId?.(data),
|
|
336
|
+
workspaceId: entityConfig.getWorkspaceId?.(data)
|
|
337
|
+
}, options);
|
|
338
|
+
if (entityConfig.onCreated) {
|
|
339
|
+
await entityConfig.onCreated(data, publisher, options);
|
|
340
|
+
}
|
|
341
|
+
}, "created"),
|
|
342
|
+
updated: /* @__PURE__ */ __name(async (data, options) => {
|
|
343
|
+
await publisher.publishEntityUpdated({
|
|
344
|
+
id: data["id"],
|
|
345
|
+
type: entityConfig.type,
|
|
346
|
+
data,
|
|
347
|
+
organizationId: entityConfig.getOrgId?.(data),
|
|
348
|
+
workspaceId: entityConfig.getWorkspaceId?.(data)
|
|
349
|
+
}, options);
|
|
350
|
+
if (entityConfig.onUpdated) {
|
|
351
|
+
await entityConfig.onUpdated(data, publisher, options);
|
|
352
|
+
}
|
|
353
|
+
}, "updated"),
|
|
354
|
+
deleted: /* @__PURE__ */ __name(async (data, options) => {
|
|
355
|
+
await publisher.publishEntityDeleted({
|
|
356
|
+
id: data["id"],
|
|
357
|
+
type: entityConfig.type,
|
|
358
|
+
data,
|
|
359
|
+
organizationId: entityConfig.getOrgId?.(data),
|
|
360
|
+
workspaceId: entityConfig.getWorkspaceId?.(data)
|
|
361
|
+
}, options);
|
|
362
|
+
if (entityConfig.onDeleted) {
|
|
363
|
+
await entityConfig.onDeleted(data, publisher, options);
|
|
364
|
+
}
|
|
365
|
+
}, "deleted")
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
return helper;
|
|
369
|
+
}
|
|
370
|
+
__name(createGraphEventsHelper, "createGraphEventsHelper");
|
|
371
|
+
|
|
372
|
+
// src/graph/export-service.ts
|
|
373
|
+
var GraphExportService = class {
|
|
374
|
+
static {
|
|
375
|
+
__name(this, "GraphExportService");
|
|
376
|
+
}
|
|
377
|
+
moduleName;
|
|
378
|
+
config;
|
|
379
|
+
fetchers;
|
|
380
|
+
relationshipFetchers;
|
|
381
|
+
customTransformer;
|
|
382
|
+
constructor(serviceConfig) {
|
|
383
|
+
this.moduleName = serviceConfig.moduleName;
|
|
384
|
+
this.config = serviceConfig.config;
|
|
385
|
+
this.fetchers = serviceConfig.fetchers;
|
|
386
|
+
this.relationshipFetchers = serviceConfig.relationshipFetchers || {};
|
|
387
|
+
this.customTransformer = serviceConfig.transformEntity;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Get the graph configuration schema
|
|
391
|
+
*/
|
|
392
|
+
getSchema() {
|
|
393
|
+
const entities = {};
|
|
394
|
+
const relationships = {};
|
|
395
|
+
for (const [entityType, config] of Object.entries(this.config.entities)) {
|
|
396
|
+
entities[entityType] = {
|
|
397
|
+
fields: Array.isArray(config.fields) ? config.fields : [
|
|
398
|
+
"id",
|
|
399
|
+
"createdAt",
|
|
400
|
+
"updatedAt"
|
|
401
|
+
],
|
|
402
|
+
collection: config.collection || "product_entities"
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
if (this.config.relationships) {
|
|
406
|
+
for (const [relType, config] of Object.entries(this.config.relationships)) {
|
|
407
|
+
relationships[relType] = {
|
|
408
|
+
from: config.from,
|
|
409
|
+
fromType: config.from,
|
|
410
|
+
to: config.to,
|
|
411
|
+
toType: config.to,
|
|
412
|
+
edgeType: config.label || "relates_to"
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
moduleName: this.config.moduleName,
|
|
418
|
+
entities,
|
|
419
|
+
relationships
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Export all entities for a given organization/workspace
|
|
424
|
+
*/
|
|
425
|
+
async exportEntities(organizationId, workspaceId, entityTypes) {
|
|
426
|
+
const result = {};
|
|
427
|
+
const typesToExport = entityTypes || Object.keys(this.config.entities);
|
|
428
|
+
for (const entityType of typesToExport) {
|
|
429
|
+
if (!this.config.entities[entityType]) {
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
const fetcher = this.fetchers[entityType];
|
|
433
|
+
if (!fetcher) {
|
|
434
|
+
logger.warn(`[GraphExportService] No fetcher for ${entityType}`);
|
|
435
|
+
result[entityType] = [];
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
const entities = await fetcher(organizationId, workspaceId);
|
|
440
|
+
result[entityType] = entities.map((item) => this.transformEntity(entityType, item));
|
|
441
|
+
} catch (error) {
|
|
442
|
+
logger.error(`[GraphExportService] Error fetching ${entityType}:`, error);
|
|
443
|
+
result[entityType] = [];
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return result;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Export all relationships for a given organization/workspace
|
|
450
|
+
*/
|
|
451
|
+
async exportRelationships(organizationId, workspaceId) {
|
|
452
|
+
const relationships = [];
|
|
453
|
+
for (const [name, fetcher] of Object.entries(this.relationshipFetchers)) {
|
|
454
|
+
try {
|
|
455
|
+
const rels = await fetcher(organizationId, workspaceId);
|
|
456
|
+
relationships.push(...rels);
|
|
457
|
+
} catch (error) {
|
|
458
|
+
logger.error(`[GraphExportService] Error fetching relationships ${name}:`, error);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return relationships;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Export complete graph data (schema + entities + relationships)
|
|
465
|
+
*/
|
|
466
|
+
async exportAll(organizationId, workspaceId) {
|
|
467
|
+
const schema = this.getSchema();
|
|
468
|
+
const entities = await this.exportEntities(organizationId, workspaceId);
|
|
469
|
+
const relationships = await this.exportRelationships(organizationId, workspaceId);
|
|
470
|
+
const entityCounts = {};
|
|
471
|
+
let totalEntities = 0;
|
|
472
|
+
for (const [type, items] of Object.entries(entities)) {
|
|
473
|
+
entityCounts[type] = items.length;
|
|
474
|
+
totalEntities += items.length;
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
moduleName: this.config.moduleName,
|
|
478
|
+
productSource: this.moduleName,
|
|
479
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
480
|
+
schema: {
|
|
481
|
+
entities: schema.entities,
|
|
482
|
+
relationships: schema.relationships
|
|
483
|
+
},
|
|
484
|
+
data: {
|
|
485
|
+
entities,
|
|
486
|
+
relationships
|
|
487
|
+
},
|
|
488
|
+
stats: {
|
|
489
|
+
totalEntities,
|
|
490
|
+
totalRelationships: relationships.length,
|
|
491
|
+
entityCounts
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Transform a database entity to graph export format
|
|
497
|
+
*/
|
|
498
|
+
transformEntity(type, item) {
|
|
499
|
+
if (this.customTransformer) {
|
|
500
|
+
return this.customTransformer(type, item);
|
|
501
|
+
}
|
|
502
|
+
const { id, createdAt, updatedAt, workspaceId, tenantId, ...data } = item;
|
|
503
|
+
return {
|
|
504
|
+
id,
|
|
505
|
+
type,
|
|
506
|
+
data,
|
|
507
|
+
organizationId: tenantId,
|
|
508
|
+
workspaceId,
|
|
509
|
+
createdAt: createdAt instanceof Date ? createdAt.toISOString() : String(createdAt),
|
|
510
|
+
updatedAt: updatedAt instanceof Date ? updatedAt.toISOString() : String(updatedAt)
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
function createGraphExportService(config) {
|
|
515
|
+
return new GraphExportService(config);
|
|
516
|
+
}
|
|
517
|
+
__name(createGraphExportService, "createGraphExportService");
|
|
518
|
+
function createRelationshipFromEntity(entity, relationConfig, relationType) {
|
|
519
|
+
const fromId = entity[relationConfig.from];
|
|
520
|
+
const toId = entity[relationConfig.to];
|
|
521
|
+
if (!fromId || !toId) {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
const metadata = relationConfig.extractMetadata ? relationConfig.extractMetadata(entity) : {};
|
|
525
|
+
return {
|
|
526
|
+
id: `${relationType}_${entity["id"] || fromId}_${toId}`,
|
|
527
|
+
type: relationType,
|
|
528
|
+
fromId,
|
|
529
|
+
fromType: relationConfig.from || "Entity",
|
|
530
|
+
toId,
|
|
531
|
+
toType: relationConfig.to || "Entity",
|
|
532
|
+
metadata,
|
|
533
|
+
createdAt: entity["createdAt"] instanceof Date ? entity["createdAt"].toISOString() : String(entity["createdAt"] || (/* @__PURE__ */ new Date()).toISOString()),
|
|
534
|
+
updatedAt: entity["updatedAt"] instanceof Date ? entity["updatedAt"].toISOString() : String(entity["updatedAt"] || (/* @__PURE__ */ new Date()).toISOString())
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
__name(createRelationshipFromEntity, "createRelationshipFromEntity");
|
|
538
|
+
|
|
539
|
+
// src/graph/module-graph.ts
|
|
540
|
+
var GraphEventsPublisherInternal = class GraphEventsPublisherInternal2 {
|
|
541
|
+
static {
|
|
542
|
+
__name(this, "GraphEventsPublisherInternal");
|
|
543
|
+
}
|
|
544
|
+
productName;
|
|
545
|
+
initialized = false;
|
|
546
|
+
emitter = null;
|
|
547
|
+
constructor(productName) {
|
|
548
|
+
this.productName = productName;
|
|
549
|
+
}
|
|
550
|
+
async initialize() {
|
|
551
|
+
if (this.initialized) return;
|
|
552
|
+
try {
|
|
553
|
+
this.emitter = getGraphEventEmitter();
|
|
554
|
+
logger.info(`[ModuleGraph:${this.productName}] Graph event emitter initialized`);
|
|
555
|
+
this.initialized = true;
|
|
556
|
+
const natsUrl = process.env["NATS_URL"] || process.env["NATS_SERVERS"];
|
|
557
|
+
if (natsUrl) {
|
|
558
|
+
logger.info(`[ModuleGraph:${this.productName}] NATS configured: ${natsUrl}`);
|
|
559
|
+
} else {
|
|
560
|
+
logger.warn(`[ModuleGraph:${this.productName}] NATS not configured, using in-memory fallback`);
|
|
561
|
+
}
|
|
562
|
+
} catch (error) {
|
|
563
|
+
logger.error(`[ModuleGraph:${this.productName}] Failed to initialize:`, error);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
async ensureInitialized() {
|
|
567
|
+
if (!this.initialized) {
|
|
568
|
+
await this.initialize();
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
async publishEntityCreated(entity, options, rbac) {
|
|
572
|
+
await this.publishEntityEvent("entity.created", entity, options, rbac);
|
|
573
|
+
}
|
|
574
|
+
async publishEntityUpdated(entity, options, rbac) {
|
|
575
|
+
await this.publishEntityEvent("entity.updated", entity, options, rbac);
|
|
576
|
+
}
|
|
577
|
+
async publishEntityDeleted(entity, options, rbac) {
|
|
578
|
+
await this.publishEntityEvent("entity.deleted", entity, options, rbac);
|
|
579
|
+
}
|
|
580
|
+
async publishRelationshipCreated(relationship, organizationId, options) {
|
|
581
|
+
await this.publishRelationshipEvent("relationship.created", relationship, organizationId, options);
|
|
582
|
+
}
|
|
583
|
+
async publishRelationshipDeleted(relationship, organizationId, options) {
|
|
584
|
+
await this.publishRelationshipEvent("relationship.deleted", relationship, organizationId, options);
|
|
585
|
+
}
|
|
586
|
+
async publishEntityEvent(eventType, entity, options, rbac) {
|
|
587
|
+
await this.ensureInitialized();
|
|
588
|
+
if (!this.emitter) {
|
|
589
|
+
logger.warn(`[ModuleGraph:${this.productName}] Emitter not initialized, skipping event`);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
await this.emitter.publishEntityEvent(eventType, {
|
|
594
|
+
id: entity.id,
|
|
595
|
+
type: entity.type,
|
|
596
|
+
data: entity.data
|
|
597
|
+
}, {
|
|
598
|
+
productName: this.productName,
|
|
599
|
+
organizationId: entity.organizationId || "unknown",
|
|
600
|
+
workspaceId: entity.workspaceId,
|
|
601
|
+
actorId: options?.actorId
|
|
602
|
+
}, rbac);
|
|
603
|
+
logger.debug(`[ModuleGraph:${this.productName}] Published ${eventType} for ${entity.type}:${entity.id}`, {
|
|
604
|
+
hasRbac: !!rbac
|
|
605
|
+
});
|
|
606
|
+
} catch (error) {
|
|
607
|
+
logger.error(`[ModuleGraph:${this.productName}] Failed to publish ${eventType}:`, error);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async publishRelationshipEvent(eventType, relationship, organizationId, options) {
|
|
611
|
+
await this.ensureInitialized();
|
|
612
|
+
if (!this.emitter) {
|
|
613
|
+
logger.warn(`[ModuleGraph:${this.productName}] Emitter not initialized, skipping event`);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
try {
|
|
617
|
+
await this.emitter.publishEntityEvent(eventType, {
|
|
618
|
+
id: `${relationship.from.id}_${relationship.to.id}`,
|
|
619
|
+
type: relationship.relationType,
|
|
620
|
+
data: {
|
|
621
|
+
from: relationship.from,
|
|
622
|
+
to: relationship.to,
|
|
623
|
+
metadata: relationship.metadata
|
|
624
|
+
}
|
|
625
|
+
}, {
|
|
626
|
+
productName: this.productName,
|
|
627
|
+
organizationId,
|
|
628
|
+
actorId: options?.actorId
|
|
629
|
+
});
|
|
630
|
+
logger.debug(`[ModuleGraph:${this.productName}] Published ${eventType} for ${relationship.relationType}`);
|
|
631
|
+
} catch (error) {
|
|
632
|
+
logger.error(`[ModuleGraph:${this.productName}] Failed to publish ${eventType}:`, error);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
async shutdown() {
|
|
636
|
+
this.initialized = false;
|
|
637
|
+
this.emitter = null;
|
|
638
|
+
logger.info(`[ModuleGraph:${this.productName}] Shutdown complete`);
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
function defineModuleGraph(definition) {
|
|
642
|
+
const { moduleName, productSource, entities, relationships = {} } = definition;
|
|
643
|
+
const publisher = new GraphEventsPublisherInternal(productSource ?? moduleName);
|
|
644
|
+
const events = {};
|
|
645
|
+
for (const [entityName, entityDef] of Object.entries(entities)) {
|
|
646
|
+
const getOrgId = entityDef.getOrgId || (() => void 0);
|
|
647
|
+
const getWorkspaceId = entityDef.getWorkspaceId || (() => void 0);
|
|
648
|
+
const createEventContext = /* @__PURE__ */ __name(() => ({
|
|
649
|
+
publishRelationship: /* @__PURE__ */ __name(async (data, orgId) => {
|
|
650
|
+
await publisher.publishRelationshipCreated(data, orgId || "unknown");
|
|
651
|
+
}, "publishRelationship"),
|
|
652
|
+
deleteRelationship: /* @__PURE__ */ __name(async (data, orgId) => {
|
|
653
|
+
await publisher.publishRelationshipDeleted(data, orgId || "unknown");
|
|
654
|
+
}, "deleteRelationship"),
|
|
655
|
+
getOrgId,
|
|
656
|
+
getWorkspaceId
|
|
657
|
+
}), "createEventContext");
|
|
658
|
+
const buildRbacMetadata = /* @__PURE__ */ __name((data) => {
|
|
659
|
+
if (!entityDef.rbac) return void 0;
|
|
660
|
+
const rbacConfig = entityDef.rbac;
|
|
661
|
+
if (rbacConfig.createResource === false) return void 0;
|
|
662
|
+
const entityId = data["id"];
|
|
663
|
+
const title = data["name"] || data["title"] || data["label"] || entityId;
|
|
664
|
+
return {
|
|
665
|
+
resourceTypeName: rbacConfig.resourceTypeName,
|
|
666
|
+
name: rbacConfig.extractName?.(data) ?? title,
|
|
667
|
+
identifier: rbacConfig.extractIdentifier?.(data) ?? entityId,
|
|
668
|
+
parentResourceIdentifier: rbacConfig.parentResourceField ? data[rbacConfig.parentResourceField] : void 0,
|
|
669
|
+
createResource: rbacConfig.createResource ?? true
|
|
670
|
+
};
|
|
671
|
+
}, "buildRbacMetadata");
|
|
672
|
+
events[entityName] = {
|
|
673
|
+
created: /* @__PURE__ */ __name(async (data, options) => {
|
|
674
|
+
await publisher.publishEntityCreated({
|
|
675
|
+
id: data["id"],
|
|
676
|
+
type: entityDef.type,
|
|
677
|
+
data,
|
|
678
|
+
organizationId: getOrgId(data),
|
|
679
|
+
workspaceId: getWorkspaceId(data)
|
|
680
|
+
}, options, buildRbacMetadata(data));
|
|
681
|
+
if (entityDef.onCreated) {
|
|
682
|
+
await entityDef.onCreated(data, createEventContext(), options);
|
|
683
|
+
}
|
|
684
|
+
}, "created"),
|
|
685
|
+
updated: /* @__PURE__ */ __name(async (data, options) => {
|
|
686
|
+
await publisher.publishEntityUpdated({
|
|
687
|
+
id: data["id"],
|
|
688
|
+
type: entityDef.type,
|
|
689
|
+
data,
|
|
690
|
+
organizationId: getOrgId(data),
|
|
691
|
+
workspaceId: getWorkspaceId(data)
|
|
692
|
+
}, options, buildRbacMetadata(data));
|
|
693
|
+
if (entityDef.onUpdated) {
|
|
694
|
+
await entityDef.onUpdated(data, createEventContext(), options);
|
|
695
|
+
}
|
|
696
|
+
}, "updated"),
|
|
697
|
+
deleted: /* @__PURE__ */ __name(async (data, options) => {
|
|
698
|
+
await publisher.publishEntityDeleted({
|
|
699
|
+
id: data["id"],
|
|
700
|
+
type: entityDef.type,
|
|
701
|
+
data,
|
|
702
|
+
organizationId: getOrgId(data),
|
|
703
|
+
workspaceId: getWorkspaceId(data)
|
|
704
|
+
}, options, buildRbacMetadata(data));
|
|
705
|
+
if (entityDef.onDeleted) {
|
|
706
|
+
await entityDef.onDeleted(data, createEventContext(), options);
|
|
707
|
+
}
|
|
708
|
+
}, "deleted")
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
const getSchema = /* @__PURE__ */ __name(() => {
|
|
712
|
+
const entitySchema = {};
|
|
713
|
+
const relationshipSchema = {};
|
|
714
|
+
for (const [, entityDef] of Object.entries(entities)) {
|
|
715
|
+
entitySchema[entityDef.type] = {
|
|
716
|
+
fields: entityDef.fields || [
|
|
717
|
+
"id",
|
|
718
|
+
"createdAt",
|
|
719
|
+
"updatedAt"
|
|
720
|
+
],
|
|
721
|
+
collection: entityDef.collection || "entity"
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
for (const [relName, relDef] of Object.entries(relationships)) {
|
|
725
|
+
relationshipSchema[relName] = {
|
|
726
|
+
fromType: relDef.fromType,
|
|
727
|
+
toType: relDef.toType,
|
|
728
|
+
edgeType: relDef.edgeType
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
return {
|
|
732
|
+
moduleName,
|
|
733
|
+
entities: entitySchema,
|
|
734
|
+
relationships: relationshipSchema
|
|
735
|
+
};
|
|
736
|
+
}, "getSchema");
|
|
737
|
+
const exportEntities = /* @__PURE__ */ __name(async (organizationId, workspaceId, entityTypes) => {
|
|
738
|
+
const result = {};
|
|
739
|
+
for (const [entityName, entityDef] of Object.entries(entities)) {
|
|
740
|
+
if (entityTypes && !entityTypes.includes(entityDef.type)) {
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
if (!entityDef.fetch) {
|
|
744
|
+
result[entityDef.type] = [];
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
const items = await entityDef.fetch(organizationId, workspaceId);
|
|
749
|
+
result[entityDef.type] = items.map((item) => {
|
|
750
|
+
const { id, createdAt, updatedAt, workspaceId: ws, tenantId, ...data } = item;
|
|
751
|
+
return {
|
|
752
|
+
id,
|
|
753
|
+
type: entityDef.type,
|
|
754
|
+
data,
|
|
755
|
+
organizationId: tenantId,
|
|
756
|
+
workspaceId: ws,
|
|
757
|
+
createdAt: createdAt instanceof Date ? createdAt.toISOString() : String(createdAt),
|
|
758
|
+
updatedAt: updatedAt instanceof Date ? updatedAt.toISOString() : String(updatedAt)
|
|
759
|
+
};
|
|
760
|
+
});
|
|
761
|
+
} catch (error) {
|
|
762
|
+
logger.error(`[ModuleGraph:${moduleName}] Error fetching ${entityName}:`, error);
|
|
763
|
+
result[entityDef.type] = [];
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return result;
|
|
767
|
+
}, "exportEntities");
|
|
768
|
+
const exportRelationships = /* @__PURE__ */ __name(async (organizationId, workspaceId) => {
|
|
769
|
+
const result = [];
|
|
770
|
+
for (const [relName, relDef] of Object.entries(relationships)) {
|
|
771
|
+
if (!relDef.fetch) {
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
try {
|
|
775
|
+
const items = await relDef.fetch(organizationId, workspaceId);
|
|
776
|
+
for (const item of items) {
|
|
777
|
+
result.push({
|
|
778
|
+
id: item.id,
|
|
779
|
+
type: relDef.edgeType,
|
|
780
|
+
fromId: item.fromId,
|
|
781
|
+
fromType: relDef.fromType,
|
|
782
|
+
toId: item.toId,
|
|
783
|
+
toType: relDef.toType,
|
|
784
|
+
metadata: item.metadata || {},
|
|
785
|
+
createdAt: item.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
786
|
+
updatedAt: item.updatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
} catch (error) {
|
|
790
|
+
logger.error(`[ModuleGraph:${moduleName}] Error fetching relationship ${relName}:`, error);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return result;
|
|
794
|
+
}, "exportRelationships");
|
|
795
|
+
const exportAll = /* @__PURE__ */ __name(async (organizationId, workspaceId) => {
|
|
796
|
+
const schema = getSchema();
|
|
797
|
+
const entitiesData = await exportEntities(organizationId, workspaceId);
|
|
798
|
+
const relationshipsData = await exportRelationships(organizationId, workspaceId);
|
|
799
|
+
const entityCounts = {};
|
|
800
|
+
let totalEntities = 0;
|
|
801
|
+
for (const [type, items] of Object.entries(entitiesData)) {
|
|
802
|
+
entityCounts[type] = items.length;
|
|
803
|
+
totalEntities += items.length;
|
|
804
|
+
}
|
|
805
|
+
return {
|
|
806
|
+
moduleName,
|
|
807
|
+
productSource: productSource ?? moduleName,
|
|
808
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
809
|
+
schema,
|
|
810
|
+
data: {
|
|
811
|
+
entities: entitiesData,
|
|
812
|
+
relationships: relationshipsData
|
|
813
|
+
},
|
|
814
|
+
stats: {
|
|
815
|
+
totalEntities,
|
|
816
|
+
totalRelationships: relationshipsData.length,
|
|
817
|
+
entityCounts
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
}, "exportAll");
|
|
821
|
+
return {
|
|
822
|
+
moduleName,
|
|
823
|
+
events,
|
|
824
|
+
initialize: /* @__PURE__ */ __name(() => publisher.initialize(), "initialize"),
|
|
825
|
+
shutdown: /* @__PURE__ */ __name(() => publisher.shutdown(), "shutdown"),
|
|
826
|
+
getSchema,
|
|
827
|
+
exportEntities,
|
|
828
|
+
exportRelationships,
|
|
829
|
+
exportAll,
|
|
830
|
+
getPublisher: /* @__PURE__ */ __name(() => publisher, "getPublisher")
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
__name(defineModuleGraph, "defineModuleGraph");
|
|
834
|
+
|
|
835
|
+
// src/graph/config/builder.ts
|
|
836
|
+
var STANDARD_FIELDS = [
|
|
837
|
+
"id",
|
|
838
|
+
"createdAt",
|
|
839
|
+
"updatedAt"
|
|
840
|
+
];
|
|
841
|
+
var SOFT_DELETE_FIELDS = [
|
|
842
|
+
"deletedAt"
|
|
843
|
+
];
|
|
844
|
+
var AUDIT_FIELDS = [
|
|
845
|
+
"metadata",
|
|
846
|
+
"context"
|
|
847
|
+
];
|
|
848
|
+
var TENANT_FIELDS = [
|
|
849
|
+
"workspaceId",
|
|
850
|
+
"tenantId"
|
|
851
|
+
];
|
|
852
|
+
var DEFAULT_GET_WORKSPACE_ID = /* @__PURE__ */ __name((data) => data["workspaceId"], "DEFAULT_GET_WORKSPACE_ID");
|
|
853
|
+
var DEFAULT_GET_ORG_ID = /* @__PURE__ */ __name((data) => {
|
|
854
|
+
if (data["tenantId"]) {
|
|
855
|
+
return data["tenantId"];
|
|
856
|
+
}
|
|
857
|
+
const context = data["context"];
|
|
858
|
+
return context?.["organizationId"];
|
|
859
|
+
}, "DEFAULT_GET_ORG_ID");
|
|
860
|
+
function createDefaultExtractTitle(entityType) {
|
|
861
|
+
return (data) => {
|
|
862
|
+
const id = data["id"];
|
|
863
|
+
if (typeof id === "string" && id.length > 0) {
|
|
864
|
+
return `${entityType.toLowerCase()}_${id}`;
|
|
865
|
+
}
|
|
866
|
+
return void 0;
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
__name(createDefaultExtractTitle, "createDefaultExtractTitle");
|
|
870
|
+
function generateRelationshipLabel(relationshipName) {
|
|
871
|
+
return relationshipName.replace(/([A-Z])/g, "_$1").toUpperCase().replace(/^_/, "");
|
|
872
|
+
}
|
|
873
|
+
__name(generateRelationshipLabel, "generateRelationshipLabel");
|
|
874
|
+
function extractFieldsFromModel(modelName, options, prismaFieldsModule) {
|
|
875
|
+
if (!prismaFieldsModule.hasModel(modelName)) {
|
|
876
|
+
const availableModels = prismaFieldsModule.getAllModelNames().join(", ");
|
|
877
|
+
throw new Error(`Prisma model '${modelName}' not found. Available models: ${availableModels}`);
|
|
878
|
+
}
|
|
879
|
+
const allFields = prismaFieldsModule.getScalarFields(modelName);
|
|
880
|
+
const sdkDefaults = [
|
|
881
|
+
...STANDARD_FIELDS,
|
|
882
|
+
...options.softDelete !== false ? SOFT_DELETE_FIELDS : [],
|
|
883
|
+
...options.multiTenant !== false ? TENANT_FIELDS : [],
|
|
884
|
+
...options.audit !== false ? AUDIT_FIELDS : []
|
|
885
|
+
];
|
|
886
|
+
const excluded = /* @__PURE__ */ new Set([
|
|
887
|
+
...sdkDefaults,
|
|
888
|
+
...options.exclude ?? []
|
|
889
|
+
]);
|
|
890
|
+
const filteredFields = allFields.filter((field) => !excluded.has(field));
|
|
891
|
+
const finalFields = [
|
|
892
|
+
...filteredFields,
|
|
893
|
+
...options.include ?? []
|
|
894
|
+
];
|
|
895
|
+
return finalFields;
|
|
896
|
+
}
|
|
897
|
+
__name(extractFieldsFromModel, "extractFieldsFromModel");
|
|
898
|
+
function validateEntityConfig(entityType, config) {
|
|
899
|
+
const errors = [];
|
|
900
|
+
if (!config.fields || config.fields.length === 0) {
|
|
901
|
+
errors.push({
|
|
902
|
+
type: "INVALID_ENTITY_TYPE",
|
|
903
|
+
entityType,
|
|
904
|
+
message: `Entity '${entityType}' must have at least one field`
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
if (!config.getWorkspaceId) {
|
|
908
|
+
errors.push({
|
|
909
|
+
type: "MISSING_WORKSPACE_ID",
|
|
910
|
+
entityType,
|
|
911
|
+
message: `Entity '${entityType}' must define getWorkspaceId() to enforce workspace boundary`
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
if (!config.getOrgId) {
|
|
915
|
+
errors.push({
|
|
916
|
+
type: "MISSING_ORG_ID",
|
|
917
|
+
entityType,
|
|
918
|
+
message: `Entity '${entityType}' must define getOrgId() for multi-tenant isolation`
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
return errors;
|
|
922
|
+
}
|
|
923
|
+
__name(validateEntityConfig, "validateEntityConfig");
|
|
924
|
+
function validateRelationshipConfig(relationshipKey, config, entityTypes) {
|
|
925
|
+
const errors = [];
|
|
926
|
+
if (!entityTypes.has(config.from)) {
|
|
927
|
+
errors.push({
|
|
928
|
+
type: "INVALID_RELATIONSHIP",
|
|
929
|
+
relationshipKey,
|
|
930
|
+
message: `Relationship '${relationshipKey}' references unknown 'from' entity: ${config.from}`
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
if (config.to !== "*" && !entityTypes.has(config.to)) {
|
|
934
|
+
errors.push({
|
|
935
|
+
type: "INVALID_RELATIONSHIP",
|
|
936
|
+
relationshipKey,
|
|
937
|
+
message: `Relationship '${relationshipKey}' references unknown 'to' entity: ${config.to}`
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
if (config.to === "*" && !config.polymorphicTypeField) {
|
|
941
|
+
errors.push({
|
|
942
|
+
type: "INVALID_RELATIONSHIP",
|
|
943
|
+
relationshipKey,
|
|
944
|
+
message: `Polymorphic relationship '${relationshipKey}' must define polymorphicTypeField`
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
if (!config.fromField) {
|
|
948
|
+
errors.push({
|
|
949
|
+
type: "INVALID_RELATIONSHIP",
|
|
950
|
+
relationshipKey,
|
|
951
|
+
message: `Relationship '${relationshipKey}' must define fromField`
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
if (!config.label) {
|
|
955
|
+
errors.push({
|
|
956
|
+
type: "INVALID_RELATIONSHIP",
|
|
957
|
+
relationshipKey,
|
|
958
|
+
message: `Relationship '${relationshipKey}' must define label`
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
return errors;
|
|
962
|
+
}
|
|
963
|
+
__name(validateRelationshipConfig, "validateRelationshipConfig");
|
|
964
|
+
function validateWorkspaceBoundary(config, _entityTypes) {
|
|
965
|
+
const errors = [];
|
|
966
|
+
const warnings = [];
|
|
967
|
+
const rootTypes = /* @__PURE__ */ new Set([
|
|
968
|
+
"Workspace",
|
|
969
|
+
"Project",
|
|
970
|
+
"Organization",
|
|
971
|
+
"Tenant"
|
|
972
|
+
]);
|
|
973
|
+
for (const [entityType, entityConfig] of Object.entries(config.entities)) {
|
|
974
|
+
if (entityConfig.enabled === false) continue;
|
|
975
|
+
if (rootTypes.has(entityType)) {
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (!entityConfig.getWorkspaceId) {
|
|
979
|
+
errors.push({
|
|
980
|
+
type: "ORPHANED_ENTITY",
|
|
981
|
+
entityType,
|
|
982
|
+
message: `Entity '${entityType}' has no workspace boundary - every entity must connect to a workspace`
|
|
983
|
+
});
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
let hasWorkspaceLink = false;
|
|
987
|
+
if (config.relationships) {
|
|
988
|
+
for (const [, relConfig] of Object.entries(config.relationships)) {
|
|
989
|
+
if (relConfig.enabled === false) continue;
|
|
990
|
+
if (relConfig.from === entityType && (relConfig.to === "Workspace" || relConfig.to === "Project" || rootTypes.has(relConfig.to))) {
|
|
991
|
+
hasWorkspaceLink = true;
|
|
992
|
+
break;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if (!hasWorkspaceLink) {
|
|
997
|
+
warnings.push(`Entity '${entityType}' has no direct relationship to Workspace/Project. Ensure getWorkspaceId() is correctly implemented.`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return {
|
|
1001
|
+
errors,
|
|
1002
|
+
warnings
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
__name(validateWorkspaceBoundary, "validateWorkspaceBoundary");
|
|
1006
|
+
function validateNoDuplicates(config, _entityTypes) {
|
|
1007
|
+
const errors = [];
|
|
1008
|
+
const warnings = [];
|
|
1009
|
+
const seenEntityTypes = /* @__PURE__ */ new Set();
|
|
1010
|
+
const relationshipLabels = /* @__PURE__ */ new Set();
|
|
1011
|
+
for (const entityType of Object.keys(config.entities)) {
|
|
1012
|
+
if (seenEntityTypes.has(entityType)) {
|
|
1013
|
+
errors.push({
|
|
1014
|
+
type: "DUPLICATE_ENTITY_TYPE",
|
|
1015
|
+
entityType,
|
|
1016
|
+
message: `Duplicate entity type: ${entityType}`
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
seenEntityTypes.add(entityType);
|
|
1020
|
+
}
|
|
1021
|
+
if (config.relationships) {
|
|
1022
|
+
for (const [key, relConfig] of Object.entries(config.relationships)) {
|
|
1023
|
+
const label = relConfig.label;
|
|
1024
|
+
if (relationshipLabels.has(label)) {
|
|
1025
|
+
warnings.push(`Duplicate relationship label: ${label} (in ${key}). This may cause query ambiguity.`);
|
|
1026
|
+
}
|
|
1027
|
+
relationshipLabels.add(label);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return {
|
|
1031
|
+
errors,
|
|
1032
|
+
warnings
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
__name(validateNoDuplicates, "validateNoDuplicates");
|
|
1036
|
+
function validateGraphConfig(config) {
|
|
1037
|
+
const allErrors = [];
|
|
1038
|
+
const allWarnings = [];
|
|
1039
|
+
for (const [entityType, entityConfig] of Object.entries(config.entities)) {
|
|
1040
|
+
if (entityConfig.enabled === false) continue;
|
|
1041
|
+
const errors = validateEntityConfig(entityType, entityConfig);
|
|
1042
|
+
allErrors.push(...errors);
|
|
1043
|
+
}
|
|
1044
|
+
const entityTypes = new Set(Object.keys(config.entities));
|
|
1045
|
+
if (config.relationships) {
|
|
1046
|
+
for (const [relationshipKey, relationshipConfig] of Object.entries(config.relationships)) {
|
|
1047
|
+
if (relationshipConfig.enabled === false) continue;
|
|
1048
|
+
const errors = validateRelationshipConfig(relationshipKey, relationshipConfig, entityTypes);
|
|
1049
|
+
allErrors.push(...errors);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
const boundaryResult = validateWorkspaceBoundary(config);
|
|
1053
|
+
allErrors.push(...boundaryResult.errors);
|
|
1054
|
+
allWarnings.push(...boundaryResult.warnings);
|
|
1055
|
+
const duplicateResult = validateNoDuplicates(config);
|
|
1056
|
+
allErrors.push(...duplicateResult.errors);
|
|
1057
|
+
allWarnings.push(...duplicateResult.warnings);
|
|
1058
|
+
return {
|
|
1059
|
+
valid: allErrors.length === 0,
|
|
1060
|
+
errors: allErrors,
|
|
1061
|
+
warnings: allWarnings
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
__name(validateGraphConfig, "validateGraphConfig");
|
|
1065
|
+
function processEntityConfig(entityName, entityOptions, prismaFieldsModule) {
|
|
1066
|
+
const { softDelete = true, multiTenant = true, audit = true, fields, model, exclude, include, collection, getWorkspaceId, getOrgId, extractTitle, rbac, ...rest } = entityOptions;
|
|
1067
|
+
let entityFields;
|
|
1068
|
+
if (model) {
|
|
1069
|
+
if (!prismaFieldsModule) {
|
|
1070
|
+
throw new Error(`Entity '${entityName}' uses model='${model}' but prismaFieldsModule is not provided. Import prisma-fields.ts and pass it to defineGraphConfig({ prismaFieldsModule: ... })`);
|
|
1071
|
+
}
|
|
1072
|
+
entityFields = extractFieldsFromModel(model, {
|
|
1073
|
+
exclude,
|
|
1074
|
+
include,
|
|
1075
|
+
softDelete,
|
|
1076
|
+
multiTenant,
|
|
1077
|
+
audit
|
|
1078
|
+
}, prismaFieldsModule);
|
|
1079
|
+
} else if (fields) {
|
|
1080
|
+
entityFields = fields;
|
|
1081
|
+
} else {
|
|
1082
|
+
throw new Error(`Entity '${entityName}' must specify either 'fields' or 'model' for field definition`);
|
|
1083
|
+
}
|
|
1084
|
+
const autoFields = [
|
|
1085
|
+
...STANDARD_FIELDS,
|
|
1086
|
+
...softDelete ? SOFT_DELETE_FIELDS : [],
|
|
1087
|
+
...multiTenant ? TENANT_FIELDS : [],
|
|
1088
|
+
...audit ? AUDIT_FIELDS : []
|
|
1089
|
+
];
|
|
1090
|
+
const mergedFields = [
|
|
1091
|
+
.../* @__PURE__ */ new Set([
|
|
1092
|
+
...autoFields,
|
|
1093
|
+
...entityFields
|
|
1094
|
+
])
|
|
1095
|
+
];
|
|
1096
|
+
const finalGetWorkspaceId = multiTenant === false ? (_data) => void 0 : getWorkspaceId ?? DEFAULT_GET_WORKSPACE_ID;
|
|
1097
|
+
const finalGetOrgId = multiTenant === false ? (_data) => void 0 : getOrgId ?? DEFAULT_GET_ORG_ID;
|
|
1098
|
+
return {
|
|
1099
|
+
collection: collection ?? "entities",
|
|
1100
|
+
fields: mergedFields,
|
|
1101
|
+
getWorkspaceId: finalGetWorkspaceId,
|
|
1102
|
+
getOrgId: finalGetOrgId,
|
|
1103
|
+
extractTitle: extractTitle ?? createDefaultExtractTitle(entityName),
|
|
1104
|
+
...rbac && {
|
|
1105
|
+
rbac
|
|
1106
|
+
},
|
|
1107
|
+
...rest
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
__name(processEntityConfig, "processEntityConfig");
|
|
1111
|
+
function processRelationships(relationshipOptions) {
|
|
1112
|
+
const relationships = {};
|
|
1113
|
+
for (const [relationshipName, options] of Object.entries(relationshipOptions)) {
|
|
1114
|
+
const { label, toField, ...rest } = options;
|
|
1115
|
+
relationships[relationshipName] = {
|
|
1116
|
+
toField: toField ?? "id",
|
|
1117
|
+
label: label ?? generateRelationshipLabel(relationshipName),
|
|
1118
|
+
...rest
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
return relationships;
|
|
1122
|
+
}
|
|
1123
|
+
__name(processRelationships, "processRelationships");
|
|
1124
|
+
function logValidationResults(moduleName, validationResult) {
|
|
1125
|
+
if (validationResult.errors.length > 0) {
|
|
1126
|
+
logger.error(`[GraphConfig:${moduleName}] Configuration validation failed:`);
|
|
1127
|
+
for (const error of validationResult.errors) {
|
|
1128
|
+
logger.error(` - ${error.message}`, {
|
|
1129
|
+
type: error.type,
|
|
1130
|
+
entityType: error.entityType,
|
|
1131
|
+
relationshipKey: error.relationshipKey
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
throw new Error(`Graph configuration validation failed for ${moduleName}`);
|
|
1135
|
+
}
|
|
1136
|
+
if (validationResult.warnings.length > 0) {
|
|
1137
|
+
logger.warn(`[GraphConfig:${moduleName}] Configuration warnings:`);
|
|
1138
|
+
for (const warning of validationResult.warnings) {
|
|
1139
|
+
logger.warn(` - ${warning}`);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
logger.info(`[GraphConfig:${moduleName}] Configuration validated successfully`, {
|
|
1143
|
+
valid: validationResult.valid,
|
|
1144
|
+
errorCount: validationResult.errors.length,
|
|
1145
|
+
warningCount: validationResult.warnings.length,
|
|
1146
|
+
autoFieldsApplied: true
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
__name(logValidationResults, "logValidationResults");
|
|
1150
|
+
function defineGraphConfig(options) {
|
|
1151
|
+
const entities = {};
|
|
1152
|
+
for (const [entityName, entityOptions] of Object.entries(options.entities)) {
|
|
1153
|
+
entities[entityName] = processEntityConfig(entityName, entityOptions, options.prismaFieldsModule);
|
|
1154
|
+
}
|
|
1155
|
+
const relationships = options.relationships ? processRelationships(options.relationships) : {};
|
|
1156
|
+
const config = {
|
|
1157
|
+
moduleName: options.moduleName,
|
|
1158
|
+
entities,
|
|
1159
|
+
relationships
|
|
1160
|
+
};
|
|
1161
|
+
const validationResult = validateGraphConfig(config);
|
|
1162
|
+
logValidationResults(config.moduleName, validationResult);
|
|
1163
|
+
autoRegisterWithRegistry(config);
|
|
1164
|
+
return config;
|
|
1165
|
+
}
|
|
1166
|
+
__name(defineGraphConfig, "defineGraphConfig");
|
|
1167
|
+
function autoRegisterWithRegistry(config) {
|
|
1168
|
+
const { moduleName, entities, relationships = {} } = config;
|
|
1169
|
+
const registryEntities = {};
|
|
1170
|
+
for (const [entityType, entityConfig] of Object.entries(entities)) {
|
|
1171
|
+
registryEntities[entityType] = {
|
|
1172
|
+
fields: entityConfig.fields,
|
|
1173
|
+
collection: entityConfig.collection || "entities",
|
|
1174
|
+
extractTitle: entityConfig.extractTitle,
|
|
1175
|
+
getOrgId: entityConfig.getOrgId,
|
|
1176
|
+
getWorkspaceId: entityConfig.getWorkspaceId,
|
|
1177
|
+
enabled: entityConfig.enabled,
|
|
1178
|
+
// Pass through RBAC config so the Prisma graph extension
|
|
1179
|
+
// can include RBAC metadata in entity lifecycle events
|
|
1180
|
+
...entityConfig.rbac && {
|
|
1181
|
+
rbac: {
|
|
1182
|
+
resourceTypeName: entityConfig.rbac.resourceTypeName,
|
|
1183
|
+
extractName: entityConfig.rbac.extractName,
|
|
1184
|
+
extractIdentifier: entityConfig.rbac.extractIdentifier,
|
|
1185
|
+
parentResourceField: entityConfig.rbac.parentResourceField,
|
|
1186
|
+
createResource: entityConfig.rbac.createResource
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
const registryRelationships = {};
|
|
1192
|
+
for (const [key, relConfig] of Object.entries(relationships)) {
|
|
1193
|
+
registryRelationships[key] = {
|
|
1194
|
+
from: relConfig.from,
|
|
1195
|
+
to: relConfig.to,
|
|
1196
|
+
fromField: relConfig.fromField,
|
|
1197
|
+
toField: relConfig.toField,
|
|
1198
|
+
label: relConfig.label,
|
|
1199
|
+
description: relConfig.description,
|
|
1200
|
+
extractMetadata: relConfig.extractMetadata,
|
|
1201
|
+
polymorphicTypeField: relConfig.polymorphicTypeField,
|
|
1202
|
+
enabled: relConfig.enabled
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
GraphRegistry.registerModule({
|
|
1206
|
+
moduleName,
|
|
1207
|
+
entities: registryEntities,
|
|
1208
|
+
relationships: registryRelationships
|
|
1209
|
+
});
|
|
1210
|
+
logger.info(`[GraphConfig:${moduleName}] Auto-registered ${Object.keys(entities).length} entities with GraphRegistry`);
|
|
1211
|
+
}
|
|
1212
|
+
__name(autoRegisterWithRegistry, "autoRegisterWithRegistry");
|
|
1213
|
+
function defineEntity(config) {
|
|
1214
|
+
return config;
|
|
1215
|
+
}
|
|
1216
|
+
__name(defineEntity, "defineEntity");
|
|
1217
|
+
function defineRelationship(config) {
|
|
1218
|
+
return config;
|
|
1219
|
+
}
|
|
1220
|
+
__name(defineRelationship, "defineRelationship");
|
|
1221
|
+
|
|
1222
|
+
// src/graph/config/init.ts
|
|
1223
|
+
var RuntimeValidator = class RuntimeValidator2 {
|
|
1224
|
+
static {
|
|
1225
|
+
__name(this, "RuntimeValidator");
|
|
1226
|
+
}
|
|
1227
|
+
config;
|
|
1228
|
+
constructor(config) {
|
|
1229
|
+
this.config = config;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Validate entity data extraction
|
|
1233
|
+
*/
|
|
1234
|
+
validateEntityData(entityType, data) {
|
|
1235
|
+
const errors = [];
|
|
1236
|
+
const entityConfig = this.config.entities[entityType];
|
|
1237
|
+
if (!entityConfig) {
|
|
1238
|
+
return {
|
|
1239
|
+
valid: true,
|
|
1240
|
+
errors: []
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
try {
|
|
1244
|
+
const workspaceId = entityConfig.getWorkspaceId(data);
|
|
1245
|
+
if (!workspaceId) {
|
|
1246
|
+
errors.push(`Entity ${entityType}:${data["id"]} has no workspaceId`);
|
|
1247
|
+
}
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
errors.push(`Error extracting workspaceId for ${entityType}: ${error}`);
|
|
1250
|
+
}
|
|
1251
|
+
try {
|
|
1252
|
+
const orgId = entityConfig.getOrgId(data);
|
|
1253
|
+
if (!orgId) {
|
|
1254
|
+
errors.push(`Entity ${entityType}:${data["id"]} has no organizationId`);
|
|
1255
|
+
}
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
errors.push(`Error extracting organizationId for ${entityType}: ${error}`);
|
|
1258
|
+
}
|
|
1259
|
+
for (const field of entityConfig.fields) {
|
|
1260
|
+
if (!(field in data)) {
|
|
1261
|
+
errors.push(`Entity ${entityType}:${data["id"]} missing configured field: ${field}`);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
return {
|
|
1265
|
+
valid: errors.length === 0,
|
|
1266
|
+
errors
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
function initializeGraph(prismaClient, config, options) {
|
|
1271
|
+
const { productName, eventEmitter, monitoredEntityTypes = [], enabled = true, validateRuntime } = options;
|
|
1272
|
+
const shouldValidate = validateRuntime ?? process.env["NODE_ENV"] !== "production";
|
|
1273
|
+
logger.info(`[GraphInit:${config.moduleName}] Initializing graph integration`, {
|
|
1274
|
+
productName,
|
|
1275
|
+
enabled,
|
|
1276
|
+
validateRuntime: shouldValidate,
|
|
1277
|
+
entities: Object.keys(config.entities).length,
|
|
1278
|
+
relationships: config.relationships ? Object.keys(config.relationships).length : 0
|
|
1279
|
+
});
|
|
1280
|
+
if (!enabled) {
|
|
1281
|
+
logger.warn(`[GraphInit:${config.moduleName}] Graph integration disabled by configuration`);
|
|
1282
|
+
return prismaClient;
|
|
1283
|
+
}
|
|
1284
|
+
registerConfigWithRegistry(config);
|
|
1285
|
+
const validator = shouldValidate ? new RuntimeValidator(config) : null;
|
|
1286
|
+
const wrappedEmitter = {
|
|
1287
|
+
publishEntityEvent: /* @__PURE__ */ __name(async (eventType, entity, context, rbac) => {
|
|
1288
|
+
if (validator && (eventType === "entity.created" || eventType === "entity.updated")) {
|
|
1289
|
+
const validation = validator.validateEntityData(entity.type, entity.data);
|
|
1290
|
+
if (!validation.valid) {
|
|
1291
|
+
logger.error(`[GraphInit:${config.moduleName}] Runtime validation failed for ${entity.type}:${entity.id}`, {
|
|
1292
|
+
errors: validation.errors
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
return eventEmitter.publishEntityEvent(eventType, entity, context, rbac);
|
|
1297
|
+
}, "publishEntityEvent")
|
|
1298
|
+
};
|
|
1299
|
+
const extendedClient = addGraphExtension(prismaClient, {
|
|
1300
|
+
enabled: true,
|
|
1301
|
+
productName,
|
|
1302
|
+
eventEmitter: wrappedEmitter,
|
|
1303
|
+
entityTypes: monitoredEntityTypes
|
|
1304
|
+
});
|
|
1305
|
+
logger.info(`[GraphInit:${config.moduleName}] Graph integration initialized successfully`);
|
|
1306
|
+
return extendedClient;
|
|
1307
|
+
}
|
|
1308
|
+
__name(initializeGraph, "initializeGraph");
|
|
1309
|
+
function registerConfigWithRegistry(config) {
|
|
1310
|
+
const { moduleName, entities, relationships = {} } = config;
|
|
1311
|
+
const registryEntities = {};
|
|
1312
|
+
for (const [entityType, entityConfig] of Object.entries(entities)) {
|
|
1313
|
+
registryEntities[entityType] = {
|
|
1314
|
+
fields: entityConfig.fields,
|
|
1315
|
+
collection: entityConfig.collection || "entities",
|
|
1316
|
+
extractTitle: entityConfig.extractTitle,
|
|
1317
|
+
getOrgId: entityConfig.getOrgId,
|
|
1318
|
+
getWorkspaceId: entityConfig.getWorkspaceId,
|
|
1319
|
+
enabled: entityConfig.enabled
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
const registryRelationships = {};
|
|
1323
|
+
for (const [key, relConfig] of Object.entries(relationships)) {
|
|
1324
|
+
registryRelationships[key] = {
|
|
1325
|
+
from: relConfig.from,
|
|
1326
|
+
to: relConfig.to,
|
|
1327
|
+
fromField: relConfig.fromField,
|
|
1328
|
+
toField: relConfig.toField,
|
|
1329
|
+
label: relConfig.label,
|
|
1330
|
+
description: relConfig.description,
|
|
1331
|
+
extractMetadata: relConfig.extractMetadata,
|
|
1332
|
+
polymorphicTypeField: relConfig.polymorphicTypeField,
|
|
1333
|
+
enabled: relConfig.enabled
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
GraphRegistry.registerModule({
|
|
1337
|
+
moduleName,
|
|
1338
|
+
entities: registryEntities,
|
|
1339
|
+
relationships: registryRelationships
|
|
1340
|
+
});
|
|
1341
|
+
logger.info(`[GraphInit:${moduleName}] Configuration registered with GraphRegistry`);
|
|
1342
|
+
}
|
|
1343
|
+
__name(registerConfigWithRegistry, "registerConfigWithRegistry");
|
|
1344
|
+
function isGraphInitialized(moduleName) {
|
|
1345
|
+
const registeredModules = GraphRegistry.getRegisteredModules();
|
|
1346
|
+
return registeredModules.includes(moduleName);
|
|
1347
|
+
}
|
|
1348
|
+
__name(isGraphInitialized, "isGraphInitialized");
|
|
1349
|
+
function getGraphStats() {
|
|
1350
|
+
const stats = GraphRegistry.getStats();
|
|
1351
|
+
const registeredModules = GraphRegistry.getRegisteredModules();
|
|
1352
|
+
return {
|
|
1353
|
+
modules: stats.modules,
|
|
1354
|
+
entities: stats.entities,
|
|
1355
|
+
relationships: stats.relationships,
|
|
1356
|
+
registeredModules
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
__name(getGraphStats, "getGraphStats");
|
|
1360
|
+
|
|
1361
|
+
// src/graph/index.ts
|
|
1362
|
+
var GraphModule = class {
|
|
1363
|
+
static {
|
|
1364
|
+
__name(this, "GraphModule");
|
|
1365
|
+
}
|
|
1366
|
+
client;
|
|
1367
|
+
context;
|
|
1368
|
+
constructor(client, context) {
|
|
1369
|
+
this.client = client;
|
|
1370
|
+
this.context = context;
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Create graph node
|
|
1374
|
+
*/
|
|
1375
|
+
async createNode(options) {
|
|
1376
|
+
if (!options.type) {
|
|
1377
|
+
throw new ValidationError("Node type is required");
|
|
1378
|
+
}
|
|
1379
|
+
if (!options.properties || typeof options.properties !== "object") {
|
|
1380
|
+
throw new ValidationError("Node properties must be an object");
|
|
1381
|
+
}
|
|
1382
|
+
logger.debug("[Graph] Creating node", {
|
|
1383
|
+
type: options.type
|
|
1384
|
+
});
|
|
1385
|
+
const mutation = `
|
|
1386
|
+
mutation CreateGraphNode($input: CreateGraphNodeInput!) {
|
|
1387
|
+
createGraphNode(input: $input) {
|
|
1388
|
+
id
|
|
1389
|
+
type
|
|
1390
|
+
properties
|
|
1391
|
+
workspaceId
|
|
1392
|
+
organizationId
|
|
1393
|
+
createdAt
|
|
1394
|
+
updatedAt
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
`;
|
|
1398
|
+
const variables = {
|
|
1399
|
+
input: {
|
|
1400
|
+
type: options.type,
|
|
1401
|
+
properties: options.properties,
|
|
1402
|
+
workspaceId: options.workspaceId || this.context.workspaceId,
|
|
1403
|
+
organizationId: options.organizationId
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
const response = await this.client.query(mutation, variables, this.context);
|
|
1407
|
+
logger.info("[Graph] Node created", {
|
|
1408
|
+
nodeId: response.createGraphNode.id
|
|
1409
|
+
});
|
|
1410
|
+
return response.createGraphNode;
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Create graph edge
|
|
1414
|
+
*/
|
|
1415
|
+
async createEdge(options) {
|
|
1416
|
+
if (!options.from) {
|
|
1417
|
+
throw new ValidationError("Source node ID is required");
|
|
1418
|
+
}
|
|
1419
|
+
if (!options.to) {
|
|
1420
|
+
throw new ValidationError("Target node ID is required");
|
|
1421
|
+
}
|
|
1422
|
+
if (!options.type) {
|
|
1423
|
+
throw new ValidationError("Edge type is required");
|
|
1424
|
+
}
|
|
1425
|
+
logger.debug("[Graph] Creating edge", {
|
|
1426
|
+
from: options.from,
|
|
1427
|
+
to: options.to,
|
|
1428
|
+
type: options.type
|
|
1429
|
+
});
|
|
1430
|
+
const mutation = `
|
|
1431
|
+
mutation CreateGraphEdge($input: CreateGraphEdgeInput!) {
|
|
1432
|
+
createGraphEdge(input: $input) {
|
|
1433
|
+
id
|
|
1434
|
+
from
|
|
1435
|
+
to
|
|
1436
|
+
type
|
|
1437
|
+
properties
|
|
1438
|
+
workspaceId
|
|
1439
|
+
organizationId
|
|
1440
|
+
createdAt
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
`;
|
|
1444
|
+
const variables = {
|
|
1445
|
+
input: {
|
|
1446
|
+
from: options.from,
|
|
1447
|
+
to: options.to,
|
|
1448
|
+
type: options.type,
|
|
1449
|
+
properties: options.properties,
|
|
1450
|
+
workspaceId: options.workspaceId || this.context.workspaceId,
|
|
1451
|
+
organizationId: options.organizationId
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
const response = await this.client.query(mutation, variables, this.context);
|
|
1455
|
+
logger.info("[Graph] Edge created", {
|
|
1456
|
+
edgeId: response.createGraphEdge.id
|
|
1457
|
+
});
|
|
1458
|
+
return response.createGraphEdge;
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Get node by ID
|
|
1462
|
+
*/
|
|
1463
|
+
async getNode(nodeId) {
|
|
1464
|
+
logger.debug("[Graph] Getting node", {
|
|
1465
|
+
nodeId
|
|
1466
|
+
});
|
|
1467
|
+
const query = `
|
|
1468
|
+
query GetGraphNode($nodeId: ID!) {
|
|
1469
|
+
getGraphNode(nodeId: $nodeId) {
|
|
1470
|
+
id
|
|
1471
|
+
type
|
|
1472
|
+
properties
|
|
1473
|
+
workspaceId
|
|
1474
|
+
organizationId
|
|
1475
|
+
createdAt
|
|
1476
|
+
updatedAt
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
`;
|
|
1480
|
+
const response = await this.client.query(query, {
|
|
1481
|
+
nodeId
|
|
1482
|
+
}, this.context);
|
|
1483
|
+
return response.getGraphNode;
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Update node
|
|
1487
|
+
*/
|
|
1488
|
+
async updateNode(nodeId, properties) {
|
|
1489
|
+
logger.debug("[Graph] Updating node", {
|
|
1490
|
+
nodeId
|
|
1491
|
+
});
|
|
1492
|
+
const mutation = `
|
|
1493
|
+
mutation UpdateGraphNode($nodeId: ID!, $properties: JSON!) {
|
|
1494
|
+
updateGraphNode(nodeId: $nodeId, properties: $properties) {
|
|
1495
|
+
id
|
|
1496
|
+
type
|
|
1497
|
+
properties
|
|
1498
|
+
workspaceId
|
|
1499
|
+
organizationId
|
|
1500
|
+
createdAt
|
|
1501
|
+
updatedAt
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
`;
|
|
1505
|
+
const response = await this.client.query(mutation, {
|
|
1506
|
+
nodeId,
|
|
1507
|
+
properties
|
|
1508
|
+
}, this.context);
|
|
1509
|
+
logger.info("[Graph] Node updated", {
|
|
1510
|
+
nodeId
|
|
1511
|
+
});
|
|
1512
|
+
return response.updateGraphNode;
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Delete node
|
|
1516
|
+
*/
|
|
1517
|
+
async deleteNode(nodeId) {
|
|
1518
|
+
logger.debug("[Graph] Deleting node", {
|
|
1519
|
+
nodeId
|
|
1520
|
+
});
|
|
1521
|
+
const mutation = `
|
|
1522
|
+
mutation DeleteGraphNode($nodeId: ID!) {
|
|
1523
|
+
deleteGraphNode(nodeId: $nodeId) {
|
|
1524
|
+
success
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
`;
|
|
1528
|
+
await this.client.query(mutation, {
|
|
1529
|
+
nodeId
|
|
1530
|
+
}, this.context);
|
|
1531
|
+
logger.info("[Graph] Node deleted", {
|
|
1532
|
+
nodeId
|
|
1533
|
+
});
|
|
1534
|
+
return true;
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Delete edge
|
|
1538
|
+
*/
|
|
1539
|
+
async deleteEdge(edgeId) {
|
|
1540
|
+
logger.debug("[Graph] Deleting edge", {
|
|
1541
|
+
edgeId
|
|
1542
|
+
});
|
|
1543
|
+
const mutation = `
|
|
1544
|
+
mutation DeleteGraphEdge($edgeId: ID!) {
|
|
1545
|
+
deleteGraphEdge(edgeId: $edgeId) {
|
|
1546
|
+
success
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
`;
|
|
1550
|
+
await this.client.query(mutation, {
|
|
1551
|
+
edgeId
|
|
1552
|
+
}, this.context);
|
|
1553
|
+
logger.info("[Graph] Edge deleted", {
|
|
1554
|
+
edgeId
|
|
1555
|
+
});
|
|
1556
|
+
return true;
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Traverse graph
|
|
1560
|
+
*/
|
|
1561
|
+
async traverse(options) {
|
|
1562
|
+
if (!options.startNode) {
|
|
1563
|
+
throw new ValidationError("Start node ID is required");
|
|
1564
|
+
}
|
|
1565
|
+
logger.debug("[Graph] Traversing graph", {
|
|
1566
|
+
startNode: options.startNode,
|
|
1567
|
+
direction: options.direction || "outbound"
|
|
1568
|
+
});
|
|
1569
|
+
const query = `
|
|
1570
|
+
query TraverseGraph(
|
|
1571
|
+
$startNode: ID!
|
|
1572
|
+
$direction: GraphDirection
|
|
1573
|
+
$edgeTypes: [String!]
|
|
1574
|
+
$minDepth: Int
|
|
1575
|
+
$maxDepth: Int
|
|
1576
|
+
$limit: Int
|
|
1577
|
+
) {
|
|
1578
|
+
traverseGraph(
|
|
1579
|
+
startNode: $startNode
|
|
1580
|
+
direction: $direction
|
|
1581
|
+
edgeTypes: $edgeTypes
|
|
1582
|
+
minDepth: $minDepth
|
|
1583
|
+
maxDepth: $maxDepth
|
|
1584
|
+
limit: $limit
|
|
1585
|
+
) {
|
|
1586
|
+
nodes {
|
|
1587
|
+
id
|
|
1588
|
+
type
|
|
1589
|
+
properties
|
|
1590
|
+
workspaceId
|
|
1591
|
+
organizationId
|
|
1592
|
+
createdAt
|
|
1593
|
+
updatedAt
|
|
1594
|
+
}
|
|
1595
|
+
edges {
|
|
1596
|
+
id
|
|
1597
|
+
from
|
|
1598
|
+
to
|
|
1599
|
+
type
|
|
1600
|
+
properties
|
|
1601
|
+
workspaceId
|
|
1602
|
+
organizationId
|
|
1603
|
+
createdAt
|
|
1604
|
+
}
|
|
1605
|
+
paths {
|
|
1606
|
+
nodes {
|
|
1607
|
+
id
|
|
1608
|
+
type
|
|
1609
|
+
properties
|
|
1610
|
+
}
|
|
1611
|
+
edges {
|
|
1612
|
+
id
|
|
1613
|
+
from
|
|
1614
|
+
to
|
|
1615
|
+
type
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
`;
|
|
1621
|
+
const variables = {
|
|
1622
|
+
startNode: options.startNode,
|
|
1623
|
+
direction: options.direction || "outbound",
|
|
1624
|
+
edgeTypes: options.edgeTypes,
|
|
1625
|
+
minDepth: options.minDepth || 1,
|
|
1626
|
+
maxDepth: options.maxDepth || 3,
|
|
1627
|
+
limit: options.limit || 100
|
|
1628
|
+
};
|
|
1629
|
+
const response = await this.client.query(query, variables, this.context);
|
|
1630
|
+
return response.traverseGraph;
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Find shortest path between two nodes
|
|
1634
|
+
*/
|
|
1635
|
+
async findShortestPath(fromNode, toNode, options) {
|
|
1636
|
+
logger.debug("[Graph] Finding shortest path", {
|
|
1637
|
+
fromNode,
|
|
1638
|
+
toNode
|
|
1639
|
+
});
|
|
1640
|
+
const query = `
|
|
1641
|
+
query FindShortestPath(
|
|
1642
|
+
$fromNode: ID!
|
|
1643
|
+
$toNode: ID!
|
|
1644
|
+
$edgeTypes: [String!]
|
|
1645
|
+
$maxDepth: Int
|
|
1646
|
+
) {
|
|
1647
|
+
findShortestPath(
|
|
1648
|
+
fromNode: $fromNode
|
|
1649
|
+
toNode: $toNode
|
|
1650
|
+
edgeTypes: $edgeTypes
|
|
1651
|
+
maxDepth: $maxDepth
|
|
1652
|
+
) {
|
|
1653
|
+
nodes {
|
|
1654
|
+
id
|
|
1655
|
+
type
|
|
1656
|
+
properties
|
|
1657
|
+
}
|
|
1658
|
+
edges {
|
|
1659
|
+
id
|
|
1660
|
+
from
|
|
1661
|
+
to
|
|
1662
|
+
type
|
|
1663
|
+
}
|
|
1664
|
+
distance
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
`;
|
|
1668
|
+
const response = await this.client.query(query, {
|
|
1669
|
+
fromNode,
|
|
1670
|
+
toNode,
|
|
1671
|
+
edgeTypes: options?.edgeTypes,
|
|
1672
|
+
maxDepth: options?.maxDepth || 10
|
|
1673
|
+
}, this.context);
|
|
1674
|
+
return response.findShortestPath;
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Query graph with nGQL. Proxied to wspace-graph-svc's `executeNGQL` resolver
|
|
1678
|
+
* via the federated GraphQL gateway — no direct graph-DB access.
|
|
1679
|
+
*/
|
|
1680
|
+
async query(options) {
|
|
1681
|
+
if (!options.query) {
|
|
1682
|
+
throw new ValidationError("nGQL query is required");
|
|
1683
|
+
}
|
|
1684
|
+
logger.debug("[Graph] Executing nGQL query");
|
|
1685
|
+
const query = `
|
|
1686
|
+
query ExecuteNGQL($query: String!, $bindVars: JSON, $limit: Int) {
|
|
1687
|
+
executeNGQL(query: $query, bindVars: $bindVars, limit: $limit) {
|
|
1688
|
+
results
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
`;
|
|
1692
|
+
const variables = {
|
|
1693
|
+
query: options.query,
|
|
1694
|
+
bindVars: options.bindVars,
|
|
1695
|
+
limit: options.limit || 1e3
|
|
1696
|
+
};
|
|
1697
|
+
const response = await this.client.query(query, variables, this.context);
|
|
1698
|
+
return response.executeNGQL.results;
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Get neighbors of a node
|
|
1702
|
+
*/
|
|
1703
|
+
async getNeighbors(nodeId, options) {
|
|
1704
|
+
logger.debug("[Graph] Getting neighbors", {
|
|
1705
|
+
nodeId
|
|
1706
|
+
});
|
|
1707
|
+
const query = `
|
|
1708
|
+
query GetNeighbors(
|
|
1709
|
+
$nodeId: ID!
|
|
1710
|
+
$direction: GraphDirection
|
|
1711
|
+
$edgeTypes: [String!]
|
|
1712
|
+
$limit: Int
|
|
1713
|
+
) {
|
|
1714
|
+
getNeighbors(
|
|
1715
|
+
nodeId: $nodeId
|
|
1716
|
+
direction: $direction
|
|
1717
|
+
edgeTypes: $edgeTypes
|
|
1718
|
+
limit: $limit
|
|
1719
|
+
) {
|
|
1720
|
+
nodes {
|
|
1721
|
+
id
|
|
1722
|
+
type
|
|
1723
|
+
properties
|
|
1724
|
+
workspaceId
|
|
1725
|
+
organizationId
|
|
1726
|
+
createdAt
|
|
1727
|
+
updatedAt
|
|
1728
|
+
}
|
|
1729
|
+
edges {
|
|
1730
|
+
id
|
|
1731
|
+
from
|
|
1732
|
+
to
|
|
1733
|
+
type
|
|
1734
|
+
properties
|
|
1735
|
+
workspaceId
|
|
1736
|
+
organizationId
|
|
1737
|
+
createdAt
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
`;
|
|
1742
|
+
const response = await this.client.query(query, {
|
|
1743
|
+
nodeId,
|
|
1744
|
+
direction: options?.direction || "outbound",
|
|
1745
|
+
edgeTypes: options?.edgeTypes,
|
|
1746
|
+
limit: options?.limit || 100
|
|
1747
|
+
}, this.context);
|
|
1748
|
+
return response.getNeighbors;
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Get node degree (number of connections)
|
|
1752
|
+
*/
|
|
1753
|
+
async getNodeDegree(nodeId, direction) {
|
|
1754
|
+
logger.debug("[Graph] Getting node degree", {
|
|
1755
|
+
nodeId
|
|
1756
|
+
});
|
|
1757
|
+
const query = `
|
|
1758
|
+
query GetNodeDegree($nodeId: ID!, $direction: GraphDirection) {
|
|
1759
|
+
getNodeDegree(nodeId: $nodeId, direction: $direction) {
|
|
1760
|
+
inbound
|
|
1761
|
+
outbound
|
|
1762
|
+
total
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
`;
|
|
1766
|
+
const response = await this.client.query(query, {
|
|
1767
|
+
nodeId,
|
|
1768
|
+
direction
|
|
1769
|
+
}, this.context);
|
|
1770
|
+
return response.getNodeDegree;
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Find nodes by properties
|
|
1774
|
+
*/
|
|
1775
|
+
async findNodes(type, filters, options) {
|
|
1776
|
+
logger.debug("[Graph] Finding nodes", {
|
|
1777
|
+
type,
|
|
1778
|
+
filters
|
|
1779
|
+
});
|
|
1780
|
+
const query = `
|
|
1781
|
+
query FindGraphNodes(
|
|
1782
|
+
$type: String!
|
|
1783
|
+
$filters: JSON!
|
|
1784
|
+
$limit: Int
|
|
1785
|
+
$offset: Int
|
|
1786
|
+
) {
|
|
1787
|
+
findGraphNodes(
|
|
1788
|
+
type: $type
|
|
1789
|
+
filters: $filters
|
|
1790
|
+
limit: $limit
|
|
1791
|
+
offset: $offset
|
|
1792
|
+
) {
|
|
1793
|
+
nodes {
|
|
1794
|
+
id
|
|
1795
|
+
type
|
|
1796
|
+
properties
|
|
1797
|
+
workspaceId
|
|
1798
|
+
organizationId
|
|
1799
|
+
createdAt
|
|
1800
|
+
updatedAt
|
|
1801
|
+
}
|
|
1802
|
+
total
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
`;
|
|
1806
|
+
const response = await this.client.query(query, {
|
|
1807
|
+
type,
|
|
1808
|
+
filters,
|
|
1809
|
+
limit: options?.limit || 100,
|
|
1810
|
+
offset: options?.offset || 0
|
|
1811
|
+
}, this.context);
|
|
1812
|
+
return response.findGraphNodes;
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Batch create nodes
|
|
1816
|
+
*/
|
|
1817
|
+
async createNodesBatch(nodes) {
|
|
1818
|
+
logger.debug("[Graph] Batch creating nodes", {
|
|
1819
|
+
count: nodes.length
|
|
1820
|
+
});
|
|
1821
|
+
const mutation = `
|
|
1822
|
+
mutation CreateGraphNodesBatch($nodes: [CreateGraphNodeInput!]!) {
|
|
1823
|
+
createGraphNodesBatch(nodes: $nodes) {
|
|
1824
|
+
id
|
|
1825
|
+
type
|
|
1826
|
+
properties
|
|
1827
|
+
workspaceId
|
|
1828
|
+
organizationId
|
|
1829
|
+
createdAt
|
|
1830
|
+
updatedAt
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
`;
|
|
1834
|
+
const response = await this.client.query(mutation, {
|
|
1835
|
+
nodes
|
|
1836
|
+
}, this.context);
|
|
1837
|
+
logger.info("[Graph] Nodes batch created", {
|
|
1838
|
+
count: response.createGraphNodesBatch.length
|
|
1839
|
+
});
|
|
1840
|
+
return response.createGraphNodesBatch;
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Batch create edges
|
|
1844
|
+
*/
|
|
1845
|
+
async createEdgesBatch(edges) {
|
|
1846
|
+
logger.debug("[Graph] Batch creating edges", {
|
|
1847
|
+
count: edges.length
|
|
1848
|
+
});
|
|
1849
|
+
const mutation = `
|
|
1850
|
+
mutation CreateGraphEdgesBatch($edges: [CreateGraphEdgeInput!]!) {
|
|
1851
|
+
createGraphEdgesBatch(edges: $edges) {
|
|
1852
|
+
id
|
|
1853
|
+
from
|
|
1854
|
+
to
|
|
1855
|
+
type
|
|
1856
|
+
properties
|
|
1857
|
+
workspaceId
|
|
1858
|
+
organizationId
|
|
1859
|
+
createdAt
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
`;
|
|
1863
|
+
const response = await this.client.query(mutation, {
|
|
1864
|
+
edges
|
|
1865
|
+
}, this.context);
|
|
1866
|
+
logger.info("[Graph] Edges batch created", {
|
|
1867
|
+
count: response.createGraphEdgesBatch.length
|
|
1868
|
+
});
|
|
1869
|
+
return response.createGraphEdgesBatch;
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1872
|
+
|
|
1873
|
+
export { AUDIT_FIELDS, GraphEventsPublisher, GraphExportService, GraphModule, SOFT_DELETE_FIELDS, STANDARD_FIELDS, TENANT_FIELDS, createGraphEventsHelper, createGraphExportService, createPrismaGraphIntegration, createRelationshipFromEntity, defineEntity, defineGraphConfig, defineModuleGraph, defineRelationship, getGraphEventsPublisher, getGraphStats, graph_exports, initializeGraph, isGraphInitialized };
|
|
1874
|
+
//# sourceMappingURL=chunk-F5XEAGHQ.js.map
|
|
1875
|
+
//# sourceMappingURL=chunk-F5XEAGHQ.js.map
|