@memberjunction/graphql-dataprovider 1.6.0 → 1.7.0

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.
@@ -0,0 +1,1223 @@
1
+ /**************************************************************************************************************
2
+ * The graphQLDataProvider provides a data provider for the entities framework that uses GraphQL to communicate
3
+ * with the server.
4
+ * In practice - this FILE will NOT exist in the entities library, we need to move to its own separate project
5
+ * so it is only included by the consumer of the entities library if they want to use it.
6
+ **************************************************************************************************************/
7
+ import { ProviderConfigDataBase, EntityFieldTSType, ProviderBase, ProviderType, UserInfo, UserRoleInfo, LogError, TransactionItem, BaseEntityResult } from "@memberjunction/core";
8
+ import { ViewInfo } from '@memberjunction/core-entities';
9
+ import { gql, GraphQLClient } from 'graphql-request';
10
+ import { GraphQLTransactionGroup } from "./graphQLTransactionGroup";
11
+ import { openDB } from '@tempfix/idb';
12
+ import { Observable } from 'rxjs';
13
+ import { createClient } from 'graphql-ws';
14
+ export class GraphQLProviderConfigData extends ProviderConfigDataBase {
15
+ /**
16
+ * Token is the JWT token that is used to authenticate the user with the server
17
+ */
18
+ get Token() { return this.Data.Token; }
19
+ set Token(token) { this.Data.Token = token; }
20
+ /**
21
+ * URL is the URL to the GraphQL endpoint
22
+ */
23
+ get URL() { return this.Data.URL; }
24
+ /**
25
+ * WSURL is the URL to the GraphQL websocket endpoint. This is used for subscriptions, if you are not using subscriptions, you can pass in a blank string for this
26
+ */
27
+ get WSURL() { return this.Data.WSURL; }
28
+ /**
29
+ * RefreshTokenFunction is a function that can be called by the GraphQLDataProvider whenever it receives an exception that the JWT it has already is expired
30
+ */
31
+ get RefreshTokenFunction() { return this.Data.RefreshFunction; }
32
+ /**
33
+ *
34
+ * @param token Token is the JWT token that is used to authenticate the user with the server
35
+ * @param url the URL to the GraphQL endpoint
36
+ * @param wsurl the URL to the GraphQL websocket endpoint. This is used for subscriptions, if you are not using subscriptions, you can pass in a blank string for this
37
+ * @param refreshTokenFunction is a function that can be called by the GraphQLDataProvider whenever it receives an exception that the JWT it has already is expired
38
+ * @param MJCoreSchemaName the name of the MJ Core schema, if it is not the default name of __mj
39
+ * @param includeSchemas optional, an array of schema names to include in the metadata. If not passed, all schemas are included
40
+ * @param excludeSchemas optional, an array of schema names to exclude from the metadata. If not passed, no schemas are excluded
41
+ */
42
+ constructor(token, url, wsurl, refreshTokenFunction, MJCoreSchemaName, includeSchemas, excludeSchemas) {
43
+ super({
44
+ Token: token,
45
+ URL: url,
46
+ WSURL: wsurl,
47
+ RefreshTokenFunction: refreshTokenFunction,
48
+ }, MJCoreSchemaName, includeSchemas, excludeSchemas);
49
+ }
50
+ }
51
+ // The GraphQLDataProvider implements both the IEntityDataProvider and IMetadataProvider interfaces.
52
+ export class GraphQLDataProvider extends ProviderBase {
53
+ constructor() {
54
+ super(...arguments);
55
+ // private _allLatestMetadataUpdatesQuery = gql`query mdUpdates {
56
+ // AllLatestMetadataUpdates {
57
+ // ID
58
+ // Type
59
+ // UpdatedAt
60
+ // }
61
+ // }
62
+ // `
63
+ // private _innerAllEntitiesQueryString = `AllEntities {
64
+ // ${this.entityInfoString()}
65
+ // }`
66
+ // private _innerAllEntityFieldsQueryString = `AllEntityFields {
67
+ // ${this.entityFieldInfoString()}
68
+ // }`
69
+ // private _innerAllEntityRelationshipsQueryString = `AllEntityRelationships {
70
+ // ${this.entityRelationshipInfoString()}
71
+ // }`
72
+ // private _innerAllEntityPermissionsQueryString = `AllEntityPermissions {
73
+ // ${this.entityPermissionInfoString()}
74
+ // }`
75
+ // private _innerAllApplicationsQueryString = `AllApplications {
76
+ // ${this.applicationInfoString()}
77
+ // ApplicationEntities {
78
+ // ${this.applicationEntityInfoString()}
79
+ // }
80
+ // }
81
+ // `
82
+ this._innerCurrentUserQueryString = `CurrentUser {
83
+ ${this.userInfoString()}
84
+ UserRolesArray {
85
+ ${this.userRoleInfoString()}
86
+ }
87
+ }
88
+ `;
89
+ // private _allApplicationsQuery = gql`
90
+ // ${this._innerAllApplicationsQueryString}
91
+ // `
92
+ // private _innerAllRolesQueryString = `AllRoles {
93
+ // ${this.roleInfoString()}
94
+ // }
95
+ // `
96
+ // private _innerAllRowLevelSecurityFiltersQueryString = `AllRowLevelSecurityFilters {
97
+ // ${this.rowLevelSecurityFilterInfoString()}
98
+ // }
99
+ // `
100
+ // private _innerAllAuditLogTypesQueryString = `AllAuditLogTypes {
101
+ // ${this.auditLogTypeInfoString()}
102
+ // }
103
+ // `
104
+ // private _innerAllAuthorizationsQueryString = `AllAuthorizations {
105
+ // ${this.authorizationInfoString()}
106
+ // }
107
+ // `
108
+ // private _innerAllQueriesQueryString = `AllQueries {
109
+ // ${this.queryInfoString()}
110
+ // }
111
+ // `
112
+ // private _innerAllQueryCategoriesQueryString = `AllQueryCategories {
113
+ // ${this.queryCategoryInfoString()}
114
+ // }
115
+ // `
116
+ // private _allMetaDataQuery = gql`query AllApplicationsAndEntities {
117
+ // ${this._innerAllApplicationsQueryString}
118
+ // ${this._innerAllEntitiesQueryString}
119
+ // ${this._innerAllEntityFieldsQueryString}
120
+ // ${this._innerAllEntityPermissionsQueryString}
121
+ // ${this._innerAllEntityRelationshipsQueryString}
122
+ // ${this._innerCurrentUserQueryString}
123
+ // ${this._innerAllRolesQueryString}
124
+ // ${this._innerAllRowLevelSecurityFiltersQueryString}
125
+ // ${this._innerAllAuditLogTypesQueryString}
126
+ // ${this._innerAllAuthorizationsQueryString}
127
+ // ${this._innerAllQueriesQueryString}
128
+ // ${this._innerAllQueryCategoriesQueryString}
129
+ // }`
130
+ this._currentUserQuery = gql `query CurrentUserAndRoles {
131
+ ${this._innerCurrentUserQueryString}
132
+ }`;
133
+ this._wsClient = null;
134
+ this._pushStatusRequests = [];
135
+ }
136
+ get ConfigData() { return GraphQLDataProvider._configData; }
137
+ GenerateUUID() {
138
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
139
+ var r = (Math.random() * 16) | 0, v = c === 'x' ? r : (r & 0x3) | 0x8;
140
+ return v.toString(16);
141
+ });
142
+ }
143
+ async Config(configData) {
144
+ try {
145
+ // FIRST, set up the GraphQL client
146
+ if (GraphQLDataProvider._sessionId === undefined)
147
+ GraphQLDataProvider._sessionId = this.GenerateUUID();
148
+ GraphQLDataProvider._configData = configData;
149
+ // now create the new client, if it isn't alreayd created
150
+ if (!GraphQLDataProvider._client)
151
+ GraphQLDataProvider._client = GraphQLDataProvider.CreateNewGraphQLClient(configData.URL, configData.Token, GraphQLDataProvider._sessionId);
152
+ return super.Config(configData); // now parent class can do it's config
153
+ }
154
+ catch (e) {
155
+ LogError(e);
156
+ throw (e);
157
+ }
158
+ }
159
+ get sessionId() {
160
+ return GraphQLDataProvider._sessionId;
161
+ }
162
+ get AllowRefresh() {
163
+ return true; // this provider doesn't have any issues with allowing refreshes at any time
164
+ }
165
+ async GetCurrentUser() {
166
+ const d = await GraphQLDataProvider.ExecuteGQL(this._currentUserQuery, null);
167
+ if (d) {
168
+ return new UserInfo(this, { ...d.CurrentUser, UserRoles: d.CurrentUser.UserRolesArray }); // need to pass in the UserRoles as a separate property that is what is expected here
169
+ }
170
+ }
171
+ /**************************************************************************/
172
+ // START ---- IRunReportProvider
173
+ /**************************************************************************/
174
+ async RunReport(params, contextUser) {
175
+ const query = gql `
176
+ query GetReportDataQuery ($ReportID: Int!) {
177
+ GetReportData(ReportID: $ReportID) {
178
+ Success
179
+ Results
180
+ RowCount
181
+ ExecutionTime
182
+ ErrorMessage
183
+ }
184
+ }`;
185
+ const result = await GraphQLDataProvider.ExecuteGQL(query, { ReportID: params.ReportID });
186
+ if (result && result.GetReportData)
187
+ return {
188
+ ReportID: params.ReportID,
189
+ Success: result.GetReportData.Success,
190
+ Results: JSON.parse(result.GetReportData.Results),
191
+ RowCount: result.GetReportData.RowCount,
192
+ ExecutionTime: result.GetReportData.ExecutionTime,
193
+ ErrorMessage: result.GetReportData.ErrorMessage,
194
+ };
195
+ }
196
+ /**************************************************************************/
197
+ // END ---- IRunReportProvider
198
+ /**************************************************************************/
199
+ /**************************************************************************/
200
+ // START ---- IRunQueryProvider
201
+ /**************************************************************************/
202
+ async RunQuery(params, contextUser) {
203
+ const query = gql `
204
+ query GetQueryDataQuery ($QueryID: Int!) {
205
+ GetQueryData(QueryID: $QueryID) {
206
+ Success
207
+ Results
208
+ RowCount
209
+ ExecutionTime
210
+ ErrorMessage
211
+ }
212
+ }`;
213
+ const queryId = typeof params.QueryID === 'string' ? parseInt(params.QueryID) : params.QueryID;
214
+ const result = await GraphQLDataProvider.ExecuteGQL(query, { QueryID: queryId });
215
+ if (result && result.GetQueryData)
216
+ return {
217
+ QueryID: params.QueryID,
218
+ Success: result.GetQueryData.Success,
219
+ Results: JSON.parse(result.GetQueryData.Results),
220
+ RowCount: result.GetQueryData.RowCount,
221
+ ExecutionTime: result.GetQueryData.ExecutionTime,
222
+ ErrorMessage: result.GetQueryData.ErrorMessage,
223
+ };
224
+ }
225
+ /**************************************************************************/
226
+ // END ---- IRunReportProvider
227
+ /**************************************************************************/
228
+ /**************************************************************************/
229
+ // START ---- IRunViewProvider
230
+ /**************************************************************************/
231
+ async RunView(params, contextUser) {
232
+ try {
233
+ let qName = '';
234
+ let paramType = '';
235
+ if (params) {
236
+ const innerParams = {};
237
+ let entity, viewEntity;
238
+ if (params.ViewEntity) {
239
+ viewEntity = params.ViewEntity;
240
+ entity = viewEntity.Entity;
241
+ }
242
+ else {
243
+ const { entityName, v } = await this.getEntityNameAndUserView(params, contextUser);
244
+ viewEntity = v;
245
+ entity = entityName;
246
+ }
247
+ // get entity metadata
248
+ const e = this.Entities.find(e => e.Name === entity);
249
+ if (!e)
250
+ throw new Error(`Entity ${entity} not found in metadata`);
251
+ let dynamicView = false;
252
+ if (params.ViewID) {
253
+ qName = `Run${e.ClassName}ViewByID`;
254
+ paramType = 'RunViewByIDInput';
255
+ innerParams.ViewID = params.ViewID;
256
+ }
257
+ else if (params.ViewName) {
258
+ qName = `Run${e.ClassName}ViewByName`;
259
+ paramType = 'RunViewByNameInput';
260
+ innerParams.ViewName = params.ViewName;
261
+ }
262
+ else {
263
+ dynamicView = true;
264
+ qName = `Run${e.ClassName}DynamicView`;
265
+ paramType = 'RunDynamicViewInput';
266
+ innerParams.EntityName = params.EntityName;
267
+ }
268
+ innerParams.ExtraFilter = params.ExtraFilter ? params.ExtraFilter : '';
269
+ innerParams.OrderBy = params.OrderBy ? params.OrderBy : '';
270
+ innerParams.UserSearchString = params.UserSearchString ? params.UserSearchString : '';
271
+ innerParams.Fields = params.Fields; // pass it straight through, either null or array of strings
272
+ innerParams.IgnoreMaxRows = params.IgnoreMaxRows ? params.IgnoreMaxRows : false;
273
+ innerParams.MaxRows = params.MaxRows ? params.MaxRows : 0;
274
+ innerParams.ForceAuditLog = params.ForceAuditLog ? params.ForceAuditLog : false;
275
+ innerParams.ResultType = params.ResultType ? params.ResultType : 'simple';
276
+ if (params.AuditLogDescription && params.AuditLogDescription.length > 0)
277
+ innerParams.AuditLogDescription = params.AuditLogDescription;
278
+ if (!dynamicView) {
279
+ innerParams.ExcludeUserViewRunID = params.ExcludeUserViewRunID ? params.ExcludeUserViewRunID : -1;
280
+ innerParams.ExcludeDataFromAllPriorViewRuns = params.ExcludeDataFromAllPriorViewRuns ? params.ExcludeDataFromAllPriorViewRuns : false;
281
+ innerParams.OverrideExcludeFilter = params.OverrideExcludeFilter ? params.OverrideExcludeFilter : '';
282
+ innerParams.SaveViewResults = params.SaveViewResults ? params.SaveViewResults : false;
283
+ }
284
+ const fieldList = this.getViewRunTimeFieldList(e, viewEntity, params, dynamicView);
285
+ const query = gql `
286
+ query RunViewQuery ($input: ${paramType}!) {
287
+ ${qName}(input: $input) {
288
+ Results {
289
+ ${fieldList.join("\n ")}
290
+ }
291
+ UserViewRunID
292
+ RowCount
293
+ TotalRowCount
294
+ ExecutionTime
295
+ Success
296
+ ErrorMessage
297
+ }
298
+ }`;
299
+ const viewData = await GraphQLDataProvider.ExecuteGQL(query, { input: innerParams });
300
+ if (viewData && viewData[qName]) {
301
+ // now, if we have any results in viewData that are for the CodeName, we need to convert them to the Name
302
+ // so that the caller gets back what they expect
303
+ const results = viewData[qName].Results;
304
+ if (results && results.length > 0) {
305
+ const fields = e.Fields.filter(f => f.CodeName !== f.Name);
306
+ if (fields.length > 0) {
307
+ results.forEach(r => {
308
+ fields.forEach(f => {
309
+ if (r[f.CodeName] !== undefined) {
310
+ r[f.Name] = r[f.CodeName];
311
+ // delete r[f.CodeName]; // Leave the CodeName in the results, it is useful to have both
312
+ }
313
+ });
314
+ });
315
+ }
316
+ }
317
+ return viewData[qName];
318
+ }
319
+ }
320
+ else
321
+ throw ("No parameters passed to RunView");
322
+ return null;
323
+ }
324
+ catch (e) {
325
+ LogError(e);
326
+ throw (e);
327
+ }
328
+ }
329
+ async getEntityNameAndUserView(params, contextUser) {
330
+ let entityName;
331
+ let v;
332
+ if (!params.EntityName) {
333
+ if (params.ViewID) {
334
+ v = await ViewInfo.GetViewEntity(params.ViewID, contextUser);
335
+ entityName = v.Entity;
336
+ }
337
+ else if (params.ViewName) {
338
+ v = await ViewInfo.GetViewEntityByName(params.ViewName, contextUser);
339
+ entityName = v.Entity;
340
+ }
341
+ else
342
+ throw new Error(`No EntityName, ViewID or ViewName passed to RunView`);
343
+ }
344
+ else
345
+ entityName = params.EntityName;
346
+ return { entityName, v };
347
+ }
348
+ getViewRunTimeFieldList(e, v, params, dynamicView) {
349
+ const fieldList = [];
350
+ if (params.Fields) {
351
+ for (const kv of e.PrimaryKeys) {
352
+ if (params.Fields.find(f => f.trim().toLowerCase() === kv.Name.toLowerCase()) === undefined)
353
+ fieldList.push(kv.Name); // always include the primary key fields in view run time field list
354
+ }
355
+ // now add any other fields that were passed in
356
+ params.Fields.forEach(f => fieldList.push(f));
357
+ }
358
+ else {
359
+ // no fields were passed in. So, let's check to see if we are running an dynamic view.
360
+ // If so, we need to include all fields since the caller didn't specify the fields they want
361
+ // otherwise, we include the fields that are part of the view definition.
362
+ if (dynamicView) {
363
+ // include all fields since no fields were passed in
364
+ e.Fields.forEach(f => {
365
+ if (!f.IsBinaryFieldType)
366
+ fieldList.push(f.CodeName);
367
+ });
368
+ }
369
+ else {
370
+ // NOTE: in the below, c.EntityField SHOULD always exist, however there is a possibility that at some point a VIEW was created that used fields
371
+ // and those fields are NO LONGER part of an entity, in that situation we should just remove them, rather than letting the whole view blow up which
372
+ // would happen if we dno't check for c.EntityField? in the below
373
+ // first make sure we have the primary key field in the view column list, always should, but make sure
374
+ for (const kv of e.PrimaryKeys) {
375
+ if (fieldList.find(f => f.trim().toLowerCase() === kv.Name.toLowerCase()) === undefined)
376
+ fieldList.push(kv.Name); // always include the primary key fields in view run time field list
377
+ }
378
+ // Now: include the fields that are part of the view definition
379
+ v.Columns.forEach(c => {
380
+ if (c.hidden === false && !fieldList.find(item => item.trim().toLowerCase() === c.EntityField?.Name.trim().toLowerCase())) // don't include hidden fields and don't include the pkey field again
381
+ fieldList.push(c.EntityField.CodeName);
382
+ });
383
+ }
384
+ }
385
+ return fieldList;
386
+ }
387
+ /**************************************************************************/
388
+ // END ---- IRunViewProvider
389
+ /**************************************************************************/
390
+ /**************************************************************************/
391
+ // START ---- IEntityDataProvider
392
+ /**************************************************************************/
393
+ get ProviderType() {
394
+ return ProviderType.Network;
395
+ }
396
+ async GetRecordChanges(entityName, primaryKey) {
397
+ try {
398
+ const p = {
399
+ EntityName: 'Record Changes',
400
+ ExtraFilter: `RecordID = '${primaryKey.Values()}' AND Entity = '${entityName}'`,
401
+ //OrderBy: 'ChangedAt DESC',
402
+ };
403
+ const result = await this.RunView(p);
404
+ if (result) {
405
+ // sort the results client side because, for now, the RunViewParams doesn't support OrderBy dynamically like we tried. Later change this to do via the SQL query
406
+ return result.Results.sort((a, b) => {
407
+ return (a.ChangedAt > b.ChangedAt) ? -1 : 1; // sort descending on the date.... GraphQL passes back the date as time since base date
408
+ });
409
+ }
410
+ else
411
+ return null;
412
+ }
413
+ catch (e) {
414
+ LogError(e);
415
+ throw (e);
416
+ }
417
+ }
418
+ /**
419
+ * Returns a list of dependencies - records that are linked to the specified Entity/KeyValuePairs combination. A dependency is as defined by the relationships in the database. The MemberJunction metadata that is used
420
+ * for this simply reflects the foreign key relationships that exist in the database. The CodeGen tool is what detects all of the relationships and generates the metadata that is used by MemberJunction. The metadata in question
421
+ * is within the EntityField table and specifically the RelatedEntity and RelatedEntityField columns. In turn, this method uses that metadata and queries the database to determine the dependencies. To get the list of entity dependencies
422
+ * you can use the utility method GetEntityDependencies(), which doesn't check for dependencies on a specific record, but rather gets the metadata in one shot that can be used for dependency checking.
423
+ * @param entityName the name of the entity to check
424
+ * @param KeyValuePairs the KeyValuePairs of the record to check
425
+ */
426
+ async GetRecordDependencies(entityName, primaryKey) {
427
+ try {
428
+ // execute the gql query to get the dependencies
429
+ const query = gql `query GetRecordDependenciesQuery ($entityName: String!, $CompositeKey: CompositeKeyInputType!) {
430
+ GetRecordDependencies(entityName: $entityName, CompositeKey: $CompositeKey) {
431
+ EntityName
432
+ RelatedEntityName
433
+ FieldName
434
+ CompositeKey {
435
+ KeyValuePairs {
436
+ FieldName
437
+ Value
438
+ }
439
+ }
440
+ }
441
+ }`;
442
+ // now we have our query built, execute it
443
+ const vars = {
444
+ entityName: entityName,
445
+ CompositeKey: { KeyValuePairs: this.ensureKeyValuePairValueIsString(primaryKey.KeyValuePairs) }
446
+ };
447
+ const data = await GraphQLDataProvider.ExecuteGQL(query, vars);
448
+ return data?.GetRecordDependencies; // shape of the result should exactly match the RecordDependency type
449
+ }
450
+ catch (e) {
451
+ LogError(e);
452
+ throw (e);
453
+ }
454
+ }
455
+ ensureKeyValuePairValueIsString(kvps) {
456
+ return kvps.map(kv => {
457
+ return { FieldName: kv.FieldName, Value: kv.Value.toString() };
458
+ });
459
+ }
460
+ async GetRecordDuplicates(params, contextUser) {
461
+ if (!params) {
462
+ return null;
463
+ }
464
+ const query = gql `query GetRecordDuplicatesQuery ($params: PotentialDuplicateRequestType!) {
465
+ GetRecordDuplicates(params: $params) {
466
+ Status
467
+ ErrorMessage
468
+ PotentialDuplicateResult {
469
+ EntityID
470
+ DuplicateRunDetailMatchRecordIDs
471
+ RecordPrimaryKeys {
472
+ KeyValuePairs {
473
+ FieldName
474
+ Value
475
+ }
476
+ }
477
+ Duplicates {
478
+ ProbabilityScore
479
+ KeyValuePairs {
480
+ FieldName
481
+ Value
482
+ }
483
+ }
484
+ }
485
+ }
486
+ }`;
487
+ let request = {
488
+ EntityID: params.EntityID,
489
+ EntityDocumentID: params.EntityDocumentID,
490
+ ListID: params.ListID,
491
+ ProbabilityScore: params.ProbabilityScore,
492
+ Options: params.Options,
493
+ RecordIDs: params.RecordIDs.map(recordID => {
494
+ return recordID.Copy();
495
+ })
496
+ };
497
+ const data = await GraphQLDataProvider.ExecuteGQL(query, { params: request });
498
+ if (data && data.GetRecordDuplicates) {
499
+ return data.GetRecordDuplicates;
500
+ }
501
+ }
502
+ async MergeRecords(request) {
503
+ try {
504
+ // execute the gql query to get the dependencies
505
+ const mutation = gql `mutation MergeRecordsMutation ($request: RecordMergeRequest!) {
506
+ MergeRecords(request: $request) {
507
+ Success
508
+ OverallStatus
509
+ RecordMergeLogID
510
+ RecordStatus {
511
+ CompositeKey {
512
+ KeyValuePairs {
513
+ FieldName
514
+ Value
515
+ }
516
+ }
517
+ Success
518
+ RecordMergeDeletionLogID
519
+ Message
520
+ }
521
+ }
522
+ }`;
523
+ // create a new request that is compatible with the server's expectations where field maps and also the primary key values are all strings
524
+ const newRequest = {
525
+ EntityName: request.EntityName,
526
+ SurvivingRecordCompositeKey: { KeyValuePairs: this.ensureKeyValuePairValueIsString(request.SurvivingRecordCompositeKey.KeyValuePairs) },
527
+ FieldMap: request.FieldMap?.map(fm => {
528
+ return {
529
+ FieldName: fm.FieldName,
530
+ Value: fm.Value.toString() // turn the value into a string, since that is what the server expects
531
+ };
532
+ }),
533
+ RecordsToMerge: request.RecordsToMerge.map(r => {
534
+ return r.Copy();
535
+ })
536
+ };
537
+ // now we have our query built, execute it
538
+ const data = await GraphQLDataProvider.ExecuteGQL(mutation, { request: newRequest });
539
+ return data?.MergeRecords; // shape of the result should exactly match the RecordDependency type
540
+ }
541
+ catch (e) {
542
+ LogError(e);
543
+ return {
544
+ Success: false,
545
+ OverallStatus: e && e.message ? e.message : e,
546
+ RecordStatus: [],
547
+ RecordMergeLogID: -1,
548
+ Request: request,
549
+ };
550
+ throw (e);
551
+ }
552
+ }
553
+ async Save(entity, user, options) {
554
+ const result = new BaseEntityResult();
555
+ try {
556
+ const vars = { input: {} };
557
+ const type = entity.IsSaved ? "Update" : "Create";
558
+ result.StartedAt = new Date();
559
+ result.Type = entity.IsSaved ? 'update' : 'create';
560
+ result.OriginalValues = entity.Fields.map(f => { return { FieldName: f.CodeName, Value: f.Value }; });
561
+ entity.ResultHistory.push(result); // push the new result as we have started a process
562
+ // Create the query for the mutation first, we will provide the specific
563
+ // input values later in the loop below. Here we are just setting up the mutation
564
+ // and the fields that will be returned since the mutation returns back the latest
565
+ // values for the entity and we need to have those values to update the entity after the
566
+ // save
567
+ const mutationName = `${type}${entity.EntityInfo.ClassName}`;
568
+ // only pass along writable fields, AND the PKEY value if this is an update
569
+ const filteredFields = entity.Fields.filter(f => f.SQLType.trim().toLowerCase() !== 'uniqueidentifier' && (f.ReadOnly === false || (f.IsPrimaryKey && entity.IsSaved)));
570
+ const inner = ` ${mutationName}(input: $input) {
571
+ ${entity.Fields.map(f => f.CodeName).join("\n ")}
572
+ }`;
573
+ const outer = gql `mutation ${type}${entity.EntityInfo.ClassName} ($input: ${mutationName}Input!) {
574
+ ${inner}
575
+ }
576
+ `;
577
+ for (let i = 0; i < filteredFields.length; i++) {
578
+ const f = filteredFields[i];
579
+ let val = f.Value;
580
+ if (val && f.EntityFieldInfo.TSType === EntityFieldTSType.Date)
581
+ val = val.getTime();
582
+ if (val && f.EntityFieldInfo.TSType === EntityFieldTSType.Boolean && typeof val !== 'boolean')
583
+ val = parseInt(val) === 0 ? false : true; // convert to boolean
584
+ if (val == null && f.EntityFieldInfo.AllowsNull == false) {
585
+ if (f.EntityFieldInfo.DefaultValue != null) {
586
+ // no value, but there is a default value, so use that, since field does NOT allow NULL
587
+ val = f.EntityFieldInfo.DefaultValue;
588
+ }
589
+ else {
590
+ // no default value, null value and field doesn't allow nulls, so set to either 0 or empty string
591
+ if (f.FieldType == EntityFieldTSType.Number || f.FieldType == EntityFieldTSType.Boolean)
592
+ val = 0;
593
+ else
594
+ val = '';
595
+ }
596
+ }
597
+ vars.input[f.CodeName] = val;
598
+ }
599
+ // now add an OldValues prop to the vars IF the type === 'update'
600
+ if (type.trim().toLowerCase() === 'update') {
601
+ const ov = [];
602
+ entity.Fields.forEach(f => {
603
+ const val = f.OldValue ? (typeof f.OldValue === 'string' ? f.OldValue : f.OldValue.toString()) : null;
604
+ ov.push({ Key: f.CodeName, Value: val }); // pass ALL old values to server, slightly inefficient but we want full record
605
+ });
606
+ vars.input['OldValues___'] = ov; // add the OldValues prop to the input property that is part of the vars already
607
+ }
608
+ if (entity.TransactionGroup) {
609
+ return new Promise((resolve, reject) => {
610
+ const mutationInputTypes = [
611
+ {
612
+ varName: 'input',
613
+ inputType: mutationName + 'Input!'
614
+ }
615
+ ];
616
+ // we are part of a transaction group, so just add our query to the list
617
+ // and when the transaction is committed, we will send all the queries at once
618
+ entity.TransactionGroup.AddTransaction(new TransactionItem(entity, inner, vars, { mutationName,
619
+ mutationInputTypes: mutationInputTypes }, (results, success) => {
620
+ // we get here whenever the transaction group does gets around to committing
621
+ // our query. We need to update our entity with the values that were returned
622
+ // from the mutation if it was successful.
623
+ result.EndedAt = new Date();
624
+ if (success && results) {
625
+ // got our data, send it back to the caller, which is the entity object
626
+ // and that object needs to update itself from this data.
627
+ result.Success = true;
628
+ resolve(results);
629
+ }
630
+ else {
631
+ // the transaction failed, nothing to update, but we need to call Reject so the
632
+ // promise resolves with a rejection so our outer caller knows
633
+ result.Success = false;
634
+ result.Message = 'Transaction failed';
635
+ reject();
636
+ }
637
+ }));
638
+ });
639
+ }
640
+ else {
641
+ // not part of a transaction group, so just go for it and send across our GQL
642
+ const d = await GraphQLDataProvider.ExecuteGQL(outer, vars);
643
+ if (d && d[type + entity.EntityInfo.ClassName]) {
644
+ result.Success = true;
645
+ result.EndedAt = new Date();
646
+ return d[type + entity.EntityInfo.ClassName];
647
+ }
648
+ else
649
+ throw new Error(`Save failed for ${entity.EntityInfo.ClassName}`);
650
+ }
651
+ }
652
+ catch (e) {
653
+ result.Success = false;
654
+ result.EndedAt = new Date();
655
+ result.Message = e.response?.errors?.length > 0 ? e.response.errors[0].message : e.message;
656
+ LogError(e);
657
+ return null;
658
+ }
659
+ }
660
+ async Load(entity, primaryKey, EntityRelationshipsToLoad = null, user) {
661
+ try {
662
+ const vars = {};
663
+ let pkeyInnerParamString = '';
664
+ let pkeyOuterParamString = '';
665
+ for (let i = 0; i < primaryKey.KeyValuePairs.length; i++) {
666
+ const field = entity.Fields.find(f => f.Name.trim().toLowerCase() === primaryKey.KeyValuePairs[i].FieldName.trim().toLowerCase()).EntityFieldInfo;
667
+ const val = primaryKey.GetValueByIndex(i);
668
+ const pkeyGraphQLType = field.GraphQLType;
669
+ // build up the param string for the outer query definition
670
+ if (pkeyOuterParamString.length > 0)
671
+ pkeyOuterParamString += ', ';
672
+ pkeyOuterParamString += `$${field.CodeName}: ${pkeyGraphQLType}!`;
673
+ // build up the param string for the inner query call
674
+ if (pkeyInnerParamString.length > 0)
675
+ pkeyInnerParamString += ', ';
676
+ pkeyInnerParamString += `${field.CodeName}: $${field.CodeName}`;
677
+ // build up the variables we are passing along to the query
678
+ if (field.TSType === EntityFieldTSType.Number) {
679
+ if (isNaN(primaryKey.GetValueByIndex(i)))
680
+ throw new Error(`Primary Key value ${val} (${field.Name}) is not a valid number`);
681
+ vars[field.CodeName] = parseInt(val); // converting to number here for graphql type to work properly
682
+ }
683
+ else
684
+ vars[field.CodeName] = val;
685
+ }
686
+ const rel = EntityRelationshipsToLoad && EntityRelationshipsToLoad.length > 0 ? this.getRelatedEntityString(entity.EntityInfo, EntityRelationshipsToLoad) : '';
687
+ const query = gql `query Single${entity.EntityInfo.ClassName}${rel.length > 0 ? 'Full' : ''} (${pkeyOuterParamString}) {
688
+ ${entity.EntityInfo.ClassName}(${pkeyInnerParamString}) {
689
+ ${entity.Fields.filter(f => !f.EntityFieldInfo.IsBinaryFieldType).map(f => f.CodeName).join("\n ")}
690
+ ${rel}
691
+ }
692
+ }
693
+ `;
694
+ const d = await GraphQLDataProvider.ExecuteGQL(query, vars);
695
+ if (d && d[entity.EntityInfo.ClassName]) {
696
+ return d[entity.EntityInfo.ClassName];
697
+ }
698
+ else
699
+ return null;
700
+ }
701
+ catch (e) {
702
+ LogError(e);
703
+ return null;
704
+ }
705
+ }
706
+ getRelatedEntityString(entityInfo, EntityRelationshipsToLoad) {
707
+ let rel = '';
708
+ for (let i = 0; i < entityInfo.RelatedEntities.length; i++) {
709
+ if (EntityRelationshipsToLoad.indexOf(entityInfo.RelatedEntities[i].RelatedEntity) >= 0) {
710
+ const r = entityInfo.RelatedEntities[i];
711
+ const re = this.Entities.find(e => e.ID === r.RelatedEntityID);
712
+ rel += `
713
+ ${re.CodeName} {
714
+ ${re.Fields.map(f => f.CodeName).join("\n ")}
715
+ }
716
+ `;
717
+ }
718
+ }
719
+ return rel;
720
+ }
721
+ async Delete(entity, options, user) {
722
+ const result = new BaseEntityResult();
723
+ try {
724
+ result.StartedAt = new Date();
725
+ result.Type = 'delete';
726
+ result.OriginalValues = entity.Fields.map(f => { return { FieldName: f.CodeName, Value: f.Value }; });
727
+ entity.ResultHistory.push(result); // push the new result as we have started a process
728
+ const vars = {};
729
+ const mutationInputTypes = [];
730
+ let pkeyInnerParamString = '';
731
+ let pkeyOuterParamString = '';
732
+ let returnValues = '';
733
+ for (let kv of entity.PrimaryKey.KeyValuePairs) {
734
+ const pk = entity.Fields.find(f => f.Name.trim().toLowerCase() === kv.FieldName.trim().toLowerCase()); // get the field for the primary key field
735
+ vars[pk.CodeName] = pk.Value;
736
+ mutationInputTypes.push({ varName: pk.CodeName, inputType: pk.EntityFieldInfo.GraphQLType + '!' }); // only used when doing a transaction group, but it is easier to do in this main loop
737
+ if (pkeyInnerParamString.length > 0)
738
+ pkeyInnerParamString += ', ';
739
+ pkeyInnerParamString += `${pk.CodeName}: $${pk.CodeName}`;
740
+ if (pkeyOuterParamString.length > 0)
741
+ pkeyOuterParamString += ', ';
742
+ pkeyOuterParamString += `$${pk.CodeName}: ${pk.EntityFieldInfo.GraphQLType}!`;
743
+ if (returnValues.length > 0)
744
+ returnValues += '\n ';
745
+ returnValues += `${pk.CodeName}`;
746
+ }
747
+ mutationInputTypes.push({ varName: "options___", inputType: 'DeleteOptionsInput!' }); // only used when doing a transaction group, but it is easier to do in this main loop
748
+ vars["options___"] = options ? options : { SkipEntityAIActions: false, SkipEntityActions: false };
749
+ const queryName = 'Delete' + entity.EntityInfo.ClassName;
750
+ const inner = gql `${queryName}(${pkeyInnerParamString}, options___: $options___) {
751
+ ${returnValues}
752
+ }
753
+ `;
754
+ const query = gql `mutation ${queryName} (${pkeyOuterParamString}, $options___: DeleteOptionsInput!) {
755
+ ${inner}
756
+ }
757
+ `;
758
+ if (entity.TransactionGroup) {
759
+ // we have a transaction group, need to play nice and be part of it
760
+ return new Promise((resolve, reject) => {
761
+ // we are part of a transaction group, so just add our query to the list
762
+ // and when the transaction is committed, we will send all the queries at once
763
+ entity.TransactionGroup.AddTransaction(new TransactionItem(entity, inner, vars, { mutationName: queryName,
764
+ mutationInputTypes: mutationInputTypes }, (results, success) => {
765
+ // we get here whenever the transaction group does gets around to committing
766
+ // our query.
767
+ result.EndedAt = new Date(); // done processing
768
+ if (success && results) {
769
+ // success indicated by the entity.PrimaryKey.Value matching the return value of the mutation
770
+ let success = true;
771
+ for (const pk of entity.PrimaryKey.KeyValuePairs) {
772
+ // check each primary key value to see if it matches the return value of the mutation
773
+ if (pk.Value !== results[pk.FieldName]) {
774
+ success = false;
775
+ }
776
+ }
777
+ if (success) {
778
+ result.Success = true;
779
+ resolve(true);
780
+ }
781
+ else {
782
+ // the transaction failed, nothing to update, but we need to call Reject so the
783
+ // promise resolves with a rejection so our outer caller knows
784
+ result.Success = false;
785
+ result.Message = 'Transaction failed to commit';
786
+ reject();
787
+ }
788
+ }
789
+ else {
790
+ // the transaction failed, nothing to update, but we need to call Reject so the
791
+ // promise resolves with a rejection so our outer caller knows
792
+ result.Success = false;
793
+ result.Message = 'Transaction failed to commit';
794
+ reject();
795
+ }
796
+ }));
797
+ });
798
+ }
799
+ else {
800
+ // no transaction just go for it
801
+ const d = await GraphQLDataProvider.ExecuteGQL(query, vars);
802
+ if (d && d[queryName]) {
803
+ for (let key of entity.PrimaryKey.KeyValuePairs) {
804
+ if (key.Value !== d[queryName][key.FieldName])
805
+ throw new Error('Missing primary key value in server Delete response: ' + key.FieldName);
806
+ }
807
+ result.Success = true;
808
+ result.EndedAt = new Date(); // done processing
809
+ return true; // all of the return values match the primary key values, so we are good and delete worked
810
+ }
811
+ else
812
+ throw new Error(`Delete failed for ${entity.EntityInfo.Name}: ${entity.PrimaryKey.ToString()} `);
813
+ }
814
+ }
815
+ catch (e) {
816
+ result.EndedAt = new Date(); // done processing
817
+ result.Success = false;
818
+ result.Message = e.response?.errors?.length > 0 ? e.response.errors[0].message : e.message;
819
+ LogError(e);
820
+ return false;
821
+ }
822
+ }
823
+ /**************************************************************************/
824
+ // END ---- IEntityDataProvider
825
+ /**************************************************************************/
826
+ /**************************************************************************/
827
+ // START ---- IMetadataProvider
828
+ /**************************************************************************/
829
+ async GetDatasetByName(datasetName, itemFilters) {
830
+ const query = gql `query GetDatasetByName($DatasetName: String!, $ItemFilters: [DatasetItemFilterTypeGQL!]) {
831
+ GetDatasetByName(DatasetName: $DatasetName, ItemFilters: $ItemFilters) {
832
+ DatasetID
833
+ DatasetName
834
+ Success
835
+ Status
836
+ LatestUpdateDate
837
+ Results
838
+ }
839
+ }`;
840
+ const data = await GraphQLDataProvider.ExecuteGQL(query, { DatasetName: datasetName, ItemFilters: itemFilters });
841
+ if (data && data.GetDatasetByName && data.GetDatasetByName.Success) {
842
+ return {
843
+ DatasetID: data.GetDatasetByName.DatasetID,
844
+ DatasetName: data.GetDatasetByName.DatasetName,
845
+ Success: data.GetDatasetByName.Success,
846
+ Status: data.GetDatasetByName.Status,
847
+ LatestUpdateDate: new Date(data.GetDatasetByName.LatestUpdateDate),
848
+ Results: JSON.parse(data.GetDatasetByName.Results)
849
+ };
850
+ }
851
+ else {
852
+ return {
853
+ DatasetID: 0,
854
+ DatasetName: datasetName,
855
+ Success: false,
856
+ Status: 'Unknown',
857
+ LatestUpdateDate: null,
858
+ Results: null
859
+ };
860
+ }
861
+ }
862
+ async GetDatasetStatusByName(datasetName, itemFilters) {
863
+ const query = gql `query GetDatasetStatusByName($DatasetName: String!, $ItemFilters: [DatasetItemFilterTypeGQL!]) {
864
+ GetDatasetStatusByName(DatasetName: $DatasetName, ItemFilters: $ItemFilters) {
865
+ DatasetID
866
+ DatasetName
867
+ Success
868
+ Status
869
+ LatestUpdateDate
870
+ EntityUpdateDates
871
+ }
872
+ }`;
873
+ const data = await GraphQLDataProvider.ExecuteGQL(query, { DatasetName: datasetName, ItemFilters: itemFilters });
874
+ if (data && data.GetDatasetStatusByName && data.GetDatasetStatusByName.Success) {
875
+ return {
876
+ DatasetID: data.GetDatasetStatusByName.DatasetID,
877
+ DatasetName: data.GetDatasetStatusByName.DatasetName,
878
+ Success: data.GetDatasetStatusByName.Success,
879
+ Status: data.GetDatasetStatusByName.Status,
880
+ LatestUpdateDate: new Date(data.GetDatasetStatusByName.LatestUpdateDate),
881
+ EntityUpdateDates: JSON.parse(data.GetDatasetStatusByName.EntityUpdateDates)
882
+ };
883
+ }
884
+ else {
885
+ return {
886
+ DatasetID: 0,
887
+ DatasetName: datasetName,
888
+ Success: false,
889
+ Status: 'Unknown',
890
+ LatestUpdateDate: null,
891
+ EntityUpdateDates: null
892
+ };
893
+ }
894
+ }
895
+ async CreateTransactionGroup() {
896
+ return new GraphQLTransactionGroup();
897
+ }
898
+ async GetRecordFavoriteStatus(userId, entityName, primaryKey) {
899
+ const valResult = primaryKey.Validate();
900
+ if (!valResult.IsValid)
901
+ return false;
902
+ const e = this.Entities.find(e => e.Name === entityName);
903
+ if (!e)
904
+ throw new Error(`Entity ${entityName} not found in metadata`);
905
+ const query = gql `query GetRecordFavoriteStatus($params: UserFavoriteSearchParams!) {
906
+ GetRecordFavoriteStatus(params: $params) {
907
+ Success
908
+ IsFavorite
909
+ }
910
+ }`;
911
+ const data = await GraphQLDataProvider.ExecuteGQL(query, { params: {
912
+ UserID: userId,
913
+ EntityID: e.ID,
914
+ CompositeKey: { KeyValuePairs: this.ensureKeyValuePairValueIsString(primaryKey.KeyValuePairs) }
915
+ }
916
+ });
917
+ if (data && data.GetRecordFavoriteStatus && data.GetRecordFavoriteStatus.Success)
918
+ return data.GetRecordFavoriteStatus.IsFavorite;
919
+ }
920
+ async SetRecordFavoriteStatus(userId, entityName, primaryKey, isFavorite, contextUser) {
921
+ const e = this.Entities.find(e => e.Name === entityName);
922
+ if (!e) {
923
+ throw new Error(`Entity ${entityName} not found in metadata`);
924
+ }
925
+ const query = gql `mutation SetRecordFavoriteStatus($params: UserFavoriteSetParams!) {
926
+ SetRecordFavoriteStatus(params: $params){
927
+ Success
928
+ }
929
+ }`;
930
+ const data = await GraphQLDataProvider.ExecuteGQL(query, { params: {
931
+ UserID: userId,
932
+ EntityID: e.ID,
933
+ CompositeKey: { KeyValuePairs: this.ensureKeyValuePairValueIsString(primaryKey.KeyValuePairs) },
934
+ IsFavorite: isFavorite
935
+ }
936
+ });
937
+ if (data && data.SetRecordFavoriteStatus !== null)
938
+ return data.SetRecordFavoriteStatus.Success;
939
+ }
940
+ async GetEntityRecordName(entityName, primaryKey) {
941
+ if (!entityName || !primaryKey || primaryKey.KeyValuePairs?.length === 0) {
942
+ return null;
943
+ }
944
+ const query = gql `query GetEntityRecordNameQuery ($EntityName: String!, $CompositeKey: CompositeKeyInputType!) {
945
+ GetEntityRecordName(EntityName: $EntityName, CompositeKey: $CompositeKey) {
946
+ Success
947
+ Status
948
+ RecordName
949
+ }
950
+ }`;
951
+ const data = await GraphQLDataProvider.ExecuteGQL(query, {
952
+ EntityName: entityName,
953
+ CompositeKey: { KeyValuePairs: this.ensureKeyValuePairValueIsString(primaryKey.KeyValuePairs) }
954
+ });
955
+ if (data && data.GetEntityRecordName && data.GetEntityRecordName.Success)
956
+ return data.GetEntityRecordName.RecordName;
957
+ }
958
+ async GetEntityRecordNames(info) {
959
+ if (!info)
960
+ return null;
961
+ const query = gql `query GetEntityRecordNamesQuery ($info: [EntityRecordNameInput!]!) {
962
+ GetEntityRecordNames(info: $info) {
963
+ Success
964
+ Status
965
+ CompositeKey {
966
+ KeyValuePairs {
967
+ FieldName
968
+ Value
969
+ }
970
+ }
971
+ EntityName
972
+ RecordName
973
+ }
974
+ }`;
975
+ const data = await GraphQLDataProvider.ExecuteGQL(query, { info: info.map(i => {
976
+ return {
977
+ EntityName: i.EntityName,
978
+ CompositeKey: { KeyValuePairs: this.ensureKeyValuePairValueIsString(i.CompositeKey.KeyValuePairs) }
979
+ };
980
+ }) });
981
+ if (data && data.GetEntityRecordNames)
982
+ return data.GetEntityRecordNames;
983
+ }
984
+ static async ExecuteGQL(query, variables, refreshTokenIfNeeded = true) {
985
+ try {
986
+ const data = await GraphQLDataProvider._client.request(query, variables);
987
+ return data;
988
+ }
989
+ catch (e) {
990
+ if (e && e.response && e.response.errors?.length > 0) { //e.code === 'JWT_EXPIRED') {
991
+ const error = e.response.errors[0];
992
+ const code = error?.extensions?.code?.toUpperCase().trim();
993
+ if (code === 'JWT_EXPIRED') {
994
+ if (refreshTokenIfNeeded) {
995
+ // token expired, so we need to refresh it and try again
996
+ await GraphQLDataProvider.RefreshToken();
997
+ return await GraphQLDataProvider.ExecuteGQL(query, variables, false /*don't attempt to refresh again*/);
998
+ }
999
+ else {
1000
+ // token expired but the caller doesn't want a refresh, so just return the error
1001
+ LogError(`JWT_EXPIRED and refreshTokenIfNeeded is false`);
1002
+ throw e;
1003
+ }
1004
+ }
1005
+ else
1006
+ throw e;
1007
+ }
1008
+ else {
1009
+ LogError(e);
1010
+ throw e; // force the caller to handle the error
1011
+ }
1012
+ }
1013
+ }
1014
+ static async RefreshToken() {
1015
+ if (GraphQLDataProvider._configData.Data.RefreshTokenFunction) {
1016
+ const newToken = await GraphQLDataProvider._configData.Data.RefreshTokenFunction();
1017
+ if (newToken) {
1018
+ GraphQLDataProvider._configData.Token = newToken; // update the token
1019
+ GraphQLDataProvider._client = this.CreateNewGraphQLClient(GraphQLDataProvider._configData.URL, GraphQLDataProvider._configData.Token, GraphQLDataProvider._sessionId);
1020
+ }
1021
+ else {
1022
+ throw new Error('Refresh token function returned null or undefined token');
1023
+ }
1024
+ }
1025
+ else {
1026
+ throw new Error('No refresh token function provided');
1027
+ }
1028
+ }
1029
+ static CreateNewGraphQLClient(url, token, sessionId) {
1030
+ return new GraphQLClient(url, {
1031
+ headers: {
1032
+ authorization: 'Bearer ' + token,
1033
+ 'x-session-id': sessionId
1034
+ }
1035
+ });
1036
+ }
1037
+ // private roleInfoString(): string {
1038
+ // return this.infoString(new RoleInfo(null))
1039
+ // }
1040
+ userInfoString() {
1041
+ return this.infoString(new UserInfo(null, null));
1042
+ }
1043
+ userRoleInfoString() {
1044
+ return this.infoString(new UserRoleInfo(null));
1045
+ }
1046
+ // private rowLevelSecurityFilterInfoString(): string {
1047
+ // return this.infoString(new RowLevelSecurityFilterInfo(null))
1048
+ // }
1049
+ // private auditLogTypeInfoString(): string {
1050
+ // return this.infoString(new AuditLogTypeInfo(null))
1051
+ // }
1052
+ // private authorizationInfoString(): string {
1053
+ // return this.infoString(new AuthorizationInfo(null))
1054
+ // }
1055
+ // private queryInfoString(): string {
1056
+ // return this.infoString(new QueryInfo(null))
1057
+ // }
1058
+ // private queryCategoryInfoString(): string {
1059
+ // return this.infoString(new QueryCategoryInfo(null))
1060
+ // }
1061
+ // private applicationInfoString(): string {
1062
+ // return this.infoString(new ApplicationInfo(null, null))
1063
+ // }
1064
+ // private applicationEntityInfoString(): string {
1065
+ // return this.infoString(new ApplicationEntityInfo(null))
1066
+ // }
1067
+ // private entityInfoString(): string {
1068
+ // return this.infoString(new EntityInfo(null))
1069
+ // }
1070
+ // private entityFieldInfoString(): string {
1071
+ // return this.infoString(new EntityFieldInfo(null))
1072
+ // }
1073
+ // private entityRelationshipInfoString(): string {
1074
+ // return this.infoString(new EntityRelationshipInfo(null))
1075
+ // }
1076
+ // private entityPermissionInfoString(): string {
1077
+ // return this.infoString(new EntityPermissionInfo(null))
1078
+ // }
1079
+ infoString(object) {
1080
+ let sOutput = '';
1081
+ const keys = Object.keys(object);
1082
+ for (let i = 0; i < keys.length; i++) {
1083
+ if (keys[i].substring(0, 1) != '_')
1084
+ sOutput += keys[i] + '\n ';
1085
+ }
1086
+ return sOutput;
1087
+ }
1088
+ get LocalStorageProvider() {
1089
+ if (!this._localStorageProvider)
1090
+ this._localStorageProvider = new BrowserIndexedDBStorageProvider();
1091
+ return this._localStorageProvider;
1092
+ }
1093
+ /**************************************************************************/
1094
+ // END ---- IMetadataProvider
1095
+ /**************************************************************************/
1096
+ get Metadata() {
1097
+ return this;
1098
+ }
1099
+ PushStatusUpdates(sessionId = null) {
1100
+ if (!sessionId)
1101
+ sessionId = this.sessionId;
1102
+ if (!this._wsClient)
1103
+ this._wsClient = createClient({
1104
+ url: this.ConfigData.WSURL,
1105
+ connectionParams: {
1106
+ Authorization: 'Bearer ' + this.ConfigData.Token,
1107
+ },
1108
+ });
1109
+ const existingRequest = this._pushStatusRequests.find(r => r.sessionId === sessionId);
1110
+ if (existingRequest)
1111
+ return existingRequest.observable;
1112
+ const SUBSCRIBE_TO_STATUS = gql `subscription StatusUpdates($sessionId: String!) {
1113
+ statusUpdates(sessionId: $sessionId) {
1114
+ date
1115
+ message
1116
+ sessionId
1117
+ }
1118
+ }
1119
+ `;
1120
+ const newObservable = new Observable((observer) => {
1121
+ const unsubscribe = this._wsClient.subscribe({ query: SUBSCRIBE_TO_STATUS, variables: { sessionId } }, {
1122
+ next: (data) => {
1123
+ return observer.next(data.data.statusUpdates);
1124
+ },
1125
+ error: (error) => {
1126
+ return observer.error(error);
1127
+ },
1128
+ complete: () => {
1129
+ return observer.complete();
1130
+ },
1131
+ });
1132
+ return () => {
1133
+ // Cleanup logic
1134
+ console.log('would unsub here');
1135
+ //unsubscribe();
1136
+ };
1137
+ });
1138
+ this._pushStatusRequests.push({ sessionId, observable: newObservable });
1139
+ return newObservable;
1140
+ }
1141
+ }
1142
+ // this class implements a simple in-memory only storage as a fallback if the browser doesn't support local storage
1143
+ class BrowserStorageProviderBase {
1144
+ constructor() {
1145
+ this._localStorage = {};
1146
+ }
1147
+ async getItem(key) {
1148
+ return new Promise((resolve) => {
1149
+ if (this._localStorage.hasOwnProperty(key))
1150
+ resolve(this._localStorage[key]);
1151
+ else
1152
+ resolve(null);
1153
+ });
1154
+ }
1155
+ async setItem(key, value) {
1156
+ return new Promise((resolve) => {
1157
+ this._localStorage[key] = value;
1158
+ resolve();
1159
+ });
1160
+ }
1161
+ async remove(key) {
1162
+ return new Promise((resolve) => {
1163
+ if (this._localStorage.hasOwnProperty(key)) {
1164
+ delete this._localStorage[key];
1165
+ }
1166
+ resolve();
1167
+ });
1168
+ }
1169
+ }
1170
+ // This implementation just wraps the browser local storage and if for some reason the browser doesn't
1171
+ // have a localStorage object, we just use a simple object to store the data in memory.
1172
+ class BrowserLocalStorageProvider extends BrowserStorageProviderBase {
1173
+ async getItem(key) {
1174
+ if (localStorage)
1175
+ return localStorage.getItem(key);
1176
+ else
1177
+ return await super.getItem(key);
1178
+ }
1179
+ async setItem(key, value) {
1180
+ if (localStorage)
1181
+ localStorage.setItem(key, value);
1182
+ else
1183
+ await super.setItem(key, value);
1184
+ }
1185
+ async remove(key) {
1186
+ if (localStorage)
1187
+ localStorage.removeItem(key);
1188
+ else
1189
+ await super.remove(key);
1190
+ }
1191
+ }
1192
+ const IDB_DB_NAME = 'MJ_Metadata';
1193
+ const IDB_DB_ObjectStoreName = 'Metadata_KVPairs';
1194
+ class BrowserIndexedDBStorageProvider extends BrowserStorageProviderBase {
1195
+ constructor() {
1196
+ super();
1197
+ this.dbPromise = openDB(IDB_DB_NAME, 1, {
1198
+ upgrade(db) {
1199
+ if (!db.objectStoreNames.contains(IDB_DB_ObjectStoreName)) {
1200
+ db.createObjectStore(IDB_DB_ObjectStoreName);
1201
+ }
1202
+ },
1203
+ });
1204
+ }
1205
+ async setItem(key, value) {
1206
+ const db = await this.dbPromise;
1207
+ const tx = db.transaction(IDB_DB_ObjectStoreName, 'readwrite');
1208
+ await tx.objectStore(IDB_DB_ObjectStoreName).put(value, key);
1209
+ await tx.done;
1210
+ }
1211
+ async getItem(key) {
1212
+ const db = await this.dbPromise;
1213
+ const value = await db.transaction(IDB_DB_ObjectStoreName).objectStore(IDB_DB_ObjectStoreName).get(key);
1214
+ return value;
1215
+ }
1216
+ async remove(key) {
1217
+ const db = await this.dbPromise;
1218
+ const tx = db.transaction(IDB_DB_ObjectStoreName, 'readwrite');
1219
+ await tx.objectStore(IDB_DB_ObjectStoreName).delete(key);
1220
+ await tx.done;
1221
+ }
1222
+ }
1223
+ //# sourceMappingURL=graphQLDataProvider.js.map