@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,4 +1,5 @@
|
|
|
1
|
-
import { BaseEntity, BaseEntityEvent, CompositeKey, EntityFieldTSType, EntityPermissionType, KeyValuePair, LogDebug, LogError, LogStatus, Metadata, ReadableFieldsTransportKey, RecordChangeFieldSecurityProjector, RecordChangePayloadFields, } from '@memberjunction/core';
|
|
1
|
+
import { AssertClientInputWritableForUser, BaseEntity, BaseEntityEvent, CompositeKey, EntityFieldTSType, EntityPermissionType, KeyValuePair, LogDebug, LogError, LogErrorEx, LogStatus, Metadata, ProjectEntityValuesForUser, ReadableFieldsTransportKey, RecordChangeFieldSecurityProjector, RecordChangePayloadFields, } from '@memberjunction/core';
|
|
2
|
+
import { UserViewEngine } from '@memberjunction/core-entities';
|
|
2
3
|
import { ClientClauseScreen, UserCache } from '@memberjunction/generic-database-provider';
|
|
3
4
|
import { AuthorizationError } from 'type-graphql';
|
|
4
5
|
import { GraphQLError } from 'graphql';
|
|
@@ -6,10 +7,10 @@ import { RefusalExtensions } from './refusalExtensions.js';
|
|
|
6
7
|
import { GetAPIKeyEngine } from '@memberjunction/api-keys';
|
|
7
8
|
import { httpTransport, CloudEvent, emitterFor } from 'cloudevents';
|
|
8
9
|
import { DeleteOptionsInput } from './DeleteOptionsInput.js';
|
|
9
|
-
import { MJEventType, MJGlobal, ENCRYPTED_SENTINEL, ReplaceByteArraysWithBase64, EscapeSQLString, IsValueEncrypted, IsOnlyTimezoneShift } from '@memberjunction/global';
|
|
10
|
+
import { MJEventType, MJGlobal, UUIDsEqual, ENCRYPTED_SENTINEL, ReplaceByteArraysWithBase64, EscapeSQLString, IsValueEncrypted, IsOnlyTimezoneShift } from '@memberjunction/global';
|
|
10
11
|
import { SQLParser, IsBalancedSQLFragment, LexSQL } from '@memberjunction/sql-parser';
|
|
11
12
|
import { EncryptionEngine, IsEncryptionKeyUnavailable, ReportEncryptionKeyUnavailable } from '@memberjunction/encryption';
|
|
12
|
-
import { PublishStatusUpdate } from './PushStatusResolver.js';
|
|
13
|
+
import { PublishStatusUpdate, StatusUpdateOwnerID } from './PushStatusResolver.js';
|
|
13
14
|
import { CACHE_INVALIDATION_TOPIC, MayBroadcastRecordData } from './CacheInvalidationResolver.js';
|
|
14
15
|
import { PubSubManager } from './PubSubManager.js';
|
|
15
16
|
import { FieldMapper } from '@memberjunction/graphql-dataprovider';
|
|
@@ -17,11 +18,18 @@ import { FieldMapper } from '@memberjunction/graphql-dataprovider';
|
|
|
17
18
|
* Entities whose update is always hydrated from the stored row, never from the client's `OldValues___`, though they
|
|
18
19
|
* do not track record changes: their server-side write rules compare against the prior state, which a client could
|
|
19
20
|
* otherwise supply itself (lower-case names). See `ResolverBase.MustLoadTruthFromDatabase`.
|
|
21
|
+
*
|
|
22
|
+
* `mj: conversations` and `mj: ai agent sessions` carry the anonymous realtime-session ownership key
|
|
23
|
+
* (`ExternalID` / `ConversationID`), which their server entities refuse an anonymous visitor to change — a rule
|
|
24
|
+
* that decides on `Dirty`, so the baseline must be the stored row, not a client-supplied one (#4931).
|
|
20
25
|
*/
|
|
21
26
|
const ENTITIES_THAT_ALWAYS_LOAD_STORED_ROW = new Set([
|
|
22
27
|
'mj: record changes',
|
|
23
28
|
'mj: conversation details',
|
|
24
29
|
'mj: data context items',
|
|
30
|
+
'mj: conversations',
|
|
31
|
+
'mj: ai agent sessions',
|
|
32
|
+
'mj: ai agent runs',
|
|
25
33
|
]);
|
|
26
34
|
export class ResolverBase {
|
|
27
35
|
static { this._emit = process.env.CLOUDEVENTS_HTTP_TRANSPORT ? emitterFor(httpTransport(process.env.CLOUDEVENTS_HTTP_TRANSPORT)) : null; }
|
|
@@ -69,7 +77,7 @@ export class ResolverBase {
|
|
|
69
77
|
const entityInfo = md.EntityByName(entityName);
|
|
70
78
|
if (!entityInfo)
|
|
71
79
|
throw new Error(`Entity ${entityName} not found in metadata`);
|
|
72
|
-
const denied = deniedReadFields ?? (entityInfo.
|
|
80
|
+
const denied = deniedReadFields ?? (entityInfo.FieldLevelSecurityApplies && contextUser
|
|
73
81
|
? entityInfo.GetDeniedReadFields(contextUser)
|
|
74
82
|
: null);
|
|
75
83
|
const mapper = new FieldMapper();
|
|
@@ -284,6 +292,7 @@ export class ResolverBase {
|
|
|
284
292
|
if (!loaded) {
|
|
285
293
|
return null;
|
|
286
294
|
}
|
|
295
|
+
// fls-guard: MapFieldNamesToCodeNames drops the fields the caller may not read
|
|
287
296
|
const mapped = await this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll(), contextUser);
|
|
288
297
|
return mapped;
|
|
289
298
|
}
|
|
@@ -449,23 +458,54 @@ export class ResolverBase {
|
|
|
449
458
|
return [];
|
|
450
459
|
}
|
|
451
460
|
}
|
|
461
|
+
/**
|
|
462
|
+
* Resolves a User View by name, consulting the in-memory {@link UserViewEngine} cache first
|
|
463
|
+
* and falling back to a database `RunView` only on a cache miss.
|
|
464
|
+
*/
|
|
465
|
+
async resolveUserViewByName(viewName, provider, contextUser) {
|
|
466
|
+
if (UserViewEngine.Instance.Loaded) {
|
|
467
|
+
const cached = UserViewEngine.Instance.GetViewByName(viewName);
|
|
468
|
+
if (cached) {
|
|
469
|
+
return cached;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const rv = provider;
|
|
473
|
+
const result = await rv.RunView({
|
|
474
|
+
EntityName: 'MJ: User Views',
|
|
475
|
+
// SECURITY: the view name is client-supplied and lands inside a SQL literal.
|
|
476
|
+
ExtraFilter: "Name='" + EscapeSQLString(viewName) + "'",
|
|
477
|
+
}, contextUser);
|
|
478
|
+
if (result && result.Success && result.Results.length > 0) {
|
|
479
|
+
return result.Results[0];
|
|
480
|
+
}
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Resolves a User View by ID, consulting the in-memory {@link UserViewEngine} cache first
|
|
485
|
+
* and falling back to an entity `Load()` only on a cache miss.
|
|
486
|
+
*/
|
|
487
|
+
async resolveUserViewByID(viewID, provider, contextUser) {
|
|
488
|
+
if (UserViewEngine.Instance.Loaded) {
|
|
489
|
+
const cached = UserViewEngine.Instance.GetViewById(viewID);
|
|
490
|
+
if (cached) {
|
|
491
|
+
return cached;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const viewInfo = await provider.GetEntityObject('MJ: User Views', contextUser);
|
|
495
|
+
await viewInfo.Load(viewID);
|
|
496
|
+
return viewInfo;
|
|
497
|
+
}
|
|
452
498
|
async RunViewByNameGeneric(viewInput, provider, userPayload, pubSub) {
|
|
453
499
|
try {
|
|
454
500
|
// Log aggregate input for debugging
|
|
455
501
|
if (viewInput.Aggregates?.length) {
|
|
456
502
|
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 })))}`);
|
|
457
503
|
}
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
EntityName: 'MJ: User Views',
|
|
461
|
-
// SECURITY: the view name is client-supplied and lands inside a SQL literal.
|
|
462
|
-
ExtraFilter: "Name='" + EscapeSQLString(viewInput.ViewName) + "'",
|
|
463
|
-
}, userPayload.userRecord);
|
|
464
|
-
if (result && result.Success && result.Results.length > 0) {
|
|
465
|
-
const viewInfo = result.Results[0];
|
|
504
|
+
const viewInfo = await this.resolveUserViewByName(viewInput.ViewName, provider, userPayload.userRecord);
|
|
505
|
+
if (viewInfo) {
|
|
466
506
|
return this.RunViewGenericInternal(provider, viewInfo, viewInput.ExtraFilter, viewInput.OrderBy, viewInput.UserSearchString, viewInput.ExcludeUserViewRunID, viewInput.OverrideExcludeFilter, viewInput.SaveViewResults, viewInput.Fields, viewInput.IgnoreMaxRows, viewInput.ExcludeDataFromAllPriorViewRuns, viewInput.ForceAuditLog, viewInput.AuditLogDescription, viewInput.ResultType, userPayload, viewInput.MaxRows, viewInput.StartRow, viewInput.Aggregates, viewInput.AfterKey
|
|
467
507
|
? CompositeKey.FromKeyValuePairs(viewInput.AfterKey.KeyValuePairs)
|
|
468
|
-
: undefined, viewInput.BypassCache, viewInput.DataSource, viewInput.IncludeBinaryFields);
|
|
508
|
+
: undefined, viewInput.BypassCache, viewInput.DataSource, viewInput.IncludeBinaryFields, viewInput.AlternateBaseView, viewInput.Properties);
|
|
469
509
|
}
|
|
470
510
|
else {
|
|
471
511
|
LogError(`RunViewByNameGeneric: View ${viewInput.ViewName} not found or no results returned`);
|
|
@@ -484,9 +524,11 @@ export class ResolverBase {
|
|
|
484
524
|
LogStatus(`[ResolverBase] RunViewByIDGeneric received aggregates: viewID=${viewInput.ViewID}, aggregateCount=${viewInput.Aggregates.length}, aggregates=${JSON.stringify(viewInput.Aggregates.map(a => ({ expression: a.expression, alias: a.alias })))}`);
|
|
485
525
|
}
|
|
486
526
|
const contextUser = this.GetUserFromPayload(userPayload);
|
|
487
|
-
const viewInfo = await
|
|
488
|
-
|
|
489
|
-
|
|
527
|
+
const viewInfo = await this.resolveUserViewByID(viewInput.ViewID, provider, contextUser);
|
|
528
|
+
if (!viewInfo) {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
return this.RunViewGenericInternal(provider, viewInfo, viewInput.ExtraFilter, viewInput.OrderBy, viewInput.UserSearchString, viewInput.ExcludeUserViewRunID, viewInput.OverrideExcludeFilter, viewInput.SaveViewResults, viewInput.Fields, viewInput.IgnoreMaxRows, viewInput.ExcludeDataFromAllPriorViewRuns, viewInput.ForceAuditLog, viewInput.AuditLogDescription, viewInput.ResultType, userPayload, viewInput.MaxRows, viewInput.StartRow, viewInput.Aggregates, undefined, viewInput.BypassCache, viewInput.DataSource, viewInput.IncludeBinaryFields, viewInput.AlternateBaseView, viewInput.Properties);
|
|
490
532
|
}
|
|
491
533
|
catch (err) {
|
|
492
534
|
console.log(err);
|
|
@@ -509,7 +551,7 @@ export class ResolverBase {
|
|
|
509
551
|
EntityID: entity.ID,
|
|
510
552
|
EntityBaseView: entity.BaseView,
|
|
511
553
|
}; // only providing a few bits of data here, but it's enough to get the view to run
|
|
512
|
-
return this.RunViewGenericInternal(provider, viewInfo, viewInput.ExtraFilter, viewInput.OrderBy, viewInput.UserSearchString, viewInput.ExcludeUserViewRunID, viewInput.OverrideExcludeFilter, false, viewInput.Fields, viewInput.IgnoreMaxRows, false, viewInput.ForceAuditLog, viewInput.AuditLogDescription, viewInput.ResultType, userPayload, viewInput.MaxRows, viewInput.StartRow, viewInput.Aggregates, undefined, viewInput.BypassCache, viewInput.DataSource, viewInput.IncludeBinaryFields);
|
|
554
|
+
return this.RunViewGenericInternal(provider, viewInfo, viewInput.ExtraFilter, viewInput.OrderBy, viewInput.UserSearchString, viewInput.ExcludeUserViewRunID, viewInput.OverrideExcludeFilter, false, viewInput.Fields, viewInput.IgnoreMaxRows, false, viewInput.ForceAuditLog, viewInput.AuditLogDescription, viewInput.ResultType, userPayload, viewInput.MaxRows, viewInput.StartRow, viewInput.Aggregates, undefined, viewInput.BypassCache, viewInput.DataSource, viewInput.IncludeBinaryFields, viewInput.AlternateBaseView);
|
|
513
555
|
}
|
|
514
556
|
catch (err) {
|
|
515
557
|
console.log(err);
|
|
@@ -524,7 +566,7 @@ export class ResolverBase {
|
|
|
524
566
|
try {
|
|
525
567
|
let viewInfo = null;
|
|
526
568
|
if (viewInput.ViewName) {
|
|
527
|
-
viewInfo =
|
|
569
|
+
viewInfo = await this.resolveUserViewByName(viewInput.ViewName, provider, userPayload.userRecord);
|
|
528
570
|
// Populate EntityName on the input so callers (e.g. RunViews resolver) can
|
|
529
571
|
// look up the entity without re-querying the view
|
|
530
572
|
if (viewInfo && !viewInput.EntityName) {
|
|
@@ -532,8 +574,7 @@ export class ResolverBase {
|
|
|
532
574
|
}
|
|
533
575
|
}
|
|
534
576
|
else if (viewInput.ViewID) {
|
|
535
|
-
viewInfo = await
|
|
536
|
-
await viewInfo.Load(viewInput.ViewID);
|
|
577
|
+
viewInfo = await this.resolveUserViewByID(viewInput.ViewID, provider, contextUser);
|
|
537
578
|
// Populate EntityName on the input so callers can look up the entity
|
|
538
579
|
if (viewInfo && !viewInput.EntityName) {
|
|
539
580
|
viewInput.EntityName = viewInfo.Entity;
|
|
@@ -580,6 +621,8 @@ export class ResolverBase {
|
|
|
580
621
|
bypassCache: viewInput.BypassCache,
|
|
581
622
|
dataSource: viewInput.DataSource,
|
|
582
623
|
includeBinaryFields: viewInput.IncludeBinaryFields,
|
|
624
|
+
alternateBaseView: viewInput.AlternateBaseView,
|
|
625
|
+
properties: viewInput.Properties,
|
|
583
626
|
});
|
|
584
627
|
}
|
|
585
628
|
catch (err) {
|
|
@@ -590,6 +633,30 @@ export class ResolverBase {
|
|
|
590
633
|
let results = await this.RunViewsGenericInternal(params);
|
|
591
634
|
return results;
|
|
592
635
|
}
|
|
636
|
+
/**
|
|
637
|
+
* What an entity event may carry to a CloudEvents sink or a log line.
|
|
638
|
+
*
|
|
639
|
+
* The whole row for an entity without field security. For one where it applies (its own flag
|
|
640
|
+
* or an IS-A ancestor's), and for `MJ: Record Changes` (whose payload columns hold other
|
|
641
|
+
* entities' values), only the primary key and the names of the fields that changed: the sink
|
|
642
|
+
* is outside MemberJunction with no reader to project the row for, and these events fire for
|
|
643
|
+
* server-internal saves too, whose objects hold every column.
|
|
644
|
+
*/
|
|
645
|
+
EntityEventPayload(baseEntity, changedFieldNames) {
|
|
646
|
+
const entityInfo = baseEntity.EntityInfo;
|
|
647
|
+
if (!entityInfo?.FieldLevelSecurityApplies && !RecordChangeFieldSecurityProjector.IsRecordChangesEntity(entityInfo)) {
|
|
648
|
+
// fls-guard: entity without field security and not the audit trail; whole row is the documented payload
|
|
649
|
+
return baseEntity.GetAll();
|
|
650
|
+
}
|
|
651
|
+
return {
|
|
652
|
+
// Read through Get() rather than PrimaryKey, which builds every field object of a raw-mode record.
|
|
653
|
+
PrimaryKey: (entityInfo.PrimaryKeys ?? []).map((pk) => ({ FieldName: pk.Name, Value: baseEntity.Get(pk.Name) })),
|
|
654
|
+
// A completed save has already rebased the object, so nothing on it is dirty: the event
|
|
655
|
+
// carries the names the save wrote. Other events (save_started) still read them off the object.
|
|
656
|
+
// GetDirtyFieldNames answers without building field objects for a record still in raw mode.
|
|
657
|
+
ChangedFields: changedFieldNames ?? baseEntity.GetDirtyFieldNames(),
|
|
658
|
+
};
|
|
659
|
+
}
|
|
593
660
|
static { this._priorEmittedData = []; }
|
|
594
661
|
async EmitCloudEvent({ component, event, eventCode, args }) {
|
|
595
662
|
if (ResolverBase._emit && event === MJEventType.ComponentEvent && eventCode === BaseEntity.BaseEventCode) {
|
|
@@ -597,7 +664,7 @@ export class ResolverBase {
|
|
|
597
664
|
const type = `MemberJunction.${event}${extendedType}`;
|
|
598
665
|
const source = `${process.env.CLOUDEVENTS_SOURCE ?? 'MemberJunction'}`;
|
|
599
666
|
const subject = args instanceof BaseEntityEvent ? args.baseEntity.EntityInfo.CodeName : undefined;
|
|
600
|
-
const data = args?.baseEntity
|
|
667
|
+
const data = args?.baseEntity ? this.EntityEventPayload(args.baseEntity, args.ChangedFieldNames) : {};
|
|
601
668
|
const cloudEvent = new CloudEvent({ type, source, subject, data });
|
|
602
669
|
try {
|
|
603
670
|
// check to see if the combination of Entity and pkey was already emitted, if so, Log that condtion next
|
|
@@ -873,7 +940,7 @@ export class ResolverBase {
|
|
|
873
940
|
* - Field filtering at source (Fix #7)
|
|
874
941
|
* - Improved error handling (Fix #9)
|
|
875
942
|
*/
|
|
876
|
-
async RunViewGenericInternal(provider, viewInfo, extraFilter, orderBy, userSearchString, excludeUserViewRunID, overrideExcludeFilter, saveViewResults, fields, ignoreMaxRows, excludeDataFromAllPriorViewRuns, forceAuditLog, auditLogDescription, resultType, userPayload, maxRows, startRow, aggregates, afterKey, bypassCache, dataSource, includeBinaryFields) {
|
|
943
|
+
async RunViewGenericInternal(provider, viewInfo, extraFilter, orderBy, userSearchString, excludeUserViewRunID, overrideExcludeFilter, saveViewResults, fields, ignoreMaxRows, excludeDataFromAllPriorViewRuns, forceAuditLog, auditLogDescription, resultType, userPayload, maxRows, startRow, aggregates, afterKey, bypassCache, dataSource, includeBinaryFields, alternateBaseView, properties) {
|
|
877
944
|
try {
|
|
878
945
|
if (!viewInfo || !userPayload)
|
|
879
946
|
return null;
|
|
@@ -919,6 +986,7 @@ export class ResolverBase {
|
|
|
919
986
|
const result = await rv.RunView({
|
|
920
987
|
ViewID: viewInfo.ID,
|
|
921
988
|
ViewName: viewInfo.Name,
|
|
989
|
+
ViewEntity: viewInfo.ID ? viewInfo : undefined,
|
|
922
990
|
EntityName: viewInfo.Entity,
|
|
923
991
|
ExtraFilter: extraFilter,
|
|
924
992
|
OrderBy: orderBy,
|
|
@@ -939,7 +1007,18 @@ export class ResolverBase {
|
|
|
939
1007
|
BypassCache: bypassCache,
|
|
940
1008
|
DataSource: dataSource,
|
|
941
1009
|
IncludeBinaryFields: includeBinaryFields,
|
|
1010
|
+
AlternateBaseView: alternateBaseView,
|
|
1011
|
+
Properties: properties,
|
|
1012
|
+
TimeZone: userPayload?.TimeZone,
|
|
942
1013
|
}, user);
|
|
1014
|
+
if (result && typeof result === 'object') {
|
|
1015
|
+
Object.defineProperty(result, '_resolvedViewEntity', {
|
|
1016
|
+
value: viewInfo,
|
|
1017
|
+
enumerable: false,
|
|
1018
|
+
configurable: true,
|
|
1019
|
+
writable: true,
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
943
1022
|
// Log aggregate results for debugging
|
|
944
1023
|
if (aggregates?.length) {
|
|
945
1024
|
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)}`);
|
|
@@ -966,7 +1045,7 @@ export class ResolverBase {
|
|
|
966
1045
|
if (result?.Success && result.Results?.length) {
|
|
967
1046
|
result.Results = result.Results.map(r => mapper.MapFields({ ...r }));
|
|
968
1047
|
// Filter encrypted fields before sending to API client
|
|
969
|
-
await this.ArrayFilterEncryptedFieldsForAPI(viewInfo.Entity, result.Results, user);
|
|
1048
|
+
await this.ArrayFilterEncryptedFieldsForAPI(viewInfo.Entity, result.Results, user, md);
|
|
970
1049
|
}
|
|
971
1050
|
return result;
|
|
972
1051
|
}
|
|
@@ -1050,6 +1129,7 @@ export class ResolverBase {
|
|
|
1050
1129
|
runViewParams.push({
|
|
1051
1130
|
ViewID: param.viewInfo.ID,
|
|
1052
1131
|
ViewName: param.viewInfo.Name,
|
|
1132
|
+
ViewEntity: param.viewInfo.ID ? param.viewInfo : undefined,
|
|
1053
1133
|
EntityName: param.viewInfo.Entity,
|
|
1054
1134
|
ExtraFilter: param.extraFilter,
|
|
1055
1135
|
OrderBy: param.orderBy,
|
|
@@ -1070,6 +1150,9 @@ export class ResolverBase {
|
|
|
1070
1150
|
BypassCache: param.bypassCache,
|
|
1071
1151
|
DataSource: param.dataSource,
|
|
1072
1152
|
IncludeBinaryFields: param.includeBinaryFields,
|
|
1153
|
+
AlternateBaseView: param.alternateBaseView,
|
|
1154
|
+
Properties: param.properties,
|
|
1155
|
+
TimeZone: param.userPayload?.TimeZone,
|
|
1073
1156
|
});
|
|
1074
1157
|
}
|
|
1075
1158
|
// Fix #4: Run views in a single batch through RunViews
|
|
@@ -1086,7 +1169,7 @@ export class ResolverBase {
|
|
|
1086
1169
|
// Use the corresponding param's entity name
|
|
1087
1170
|
const entityName = params[i]?.viewInfo?.Entity;
|
|
1088
1171
|
if (entityName && contextUser) {
|
|
1089
|
-
await this.ArrayFilterEncryptedFieldsForAPI(entityName, runViewResult.Results, contextUser);
|
|
1172
|
+
await this.ArrayFilterEncryptedFieldsForAPI(entityName, runViewResult.Results, contextUser, md);
|
|
1090
1173
|
}
|
|
1091
1174
|
}
|
|
1092
1175
|
}
|
|
@@ -1205,8 +1288,9 @@ export class ResolverBase {
|
|
|
1205
1288
|
* skip redundant re-fetches (it already handled the event locally).
|
|
1206
1289
|
*
|
|
1207
1290
|
* The row itself rides along ONLY for entities opted in via
|
|
1208
|
-
* `cacheSettings.recordDataBroadcastEntities
|
|
1209
|
-
*
|
|
1291
|
+
* `cacheSettings.recordDataBroadcastEntities`, and never for one under field-level security —
|
|
1292
|
+
* this event reaches every connected client unprojected, so the row would otherwise be readable
|
|
1293
|
+
* by sessions that could not read the record or its restricted fields.
|
|
1210
1294
|
*/
|
|
1211
1295
|
PublishCacheInvalidation(entityObject, action, userPayload) {
|
|
1212
1296
|
const entityName = entityObject.EntityInfo.Name;
|
|
@@ -1217,23 +1301,25 @@ export class ResolverBase {
|
|
|
1217
1301
|
sourceServerId: MJGlobal.Instance.ProcessUUID,
|
|
1218
1302
|
timestamp: new Date(),
|
|
1219
1303
|
originSessionId: userPayload?.sessionId || null,
|
|
1220
|
-
recordData: action === 'save' && MayBroadcastRecordData(entityName)
|
|
1304
|
+
recordData: action === 'save' && MayBroadcastRecordData(entityName, entityObject.EntityInfo)
|
|
1305
|
+
// fls-guard: MayBroadcastRecordData refuses FLS entities and Record Changes
|
|
1221
1306
|
? JSON.stringify(entityObject.GetAll())
|
|
1222
1307
|
: undefined,
|
|
1223
1308
|
});
|
|
1224
1309
|
}
|
|
1225
1310
|
/**
|
|
1226
1311
|
* Publishes a push-status update to the client on {@link PUSH_STATUS_UPDATES_TOPIC}, stamping the
|
|
1227
|
-
*
|
|
1228
|
-
*
|
|
1229
|
-
*
|
|
1230
|
-
*
|
|
1231
|
-
*
|
|
1312
|
+
* owner key of `userPayload` (`StatusUpdateOwnerID`: the request's user, or the widget guest an
|
|
1313
|
+
* elevated payload stands in for) so the subscription filter can bind delivery to identity (see B49 /
|
|
1314
|
+
* `StatusUpdatesFilter`). The ergonomic wrapper every resolver should use instead of calling
|
|
1315
|
+
* `pubSub.publish` on the topic directly — it makes omitting identity impossible. Non-resolver
|
|
1316
|
+
* publishers (services, the liveness heartbeat) call the shared `PublishStatusUpdate()` function
|
|
1317
|
+
* directly with an explicit `ownerUserId`.
|
|
1232
1318
|
*/
|
|
1233
1319
|
PublishStatusUpdate(pubSub, sessionId, message, userPayload) {
|
|
1234
1320
|
PublishStatusUpdate(pubSub, {
|
|
1235
1321
|
sessionId,
|
|
1236
|
-
ownerUserId: userPayload
|
|
1322
|
+
ownerUserId: StatusUpdateOwnerID(userPayload),
|
|
1237
1323
|
message,
|
|
1238
1324
|
});
|
|
1239
1325
|
}
|
|
@@ -1253,28 +1339,50 @@ export class ResolverBase {
|
|
|
1253
1339
|
if (baseEntity?.EntityInfo?.Name !== uniqueKey) {
|
|
1254
1340
|
return;
|
|
1255
1341
|
}
|
|
1256
|
-
const baseEntityValues = baseEntity ? baseEntity.
|
|
1342
|
+
const baseEntityValues = baseEntity ? this.EntityEventPayload(baseEntity, event.args instanceof BaseEntityEvent ? event.args.ChangedFieldNames : undefined) : null;
|
|
1257
1343
|
const eventToLog = { entityName: entityObject.EntityInfo.Name, baseEntity: baseEntityValues, event: event.event, eventCode: event.eventCode };
|
|
1258
1344
|
LogDebug(`ResolverBase.ListenForEntityMessages: Received Event from within MJGlobal.Instance.GetEventListener() callback. Will call EmitCloudEvent() next\nEvent data:\n${JSON.stringify(eventToLog)}`);
|
|
1259
1345
|
await this.EmitCloudEvent(event);
|
|
1260
1346
|
LogDebug(`ResolverBase.ListenForEntityMessages: EmitCloudEvent() completed successfully`);
|
|
1261
|
-
if (event.args && event.args instanceof BaseEntityEvent) {
|
|
1262
|
-
|
|
1263
|
-
// message from our entity object, relay it to the client
|
|
1347
|
+
if (event.args && event.args instanceof BaseEntityEvent && this.EntityEventBelongsToSession(event.args, userPayload)) {
|
|
1348
|
+
// message from an entity object of this session's user, relay it to the client
|
|
1264
1349
|
LogDebug('ResolverBase.ListenForEntityMessages: About to publish PUSH_STATUS_UPDATES_TOPIC');
|
|
1265
|
-
this.PublishStatusUpdate(pubSub, userPayload.sessionId,
|
|
1266
|
-
status: 'OK',
|
|
1267
|
-
type: 'EntityObjectStatusMessage',
|
|
1268
|
-
entityName: baseEntityEvent.baseEntity.EntityInfo.Name,
|
|
1269
|
-
primaryKey: baseEntityEvent.baseEntity.PrimaryKey,
|
|
1270
|
-
message: event.args.payload,
|
|
1271
|
-
}), userPayload);
|
|
1350
|
+
this.PublishStatusUpdate(pubSub, userPayload.sessionId, this.EntityStatusMessage(event.args), userPayload);
|
|
1272
1351
|
}
|
|
1273
1352
|
}
|
|
1274
1353
|
});
|
|
1275
1354
|
this.eventSubscriptions.set(uniqueKey, theSub);
|
|
1276
1355
|
}
|
|
1277
1356
|
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Whether an entity event may be relayed to the session whose call created the listener. The
|
|
1359
|
+
* listener is shared by every caller in the process for that entity, so an event raised by an
|
|
1360
|
+
* object loaded for another user (or for the system user) is never relayed to this session.
|
|
1361
|
+
*/
|
|
1362
|
+
EntityEventBelongsToSession(event, userPayload) {
|
|
1363
|
+
const sessionUserId = userPayload?.userRecord?.ID;
|
|
1364
|
+
const objectUserId = event.baseEntity?.ContextCurrentUser?.ID;
|
|
1365
|
+
return Boolean(sessionUserId && objectUserId && UUIDsEqual(sessionUserId, objectUserId));
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* The status message relayed for an entity event: the entity, key and event type. The event's
|
|
1369
|
+
* payload is included only when it cannot carry record values: never for a delete (its payload
|
|
1370
|
+
* is the whole deleted row), an entity under field-level security, or `MJ: Record Changes`.
|
|
1371
|
+
*/
|
|
1372
|
+
EntityStatusMessage(event) {
|
|
1373
|
+
const entityInfo = event.baseEntity.EntityInfo;
|
|
1374
|
+
const mayCarryPayload = event.type !== 'delete'
|
|
1375
|
+
&& !entityInfo.FieldLevelSecurityApplies
|
|
1376
|
+
&& !RecordChangeFieldSecurityProjector.IsRecordChangesEntity(entityInfo);
|
|
1377
|
+
return JSON.stringify({
|
|
1378
|
+
status: 'OK',
|
|
1379
|
+
type: 'EntityObjectStatusMessage',
|
|
1380
|
+
entityName: entityInfo.Name,
|
|
1381
|
+
primaryKey: event.baseEntity.PrimaryKey,
|
|
1382
|
+
eventType: event.type,
|
|
1383
|
+
message: mayCarryPayload ? event.payload : undefined,
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1278
1386
|
/**
|
|
1279
1387
|
* IS-A PROMOTION over the wire: an EXISTING parent record gaining a subtype — an Animal that is
|
|
1280
1388
|
* now also a Dog. It arrives as the CHILD's create mutation carrying the existing parent's
|
|
@@ -1348,6 +1456,9 @@ export class ResolverBase {
|
|
|
1348
1456
|
// IS-A promotion: bind the new child to its EXISTING parent row BEFORE the field
|
|
1349
1457
|
// assignments, so values the client sent for parent fields land on the loaded parent
|
|
1350
1458
|
// (as an update) instead of being wiped by the load.
|
|
1459
|
+
// Field-level security: refuse a key the caller may not read, or that names no field, before
|
|
1460
|
+
// anything is applied (the same gate as the update path).
|
|
1461
|
+
AssertClientInputWritableForUser(entityObject.EntityInfo, fieldsForSet, this.GetUserFromPayload(userPayload));
|
|
1351
1462
|
await this.attachToExistingParentIfPromotion(entityObject, fieldsForSet);
|
|
1352
1463
|
entityObject.SetMany(fieldsForSet);
|
|
1353
1464
|
// Reconstruct the client-side restore context, if any, on this server
|
|
@@ -1361,6 +1472,7 @@ export class ResolverBase {
|
|
|
1361
1472
|
// Cache invalidation is now handled globally by the MJGlobal listener in index.ts
|
|
1362
1473
|
const contextUser = this.GetUserFromPayload(userPayload);
|
|
1363
1474
|
// MapFieldNamesToCodeNames now handles encryption filtering as well
|
|
1475
|
+
// fls-guard: MapFieldNamesToCodeNames drops the fields the caller may not read
|
|
1364
1476
|
return await this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll(), contextUser);
|
|
1365
1477
|
}
|
|
1366
1478
|
// save failed, throw error with message
|
|
@@ -1398,12 +1510,11 @@ export class ResolverBase {
|
|
|
1398
1510
|
}
|
|
1399
1511
|
});
|
|
1400
1512
|
// Field-level security: any value the client sends for a field it cannot READ is
|
|
1401
|
-
// fabricated by construction (the field was
|
|
1402
|
-
// received), so
|
|
1403
|
-
// denied we also force the truth-load branch below
|
|
1404
|
-
//
|
|
1405
|
-
|
|
1406
|
-
const hasDeniedReadFields = this.StripDeniedReadFieldsFromClientInput(entityInfo, userInfo, input, clientNewValues);
|
|
1513
|
+
// fabricated by construction (the field was absent from every payload the client ever
|
|
1514
|
+
// received), so the input is refused before anything applies it. When any field is
|
|
1515
|
+
// denied we also force the truth-load branch below, so the denied columns keep what the
|
|
1516
|
+
// database holds rather than anything derived from the client's OldValues.
|
|
1517
|
+
const hasDeniedReadFields = this.AssertClientInputFieldSecurity(entityInfo, userInfo, input, clientNewValues);
|
|
1407
1518
|
// The audit-trail equivalent. Its `true` matters most on THIS entity: 'MJ: Record Changes'
|
|
1408
1519
|
// has TrackRecordChanges off, so without it a client sending OldValues takes the branch below
|
|
1409
1520
|
// that hydrates from those values — never loading what the database actually holds.
|
|
@@ -1459,6 +1570,7 @@ export class ResolverBase {
|
|
|
1459
1570
|
await this.AfterUpdate(provider, input); // fire event
|
|
1460
1571
|
// Cache invalidation is now handled globally by the MJGlobal listener in index.ts
|
|
1461
1572
|
// MapFieldNamesToCodeNames now handles encryption filtering as well
|
|
1573
|
+
// fls-guard: MapFieldNamesToCodeNames drops the fields the caller may not read
|
|
1462
1574
|
return await this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll(), userInfo);
|
|
1463
1575
|
}
|
|
1464
1576
|
else {
|
|
@@ -1510,7 +1622,7 @@ export class ResolverBase {
|
|
|
1510
1622
|
* writable fields in `OldValues___`, so {@link hasCompleteOldValues} returns `false` and forces the
|
|
1511
1623
|
* database load (`InnerLoad`) so untouched columns retain their stored values.
|
|
1512
1624
|
*
|
|
1513
|
-
* `
|
|
1625
|
+
* `FieldLevelSecurityApplies` (own flag or an IS-A ancestor's) is the security-critical one, and it is NOT redundant with
|
|
1514
1626
|
* `hasDeniedReadFields`. That flag reports READ denials, and the canonical FLS configuration is
|
|
1515
1627
|
* Read Allow + Update Deny — which leaves it false. Such a caller would be hydrated from its own
|
|
1516
1628
|
* `OldValues___`, and a value it supplies there for an update-denied field arrives through
|
|
@@ -1558,60 +1670,43 @@ export class ResolverBase {
|
|
|
1558
1670
|
!this.hasCompleteOldValues(entityInfo, input.OldValues___) ||
|
|
1559
1671
|
hasDeniedReadFields ||
|
|
1560
1672
|
hasNarrowedAuditPayload ||
|
|
1561
|
-
entityInfo.
|
|
1673
|
+
entityInfo.FieldLevelSecurityApplies ||
|
|
1562
1674
|
!user ||
|
|
1563
1675
|
entityInfo.GetEffectiveRowFilterWhereClause(user, EntityPermissionType.Update, '').length > 0);
|
|
1564
1676
|
}
|
|
1565
1677
|
/**
|
|
1566
|
-
* Field-level security
|
|
1678
|
+
* Field-level security gate on the update path's client input.
|
|
1567
1679
|
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1572
|
-
* save" safe for a restricted user.
|
|
1680
|
+
* For a caller denied at least one field on the entity, refuses (with a `FieldSecurityError`)
|
|
1681
|
+
* any key in the new values or in `OldValues___` that names a field the caller cannot READ, or
|
|
1682
|
+
* that names no field at all, with the same message for both (see
|
|
1683
|
+
* `AssertClientInputWritableForUser`), so the refusal does not reveal which columns exist.
|
|
1573
1684
|
*
|
|
1574
|
-
*
|
|
1575
|
-
*
|
|
1685
|
+
* Refused rather than dropped: a field the caller cannot read was absent from every payload the
|
|
1686
|
+
* client received, so a value for it can only be fabricated. A well-behaved client never sends
|
|
1687
|
+
* one (it omits fields it did not load), so ordinary traffic is unaffected.
|
|
1576
1688
|
*
|
|
1577
|
-
*
|
|
1578
|
-
*
|
|
1579
|
-
* and is restricted).
|
|
1689
|
+
* There is no split by update permission: Read is required for Update, so a user denied read is
|
|
1690
|
+
* denied update too.
|
|
1580
1691
|
*
|
|
1581
|
-
* Returns true when the user has a non-empty denied-read set on this entity. The caller
|
|
1582
|
-
*
|
|
1583
|
-
*
|
|
1692
|
+
* Returns true when the user has a non-empty denied-read set on this entity. The caller must
|
|
1693
|
+
* then hydrate the entity from the DATABASE (never from client OldValues), so denied fields hold
|
|
1694
|
+
* true values the client input never touches.
|
|
1584
1695
|
*/
|
|
1585
|
-
|
|
1586
|
-
if (!entityInfo.
|
|
1696
|
+
AssertClientInputFieldSecurity(entityInfo, userInfo, input, clientNewValues) {
|
|
1697
|
+
if (!entityInfo.FieldLevelSecurityApplies || !userInfo) {
|
|
1587
1698
|
return false;
|
|
1588
1699
|
}
|
|
1589
|
-
|
|
1590
|
-
if (deniedReadNames.size === 0) {
|
|
1700
|
+
if (entityInfo.GetDeniedReadFields(userInfo).size === 0) {
|
|
1591
1701
|
return false;
|
|
1592
1702
|
}
|
|
1593
|
-
|
|
1594
|
-
// lowercased field Names — bridge via the field metadata once.
|
|
1595
|
-
const deniedReadCodeNames = new Set();
|
|
1596
|
-
for (const field of entityInfo.Fields) {
|
|
1597
|
-
if (deniedReadNames.has(field.Name.trim().toLowerCase())) {
|
|
1598
|
-
deniedReadCodeNames.add(field.CodeName.trim().toLowerCase());
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
const stripped = [];
|
|
1602
|
-
for (const key of Object.keys(clientNewValues)) {
|
|
1603
|
-
if (deniedReadCodeNames.has(key.trim().toLowerCase())) {
|
|
1604
|
-
delete clientNewValues[key];
|
|
1605
|
-
delete input[key];
|
|
1606
|
-
stripped.push(key);
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1703
|
+
AssertClientInputWritableForUser(entityInfo, clientNewValues, userInfo);
|
|
1609
1704
|
if (Array.isArray(input.OldValues___)) {
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1705
|
+
const oldValueKeys = {};
|
|
1706
|
+
for (const item of input.OldValues___) {
|
|
1707
|
+
oldValueKeys[String(item.Key)] = null;
|
|
1708
|
+
}
|
|
1709
|
+
AssertClientInputWritableForUser(entityInfo, oldValueKeys, userInfo);
|
|
1615
1710
|
}
|
|
1616
1711
|
return true;
|
|
1617
1712
|
}
|
|
@@ -1740,6 +1835,7 @@ export class ResolverBase {
|
|
|
1740
1835
|
});
|
|
1741
1836
|
// clientOldValues now has all of the oldValues the CLIENT passed us. Now we need to build the same kind of object
|
|
1742
1837
|
// with the DB values
|
|
1838
|
+
// fls-guard: compared on the server only, never returned
|
|
1743
1839
|
const dbValues = entityObject.GetAll();
|
|
1744
1840
|
// 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
|
|
1745
1841
|
const dbDifferences = [];
|
|
@@ -1820,30 +1916,31 @@ export class ResolverBase {
|
|
|
1820
1916
|
};
|
|
1821
1917
|
// Log as warning to console and ErrorLog table instead of throwing error
|
|
1822
1918
|
console.warn('Entity save inconsistency detected but allowing save to continue:', JSON.stringify(msg));
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1919
|
+
LogErrorEx({
|
|
1920
|
+
message: `Entity save inconsistency detected: ${JSON.stringify(msg)}`,
|
|
1921
|
+
severity: 'warning',
|
|
1922
|
+
category: 'ResolverBase.TestAndSetClientOldValuesToDBValues',
|
|
1923
|
+
metadata: {
|
|
1828
1924
|
entityName: entityObject.EntityInfo.Name,
|
|
1829
1925
|
clientDifferences: clientDifferences,
|
|
1830
1926
|
dbDifferences: dbDifferences,
|
|
1831
|
-
overlap: overlap
|
|
1832
|
-
}
|
|
1927
|
+
overlap: overlap,
|
|
1928
|
+
},
|
|
1833
1929
|
});
|
|
1834
1930
|
// Create ErrorLog record in the database — use the entity's bound provider so the
|
|
1835
1931
|
// ErrorLog write goes to the same connection as the entity that triggered it.
|
|
1836
1932
|
try {
|
|
1837
1933
|
const md = entityObject.ProviderToUse;
|
|
1838
1934
|
const errorLogEntity = await md.GetEntityObject('MJ: Error Logs', contextUser);
|
|
1839
|
-
|
|
1935
|
+
// Code is NCHAR(20); a longer value is rejected at the database
|
|
1936
|
+
errorLogEntity.Code = 'SAVE_INCONSISTENCY';
|
|
1840
1937
|
errorLogEntity.Message = `Entity save inconsistency detected for ${entityObject.EntityInfo.Name}: ${JSON.stringify(msg)}`;
|
|
1841
1938
|
errorLogEntity.Status = 'Warning';
|
|
1842
1939
|
errorLogEntity.Category = 'Entity Save';
|
|
1843
1940
|
errorLogEntity.CreatedBy = contextUser.Email || contextUser.Name;
|
|
1844
1941
|
const saveResult = await errorLogEntity.Save();
|
|
1845
1942
|
if (!saveResult) {
|
|
1846
|
-
console.error(
|
|
1943
|
+
console.error(`Failed to save ErrorLog record for ${entityObject.EntityInfo.Name}: ${errorLogEntity.LatestResult?.CompleteMessage}`);
|
|
1847
1944
|
}
|
|
1848
1945
|
}
|
|
1849
1946
|
catch (errorLogError) {
|
|
@@ -1872,7 +1969,10 @@ export class ResolverBase {
|
|
|
1872
1969
|
extensions: { code: 'LOAD_ENTITY_ERROR', entityName },
|
|
1873
1970
|
});
|
|
1874
1971
|
}
|
|
1875
|
-
|
|
1972
|
+
// Grab the values before we delete so we can return the last state if the delete succeeds,
|
|
1973
|
+
// projected for the caller in case the object holds a column they may not read.
|
|
1974
|
+
// fls-guard: projected by ProjectEntityValuesForUser
|
|
1975
|
+
const returnValue = ProjectEntityValuesForUser(provider, entityObject.EntityInfo, entityObject.GetAll(), this.GetUserFromPayload(userPayload));
|
|
1876
1976
|
this.ListenForEntityMessages(entityObject, pubSub, userPayload);
|
|
1877
1977
|
if (await entityObject.Delete(options)) {
|
|
1878
1978
|
await this.AfterDelete(provider, key); // fire event
|