@memberjunction/server 5.51.0 → 6.1.0-edge.1
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/LICENSE +7 -0
- package/dist/agentSessions/SessionManager.d.ts +22 -1
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +13 -3
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/auth/actingContextResolver.d.ts +44 -0
- package/dist/auth/actingContextResolver.d.ts.map +1 -0
- package/dist/auth/actingContextResolver.js +18 -0
- package/dist/auth/actingContextResolver.js.map +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/sessionUserClone.d.ts +32 -0
- package/dist/auth/sessionUserClone.d.ts.map +1 -0
- package/dist/auth/sessionUserClone.js +50 -0
- package/dist/auth/sessionUserClone.js.map +1 -0
- package/dist/context.d.ts +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +43 -6
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +234 -642
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +1277 -3755
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts +5 -0
- package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.js +5 -4
- package/dist/generic/FireAndForgetHeartbeat.js.map +1 -1
- package/dist/generic/PushStatusResolver.d.ts +58 -0
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +42 -3
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +9 -0
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +31 -13
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/dist/multiTenancy/index.d.ts +19 -0
- package/dist/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +82 -5
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +12 -5
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +13 -16
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +15 -14
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +19 -26
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +30 -40
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTestResolver.js +90 -112
- package/dist/resolvers/RunTestResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.js +3 -3
- package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.js +2 -2
- package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts +85 -0
- package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -0
- package/dist/resolvers/TaskGraphFrameResolver.js +163 -0
- package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -0
- package/dist/resolvers/VersionHistoryResolver.d.ts.map +1 -1
- package/dist/resolvers/VersionHistoryResolver.js +8 -12
- package/dist/resolvers/VersionHistoryResolver.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts +0 -4
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +1 -15
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/services/StartTaskGraphDispatcher.d.ts +33 -0
- package/dist/services/StartTaskGraphDispatcher.d.ts.map +1 -0
- package/dist/services/StartTaskGraphDispatcher.js +31 -0
- package/dist/services/StartTaskGraphDispatcher.js.map +1 -0
- package/dist/services/TaskGraphAgentRunner.d.ts +15 -0
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -0
- package/dist/services/TaskGraphAgentRunner.js +74 -0
- package/dist/services/TaskGraphAgentRunner.js.map +1 -0
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +70 -0
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -0
- package/dist/services/TaskGraphContinuationDeliverer.js +162 -0
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -0
- package/dist/services/TaskGraphProviderFactory.d.ts +39 -0
- package/dist/services/TaskGraphProviderFactory.d.ts.map +1 -0
- package/dist/services/TaskGraphProviderFactory.js +28 -0
- package/dist/services/TaskGraphProviderFactory.js.map +1 -0
- package/package.json +103 -100
- package/src/__tests__/RealtimeBridgeResolver.test.ts +6 -0
- package/src/__tests__/SessionManager.test.ts +44 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +249 -0
- package/src/__tests__/TaskGraphFrameResolver.test.ts +136 -0
- package/src/__tests__/multiTenancy.security.test.ts +254 -22
- package/src/__tests__/multiTenancy.test.ts +22 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +56 -0
- package/src/__tests__/resolverBase.rls.test.ts +4 -0
- package/src/__tests__/search-knowledge-tags.test.ts +74 -6
- package/src/__tests__/sessionUserClone.test.ts +161 -0
- package/src/agentSessions/SessionManager.ts +26 -2
- package/src/auth/actingContextResolver.ts +56 -0
- package/src/auth/index.ts +1 -0
- package/src/auth/sessionUserClone.ts +50 -0
- package/src/context.ts +48 -6
- package/src/generated/generated.ts +899 -2555
- package/src/generic/FireAndForgetHeartbeat.ts +10 -4
- package/src/generic/PushStatusResolver.ts +79 -4
- package/src/generic/ResolverBase.ts +33 -13
- package/src/index.ts +21 -4
- package/src/multiTenancy/index.ts +92 -5
- package/src/resolvers/ActionResolver.ts +15 -7
- package/src/resolvers/MCPResolver.ts +13 -15
- package/src/resolvers/RealtimeClientSessionResolver.ts +15 -13
- package/src/resolvers/RemoteBrowserActionResolver.ts +19 -25
- package/src/resolvers/RunAIAgentResolver.ts +30 -40
- package/src/resolvers/RunTestResolver.ts +90 -112
- package/src/resolvers/SearchKnowledgeResolver.ts +3 -3
- package/src/resolvers/SearchKnowledgeStreamResolver.ts +2 -2
- package/src/resolvers/TaskGraphFrameResolver.ts +160 -0
- package/src/resolvers/VersionHistoryResolver.ts +10 -13
- package/src/rest/RESTEndpointHandler.ts +1 -15
- package/src/services/StartTaskGraphDispatcher.ts +61 -0
- package/src/services/TaskGraphAgentRunner.ts +81 -0
- package/src/services/TaskGraphContinuationDeliverer.ts +181 -0
- package/src/services/TaskGraphProviderFactory.ts +53 -0
- package/dist/resolvers/ReportResolver.d.ts +0 -24
- package/dist/resolvers/ReportResolver.d.ts.map +0 -1
- package/dist/resolvers/ReportResolver.js +0 -190
- package/dist/resolvers/ReportResolver.js.map +0 -1
- package/dist/resolvers/TaskResolver.d.ts +0 -31
- package/dist/resolvers/TaskResolver.d.ts.map +0 -1
- package/dist/resolvers/TaskResolver.js +0 -157
- package/dist/resolvers/TaskResolver.js.map +0 -1
- package/dist/services/TaskOrchestrator.d.ts +0 -130
- package/dist/services/TaskOrchestrator.d.ts.map +0 -1
- package/dist/services/TaskOrchestrator.js +0 -690
- package/dist/services/TaskOrchestrator.js.map +0 -1
- package/src/resolvers/ReportResolver.ts +0 -157
- package/src/resolvers/TaskResolver.ts +0 -148
- package/src/services/TaskOrchestrator.ts +0 -873
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Live signal for durable task graphs — the dispatcher's lifecycle frames, published
|
|
3
|
+
* to anyone watching a given graph.
|
|
4
|
+
*
|
|
5
|
+
* **Why frames rather than cache invalidation.** The alternative considered was emitting
|
|
6
|
+
* invalidation events after each CAS claim so clients re-read Task rows. That works, but it makes
|
|
7
|
+
* every consumer reconstruct meaning by diffing rows, and couples the UI to claim mechanics. A
|
|
8
|
+
* frame says what happened — "this step failed, here is why" — so a viewer renders progress
|
|
9
|
+
* directly. This is §3.4's originally-promised mechanism.
|
|
10
|
+
*
|
|
11
|
+
* **Addressed by `ParentTaskID`, deliberately not by session.** A durable graph outlives the tab
|
|
12
|
+
* that submitted it, and may be started by a schedule with no session at all. Keying on the graph
|
|
13
|
+
* means "watch this workflow run" works for whoever is permitted to see it, whenever they arrive —
|
|
14
|
+
* including after a page refresh, which a session-keyed push cannot survive.
|
|
15
|
+
*
|
|
16
|
+
* @module @memberjunction/server
|
|
17
|
+
*/
|
|
18
|
+
import { Arg, Field, ID, ObjectType, PubSubEngine, Resolver, ResolverFilterData, Root, Subscription } from 'type-graphql';
|
|
19
|
+
import { LogError } from '@memberjunction/core';
|
|
20
|
+
import { UUIDsEqual } from '@memberjunction/global';
|
|
21
|
+
import type { TaskGraphFrame, TaskGraphObserver } from '@memberjunction/task-graph';
|
|
22
|
+
|
|
23
|
+
export const TASK_GRAPH_FRAMES_TOPIC = 'TASK_GRAPH_FRAMES';
|
|
24
|
+
|
|
25
|
+
/** One lifecycle event, as it reaches a subscriber. */
|
|
26
|
+
@ObjectType()
|
|
27
|
+
export class TaskGraphFrameNotification {
|
|
28
|
+
@Field(() => String)
|
|
29
|
+
kind!: string;
|
|
30
|
+
|
|
31
|
+
@Field(() => ID)
|
|
32
|
+
parentTaskId!: string;
|
|
33
|
+
|
|
34
|
+
@Field(() => ID, { nullable: true })
|
|
35
|
+
taskId?: string;
|
|
36
|
+
|
|
37
|
+
@Field(() => String, { nullable: true })
|
|
38
|
+
taskName?: string;
|
|
39
|
+
|
|
40
|
+
@Field(() => String, { nullable: true })
|
|
41
|
+
status?: string;
|
|
42
|
+
|
|
43
|
+
@Field(() => String, { nullable: true })
|
|
44
|
+
errorMessage?: string;
|
|
45
|
+
|
|
46
|
+
@Field(() => ID, { nullable: true })
|
|
47
|
+
assignedUserId?: string;
|
|
48
|
+
|
|
49
|
+
@Field(() => Number, { nullable: true })
|
|
50
|
+
completedCount?: number;
|
|
51
|
+
|
|
52
|
+
@Field(() => Number, { nullable: true })
|
|
53
|
+
totalCount?: number;
|
|
54
|
+
|
|
55
|
+
@Field(() => Date)
|
|
56
|
+
date!: Date;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Wire payload. Identical to {@link TaskGraphFrame} plus the graph's owner, which exists ONLY as the
|
|
61
|
+
* server-side filter key and is never returned to a subscriber — the same discipline
|
|
62
|
+
* `PushStatusNotificationPayload` uses for `ownerUserId`.
|
|
63
|
+
*/
|
|
64
|
+
export interface TaskGraphFramePayload extends TaskGraphFrame {
|
|
65
|
+
/** User the graph belongs to. Filter key; never sent to the client. */
|
|
66
|
+
ownerUserId: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface TaskGraphFrameArgs {
|
|
70
|
+
parentTaskId: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Minimal shape of the subscription's connection context needed by the filter. */
|
|
74
|
+
export interface TaskGraphFrameFilterContext {
|
|
75
|
+
userPayload?: { userRecord?: { ID?: string } };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Delivery predicate. A frame reaches a subscriber only when BOTH hold:
|
|
80
|
+
* 1. it belongs to the graph the subscriber asked to watch, AND
|
|
81
|
+
* 2. the graph's owner matches the subscriber CONNECTION's authenticated identity.
|
|
82
|
+
*
|
|
83
|
+
* Condition 2 is load-bearing, and for the same reason `statusUpdatesFilter` needs it: a
|
|
84
|
+
* `parentTaskId` is discoverable, so without an identity check anyone holding one could watch
|
|
85
|
+
* another user's workflow — including its per-step error messages. **Fails closed** — a missing
|
|
86
|
+
* identity on either side never matches.
|
|
87
|
+
*/
|
|
88
|
+
export function taskGraphFrameFilter(data: {
|
|
89
|
+
payload: TaskGraphFramePayload;
|
|
90
|
+
args: TaskGraphFrameArgs;
|
|
91
|
+
context: TaskGraphFrameFilterContext | undefined;
|
|
92
|
+
}): boolean {
|
|
93
|
+
const { payload, args, context } = data;
|
|
94
|
+
if (!payload.ParentTaskID || !args.parentTaskId) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (!UUIDsEqual(payload.ParentTaskID, args.parentTaskId)) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
const connectionUserId = context?.userPayload?.userRecord?.ID;
|
|
101
|
+
if (!connectionUserId || !payload.ownerUserId) {
|
|
102
|
+
return false; // fail closed
|
|
103
|
+
}
|
|
104
|
+
return UUIDsEqual(payload.ownerUserId, connectionUserId);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* MJServer's `TaskGraphObserver` — turns dispatcher frames into a PubSub publish.
|
|
109
|
+
*
|
|
110
|
+
* `OnFrame` is synchronous and swallows everything by contract: a frame is commentary on work, never
|
|
111
|
+
* a step of it, so nothing here may stall or fail a graph. The publish itself is fire-and-forget for
|
|
112
|
+
* the same reason.
|
|
113
|
+
*/
|
|
114
|
+
export class TaskGraphFrameBroadcaster implements TaskGraphObserver {
|
|
115
|
+
constructor(private readonly pubSub: PubSubEngine) {}
|
|
116
|
+
|
|
117
|
+
public OnFrame(frame: TaskGraphFrame): void {
|
|
118
|
+
try {
|
|
119
|
+
if (!frame.OwnerUserID) {
|
|
120
|
+
// A graph with no recorded owner cannot be authorized to anyone, so publishing would only
|
|
121
|
+
// produce frames the filter refuses. Dropping is the same outcome, without the traffic.
|
|
122
|
+
// Reachable for graphs submitted before ownership was recorded on the parent.
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
void this.pubSub.publish(
|
|
126
|
+
TASK_GRAPH_FRAMES_TOPIC,
|
|
127
|
+
{ ...frame, ownerUserId: frame.OwnerUserID } satisfies TaskGraphFramePayload,
|
|
128
|
+
);
|
|
129
|
+
} catch (e) {
|
|
130
|
+
LogError(`[TaskGraphFrameBroadcaster] Could not publish ${frame.Kind} (ignored): ${e instanceof Error ? e.message : String(e)}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@Resolver()
|
|
136
|
+
export class TaskGraphFrameResolver {
|
|
137
|
+
@Subscription(() => TaskGraphFrameNotification, {
|
|
138
|
+
topics: TASK_GRAPH_FRAMES_TOPIC,
|
|
139
|
+
filter: (data: ResolverFilterData<TaskGraphFramePayload, TaskGraphFrameArgs, TaskGraphFrameFilterContext>) =>
|
|
140
|
+
taskGraphFrameFilter(data),
|
|
141
|
+
})
|
|
142
|
+
taskGraphFrames(
|
|
143
|
+
@Root() payload: TaskGraphFramePayload,
|
|
144
|
+
@Arg('parentTaskId', () => ID) parentTaskId: string
|
|
145
|
+
): TaskGraphFrameNotification {
|
|
146
|
+
// ownerUserId is intentionally NOT returned — it is a server-side filter key only.
|
|
147
|
+
return {
|
|
148
|
+
kind: payload.Kind,
|
|
149
|
+
parentTaskId,
|
|
150
|
+
taskId: payload.TaskID,
|
|
151
|
+
taskName: payload.TaskName,
|
|
152
|
+
status: payload.Status,
|
|
153
|
+
errorMessage: payload.ErrorMessage,
|
|
154
|
+
assignedUserId: payload.AssignedUserID,
|
|
155
|
+
completedCount: payload.CompletedCount,
|
|
156
|
+
totalCount: payload.TotalCount,
|
|
157
|
+
date: new Date(),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Resolver, Mutation, Arg, Ctx, ObjectType, Field, Int, InputType, PubSub, PubSubEngine } from 'type-graphql';
|
|
2
|
-
import { AppContext } from '../types.js';
|
|
2
|
+
import { AppContext, UserPayload } from '../types.js';
|
|
3
3
|
import { LogError, LogStatus, CompositeKey, KeyValuePair } from '@memberjunction/core';
|
|
4
4
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
5
5
|
import { VersionHistoryEngine } from '@memberjunction/version-history';
|
|
6
6
|
import { CreateLabelParams, CreateLabelProgressUpdate, VersionLabelScope } from '@memberjunction/version-history';
|
|
7
7
|
import { KeyValuePairInput } from '../generic/KeyValuePairInput.js';
|
|
8
|
-
import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
|
|
9
8
|
|
|
10
9
|
// =========================================================================
|
|
11
10
|
// GraphQL types
|
|
@@ -115,7 +114,7 @@ export class VersionHistoryResolver extends ResolverBase {
|
|
|
115
114
|
|
|
116
115
|
// Build progress callback that publishes to PubSub
|
|
117
116
|
const resolvedSessionId = sessionId ?? context.userPayload.sessionId;
|
|
118
|
-
const onProgress = this.buildProgressCallback(pubSub, resolvedSessionId);
|
|
117
|
+
const onProgress = this.buildProgressCallback(pubSub, resolvedSessionId, context.userPayload);
|
|
119
118
|
|
|
120
119
|
const params: CreateLabelParams = {
|
|
121
120
|
Name: input.Name,
|
|
@@ -160,18 +159,16 @@ export class VersionHistoryResolver extends ResolverBase {
|
|
|
160
159
|
*/
|
|
161
160
|
private buildProgressCallback(
|
|
162
161
|
pubSub: PubSubEngine,
|
|
163
|
-
sessionId: string
|
|
162
|
+
sessionId: string,
|
|
163
|
+
userPayload: UserPayload
|
|
164
164
|
): (progress: CreateLabelProgressUpdate) => void {
|
|
165
165
|
return (progress: CreateLabelProgressUpdate) => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}),
|
|
173
|
-
sessionId,
|
|
174
|
-
});
|
|
166
|
+
this.PublishStatusUpdate(pubSub, sessionId, JSON.stringify({
|
|
167
|
+
resolver: 'VersionHistoryResolver',
|
|
168
|
+
type: 'CreateLabelProgress',
|
|
169
|
+
status: 'ok',
|
|
170
|
+
data: progress,
|
|
171
|
+
}), userPayload);
|
|
175
172
|
};
|
|
176
173
|
}
|
|
177
174
|
}
|
|
@@ -189,8 +189,7 @@ export class RESTEndpointHandler {
|
|
|
189
189
|
// Transaction operations
|
|
190
190
|
this.router.post('/transactions', this.executeTransaction.bind(this));
|
|
191
191
|
|
|
192
|
-
//
|
|
193
|
-
this.router.get('/reports/:reportId', this.runReport.bind(this));
|
|
192
|
+
// Queries
|
|
194
193
|
this.router.post('/queries/run', this.runQuery.bind(this));
|
|
195
194
|
|
|
196
195
|
// Error handling
|
|
@@ -781,19 +780,6 @@ export class RESTEndpointHandler {
|
|
|
781
780
|
}
|
|
782
781
|
}
|
|
783
782
|
|
|
784
|
-
/**
|
|
785
|
-
* Run a report
|
|
786
|
-
*/
|
|
787
|
-
private async runReport(req: express.Request, res: express.Response): Promise<void> {
|
|
788
|
-
try {
|
|
789
|
-
// Placeholder implementation - this would need to be implemented to run reports
|
|
790
|
-
res.status(501).json({ error: 'Not implemented' });
|
|
791
|
-
} catch (error) {
|
|
792
|
-
LogError(error);
|
|
793
|
-
res.status(500).json({ error: (error as Error)?.message || 'Unknown error' });
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
783
|
/**
|
|
798
784
|
* Run a query
|
|
799
785
|
*/
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Boots the durable task-graph dispatcher inside MJServer.
|
|
3
|
+
*
|
|
4
|
+
* Phase 2 landed the dispatcher and the submission path but never started anything, so a submitted
|
|
5
|
+
* graph persisted correctly and then sat in `Pending` forever. Submission being durable is only
|
|
6
|
+
* half the promise — something has to *pick the work up*.
|
|
7
|
+
*
|
|
8
|
+
* The continuation deliverer is supplied here for the same reason the agent runner is: posting into
|
|
9
|
+
* a conversation is a host concern the package deliberately refuses to know about.
|
|
10
|
+
*
|
|
11
|
+
* Registration of the package's Remote Operations is a separate concern handled by the generated
|
|
12
|
+
* class-registration manifests (`ServerBootstrap` / `ServerBootstrapLite`), which import the
|
|
13
|
+
* subclasses so their `@RegisterClass` decorators run. `LoadTaskGraphOperations` is called here
|
|
14
|
+
* anyway as a cheap guard: a host that starts a dispatcher but never loaded a manifest would accept
|
|
15
|
+
* submissions that route to the CodeGen contract-only base and silently do nothing.
|
|
16
|
+
*
|
|
17
|
+
* @module @memberjunction/server
|
|
18
|
+
*/
|
|
19
|
+
import { UserInfo } from '@memberjunction/core';
|
|
20
|
+
import { LoadTaskGraphOperations, TaskGraphDispatcher } from '@memberjunction/task-graph';
|
|
21
|
+
import sql from 'mssql';
|
|
22
|
+
import { CreateTaskGraphProviderFactory } from './TaskGraphProviderFactory.js';
|
|
23
|
+
import { TaskGraphAgentRunner } from './TaskGraphAgentRunner.js';
|
|
24
|
+
import { TaskGraphContinuationDeliverer } from './TaskGraphContinuationDeliverer.js';
|
|
25
|
+
import { PubSubManager } from '../generic/PubSubManager.js';
|
|
26
|
+
import { TaskGraphFrameBroadcaster } from '../resolvers/TaskGraphFrameResolver.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Starts one dispatcher for this process and returns it.
|
|
30
|
+
*
|
|
31
|
+
* The instance ID must be stable for the process lifetime and distinct per instance — it is what
|
|
32
|
+
* lets reconciliation tell "my own orphaned work, reclaim it" from "another live instance's work,
|
|
33
|
+
* leave it alone". `pid` plus hostname satisfies both: stable while the process lives, and never
|
|
34
|
+
* reused by a peer.
|
|
35
|
+
*
|
|
36
|
+
* The dispatcher self-registers with `ShutdownRegistry`, so the caller does not wire teardown.
|
|
37
|
+
*/
|
|
38
|
+
export async function StartTaskGraphDispatcher(
|
|
39
|
+
pool: sql.ConnectionPool,
|
|
40
|
+
contextUser: UserInfo,
|
|
41
|
+
instanceID?: string,
|
|
42
|
+
): Promise<TaskGraphDispatcher> {
|
|
43
|
+
LoadTaskGraphOperations();
|
|
44
|
+
|
|
45
|
+
const providerFactory = CreateTaskGraphProviderFactory(pool);
|
|
46
|
+
const pubSub = PubSubManager.Instance.PubSubEngine;
|
|
47
|
+
const dispatcher = new TaskGraphDispatcher(
|
|
48
|
+
providerFactory,
|
|
49
|
+
new TaskGraphAgentRunner(),
|
|
50
|
+
contextUser,
|
|
51
|
+
{ InstanceID: instanceID ?? `${process.env.HOSTNAME ?? 'mjapi'}-${process.pid}` },
|
|
52
|
+
// Without this the dispatcher had nowhere to deliver: a finished graph logged its outcome,
|
|
53
|
+
// marked itself delivered, and said nothing to the conversation that asked for it.
|
|
54
|
+
new TaskGraphContinuationDeliverer(providerFactory, contextUser),
|
|
55
|
+
// Live signal. Supplied only when a PubSub engine exists — in a worker or a test there is
|
|
56
|
+
// nobody to publish to, and the dispatcher runs identically without one.
|
|
57
|
+
pubSub ? new TaskGraphFrameBroadcaster(pubSub) : undefined,
|
|
58
|
+
);
|
|
59
|
+
await dispatcher.Start();
|
|
60
|
+
return dispatcher;
|
|
61
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MJServer's implementation of the task-graph `TaskAgentRunner` seam.
|
|
3
|
+
*
|
|
4
|
+
* The dispatcher knows *when* a task should run and *what* it depends on, but nothing about how an
|
|
5
|
+
* agent executes. That is injected, so the package stays testable without standing up the agent
|
|
6
|
+
* framework and so a host with a different execution strategy (a queue, a remote worker) can
|
|
7
|
+
* substitute its own.
|
|
8
|
+
*
|
|
9
|
+
* This adapter is the thin translation between the two: it turns a task's input payload and its
|
|
10
|
+
* dependencies' outputs into agent messages, runs the agent, and maps the result back into the
|
|
11
|
+
* shape the dispatcher persists.
|
|
12
|
+
*
|
|
13
|
+
* @module @memberjunction/server
|
|
14
|
+
*/
|
|
15
|
+
import { AgentRunner } from '@memberjunction/ai-agents';
|
|
16
|
+
import { ChatMessageRole } from '@memberjunction/ai';
|
|
17
|
+
import { LogError } from '@memberjunction/core';
|
|
18
|
+
import { MJAIAgentEntityExtended } from '@memberjunction/ai-core-plus';
|
|
19
|
+
import type { TaskAgentRunner, TaskAgentRunParams, TaskAgentRunResult } from '@memberjunction/task-graph';
|
|
20
|
+
|
|
21
|
+
export class TaskGraphAgentRunner implements TaskAgentRunner {
|
|
22
|
+
public async RunAgentForTask(params: TaskAgentRunParams): Promise<TaskAgentRunResult> {
|
|
23
|
+
try {
|
|
24
|
+
const agent = await params.Provider.GetEntityObject<MJAIAgentEntityExtended>('MJ: AI Agents', params.ContextUser);
|
|
25
|
+
if (!(await agent.Load(params.AgentID))) {
|
|
26
|
+
return { Success: false, ErrorMessage: `Agent ${params.AgentID} could not be loaded.` };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const result = await new AgentRunner().RunAgent({
|
|
30
|
+
agent,
|
|
31
|
+
conversationMessages: [{ role: ChatMessageRole.user, content: this.buildPrompt(params) }],
|
|
32
|
+
contextUser: params.ContextUser,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const success = result?.success === true;
|
|
36
|
+
return {
|
|
37
|
+
Success: success,
|
|
38
|
+
Output: this.extractOutput(result),
|
|
39
|
+
ErrorMessage: success ? undefined : (result?.agentRun?.ErrorMessage ?? 'Agent execution failed'),
|
|
40
|
+
AgentRunID: result?.agentRun?.ID,
|
|
41
|
+
};
|
|
42
|
+
} catch (e) {
|
|
43
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
44
|
+
LogError(`[TaskGraphAgentRunner] Task ${params.TaskID} failed: ${message}`);
|
|
45
|
+
return { Success: false, ErrorMessage: message };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Builds the agent's instruction from the task's own input plus its dependencies' outputs.
|
|
51
|
+
*
|
|
52
|
+
* Dependency outputs are included inline because a task's whole reason for depending on another
|
|
53
|
+
* is to consume what it produced. They are rendered as labelled JSON rather than prose so the
|
|
54
|
+
* agent can parse them reliably rather than inferring structure from formatting.
|
|
55
|
+
*/
|
|
56
|
+
private buildPrompt(params: TaskAgentRunParams): string {
|
|
57
|
+
const sections: string[] = [];
|
|
58
|
+
|
|
59
|
+
if (params.InputPayload != null) {
|
|
60
|
+
sections.push(`## Task input\n\`\`\`json\n${JSON.stringify(params.InputPayload, null, 2)}\n\`\`\``);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (params.DependencyOutputs.size > 0) {
|
|
64
|
+
const rendered = [...params.DependencyOutputs.entries()]
|
|
65
|
+
.map(([taskID, output]) => `### Output of task ${taskID}\n\`\`\`json\n${JSON.stringify(output, null, 2)}\n\`\`\``)
|
|
66
|
+
.join('\n\n');
|
|
67
|
+
sections.push(`## Results from prerequisite tasks\n\n${rendered}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return sections.length > 0
|
|
71
|
+
? sections.join('\n\n')
|
|
72
|
+
: 'Execute this task.';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Prefers a structured payload over prose, so downstream tasks get data rather than text. */
|
|
76
|
+
private extractOutput(result: unknown): unknown {
|
|
77
|
+
const r = result as { payload?: unknown; agentRun?: { Message?: string } } | null;
|
|
78
|
+
if (r?.payload != null) return r.payload;
|
|
79
|
+
return r?.agentRun?.Message ?? null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MJServer's implementation of the task-graph `TaskContinuationDeliverer` seam.
|
|
3
|
+
*
|
|
4
|
+
* **The dispatcher shipped with nowhere to deliver.** `StartTaskGraphDispatcher` constructed it
|
|
5
|
+
* without a deliverer at all, so a graph that finished logged its outcome, marked itself delivered,
|
|
6
|
+
* and said nothing to the conversation that asked for it. Durable execution nobody hears about is
|
|
7
|
+
* half a promise — the same shape as Phase 2's dispatcher that ran nothing.
|
|
8
|
+
*
|
|
9
|
+
* Posting into a conversation is a host concern by design: the task-graph package must not depend on
|
|
10
|
+
* the conversation layer, and cannot depend on the agent framework at all without a cycle (agents
|
|
11
|
+
* submit graphs). This adapter is where those dependencies are allowed to live.
|
|
12
|
+
*
|
|
13
|
+
* @module @memberjunction/server
|
|
14
|
+
*/
|
|
15
|
+
import { LogError, LogStatus, type IMetadataProvider, type UserInfo } from '@memberjunction/core';
|
|
16
|
+
import { MJAIAgentRunEntity, MJConversationDetailEntity } from '@memberjunction/core-entities';
|
|
17
|
+
import { MJAIAgentEntityExtended } from '@memberjunction/ai-core-plus';
|
|
18
|
+
import { AgentRunner } from '@memberjunction/ai-agents';
|
|
19
|
+
import { ChatMessageRole } from '@memberjunction/ai';
|
|
20
|
+
import type { ProviderFactory, TaskContinuationDeliverer, TaskContinuationParams } from '@memberjunction/task-graph';
|
|
21
|
+
|
|
22
|
+
/** How many per-task lines a posted summary shows before collapsing the rest into a count. */
|
|
23
|
+
const MAX_LISTED_TASKS = 20;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Delivers a finished graph's outcome — as a conversation message, or by starting the submitting
|
|
27
|
+
* agent a fresh turn.
|
|
28
|
+
*
|
|
29
|
+
* **The chain is bounded, and that is what took a schema change to make true.** A graph may declare
|
|
30
|
+
* `continuation: 'reinvoke'`, and the agent it restarts can emit another graph, which can reinvoke
|
|
31
|
+
* again. The guard for that (`MAX_REINVOKE_DEPTH`) shipped in Phase 3, but the number it compared
|
|
32
|
+
* was permanently zero: `TaskGraphService.Submit` reads `ReinvokeDepth` from its caller, and a
|
|
33
|
+
* reinvoked agent had no way to know it *was* a continuation. `AIAgentRun.ContinuationDepth` closes
|
|
34
|
+
* that loop — this stamps depth + 1 on the run it starts, `BaseAgent` passes it into any graph that
|
|
35
|
+
* run submits, and the cap finally compares against something real.
|
|
36
|
+
*/
|
|
37
|
+
export class TaskGraphContinuationDeliverer implements TaskContinuationDeliverer {
|
|
38
|
+
/**
|
|
39
|
+
* @param providerFactory mints a fresh provider per delivery, for the same reason the dispatcher
|
|
40
|
+
* does: deliveries run outside any request and concurrently with task execution, so
|
|
41
|
+
* sharing one provider would share one transaction scope across unrelated work.
|
|
42
|
+
*/
|
|
43
|
+
constructor(
|
|
44
|
+
private readonly providerFactory: ProviderFactory,
|
|
45
|
+
private readonly contextUser: UserInfo,
|
|
46
|
+
) {}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Posts the roll-up as an AI-role message in the graph's conversation.
|
|
50
|
+
*
|
|
51
|
+
* Never throws. The dispatcher calls this inside the compare-and-swap that marks a completion
|
|
52
|
+
* delivered; an error escaping would either abort that guard or leave the graph looking
|
|
53
|
+
* undelivered and re-notifying on every later sweep.
|
|
54
|
+
*/
|
|
55
|
+
public async PostMessage(params: TaskContinuationParams): Promise<void> {
|
|
56
|
+
try {
|
|
57
|
+
if (!params.ConversationDetailID) {
|
|
58
|
+
// A graph submitted headlessly — a schedule, an entity-change trigger, an API call —
|
|
59
|
+
// has no conversation to answer. Not an error; most workflows are in this shape.
|
|
60
|
+
LogStatus(`[TaskGraphContinuationDeliverer] "${params.WorkflowName}" finished with no conversation to post to.`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const provider = await this.providerFactory.CreateProvider();
|
|
65
|
+
const conversationID = await this.resolveConversationID(params.ConversationDetailID, provider);
|
|
66
|
+
if (!conversationID) {
|
|
67
|
+
LogError(
|
|
68
|
+
`[TaskGraphContinuationDeliverer] Conversation detail ${params.ConversationDetailID} could not be loaded — ` +
|
|
69
|
+
`"${params.WorkflowName}" has nowhere to post its outcome.`
|
|
70
|
+
);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const detail = await provider.GetEntityObject<MJConversationDetailEntity>('MJ: Conversation Details', this.contextUser);
|
|
75
|
+
detail.NewRecord();
|
|
76
|
+
detail.ConversationID = conversationID;
|
|
77
|
+
detail.Role = 'AI';
|
|
78
|
+
detail.Status = 'Complete';
|
|
79
|
+
detail.HiddenToUser = false;
|
|
80
|
+
detail.Message = this.renderMessage(params);
|
|
81
|
+
|
|
82
|
+
if (!(await detail.Save())) {
|
|
83
|
+
LogError(
|
|
84
|
+
`[TaskGraphContinuationDeliverer] Could not post the outcome of "${params.WorkflowName}": ` +
|
|
85
|
+
`${detail.LatestResult?.CompleteMessage ?? 'unknown error'}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
LogError(`[TaskGraphContinuationDeliverer] Posting the outcome of "${params.WorkflowName}" threw`, undefined, e);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Starts the submitting agent a fresh turn carrying the graph's outcome.
|
|
95
|
+
*
|
|
96
|
+
* Never throws, for the same reason `PostMessage` does not: the dispatcher calls this inside the
|
|
97
|
+
* compare-and-swap that marks a completion delivered.
|
|
98
|
+
*
|
|
99
|
+
* The new run records `ReinvokeDepth + 1` so the chain is bounded. The dispatcher has already
|
|
100
|
+
* refused to call this when the cap is reached — it degrades to `PostMessage` — so arriving here
|
|
101
|
+
* means there is budget left, and stamping the depth is what keeps that true for the next hop.
|
|
102
|
+
*/
|
|
103
|
+
public async Reinvoke(params: TaskContinuationParams): Promise<void> {
|
|
104
|
+
try {
|
|
105
|
+
if (!params.SubmittedByAgentRunID) {
|
|
106
|
+
// Nothing to restart. A graph with no submitting run came from a schedule or a
|
|
107
|
+
// trigger, where "continue the conversation" has no meaning.
|
|
108
|
+
LogStatus(`[TaskGraphContinuationDeliverer] "${params.WorkflowName}" asked to reinvoke but records no submitting agent run — posting instead.`);
|
|
109
|
+
await this.PostMessage(params);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const provider = await this.providerFactory.CreateProvider();
|
|
114
|
+
const priorRun = await provider.GetEntityObject<MJAIAgentRunEntity>('MJ: AI Agent Runs', this.contextUser);
|
|
115
|
+
if (!(await priorRun.Load(params.SubmittedByAgentRunID))) {
|
|
116
|
+
LogError(`[TaskGraphContinuationDeliverer] Submitting run ${params.SubmittedByAgentRunID} could not be loaded — posting "${params.WorkflowName}" instead.`);
|
|
117
|
+
await this.PostMessage(params);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const agent = await provider.GetEntityObject<MJAIAgentEntityExtended>('MJ: AI Agents', this.contextUser);
|
|
122
|
+
if (!(await agent.Load(priorRun.AgentID))) {
|
|
123
|
+
LogError(`[TaskGraphContinuationDeliverer] Agent ${priorRun.AgentID} could not be loaded — posting "${params.WorkflowName}" instead.`);
|
|
124
|
+
await this.PostMessage(params);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await new AgentRunner().RunAgent({
|
|
129
|
+
agent,
|
|
130
|
+
conversationMessages: [{ role: ChatMessageRole.user, content: this.renderMessage(params) }],
|
|
131
|
+
contextUser: this.contextUser,
|
|
132
|
+
conversationDetailId: params.ConversationDetailID ?? undefined,
|
|
133
|
+
// The load-bearing value: without it the next graph this run submits restarts the
|
|
134
|
+
// chain at zero and the cap never fires.
|
|
135
|
+
continuationDepth: params.ReinvokeDepth + 1,
|
|
136
|
+
});
|
|
137
|
+
} catch (e) {
|
|
138
|
+
LogError(`[TaskGraphContinuationDeliverer] Reinvoking for "${params.WorkflowName}" threw`, undefined, e);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The conversation a detail belongs to. */
|
|
143
|
+
private async resolveConversationID(conversationDetailID: string, provider: IMetadataProvider): Promise<string | null> {
|
|
144
|
+
const detail = await provider.GetEntityObject<MJConversationDetailEntity>('MJ: Conversation Details', this.contextUser);
|
|
145
|
+
return (await detail.Load(conversationDetailID)) ? detail.ConversationID : null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Renders the outcome as Markdown.
|
|
150
|
+
*
|
|
151
|
+
* Per-task lines rather than one aggregate status: a graph where nine of ten steps succeeded is a
|
|
152
|
+
* materially different message from one that failed outright, and the roll-up alone cannot say
|
|
153
|
+
* which step went wrong. Long graphs truncate so a fifty-step workflow does not bury the
|
|
154
|
+
* conversation — the task rows remain the complete record.
|
|
155
|
+
*/
|
|
156
|
+
private renderMessage(params: TaskContinuationParams): string {
|
|
157
|
+
const lines = [`**${params.WorkflowName}** finished.`, '', params.Summary];
|
|
158
|
+
|
|
159
|
+
if (params.Tasks.length > 0) {
|
|
160
|
+
lines.push('');
|
|
161
|
+
for (const task of params.Tasks.slice(0, MAX_LISTED_TASKS)) {
|
|
162
|
+
const detail = task.ErrorMessage ?? task.Summary;
|
|
163
|
+
lines.push(`- ${this.statusIcon(task.Status)} **${task.Name}** — ${task.Status}${detail ? `: ${detail}` : ''}`);
|
|
164
|
+
}
|
|
165
|
+
if (params.Tasks.length > MAX_LISTED_TASKS) {
|
|
166
|
+
lines.push(`- …and ${params.Tasks.length - MAX_LISTED_TASKS} more`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return lines.join('\n');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private statusIcon(status: string): string {
|
|
174
|
+
switch (status) {
|
|
175
|
+
case 'Complete': return '✅';
|
|
176
|
+
case 'Failed': return '❌';
|
|
177
|
+
case 'Cancelled': return '⊘';
|
|
178
|
+
default: return '•';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MJServer's implementation of the task-graph `ProviderFactory` seam.
|
|
3
|
+
*
|
|
4
|
+
* The durable dispatcher runs outside any HTTP request and executes tasks concurrently, so it must
|
|
5
|
+
* never share one provider — and therefore one transaction scope and one set of entity instances —
|
|
6
|
+
* across parallel work. It needs a way to mint providers on demand.
|
|
7
|
+
*
|
|
8
|
+
* Rather than have `@memberjunction/task-graph` import MJServer to get at that machinery (which
|
|
9
|
+
* would invert the dependency: MJServer depends on task-graph, not the reverse), the package
|
|
10
|
+
* declares a `ProviderFactory` interface and the host supplies an implementation. This is that
|
|
11
|
+
* implementation.
|
|
12
|
+
*
|
|
13
|
+
* It mints exactly what the per-request path already mints — a fresh provider over the **shared
|
|
14
|
+
* connection pool**, with metadata reuse — which is proven cheap at request scale and is why the
|
|
15
|
+
* plan chose this mechanism rather than inventing a dispatcher-specific one. The pool remains the
|
|
16
|
+
* real concurrency governor; the dispatcher's own `MaxConcurrentTasks` sits below it.
|
|
17
|
+
*
|
|
18
|
+
* @module @memberjunction/server
|
|
19
|
+
*/
|
|
20
|
+
import { DatabaseProviderBase, IMetadataProvider } from '@memberjunction/core';
|
|
21
|
+
import type { ProviderFactory } from '@memberjunction/task-graph';
|
|
22
|
+
import { SQLServerDataProvider, SQLServerProviderConfigData } from '@memberjunction/sqlserver-dataprovider';
|
|
23
|
+
import sql from 'mssql';
|
|
24
|
+
import { mj_core_schema } from '../config.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates providers for the dispatcher, one per task execution.
|
|
28
|
+
*
|
|
29
|
+
* SQL Server path only for now, matching where the dispatcher is hosted. The PostgreSQL branch of
|
|
30
|
+
* `createPerRequestProviders` is deliberately not duplicated here — when PG hosting is needed, the
|
|
31
|
+
* right move is to lift the shared branch out of `context.ts` rather than fork it a second time.
|
|
32
|
+
*/
|
|
33
|
+
export class TaskGraphProviderFactory implements ProviderFactory {
|
|
34
|
+
constructor(private readonly pool: sql.ConnectionPool) {}
|
|
35
|
+
|
|
36
|
+
public async CreateProvider(): Promise<IMetadataProvider> {
|
|
37
|
+
// `loadIfNeeded = false` (the trailing false) reuses already-loaded metadata rather than
|
|
38
|
+
// re-reading it per provider — the difference between "cheap enough to do per task" and
|
|
39
|
+
// "prohibitively expensive".
|
|
40
|
+
const config = new SQLServerProviderConfigData(this.pool, mj_core_schema, 0, undefined, undefined, false);
|
|
41
|
+
const provider = new SQLServerDataProvider();
|
|
42
|
+
await provider.Config(config);
|
|
43
|
+
return provider as unknown as IMetadataProvider;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Convenience for hosts that already hold a pool. */
|
|
48
|
+
export function CreateTaskGraphProviderFactory(pool: sql.ConnectionPool): ProviderFactory {
|
|
49
|
+
return new TaskGraphProviderFactory(pool);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Re-exported for hosts that need the concrete type. */
|
|
53
|
+
export type { DatabaseProviderBase };
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { AppContext } from '../types.js';
|
|
2
|
-
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
3
|
-
export declare class RunReportResultType {
|
|
4
|
-
ReportID: string;
|
|
5
|
-
Success: boolean;
|
|
6
|
-
Results: string;
|
|
7
|
-
RowCount: number;
|
|
8
|
-
ExecutionTime: number;
|
|
9
|
-
ErrorMessage: string;
|
|
10
|
-
}
|
|
11
|
-
export declare class CreateReportResultType {
|
|
12
|
-
ReportID: string;
|
|
13
|
-
ReportName: string;
|
|
14
|
-
Success: boolean;
|
|
15
|
-
ErrorMessage: string;
|
|
16
|
-
}
|
|
17
|
-
export declare class ReportResolverExtended extends ResolverBase {
|
|
18
|
-
GetReportData(ReportID: string, context: AppContext): Promise<RunReportResultType>;
|
|
19
|
-
/**
|
|
20
|
-
* This mutation will create a new report from a conversation detail ID
|
|
21
|
-
*/
|
|
22
|
-
CreateReportFromConversationDetailID(ConversationDetailID: string, { dataSource, userPayload, providers }: AppContext): Promise<CreateReportResultType>;
|
|
23
|
-
}
|
|
24
|
-
//# sourceMappingURL=ReportResolver.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ReportResolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/ReportResolver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAOzC,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE1D,qBACa,mBAAmB;IAE9B,QAAQ,EAAE,MAAM,CAAC;IAGjB,OAAO,EAAE,OAAO,CAAC;IAGjB,OAAO,EAAE,MAAM,CAAC;IAGhB,QAAQ,EAAE,MAAM,CAAC;IAGjB,aAAa,EAAE,MAAM,CAAC;IAGtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBACa,sBAAsB;IAEjC,QAAQ,EAAE,MAAM,CAAC;IAGjB,UAAU,EAAE,MAAM,CAAC;IAGnB,OAAO,EAAE,OAAO,CAAC;IAGjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBACa,sBAAuB,SAAQ,YAAY;IAEhD,aAAa,CAAgC,QAAQ,EAAE,MAAM,EAAS,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAkB9H;;OAEG;IAEG,oCAAoC,CACG,oBAAoB,EAAE,MAAM,EAChE,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,UAAU,GACxD,OAAO,CAAC,sBAAsB,CAAC;CAiFnC"}
|