@memberjunction/server 2.47.0 → 2.49.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.
Files changed (81) hide show
  1. package/dist/apolloServer/TransactionPlugin.js +7 -7
  2. package/dist/apolloServer/TransactionPlugin.js.map +1 -1
  3. package/dist/auth/index.d.ts +3 -3
  4. package/dist/auth/index.d.ts.map +1 -1
  5. package/dist/auth/index.js.map +1 -1
  6. package/dist/auth/newUsers.d.ts.map +1 -1
  7. package/dist/auth/newUsers.js +2 -13
  8. package/dist/auth/newUsers.js.map +1 -1
  9. package/dist/context.d.ts +3 -3
  10. package/dist/context.d.ts.map +1 -1
  11. package/dist/context.js.map +1 -1
  12. package/dist/generated/generated.d.ts +686 -329
  13. package/dist/generated/generated.d.ts.map +1 -1
  14. package/dist/generated/generated.js +5786 -3286
  15. package/dist/generated/generated.js.map +1 -1
  16. package/dist/generic/ResolverBase.d.ts +17 -17
  17. package/dist/generic/ResolverBase.d.ts.map +1 -1
  18. package/dist/generic/ResolverBase.js +7 -5
  19. package/dist/generic/ResolverBase.js.map +1 -1
  20. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  21. package/dist/generic/RunViewResolver.js +12 -4
  22. package/dist/generic/RunViewResolver.js.map +1 -1
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +17 -14
  26. package/dist/index.js.map +1 -1
  27. package/dist/orm.d.ts +3 -3
  28. package/dist/orm.d.ts.map +1 -1
  29. package/dist/orm.js +14 -15
  30. package/dist/orm.js.map +1 -1
  31. package/dist/resolvers/AskSkipResolver.d.ts +19 -19
  32. package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
  33. package/dist/resolvers/AskSkipResolver.js +17 -12
  34. package/dist/resolvers/AskSkipResolver.js.map +1 -1
  35. package/dist/resolvers/EntityResolver.d.ts +2 -1
  36. package/dist/resolvers/EntityResolver.d.ts.map +1 -1
  37. package/dist/resolvers/EntityResolver.js +6 -3
  38. package/dist/resolvers/EntityResolver.js.map +1 -1
  39. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  40. package/dist/resolvers/FileCategoryResolver.js +10 -2
  41. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  42. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  43. package/dist/resolvers/GetDataResolver.js +4 -2
  44. package/dist/resolvers/GetDataResolver.js.map +1 -1
  45. package/dist/resolvers/ReportResolver.d.ts.map +1 -1
  46. package/dist/resolvers/ReportResolver.js +5 -3
  47. package/dist/resolvers/ReportResolver.js.map +1 -1
  48. package/dist/resolvers/RunAIAgentResolver.d.ts +19 -0
  49. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -0
  50. package/dist/resolvers/RunAIAgentResolver.js +194 -0
  51. package/dist/resolvers/RunAIAgentResolver.js.map +1 -0
  52. package/dist/resolvers/UserFavoriteResolver.d.ts +2 -2
  53. package/dist/resolvers/UserViewResolver.d.ts +4 -4
  54. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  55. package/dist/resolvers/UserViewResolver.js.map +1 -1
  56. package/dist/types.d.ts +6 -6
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/types.js.map +1 -1
  59. package/dist/util.d.ts +10 -3
  60. package/dist/util.d.ts.map +1 -1
  61. package/dist/util.js +22 -3
  62. package/dist/util.js.map +1 -1
  63. package/package.json +24 -25
  64. package/src/apolloServer/TransactionPlugin.ts +8 -8
  65. package/src/auth/index.ts +4 -4
  66. package/src/auth/newUsers.ts +0 -1
  67. package/src/context.ts +2 -2
  68. package/src/generated/generated.ts +4759 -3021
  69. package/src/generic/ResolverBase.ts +23 -22
  70. package/src/generic/RunViewResolver.ts +12 -4
  71. package/src/index.ts +18 -15
  72. package/src/orm.ts +18 -17
  73. package/src/resolvers/AskSkipResolver.ts +36 -32
  74. package/src/resolvers/EntityResolver.ts +5 -2
  75. package/src/resolvers/FileCategoryResolver.ts +9 -2
  76. package/src/resolvers/GetDataResolver.ts +4 -2
  77. package/src/resolvers/ReportResolver.ts +5 -3
  78. package/src/resolvers/RunAIAgentResolver.ts +174 -0
  79. package/src/resolvers/UserViewResolver.ts +0 -1
  80. package/src/types.ts +8 -8
  81. package/src/util.ts +31 -6
@@ -17,7 +17,7 @@ import { AuditLogEntity, UserViewEntity } from '@memberjunction/core-entities';
17
17
  import { UserCache } from '@memberjunction/sqlserver-dataprovider';
18
18
  import { PubSubEngine } from 'type-graphql';
19
19
  import { GraphQLError } from 'graphql';
20
- import { DataSource } from 'typeorm';
20
+ import sql from 'mssql';
21
21
  import { httpTransport, CloudEvent, emitterFor } from 'cloudevents';
22
22
 
23
23
  import { RunViewGenericParams, UserPayload } from '../types.js';
@@ -70,7 +70,7 @@ export class ResolverBase {
70
70
  return dataObject;
71
71
  }
72
72
 
73
- protected ArrayMapFieldNamesToCodeNames(entityName: string, dataObjectArray: []) {
73
+ protected ArrayMapFieldNamesToCodeNames(entityName: string, dataObjectArray: any[]) {
74
74
  // iterate through the array and call MapFieldNamesToCodeNames for each element
75
75
  if (dataObjectArray && dataObjectArray.length > 0) {
76
76
  dataObjectArray.forEach((element) => {
@@ -80,29 +80,30 @@ export class ResolverBase {
80
80
  return dataObjectArray;
81
81
  }
82
82
 
83
- protected async findBy(dataSource: DataSource, entity: string, params: any) {
83
+ protected async findBy(dataSource: sql.ConnectionPool, entity: string, params: any) {
84
84
  // build the SQL query based on the params passed in
85
85
  const md = new Metadata();
86
86
  const e = md.Entities.find((e) => e.Name === entity);
87
87
  if (!e) throw new Error(`Entity ${entity} not found in metadata`);
88
88
  // now build a SQL string using the entityInfo and using the properties in the params object
89
- let sql = `SELECT * FROM ${e.SchemaName}.${e.BaseView} WHERE `;
89
+ let sqlQuery = `SELECT * FROM ${e.SchemaName}.${e.BaseView} WHERE `;
90
90
  const keys = Object.keys(params);
91
91
  keys.forEach((k, i) => {
92
- if (i > 0) sql += ' AND ';
92
+ if (i > 0) sqlQuery += ' AND ';
93
93
  // look up the field in the entityInfo to see if it needs quotes
94
94
  const field = e.Fields.find((f) => f.Name === k);
95
95
  if (!field) throw new Error(`Field ${k} not found in entity ${entity}`);
96
96
  const quotes = field.NeedsQuotes ? "'" : '';
97
- sql += `${k} = ${quotes}${params[k]}${quotes}`;
97
+ sqlQuery += `${k} = ${quotes}${params[k]}${quotes}`;
98
98
  });
99
99
 
100
100
  // ok, now we have a SQL string, run it and return the results
101
- const result = await dataSource.query(sql);
102
- return result;
101
+ const request = new sql.Request(dataSource);
102
+ const result = await request.query(sqlQuery);
103
+ return result.recordset;
103
104
  }
104
105
 
105
- async RunViewByNameGeneric(viewInput: RunViewByNameInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
106
+ async RunViewByNameGeneric(viewInput: RunViewByNameInput, dataSource: sql.ConnectionPool, userPayload: UserPayload, pubSub: PubSubEngine) {
106
107
  try {
107
108
  const viewInfo: UserViewEntity = this.safeFirstArrayElement(
108
109
  await this.findBy(dataSource, 'User Views', { Name: viewInput.ViewName })
@@ -131,7 +132,7 @@ export class ResolverBase {
131
132
  }
132
133
  }
133
134
 
134
- async RunViewByIDGeneric(viewInput: RunViewByIDInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
135
+ async RunViewByIDGeneric(viewInput: RunViewByIDInput, dataSource: sql.ConnectionPool, userPayload: UserPayload, pubSub: PubSubEngine) {
135
136
  try {
136
137
  const viewInfo: UserViewEntity = this.safeFirstArrayElement(await this.findBy(dataSource, 'User Views', { ID: viewInput.ViewID }));
137
138
  return this.RunViewGenericInternal(
@@ -158,7 +159,7 @@ export class ResolverBase {
158
159
  }
159
160
  }
160
161
 
161
- async RunDynamicViewGeneric(viewInput: RunDynamicViewInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
162
+ async RunDynamicViewGeneric(viewInput: RunDynamicViewInput, dataSource: sql.ConnectionPool, userPayload: UserPayload, pubSub: PubSubEngine) {
162
163
  try {
163
164
  const md = new Metadata();
164
165
  const entity = md.Entities.find((e) => e.Name === viewInput.EntityName);
@@ -197,7 +198,7 @@ export class ResolverBase {
197
198
 
198
199
  async RunViewsGeneric(
199
200
  viewInputs: (RunViewByNameInput & RunViewByIDInput & RunDynamicViewInput)[],
200
- dataSource: DataSource,
201
+ dataSource: sql.ConnectionPool,
201
202
  userPayload: UserPayload,
202
203
  pubSub: PubSubEngine
203
204
  ) {
@@ -332,7 +333,7 @@ export class ResolverBase {
332
333
  */
333
334
  protected async RunViewGenericInternal(
334
335
  viewInfo: UserViewEntity,
335
- dataSource: DataSource,
336
+ dataSource: sql.ConnectionPool,
336
337
  extraFilter: string,
337
338
  orderBy: string,
338
339
  userSearchString: string,
@@ -660,7 +661,7 @@ export class ResolverBase {
660
661
  }
661
662
  }
662
663
 
663
- protected async CreateRecord(entityName: string, input: any, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
664
+ protected async CreateRecord(entityName: string, input: any, dataSource: sql.ConnectionPool, userPayload: UserPayload, pubSub: PubSubEngine) {
664
665
  if (await this.BeforeCreate(dataSource, input)) {
665
666
  // fire event and proceed if it wasn't cancelled
666
667
  const entityObject = await new Metadata().GetEntityObject(entityName, this.GetUserFromPayload(userPayload));
@@ -680,12 +681,12 @@ export class ResolverBase {
680
681
  }
681
682
 
682
683
  // Before/After CREATE Event Hooks for Sub-Classes to Override
683
- protected async BeforeCreate(dataSource: DataSource, input: any): Promise<boolean> {
684
+ protected async BeforeCreate(dataSource: sql.ConnectionPool, input: any): Promise<boolean> {
684
685
  return true;
685
686
  }
686
- protected async AfterCreate(dataSource: DataSource, input: any) {}
687
+ protected async AfterCreate(dataSource: sql.ConnectionPool, input: any) {}
687
688
 
688
- protected async UpdateRecord(entityName: string, input: any, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
689
+ protected async UpdateRecord(entityName: string, input: any, dataSource: sql.ConnectionPool, userPayload: UserPayload, pubSub: PubSubEngine) {
689
690
  if (await this.BeforeUpdate(dataSource, input)) {
690
691
  // fire event and proceed if it wasn't cancelled
691
692
  const md = new Metadata();
@@ -893,7 +894,7 @@ export class ResolverBase {
893
894
  entityName: string,
894
895
  key: CompositeKey,
895
896
  options: DeleteOptionsInput,
896
- dataSource: DataSource,
897
+ dataSource: sql.ConnectionPool,
897
898
  userPayload: UserPayload,
898
899
  pubSub: PubSubEngine
899
900
  ) {
@@ -921,14 +922,14 @@ export class ResolverBase {
921
922
  }
922
923
 
923
924
  // Before/After DELETE Event Hooks for Sub-Classes to Override
924
- protected async BeforeDelete(dataSource: DataSource, key: CompositeKey): Promise<boolean> {
925
+ protected async BeforeDelete(dataSource: sql.ConnectionPool, key: CompositeKey): Promise<boolean> {
925
926
  return true;
926
927
  }
927
- protected async AfterDelete(dataSource: DataSource, key: CompositeKey) {}
928
+ protected async AfterDelete(dataSource: sql.ConnectionPool, key: CompositeKey) {}
928
929
 
929
930
  // Before/After UPDATE Event Hooks for Sub-Classes to Override
930
- protected async BeforeUpdate(dataSource: DataSource, input: any): Promise<boolean> {
931
+ protected async BeforeUpdate(dataSource: sql.ConnectionPool, input: any): Promise<boolean> {
931
932
  return true;
932
933
  }
933
- protected async AfterUpdate(dataSource: DataSource, input: any) {}
934
+ protected async AfterUpdate(dataSource: sql.ConnectionPool, input: any) {}
934
935
  }
@@ -463,7 +463,9 @@ export class RunViewResolver extends ResolverBase {
463
463
  const rawData = await super.RunViewByNameGeneric(input, dataSource, userPayload, pubSub);
464
464
  if (rawData === null) return null;
465
465
 
466
- const entityId = await dataSource.query(`SELECT EntityID from [${this.MJCoreSchema}].vwUserViews WHERE Name='${input.ViewName}'`);
466
+ const request = dataSource.request();
467
+ const entityIdResult = await request.query(`SELECT EntityID from [${this.MJCoreSchema}].vwUserViews WHERE Name='${input.ViewName}'`);
468
+ const entityId = entityIdResult.recordset;
467
469
  const returnData = this.processRawData(rawData.Results, entityId[0].EntityID);
468
470
  return {
469
471
  Results: returnData,
@@ -488,7 +490,9 @@ export class RunViewResolver extends ResolverBase {
488
490
  const rawData = await super.RunViewByIDGeneric(input, dataSource, userPayload, pubSub);
489
491
  if (rawData === null) return null;
490
492
 
491
- const entityId = await dataSource.query(`SELECT EntityID from [${this.MJCoreSchema}].vwUserViews WHERE ID=${input.ViewID}`);
493
+ const request = dataSource.request();
494
+ const entityIdResult = await request.query(`SELECT EntityID from [${this.MJCoreSchema}].vwUserViews WHERE ID=${input.ViewID}`);
495
+ const entityId = entityIdResult.recordset;
492
496
  const returnData = this.processRawData(rawData.Results, entityId[0].EntityID);
493
497
  return {
494
498
  Results: returnData,
@@ -513,7 +517,9 @@ export class RunViewResolver extends ResolverBase {
513
517
  const rawData = await super.RunDynamicViewGeneric(input, dataSource, userPayload, pubSub);
514
518
  if (rawData === null) return null;
515
519
 
516
- const entityId = await dataSource.query(`SELECT ID from [${this.MJCoreSchema}].vwEntities WHERE Name='${input.EntityName}'`);
520
+ const request = dataSource.request();
521
+ const entityIdResult = await request.query(`SELECT ID from [${this.MJCoreSchema}].vwEntities WHERE Name='${input.EntityName}'`);
522
+ const entityId = entityIdResult.recordset;
517
523
  const returnData = this.processRawData(rawData.Results, entityId[0].EntityID);
518
524
  return {
519
525
  Results: returnData,
@@ -542,9 +548,11 @@ export class RunViewResolver extends ResolverBase {
542
548
 
543
549
  let results: RunViewGenericResult[] = [];
544
550
  for (const [index, data] of rawData.entries()) {
545
- const entityId = await dataSource.query(
551
+ const request = dataSource.request();
552
+ const entityIdResult = await request.query(
546
553
  `SELECT TOP 1 ID from [${this.MJCoreSchema}].vwEntities WHERE Name='${input[index].EntityName}'`
547
554
  );
555
+ const entityId = entityIdResult.recordset;
548
556
  const returnData: any[] = this.processRawData(data.Results, entityId[0].ID);
549
557
 
550
558
  results.push({
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import { expressMiddleware } from '@apollo/server/express4';
6
6
  import { mergeSchemas } from '@graphql-tools/schema';
7
7
  import { Metadata } from '@memberjunction/core';
8
8
  import { setupSQLServerClient, SQLServerProviderConfigData, UserCache } from '@memberjunction/sqlserver-dataprovider';
9
+ import { extendConnectionPoolWithQuery } from './util.js';
9
10
  import { default as BodyParser } from 'body-parser';
10
11
  import compression from 'compression'; // Add compression middleware
11
12
  import cors from 'cors';
@@ -18,13 +19,13 @@ import { sep } from 'node:path';
18
19
  import 'reflect-metadata';
19
20
  import { ReplaySubject } from 'rxjs';
20
21
  import { BuildSchemaOptions, buildSchemaSync, GraphQLTimestamp } from 'type-graphql';
21
- import { DataSource } from 'typeorm';
22
+ import sql from 'mssql';
22
23
  import { WebSocketServer } from 'ws';
23
24
  import buildApolloServer from './apolloServer/index.js';
24
25
  import { configInfo, dbDatabase, dbHost, dbPort, dbUsername, graphqlPort, graphqlRootPath, mj_core_schema, websiteRunFromPackage, RESTApiOptions } from './config.js';
25
26
  import { contextFunction, getUserPayload } from './context.js';
26
27
  import { requireSystemUserDirective, publicDirective } from './directives/index.js';
27
- import orm from './orm.js';
28
+ import createMSSQLConfig from './orm.js';
28
29
  import { setupRESTEndpoints } from './rest/setupRESTEndpoints.js';
29
30
 
30
31
  import { LoadActionEntityServer } from '@memberjunction/actions';
@@ -54,6 +55,7 @@ export * from './generic/ResolverBase.js';
54
55
  export * from './generic/RunViewResolver.js';
55
56
  export * from './resolvers/RunTemplateResolver.js';
56
57
  export * from './resolvers/RunAIPromptResolver.js';
58
+ export * from './resolvers/RunAIAgentResolver.js';
57
59
  export * from './generic/KeyValuePairInput.js';
58
60
  export * from './generic/KeyInputOutputTypes.js';
59
61
  export * from './generic/DeleteOptionsInput.js';
@@ -105,31 +107,31 @@ export const serve = async (resolverPaths: Array<string>, app = createApp(), opt
105
107
  console.log({ combinedResolverPaths, paths, cwd: process.cwd() });
106
108
  }
107
109
 
108
- const dataSource = new DataSource(orm(paths));
110
+ const pool = new sql.ConnectionPool(createMSSQLConfig());
109
111
  const setupComplete$ = new ReplaySubject(1);
110
- await dataSource.initialize();
112
+ await pool.connect();
111
113
 
112
- const config = new SQLServerProviderConfigData(dataSource, '', mj_core_schema, cacheRefreshInterval);
114
+ const config = new SQLServerProviderConfigData(pool, '', mj_core_schema, cacheRefreshInterval);
113
115
  await setupSQLServerClient(config); // datasource is already initialized, so we can setup the client right away
114
116
  const md = new Metadata();
115
117
  console.log(`Data Source has been initialized. ${md?.Entities ? md.Entities.length : 0} entities loaded.`);
116
118
 
117
- const dataSources = [new DataSourceInfo({dataSource, type: 'Read-Write', host: dbHost, port: dbPort, database: dbDatabase, userName: dbUsername})];
119
+ const dataSources = [new DataSourceInfo({dataSource: pool, type: 'Read-Write', host: dbHost, port: dbPort, database: dbDatabase, userName: dbUsername})];
118
120
 
119
121
  // Establish a second read-only connection to the database if dbReadOnlyUsername and dbReadOnlyPassword exist
120
- let readOnlyDataSource: DataSource | null = null;
122
+ let readOnlyPool: sql.ConnectionPool | null = null;
121
123
  if (configInfo.dbReadOnlyUsername && configInfo.dbReadOnlyPassword) {
122
124
  const readOnlyConfig = {
123
- ...orm(paths),
124
- username: configInfo.dbReadOnlyUsername,
125
+ ...createMSSQLConfig(),
126
+ user: configInfo.dbReadOnlyUsername,
125
127
  password: configInfo.dbReadOnlyPassword,
126
128
  };
127
- readOnlyDataSource = new DataSource(readOnlyConfig);
128
- await readOnlyDataSource.initialize();
129
+ readOnlyPool = new sql.ConnectionPool(readOnlyConfig);
130
+ await readOnlyPool.connect();
129
131
 
130
- // since we created a read-only data source, add it to the list of data sources
131
- dataSources.push(new DataSourceInfo({dataSource: readOnlyDataSource, type: 'Read-Only', host: dbHost, port: dbPort, database: dbDatabase, userName: configInfo.dbReadOnlyUsername}));
132
- console.log('Read-only Data Source has been initialized.');
132
+ // since we created a read-only pool, add it to the list of data sources
133
+ dataSources.push(new DataSourceInfo({dataSource: readOnlyPool, type: 'Read-Only', host: dbHost, port: dbPort, database: dbDatabase, userName: configInfo.dbReadOnlyUsername}));
134
+ console.log('Read-only Connection Pool has been initialized.');
133
135
  }
134
136
 
135
137
  setupComplete$.next(true);
@@ -224,7 +226,7 @@ export const serve = async (resolverPaths: Array<string>, app = createApp(), opt
224
226
  expressMiddleware(apolloServer, {
225
227
  context: contextFunction({
226
228
  setupComplete$,
227
- dataSource, // default read-write data source
229
+ dataSource: extendConnectionPoolWithQuery(pool), // default read-write data source
228
230
  dataSources // all data source
229
231
  }),
230
232
  })
@@ -288,5 +290,6 @@ export const serve = async (resolverPaths: Array<string>, app = createApp(), opt
288
290
  }
289
291
 
290
292
  await new Promise<void>((resolve) => httpServer.listen({ port: graphqlPort }, resolve));
293
+ console.log(`📦 Connected to database: ${dbHost}:${dbPort}/${dbDatabase}`);
291
294
  console.log(`🚀 Server ready at http://localhost:${graphqlPort}/`);
292
295
  };
package/src/orm.ts CHANGED
@@ -1,36 +1,37 @@
1
- import { DataSourceOptions } from 'typeorm';
1
+ import sql from 'mssql';
2
2
  import { configInfo, dbDatabase, dbHost, dbPassword, dbPort, dbUsername, dbInstanceName, dbTrustServerCertificate } from './config.js';
3
3
 
4
- const orm = (entities: Array<string>): DataSourceOptions => {
5
- const ormConfig = {
6
- type: 'mssql' as const,
7
- entities,
8
- logging: false,
9
- host: dbHost,
4
+ const createMSSQLConfig = (): sql.config => {
5
+ const mssqlConfig: sql.config = {
6
+ server: dbHost,
10
7
  port: dbPort,
11
- username: dbUsername,
8
+ user: dbUsername,
12
9
  password: dbPassword,
13
10
  database: dbDatabase,
14
- synchronize: false,
15
11
  requestTimeout: configInfo.databaseSettings.requestTimeout,
16
12
  connectionTimeout: configInfo.databaseSettings.connectionTimeout,
17
- options: {},
13
+ options: {
14
+ encrypt: true, // Use encryption
15
+ enableArithAbort: true,
16
+ },
18
17
  };
18
+
19
19
  if (dbInstanceName !== null && dbInstanceName !== undefined && dbInstanceName.trim().length > 0) {
20
- ormConfig.options = {
21
- ...ormConfig.options,
20
+ mssqlConfig.options = {
21
+ ...mssqlConfig.options,
22
22
  instanceName: dbInstanceName,
23
23
  };
24
24
  }
25
+
25
26
  if (dbTrustServerCertificate !== null && dbTrustServerCertificate !== undefined) {
26
- ormConfig.options = {
27
- ...ormConfig.options,
27
+ mssqlConfig.options = {
28
+ ...mssqlConfig.options,
28
29
  trustServerCertificate: dbTrustServerCertificate === 'Y',
29
30
  };
30
31
  }
31
32
 
32
- //console.log({ ormConfig: { ...ormConfig, password: '***' } });
33
- return ormConfig;
33
+ //console.log({ mssqlConfig: { ...mssqlConfig, password: '***' } });
34
+ return mssqlConfig;
34
35
  };
35
36
 
36
- export default orm;
37
+ export default createMSSQLConfig;
@@ -53,8 +53,8 @@ import {
53
53
  UserNotificationEntity,
54
54
  AIAgentEntityExtended
55
55
  } from '@memberjunction/core-entities';
56
- import { DataSource } from 'typeorm';
57
56
  import { apiKey, baseUrl, configInfo, graphqlPort, mj_core_schema } from '../config.js';
57
+ import mssql from 'mssql';
58
58
 
59
59
  import { registerEnumType } from 'type-graphql';
60
60
  import { MJGlobal, CopyScalarsAndArrays } from '@memberjunction/global';
@@ -882,7 +882,7 @@ cycle.`);
882
882
  */
883
883
  protected async buildBaseSkipRequest(
884
884
  contextUser: UserInfo,
885
- dataSource: DataSource,
885
+ dataSource: mssql.ConnectionPool,
886
886
  includeEntities: boolean,
887
887
  includeQueries: boolean,
888
888
  includeNotes: boolean,
@@ -955,7 +955,7 @@ cycle.`);
955
955
  includeQueries: boolean,
956
956
  includeNotes: boolean,
957
957
  includeRequests: boolean,
958
- dataSource: DataSource,
958
+ dataSource: mssql.ConnectionPool,
959
959
  contextUser: UserInfo,
960
960
  forceEntitiesRefresh: boolean = false,
961
961
  includeCallBackKeyAndAccessToken: boolean = false
@@ -1005,7 +1005,7 @@ cycle.`);
1005
1005
  */
1006
1006
  protected async BuildSkipLearningCycleNewConversations(
1007
1007
  lastLearningCycleDate: Date,
1008
- dataSource: DataSource,
1008
+ dataSource: mssql.ConnectionPool,
1009
1009
  contextUser: UserInfo
1010
1010
  ): Promise<SkipConversation[]> {
1011
1011
  try {
@@ -1143,7 +1143,7 @@ cycle.`);
1143
1143
  includeNotes: boolean,
1144
1144
  includeRequests: boolean,
1145
1145
  contextUser: UserInfo,
1146
- dataSource: DataSource,
1146
+ dataSource: mssql.ConnectionPool,
1147
1147
  forceEntitiesRefresh: boolean = false,
1148
1148
  includeCallBackKeyAndAccessToken: boolean = false
1149
1149
  ): Promise<SkipAPIRequest> {
@@ -1191,7 +1191,7 @@ cycle.`);
1191
1191
  * @param conversationId ID of the conversation
1192
1192
  * @returns Array of artifacts associated with the conversation
1193
1193
  */
1194
- protected async buildSkipAPIArtifacts(contextUser: UserInfo, dataSource: DataSource, conversationId: string): Promise<SkipAPIArtifact[]> {
1194
+ protected async buildSkipAPIArtifacts(contextUser: UserInfo, dataSource: mssql.ConnectionPool, conversationId: string): Promise<SkipAPIArtifact[]> {
1195
1195
  const md = new Metadata();
1196
1196
  const ei = md.EntityByName('MJ: Conversation Artifacts');
1197
1197
  const rv = new RunView();
@@ -1507,7 +1507,7 @@ cycle.`);
1507
1507
  * @param dataSource Database connection
1508
1508
  * @returns Array of entity rows based on packing configuration
1509
1509
  */
1510
- protected async PackEntityRows(e: EntityInfo, dataSource: DataSource): Promise<any[]> {
1510
+ protected async PackEntityRows(e: EntityInfo, dataSource: mssql.ConnectionPool): Promise<any[]> {
1511
1511
  try {
1512
1512
  if (e.RowsToPackWithSchema === 'None')
1513
1513
  return [];
@@ -1545,12 +1545,13 @@ cycle.`);
1545
1545
  break;
1546
1546
  }
1547
1547
  }
1548
- const result = await dataSource.query(sql);
1549
- if (!result) {
1548
+ const request = new mssql.Request(dataSource);
1549
+ const result = await request.query(sql);
1550
+ if (!result || !result.recordset) {
1550
1551
  return [];
1551
1552
  }
1552
1553
  else {
1553
- return result;
1554
+ return result.recordset;
1554
1555
  }
1555
1556
  }
1556
1557
  catch (e) {
@@ -1567,7 +1568,7 @@ cycle.`);
1567
1568
  * @param dataSource Database connection
1568
1569
  * @returns Array of possible values for the field
1569
1570
  */
1570
- protected async PackFieldPossibleValues(f: EntityFieldInfo, dataSource: DataSource): Promise<SkipEntityFieldValueInfo[]> {
1571
+ protected async PackFieldPossibleValues(f: EntityFieldInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityFieldValueInfo[]> {
1571
1572
  try {
1572
1573
  if (f.ValuesToPackWithSchema === 'None') {
1573
1574
  return []; // don't pack anything
@@ -1617,15 +1618,16 @@ cycle.`);
1617
1618
  * @param dataSource Database connection
1618
1619
  * @returns Array of distinct values for the field
1619
1620
  */
1620
- protected async GetFieldDistinctValues(f: EntityFieldInfo, dataSource: DataSource): Promise<SkipEntityFieldValueInfo[]> {
1621
+ protected async GetFieldDistinctValues(f: EntityFieldInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityFieldValueInfo[]> {
1621
1622
  try {
1622
1623
  const sql = `SELECT DISTINCT ${f.Name} FROM ${f.SchemaName}.${f.BaseView}`;
1623
- const result = await dataSource.query(sql);
1624
- if (!result) {
1624
+ const request = new mssql.Request(dataSource);
1625
+ const result = await request.query(sql);
1626
+ if (!result || !result.recordset) {
1625
1627
  return [];
1626
1628
  }
1627
1629
  else {
1628
- return result.map((r) => {
1630
+ return result.recordset.map((r) => {
1629
1631
  return {
1630
1632
  value: r[f.Name],
1631
1633
  displayValue: r[f.Name]
@@ -1652,7 +1654,7 @@ cycle.`);
1652
1654
  * @param dataSource Database connection
1653
1655
  * @returns Updated array of entity information
1654
1656
  */
1655
- private async refreshSkipEntities(dataSource: DataSource): Promise<SkipEntityInfo[]> {
1657
+ private async refreshSkipEntities(dataSource: mssql.ConnectionPool): Promise<SkipEntityInfo[]> {
1656
1658
  try {
1657
1659
  const md = new Metadata();
1658
1660
  const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSend?.includeEntitiesFromExcludedSchemas ?? [])
@@ -1695,7 +1697,7 @@ cycle.`);
1695
1697
  * @param refreshIntervalMinutes Minutes before cache expires
1696
1698
  * @returns Array of entity information
1697
1699
  */
1698
- public async BuildSkipEntities(dataSource: DataSource, forceRefresh: boolean = false, refreshIntervalMinutes: number = 15): Promise<SkipEntityInfo[]> {
1700
+ public async BuildSkipEntities(dataSource: mssql.ConnectionPool, forceRefresh: boolean = false, refreshIntervalMinutes: number = 15): Promise<SkipEntityInfo[]> {
1699
1701
  try {
1700
1702
  const now = Date.now();
1701
1703
  const cacheExpired = (now - AskSkipResolver.__lastRefreshTime) > (refreshIntervalMinutes * 60 * 1000);
@@ -1723,7 +1725,7 @@ cycle.`);
1723
1725
  * @param dataSource Database connection
1724
1726
  * @returns Packaged entity information
1725
1727
  */
1726
- protected async PackSingleSkipEntityInfo(e: EntityInfo, dataSource: DataSource): Promise<SkipEntityInfo> {
1728
+ protected async PackSingleSkipEntityInfo(e: EntityInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityInfo> {
1727
1729
  try {
1728
1730
  const ret: SkipEntityInfo = {
1729
1731
  id: e.ID,
@@ -1794,7 +1796,7 @@ cycle.`);
1794
1796
  * @param dataSource Database connection
1795
1797
  * @returns Packaged field information
1796
1798
  */
1797
- protected async PackSingleSkipEntityField(f: EntityFieldInfo, dataSource: DataSource): Promise<SkipEntityFieldInfo> {
1799
+ protected async PackSingleSkipEntityField(f: EntityFieldInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityFieldInfo> {
1798
1800
  try {
1799
1801
  return {
1800
1802
  //id: f.ID,
@@ -1848,7 +1850,7 @@ cycle.`);
1848
1850
  * @returns Object containing loaded entities and contexts
1849
1851
  */
1850
1852
  protected async HandleSkipChatInitialObjectLoading(
1851
- dataSource: DataSource,
1853
+ dataSource: mssql.ConnectionPool,
1852
1854
  ConversationId: string,
1853
1855
  UserQuestion: string,
1854
1856
  user: UserInfo,
@@ -1985,7 +1987,7 @@ cycle.`);
1985
1987
  * @returns Array of messages in Skip format
1986
1988
  */
1987
1989
  protected async LoadConversationDetailsIntoSkipMessages(
1988
- dataSource: DataSource,
1990
+ dataSource: mssql.ConnectionPool,
1989
1991
  ConversationId: string,
1990
1992
  maxHistoricalMessages?: number,
1991
1993
  roleFilter?: string
@@ -2010,14 +2012,15 @@ cycle.`);
2010
2012
  ConversationID = '${ConversationId}'${roleFilterClause}
2011
2013
  ORDER
2012
2014
  BY __mj_CreatedAt DESC`;
2013
- const result = await dataSource.query(sql);
2014
- if (!result)
2015
+ const request = new mssql.Request(dataSource);
2016
+ const result = await request.query(sql);
2017
+ if (!result || !result.recordset)
2015
2018
  throw new Error(`Error running SQL: ${sql}`);
2016
2019
  else {
2017
2020
  // first, let's sort the result array into a local variable called returnData and in that we will sort by __mj_CreatedAt in ASCENDING order so we have the right chronological order
2018
2021
  // the reason we're doing a LOCAL sort here is because in the SQL query above, we're sorting in DESCENDING order so we can use the TOP clause to limit the number of records and get the
2019
2022
  // N most recent records. We want to sort in ASCENDING order because we want to send the messages to the Skip API in the order they were created.
2020
- const returnData = result.sort((a: any, b: any) => {
2023
+ const returnData = result.recordset.sort((a: any, b: any) => {
2021
2024
  const aDate = new Date(a.__mj_CreatedAt);
2022
2025
  const bDate = new Date(b.__mj_CreatedAt);
2023
2026
  return aDate.getTime() - bDate.getTime();
@@ -2123,7 +2126,7 @@ cycle.`);
2123
2126
  input: SkipAPIRequest,
2124
2127
  UserQuestion: string,
2125
2128
  user: UserInfo,
2126
- dataSource: DataSource,
2129
+ dataSource: mssql.ConnectionPool,
2127
2130
  ConversationId: string,
2128
2131
  userPayload: UserPayload,
2129
2132
  pubSub: PubSubEngine,
@@ -2371,7 +2374,7 @@ cycle.`);
2371
2374
  apiResponse: SkipAPIAnalysisCompleteResponse,
2372
2375
  UserQuestion: string,
2373
2376
  user: UserInfo,
2374
- dataSource: DataSource,
2377
+ dataSource: mssql.ConnectionPool,
2375
2378
  ConversationId: string,
2376
2379
  userPayload: UserPayload,
2377
2380
  pubSub: PubSubEngine,
@@ -2435,7 +2438,7 @@ cycle.`);
2435
2438
  apiResponse: SkipAPIClarifyingQuestionResponse,
2436
2439
  UserQuestion: string,
2437
2440
  user: UserInfo,
2438
- dataSource: DataSource,
2441
+ dataSource: mssql.ConnectionPool,
2439
2442
  ConversationId: string,
2440
2443
  userPayload: UserPayload,
2441
2444
  pubSub: PubSubEngine,
@@ -2509,7 +2512,7 @@ cycle.`);
2509
2512
  apiResponse: SkipAPIDataRequestResponse,
2510
2513
  UserQuestion: string,
2511
2514
  user: UserInfo,
2512
- dataSource: DataSource,
2515
+ dataSource: mssql.ConnectionPool,
2513
2516
  ConversationId: string,
2514
2517
  userPayload: UserPayload,
2515
2518
  pubSub: PubSubEngine,
@@ -2681,7 +2684,7 @@ cycle.`);
2681
2684
  convoEntity: ConversationEntity,
2682
2685
  pubSub: PubSubEngine,
2683
2686
  userPayload: UserPayload,
2684
- dataSource: DataSource,
2687
+ dataSource: mssql.ConnectionPool,
2685
2688
  startTime: Date
2686
2689
  ): Promise<{ AIMessageConversationDetailID: string }> {
2687
2690
  const sTitle = apiResponse.title || apiResponse.reportTitle;
@@ -2722,9 +2725,10 @@ cycle.`);
2722
2725
  const ei = md.EntityByName("MJ: Conversation Artifact Versions");
2723
2726
  const sSQL = `SELECT ISNULL(MAX(Version),0) AS MaxVersion FROM [${ei.SchemaName}].[${ei.BaseView}] WHERE ConversationArtifactID = '${artifactId}'`;
2724
2727
  try {
2725
- const result = await dataSource.query(sSQL);
2726
- if (result && result.length > 0) {
2727
- newVersion = result[0].MaxVersion + 1;
2728
+ const request = new mssql.Request(dataSource);
2729
+ const result = await request.query(sSQL);
2730
+ if (result && result.recordset && result.recordset.length > 0) {
2731
+ newVersion = result.recordset[0].MaxVersion + 1;
2728
2732
  }
2729
2733
  else {
2730
2734
  LogError(`Error getting max version for artifact ID: ${artifactId}`, undefined, result);
@@ -2,11 +2,12 @@ import { EntityPermissionType } from '@memberjunction/core';
2
2
  import { AppContext } from '../types.js';
3
3
  import { Arg, Ctx, Query, Resolver, InputType, Field } from 'type-graphql';
4
4
  import { Entity_, EntityResolverBase } from '../generated/generated.js';
5
+ import sql from 'mssql';
5
6
 
6
7
  @Resolver(Entity_)
7
8
  export class EntityResolver extends EntityResolverBase {
8
9
  @Query(() => [Entity_])
9
- EntitiesBySchemas(
10
+ async EntitiesBySchemas(
10
11
  @Ctx() { dataSource, userPayload }: AppContext,
11
12
  @Arg('IncludeSchemas', () => [String], { nullable: true }) IncludeSchemas?: string[],
12
13
  @Arg('ExcludeSchemas', () => [String], { nullable: true }) ExcludeSchemas?: string[]
@@ -30,6 +31,8 @@ export class EntityResolver extends EntityResolverBase {
30
31
  else totalWhere = ` WHERE ${rlsWhere}`;
31
32
  }
32
33
  const sSQL = `SELECT * FROM [${this.MJCoreSchema}].vwEntities${totalWhere}`;
33
- return dataSource.query(sSQL);
34
+ const request = new sql.Request(dataSource);
35
+ const result = await request.query(sSQL);
36
+ return result.recordset;
34
37
  }
35
38
  }
@@ -3,6 +3,7 @@ import { FileCategoryEntity, FileEntity } from '@memberjunction/core-entities';
3
3
  import { AppContext, Arg, Ctx, DeleteOptionsInput, Int, Mutation } from '@memberjunction/server';
4
4
  import { mj_core_schema } from '../config.js';
5
5
  import { FileCategoryResolver as FileCategoryResolverBase, FileCategory_ } from '../generated/generated.js';
6
+ import sql from 'mssql';
6
7
 
7
8
  export class FileResolver extends FileCategoryResolverBase {
8
9
  @Mutation(() => FileCategory_)
@@ -29,7 +30,9 @@ export class FileResolver extends FileCategoryResolverBase {
29
30
  const returnValue = fileCategoryEntity.GetAll();
30
31
 
31
32
  // Any files using the deleted category fall back to its parent
32
- await dataSource.transaction(async () => {
33
+ const transaction = new sql.Transaction(dataSource);
34
+ await transaction.begin();
35
+ try {
33
36
  // SHOULD USE BaseEntity for each of these records to ensure object model
34
37
  // is used everywhere - new code below. The below is SLOWER than a single
35
38
  // Update statement, but it ensures that the object model is used everywhere
@@ -61,7 +64,11 @@ export class FileResolver extends FileCategoryResolverBase {
61
64
  }
62
65
  }
63
66
  await fileCategoryEntity.Delete();
64
- });
67
+ await transaction.commit();
68
+ } catch (error) {
69
+ await transaction.rollback();
70
+ throw error;
71
+ }
65
72
 
66
73
  await this.AfterDelete(dataSource, key); // fire event
67
74
  return returnValue;
@@ -4,6 +4,7 @@ import { LogError, LogStatus, Metadata } from '@memberjunction/core';
4
4
  import { RequireSystemUser } from '../directives/RequireSystemUser.js';
5
5
  import { v4 as uuidv4 } from 'uuid';
6
6
  import { GetReadOnlyDataSource } from '../util.js';
7
+ import sql from 'mssql';
7
8
 
8
9
  @InputType()
9
10
  export class GetDataInputType {
@@ -132,8 +133,9 @@ export class GetDataResolver {
132
133
  const results = await Promise.allSettled(
133
134
  input.Queries.map(async (query) => {
134
135
  try {
135
- const result = await readOnlyDataSource.query(query);
136
- return { result, error: null };
136
+ const request = new sql.Request(readOnlyDataSource);
137
+ const result = await request.query(query);
138
+ return { result: result.recordset, error: null };
137
139
  } catch (err) {
138
140
  return { result: null, error: err };
139
141
  }