@memberjunction/server 6.1.0-edge.1 → 6.1.0-edge.3
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 +180 -4
- package/dist/auth/AuthProviderCatalogRouter.d.ts +31 -0
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -0
- package/dist/auth/AuthProviderCatalogRouter.js +69 -0
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -0
- package/dist/auth/AuthProviderEngine.d.ts +96 -0
- package/dist/auth/AuthProviderEngine.d.ts.map +1 -0
- package/dist/auth/AuthProviderEngine.js +245 -0
- package/dist/auth/AuthProviderEngine.js.map +1 -0
- package/dist/auth/exampleNewUserSubClass.js +1 -1
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/index.d.ts +11 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +65 -18
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.d.ts +64 -1
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +117 -5
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +1 -1
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.js +1 -1
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/config.d.ts +89 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +52 -35
- package/dist/config.js.map +1 -1
- package/dist/context.js +2 -2
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +540 -1595
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +2755 -16857
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +20 -4
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +58 -21
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +4 -0
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +31 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +183 -54
- package/dist/index.js.map +1 -1
- package/dist/integration/RSUProgressBridge.d.ts +83 -0
- package/dist/integration/RSUProgressBridge.d.ts.map +1 -0
- package/dist/integration/RSUProgressBridge.js +230 -0
- package/dist/integration/RSUProgressBridge.js.map +1 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts +52 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -0
- package/dist/integration/SchemaRefreshLaunch.js +50 -0
- package/dist/integration/SchemaRefreshLaunch.js.map +1 -0
- package/dist/logging/secretRedactor.d.ts +9 -0
- package/dist/logging/secretRedactor.d.ts.map +1 -1
- package/dist/logging/secretRedactor.js +39 -0
- package/dist/logging/secretRedactor.js.map +1 -1
- package/dist/middleware/BaseServerMiddleware.d.ts +5 -0
- package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -1
- package/dist/middleware/BaseServerMiddleware.js +5 -0
- package/dist/middleware/BaseServerMiddleware.js.map +1 -1
- package/dist/providerConfigUnits.d.ts +20 -0
- package/dist/providerConfigUnits.d.ts.map +1 -0
- package/dist/providerConfigUnits.js +22 -0
- package/dist/providerConfigUnits.js.map +1 -0
- package/dist/realtimeWidget/WidgetSessionService.js +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +12 -0
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
- package/dist/resolvers/EntityPermissionResolver.js +1 -1
- package/dist/resolvers/EntityPermissionResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +34 -4
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +218 -28
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +85 -3
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +302 -82
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts +5 -4
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +29 -14
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +12 -3
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +33 -14
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +10 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +5 -0
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +3 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +20 -7
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/SearchEntitiesResolver.js +1 -1
- package/dist/resolvers/SearchEntitiesResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +2 -2
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts +18 -0
- package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.js +75 -0
- package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.js +1 -1
- package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
- package/dist/resolvers/UserResolver.d.ts +19 -17
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +66 -67
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/currentUserRoles.d.ts +19 -0
- package/dist/resolvers/currentUserRoles.d.ts.map +1 -0
- package/dist/resolvers/currentUserRoles.js +36 -0
- package/dist/resolvers/currentUserRoles.js.map +1 -0
- package/dist/rest/MediaAccessKeys.d.ts +17 -0
- package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
- package/dist/rest/MediaAccessKeys.js +42 -1
- package/dist/rest/MediaAccessKeys.js.map +1 -1
- package/dist/rest/MediaStreamHandler.d.ts +3 -2
- package/dist/rest/MediaStreamHandler.d.ts.map +1 -1
- package/dist/rest/MediaStreamHandler.js +83 -2
- package/dist/rest/MediaStreamHandler.js.map +1 -1
- package/dist/rest/OAuthCallbackHandler.d.ts +49 -1
- package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
- package/dist/rest/OAuthCallbackHandler.js +130 -44
- package/dist/rest/OAuthCallbackHandler.js.map +1 -1
- package/dist/rest/SignatureWebhookHandler.js +1 -1
- package/dist/rest/SignatureWebhookHandler.js.map +1 -1
- package/dist/rest/UploadTokenManager.d.ts +90 -0
- package/dist/rest/UploadTokenManager.d.ts.map +1 -0
- package/dist/rest/UploadTokenManager.js +156 -0
- package/dist/rest/UploadTokenManager.js.map +1 -0
- package/dist/services/DurableEntityActionTaskSubmitter.d.ts +26 -0
- package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -0
- package/dist/services/DurableEntityActionTaskSubmitter.js +55 -0
- package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -0
- package/dist/services/IntegrationSyncWorkerService.d.ts +48 -0
- package/dist/services/IntegrationSyncWorkerService.d.ts.map +1 -0
- package/dist/services/IntegrationSyncWorkerService.js +132 -0
- package/dist/services/IntegrationSyncWorkerService.js.map +1 -0
- package/dist/services/ScheduledJobsService.js +1 -1
- package/dist/services/ScheduledJobsService.js.map +1 -1
- package/dist/services/StartTaskGraphDispatcher.d.ts +0 -10
- package/dist/services/StartTaskGraphDispatcher.d.ts.map +1 -1
- package/dist/services/StartTaskGraphDispatcher.js +37 -2
- package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +34 -0
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -0
- package/dist/services/TaskGraphActionRunner.js +95 -0
- package/dist/services/TaskGraphActionRunner.js.map +1 -0
- package/dist/services/TaskGraphAgentRunner.d.ts +20 -0
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +53 -0
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +12 -1
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/services/TaskGraphPromptRunner.d.ts +31 -0
- package/dist/services/TaskGraphPromptRunner.d.ts.map +1 -0
- package/dist/services/TaskGraphPromptRunner.js +134 -0
- package/dist/services/TaskGraphPromptRunner.js.map +1 -0
- package/dist/telephony/RingCentralTelephonyService.js +1 -1
- package/dist/telephony/RingCentralTelephonyService.js.map +1 -1
- package/dist/telephony/TwilioTelephonyRouter.js +1 -1
- package/dist/telephony/TwilioTelephonyRouter.js.map +1 -1
- package/dist/telephony/VonageTelephonyRouter.js +1 -1
- package/dist/telephony/VonageTelephonyRouter.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +91 -91
- package/src/__tests__/APIKeyScopeAuth.test.ts +475 -0
- package/src/__tests__/AuthProviderEngine.test.ts +278 -0
- package/src/__tests__/EntityCRUDHandler.test.ts +378 -0
- package/src/__tests__/IntegrationSyncWorkerService.test.ts +275 -0
- package/src/__tests__/OAuthCallbackHandler.openRedirect.test.ts +117 -0
- package/src/__tests__/OAuthCallbackHandler.xss.test.ts +4 -1
- package/src/__tests__/RESTEndpointHandler.test.ts +672 -0
- package/src/__tests__/RSUProgressBridge.test.ts +250 -0
- package/src/__tests__/RealtimeBridgeResolver.test.ts +10 -2
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +1 -1
- package/src/__tests__/ResolverBase.frozenRecordMapping.test.ts +133 -0
- package/src/__tests__/ResolverBase.transportMapping.test.ts +289 -0
- package/src/__tests__/SchemaRefreshLaunch.test.ts +114 -0
- package/src/__tests__/SessionManager.test.ts +1 -1
- package/src/__tests__/UserResolver.currentUser.test.ts +87 -0
- package/src/__tests__/ViewOperationsHandler.test.ts +277 -0
- package/src/__tests__/WidgetSessionService.test.ts +1 -1
- package/src/__tests__/config-units.test.ts +74 -0
- package/src/__tests__/databaseAbstraction.test.ts +1 -1
- package/src/__tests__/initializeProviders.refresh.test.ts +146 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +1 -1
- package/src/__tests__/mjapi-bootstrap.test.ts +0 -6
- package/src/__tests__/newUsers.test.ts +726 -0
- package/src/__tests__/resolverBase.rls.test.ts +3 -0
- package/src/__tests__/secretRedactor.failOpen.test.ts +135 -0
- package/src/__tests__/uploadTokenManager.test.ts +159 -0
- package/src/__tests__/widgetGuestElevation.test.ts +1 -1
- package/src/auth/AuthProviderCatalogRouter.ts +76 -0
- package/src/auth/AuthProviderEngine.ts +253 -0
- package/src/auth/exampleNewUserSubClass.ts +1 -1
- package/src/auth/index.ts +69 -21
- package/src/auth/initializeProviders.ts +128 -7
- package/src/auth/magicLink/MagicLinkService.ts +1 -1
- package/src/auth/newUsers.ts +1 -1
- package/src/config.ts +55 -36
- package/src/context.ts +2 -2
- package/src/generated/generated.ts +2038 -10529
- package/src/generic/ResolverBase.ts +63 -21
- package/src/generic/RunViewResolver.ts +31 -0
- package/src/index.ts +199 -56
- package/src/integration/RSUProgressBridge.ts +248 -0
- package/src/integration/SchemaRefreshLaunch.ts +77 -0
- package/src/logging/secretRedactor.ts +43 -0
- package/src/middleware/BaseServerMiddleware.ts +5 -0
- package/src/providerConfigUnits.ts +21 -0
- package/src/realtimeWidget/WidgetSessionService.ts +1 -1
- package/src/realtimeWidget/widgetGuestElevation.ts +1 -1
- package/src/resolvers/ActionResolver.ts +13 -0
- package/src/resolvers/ComponentRegistryResolver.ts +1 -1
- package/src/resolvers/EntityPermissionResolver.ts +1 -1
- package/src/resolvers/FileResolver.ts +200 -40
- package/src/resolvers/IntegrationDiscoveryResolver.ts +328 -70
- package/src/resolvers/PotentialDuplicateRecordResolver.ts +1 -1
- package/src/resolvers/QueryResolver.ts +25 -11
- package/src/resolvers/QuerySystemUserResolver.ts +28 -13
- package/src/resolvers/RealtimeClientSessionResolver.ts +13 -0
- package/src/resolvers/RunAIAgentResolver.ts +21 -6
- package/src/resolvers/SearchEntitiesResolver.ts +1 -1
- package/src/resolvers/SqlLoggingConfigResolver.ts +1 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +2 -2
- package/src/resolvers/TaskGraphFrameResolver.ts +67 -0
- package/src/resolvers/UserFavoriteResolver.ts +1 -1
- package/src/resolvers/UserResolver.ts +99 -60
- package/src/resolvers/currentUserRoles.ts +47 -0
- package/src/rest/MediaAccessKeys.ts +47 -1
- package/src/rest/MediaStreamHandler.ts +98 -3
- package/src/rest/OAuthCallbackHandler.ts +150 -42
- package/src/rest/SignatureWebhookHandler.ts +1 -1
- package/src/rest/UploadTokenManager.ts +211 -0
- package/src/services/DurableEntityActionTaskSubmitter.ts +78 -0
- package/src/services/IntegrationSyncWorkerService.ts +141 -0
- package/src/services/ScheduledJobsService.ts +1 -1
- package/src/services/StartTaskGraphDispatcher.ts +38 -1
- package/src/services/TaskGraphActionRunner.ts +99 -0
- package/src/services/TaskGraphAgentRunner.ts +58 -1
- package/src/services/TaskGraphContinuationDeliverer.ts +11 -1
- package/src/services/TaskGraphPromptRunner.ts +144 -0
- package/src/telephony/RingCentralTelephonyService.ts +1 -1
- package/src/telephony/TwilioTelephonyRouter.ts +1 -1
- package/src/telephony/VonageTelephonyRouter.ts +1 -1
- package/src/types.ts +6 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Ephemeral in-memory staged upload token manager.
|
|
3
|
+
*
|
|
4
|
+
* Provides a secure, single-use, time-to-live (TTL) bounded staging area for raw
|
|
5
|
+
* binary upload buffers received via REST streaming before being committed through
|
|
6
|
+
* the GraphQL `UploadStorageFile` pipeline.
|
|
7
|
+
*
|
|
8
|
+
* Security properties:
|
|
9
|
+
* - Single-use nonce: Token and buffer are atomically destroyed on first consumption.
|
|
10
|
+
* - User binding: Tokens are bound to the authenticated User ID that uploaded them.
|
|
11
|
+
* - Auto-eviction: Abandoned tokens are automatically evicted from RAM after the TTL.
|
|
12
|
+
* - Memory caps: Enforces per-file max size (default 100MB) and total pool capacity (default 500MB).
|
|
13
|
+
*
|
|
14
|
+
* @module @memberjunction/server/rest/UploadTokenManager
|
|
15
|
+
*/
|
|
16
|
+
import { BaseSingleton, UUIDsEqual } from '@memberjunction/global';
|
|
17
|
+
import { LogError } from '@memberjunction/core';
|
|
18
|
+
import { randomBytes } from 'node:crypto';
|
|
19
|
+
/**
|
|
20
|
+
* Ephemeral In-Memory Staged Upload Token Manager.
|
|
21
|
+
*
|
|
22
|
+
* Architecture & Deployment Topology Note:
|
|
23
|
+
* This implementation maintains staged buffers in-process RAM with a 5-minute sliding TTL.
|
|
24
|
+
* In a clustered/multi-replica deployment behind a load balancer, sticky sessions (or routing
|
|
25
|
+
* the subsequent GraphQL `UploadStorageFile` commit to the same node) are recommended.
|
|
26
|
+
* For shared-memory multi-node clustering, an external ephemeral store (such as Redis)
|
|
27
|
+
* can be plugged in behind this interface.
|
|
28
|
+
*/
|
|
29
|
+
export class UploadTokenManager extends BaseSingleton {
|
|
30
|
+
// Public constructor required by BaseSingleton
|
|
31
|
+
constructor() {
|
|
32
|
+
super();
|
|
33
|
+
this._staged = new Map();
|
|
34
|
+
this._totalMemoryBytes = 0;
|
|
35
|
+
this.defaultTtlSeconds = 300; // 5 minutes
|
|
36
|
+
this.maxFileSizeBytes = 100 * 1024 * 1024; // 100 MB
|
|
37
|
+
this.maxUserMemoryBytes = 150 * 1024 * 1024; // 150 MB per user
|
|
38
|
+
this.maxPoolMemoryBytes = 500 * 1024 * 1024; // 500 MB
|
|
39
|
+
}
|
|
40
|
+
static get Instance() {
|
|
41
|
+
return UploadTokenManager.getInstance();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Calculates total active staged bytes currently held for a given user.
|
|
45
|
+
*/
|
|
46
|
+
GetUserMemoryBytes(userId) {
|
|
47
|
+
let total = 0;
|
|
48
|
+
for (const entry of this._staged.values()) {
|
|
49
|
+
if (UUIDsEqual(entry.userId, userId)) {
|
|
50
|
+
total += entry.contentLength;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return total;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Stages a raw binary buffer in RAM, returning a single-use cryptographic token.
|
|
57
|
+
*
|
|
58
|
+
* @throws Error if buffer exceeds single file limit, per-user quota, or global pool memory capacity.
|
|
59
|
+
*/
|
|
60
|
+
Stage(params) {
|
|
61
|
+
const size = params.buffer.length;
|
|
62
|
+
if (size > this.maxFileSizeBytes) {
|
|
63
|
+
throw new Error(`Staged upload exceeds maximum allowed file size of ${Math.round(this.maxFileSizeBytes / (1024 * 1024))}MB (received ${Math.round(size / (1024 * 1024))}MB).`);
|
|
64
|
+
}
|
|
65
|
+
const userCurrentBytes = this.GetUserMemoryBytes(params.userId);
|
|
66
|
+
if (userCurrentBytes + size > this.maxUserMemoryBytes) {
|
|
67
|
+
throw new Error(`Per-user upload memory quota reached (${Math.round(userCurrentBytes / (1024 * 1024))}MB / ${Math.round(this.maxUserMemoryBytes / (1024 * 1024))}MB used). Please wait for active uploads to finalize.`);
|
|
68
|
+
}
|
|
69
|
+
if (this._totalMemoryBytes + size > this.maxPoolMemoryBytes) {
|
|
70
|
+
throw new Error(`Staged upload memory capacity reached (${Math.round(this._totalMemoryBytes / (1024 * 1024))}MB / ${Math.round(this.maxPoolMemoryBytes / (1024 * 1024))}MB used). Please try again shortly.`);
|
|
71
|
+
}
|
|
72
|
+
const token = 'upt_' + randomBytes(32).toString('hex');
|
|
73
|
+
const ttl = params.ttlSeconds || this.defaultTtlSeconds;
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
const expiresAt = now + ttl * 1000;
|
|
76
|
+
const timer = setTimeout(() => {
|
|
77
|
+
this.Evict(token);
|
|
78
|
+
}, ttl * 1000);
|
|
79
|
+
// Unref timer so it doesn't hold open process shutdown in tests or CLI
|
|
80
|
+
if (typeof timer.unref === 'function') {
|
|
81
|
+
timer.unref();
|
|
82
|
+
}
|
|
83
|
+
const entry = {
|
|
84
|
+
token,
|
|
85
|
+
buffer: params.buffer,
|
|
86
|
+
fileName: params.fileName,
|
|
87
|
+
mimeType: params.mimeType,
|
|
88
|
+
contentLength: size,
|
|
89
|
+
userId: params.userId,
|
|
90
|
+
createdAt: now,
|
|
91
|
+
expiresAt,
|
|
92
|
+
timer,
|
|
93
|
+
};
|
|
94
|
+
this._staged.set(token, entry);
|
|
95
|
+
this._totalMemoryBytes += size;
|
|
96
|
+
return token;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Atomically consumes and evicts a staged upload token.
|
|
100
|
+
*
|
|
101
|
+
* @param token The staged upload token.
|
|
102
|
+
* @param claimingUserId The user ID claiming this token. If provided, must match the user who uploaded it.
|
|
103
|
+
* @returns The staged buffer and metadata, or null if the token is invalid, expired, or claimed by the wrong user.
|
|
104
|
+
*/
|
|
105
|
+
Consume(token, claimingUserId) {
|
|
106
|
+
if (!token || !this._staged.has(token)) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const entry = this._staged.get(token);
|
|
110
|
+
// Security check: verify user ownership using UUIDsEqual for robust ID normalization
|
|
111
|
+
if (claimingUserId && entry.userId && !UUIDsEqual(entry.userId, claimingUserId)) {
|
|
112
|
+
LogError(`[UploadTokenManager] Security violation: User '${claimingUserId}' attempted to claim upload token owned by User '${entry.userId}'.`);
|
|
113
|
+
// Evict immediately to prevent further probing
|
|
114
|
+
this.Evict(token);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
// Atomic consumption & memory reclamation
|
|
118
|
+
this.Evict(token);
|
|
119
|
+
return {
|
|
120
|
+
buffer: entry.buffer,
|
|
121
|
+
fileName: entry.fileName,
|
|
122
|
+
mimeType: entry.mimeType,
|
|
123
|
+
contentLength: entry.contentLength,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Evicts a token from RAM and reclaims memory.
|
|
128
|
+
*/
|
|
129
|
+
Evict(token) {
|
|
130
|
+
const entry = this._staged.get(token);
|
|
131
|
+
if (!entry) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
clearTimeout(entry.timer);
|
|
135
|
+
this._staged.delete(token);
|
|
136
|
+
this._totalMemoryBytes = Math.max(0, this._totalMemoryBytes - entry.contentLength);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
/** Total RAM in bytes currently consumed by all active staged uploads. */
|
|
140
|
+
get TotalMemoryBytes() {
|
|
141
|
+
return this._totalMemoryBytes;
|
|
142
|
+
}
|
|
143
|
+
/** Count of currently active staged uploads. */
|
|
144
|
+
get ActiveCount() {
|
|
145
|
+
return this._staged.size;
|
|
146
|
+
}
|
|
147
|
+
/** Clears all active staged uploads. */
|
|
148
|
+
Clear() {
|
|
149
|
+
for (const [, entry] of this._staged) {
|
|
150
|
+
clearTimeout(entry.timer);
|
|
151
|
+
}
|
|
152
|
+
this._staged.clear();
|
|
153
|
+
this._totalMemoryBytes = 0;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=UploadTokenManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UploadTokenManager.js","sourceRoot":"","sources":["../../src/rest/UploadTokenManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAiB1C;;;;;;;;;GASG;AACH,MAAM,OAAO,kBAAmB,SAAQ,aAAiC;IASvE,+CAA+C;IAC/C;QACE,KAAK,EAAE,CAAC;QAVO,YAAO,GAAG,IAAI,GAAG,EAA6B,CAAC;QACxD,sBAAiB,GAAG,CAAC,CAAC;QAEvB,sBAAiB,GAAG,GAAG,CAAC,CAAC,YAAY;QACrC,qBAAgB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,SAAS;QAC/C,uBAAkB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,kBAAkB;QAC1D,uBAAkB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,SAAS;IAKxD,CAAC;IAEM,MAAM,KAAK,QAAQ;QACxB,OAAO,kBAAkB,CAAC,WAAW,EAAsB,CAAC;IAC9D,CAAC;IAED;;OAEG;IACI,kBAAkB,CAAC,MAAc;QACtC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1C,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;gBACrC,KAAK,IAAI,KAAK,CAAC,aAAa,CAAC;YAC/B,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,MAMZ;QACC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;QAElC,IAAI,IAAI,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CACb,sDAAsD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,gBAAgB,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,MAAM,CAC9J,CAAC;QACJ,CAAC;QAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChE,IAAI,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACtD,MAAM,IAAI,KAAK,CACb,yCAAyC,IAAI,CAAC,KAAK,CAAC,gBAAgB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,uDAAuD,CACxM,CAAC;QACJ,CAAC;QAED,IAAI,IAAI,CAAC,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5D,MAAM,IAAI,KAAK,CACb,0CAA0C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,qCAAqC,CAC7L,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC,iBAAiB,CAAC;QACxD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,SAAS,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC;QAEnC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC,CAAC;QAEf,uEAAuE;QACvE,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YACtC,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;QAED,MAAM,KAAK,GAAsB;YAC/B,KAAK;YACL,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,aAAa,EAAE,IAAI;YACnB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,SAAS,EAAE,GAAG;YACd,SAAS;YACT,KAAK;SACN,CAAC;QAEF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC/B,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC;QAE/B,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;;OAMG;IACI,OAAO,CACZ,KAAa,EACb,cAAuB;QAEvB,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACvC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAE,CAAC;QAEvC,qFAAqF;QACrF,IAAI,cAAc,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,CAAC;YAChF,QAAQ,CAAC,kDAAkD,cAAc,oDAAoD,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;YAC/I,+CAA+C;YAC/C,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAClB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,0CAA0C;QAC1C,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAElB,OAAO;YACL,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,aAAa,EAAE,KAAK,CAAC,aAAa;SACnC,CAAC;IACJ,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,KAAa;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,KAAK,CAAC;QACf,CAAC;QAED,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC;QACnF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0EAA0E;IAC1E,IAAW,gBAAgB;QACzB,OAAO,IAAI,CAAC,iBAAiB,CAAC;IAChC,CAAC;IAED,gDAAgD;IAChD,IAAW,WAAW;QACpB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;IAED,wCAAwC;IACjC,KAAK;QACV,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC;IAC7B,CAAC;CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MJServer's implementation of the `DurableEntityActionSubmitter` seam.
|
|
3
|
+
*
|
|
4
|
+
* Turns one After* entity-action dispatch into a single-node durable task graph, which is D14's
|
|
5
|
+
* whole answer to "run this durably": not a new queue, the substrate that already has a claim
|
|
6
|
+
* protocol, restart recovery and orphan reclaim.
|
|
7
|
+
*
|
|
8
|
+
* Registered at boot beside the dispatcher, because the two are one capability — a submitter with
|
|
9
|
+
* no dispatcher writes rows nobody picks up, which is worse than staying inline.
|
|
10
|
+
*
|
|
11
|
+
* @module @memberjunction/server
|
|
12
|
+
*/
|
|
13
|
+
import type { DurableEntityActionRequest, DurableEntityActionSubmission, DurableEntityActionSubmitter } from '@memberjunction/actions-base';
|
|
14
|
+
import { type ProviderFactory } from '@memberjunction/task-graph';
|
|
15
|
+
export declare class DurableEntityActionTaskSubmitter implements DurableEntityActionSubmitter {
|
|
16
|
+
private readonly providerFactory;
|
|
17
|
+
private readonly environmentID;
|
|
18
|
+
/**
|
|
19
|
+
* @param environmentID which environment the tasks belong to. Defaults to the platform default
|
|
20
|
+
* because a save has no environment of its own to inherit — the record's entity is global, and
|
|
21
|
+
* inventing a per-save environment would put a durable dispatch somewhere nobody looks.
|
|
22
|
+
*/
|
|
23
|
+
constructor(providerFactory: ProviderFactory, environmentID?: string);
|
|
24
|
+
Submit(request: DurableEntityActionRequest): Promise<DurableEntityActionSubmission>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=DurableEntityActionTaskSubmitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DurableEntityActionTaskSubmitter.d.ts","sourceRoot":"","sources":["../../src/services/DurableEntityActionTaskSubmitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EACR,0BAA0B,EAC1B,6BAA6B,EAC7B,4BAA4B,EAC/B,MAAM,8BAA8B,CAAC;AAItC,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAEpF,qBAAa,gCAAiC,YAAW,4BAA4B;IAO7E,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAPlC;;;;OAIG;gBAEkB,eAAe,EAAE,eAAe,EAChC,aAAa,GAAE,MAAyD;IAGhF,MAAM,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,6BAA6B,CAAC;CA4CnG"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { LogError } from '@memberjunction/core';
|
|
2
|
+
import { TaskNode } from '@memberjunction/ai-core-plus';
|
|
3
|
+
import { MJEnvironmentEntityExtended } from '@memberjunction/core-entities';
|
|
4
|
+
import { TaskGraphService } from '@memberjunction/task-graph';
|
|
5
|
+
export class DurableEntityActionTaskSubmitter {
|
|
6
|
+
/**
|
|
7
|
+
* @param environmentID which environment the tasks belong to. Defaults to the platform default
|
|
8
|
+
* because a save has no environment of its own to inherit — the record's entity is global, and
|
|
9
|
+
* inventing a per-save environment would put a durable dispatch somewhere nobody looks.
|
|
10
|
+
*/
|
|
11
|
+
constructor(providerFactory, environmentID = MJEnvironmentEntityExtended.DefaultEnvironmentID) {
|
|
12
|
+
this.providerFactory = providerFactory;
|
|
13
|
+
this.environmentID = environmentID;
|
|
14
|
+
}
|
|
15
|
+
async Submit(request) {
|
|
16
|
+
try {
|
|
17
|
+
const spec = {
|
|
18
|
+
workflowName: `${request.ActionName} — ${request.EntityName}`,
|
|
19
|
+
reasoning: `Durable dispatch of entity action binding ${request.EntityActionID} on ` +
|
|
20
|
+
`${request.EntityName} (${request.RecordID}), fired by ${request.InvocationType}.`,
|
|
21
|
+
tasks: [
|
|
22
|
+
TaskNode.Action({
|
|
23
|
+
tempId: 'action',
|
|
24
|
+
name: request.ActionName,
|
|
25
|
+
description: `Run ${request.ActionName} for ${request.EntityName} ${request.RecordID}.`,
|
|
26
|
+
dependsOn: [],
|
|
27
|
+
inputPayload: request.RedactedParams,
|
|
28
|
+
}, { actionName: request.ActionName }),
|
|
29
|
+
],
|
|
30
|
+
// Nothing to say to anyone: this graph exists because a record was saved, not
|
|
31
|
+
// because a person asked a question. Defaulting to 'message' would post an
|
|
32
|
+
// orphaned result into whichever conversation happened to be nearby.
|
|
33
|
+
continuation: 'none',
|
|
34
|
+
// The point of the exercise. A one-node graph constant-folds to in-run execution by
|
|
35
|
+
// default (D9), which is exactly the fire-and-forget behaviour being escaped.
|
|
36
|
+
durable: true,
|
|
37
|
+
};
|
|
38
|
+
const provider = await this.providerFactory.CreateProvider();
|
|
39
|
+
const result = await new TaskGraphService().Submit(spec, {
|
|
40
|
+
EnvironmentID: this.environmentID,
|
|
41
|
+
ContextUser: request.ContextUser,
|
|
42
|
+
Provider: provider,
|
|
43
|
+
});
|
|
44
|
+
return result.Success
|
|
45
|
+
? { Success: true, ParentTaskID: result.ParentTaskID }
|
|
46
|
+
: { Success: false, ErrorMessage: result.ErrorMessage };
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
50
|
+
LogError(`[DurableEntityActionTaskSubmitter] Submission failed for binding ${request.EntityActionID}: ${message}`);
|
|
51
|
+
return { Success: false, ErrorMessage: message };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=DurableEntityActionTaskSubmitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DurableEntityActionTaskSubmitter.js","sourceRoot":"","sources":["../../src/services/DurableEntityActionTaskSubmitter.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAsB,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAwB,MAAM,4BAA4B,CAAC;AAEpF,MAAM,OAAO,gCAAgC;IACzC;;;;OAIG;IACH,YACqB,eAAgC,EAChC,gBAAwB,2BAA2B,CAAC,oBAAoB;QADxE,oBAAe,GAAf,eAAe,CAAiB;QAChC,kBAAa,GAAb,aAAa,CAA2D;IAC1F,CAAC;IAEG,KAAK,CAAC,MAAM,CAAC,OAAmC;QACnD,IAAI,CAAC;YACD,MAAM,IAAI,GAAkB;gBACxB,YAAY,EAAE,GAAG,OAAO,CAAC,UAAU,MAAM,OAAO,CAAC,UAAU,EAAE;gBAC7D,SAAS,EACL,6CAA6C,OAAO,CAAC,cAAc,MAAM;oBACzE,GAAG,OAAO,CAAC,UAAU,KAAK,OAAO,CAAC,QAAQ,eAAe,OAAO,CAAC,cAAc,GAAG;gBACtF,KAAK,EAAE;oBACH,QAAQ,CAAC,MAAM,CACX;wBACI,MAAM,EAAE,QAAQ;wBAChB,IAAI,EAAE,OAAO,CAAC,UAAU;wBACxB,WAAW,EAAE,OAAO,OAAO,CAAC,UAAU,QAAQ,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,QAAQ,GAAG;wBACvF,SAAS,EAAE,EAAE;wBACb,YAAY,EAAE,OAAO,CAAC,cAAc;qBACvC,EACD,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CACrC;iBACJ;gBACD,8EAA8E;gBAC9E,2EAA2E;gBAC3E,qEAAqE;gBACrE,YAAY,EAAE,MAAM;gBACpB,oFAAoF;gBACpF,8EAA8E;gBAC9E,OAAO,EAAE,IAAI;aAChB,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,cAAc,EAAE,CAAC;YAC7D,MAAM,MAAM,GAAG,MAAM,IAAI,gBAAgB,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE;gBACrD,aAAa,EAAE,IAAI,CAAC,aAAa;gBACjC,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,QAAQ,EAAE,QAAQ;aACrB,CAAC,CAAC;YAEH,OAAO,MAAM,CAAC,OAAO;gBACjB,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE;gBACtD,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC3D,QAAQ,CAAC,oEAAoE,OAAO,CAAC,cAAc,KAAK,OAAO,EAAE,CAAC,CAAC;YACnH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;QACrD,CAAC;IACL,CAAC;CACJ"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Worker-mode poll loop for durable integration sync runs (PR 1 item 8).
|
|
3
|
+
* @module MJServer/services
|
|
4
|
+
*/
|
|
5
|
+
import { IntegrationSyncWorkerConfig } from '../config.js';
|
|
6
|
+
/**
|
|
7
|
+
* Polls `CompanyIntegrationRun` for `Status='Queued'` rows, claims each one atomically,
|
|
8
|
+
* executes it, and releases — the enqueue/execute split that lets a sync outlive the
|
|
9
|
+
* request that asked for it.
|
|
10
|
+
*
|
|
11
|
+
* Multiple workers (in this process or others) may poll the same queue: the claim sproc
|
|
12
|
+
* admits exactly one, and every loser simply moves on to the next candidate. Nothing here
|
|
13
|
+
* assumes it is the only worker.
|
|
14
|
+
*/
|
|
15
|
+
export declare class IntegrationSyncWorkerService {
|
|
16
|
+
private config;
|
|
17
|
+
private systemUser;
|
|
18
|
+
private timer;
|
|
19
|
+
private isRunning;
|
|
20
|
+
private polling;
|
|
21
|
+
/** Run IDs currently executing in THIS process — bounds concurrency and prevents self-collision. */
|
|
22
|
+
private inFlight;
|
|
23
|
+
constructor(config: IntegrationSyncWorkerConfig);
|
|
24
|
+
get IsEnabled(): boolean;
|
|
25
|
+
get IsRunning(): boolean;
|
|
26
|
+
/** Number of queued runs this worker is currently executing. */
|
|
27
|
+
get InFlightCount(): number;
|
|
28
|
+
/** Resolve the system user the worker executes syncs as. */
|
|
29
|
+
Initialize(): Promise<void>;
|
|
30
|
+
/** Begin polling the queue. */
|
|
31
|
+
Start(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Stop polling. In-flight runs are left to finish on their own — their leases are
|
|
34
|
+
* renewed by the engine's heartbeat, so killing them here would only strand rows.
|
|
35
|
+
*/
|
|
36
|
+
Stop(): void;
|
|
37
|
+
/**
|
|
38
|
+
* One poll pass: fetch claimable candidates up to the free concurrency slots and start
|
|
39
|
+
* each. Overlapping passes are suppressed so a slow query can't stack up timers.
|
|
40
|
+
*/
|
|
41
|
+
private pollOnce;
|
|
42
|
+
/**
|
|
43
|
+
* Execute one queued run in the background. Losing the claim race is normal — the run
|
|
44
|
+
* is simply someone else's — so it is logged as information, never as an error.
|
|
45
|
+
*/
|
|
46
|
+
private startRun;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=IntegrationSyncWorkerService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IntegrationSyncWorkerService.d.ts","sourceRoot":"","sources":["../../src/services/IntegrationSyncWorkerService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAE3D;;;;;;;;GAQG;AACH,qBAAa,4BAA4B;IACrC,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,oGAAoG;IACpG,OAAO,CAAC,QAAQ,CAAqB;gBAEzB,MAAM,EAAE,2BAA2B;IAI/C,IAAW,SAAS,IAAI,OAAO,CAE9B;IAED,IAAW,SAAS,IAAI,OAAO,CAE9B;IAED,gEAAgE;IAChE,IAAW,aAAa,IAAI,MAAM,CAEjC;IAED,4DAA4D;IAC/C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAWxC,+BAA+B;IACxB,KAAK,IAAI,IAAI;IAoBpB;;;OAGG;IACI,IAAI,IAAI,IAAI;IAUnB;;;OAGG;YACW,QAAQ;IAoBtB;;;OAGG;IACH,OAAO,CAAC,QAAQ;CAoBnB"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Worker-mode poll loop for durable integration sync runs (PR 1 item 8).
|
|
3
|
+
* @module MJServer/services
|
|
4
|
+
*/
|
|
5
|
+
import { LogError, LogStatus } from '@memberjunction/core';
|
|
6
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
7
|
+
import { IntegrationEngine } from '@memberjunction/integration-engine';
|
|
8
|
+
/**
|
|
9
|
+
* Polls `CompanyIntegrationRun` for `Status='Queued'` rows, claims each one atomically,
|
|
10
|
+
* executes it, and releases — the enqueue/execute split that lets a sync outlive the
|
|
11
|
+
* request that asked for it.
|
|
12
|
+
*
|
|
13
|
+
* Multiple workers (in this process or others) may poll the same queue: the claim sproc
|
|
14
|
+
* admits exactly one, and every loser simply moves on to the next candidate. Nothing here
|
|
15
|
+
* assumes it is the only worker.
|
|
16
|
+
*/
|
|
17
|
+
export class IntegrationSyncWorkerService {
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.systemUser = null;
|
|
20
|
+
this.timer = null;
|
|
21
|
+
this.isRunning = false;
|
|
22
|
+
this.polling = false;
|
|
23
|
+
/** Run IDs currently executing in THIS process — bounds concurrency and prevents self-collision. */
|
|
24
|
+
this.inFlight = new Set();
|
|
25
|
+
this.config = config;
|
|
26
|
+
}
|
|
27
|
+
get IsEnabled() {
|
|
28
|
+
return this.config.enabled;
|
|
29
|
+
}
|
|
30
|
+
get IsRunning() {
|
|
31
|
+
return this.isRunning;
|
|
32
|
+
}
|
|
33
|
+
/** Number of queued runs this worker is currently executing. */
|
|
34
|
+
get InFlightCount() {
|
|
35
|
+
return this.inFlight.size;
|
|
36
|
+
}
|
|
37
|
+
/** Resolve the system user the worker executes syncs as. */
|
|
38
|
+
async Initialize() {
|
|
39
|
+
if (!this.config.enabled)
|
|
40
|
+
return;
|
|
41
|
+
const email = this.config.systemUserEmail;
|
|
42
|
+
this.systemUser = UserCache.Users.find(u => u.Email?.toLowerCase() === email.toLowerCase()) ?? null;
|
|
43
|
+
if (!this.systemUser) {
|
|
44
|
+
throw new Error(`[IntegrationSyncWorker] System user not found with email: ${email}`);
|
|
45
|
+
}
|
|
46
|
+
await IntegrationEngine.Instance.Config(false, this.systemUser);
|
|
47
|
+
}
|
|
48
|
+
/** Begin polling the queue. */
|
|
49
|
+
Start() {
|
|
50
|
+
if (!this.config.enabled || this.isRunning)
|
|
51
|
+
return;
|
|
52
|
+
if (!this.systemUser) {
|
|
53
|
+
throw new Error('[IntegrationSyncWorker] Not initialized — call Initialize() first');
|
|
54
|
+
}
|
|
55
|
+
this.isRunning = true;
|
|
56
|
+
this.timer = setInterval(() => void this.pollOnce(), this.config.pollingIntervalMs);
|
|
57
|
+
// Standard level, NOT verboseOnly: this states a deployment-shaping fact — that this process
|
|
58
|
+
// executes queued syncs — and it's one line per process lifetime. Stop() already logs at
|
|
59
|
+
// standard level, so hiding Start() produced logs showing a worker stopping that never
|
|
60
|
+
// started, and no way to confirm from logs that a deployment is in worker mode at all.
|
|
61
|
+
LogStatus(`🔄 Integration Sync Worker: polling every ${Math.round(this.config.pollingIntervalMs / 1000)}s, ` +
|
|
62
|
+
`up to ${this.config.maxConcurrentRuns} concurrent run(s)`);
|
|
63
|
+
// Don't wait a full interval to drain a queue that already has work.
|
|
64
|
+
void this.pollOnce();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Stop polling. In-flight runs are left to finish on their own — their leases are
|
|
68
|
+
* renewed by the engine's heartbeat, so killing them here would only strand rows.
|
|
69
|
+
*/
|
|
70
|
+
Stop() {
|
|
71
|
+
if (!this.isRunning)
|
|
72
|
+
return;
|
|
73
|
+
if (this.timer) {
|
|
74
|
+
clearInterval(this.timer);
|
|
75
|
+
this.timer = null;
|
|
76
|
+
}
|
|
77
|
+
this.isRunning = false;
|
|
78
|
+
LogStatus(`[IntegrationSyncWorker] Stopped polling (${this.inFlight.size} run(s) still in flight)`);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* One poll pass: fetch claimable candidates up to the free concurrency slots and start
|
|
82
|
+
* each. Overlapping passes are suppressed so a slow query can't stack up timers.
|
|
83
|
+
*/
|
|
84
|
+
async pollOnce() {
|
|
85
|
+
if (this.polling || !this.isRunning || !this.systemUser)
|
|
86
|
+
return;
|
|
87
|
+
const slots = this.config.maxConcurrentRuns - this.inFlight.size;
|
|
88
|
+
if (slots <= 0)
|
|
89
|
+
return;
|
|
90
|
+
this.polling = true;
|
|
91
|
+
try {
|
|
92
|
+
const candidates = await IntegrationEngine.PollQueuedRuns(this.systemUser, slots);
|
|
93
|
+
for (const candidate of candidates) {
|
|
94
|
+
if (this.inFlight.has(candidate.ID))
|
|
95
|
+
continue;
|
|
96
|
+
this.startRun(candidate.ID);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
LogError(`[IntegrationSyncWorker] Poll failed`, undefined, err);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
this.polling = false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Execute one queued run in the background. Losing the claim race is normal — the run
|
|
108
|
+
* is simply someone else's — so it is logged as information, never as an error.
|
|
109
|
+
*/
|
|
110
|
+
startRun(runID) {
|
|
111
|
+
if (!this.systemUser)
|
|
112
|
+
return;
|
|
113
|
+
this.inFlight.add(runID);
|
|
114
|
+
void IntegrationEngine.Instance
|
|
115
|
+
.ExecuteQueuedRun(runID, this.systemUser)
|
|
116
|
+
.then(result => {
|
|
117
|
+
if (result.Success) {
|
|
118
|
+
LogStatus(`[IntegrationSyncWorker] Run ${runID} completed: ${result.RecordsProcessed} record(s) processed`);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
LogStatus(`[IntegrationSyncWorker] Run ${runID} did not complete: ${result.ErrorMessage ?? 'unknown reason'}`);
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
.catch(err => {
|
|
125
|
+
LogError(`[IntegrationSyncWorker] Run ${runID} threw`, undefined, err);
|
|
126
|
+
})
|
|
127
|
+
.finally(() => {
|
|
128
|
+
this.inFlight.delete(runID);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=IntegrationSyncWorkerService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IntegrationSyncWorkerService.js","sourceRoot":"","sources":["../../src/services/IntegrationSyncWorkerService.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @module MJServer/services
|
|
4
4
|
*/
|
|
5
5
|
import { LogError, LogStatus, LogStatusEx } from '@memberjunction/core';
|
|
6
|
-
import { UserCache } from '@memberjunction/
|
|
6
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
7
7
|
import { SchedulingEngine } from '@memberjunction/scheduling-engine';
|
|
8
8
|
/**
|
|
9
9
|
* Service for managing scheduled jobs lifecycle
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScheduledJobsService.js","sourceRoot":"","sources":["../../src/services/ScheduledJobsService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAY,MAAM,sBAAsB,CAAC;AAClF,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"ScheduledJobsService.js","sourceRoot":"","sources":["../../src/services/ScheduledJobsService.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,15 +19,5 @@
|
|
|
19
19
|
import { UserInfo } from '@memberjunction/core';
|
|
20
20
|
import { TaskGraphDispatcher } from '@memberjunction/task-graph';
|
|
21
21
|
import sql from 'mssql';
|
|
22
|
-
/**
|
|
23
|
-
* Starts one dispatcher for this process and returns it.
|
|
24
|
-
*
|
|
25
|
-
* The instance ID must be stable for the process lifetime and distinct per instance — it is what
|
|
26
|
-
* lets reconciliation tell "my own orphaned work, reclaim it" from "another live instance's work,
|
|
27
|
-
* leave it alone". `pid` plus hostname satisfies both: stable while the process lives, and never
|
|
28
|
-
* reused by a peer.
|
|
29
|
-
*
|
|
30
|
-
* The dispatcher self-registers with `ShutdownRegistry`, so the caller does not wire teardown.
|
|
31
|
-
*/
|
|
32
22
|
export declare function StartTaskGraphDispatcher(pool: sql.ConnectionPool, contextUser: UserInfo, instanceID?: string): Promise<TaskGraphDispatcher>;
|
|
33
23
|
//# sourceMappingURL=StartTaskGraphDispatcher.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartTaskGraphDispatcher.d.ts","sourceRoot":"","sources":["../../src/services/StartTaskGraphDispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAA2B,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"StartTaskGraphDispatcher.d.ts","sourceRoot":"","sources":["../../src/services/StartTaskGraphDispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAA2B,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAE1F,OAAO,GAAG,MAAM,OAAO,CAAC;AAkCxB,wBAAsB,wBAAwB,CAC1C,IAAI,EAAE,GAAG,CAAC,cAAc,EACxB,WAAW,EAAE,QAAQ,EACrB,UAAU,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,mBAAmB,CAAC,CAsC9B"}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { LoadTaskGraphOperations, TaskGraphDispatcher } from '@memberjunction/task-graph';
|
|
2
|
+
import { TaskGraphPromptRunner } from './TaskGraphPromptRunner.js';
|
|
3
|
+
import { DurableEntityActionRegistry } from '@memberjunction/actions-base';
|
|
2
4
|
import { CreateTaskGraphProviderFactory } from './TaskGraphProviderFactory.js';
|
|
5
|
+
import { TaskGraphActionRunner } from './TaskGraphActionRunner.js';
|
|
3
6
|
import { TaskGraphAgentRunner } from './TaskGraphAgentRunner.js';
|
|
7
|
+
import { randomBytes } from 'node:crypto';
|
|
8
|
+
import { DurableEntityActionTaskSubmitter } from './DurableEntityActionTaskSubmitter.js';
|
|
4
9
|
import { TaskGraphContinuationDeliverer } from './TaskGraphContinuationDeliverer.js';
|
|
5
10
|
import { PubSubManager } from '../generic/PubSubManager.js';
|
|
6
11
|
import { TaskGraphFrameBroadcaster } from '../resolvers/TaskGraphFrameResolver.js';
|
|
@@ -14,18 +19,48 @@ import { TaskGraphFrameBroadcaster } from '../resolvers/TaskGraphFrameResolver.j
|
|
|
14
19
|
*
|
|
15
20
|
* The dispatcher self-registers with `ShutdownRegistry`, so the caller does not wire teardown.
|
|
16
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* A per-process identity for the claim protocol.
|
|
24
|
+
*
|
|
25
|
+
* Host and pid are kept because they are what a human reads in a log; the random suffix is what
|
|
26
|
+
* makes the value actually unique. Generated once per call, so a restart is a new instance — which
|
|
27
|
+
* is correct: the previous process's claims should expire rather than be inherited.
|
|
28
|
+
*/
|
|
29
|
+
function defaultInstanceID() {
|
|
30
|
+
const host = process.env.HOSTNAME ?? 'mjapi';
|
|
31
|
+
const entropy = randomBytes(4).toString('hex');
|
|
32
|
+
return `${host}-${process.pid}-${entropy}`;
|
|
33
|
+
}
|
|
17
34
|
export async function StartTaskGraphDispatcher(pool, contextUser, instanceID) {
|
|
18
35
|
LoadTaskGraphOperations();
|
|
19
36
|
const providerFactory = CreateTaskGraphProviderFactory(pool);
|
|
20
37
|
const pubSub = PubSubManager.Instance.PubSubEngine;
|
|
21
|
-
const dispatcher = new TaskGraphDispatcher(providerFactory, new TaskGraphAgentRunner(), contextUser,
|
|
38
|
+
const dispatcher = new TaskGraphDispatcher(providerFactory, new TaskGraphAgentRunner(), contextUser,
|
|
39
|
+
// A RANDOM SUFFIX, because host+pid is not unique in the deployments this runs in (C2).
|
|
40
|
+
// `HOSTNAME` is not exported to child processes under systemd or pm2, and a containerised
|
|
41
|
+
// process is routinely pid 1 — so two hosts default to the same `mjapi-1`. A shared instance
|
|
42
|
+
// id defeats every `ClaimedBy=@me` guard at once: A's heartbeat renews B's lease, and A's
|
|
43
|
+
// stale terminal write lands over B's live execution. An explicit `instanceID` still wins,
|
|
44
|
+
// for hosts that have a real identity to supply.
|
|
45
|
+
{ InstanceID: instanceID ?? defaultInstanceID() },
|
|
22
46
|
// Without this the dispatcher had nowhere to deliver: a finished graph logged its outcome,
|
|
23
47
|
// marked itself delivered, and said nothing to the conversation that asked for it.
|
|
24
48
|
new TaskGraphContinuationDeliverer(providerFactory, contextUser),
|
|
25
49
|
// Live signal. Supplied only when a PubSub engine exists — in a worker or a test there is
|
|
26
50
|
// nobody to publish to, and the dispatcher runs identically without one.
|
|
27
|
-
pubSub ? new TaskGraphFrameBroadcaster(pubSub) : undefined
|
|
51
|
+
pubSub ? new TaskGraphFrameBroadcaster(pubSub) : undefined,
|
|
52
|
+
// Action nodes — the third execution shape, beside agents and people. Durable After* entity
|
|
53
|
+
// actions land as these.
|
|
54
|
+
new TaskGraphActionRunner(),
|
|
55
|
+
// Prompt nodes. Without this a Flow agent with a Prompt step submits and then
|
|
56
|
+
// stalls on a task nothing can execute — which is why the shipped User Onboarding
|
|
57
|
+
// Flow Agent could not run at all before this seam existed.
|
|
58
|
+
new TaskGraphPromptRunner());
|
|
28
59
|
await dispatcher.Start();
|
|
60
|
+
// Registered only AFTER the dispatcher is running, and only here. A submitter without a
|
|
61
|
+
// dispatcher writes Task rows nobody picks up — strictly worse than staying inline, because the
|
|
62
|
+
// work is now both undone and invisible in the place it used to be logged.
|
|
63
|
+
DurableEntityActionRegistry.Instance.Register(new DurableEntityActionTaskSubmitter(providerFactory));
|
|
29
64
|
return dispatcher;
|
|
30
65
|
}
|
|
31
66
|
//# sourceMappingURL=StartTaskGraphDispatcher.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartTaskGraphDispatcher.js","sourceRoot":"","sources":["../../src/services/StartTaskGraphDispatcher.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"StartTaskGraphDispatcher.js","sourceRoot":"","sources":["../../src/services/StartTaskGraphDispatcher.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,8BAA8B,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gCAAgC,EAAE,MAAM,uCAAuC,CAAC;AACzF,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AAEnF;;;;;;;;;GASG;AACH;;;;;;GAMG;AACH,SAAS,iBAAiB;IACtB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,OAAO,CAAC;IAC7C,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC/C,OAAO,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC1C,IAAwB,EACxB,WAAqB,EACrB,UAAmB;IAEnB,uBAAuB,EAAE,CAAC;IAE1B,MAAM,eAAe,GAAG,8BAA8B,CAAC,IAAI,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,YAAY,CAAC;IACnD,MAAM,UAAU,GAAG,IAAI,mBAAmB,CACtC,eAAe,EACf,IAAI,oBAAoB,EAAE,EAC1B,WAAW;IACX,wFAAwF;IACxF,0FAA0F;IAC1F,6FAA6F;IAC7F,0FAA0F;IAC1F,2FAA2F;IAC3F,iDAAiD;IACjD,EAAE,UAAU,EAAE,UAAU,IAAI,iBAAiB,EAAE,EAAE;IACjD,2FAA2F;IAC3F,mFAAmF;IACnF,IAAI,8BAA8B,CAAC,eAAe,EAAE,WAAW,CAAC;IAChE,0FAA0F;IAC1F,yEAAyE;IACzE,MAAM,CAAC,CAAC,CAAC,IAAI,yBAAyB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;IAC1D,4FAA4F;IAC5F,yBAAyB;IACzB,IAAI,qBAAqB,EAAE;IAC3B,8EAA8E;IAC9E,kFAAkF;IAClF,4DAA4D;IAC5D,IAAI,qBAAqB,EAAE,CAC9B,CAAC;IACF,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;IAEzB,wFAAwF;IACxF,gGAAgG;IAChG,2EAA2E;IAC3E,2BAA2B,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,gCAAgC,CAAC,eAAe,CAAC,CAAC,CAAC;IAErG,OAAO,UAAU,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { TaskActionRunner, TaskActionRunParams, TaskActionRunResult } from '@memberjunction/task-graph';
|
|
2
|
+
export declare class TaskGraphActionRunner implements TaskActionRunner {
|
|
3
|
+
RunActionForTask(params: TaskActionRunParams): Promise<TaskActionRunResult>;
|
|
4
|
+
/**
|
|
5
|
+
* Projects the action's parameters onto the flat result a workflow step is expected to produce.
|
|
6
|
+
*
|
|
7
|
+
* **Why not return `result.Params` as-is.** That is an `ActionParam[]`, and every consumer of a
|
|
8
|
+
* step's output addresses fields BY NAME: an output mapping says `{"CurrentPrice": "stockPrice"}`,
|
|
9
|
+
* and a branch condition then reads `payload.stockPrice`. Handed an array, the mapping finds no
|
|
10
|
+
* `CurrentPrice`, writes nothing, and the condition evaluates against `undefined` — which is
|
|
11
|
+
* falsy rather than erroneous, so the workflow takes the other branch and reports success. That
|
|
12
|
+
* is exactly what the Demo workflow did: step 1 completed with an output payload of `{}` and
|
|
13
|
+
* every downstream branch was skipped, with nothing anywhere reporting a problem.
|
|
14
|
+
*
|
|
15
|
+
* Only `Output` and `Both` params are included. Echoing the inputs back would let a step's own
|
|
16
|
+
* parameters masquerade as its results, so an output mapping could "succeed" by reading the
|
|
17
|
+
* value the step was given rather than the one it produced.
|
|
18
|
+
*/
|
|
19
|
+
private buildOutput;
|
|
20
|
+
/**
|
|
21
|
+
* Rebuilds the action's parameters from the task's stored payload.
|
|
22
|
+
*
|
|
23
|
+
* **The values here have been through redaction**, because `Task.InputPayload` is persistent,
|
|
24
|
+
* user-visible storage and nothing writes a raw `ActionParam[]` there. A parameter the binding
|
|
25
|
+
* marked as not-logged therefore arrives absent rather than secret — the action sees a missing
|
|
26
|
+
* value, which is the honest consequence of choosing not to persist it, and the reason durable
|
|
27
|
+
* dispatch is opt-in per binding rather than the default.
|
|
28
|
+
*
|
|
29
|
+
* Dependency outputs are merged underneath the task's own input so a node's explicit parameters
|
|
30
|
+
* always win over an upstream node that happened to emit the same key.
|
|
31
|
+
*/
|
|
32
|
+
private buildParams;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=TaskGraphActionRunner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskGraphActionRunner.d.ts","sourceRoot":"","sources":["../../src/services/TaskGraphActionRunner.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAE7G,qBAAa,qBAAsB,YAAW,gBAAgB;IAC7C,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAmCxF;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,WAAW;IAQnB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,WAAW;CAUtB"}
|