@memberjunction/server 6.1.4 → 6.1.6
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 +30 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/SessionManager.d.ts +5 -2
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +17 -8
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
- package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
- package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
- package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
- package/dist/agentSessions/index.d.ts +1 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +1 -0
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/auth/actionAuthorization.d.ts +40 -0
- package/dist/auth/actionAuthorization.d.ts.map +1 -0
- package/dist/auth/actionAuthorization.js +94 -0
- package/dist/auth/actionAuthorization.js.map +1 -0
- package/dist/auth/exampleNewUserSubClass.js +2 -1
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts +17 -15
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +55 -38
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.d.ts +14 -33
- package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.js +20 -52
- package/dist/auth/magicLink/magicLinkCore.js.map +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +15 -11
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/context.d.ts +6 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +24 -34
- package/dist/context.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +3417 -3428
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +44 -8
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +59 -19
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +95 -11
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +186 -76
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +12 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -45
- package/dist/index.js.map +1 -1
- package/dist/isolatedProvider.d.ts +20 -0
- package/dist/isolatedProvider.d.ts.map +1 -0
- package/dist/isolatedProvider.js +22 -0
- package/dist/isolatedProvider.js.map +1 -0
- package/dist/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +22 -2
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/postgresqlCompat.d.ts +8 -0
- package/dist/postgresqlCompat.d.ts.map +1 -0
- package/dist/postgresqlCompat.js +19 -0
- package/dist/postgresqlCompat.js.map +1 -0
- package/dist/postgresqlPoolSettings.d.ts +108 -0
- package/dist/postgresqlPoolSettings.d.ts.map +1 -0
- package/dist/postgresqlPoolSettings.js +134 -0
- package/dist/postgresqlPoolSettings.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +4 -2
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +4 -2
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +31 -5
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +75 -14
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +35 -17
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +92 -166
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/DatasetResolver.d.ts +24 -3
- package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
- package/dist/resolvers/DatasetResolver.js +57 -8
- package/dist/resolvers/DatasetResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +8 -23
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +11 -48
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +2 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +117 -38
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +13 -1
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +2 -1
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +83 -22
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +174 -72
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +2 -1
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +12 -11
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.d.ts +1 -0
- package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.js +5 -0
- package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.js +1 -0
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +6 -2
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/VectorizeEntityResolver.d.ts +1 -1
- package/dist/resolvers/VectorizeEntityResolver.d.ts.map +1 -1
- package/dist/resolvers/VectorizeEntityResolver.js +14 -10
- package/dist/resolvers/VectorizeEntityResolver.js.map +1 -1
- package/dist/resolvers/adhocTimeout.d.ts +7 -0
- package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
- package/dist/resolvers/adhocTimeout.js +12 -0
- package/dist/resolvers/adhocTimeout.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts +3 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +29 -17
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +21 -8
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +43 -16
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/package.json +92 -92
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
- package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
- package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
- package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
- package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
- package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
- package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
- package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
- package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +310 -31
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +227 -0
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
- package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +24 -2
- package/src/__tests__/ResolverBase.isaPromotionCreate.test.ts +412 -0
- package/src/__tests__/RunTemplateResolver.test.ts +132 -0
- package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
- package/src/__tests__/SessionManager.test.ts +94 -17
- package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +131 -0
- package/src/__tests__/TaskGraphActionRunner.storableOutputs.test.ts +76 -0
- package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
- package/src/__tests__/VectorizeEntityResolver.test.ts +106 -0
- package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +64 -0
- package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
- package/src/__tests__/magicLink.test.ts +15 -76
- package/src/__tests__/magicLinkService.consume.test.ts +230 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
- package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
- package/src/__tests__/multiTenancy.security.test.ts +51 -0
- package/src/__tests__/multiTenancy.test.ts +1 -1
- package/src/__tests__/newUsers.test.ts +62 -5
- package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
- package/src/__tests__/translateBracketsToPG.test.ts +20 -0
- package/src/agentSessions/SessionManager.ts +23 -25
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +1 -0
- package/src/auth/actionAuthorization.ts +119 -0
- package/src/auth/exampleNewUserSubClass.ts +2 -2
- package/src/auth/magicLink/MagicLinkService.ts +61 -39
- package/src/auth/magicLink/magicLinkCore.ts +19 -56
- package/src/auth/newUsers.ts +15 -11
- package/src/context.ts +25 -34
- package/src/generated/graphql-schemas/__mj.ts +3417 -3428
- package/src/generic/CacheInvalidationResolver.ts +74 -19
- package/src/generic/ResolverBase.ts +207 -80
- package/src/generic/RunViewResolver.ts +17 -0
- package/src/index.ts +49 -46
- package/src/isolatedProvider.ts +22 -0
- package/src/multiTenancy/index.ts +24 -2
- package/src/postgresqlCompat.ts +21 -0
- package/src/postgresqlPoolSettings.ts +213 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +4 -2
- package/src/resolvers/ActionResolver.ts +84 -21
- package/src/resolvers/AdhocQueryResolver.ts +105 -187
- package/src/resolvers/DatasetResolver.ts +70 -10
- package/src/resolvers/EntityRecordNameResolver.ts +11 -52
- package/src/resolvers/FetchEntityVectorsResolver.ts +2 -2
- package/src/resolvers/GetDataContextDataResolver.ts +139 -41
- package/src/resolvers/GetDataResolver.ts +14 -1
- package/src/resolvers/QuerySystemUserResolver.ts +2 -1
- package/src/resolvers/RealtimeClientSessionResolver.ts +196 -77
- package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
- package/src/resolvers/RunAIAgentResolver.ts +4 -1
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SearchKnowledgeResolver.ts +4 -0
- package/src/resolvers/SearchKnowledgeSystemUserResolver.ts +1 -0
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/VectorizeEntityResolver.ts +16 -11
- package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
- package/src/resolvers/adhocTimeout.ts +12 -0
- package/src/rest/ViewOperationsHandler.ts +37 -18
- package/src/services/TaskGraphActionRunner.ts +45 -16
- package/dist/resolvers/adhoc-query-helpers.d.ts +0 -27
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
- package/dist/resolvers/adhoc-query-helpers.js +0 -42
- package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
- package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
- package/src/resolvers/adhoc-query-helpers.ts +0 -50
|
@@ -24,6 +24,7 @@ import { MJAIAgentSessionEntity, MJAIAgentEntity } from '@memberjunction/core-en
|
|
|
24
24
|
import { NormalizeInstanceKey, RemoteBrowserEngine } from '@memberjunction/remote-browser-server';
|
|
25
25
|
import { beginBrowserGoalStep, finalizeBrowserGoalStep, extractCoAgentRunID } from '../agentSessions/remoteBrowserGoalEngine.js';
|
|
26
26
|
import { RemoteBrowserGoalRegistry } from '../agentSessions/remoteBrowserGoalRegistry.js';
|
|
27
|
+
import { SessionRunIDVerifier } from '../agentSessions/SessionRunIDVerifier.js';
|
|
27
28
|
import { randomUUID } from 'node:crypto';
|
|
28
29
|
import {
|
|
29
30
|
RemoteBrowserAction,
|
|
@@ -261,6 +262,9 @@ export class RemoteBrowserActionResolver extends ResolverBase {
|
|
|
261
262
|
*/
|
|
262
263
|
private startedScreencasts = new Map<string, number>();
|
|
263
264
|
|
|
265
|
+
/** Checks the co-agent run id read from a session's owner-editable `Config_` before a step is attached to it. */
|
|
266
|
+
private readonly runIDVerifier = new SessionRunIDVerifier();
|
|
267
|
+
|
|
264
268
|
/**
|
|
265
269
|
* Surfaces whose live tab-audio stream this resolver has already started, keyed by
|
|
266
270
|
* {@link RemoteBrowserActionResolver.streamKey} for the same reason the screencast map is. Entries
|
|
@@ -400,7 +404,13 @@ export class RemoteBrowserActionResolver extends ResolverBase {
|
|
|
400
404
|
const providerName = await this.resolveProviderName(session, contextUser, provider);
|
|
401
405
|
// Observability: nest this goal's many prompt runs under ONE "Browser goal" step on the realtime
|
|
402
406
|
// co-agent run (when the session has one). Best-effort — a null step just means the goal runs unlinked.
|
|
403
|
-
|
|
407
|
+
// The owner can edit Config_, so its run id is used only when it is a run of this session.
|
|
408
|
+
const { CoAgentRunID: coAgentRunID } = await this.runIDVerifier.Verify(
|
|
409
|
+
session.ID,
|
|
410
|
+
{ CoAgentRunID: extractCoAgentRunID(session.Config_) },
|
|
411
|
+
contextUser,
|
|
412
|
+
provider,
|
|
413
|
+
);
|
|
404
414
|
const goalStep = await beginBrowserGoalStep(provider, contextUser, coAgentRunID, goal);
|
|
405
415
|
|
|
406
416
|
// ASYNC START: a goal loop can run for minutes; do NOT hold this request open for it (browser
|
|
@@ -598,7 +598,10 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
598
598
|
detail.Message = errorMessage;
|
|
599
599
|
detail.Error = errorMessage;
|
|
600
600
|
if (!(await detail.Save())) {
|
|
601
|
-
LogError(
|
|
601
|
+
LogError(
|
|
602
|
+
`Failed to persist Error on conversation detail ${conversationDetailId}: ` +
|
|
603
|
+
`${detail.LatestResult?.CompleteMessage?.trim() || 'no failure detail recorded'}`,
|
|
604
|
+
);
|
|
602
605
|
}
|
|
603
606
|
}
|
|
604
607
|
}
|
|
@@ -2,7 +2,7 @@ import { Resolver, Mutation, Arg, Ctx, ObjectType, Field } from 'type-graphql';
|
|
|
2
2
|
import { AppContext, UserPayload } from '../types.js';
|
|
3
3
|
import { LogError, LogStatus, Metadata, RunView } from '@memberjunction/core';
|
|
4
4
|
import { MJTemplateContentEntity, MJTemplateEntityExtended } from '@memberjunction/core-entities';
|
|
5
|
-
import { TemplateEngineServer } from '@memberjunction/templates';
|
|
5
|
+
import { GetTemplateRunRefusal, TemplateEngineServer } from '@memberjunction/templates';
|
|
6
6
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
7
7
|
import { GetReadWriteProvider } from '../util.js';
|
|
8
8
|
|
|
@@ -45,6 +45,18 @@ export class RunTemplateResolver extends ResolverBase {
|
|
|
45
45
|
try {
|
|
46
46
|
LogStatus(`=== RUNNING TEMPLATE FOR ID: ${templateId} ===`);
|
|
47
47
|
|
|
48
|
+
// Same rule as the Template.Run remote operation, checked before anything is loaded
|
|
49
|
+
const currentUser = this.GetUserFromPayload(userPayload);
|
|
50
|
+
const p = GetReadWriteProvider(providers);
|
|
51
|
+
const refusal = GetTemplateRunRefusal(currentUser, p);
|
|
52
|
+
if (refusal) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
error: refusal,
|
|
56
|
+
executionTimeMs: Date.now() - startTime
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
48
60
|
// Parse context data (JSON string)
|
|
49
61
|
let data = {};
|
|
50
62
|
if (contextData) {
|
|
@@ -59,17 +71,6 @@ export class RunTemplateResolver extends ResolverBase {
|
|
|
59
71
|
}
|
|
60
72
|
}
|
|
61
73
|
|
|
62
|
-
// Get current user from payload
|
|
63
|
-
const currentUser = this.GetUserFromPayload(userPayload);
|
|
64
|
-
if (!currentUser) {
|
|
65
|
-
return {
|
|
66
|
-
success: false,
|
|
67
|
-
error: 'Unable to determine current user',
|
|
68
|
-
executionTimeMs: Date.now() - startTime
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const p = GetReadWriteProvider(providers);
|
|
73
74
|
// Load the template entity
|
|
74
75
|
const templateEntity = await p.GetEntityObject<MJTemplateEntityExtended>('MJ: Templates', currentUser);
|
|
75
76
|
await templateEntity.Load(templateId);
|
|
@@ -32,6 +32,9 @@ export class SearchKnowledgeResultItem {
|
|
|
32
32
|
@Field()
|
|
33
33
|
EntityName: string;
|
|
34
34
|
|
|
35
|
+
@Field({ nullable: true })
|
|
36
|
+
EntityDisplayName?: string;
|
|
37
|
+
|
|
35
38
|
@Field()
|
|
36
39
|
RecordID: string;
|
|
37
40
|
|
|
@@ -461,6 +464,7 @@ export class SearchKnowledgeResolver extends ResolverBase {
|
|
|
461
464
|
Results: result.Results.map((r: SearchEngineResultItem) => ({
|
|
462
465
|
ID: r.ID,
|
|
463
466
|
EntityName: r.EntityName,
|
|
467
|
+
EntityDisplayName: r.EntityDisplayName,
|
|
464
468
|
RecordID: r.RecordID,
|
|
465
469
|
SourceType: r.SourceType,
|
|
466
470
|
ResultType: r.ResultType,
|
|
@@ -88,6 +88,7 @@ export class SearchKnowledgeSystemUserResolver extends ResolverBase {
|
|
|
88
88
|
Results: result.Results.map((r: SearchEngineResultItem) => ({
|
|
89
89
|
ID: r.ID,
|
|
90
90
|
EntityName: r.EntityName,
|
|
91
|
+
EntityDisplayName: r.EntityDisplayName,
|
|
91
92
|
RecordID: r.RecordID,
|
|
92
93
|
SourceType: r.SourceType,
|
|
93
94
|
ResultType: r.ResultType,
|
|
@@ -3,6 +3,7 @@ import { AppContext, UserPayload } from '../types.js';
|
|
|
3
3
|
import { BaseEntity, CompositeKey, DatabaseProviderBase, EntityDeleteOptions, EntitySaveOptions, IMetadataProvider, LogError, Metadata, RunView, UserInfo } from '@memberjunction/core';
|
|
4
4
|
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
|
|
5
5
|
import { GetReadWriteProvider } from '../util.js';
|
|
6
|
+
import { CreateIsolatedProvider } from '../isolatedProvider.js';
|
|
6
7
|
import { CompositeKeyInputType, CompositeKeyOutputType } from '../generic/KeyInputOutputTypes.js';
|
|
7
8
|
import { MJDatasetItemEntity } from '@memberjunction/core-entities';
|
|
8
9
|
|
|
@@ -216,7 +217,11 @@ export class SyncDataResolver {
|
|
|
216
217
|
try {
|
|
217
218
|
// Run the view to find matching records, then delete them all atomically —
|
|
218
219
|
// any single failure rolls back the entire batch so the dataset stays consistent.
|
|
219
|
-
|
|
220
|
+
// The rows are loaded FROM the transaction's provider so each Delete() runs inside it;
|
|
221
|
+
// with no request provider, an isolated one, never the shared Metadata.Provider, whose
|
|
222
|
+
// transaction would swallow every concurrent caller's writes (see CreateIsolatedProvider).
|
|
223
|
+
const provider = (providerOverride as unknown as DatabaseProviderBase) ?? await CreateIsolatedProvider();
|
|
224
|
+
const rv = RunView.FromMetadataProvider(provider);
|
|
220
225
|
const data = await rv.RunView<BaseEntity>({
|
|
221
226
|
EntityName: entityName,
|
|
222
227
|
ExtraFilter: filter,
|
|
@@ -234,7 +239,6 @@ export class SyncDataResolver {
|
|
|
234
239
|
return;
|
|
235
240
|
}
|
|
236
241
|
|
|
237
|
-
const provider = (providerOverride ?? Metadata.Provider) as unknown as DatabaseProviderBase;
|
|
238
242
|
await provider.BeginTransaction();
|
|
239
243
|
try {
|
|
240
244
|
for (const entityObject of data.Results) {
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import { Resolver, Mutation, Arg, Ctx, ObjectType, Field, Float } from 'type-graphql';
|
|
2
2
|
import { AppContext } from '../types.js';
|
|
3
|
-
import { LogError, LogStatus, UserInfo } from '@memberjunction/core';
|
|
3
|
+
import { IMetadataProvider, LogError, LogStatus, UserInfo } from '@memberjunction/core';
|
|
4
4
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
5
5
|
import { EntityVectorSyncer, VectorizeEntityParams, VectorizeProgressUpdate } from '@memberjunction/ai-vector-sync';
|
|
6
6
|
import { PubSubManager } from '../generic/PubSubManager.js';
|
|
7
7
|
import { PipelineProgressNotification } from './PipelineProgressResolver.js';
|
|
8
8
|
import { v4 as uuidv4 } from 'uuid';
|
|
9
|
+
import { GetReadWriteProvider } from '../util.js';
|
|
9
10
|
|
|
10
11
|
const PIPELINE_PROGRESS_TOPIC = 'PIPELINE_PROGRESS';
|
|
11
12
|
|
|
12
13
|
@ObjectType()
|
|
13
14
|
export class VectorizeEntityResult {
|
|
14
|
-
@Field()
|
|
15
|
+
@Field(() => Boolean)
|
|
15
16
|
Success: boolean;
|
|
16
17
|
|
|
17
|
-
@Field({ nullable: true })
|
|
18
|
+
@Field(() => String, { nullable: true })
|
|
18
19
|
Status?: string;
|
|
19
20
|
|
|
20
|
-
@Field({ nullable: true })
|
|
21
|
+
@Field(() => String, { nullable: true })
|
|
21
22
|
ErrorMessage?: string;
|
|
22
23
|
|
|
23
|
-
@Field({ nullable: true })
|
|
24
|
+
@Field(() => String, { nullable: true })
|
|
24
25
|
PipelineRunID?: string;
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -28,10 +29,10 @@ export class VectorizeEntityResult {
|
|
|
28
29
|
export class VectorizeEntityResolver extends ResolverBase {
|
|
29
30
|
@Mutation(() => VectorizeEntityResult)
|
|
30
31
|
async VectorizeEntity(
|
|
31
|
-
@Arg('entityDocumentID') entityDocumentID: string,
|
|
32
|
-
@Arg('entityID') entityID: string,
|
|
32
|
+
@Arg('entityDocumentID', () => String) entityDocumentID: string,
|
|
33
|
+
@Arg('entityID', () => String) entityID: string,
|
|
33
34
|
@Arg('batchSize', () => Float, { nullable: true }) batchSize?: number,
|
|
34
|
-
@Ctx() { userPayload }: AppContext = {} as AppContext
|
|
35
|
+
@Ctx() { userPayload, providers }: AppContext = {} as AppContext
|
|
35
36
|
): Promise<VectorizeEntityResult> {
|
|
36
37
|
try {
|
|
37
38
|
const currentUser = this.GetUserFromPayload(userPayload);
|
|
@@ -39,12 +40,15 @@ export class VectorizeEntityResolver extends ResolverBase {
|
|
|
39
40
|
return { Success: false, Status: 'Error', ErrorMessage: 'Unable to determine current user' };
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
// Capture the request's provider before returning, so the background run writes through the
|
|
44
|
+
// same connection the caller used. It is also the colocated vector host (#4910).
|
|
45
|
+
const provider = GetReadWriteProvider(providers, { allowFallbackToReadOnly: true }) ?? undefined;
|
|
42
46
|
const pipelineRunID = uuidv4();
|
|
43
47
|
LogStatus(`VectorizeEntity: starting pipeline ${pipelineRunID} for entity document ${entityDocumentID}`);
|
|
44
48
|
|
|
45
49
|
// Fire-and-forget: start the pipeline in the background and return immediately.
|
|
46
50
|
// Progress is delivered to the client via the PipelineProgress GraphQL subscription.
|
|
47
|
-
this.runPipelineInBackground(pipelineRunID, entityDocumentID, entityID, batchSize, currentUser);
|
|
51
|
+
this.runPipelineInBackground(pipelineRunID, entityDocumentID, entityID, batchSize, currentUser, provider);
|
|
48
52
|
|
|
49
53
|
return {
|
|
50
54
|
Success: true,
|
|
@@ -71,10 +75,11 @@ export class VectorizeEntityResolver extends ResolverBase {
|
|
|
71
75
|
entityDocumentID: string,
|
|
72
76
|
entityID: string,
|
|
73
77
|
batchSize: number | undefined,
|
|
74
|
-
currentUser: UserInfo
|
|
78
|
+
currentUser: UserInfo,
|
|
79
|
+
provider?: IMetadataProvider
|
|
75
80
|
): Promise<void> {
|
|
76
81
|
try {
|
|
77
|
-
const syncer = new EntityVectorSyncer();
|
|
82
|
+
const syncer = new EntityVectorSyncer(provider);
|
|
78
83
|
await syncer.Config(true, currentUser);
|
|
79
84
|
|
|
80
85
|
const params: VectorizeEntityParams = {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// type-graphql decorators on the resolver class need the reflect-metadata polyfill.
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
5
|
+
import type { MockInstance } from 'vitest';
|
|
6
|
+
import { SQLExpressionValidator } from '@memberjunction/global';
|
|
7
|
+
|
|
8
|
+
const { executedSQL } = vi.hoisted(() => ({ executedSQL: [] as string[] }));
|
|
9
|
+
|
|
10
|
+
// GetData's server bootstrap, provider, auth and driver imports are replaced so the resolver runs
|
|
11
|
+
// offline. The fake driver records every text it is asked to run.
|
|
12
|
+
vi.mock('../../index.js', () => ({ getDbType: () => 'sqlserver' }));
|
|
13
|
+
vi.mock('@memberjunction/generic-database-provider', () => ({
|
|
14
|
+
QueryCompositionEngine: class {
|
|
15
|
+
HasCompositionTokens(): boolean {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
}));
|
|
20
|
+
vi.mock('../../util.js', () => ({ GetReadOnlyDataSource: () => ({}), GetReadOnlyProvider: () => null }));
|
|
21
|
+
vi.mock('../../auth/index.js', () => ({ getSystemUser: async () => ({}) }));
|
|
22
|
+
vi.mock('mssql', () => ({
|
|
23
|
+
default: {
|
|
24
|
+
Request: class {
|
|
25
|
+
async query(text: string): Promise<{ recordset: Array<Record<string, unknown>> }> {
|
|
26
|
+
executedSQL.push(text);
|
|
27
|
+
return { recordset: [] };
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
import { GetDataResolver, GetDataInputType, GetDataOutputType, registerAccessToken } from '../GetDataResolver.js';
|
|
34
|
+
import type { AppContext } from '../../types.js';
|
|
35
|
+
|
|
36
|
+
const CONTEXT = { dataSources: [] } as unknown as AppContext;
|
|
37
|
+
|
|
38
|
+
let tokenSeq = 0;
|
|
39
|
+
|
|
40
|
+
/** Calls GetData with a fresh valid token. */
|
|
41
|
+
async function runGetData(queries: string[]): Promise<GetDataOutputType> {
|
|
42
|
+
const token = `getdata-validation-${Date.now()}-${++tokenSeq}`;
|
|
43
|
+
registerAccessToken(token, 60_000);
|
|
44
|
+
const input = Object.assign(new GetDataInputType(), { Token: token, Queries: queries });
|
|
45
|
+
return new GetDataResolver().GetData(input, CONTEXT);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The text of every call to the validator's `validate` method. */
|
|
49
|
+
function validatedTexts(spy: MockInstance<SQLExpressionValidator['validate']>): string[] {
|
|
50
|
+
return spy.mock.calls.map(([text]) => text);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('GetData runs exactly the SQL it validated', () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
executedSQL.length = 0;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('validates and runs the same text when the query contains backslash sequences', async () => {
|
|
59
|
+
const query = String.raw`SELECT Name FROM __mj.vwFiles WHERE Path = 'C:\new\temp'`;
|
|
60
|
+
const validateSpy = vi.spyOn(SQLExpressionValidator.Instance, 'validate');
|
|
61
|
+
|
|
62
|
+
const output = await runGetData([query]);
|
|
63
|
+
|
|
64
|
+
expect(output.ErrorMessages).toEqual([null]);
|
|
65
|
+
expect(executedSQL).toEqual(validatedTexts(validateSpy));
|
|
66
|
+
expect(executedSQL).toEqual([query]);
|
|
67
|
+
expect(validateSpy).toHaveBeenCalledWith(query, { context: 'full_query', Dialect: 'sqlserver' });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('runs the trimmed text that the validator reads', async () => {
|
|
71
|
+
const validateSpy = vi.spyOn(SQLExpressionValidator.Instance, 'validate');
|
|
72
|
+
|
|
73
|
+
const output = await runGetData(['\n SELECT 1 AS One \n']);
|
|
74
|
+
|
|
75
|
+
expect(output.ErrorMessages).toEqual([null]);
|
|
76
|
+
expect(executedSQL).toEqual(['SELECT 1 AS One']);
|
|
77
|
+
expect(validatedTexts(validateSpy)).toEqual(executedSQL);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('refuses a query whose text, as written, does not open with SELECT or WITH', async () => {
|
|
81
|
+
const output = await runGetData(["-- note\\n SELECT 1\nPRINT 'hello'"]);
|
|
82
|
+
|
|
83
|
+
expect(executedSQL).toEqual([]);
|
|
84
|
+
expect(output.Success).toBe(false);
|
|
85
|
+
expect(output.ErrorMessages[0]).toMatch(/must start with SELECT or WITH/);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('does not run a refused query and still runs the others', async () => {
|
|
89
|
+
const output = await runGetData(['SELECT 1; SELECT 2', 'SELECT 1 AS One']);
|
|
90
|
+
|
|
91
|
+
expect(executedSQL).toEqual(['SELECT 1 AS One']);
|
|
92
|
+
expect(output.ErrorMessages[0]).toMatch(/Semicolons are not allowed/);
|
|
93
|
+
expect(output.ErrorMessages[1]).toBeNull();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The timeout an ad-hoc query gets when the caller names none. */
|
|
2
|
+
const DEFAULT_TIMEOUT_SECONDS = 30;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The timeout an ad-hoc query runs with: what the caller asked for (or the default), but never
|
|
6
|
+
* longer than the server's `requestTimeout`, so a caller cannot hold a read-only connection longer
|
|
7
|
+
* than any other request may. `requestTimeoutMs` of 0 or less means the server sets no limit.
|
|
8
|
+
*/
|
|
9
|
+
export function ClampAdhocTimeoutSeconds(requested: number | undefined, requestTimeoutMs: number): number {
|
|
10
|
+
const seconds = requested != null && requested > 0 ? requested : DEFAULT_TIMEOUT_SECONDS;
|
|
11
|
+
return requestTimeoutMs > 0 ? Math.min(seconds, Math.max(1, Math.floor(requestTimeoutMs / 1000))) : seconds;
|
|
12
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
LogError, Metadata, RunView, RunViewParams,
|
|
3
|
-
RunViewResult, UserInfo
|
|
2
|
+
BaseEntity, LogError, Metadata, RunView, RunViewParams,
|
|
3
|
+
RunViewResult, UserInfo, type IRunViewProvider
|
|
4
4
|
} from '@memberjunction/core';
|
|
5
|
+
import { ClientClauseScreen } from '@memberjunction/generic-database-provider';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* View Operations Implementation for REST endpoints
|
|
@@ -32,11 +33,11 @@ export class ViewOperationsHandler {
|
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
// Sanitize and validate parameters
|
|
36
|
-
|
|
36
|
+
// Sanitize and validate parameters against the provider that runs the view
|
|
37
|
+
const runView = new RunView();
|
|
38
|
+
this.sanitizeRunViewParams(params, md, runView.ProviderToUse, user);
|
|
37
39
|
|
|
38
40
|
// Execute the view
|
|
39
|
-
const runView = new RunView();
|
|
40
41
|
const result = await runView.RunView(params, user);
|
|
41
42
|
|
|
42
43
|
return { success: true, result };
|
|
@@ -51,8 +52,9 @@ export class ViewOperationsHandler {
|
|
|
51
52
|
*/
|
|
52
53
|
static async runViews(paramsArray: RunViewParams[], user: UserInfo): Promise<{ success: boolean, results?: RunViewResult[], error?: string }> {
|
|
53
54
|
try {
|
|
54
|
-
// Validate and sanitize each set of parameters
|
|
55
|
+
// Validate and sanitize each set of parameters against the provider that runs the views
|
|
55
56
|
const md = new Metadata(); // global-provider-ok: REST endpoint — no per-request provider injection in REST middleware yet
|
|
57
|
+
const runView = new RunView();
|
|
56
58
|
for (const params of paramsArray) {
|
|
57
59
|
// Validate entity exists
|
|
58
60
|
const entity = md.Entities.find(e => e.Name === params.EntityName);
|
|
@@ -73,11 +75,10 @@ export class ViewOperationsHandler {
|
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
// Sanitize parameters
|
|
76
|
-
this.sanitizeRunViewParams(params);
|
|
78
|
+
this.sanitizeRunViewParams(params, md, runView.ProviderToUse, user);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
// Execute the views
|
|
80
|
-
const runView = new RunView();
|
|
81
82
|
const results = await runView.RunViews(paramsArray, user);
|
|
82
83
|
|
|
83
84
|
return { success: true, results };
|
|
@@ -104,11 +105,11 @@ export class ViewOperationsHandler {
|
|
|
104
105
|
throw new Error(`User ${user.Name} does not have permission to read ${params.EntityName} records`);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
// Sanitize and validate parameters
|
|
108
|
-
|
|
108
|
+
// Sanitize and validate parameters against the provider that runs the view
|
|
109
|
+
const runView = new RunView();
|
|
110
|
+
this.sanitizeRunViewParams(params, md, runView.ProviderToUse, user);
|
|
109
111
|
|
|
110
112
|
// Execute the view
|
|
111
|
-
const runView = new RunView();
|
|
112
113
|
return await runView.RunView(params, user);
|
|
113
114
|
} catch (error) {
|
|
114
115
|
LogError(error);
|
|
@@ -173,35 +174,53 @@ export class ViewOperationsHandler {
|
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
/**
|
|
176
|
-
* Sanitize and validate RunViewParams
|
|
177
|
+
* Sanitize and validate RunViewParams. Client filter and sort text passes the same clause
|
|
178
|
+
* screen as the GraphQL RunView resolvers, parsed in the dialect of `provider`, the provider
|
|
179
|
+
* that runs the view.
|
|
177
180
|
*/
|
|
178
|
-
private static sanitizeRunViewParams(params: RunViewParams): void {
|
|
181
|
+
private static sanitizeRunViewParams(params: RunViewParams, md: Metadata, provider: IRunViewProvider, user: UserInfo): void {
|
|
179
182
|
// Ensure EntityName is provided
|
|
180
183
|
if (!params.EntityName) {
|
|
181
184
|
throw new Error('EntityName is required');
|
|
182
185
|
}
|
|
186
|
+
|
|
187
|
+
// A loaded view entity cannot arrive over JSON; the provider would trust a plain object's
|
|
188
|
+
// fields (its ID is spliced into view SQL). REST callers name a saved view by ViewID or ViewName.
|
|
189
|
+
if (params.ViewEntity != null && !(params.ViewEntity instanceof BaseEntity)) {
|
|
190
|
+
throw new Error('ViewEntity is not accepted over REST; use ViewID or ViewName');
|
|
191
|
+
}
|
|
183
192
|
|
|
184
193
|
// Convert string arrays if they came in as comma-separated strings
|
|
185
194
|
if (params.Fields && typeof params.Fields === 'string') {
|
|
186
195
|
params.Fields = (params.Fields as string).split(',');
|
|
187
196
|
}
|
|
188
197
|
|
|
189
|
-
// Sanitize numeric values
|
|
198
|
+
// Sanitize numeric values: base-10, integral, and clamped to a sane range so a
|
|
199
|
+
// negative/garbage value can never reach the SQL layer.
|
|
190
200
|
if (params.MaxRows !== undefined) {
|
|
191
|
-
|
|
192
|
-
? parseInt(params.MaxRows as string)
|
|
201
|
+
const parsed = typeof params.MaxRows === 'string'
|
|
202
|
+
? parseInt(params.MaxRows as string, 10)
|
|
193
203
|
: params.MaxRows;
|
|
204
|
+
params.MaxRows = Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed as number)) : undefined;
|
|
194
205
|
}
|
|
195
206
|
|
|
196
207
|
if (params.StartRow !== undefined) {
|
|
197
|
-
|
|
198
|
-
? parseInt(params.StartRow as string)
|
|
208
|
+
const parsed = typeof params.StartRow === 'string'
|
|
209
|
+
? parseInt(params.StartRow as string, 10)
|
|
199
210
|
: params.StartRow;
|
|
211
|
+
params.StartRow = Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed as number)) : undefined;
|
|
200
212
|
}
|
|
201
213
|
|
|
202
214
|
// Default ResultType if not provided
|
|
203
215
|
if (!params.ResultType) {
|
|
204
216
|
params.ResultType = 'simple';
|
|
205
217
|
}
|
|
218
|
+
|
|
219
|
+
ClientClauseScreen.ScreenViewClauses(
|
|
220
|
+
{ ExtraFilter: params.ExtraFilter, OrderBy: params.OrderBy, OverrideExcludeFilter: params.OverrideExcludeFilter },
|
|
221
|
+
md.Entities,
|
|
222
|
+
ClientClauseScreen.DialectFor(provider),
|
|
223
|
+
user,
|
|
224
|
+
);
|
|
206
225
|
}
|
|
207
226
|
}
|
|
@@ -35,9 +35,19 @@ export class TaskGraphActionRunner implements TaskActionRunner {
|
|
|
35
35
|
Filters: [],
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
const output = this.buildOutput(result.Params);
|
|
39
|
+
const unstorable = this.findUnstorableOutput(output);
|
|
40
|
+
if (unstorable) {
|
|
41
|
+
const message =
|
|
42
|
+
`Action "${action.Name}" returned output param "${unstorable.Name}", which cannot be stored ` +
|
|
43
|
+
`as the step's result: ${unstorable.Reason}. Output params of a durable step must be plain data.`;
|
|
44
|
+
LogError(`[TaskGraphActionRunner] Task ${params.TaskID}: ${message}`);
|
|
45
|
+
return { Success: false, ErrorMessage: message, ActionLogID: result.LogEntry?.ID };
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
return {
|
|
39
49
|
Success: result.Success,
|
|
40
|
-
Output:
|
|
50
|
+
Output: output,
|
|
41
51
|
ErrorMessage: result.Success ? undefined : result.Message,
|
|
42
52
|
// The engine already wrote the log; this is the only place its id is in hand. Without
|
|
43
53
|
// carrying it out, a workflow's action step has no path back to its own execution
|
|
@@ -51,6 +61,24 @@ export class TaskGraphActionRunner implements TaskActionRunner {
|
|
|
51
61
|
}
|
|
52
62
|
}
|
|
53
63
|
|
|
64
|
+
/**
|
|
65
|
+
* The first output param that cannot be written to the Task row as JSON, or null.
|
|
66
|
+
*
|
|
67
|
+
* Checked here because this is the last place that knows the action's and the param's NAMES: the
|
|
68
|
+
* dispatcher serializes the whole Output later and, for a live object (an entity, an observable),
|
|
69
|
+
* fails with a bare "Converting circular structure to JSON" that names neither.
|
|
70
|
+
*/
|
|
71
|
+
private findUnstorableOutput(output: Record<string, unknown>): { Name: string; Reason: string } | null {
|
|
72
|
+
for (const [Name, value] of Object.entries(output)) {
|
|
73
|
+
try {
|
|
74
|
+
JSON.stringify(value);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
return { Name, Reason: e instanceof Error ? e.message.split('\n')[0] : String(e) };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
54
82
|
/**
|
|
55
83
|
* Projects the action's parameters onto the flat result a workflow step is expected to produce.
|
|
56
84
|
*
|
|
@@ -75,25 +103,26 @@ export class TaskGraphActionRunner implements TaskActionRunner {
|
|
|
75
103
|
}
|
|
76
104
|
|
|
77
105
|
/**
|
|
78
|
-
* Rebuilds the action's parameters from the task's
|
|
106
|
+
* Rebuilds the action's parameters from the task's input, one parameter per key.
|
|
79
107
|
*
|
|
80
|
-
* **
|
|
81
|
-
*
|
|
82
|
-
* marked as not-logged
|
|
83
|
-
* value, which is the honest consequence of choosing not to persist it, and the
|
|
84
|
-
* dispatch is opt-in per binding rather than the default.
|
|
108
|
+
* For a **durable entity-action** graph the input was written by `RedactParamsToRecord`
|
|
109
|
+
* (`@memberjunction/actions-base`) at deferral time, so its values have been through redaction:
|
|
110
|
+
* a parameter the binding marked as not-logged arrives absent rather than secret — the action
|
|
111
|
+
* sees a missing value, which is the honest consequence of choosing not to persist it, and the
|
|
112
|
+
* reason durable dispatch is opt-in per binding rather than the default. Every other graph's
|
|
113
|
+
* input is whatever its spec authored. This method re-applies no redaction rule in either case.
|
|
85
114
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
115
|
+
* `InputPayload` already carries the dependency outputs: the dispatcher merges them underneath
|
|
116
|
+
* the task's own input (`mergedPayload`) before calling this runner, so a node's explicit
|
|
117
|
+
* parameters win and a step with an input mapping gets exactly the parameters it declared.
|
|
118
|
+
* `DependencyOutputs` is keyed by upstream task ID, not by parameter name, so it is deliberately
|
|
119
|
+
* not read here — spreading it would hand the action one extra parameter per dependency, named
|
|
120
|
+
* by a GUID and holding that task's whole output.
|
|
88
121
|
*/
|
|
89
122
|
private buildParams(params: TaskActionRunParams): ActionParam[] {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
merged[name] = value;
|
|
93
|
-
}
|
|
94
|
-
if (params.InputPayload && typeof params.InputPayload === 'object') {
|
|
95
|
-
Object.assign(merged, params.InputPayload as Record<string, unknown>);
|
|
123
|
+
if (!params.InputPayload || typeof params.InputPayload !== 'object') {
|
|
124
|
+
return [];
|
|
96
125
|
}
|
|
97
|
-
return Object.entries(
|
|
126
|
+
return Object.entries(params.InputPayload as Record<string, unknown>).map(([Name, Value]) => ({ Name, Value, Type: 'Input' }));
|
|
98
127
|
}
|
|
99
128
|
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure, dependency-free helpers for {@link AdhocQueryResolver}. Kept in their own
|
|
3
|
-
* module (no type-graphql / mssql imports) so the total-row-count boundary logic is
|
|
4
|
-
* unit-testable without standing up the GraphQL resolver or a database.
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Determines the exact total row count from the page alone, when it can be known
|
|
8
|
-
* without a separate `COUNT(*)`:
|
|
9
|
-
*
|
|
10
|
-
* - **Not paging** (`maxRows == null`): the query ran uncapped, so every row came
|
|
11
|
-
* back — the total is just the number returned.
|
|
12
|
-
* - **Short page** (`pageLength < maxRows`): a page shorter than the page size means
|
|
13
|
-
* there are no rows beyond it, so the total is `startRow + pageLength` exactly.
|
|
14
|
-
* - **Full page** (`pageLength >= maxRows`): more rows may exist — returns `null` to
|
|
15
|
-
* signal that a `COUNT(*)` is required to know the true total.
|
|
16
|
-
*/
|
|
17
|
-
export declare function exactTotalFromPage(startRow: number, pageLength: number, maxRows: number | null): number | null;
|
|
18
|
-
/**
|
|
19
|
-
* Reads the total row count from a `COUNT(*)` recordset (`[{ TotalRowCount }]`),
|
|
20
|
-
* falling back to `fallback` when the count is absent, non-numeric, or negative — so
|
|
21
|
-
* an unexpected count shape never yields a misleading total. `fallback` should be a
|
|
22
|
-
* safe lower bound (e.g. `startRow + rowsReturned`).
|
|
23
|
-
*/
|
|
24
|
-
export declare function resolveAdhocTotalRowCount(countRows: ReadonlyArray<{
|
|
25
|
-
TotalRowCount?: unknown;
|
|
26
|
-
}> | null | undefined, fallback: number): number;
|
|
27
|
-
//# sourceMappingURL=adhoc-query-helpers.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adhoc-query-helpers.d.ts","sourceRoot":"","sources":["../../src/resolvers/adhoc-query-helpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAC9B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,IAAI,GACvB,MAAM,GAAG,IAAI,CAQf;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACrC,SAAS,EAAE,aAAa,CAAC;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,GAAG,IAAI,GAAG,SAAS,EACxE,QAAQ,EAAE,MAAM,GACjB,MAAM,CASR"}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure, dependency-free helpers for {@link AdhocQueryResolver}. Kept in their own
|
|
3
|
-
* module (no type-graphql / mssql imports) so the total-row-count boundary logic is
|
|
4
|
-
* unit-testable without standing up the GraphQL resolver or a database.
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Determines the exact total row count from the page alone, when it can be known
|
|
8
|
-
* without a separate `COUNT(*)`:
|
|
9
|
-
*
|
|
10
|
-
* - **Not paging** (`maxRows == null`): the query ran uncapped, so every row came
|
|
11
|
-
* back — the total is just the number returned.
|
|
12
|
-
* - **Short page** (`pageLength < maxRows`): a page shorter than the page size means
|
|
13
|
-
* there are no rows beyond it, so the total is `startRow + pageLength` exactly.
|
|
14
|
-
* - **Full page** (`pageLength >= maxRows`): more rows may exist — returns `null` to
|
|
15
|
-
* signal that a `COUNT(*)` is required to know the true total.
|
|
16
|
-
*/
|
|
17
|
-
export function exactTotalFromPage(startRow, pageLength, maxRows) {
|
|
18
|
-
if (maxRows == null) {
|
|
19
|
-
return pageLength;
|
|
20
|
-
}
|
|
21
|
-
if (pageLength < maxRows) {
|
|
22
|
-
return startRow + pageLength;
|
|
23
|
-
}
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Reads the total row count from a `COUNT(*)` recordset (`[{ TotalRowCount }]`),
|
|
28
|
-
* falling back to `fallback` when the count is absent, non-numeric, or negative — so
|
|
29
|
-
* an unexpected count shape never yields a misleading total. `fallback` should be a
|
|
30
|
-
* safe lower bound (e.g. `startRow + rowsReturned`).
|
|
31
|
-
*/
|
|
32
|
-
export function resolveAdhocTotalRowCount(countRows, fallback) {
|
|
33
|
-
const raw = countRows?.[0]?.TotalRowCount;
|
|
34
|
-
if (raw != null) {
|
|
35
|
-
const n = Number(raw);
|
|
36
|
-
if (Number.isFinite(n) && n >= 0) {
|
|
37
|
-
return Math.floor(n);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return fallback;
|
|
41
|
-
}
|
|
42
|
-
//# sourceMappingURL=adhoc-query-helpers.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adhoc-query-helpers.js","sourceRoot":"","sources":["../../src/resolvers/adhoc-query-helpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAC9B,QAAgB,EAChB,UAAkB,EAClB,OAAsB;IAEtB,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;QAClB,OAAO,UAAU,CAAC;IACtB,CAAC;IACD,IAAI,UAAU,GAAG,OAAO,EAAE,CAAC;QACvB,OAAO,QAAQ,GAAG,UAAU,CAAC;IACjC,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CACrC,SAAwE,EACxE,QAAgB;IAEhB,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC;IAC1C,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;QACd,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AACpB,CAAC"}
|