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