@memberjunction/server 6.2.0-edge.5 → 6.2.0-edge.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 +27 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +3 -3
- package/dist/agentSessions/SessionJanitor.js +3 -3
- package/dist/agentSessions/SessionManager.d.ts +15 -5
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +34 -12
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +2 -0
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +51 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.d.ts +10 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +37 -1
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/localDevAuth.d.ts +79 -0
- package/dist/auth/localDevAuth.d.ts.map +1 -0
- package/dist/auth/localDevAuth.js +439 -0
- package/dist/auth/localDevAuth.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +12 -0
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.d.ts +1 -1
- package/dist/auth/sessionUserClone.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.js +3 -2
- package/dist/auth/sessionUserClone.js.map +1 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
- package/dist/broadcast/index.d.ts +6 -0
- package/dist/broadcast/index.d.ts.map +1 -0
- package/dist/broadcast/index.js +6 -0
- package/dist/broadcast/index.js.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
- package/dist/cache.d.ts +3 -2
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js.map +1 -1
- package/dist/config.d.ts +496 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +61 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +3 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +29 -8
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts.map +1 -1
- package/dist/coreRoutes.js +14 -0
- package/dist/coreRoutes.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +635 -14
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +3366 -418
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +50 -30
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +154 -79
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/PubSubManager.d.ts +10 -0
- package/dist/generic/PubSubManager.d.ts.map +1 -1
- package/dist/generic/PubSubManager.js +44 -1
- package/dist/generic/PubSubManager.js.map +1 -1
- package/dist/generic/PushStatusResolver.d.ts +39 -6
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +93 -11
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +65 -25
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +201 -101
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +11 -3
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +79 -13
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
- package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
- package/dist/generic/actionOutputFieldSecurity.js +47 -0
- package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
- package/dist/generic/fieldSecurityTransport.d.ts +30 -0
- package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
- package/dist/generic/fieldSecurityTransport.js +30 -0
- package/dist/generic/fieldSecurityTransport.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +122 -66
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +6 -1
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.js +50 -1
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts +8 -6
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.js +15 -6
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
- package/dist/realtimeSessions/sessionAccess.d.ts +88 -11
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +125 -22
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.js +19 -3
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +201 -19
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/host-identity.d.ts +1 -1
- package/dist/realtimeWidget/host-identity.js +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts +20 -1
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +17 -1
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +19 -1
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +63 -12
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +1 -0
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
- package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/AgentSessionResolver.js +40 -17
- package/dist/resolvers/AgentSessionResolver.js.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
- package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +28 -0
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +117 -21
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +1 -0
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +5 -0
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +13 -4
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +45 -7
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.js +31 -3
- package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
- package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.js +5 -3
- package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +20 -6
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +15 -6
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RSUResolver.d.ts.map +1 -1
- package/dist/resolvers/RSUResolver.js +12 -0
- package/dist/resolvers/RSUResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +16 -2
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +82 -28
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +209 -64
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +4 -4
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTestResolver.js +3 -1
- package/dist/resolvers/RunTestResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +4 -0
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +1 -0
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +17 -2
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +60 -15
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts +1 -1
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js +2 -1
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts +6 -0
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +19 -5
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +8 -5
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.js +7 -7
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts +6 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +22 -5
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/serverRoleConfigUnits.d.ts +29 -0
- package/dist/serverRoleConfigUnits.d.ts.map +1 -0
- package/dist/serverRoleConfigUnits.js +33 -0
- package/dist/serverRoleConfigUnits.js.map +1 -0
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +5 -0
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +42 -15
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +98 -98
- package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
- package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
- package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
- package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
- package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
- package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
- package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +206 -3
- package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
- package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
- package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
- package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
- package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +37 -4
- package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
- package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
- package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
- package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +68 -13
- package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
- package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +17 -27
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +8 -2
- package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
- package/src/__tests__/RunTemplateResolver.test.ts +35 -2
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
- package/src/__tests__/SessionManager.test.ts +95 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +114 -3
- package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
- package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
- package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
- package/src/__tests__/WidgetSessionService.test.ts +583 -4
- package/src/__tests__/apiRootBoot.test.ts +1 -1
- package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
- package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +45 -0
- package/src/__tests__/fieldSecurityFixtures.ts +91 -0
- package/src/__tests__/globalCors.test.ts +3 -3
- package/src/__tests__/localDevAuth.test.ts +196 -0
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +57 -1
- package/src/__tests__/realtimeRecordingHandler.test.ts +19 -2
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
- package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
- package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
- package/src/__tests__/resolverBase.fls.test.ts +146 -97
- package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
- package/src/__tests__/runParamsTimeZone.test.ts +179 -0
- package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
- package/src/__tests__/runViewProperties.test.ts +114 -0
- package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
- package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
- package/src/__tests__/sessionUserClone.test.ts +5 -1
- package/src/__tests__/startupSweepOrder.test.ts +40 -0
- package/src/__tests__/unifiedAuth.test.ts +28 -0
- package/src/__tests__/widgetGuestElevation.test.ts +2 -0
- package/src/agentSessions/SessionJanitor.ts +3 -3
- package/src/agentSessions/SessionManager.ts +35 -12
- package/src/auth/AuthProviderCatalogRouter.ts +3 -0
- package/src/auth/index.ts +56 -12
- package/src/auth/initializeProviders.ts +43 -1
- package/src/auth/localDevAuth.ts +528 -0
- package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
- package/src/auth/magicLink/types.ts +10 -0
- package/src/auth/sessionUserClone.ts +3 -2
- package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
- package/src/broadcast/index.ts +5 -0
- package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
- package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
- package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
- package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
- package/src/cache.ts +2 -1
- package/src/config.ts +69 -0
- package/src/context.ts +32 -8
- package/src/coreRoutes.ts +15 -0
- package/src/generated/graphql-schemas/__mj.ts +2239 -102
- package/src/generic/CacheInvalidationResolver.ts +135 -78
- package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
- package/src/generic/PubSubManager.ts +54 -1
- package/src/generic/PushStatusResolver.ts +108 -11
- package/src/generic/ResolverBase.ts +237 -107
- package/src/generic/RunViewResolver.ts +80 -15
- package/src/generic/actionOutputFieldSecurity.ts +75 -0
- package/src/generic/fieldSecurityTransport.ts +62 -0
- package/src/index.ts +127 -68
- package/src/integration/CustomColumnPromoter.ts +6 -1
- package/src/logging/variablesLoggingMiddleware.ts +59 -3
- package/src/realtimeSessions/README.md +7 -3
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +20 -6
- package/src/realtimeSessions/sessionAccess.ts +181 -25
- package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
- package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
- package/src/realtimeSessions/verificationWorkflow.ts +3 -1
- package/src/realtimeWidget/WidgetRouter.ts +22 -4
- package/src/realtimeWidget/WidgetSessionService.ts +252 -17
- package/src/realtimeWidget/host-identity.ts +1 -1
- package/src/realtimeWidget/widgetCore.ts +30 -1
- package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
- package/src/resolvers/ActionResolver.ts +78 -15
- package/src/resolvers/AdhocQueryResolver.ts +1 -0
- package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
- package/src/resolvers/AgentSessionResolver.ts +46 -18
- package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
- package/src/resolvers/FetchEntityVectorsResolver.ts +132 -21
- package/src/resolvers/FileCategoryResolver.ts +1 -0
- package/src/resolvers/FileResolver.ts +14 -4
- package/src/resolvers/IntegrationDiscoveryResolver.ts +49 -7
- package/src/resolvers/MergeRecordsResolver.ts +23 -2
- package/src/resolvers/PipelineProgressResolver.ts +12 -3
- package/src/resolvers/QueryResolver.ts +20 -6
- package/src/resolvers/QuerySystemUserResolver.ts +15 -6
- package/src/resolvers/RSUResolver.ts +12 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +104 -33
- package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
- package/src/resolvers/RunAIAgentResolver.ts +258 -82
- package/src/resolvers/RunTemplateResolver.ts +6 -4
- package/src/resolvers/RunTestResolver.ts +3 -1
- package/src/resolvers/SyncDataResolver.ts +4 -0
- package/src/resolvers/TestQuerySQLResolver.ts +1 -0
- package/src/resolvers/TransactionGroupResolver.ts +66 -15
- package/src/resolvers/UserViewResolver.ts +2 -1
- package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
- package/src/rest/EntityCRUDHandler.ts +22 -5
- package/src/rest/RESTEndpointHandler.ts +8 -5
- package/src/rest/RealtimeRecordingHandler.ts +13 -10
- package/src/rest/ViewOperationsHandler.ts +24 -6
- package/src/serverRoleConfigUnits.ts +48 -0
- package/src/services/TaskGraphContinuationDeliverer.ts +52 -20
- package/src/types.ts +23 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AggregateExpression,
|
|
3
|
+
AssertClientInputWritableForUser,
|
|
3
4
|
BaseEntity,
|
|
4
5
|
BaseEntityEvent,
|
|
5
6
|
CompositeKey,
|
|
@@ -14,8 +15,10 @@ import {
|
|
|
14
15
|
KeyValuePair,
|
|
15
16
|
LogDebug,
|
|
16
17
|
LogError,
|
|
18
|
+
LogErrorEx,
|
|
17
19
|
LogStatus,
|
|
18
20
|
Metadata,
|
|
21
|
+
ProjectEntityValuesForUser,
|
|
19
22
|
ReadableFieldsTransportKey,
|
|
20
23
|
RecordChangeFieldSecurityProjector,
|
|
21
24
|
RecordChangePayloadFields,
|
|
@@ -23,8 +26,17 @@ import {
|
|
|
23
26
|
RunViewParams,
|
|
24
27
|
RunViewResult,
|
|
25
28
|
UserInfo,
|
|
29
|
+
ViewPropertyValues,
|
|
26
30
|
} from '@memberjunction/core';
|
|
27
|
-
import { MJAuditLogEntity, MJErrorLogEntity, MJUserViewEntityExtended } from '@memberjunction/core-entities';
|
|
31
|
+
import { MJAuditLogEntity, MJErrorLogEntity, MJUserViewEntityExtended, UserViewEngine } from '@memberjunction/core-entities';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Extended RunViewResult carrying a non-enumerable reference to the resolved view entity
|
|
35
|
+
* so downstream resolvers (e.g. RunViewResolver) do not re-query `MJ: User Views` a third time.
|
|
36
|
+
*/
|
|
37
|
+
export type RunViewResultWithResolvedView<T = unknown> = RunViewResult<T> & {
|
|
38
|
+
_resolvedViewEntity?: MJUserViewEntityExtended;
|
|
39
|
+
};
|
|
28
40
|
import { SQLServerDataProvider } from '@memberjunction/sqlserver-dataprovider';
|
|
29
41
|
import { ClientClauseScreen, UserCache, type EntityBaseViewAllowList } from '@memberjunction/generic-database-provider';
|
|
30
42
|
import { PubSubEngine, AuthorizationError } from 'type-graphql';
|
|
@@ -37,11 +49,11 @@ import { httpTransport, CloudEvent, emitterFor } from 'cloudevents';
|
|
|
37
49
|
import { RunViewGenericParams, UserPayload } from '../types.js';
|
|
38
50
|
import { RunDynamicViewInput, RunViewByIDInput, RunViewByNameInput } from './RunViewResolver.js';
|
|
39
51
|
import { DeleteOptionsInput } from './DeleteOptionsInput.js';
|
|
40
|
-
import { MJEvent, MJEventType, MJGlobal, ENCRYPTED_SENTINEL, ReplaceByteArraysWithBase64, EscapeSQLString, IsValueEncrypted, IsOnlyTimezoneShift } from '@memberjunction/global';
|
|
52
|
+
import { MJEvent, MJEventType, MJGlobal, UUIDsEqual, ENCRYPTED_SENTINEL, ReplaceByteArraysWithBase64, EscapeSQLString, IsValueEncrypted, IsOnlyTimezoneShift } from '@memberjunction/global';
|
|
41
53
|
import { SQLParser, IsBalancedSQLFragment, LexSQL } from '@memberjunction/sql-parser';
|
|
42
54
|
import type { SQLParserDialect } from '@memberjunction/sql-dialect';
|
|
43
55
|
import { EncryptionEngine, IsEncryptionKeyUnavailable, ReportEncryptionKeyUnavailable } from '@memberjunction/encryption';
|
|
44
|
-
import { PUSH_STATUS_UPDATES_TOPIC, PublishStatusUpdate } from './PushStatusResolver.js';
|
|
56
|
+
import { PUSH_STATUS_UPDATES_TOPIC, PublishStatusUpdate, StatusUpdateOwnerID } from './PushStatusResolver.js';
|
|
45
57
|
import { CACHE_INVALIDATION_TOPIC, MayBroadcastRecordData } from './CacheInvalidationResolver.js';
|
|
46
58
|
import { PubSubManager } from './PubSubManager.js';
|
|
47
59
|
import { FieldMapper } from '@memberjunction/graphql-dataprovider';
|
|
@@ -51,11 +63,18 @@ import { Subscription } from 'rxjs';
|
|
|
51
63
|
* Entities whose update is always hydrated from the stored row, never from the client's `OldValues___`, though they
|
|
52
64
|
* do not track record changes: their server-side write rules compare against the prior state, which a client could
|
|
53
65
|
* otherwise supply itself (lower-case names). See `ResolverBase.MustLoadTruthFromDatabase`.
|
|
66
|
+
*
|
|
67
|
+
* `mj: conversations` and `mj: ai agent sessions` carry the anonymous realtime-session ownership key
|
|
68
|
+
* (`ExternalID` / `ConversationID`), which their server entities refuse an anonymous visitor to change — a rule
|
|
69
|
+
* that decides on `Dirty`, so the baseline must be the stored row, not a client-supplied one (#4931).
|
|
54
70
|
*/
|
|
55
71
|
const ENTITIES_THAT_ALWAYS_LOAD_STORED_ROW: ReadonlySet<string> = new Set([
|
|
56
72
|
'mj: record changes',
|
|
57
73
|
'mj: conversation details',
|
|
58
74
|
'mj: data context items',
|
|
75
|
+
'mj: conversations',
|
|
76
|
+
'mj: ai agent sessions',
|
|
77
|
+
'mj: ai agent runs',
|
|
59
78
|
]);
|
|
60
79
|
|
|
61
80
|
export class ResolverBase {
|
|
@@ -120,7 +139,7 @@ export class ResolverBase {
|
|
|
120
139
|
if (!entityInfo) throw new Error(`Entity ${entityName} not found in metadata`);
|
|
121
140
|
|
|
122
141
|
const denied = deniedReadFields ?? (
|
|
123
|
-
entityInfo.
|
|
142
|
+
entityInfo.FieldLevelSecurityApplies && contextUser
|
|
124
143
|
? entityInfo.GetDeniedReadFields(contextUser)
|
|
125
144
|
: null
|
|
126
145
|
);
|
|
@@ -369,6 +388,7 @@ export class ResolverBase {
|
|
|
369
388
|
if (!loaded) {
|
|
370
389
|
return null;
|
|
371
390
|
}
|
|
391
|
+
// fls-guard: MapFieldNamesToCodeNames drops the fields the caller may not read
|
|
372
392
|
const mapped = await this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll(), contextUser);
|
|
373
393
|
return mapped as T;
|
|
374
394
|
}
|
|
@@ -559,6 +579,53 @@ export class ResolverBase {
|
|
|
559
579
|
}
|
|
560
580
|
}
|
|
561
581
|
|
|
582
|
+
/**
|
|
583
|
+
* Resolves a User View by name, consulting the in-memory {@link UserViewEngine} cache first
|
|
584
|
+
* and falling back to a database `RunView` only on a cache miss.
|
|
585
|
+
*/
|
|
586
|
+
protected async resolveUserViewByName(
|
|
587
|
+
viewName: string,
|
|
588
|
+
provider: DatabaseProviderBase,
|
|
589
|
+
contextUser?: UserInfo,
|
|
590
|
+
): Promise<MJUserViewEntityExtended | null> {
|
|
591
|
+
if (UserViewEngine.Instance.Loaded) {
|
|
592
|
+
const cached = UserViewEngine.Instance.GetViewByName(viewName);
|
|
593
|
+
if (cached) {
|
|
594
|
+
return cached;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const rv = provider as unknown as IRunViewProvider;
|
|
598
|
+
const result = await rv.RunView<MJUserViewEntityExtended>({
|
|
599
|
+
EntityName: 'MJ: User Views',
|
|
600
|
+
// SECURITY: the view name is client-supplied and lands inside a SQL literal.
|
|
601
|
+
ExtraFilter: "Name='" + EscapeSQLString(viewName) + "'",
|
|
602
|
+
}, contextUser);
|
|
603
|
+
if (result && result.Success && result.Results.length > 0) {
|
|
604
|
+
return result.Results[0];
|
|
605
|
+
}
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Resolves a User View by ID, consulting the in-memory {@link UserViewEngine} cache first
|
|
611
|
+
* and falling back to an entity `Load()` only on a cache miss.
|
|
612
|
+
*/
|
|
613
|
+
protected async resolveUserViewByID(
|
|
614
|
+
viewID: string,
|
|
615
|
+
provider: DatabaseProviderBase,
|
|
616
|
+
contextUser?: UserInfo,
|
|
617
|
+
): Promise<MJUserViewEntityExtended | null> {
|
|
618
|
+
if (UserViewEngine.Instance.Loaded) {
|
|
619
|
+
const cached = UserViewEngine.Instance.GetViewById(viewID);
|
|
620
|
+
if (cached) {
|
|
621
|
+
return cached;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const viewInfo = await provider.GetEntityObject<MJUserViewEntityExtended>('MJ: User Views', contextUser);
|
|
625
|
+
await viewInfo.Load(viewID);
|
|
626
|
+
return viewInfo;
|
|
627
|
+
}
|
|
628
|
+
|
|
562
629
|
async RunViewByNameGeneric(viewInput: RunViewByNameInput, provider: DatabaseProviderBase, userPayload: UserPayload, pubSub: PubSubEngine) {
|
|
563
630
|
try {
|
|
564
631
|
// Log aggregate input for debugging
|
|
@@ -566,14 +633,8 @@ export class ResolverBase {
|
|
|
566
633
|
LogStatus(`[ResolverBase] RunViewByNameGeneric received aggregates: viewName=${viewInput.ViewName}, aggregateCount=${viewInput.Aggregates.length}, aggregates=${JSON.stringify(viewInput.Aggregates.map(a => ({ expression: a.expression, alias: a.alias })))}`);
|
|
567
634
|
}
|
|
568
635
|
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
EntityName: 'MJ: User Views',
|
|
572
|
-
// SECURITY: the view name is client-supplied and lands inside a SQL literal.
|
|
573
|
-
ExtraFilter: "Name='" + EscapeSQLString(viewInput.ViewName) + "'",
|
|
574
|
-
}, userPayload.userRecord);
|
|
575
|
-
if (result && result.Success && result.Results.length > 0) {
|
|
576
|
-
const viewInfo = result.Results[0];
|
|
636
|
+
const viewInfo = await this.resolveUserViewByName(viewInput.ViewName, provider, userPayload.userRecord);
|
|
637
|
+
if (viewInfo) {
|
|
577
638
|
return this.RunViewGenericInternal(
|
|
578
639
|
provider,
|
|
579
640
|
viewInfo,
|
|
@@ -598,7 +659,9 @@ export class ResolverBase {
|
|
|
598
659
|
: undefined,
|
|
599
660
|
viewInput.BypassCache,
|
|
600
661
|
viewInput.DataSource,
|
|
601
|
-
viewInput.IncludeBinaryFields
|
|
662
|
+
viewInput.IncludeBinaryFields,
|
|
663
|
+
viewInput.AlternateBaseView,
|
|
664
|
+
viewInput.Properties
|
|
602
665
|
);
|
|
603
666
|
}
|
|
604
667
|
else {
|
|
@@ -619,8 +682,10 @@ export class ResolverBase {
|
|
|
619
682
|
}
|
|
620
683
|
|
|
621
684
|
const contextUser = this.GetUserFromPayload(userPayload);
|
|
622
|
-
const viewInfo = await
|
|
623
|
-
|
|
685
|
+
const viewInfo = await this.resolveUserViewByID(viewInput.ViewID, provider, contextUser);
|
|
686
|
+
if (!viewInfo) {
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
624
689
|
return this.RunViewGenericInternal(
|
|
625
690
|
provider,
|
|
626
691
|
viewInfo,
|
|
@@ -643,7 +708,9 @@ export class ResolverBase {
|
|
|
643
708
|
undefined,
|
|
644
709
|
viewInput.BypassCache,
|
|
645
710
|
viewInput.DataSource,
|
|
646
|
-
viewInput.IncludeBinaryFields
|
|
711
|
+
viewInput.IncludeBinaryFields,
|
|
712
|
+
viewInput.AlternateBaseView,
|
|
713
|
+
viewInput.Properties
|
|
647
714
|
);
|
|
648
715
|
} catch (err) {
|
|
649
716
|
console.log(err);
|
|
@@ -691,7 +758,8 @@ export class ResolverBase {
|
|
|
691
758
|
undefined,
|
|
692
759
|
viewInput.BypassCache,
|
|
693
760
|
viewInput.DataSource,
|
|
694
|
-
viewInput.IncludeBinaryFields
|
|
761
|
+
viewInput.IncludeBinaryFields,
|
|
762
|
+
viewInput.AlternateBaseView
|
|
695
763
|
);
|
|
696
764
|
} catch (err) {
|
|
697
765
|
console.log(err);
|
|
@@ -712,15 +780,14 @@ export class ResolverBase {
|
|
|
712
780
|
let viewInfo: MJUserViewEntityExtended | null = null;
|
|
713
781
|
|
|
714
782
|
if (viewInput.ViewName) {
|
|
715
|
-
viewInfo =
|
|
783
|
+
viewInfo = await this.resolveUserViewByName(viewInput.ViewName, provider, userPayload.userRecord);
|
|
716
784
|
// Populate EntityName on the input so callers (e.g. RunViews resolver) can
|
|
717
785
|
// look up the entity without re-querying the view
|
|
718
786
|
if (viewInfo && !viewInput.EntityName) {
|
|
719
787
|
viewInput.EntityName = viewInfo.Entity;
|
|
720
788
|
}
|
|
721
789
|
} else if (viewInput.ViewID) {
|
|
722
|
-
viewInfo = await
|
|
723
|
-
await viewInfo.Load(viewInput.ViewID);
|
|
790
|
+
viewInfo = await this.resolveUserViewByID(viewInput.ViewID, provider, contextUser);
|
|
724
791
|
// Populate EntityName on the input so callers can look up the entity
|
|
725
792
|
if (viewInfo && !viewInput.EntityName) {
|
|
726
793
|
viewInput.EntityName = viewInfo.Entity;
|
|
@@ -767,6 +834,8 @@ export class ResolverBase {
|
|
|
767
834
|
bypassCache: viewInput.BypassCache,
|
|
768
835
|
dataSource: viewInput.DataSource,
|
|
769
836
|
includeBinaryFields: viewInput.IncludeBinaryFields,
|
|
837
|
+
alternateBaseView: viewInput.AlternateBaseView,
|
|
838
|
+
properties: viewInput.Properties,
|
|
770
839
|
});
|
|
771
840
|
} catch (err) {
|
|
772
841
|
LogError(err);
|
|
@@ -778,6 +847,31 @@ export class ResolverBase {
|
|
|
778
847
|
return results;
|
|
779
848
|
}
|
|
780
849
|
|
|
850
|
+
/**
|
|
851
|
+
* What an entity event may carry to a CloudEvents sink or a log line.
|
|
852
|
+
*
|
|
853
|
+
* The whole row for an entity without field security. For one where it applies (its own flag
|
|
854
|
+
* or an IS-A ancestor's), and for `MJ: Record Changes` (whose payload columns hold other
|
|
855
|
+
* entities' values), only the primary key and the names of the fields that changed: the sink
|
|
856
|
+
* is outside MemberJunction with no reader to project the row for, and these events fire for
|
|
857
|
+
* server-internal saves too, whose objects hold every column.
|
|
858
|
+
*/
|
|
859
|
+
protected EntityEventPayload(baseEntity: BaseEntity, changedFieldNames?: string[]): Record<string, unknown> {
|
|
860
|
+
const entityInfo = baseEntity.EntityInfo;
|
|
861
|
+
if (!entityInfo?.FieldLevelSecurityApplies && !RecordChangeFieldSecurityProjector.IsRecordChangesEntity(entityInfo)) {
|
|
862
|
+
// fls-guard: entity without field security and not the audit trail; whole row is the documented payload
|
|
863
|
+
return baseEntity.GetAll();
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
// Read through Get() rather than PrimaryKey, which builds every field object of a raw-mode record.
|
|
867
|
+
PrimaryKey: (entityInfo.PrimaryKeys ?? []).map((pk) => ({ FieldName: pk.Name, Value: baseEntity.Get(pk.Name) })),
|
|
868
|
+
// A completed save has already rebased the object, so nothing on it is dirty: the event
|
|
869
|
+
// carries the names the save wrote. Other events (save_started) still read them off the object.
|
|
870
|
+
// GetDirtyFieldNames answers without building field objects for a record still in raw mode.
|
|
871
|
+
ChangedFields: changedFieldNames ?? baseEntity.GetDirtyFieldNames(),
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
|
|
781
875
|
private static _priorEmittedData: {Entity: string, PKey: CompositeKey}[] = [];
|
|
782
876
|
protected async EmitCloudEvent({ component, event, eventCode, args }: MJEvent) {
|
|
783
877
|
if (ResolverBase._emit && event === MJEventType.ComponentEvent && eventCode === BaseEntity.BaseEventCode) {
|
|
@@ -785,7 +879,7 @@ export class ResolverBase {
|
|
|
785
879
|
const type = `MemberJunction.${event}${extendedType}`;
|
|
786
880
|
const source = `${process.env.CLOUDEVENTS_SOURCE ?? 'MemberJunction'}`;
|
|
787
881
|
const subject = args instanceof BaseEntityEvent ? args.baseEntity.EntityInfo.CodeName : undefined;
|
|
788
|
-
const data = args?.baseEntity
|
|
882
|
+
const data = args?.baseEntity ? this.EntityEventPayload(args.baseEntity, args.ChangedFieldNames) : {};
|
|
789
883
|
|
|
790
884
|
const cloudEvent = new CloudEvent({ type, source, subject, data });
|
|
791
885
|
|
|
@@ -1145,7 +1239,9 @@ export class ResolverBase {
|
|
|
1145
1239
|
afterKey?: CompositeKey,
|
|
1146
1240
|
bypassCache?: boolean,
|
|
1147
1241
|
dataSource?: 'Live' | 'Materialized',
|
|
1148
|
-
includeBinaryFields?: boolean
|
|
1242
|
+
includeBinaryFields?: boolean,
|
|
1243
|
+
alternateBaseView?: string,
|
|
1244
|
+
properties?: ViewPropertyValues
|
|
1149
1245
|
) {
|
|
1150
1246
|
try {
|
|
1151
1247
|
if (!viewInfo || !userPayload) return null;
|
|
@@ -1207,6 +1303,7 @@ export class ResolverBase {
|
|
|
1207
1303
|
{
|
|
1208
1304
|
ViewID: viewInfo.ID,
|
|
1209
1305
|
ViewName: viewInfo.Name,
|
|
1306
|
+
ViewEntity: viewInfo.ID ? viewInfo : undefined,
|
|
1210
1307
|
EntityName: viewInfo.Entity,
|
|
1211
1308
|
ExtraFilter: extraFilter,
|
|
1212
1309
|
OrderBy: orderBy,
|
|
@@ -1227,10 +1324,22 @@ export class ResolverBase {
|
|
|
1227
1324
|
BypassCache: bypassCache,
|
|
1228
1325
|
DataSource: dataSource,
|
|
1229
1326
|
IncludeBinaryFields: includeBinaryFields,
|
|
1327
|
+
AlternateBaseView: alternateBaseView,
|
|
1328
|
+
Properties: properties,
|
|
1329
|
+
TimeZone: userPayload?.TimeZone,
|
|
1230
1330
|
},
|
|
1231
1331
|
user
|
|
1232
1332
|
);
|
|
1233
1333
|
|
|
1334
|
+
if (result && typeof result === 'object') {
|
|
1335
|
+
Object.defineProperty(result, '_resolvedViewEntity', {
|
|
1336
|
+
value: viewInfo,
|
|
1337
|
+
enumerable: false,
|
|
1338
|
+
configurable: true,
|
|
1339
|
+
writable: true,
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1234
1343
|
// Log aggregate results for debugging
|
|
1235
1344
|
if (aggregates?.length) {
|
|
1236
1345
|
LogStatus(`[ResolverBase] RunView result aggregate info: entityName=${viewInfo.Entity}, hasAggregateResults=${!!result?.AggregateResults}, aggregateResultCount=${result?.AggregateResults?.length || 0}, aggregateExecutionTime=${result?.AggregateExecutionTime}, aggregateResults=${JSON.stringify(result?.AggregateResults)}`);
|
|
@@ -1261,7 +1370,8 @@ export class ResolverBase {
|
|
|
1261
1370
|
await this.ArrayFilterEncryptedFieldsForAPI(
|
|
1262
1371
|
viewInfo.Entity,
|
|
1263
1372
|
result.Results as Record<string, unknown>[],
|
|
1264
|
-
user
|
|
1373
|
+
user,
|
|
1374
|
+
md
|
|
1265
1375
|
);
|
|
1266
1376
|
}
|
|
1267
1377
|
|
|
@@ -1357,6 +1467,7 @@ export class ResolverBase {
|
|
|
1357
1467
|
runViewParams.push({
|
|
1358
1468
|
ViewID: param.viewInfo.ID,
|
|
1359
1469
|
ViewName: param.viewInfo.Name,
|
|
1470
|
+
ViewEntity: param.viewInfo.ID ? param.viewInfo : undefined,
|
|
1360
1471
|
EntityName: param.viewInfo.Entity,
|
|
1361
1472
|
ExtraFilter: param.extraFilter,
|
|
1362
1473
|
OrderBy: param.orderBy,
|
|
@@ -1377,6 +1488,9 @@ export class ResolverBase {
|
|
|
1377
1488
|
BypassCache: param.bypassCache,
|
|
1378
1489
|
DataSource: param.dataSource,
|
|
1379
1490
|
IncludeBinaryFields: param.includeBinaryFields,
|
|
1491
|
+
AlternateBaseView: param.alternateBaseView,
|
|
1492
|
+
Properties: param.properties,
|
|
1493
|
+
TimeZone: param.userPayload?.TimeZone,
|
|
1380
1494
|
});
|
|
1381
1495
|
}
|
|
1382
1496
|
|
|
@@ -1398,7 +1512,8 @@ export class ResolverBase {
|
|
|
1398
1512
|
await this.ArrayFilterEncryptedFieldsForAPI(
|
|
1399
1513
|
entityName,
|
|
1400
1514
|
runViewResult.Results as Record<string, unknown>[],
|
|
1401
|
-
contextUser
|
|
1515
|
+
contextUser,
|
|
1516
|
+
md
|
|
1402
1517
|
);
|
|
1403
1518
|
}
|
|
1404
1519
|
}
|
|
@@ -1537,8 +1652,9 @@ export class ResolverBase {
|
|
|
1537
1652
|
* skip redundant re-fetches (it already handled the event locally).
|
|
1538
1653
|
*
|
|
1539
1654
|
* The row itself rides along ONLY for entities opted in via
|
|
1540
|
-
* `cacheSettings.recordDataBroadcastEntities
|
|
1541
|
-
*
|
|
1655
|
+
* `cacheSettings.recordDataBroadcastEntities`, and never for one under field-level security —
|
|
1656
|
+
* this event reaches every connected client unprojected, so the row would otherwise be readable
|
|
1657
|
+
* by sessions that could not read the record or its restricted fields.
|
|
1542
1658
|
*/
|
|
1543
1659
|
protected PublishCacheInvalidation(entityObject: BaseEntity, action: 'save' | 'delete', userPayload: UserPayload): void {
|
|
1544
1660
|
const entityName = entityObject.EntityInfo.Name;
|
|
@@ -1550,7 +1666,8 @@ export class ResolverBase {
|
|
|
1550
1666
|
timestamp: new Date(),
|
|
1551
1667
|
originSessionId: userPayload?.sessionId || null,
|
|
1552
1668
|
recordData:
|
|
1553
|
-
action === 'save' && MayBroadcastRecordData(entityName)
|
|
1669
|
+
action === 'save' && MayBroadcastRecordData(entityName, entityObject.EntityInfo)
|
|
1670
|
+
// fls-guard: MayBroadcastRecordData refuses FLS entities and Record Changes
|
|
1554
1671
|
? JSON.stringify(entityObject.GetAll())
|
|
1555
1672
|
: undefined,
|
|
1556
1673
|
});
|
|
@@ -1558,16 +1675,17 @@ export class ResolverBase {
|
|
|
1558
1675
|
|
|
1559
1676
|
/**
|
|
1560
1677
|
* Publishes a push-status update to the client on {@link PUSH_STATUS_UPDATES_TOPIC}, stamping the
|
|
1561
|
-
*
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
1565
|
-
*
|
|
1678
|
+
* owner key of `userPayload` (`StatusUpdateOwnerID`: the request's user, or the widget guest an
|
|
1679
|
+
* elevated payload stands in for) so the subscription filter can bind delivery to identity (see B49 /
|
|
1680
|
+
* `StatusUpdatesFilter`). The ergonomic wrapper every resolver should use instead of calling
|
|
1681
|
+
* `pubSub.publish` on the topic directly — it makes omitting identity impossible. Non-resolver
|
|
1682
|
+
* publishers (services, the liveness heartbeat) call the shared `PublishStatusUpdate()` function
|
|
1683
|
+
* directly with an explicit `ownerUserId`.
|
|
1566
1684
|
*/
|
|
1567
1685
|
protected PublishStatusUpdate(pubSub: PubSubEngine, sessionId: string, message: string | undefined, userPayload: UserPayload): void {
|
|
1568
1686
|
PublishStatusUpdate(pubSub, {
|
|
1569
1687
|
sessionId,
|
|
1570
|
-
ownerUserId: userPayload
|
|
1688
|
+
ownerUserId: StatusUpdateOwnerID(userPayload),
|
|
1571
1689
|
message,
|
|
1572
1690
|
});
|
|
1573
1691
|
}
|
|
@@ -1591,23 +1709,16 @@ export class ResolverBase {
|
|
|
1591
1709
|
return;
|
|
1592
1710
|
}
|
|
1593
1711
|
|
|
1594
|
-
const baseEntityValues = baseEntity ? baseEntity.
|
|
1712
|
+
const baseEntityValues = baseEntity ? this.EntityEventPayload(baseEntity, event.args instanceof BaseEntityEvent ? event.args.ChangedFieldNames : undefined) : null;
|
|
1595
1713
|
const eventToLog = { entityName: entityObject.EntityInfo.Name, baseEntity: baseEntityValues, event: event.event, eventCode: event.eventCode };
|
|
1596
1714
|
LogDebug(`ResolverBase.ListenForEntityMessages: Received Event from within MJGlobal.Instance.GetEventListener() callback. Will call EmitCloudEvent() next\nEvent data:\n${JSON.stringify(eventToLog)}`);
|
|
1597
1715
|
await this.EmitCloudEvent(event);
|
|
1598
1716
|
LogDebug(`ResolverBase.ListenForEntityMessages: EmitCloudEvent() completed successfully`);
|
|
1599
1717
|
|
|
1600
|
-
if (event.args && event.args instanceof BaseEntityEvent) {
|
|
1601
|
-
|
|
1602
|
-
// message from our entity object, relay it to the client
|
|
1718
|
+
if (event.args && event.args instanceof BaseEntityEvent && this.EntityEventBelongsToSession(event.args, userPayload)) {
|
|
1719
|
+
// message from an entity object of this session's user, relay it to the client
|
|
1603
1720
|
LogDebug('ResolverBase.ListenForEntityMessages: About to publish PUSH_STATUS_UPDATES_TOPIC');
|
|
1604
|
-
this.PublishStatusUpdate(pubSub, userPayload.sessionId,
|
|
1605
|
-
status: 'OK',
|
|
1606
|
-
type: 'EntityObjectStatusMessage',
|
|
1607
|
-
entityName: baseEntityEvent.baseEntity.EntityInfo.Name,
|
|
1608
|
-
primaryKey: baseEntityEvent.baseEntity.PrimaryKey,
|
|
1609
|
-
message: event.args.payload,
|
|
1610
|
-
}), userPayload);
|
|
1721
|
+
this.PublishStatusUpdate(pubSub, userPayload.sessionId, this.EntityStatusMessage(event.args), userPayload);
|
|
1611
1722
|
}
|
|
1612
1723
|
}
|
|
1613
1724
|
});
|
|
@@ -1615,6 +1726,37 @@ export class ResolverBase {
|
|
|
1615
1726
|
}
|
|
1616
1727
|
}
|
|
1617
1728
|
|
|
1729
|
+
/**
|
|
1730
|
+
* Whether an entity event may be relayed to the session whose call created the listener. The
|
|
1731
|
+
* listener is shared by every caller in the process for that entity, so an event raised by an
|
|
1732
|
+
* object loaded for another user (or for the system user) is never relayed to this session.
|
|
1733
|
+
*/
|
|
1734
|
+
protected EntityEventBelongsToSession(event: BaseEntityEvent, userPayload: UserPayload): boolean {
|
|
1735
|
+
const sessionUserId = userPayload?.userRecord?.ID;
|
|
1736
|
+
const objectUserId = event.baseEntity?.ContextCurrentUser?.ID;
|
|
1737
|
+
return Boolean(sessionUserId && objectUserId && UUIDsEqual(sessionUserId, objectUserId));
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
/**
|
|
1741
|
+
* The status message relayed for an entity event: the entity, key and event type. The event's
|
|
1742
|
+
* payload is included only when it cannot carry record values: never for a delete (its payload
|
|
1743
|
+
* is the whole deleted row), an entity under field-level security, or `MJ: Record Changes`.
|
|
1744
|
+
*/
|
|
1745
|
+
protected EntityStatusMessage(event: BaseEntityEvent): string {
|
|
1746
|
+
const entityInfo = event.baseEntity.EntityInfo;
|
|
1747
|
+
const mayCarryPayload = event.type !== 'delete'
|
|
1748
|
+
&& !entityInfo.FieldLevelSecurityApplies
|
|
1749
|
+
&& !RecordChangeFieldSecurityProjector.IsRecordChangesEntity(entityInfo);
|
|
1750
|
+
return JSON.stringify({
|
|
1751
|
+
status: 'OK',
|
|
1752
|
+
type: 'EntityObjectStatusMessage',
|
|
1753
|
+
entityName: entityInfo.Name,
|
|
1754
|
+
primaryKey: event.baseEntity.PrimaryKey,
|
|
1755
|
+
eventType: event.type,
|
|
1756
|
+
message: mayCarryPayload ? event.payload : undefined,
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1618
1760
|
/**
|
|
1619
1761
|
* IS-A PROMOTION over the wire: an EXISTING parent record gaining a subtype — an Animal that is
|
|
1620
1762
|
* now also a Dog. It arrives as the CHILD's create mutation carrying the existing parent's
|
|
@@ -1691,6 +1833,9 @@ export class ResolverBase {
|
|
|
1691
1833
|
// IS-A promotion: bind the new child to its EXISTING parent row BEFORE the field
|
|
1692
1834
|
// assignments, so values the client sent for parent fields land on the loaded parent
|
|
1693
1835
|
// (as an update) instead of being wiped by the load.
|
|
1836
|
+
// Field-level security: refuse a key the caller may not read, or that names no field, before
|
|
1837
|
+
// anything is applied (the same gate as the update path).
|
|
1838
|
+
AssertClientInputWritableForUser(entityObject.EntityInfo, fieldsForSet, this.GetUserFromPayload(userPayload));
|
|
1694
1839
|
await this.attachToExistingParentIfPromotion(entityObject, fieldsForSet);
|
|
1695
1840
|
entityObject.SetMany(fieldsForSet);
|
|
1696
1841
|
|
|
@@ -1707,6 +1852,7 @@ export class ResolverBase {
|
|
|
1707
1852
|
// Cache invalidation is now handled globally by the MJGlobal listener in index.ts
|
|
1708
1853
|
const contextUser = this.GetUserFromPayload(userPayload);
|
|
1709
1854
|
// MapFieldNamesToCodeNames now handles encryption filtering as well
|
|
1855
|
+
// fls-guard: MapFieldNamesToCodeNames drops the fields the caller may not read
|
|
1710
1856
|
return await this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll(), contextUser);
|
|
1711
1857
|
}
|
|
1712
1858
|
// save failed, throw error with message
|
|
@@ -1747,12 +1893,11 @@ export class ResolverBase {
|
|
|
1747
1893
|
});
|
|
1748
1894
|
|
|
1749
1895
|
// Field-level security: any value the client sends for a field it cannot READ is
|
|
1750
|
-
// fabricated by construction (the field was
|
|
1751
|
-
// received), so
|
|
1752
|
-
// denied we also force the truth-load branch below
|
|
1753
|
-
//
|
|
1754
|
-
|
|
1755
|
-
const hasDeniedReadFields = this.StripDeniedReadFieldsFromClientInput(entityInfo, userInfo, input, clientNewValues);
|
|
1896
|
+
// fabricated by construction (the field was absent from every payload the client ever
|
|
1897
|
+
// received), so the input is refused before anything applies it. When any field is
|
|
1898
|
+
// denied we also force the truth-load branch below, so the denied columns keep what the
|
|
1899
|
+
// database holds rather than anything derived from the client's OldValues.
|
|
1900
|
+
const hasDeniedReadFields = this.AssertClientInputFieldSecurity(entityInfo, userInfo, input, clientNewValues);
|
|
1756
1901
|
// The audit-trail equivalent. Its `true` matters most on THIS entity: 'MJ: Record Changes'
|
|
1757
1902
|
// has TrackRecordChanges off, so without it a client sending OldValues takes the branch below
|
|
1758
1903
|
// that hydrates from those values — never loading what the database actually holds.
|
|
@@ -1815,6 +1960,7 @@ export class ResolverBase {
|
|
|
1815
1960
|
// Cache invalidation is now handled globally by the MJGlobal listener in index.ts
|
|
1816
1961
|
|
|
1817
1962
|
// MapFieldNamesToCodeNames now handles encryption filtering as well
|
|
1963
|
+
// fls-guard: MapFieldNamesToCodeNames drops the fields the caller may not read
|
|
1818
1964
|
return await this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll(), userInfo);
|
|
1819
1965
|
} else {
|
|
1820
1966
|
// CompleteMessage, not Message. A validation refusal puts its reasons in `Errors` and leaves
|
|
@@ -1871,7 +2017,7 @@ export class ResolverBase {
|
|
|
1871
2017
|
* writable fields in `OldValues___`, so {@link hasCompleteOldValues} returns `false` and forces the
|
|
1872
2018
|
* database load (`InnerLoad`) so untouched columns retain their stored values.
|
|
1873
2019
|
*
|
|
1874
|
-
* `
|
|
2020
|
+
* `FieldLevelSecurityApplies` (own flag or an IS-A ancestor's) is the security-critical one, and it is NOT redundant with
|
|
1875
2021
|
* `hasDeniedReadFields`. That flag reports READ denials, and the canonical FLS configuration is
|
|
1876
2022
|
* Read Allow + Update Deny — which leaves it false. Such a caller would be hydrated from its own
|
|
1877
2023
|
* `OldValues___`, and a value it supplies there for an update-denied field arrives through
|
|
@@ -1926,73 +2072,50 @@ export class ResolverBase {
|
|
|
1926
2072
|
!this.hasCompleteOldValues(entityInfo, input.OldValues___) ||
|
|
1927
2073
|
hasDeniedReadFields ||
|
|
1928
2074
|
hasNarrowedAuditPayload ||
|
|
1929
|
-
entityInfo.
|
|
2075
|
+
entityInfo.FieldLevelSecurityApplies ||
|
|
1930
2076
|
!user ||
|
|
1931
2077
|
entityInfo.GetEffectiveRowFilterWhereClause(user, EntityPermissionType.Update, '').length > 0
|
|
1932
2078
|
);
|
|
1933
2079
|
}
|
|
1934
2080
|
|
|
1935
2081
|
/**
|
|
1936
|
-
* Field-level security
|
|
2082
|
+
* Field-level security gate on the update path's client input.
|
|
1937
2083
|
*
|
|
1938
|
-
*
|
|
1939
|
-
*
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
-
* save" safe for a restricted user.
|
|
2084
|
+
* For a caller denied at least one field on the entity, refuses (with a `FieldSecurityError`)
|
|
2085
|
+
* any key in the new values or in `OldValues___` that names a field the caller cannot READ, or
|
|
2086
|
+
* that names no field at all, with the same message for both (see
|
|
2087
|
+
* `AssertClientInputWritableForUser`), so the refusal does not reveal which columns exist.
|
|
1943
2088
|
*
|
|
1944
|
-
*
|
|
1945
|
-
*
|
|
2089
|
+
* Refused rather than dropped: a field the caller cannot read was absent from every payload the
|
|
2090
|
+
* client received, so a value for it can only be fabricated. A well-behaved client never sends
|
|
2091
|
+
* one (it omits fields it did not load), so ordinary traffic is unaffected.
|
|
1946
2092
|
*
|
|
1947
|
-
*
|
|
1948
|
-
*
|
|
1949
|
-
* and is restricted).
|
|
2093
|
+
* There is no split by update permission: Read is required for Update, so a user denied read is
|
|
2094
|
+
* denied update too.
|
|
1950
2095
|
*
|
|
1951
|
-
* Returns true when the user has a non-empty denied-read set on this entity. The caller
|
|
1952
|
-
*
|
|
1953
|
-
*
|
|
2096
|
+
* Returns true when the user has a non-empty denied-read set on this entity. The caller must
|
|
2097
|
+
* then hydrate the entity from the DATABASE (never from client OldValues), so denied fields hold
|
|
2098
|
+
* true values the client input never touches.
|
|
1954
2099
|
*/
|
|
1955
|
-
protected
|
|
2100
|
+
protected AssertClientInputFieldSecurity(
|
|
1956
2101
|
entityInfo: EntityInfo,
|
|
1957
2102
|
userInfo: UserInfo,
|
|
1958
2103
|
input: { OldValues___?: Array<{ Key: string; Value: unknown }> } & Record<string, unknown>,
|
|
1959
2104
|
clientNewValues: Record<string, unknown>
|
|
1960
2105
|
): boolean {
|
|
1961
|
-
if (!entityInfo.
|
|
2106
|
+
if (!entityInfo.FieldLevelSecurityApplies || !userInfo) {
|
|
1962
2107
|
return false;
|
|
1963
2108
|
}
|
|
1964
|
-
|
|
1965
|
-
if (deniedReadNames.size === 0) {
|
|
2109
|
+
if (entityInfo.GetDeniedReadFields(userInfo).size === 0) {
|
|
1966
2110
|
return false;
|
|
1967
2111
|
}
|
|
1968
|
-
|
|
1969
|
-
// Input keys are entity CodeNames (post ReverseMapInputFieldNames); the denied set holds
|
|
1970
|
-
// lowercased field Names — bridge via the field metadata once.
|
|
1971
|
-
const deniedReadCodeNames = new Set<string>();
|
|
1972
|
-
for (const field of entityInfo.Fields) {
|
|
1973
|
-
if (deniedReadNames.has(field.Name.trim().toLowerCase())) {
|
|
1974
|
-
deniedReadCodeNames.add(field.CodeName.trim().toLowerCase());
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
|
-
const stripped: string[] = [];
|
|
1979
|
-
for (const key of Object.keys(clientNewValues)) {
|
|
1980
|
-
if (deniedReadCodeNames.has(key.trim().toLowerCase())) {
|
|
1981
|
-
delete clientNewValues[key];
|
|
1982
|
-
delete input[key];
|
|
1983
|
-
stripped.push(key);
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
2112
|
+
AssertClientInputWritableForUser(entityInfo, clientNewValues, userInfo);
|
|
1986
2113
|
if (Array.isArray(input.OldValues___)) {
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
LogDebug(
|
|
1993
|
-
`[FieldSecurity] UpdateRecord on '${entityInfo.Name}' for user ${userInfo.Email}: ` +
|
|
1994
|
-
`stripped client-sent value(s) for denied-read field(s) ${stripped.join(', ')}`
|
|
1995
|
-
);
|
|
2114
|
+
const oldValueKeys: Record<string, unknown> = {};
|
|
2115
|
+
for (const item of input.OldValues___) {
|
|
2116
|
+
oldValueKeys[String(item.Key)] = null;
|
|
2117
|
+
}
|
|
2118
|
+
AssertClientInputWritableForUser(entityInfo, oldValueKeys, userInfo);
|
|
1996
2119
|
}
|
|
1997
2120
|
return true;
|
|
1998
2121
|
}
|
|
@@ -2133,6 +2256,7 @@ export class ResolverBase {
|
|
|
2133
2256
|
|
|
2134
2257
|
// clientOldValues now has all of the oldValues the CLIENT passed us. Now we need to build the same kind of object
|
|
2135
2258
|
// with the DB values
|
|
2259
|
+
// fls-guard: compared on the server only, never returned
|
|
2136
2260
|
const dbValues = entityObject.GetAll();
|
|
2137
2261
|
|
|
2138
2262
|
// now we need to compare clientOldValues and dbValues and have a new array that has entries for any differences and have FieldName, clientOldValue and dbValue as properties
|
|
@@ -2217,16 +2341,16 @@ export class ResolverBase {
|
|
|
2217
2341
|
|
|
2218
2342
|
// Log as warning to console and ErrorLog table instead of throwing error
|
|
2219
2343
|
console.warn('Entity save inconsistency detected but allowing save to continue:', JSON.stringify(msg));
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2344
|
+
LogErrorEx({
|
|
2345
|
+
message: `Entity save inconsistency detected: ${JSON.stringify(msg)}`,
|
|
2346
|
+
severity: 'warning',
|
|
2347
|
+
category: 'ResolverBase.TestAndSetClientOldValuesToDBValues',
|
|
2348
|
+
metadata: {
|
|
2225
2349
|
entityName: entityObject.EntityInfo.Name,
|
|
2226
2350
|
clientDifferences: clientDifferences,
|
|
2227
2351
|
dbDifferences: dbDifferences,
|
|
2228
|
-
overlap: overlap
|
|
2229
|
-
}
|
|
2352
|
+
overlap: overlap,
|
|
2353
|
+
},
|
|
2230
2354
|
});
|
|
2231
2355
|
|
|
2232
2356
|
// Create ErrorLog record in the database — use the entity's bound provider so the
|
|
@@ -2234,7 +2358,8 @@ export class ResolverBase {
|
|
|
2234
2358
|
try {
|
|
2235
2359
|
const md = entityObject.ProviderToUse as unknown as IMetadataProvider;
|
|
2236
2360
|
const errorLogEntity = await md.GetEntityObject<MJErrorLogEntity>('MJ: Error Logs', contextUser);
|
|
2237
|
-
|
|
2361
|
+
// Code is NCHAR(20); a longer value is rejected at the database
|
|
2362
|
+
errorLogEntity.Code = 'SAVE_INCONSISTENCY';
|
|
2238
2363
|
errorLogEntity.Message = `Entity save inconsistency detected for ${entityObject.EntityInfo.Name}: ${JSON.stringify(msg)}`;
|
|
2239
2364
|
errorLogEntity.Status = 'Warning';
|
|
2240
2365
|
errorLogEntity.Category = 'Entity Save';
|
|
@@ -2242,7 +2367,9 @@ export class ResolverBase {
|
|
|
2242
2367
|
|
|
2243
2368
|
const saveResult = await errorLogEntity.Save();
|
|
2244
2369
|
if (!saveResult) {
|
|
2245
|
-
console.error(
|
|
2370
|
+
console.error(
|
|
2371
|
+
`Failed to save ErrorLog record for ${entityObject.EntityInfo.Name}: ${errorLogEntity.LatestResult?.CompleteMessage}`
|
|
2372
|
+
);
|
|
2246
2373
|
}
|
|
2247
2374
|
} catch (errorLogError) {
|
|
2248
2375
|
console.error('Error creating ErrorLog record:', errorLogError);
|
|
@@ -2281,7 +2408,10 @@ export class ResolverBase {
|
|
|
2281
2408
|
extensions: { code: 'LOAD_ENTITY_ERROR', entityName },
|
|
2282
2409
|
});
|
|
2283
2410
|
}
|
|
2284
|
-
|
|
2411
|
+
// Grab the values before we delete so we can return the last state if the delete succeeds,
|
|
2412
|
+
// projected for the caller in case the object holds a column they may not read.
|
|
2413
|
+
// fls-guard: projected by ProjectEntityValuesForUser
|
|
2414
|
+
const returnValue = ProjectEntityValuesForUser(provider, entityObject.EntityInfo, entityObject.GetAll(), this.GetUserFromPayload(userPayload));
|
|
2285
2415
|
|
|
2286
2416
|
this.ListenForEntityMessages(entityObject, pubSub, userPayload);
|
|
2287
2417
|
|