@memberjunction/server 6.2.0-edge.1 → 6.2.0-edge.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/config.d.ts +2189 -71
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +123 -3
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +11 -23
- package/dist/context.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +2143 -205
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +24441 -14433
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts +15 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.js +16 -2
- package/dist/generic/FireAndForgetHeartbeat.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts +31 -0
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -0
- package/dist/generic/OrphanedConversationDetailReconciler.js +215 -0
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -0
- package/dist/generic/PushStatusResolver.d.ts +38 -0
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +84 -1
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +56 -4
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +191 -74
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +4 -0
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +33 -1
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +204 -50
- package/dist/index.js.map +1 -1
- package/dist/postgresqlCompat.d.ts +8 -0
- package/dist/postgresqlCompat.d.ts.map +1 -0
- package/dist/postgresqlCompat.js +19 -0
- package/dist/postgresqlCompat.js.map +1 -0
- package/dist/postgresqlPoolSettings.d.ts +108 -0
- package/dist/postgresqlPoolSettings.d.ts.map +1 -0
- package/dist/postgresqlPoolSettings.js +134 -0
- package/dist/postgresqlPoolSettings.js.map +1 -0
- package/dist/resolvers/AdhocQueryResolver.d.ts +23 -18
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +77 -167
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.d.ts +55 -0
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -0
- package/dist/resolvers/AgentRunStatusPublisher.js +141 -0
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -0
- package/dist/resolvers/ConversationRunEventsResolver.d.ts +90 -0
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -0
- package/dist/resolvers/ConversationRunEventsResolver.js +283 -0
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -0
- package/dist/resolvers/DuplicateEntryCheckResolver.d.ts +98 -0
- package/dist/resolvers/DuplicateEntryCheckResolver.d.ts.map +1 -0
- package/dist/resolvers/DuplicateEntryCheckResolver.js +227 -0
- package/dist/resolvers/DuplicateEntryCheckResolver.js.map +1 -0
- package/dist/resolvers/EntityRecordNameResolver.d.ts +7 -23
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +9 -47
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +2 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +9 -0
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts +107 -0
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -0
- package/dist/resolvers/HumanHandoffResolver.js +324 -0
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -0
- package/dist/resolvers/MeetingResolver.d.ts +69 -0
- package/dist/resolvers/MeetingResolver.d.ts.map +1 -0
- package/dist/resolvers/MeetingResolver.js +381 -0
- package/dist/resolvers/MeetingResolver.js.map +1 -0
- package/dist/resolvers/MergeRecordsResolver.d.ts +7 -0
- package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.js +16 -2
- package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +34 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +202 -7
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +45 -89
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +94 -240
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +0 -16
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +23 -153
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunDecisionResolver.d.ts +149 -0
- package/dist/resolvers/RunDecisionResolver.d.ts.map +1 -0
- package/dist/resolvers/RunDecisionResolver.js +396 -0
- package/dist/resolvers/RunDecisionResolver.js.map +1 -0
- package/dist/resolvers/VectorizeEntityResolver.d.ts +1 -1
- package/dist/resolvers/VectorizeEntityResolver.d.ts.map +1 -1
- package/dist/resolvers/VectorizeEntityResolver.js +14 -10
- package/dist/resolvers/VectorizeEntityResolver.js.map +1 -1
- package/dist/resolvers/adhocTimeout.d.ts +7 -0
- package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
- package/dist/resolvers/adhocTimeout.js +12 -0
- package/dist/resolvers/adhocTimeout.js.map +1 -0
- package/dist/resolvers/meetings/index.d.ts +5 -0
- package/dist/resolvers/meetings/index.d.ts.map +1 -0
- package/dist/resolvers/meetings/index.js +5 -0
- package/dist/resolvers/meetings/index.js.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts +17 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.js +212 -0
- package/dist/resolvers/meetings/meetingHelpers.js.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts +12 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.js +418 -0
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts +7 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.js +113 -0
- package/dist/resolvers/meetings/meetingQueries.js.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts +89 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.js +345 -0
- package/dist/resolvers/meetings/meetingTypes.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +8 -5
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +8 -0
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -1
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +36 -0
- package/dist/services/WorkQueueHostService.d.ts.map +1 -0
- package/dist/services/WorkQueueHostService.js +97 -0
- package/dist/services/WorkQueueHostService.js.map +1 -0
- package/dist/services/workQueueConfig.d.ts +103 -0
- package/dist/services/workQueueConfig.d.ts.map +1 -0
- package/dist/services/workQueueConfig.js +48 -0
- package/dist/services/workQueueConfig.js.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +95 -92
- package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
- package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
- package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
- package/src/__tests__/AgentRunStatusPublisher.test.ts +175 -0
- package/src/__tests__/DuplicateEntryCheckResolver.test.ts +300 -0
- package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
- package/src/__tests__/HumanHandoffResolver.test.ts +233 -0
- package/src/__tests__/MeetingResolver.test.ts +374 -0
- package/src/__tests__/RealtimeBridgeResolver.test.ts +240 -2
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +216 -2
- package/src/__tests__/RemoteBrowserActionRecovery.test.ts +7 -1
- package/src/__tests__/RemoteBrowserAudioStream.test.ts +7 -1
- package/src/__tests__/RemoteBrowserSnapshot.test.ts +7 -1
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +96 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +52 -0
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +21 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +291 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +182 -12
- package/src/__tests__/RunAIAgentResolver.streaming.test.ts +18 -27
- package/src/__tests__/RunDecisionResolver.test.ts +699 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +43 -0
- package/src/__tests__/VectorizeEntityResolver.test.ts +106 -0
- package/src/__tests__/WorkQueueHostService.test.ts +133 -0
- package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
- package/src/__tests__/conversationRunEvents.test.ts +399 -0
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +283 -0
- package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
- package/src/__tests__/pushStatusFanOut.test.ts +206 -0
- package/src/__tests__/translateBracketsToPG.test.ts +20 -0
- package/src/auth/__tests__/backward-compatibility.test.ts +1 -0
- package/src/config.ts +130 -3
- package/src/context.ts +11 -24
- package/src/generated/graphql-schemas/__mj.ts +24183 -17182
- package/src/generic/FireAndForgetHeartbeat.ts +16 -2
- package/src/generic/OrphanedConversationDetailReconciler.ts +267 -0
- package/src/generic/PushStatusResolver.ts +97 -1
- package/src/generic/ResolverBase.ts +220 -76
- package/src/generic/RunViewResolver.ts +33 -2
- package/src/index.ts +228 -51
- package/src/postgresqlCompat.ts +21 -0
- package/src/postgresqlPoolSettings.ts +213 -0
- package/src/resolvers/AdhocQueryResolver.ts +88 -188
- package/src/resolvers/AgentRunStatusPublisher.ts +175 -0
- package/src/resolvers/ConversationRunEventsResolver.ts +308 -0
- package/src/resolvers/DuplicateEntryCheckResolver.ts +232 -0
- package/src/resolvers/EntityRecordNameResolver.ts +9 -51
- package/src/resolvers/FetchEntityVectorsResolver.ts +2 -1
- package/src/resolvers/GetDataResolver.ts +10 -0
- package/src/resolvers/HumanHandoffResolver.ts +304 -0
- package/src/resolvers/MeetingResolver.ts +438 -0
- package/src/resolvers/MergeRecordsResolver.ts +14 -2
- package/src/resolvers/RealtimeBridgeResolver.ts +227 -13
- package/src/resolvers/RealtimeClientSessionResolver.ts +99 -287
- package/src/resolvers/RunAIAgentResolver.ts +28 -174
- package/src/resolvers/RunDecisionResolver.ts +435 -0
- package/src/resolvers/VectorizeEntityResolver.ts +16 -11
- package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +93 -0
- package/src/resolvers/adhocTimeout.ts +12 -0
- package/src/resolvers/meetings/index.ts +4 -0
- package/src/resolvers/meetings/meetingHelpers.ts +307 -0
- package/src/resolvers/meetings/meetingMutations.ts +535 -0
- package/src/resolvers/meetings/meetingQueries.ts +172 -0
- package/src/resolvers/meetings/meetingTypes.ts +238 -0
- package/src/rest/ViewOperationsHandler.ts +8 -5
- package/src/services/TaskGraphActionRunner.ts +29 -1
- package/src/services/WorkQueueHostService.ts +129 -0
- package/src/services/workQueueConfig.ts +52 -0
- package/src/types.ts +5 -0
- package/dist/resolvers/adhoc-query-helpers.d.ts +0 -33
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
- package/dist/resolvers/adhoc-query-helpers.js +0 -50
- package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
- package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
- package/src/resolvers/adhoc-query-helpers.ts +0 -67
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResolverBase.d.ts","sourceRoot":"","sources":["../../src/generic/ResolverBase.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,UAAU,EAEV,YAAY,EACZ,oBAAoB,
|
|
1
|
+
{"version":3,"file":"ResolverBase.d.ts","sourceRoot":"","sources":["../../src/generic/ResolverBase.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseEntity, BaseEntityEvent, CompositeKey, EntityFieldTSType, KeyValuePair, LogDebug, LogError, LogStatus, Metadata, ReadableFieldsTransportKey, RecordChangeFieldSecurityProjector, RecordChangePayloadFields, } from '@memberjunction/core';
|
|
1
|
+
import { BaseEntity, BaseEntityEvent, CompositeKey, EntityFieldTSType, EntityPermissionType, KeyValuePair, LogDebug, LogError, LogStatus, Metadata, ReadableFieldsTransportKey, RecordChangeFieldSecurityProjector, RecordChangePayloadFields, } from '@memberjunction/core';
|
|
2
2
|
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
3
3
|
import { AuthorizationError } from 'type-graphql';
|
|
4
4
|
import { GraphQLError } from 'graphql';
|
|
@@ -6,7 +6,7 @@ import { RefusalExtensions } from './refusalExtensions.js';
|
|
|
6
6
|
import { GetAPIKeyEngine } from '@memberjunction/api-keys';
|
|
7
7
|
import { httpTransport, CloudEvent, emitterFor } from 'cloudevents';
|
|
8
8
|
import { DeleteOptionsInput } from './DeleteOptionsInput.js';
|
|
9
|
-
import { MJEventType, MJGlobal, ENCRYPTED_SENTINEL, EscapeSQLString, IsValueEncrypted, IsOnlyTimezoneShift } from '@memberjunction/global';
|
|
9
|
+
import { MJEventType, MJGlobal, ENCRYPTED_SENTINEL, ReplaceByteArraysWithBase64, EscapeSQLString, IsValueEncrypted, IsOnlyTimezoneShift } from '@memberjunction/global';
|
|
10
10
|
import { SQLParser } from '@memberjunction/sql-parser';
|
|
11
11
|
import { PostgreSQLDialect, SQLServerDialect } from '@memberjunction/sql-dialect';
|
|
12
12
|
import { EncryptionEngine } from '@memberjunction/encryption';
|
|
@@ -57,7 +57,10 @@ export class ResolverBase {
|
|
|
57
57
|
return null;
|
|
58
58
|
}
|
|
59
59
|
// Shallow copy up front so every write below lands on our object, never the caller's.
|
|
60
|
-
|
|
60
|
+
// Binary values are base64 strings everywhere above the providers; any byte array that still
|
|
61
|
+
// reaches a resolver (custom code, an external driver) is converted here, because GraphQL's
|
|
62
|
+
// String scalar cannot serialize a Buffer and would fail the whole response.
|
|
63
|
+
dataObject = ReplaceByteArraysWithBase64({ ...dataObject });
|
|
61
64
|
// for the given entity name provided, check to see if there are any fields
|
|
62
65
|
// where the code name is different from the field name, and for just those
|
|
63
66
|
// fields, iterate through the dataObject and REPLACE the property that has the field name
|
|
@@ -462,7 +465,7 @@ export class ResolverBase {
|
|
|
462
465
|
const viewInfo = result.Results[0];
|
|
463
466
|
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
|
|
464
467
|
? CompositeKey.FromKeyValuePairs(viewInput.AfterKey.KeyValuePairs)
|
|
465
|
-
: undefined, viewInput.BypassCache, viewInput.DataSource);
|
|
468
|
+
: undefined, viewInput.BypassCache, viewInput.DataSource, viewInput.IncludeBinaryFields);
|
|
466
469
|
}
|
|
467
470
|
else {
|
|
468
471
|
LogError(`RunViewByNameGeneric: View ${viewInput.ViewName} not found or no results returned`);
|
|
@@ -483,7 +486,7 @@ export class ResolverBase {
|
|
|
483
486
|
const contextUser = this.GetUserFromPayload(userPayload);
|
|
484
487
|
const viewInfo = await provider.GetEntityObject('MJ: User Views', contextUser);
|
|
485
488
|
await viewInfo.Load(viewInput.ViewID);
|
|
486
|
-
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);
|
|
489
|
+
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);
|
|
487
490
|
}
|
|
488
491
|
catch (err) {
|
|
489
492
|
console.log(err);
|
|
@@ -506,7 +509,7 @@ export class ResolverBase {
|
|
|
506
509
|
EntityID: entity.ID,
|
|
507
510
|
EntityBaseView: entity.BaseView,
|
|
508
511
|
}; // only providing a few bits of data here, but it's enough to get the view to run
|
|
509
|
-
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);
|
|
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);
|
|
510
513
|
}
|
|
511
514
|
catch (err) {
|
|
512
515
|
console.log(err);
|
|
@@ -576,6 +579,7 @@ export class ResolverBase {
|
|
|
576
579
|
aggregates: viewInput.Aggregates,
|
|
577
580
|
bypassCache: viewInput.BypassCache,
|
|
578
581
|
dataSource: viewInput.DataSource,
|
|
582
|
+
includeBinaryFields: viewInput.IncludeBinaryFields,
|
|
579
583
|
});
|
|
580
584
|
}
|
|
581
585
|
catch (err) {
|
|
@@ -729,7 +733,7 @@ export class ResolverBase {
|
|
|
729
733
|
* into the view. Subqueries against another entity's BaseView therefore do not inherit that
|
|
730
734
|
* entity's RLS; they are still restricted to the view (not the table).
|
|
731
735
|
*/
|
|
732
|
-
assertClientClauseUsesEntityBaseViews(clause, label, provider) {
|
|
736
|
+
assertClientClauseUsesEntityBaseViews(clause, label, provider, user) {
|
|
733
737
|
if (!clause?.trim())
|
|
734
738
|
return;
|
|
735
739
|
const dialect = this.dialectForProvider(provider);
|
|
@@ -753,13 +757,79 @@ export class ResolverBase {
|
|
|
753
757
|
const schema = this.stripSqlIdent(t.SchemaName);
|
|
754
758
|
if (table.toLowerCase() === '__mj_clause_screen')
|
|
755
759
|
continue;
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
760
|
+
this.assertTableRefReadable(allowed, schema, table, label, user);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* SECURITY — screen for a FULL client-supplied SQL statement (ad-hoc query path). In addition
|
|
765
|
+
* to the SELECT-only validation the caller performs, every table reference must resolve to an
|
|
766
|
+
* entity BaseView the acting user can read UNSCOPED. CTE names defined by the statement itself
|
|
767
|
+
* are excluded from the check. Fails closed: no user, or an unresolvable reference, refuses.
|
|
768
|
+
*
|
|
769
|
+
* Entity CanRead is not unscoped base-view read authority: RunView narrows a granted read with
|
|
770
|
+
* row-level security and denied-field projection, and raw SQL applies neither. Rather than try
|
|
771
|
+
* to rewrite arbitrary SQL (joins, CTEs) to compose that policy, this refuses any entity that
|
|
772
|
+
* carries a row filter or a denied field for the caller — those reads belong on RunView. For
|
|
773
|
+
* the remaining entities CanRead genuinely means "every row and field of the view".
|
|
774
|
+
*/
|
|
775
|
+
assertFullQueryUsesReadableEntityViews(sqlText, provider, user, label = 'SQL') {
|
|
776
|
+
if (!user) {
|
|
777
|
+
throw new Error(`Invalid ${label}: no acting user resolved for ad-hoc SQL — refusing`);
|
|
778
|
+
}
|
|
779
|
+
const dialect = this.dialectForProvider(provider);
|
|
780
|
+
const parser = new SQLParser(sqlText, dialect);
|
|
781
|
+
if (!parser.IsValid || parser.HasWriteStatement || parser.StatementKind !== 'select') {
|
|
782
|
+
throw new Error(`Invalid ${label}: not a safe read-only statement — refusing under uncertainty`);
|
|
783
|
+
}
|
|
784
|
+
if (this.astContainsWriteNode(parser.AST)) {
|
|
785
|
+
throw new Error(`Invalid ${label}: write/DDL nested in a subquery is not permitted`);
|
|
786
|
+
}
|
|
787
|
+
const cteNames = new Set();
|
|
788
|
+
this.collectCTENames(parser.AST, cteNames);
|
|
789
|
+
const allowed = this.entityBaseViewAllowList(provider);
|
|
790
|
+
const tables = SQLParser.ExtractTableRefs(sqlText, dialect);
|
|
791
|
+
for (const t of tables) {
|
|
792
|
+
const table = this.stripSqlIdent(t.TableName);
|
|
793
|
+
const schema = this.stripSqlIdent(t.SchemaName);
|
|
794
|
+
if (this.isUnqualifiedSchema(schema) && cteNames.has(table.toLowerCase()))
|
|
759
795
|
continue;
|
|
760
|
-
|
|
796
|
+
const entity = this.assertTableRefReadable(allowed, schema, table, label, user);
|
|
797
|
+
this.assertUnscopedRead(entity, user, label);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
/** Refuses an entity whose read is narrowed for this user by row-level security or field-level denials. */
|
|
801
|
+
assertUnscopedRead(entity, user, label) {
|
|
802
|
+
if (entity.GetEffectiveRowFilterWhereClause(user, EntityPermissionType.Read, '').length > 0) {
|
|
803
|
+
throw new Error(`Invalid ${label}: entity '${entity.Name}' is row-level-security filtered for you — ad-hoc SQL cannot apply that filter; use RunView`);
|
|
804
|
+
}
|
|
805
|
+
if (entity.GetDeniedReadFields(user).size > 0) {
|
|
806
|
+
throw new Error(`Invalid ${label}: entity '${entity.Name}' has fields you are not permitted to read — ad-hoc SQL cannot project them away; use RunView`);
|
|
761
807
|
}
|
|
762
808
|
}
|
|
809
|
+
/** Walks a parsed AST collecting the names of CTEs the statement itself defines. */
|
|
810
|
+
collectCTENames(node, names) {
|
|
811
|
+
if (!node || typeof node !== 'object')
|
|
812
|
+
return;
|
|
813
|
+
if (Array.isArray(node)) {
|
|
814
|
+
for (const n of node)
|
|
815
|
+
this.collectCTENames(n, names);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
const obj = node;
|
|
819
|
+
const withClause = obj.with;
|
|
820
|
+
if (Array.isArray(withClause)) {
|
|
821
|
+
for (const cte of withClause) {
|
|
822
|
+
const name = cte?.name;
|
|
823
|
+
if (typeof name === 'string')
|
|
824
|
+
names.add(name.toLowerCase());
|
|
825
|
+
else if (name && typeof name === 'object' && typeof name.value === 'string') {
|
|
826
|
+
names.add(name.value.toLowerCase());
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
for (const v of Object.values(obj))
|
|
831
|
+
this.collectCTENames(v, names);
|
|
832
|
+
}
|
|
763
833
|
/**
|
|
764
834
|
* Applies {@link assertClientClauseUsesEntityBaseViews} to every client-supplied clause
|
|
765
835
|
* a view request can carry that is actually a SQL fragment. GraphQL entry points
|
|
@@ -782,10 +852,10 @@ export class ResolverBase {
|
|
|
782
852
|
* admin-authored format may splice the term in unquoted; `createViewUserSearchSQL` re-applies
|
|
783
853
|
* `ValidateUserProvidedSQLClause` for exactly those entities.
|
|
784
854
|
*/
|
|
785
|
-
screenClientViewClauses(clauses, provider) {
|
|
786
|
-
this.assertClientClauseUsesEntityBaseViews(clauses.extraFilter, 'ExtraFilter', provider);
|
|
787
|
-
this.assertClientClauseUsesEntityBaseViews(clauses.orderBy, 'OrderBy', provider);
|
|
788
|
-
this.assertClientClauseUsesEntityBaseViews(clauses.overrideExcludeFilter, 'OverrideExcludeFilter', provider);
|
|
855
|
+
screenClientViewClauses(clauses, provider, user) {
|
|
856
|
+
this.assertClientClauseUsesEntityBaseViews(clauses.extraFilter, 'ExtraFilter', provider, user);
|
|
857
|
+
this.assertClientClauseUsesEntityBaseViews(clauses.orderBy, 'OrderBy', provider, user);
|
|
858
|
+
this.assertClientClauseUsesEntityBaseViews(clauses.overrideExcludeFilter, 'OverrideExcludeFilter', provider, user);
|
|
789
859
|
}
|
|
790
860
|
/** Same write-node walk as EDS `sqlReadOnlyScreen.astContainsWriteNode`. */
|
|
791
861
|
astContainsWriteNode(node) {
|
|
@@ -810,41 +880,77 @@ export class ResolverBase {
|
|
|
810
880
|
return new PostgreSQLDialect();
|
|
811
881
|
return new SQLServerDialect();
|
|
812
882
|
}
|
|
883
|
+
/** True for a reference with no schema — which `SQLParser.ExtractTableRefs` reports as `dbo`. */
|
|
884
|
+
isUnqualifiedSchema(schema) {
|
|
885
|
+
return !schema || schema.toLowerCase() === 'dbo';
|
|
886
|
+
}
|
|
813
887
|
stripSqlIdent(name) {
|
|
814
888
|
if (!name)
|
|
815
889
|
return '';
|
|
816
890
|
return name.replace(/^\[|\]$/g, '').replace(/^"|"$/g, '').replace(/^`|`$/g, '');
|
|
817
891
|
}
|
|
892
|
+
/**
|
|
893
|
+
* `bare` maps an unqualified view name to its entity, or to `null` when the name exists in
|
|
894
|
+
* more than one schema — the database, not this screen, would decide which one runs.
|
|
895
|
+
*/
|
|
818
896
|
entityBaseViewAllowList(provider) {
|
|
819
|
-
const qualified = new
|
|
820
|
-
const bare = new
|
|
897
|
+
const qualified = new Map();
|
|
898
|
+
const bare = new Map();
|
|
821
899
|
const entities = provider?.Entities ?? [];
|
|
822
900
|
for (const e of entities) {
|
|
823
901
|
const view = this.stripSqlIdent(e.BaseView);
|
|
824
902
|
if (!view)
|
|
825
903
|
continue;
|
|
826
904
|
const schema = this.stripSqlIdent(e.SchemaName);
|
|
827
|
-
|
|
905
|
+
const bareKey = view.toLowerCase();
|
|
906
|
+
bare.set(bareKey, bare.has(bareKey) ? null : e);
|
|
828
907
|
if (schema)
|
|
829
|
-
qualified.
|
|
908
|
+
qualified.set(`${schema}.${view}`.toLowerCase(), e);
|
|
830
909
|
}
|
|
831
910
|
return { qualified, bare };
|
|
832
911
|
}
|
|
912
|
+
/**
|
|
913
|
+
* SECURITY — resolves a table reference against the entity BaseView allow-list and, when an
|
|
914
|
+
* acting user is supplied, additionally requires that user to hold CanRead on the referenced
|
|
915
|
+
* entity. Base views do not embed RLS and entity permissions are otherwise checked only on
|
|
916
|
+
* the TOP entity of a request, so without this check a subquery (or ad-hoc query) could read
|
|
917
|
+
* entities the caller has no read grant on.
|
|
918
|
+
*
|
|
919
|
+
* A schema-qualified reference must match that exact schema — `secret.vwFoo` never resolves
|
|
920
|
+
* through another schema's `vwFoo`. An unqualified reference resolves only when the view name
|
|
921
|
+
* is unique across schemas; an ambiguous one must be qualified, because the database's
|
|
922
|
+
* default-schema resolution, not this screen, would pick which view actually runs.
|
|
923
|
+
*
|
|
924
|
+
* `SQLParser.ExtractTableRefs` reports an unqualified reference as schema `dbo`, so `dbo` is
|
|
925
|
+
* indistinguishable from "no schema" here and both take the unqualified path (an exact
|
|
926
|
+
* `dbo.<view>` entity wins first).
|
|
927
|
+
*/
|
|
928
|
+
assertTableRefReadable(allowed, schema, table, label, user) {
|
|
929
|
+
const qualified = allowed.qualified.get(`${schema}.${table}`.toLowerCase());
|
|
930
|
+
const entity = this.isUnqualifiedSchema(schema) ? qualified ?? allowed.bare.get(table.toLowerCase()) : qualified;
|
|
931
|
+
if (entity === null) {
|
|
932
|
+
throw new Error(`Invalid ${label}: '${table}' is a base view in more than one schema — qualify it with its schema`);
|
|
933
|
+
}
|
|
934
|
+
if (!entity) {
|
|
935
|
+
throw new Error(`Invalid ${label}: subquery must use an entity base view, not '${schema ? schema + '.' : ''}${table}'`);
|
|
936
|
+
}
|
|
937
|
+
if (user) {
|
|
938
|
+
const perms = entity.GetUserPermisions(user);
|
|
939
|
+
if (!perms.CanRead) {
|
|
940
|
+
throw new Error(`Invalid ${label}: you do not have read permission on entity '${entity.Name}' referenced by '${schema ? schema + '.' : ''}${table}'`);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return entity;
|
|
944
|
+
}
|
|
833
945
|
/**
|
|
834
946
|
* Optimized RunViewGenericInternal implementation with:
|
|
835
947
|
* - Field filtering at source (Fix #7)
|
|
836
948
|
* - Improved error handling (Fix #9)
|
|
837
949
|
*/
|
|
838
|
-
async RunViewGenericInternal(provider, viewInfo, extraFilter, orderBy, userSearchString, excludeUserViewRunID, overrideExcludeFilter, saveViewResults, fields, ignoreMaxRows, excludeDataFromAllPriorViewRuns, forceAuditLog, auditLogDescription, resultType, userPayload, maxRows, startRow, aggregates, afterKey, bypassCache, dataSource) {
|
|
950
|
+
async RunViewGenericInternal(provider, viewInfo, extraFilter, orderBy, userSearchString, excludeUserViewRunID, overrideExcludeFilter, saveViewResults, fields, ignoreMaxRows, excludeDataFromAllPriorViewRuns, forceAuditLog, auditLogDescription, resultType, userPayload, maxRows, startRow, aggregates, afterKey, bypassCache, dataSource, includeBinaryFields) {
|
|
839
951
|
try {
|
|
840
952
|
if (!viewInfo || !userPayload)
|
|
841
953
|
return null;
|
|
842
|
-
// SECURITY: GraphQL ExtraFilter may contain IN (SELECT … FROM <base view>).
|
|
843
|
-
// Screen at this boundary: parse, reject writes, allow only entity BaseViews.
|
|
844
|
-
this.screenClientViewClauses({ extraFilter, orderBy, userSearchString, overrideExcludeFilter }, provider);
|
|
845
|
-
// Check API key scope authorization for view operations
|
|
846
|
-
await this.CheckAPIKeyScopeAuthorization('view:run', viewInfo.Entity, userPayload);
|
|
847
|
-
const md = provider;
|
|
848
954
|
// Prefer the authenticated session's payload user — it is the authoritative per-request
|
|
849
955
|
// identity and (for magic-link sessions) carries the per-session resource scope / synthesized
|
|
850
956
|
// roles that drive RLS. The cached lookup is a fallback for paths where the payload user
|
|
@@ -853,6 +959,13 @@ export class ResolverBase {
|
|
|
853
959
|
?? UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
|
|
854
960
|
if (!user)
|
|
855
961
|
throw new Error(`User ${userPayload?.email} not found in metadata`);
|
|
962
|
+
// SECURITY: GraphQL ExtraFilter may contain IN (SELECT … FROM <base view>).
|
|
963
|
+
// Screen at this boundary: parse, reject writes, allow only entity BaseViews the
|
|
964
|
+
// acting user can read (subqueries do not inherit the top entity's permission check).
|
|
965
|
+
this.screenClientViewClauses({ extraFilter, orderBy, userSearchString, overrideExcludeFilter }, provider, user);
|
|
966
|
+
// Check API key scope authorization for view operations
|
|
967
|
+
await this.CheckAPIKeyScopeAuthorization('view:run', viewInfo.Entity, userPayload);
|
|
968
|
+
const md = provider;
|
|
856
969
|
const entityInfo = md.Entities.find((e) => e.Name === viewInfo.Entity);
|
|
857
970
|
if (!entityInfo)
|
|
858
971
|
throw new Error(`Entity ${viewInfo.Entity} not found in metadata`);
|
|
@@ -899,6 +1012,7 @@ export class ResolverBase {
|
|
|
899
1012
|
Aggregates: aggregates,
|
|
900
1013
|
BypassCache: bypassCache,
|
|
901
1014
|
DataSource: dataSource,
|
|
1015
|
+
IncludeBinaryFields: includeBinaryFields,
|
|
902
1016
|
}, user);
|
|
903
1017
|
// Log aggregate results for debugging
|
|
904
1018
|
if (aggregates?.length) {
|
|
@@ -988,7 +1102,7 @@ export class ResolverBase {
|
|
|
988
1102
|
orderBy: param.orderBy,
|
|
989
1103
|
userSearchString: param.userSearchString,
|
|
990
1104
|
overrideExcludeFilter: param.overrideExcludeFilter,
|
|
991
|
-
}, md);
|
|
1105
|
+
}, md, contextUser ?? undefined);
|
|
992
1106
|
if (param.viewInfo) {
|
|
993
1107
|
// Validate entity only once per entity type
|
|
994
1108
|
const entityName = param.viewInfo.Entity;
|
|
@@ -1028,6 +1142,7 @@ export class ResolverBase {
|
|
|
1028
1142
|
Aggregates: param.aggregates,
|
|
1029
1143
|
BypassCache: param.bypassCache,
|
|
1030
1144
|
DataSource: param.dataSource,
|
|
1145
|
+
IncludeBinaryFields: param.includeBinaryFields,
|
|
1031
1146
|
});
|
|
1032
1147
|
}
|
|
1033
1148
|
// Fix #4: Run views in a single batch through RunViews
|
|
@@ -1383,7 +1498,7 @@ export class ResolverBase {
|
|
|
1383
1498
|
}
|
|
1384
1499
|
else {
|
|
1385
1500
|
// no OldValues, so we can just set the new values from input
|
|
1386
|
-
entityObject.SetMany(
|
|
1501
|
+
entityObject.SetMany(clientNewValues);
|
|
1387
1502
|
}
|
|
1388
1503
|
}
|
|
1389
1504
|
else {
|
|
@@ -1397,8 +1512,11 @@ export class ResolverBase {
|
|
|
1397
1512
|
else {
|
|
1398
1513
|
// we get here if we are NOT tracking changes and we DO have OldValues, so we can load from them
|
|
1399
1514
|
const oldValues = {};
|
|
1400
|
-
// for each item in the oldValues array, add it to the oldValues object
|
|
1401
|
-
input.OldValues___?.forEach((item) =>
|
|
1515
|
+
// for each item in the oldValues array, add it to the oldValues object, typed like the field
|
|
1516
|
+
input.OldValues___?.forEach((item) => {
|
|
1517
|
+
const field = entityObject.EntityInfo.Fields.find((f) => f.CodeName === item.Key);
|
|
1518
|
+
oldValues[item.Key] = field ? this.ClientOldValueToFieldValue(field, item.Value) : item.Value;
|
|
1519
|
+
});
|
|
1402
1520
|
// 1) load the old values, this will be the initial state of the object
|
|
1403
1521
|
await entityObject.LoadFromData(oldValues);
|
|
1404
1522
|
// 2) set the new values from the input, not including the OldValues property
|
|
@@ -1460,9 +1578,15 @@ export class ResolverBase {
|
|
|
1460
1578
|
*
|
|
1461
1579
|
* Ordered so the boolean flag is evaluated last: the extra load lands only on entities that have
|
|
1462
1580
|
* the feature switched on, which is almost none of them.
|
|
1581
|
+
*
|
|
1582
|
+
* `MJ: Record Changes` always loads from the database: its server class allows an update only when
|
|
1583
|
+
* Comments is the one dirty field, and "dirty" compared against client-supplied OldValues lets a
|
|
1584
|
+
* caller pin forged audit columns as both old and new values. It doesn't track its own changes, so
|
|
1585
|
+
* nothing else forces the load.
|
|
1463
1586
|
*/
|
|
1464
1587
|
MustLoadTruthFromDatabase(entityInfo, input, hasDeniedReadFields, hasNarrowedAuditPayload) {
|
|
1465
1588
|
return (entityInfo.TrackRecordChanges ||
|
|
1589
|
+
entityInfo.Name.trim().toLowerCase() === 'mj: record changes' ||
|
|
1466
1590
|
!input.OldValues___ ||
|
|
1467
1591
|
hasDeniedReadFields ||
|
|
1468
1592
|
hasNarrowedAuditPayload ||
|
|
@@ -1575,6 +1699,42 @@ export class ResolverBase {
|
|
|
1575
1699
|
// fall through to the OldValues branch and write whatever it did send.
|
|
1576
1700
|
return true;
|
|
1577
1701
|
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Converts one client-sent old value (always a string on the wire; dates as epoch milliseconds,
|
|
1704
|
+
* the GraphQL Timestamp form) to the field's TypeScript type, so it compares equal to the stored
|
|
1705
|
+
* value. Both the OldValues comparison and the load-from-OldValues path use it: without it a date
|
|
1706
|
+
* old value becomes an Invalid Date and an unchanged date field reads as edited.
|
|
1707
|
+
*/
|
|
1708
|
+
ClientOldValueToFieldValue(field, raw) {
|
|
1709
|
+
let val = raw;
|
|
1710
|
+
if ((val === null || val === undefined) && field && field.DefaultValue !== null && field.DefaultValue !== undefined && !field.AllowsNull)
|
|
1711
|
+
val = field.DefaultValue; // set default value as the field was never set and it does NOT allow nulls
|
|
1712
|
+
if (val === undefined)
|
|
1713
|
+
val = null;
|
|
1714
|
+
// A null old value stays null, except a boolean, which the comparison has always read as false.
|
|
1715
|
+
if (field?.TSType === EntityFieldTSType.Boolean && val === null)
|
|
1716
|
+
return false;
|
|
1717
|
+
if (val === null)
|
|
1718
|
+
return null;
|
|
1719
|
+
if (!field) {
|
|
1720
|
+
// No field metadata to convert by: pass scalars through, stringify anything else.
|
|
1721
|
+
return typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean' || val instanceof Date ? val : String(val);
|
|
1722
|
+
}
|
|
1723
|
+
const text = String(val);
|
|
1724
|
+
switch (field.TSType) {
|
|
1725
|
+
case EntityFieldTSType.Number: {
|
|
1726
|
+
const integer = ['int', 'smallint', 'bigint', 'tinyint'].includes(field.Type.toLowerCase());
|
|
1727
|
+
return integer ? parseInt(text) : parseFloat(text);
|
|
1728
|
+
}
|
|
1729
|
+
case EntityFieldTSType.Boolean:
|
|
1730
|
+
return !(text === 'false' || text === '0' || parseInt(text) === 0);
|
|
1731
|
+
case EntityFieldTSType.Date:
|
|
1732
|
+
// Epoch milliseconds (the GraphQL Timestamp form) arrive as a numeric string.
|
|
1733
|
+
return new Date(text.trim() !== '' && !isNaN(Number(text)) ? parseInt(text) : text);
|
|
1734
|
+
default:
|
|
1735
|
+
return text; // already a string
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1578
1738
|
/**
|
|
1579
1739
|
* This routine compares the OldValues property in the input object to the values in the DB that we just loaded. If there are differences, we need to check to see if the client
|
|
1580
1740
|
* is trying to update any of those fields (e.g. overlap). If there is overlap, we throw an error. If there is no overlap, we can proceed with the update even if the DB Values
|
|
@@ -1589,50 +1749,7 @@ export class ResolverBase {
|
|
|
1589
1749
|
input.OldValues___.forEach((item) => {
|
|
1590
1750
|
// we need to do a quick transform on the values to make sure they match the TS Type for the given field because item.Value will always be a string
|
|
1591
1751
|
const field = entityObject.EntityInfo.Fields.find((f) => f.CodeName === item.Key);
|
|
1592
|
-
|
|
1593
|
-
if ((val === null || val === undefined) && field.DefaultValue !== null && field.DefaultValue !== undefined && !field.AllowsNull)
|
|
1594
|
-
val = field.DefaultValue; // set default value as the field was never set and it does NOT allow nulls
|
|
1595
|
-
if (field) {
|
|
1596
|
-
switch (field.TSType) {
|
|
1597
|
-
case EntityFieldTSType.Number:
|
|
1598
|
-
if (val == null && val == undefined) {
|
|
1599
|
-
val = null;
|
|
1600
|
-
}
|
|
1601
|
-
else {
|
|
1602
|
-
let typeLowered = field.Type.toLowerCase();
|
|
1603
|
-
switch (typeLowered) {
|
|
1604
|
-
case 'int':
|
|
1605
|
-
case 'smallint':
|
|
1606
|
-
case 'bigint':
|
|
1607
|
-
case 'tinyint':
|
|
1608
|
-
val = parseInt(val);
|
|
1609
|
-
break;
|
|
1610
|
-
case 'money':
|
|
1611
|
-
case 'smallmoney':
|
|
1612
|
-
case 'decimal':
|
|
1613
|
-
case 'numeric':
|
|
1614
|
-
case 'float':
|
|
1615
|
-
val = parseFloat(val);
|
|
1616
|
-
break;
|
|
1617
|
-
default:
|
|
1618
|
-
val = parseFloat(val);
|
|
1619
|
-
break;
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
break;
|
|
1623
|
-
case EntityFieldTSType.Boolean:
|
|
1624
|
-
val = val === null || val === undefined || val === 'false' || val === '0' || parseInt(val) === 0 ? false : true;
|
|
1625
|
-
break;
|
|
1626
|
-
case EntityFieldTSType.Date:
|
|
1627
|
-
// first, if val is a string and it is actually a number (milliseconds since epoch), convert it to a number.
|
|
1628
|
-
if (val !== null && val !== undefined && val.toString().trim() !== '' && !isNaN(val))
|
|
1629
|
-
val = parseInt(val);
|
|
1630
|
-
val = val !== null && val !== undefined ? new Date(val) : null;
|
|
1631
|
-
break;
|
|
1632
|
-
default:
|
|
1633
|
-
break; // already a string
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1752
|
+
const val = this.ClientOldValueToFieldValue(field, item.Value);
|
|
1636
1753
|
clientOldValues[item.Key] = val;
|
|
1637
1754
|
});
|
|
1638
1755
|
// clientOldValues now has all of the oldValues the CLIENT passed us. Now we need to build the same kind of object
|