@memberjunction/server 5.13.0 → 5.15.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 (37) hide show
  1. package/dist/agents/skip-sdk.d.ts +8 -0
  2. package/dist/agents/skip-sdk.d.ts.map +1 -1
  3. package/dist/agents/skip-sdk.js +19 -0
  4. package/dist/agents/skip-sdk.js.map +1 -1
  5. package/dist/config.d.ts +37 -0
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +8 -0
  8. package/dist/config.js.map +1 -1
  9. package/dist/generated/generated.d.ts +169 -0
  10. package/dist/generated/generated.d.ts.map +1 -1
  11. package/dist/generated/generated.js +909 -1
  12. package/dist/generated/generated.js.map +1 -1
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  18. package/dist/resolvers/GetDataResolver.js +2 -1
  19. package/dist/resolvers/GetDataResolver.js.map +1 -1
  20. package/dist/resolvers/{CreateQueryResolver.d.ts → QuerySystemUserResolver.d.ts} +26 -82
  21. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -0
  22. package/dist/resolvers/{CreateQueryResolver.js → QuerySystemUserResolver.js} +123 -486
  23. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -0
  24. package/dist/resolvers/TestQuerySQLResolver.d.ts +54 -0
  25. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -0
  26. package/dist/resolvers/TestQuerySQLResolver.js +189 -0
  27. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -0
  28. package/package.json +59 -59
  29. package/src/agents/skip-sdk.ts +22 -0
  30. package/src/config.ts +8 -0
  31. package/src/generated/generated.ts +635 -2
  32. package/src/index.ts +2 -1
  33. package/src/resolvers/GetDataResolver.ts +2 -1
  34. package/src/resolvers/{CreateQueryResolver.ts → QuerySystemUserResolver.ts} +143 -413
  35. package/src/resolvers/TestQuerySQLResolver.ts +149 -0
  36. package/dist/resolvers/CreateQueryResolver.d.ts.map +0 -1
  37. package/dist/resolvers/CreateQueryResolver.js.map +0 -1
@@ -1,13 +1,27 @@
1
1
  import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType, Resolver, PubSub, PubSubEngine } from 'type-graphql';
2
2
  import { AppContext } from '../types.js';
3
- import { LogError, RunView, UserInfo, CompositeKey, DatabaseProviderBase, LogStatus } from '@memberjunction/core';
3
+ import { LogError, RunView, UserInfo, CompositeKey, DatabaseProviderBase, LogStatus, QueryFieldInfo, QueryParameterInfo, QueryEntityInfo, QueryPermissionInfo } from '@memberjunction/core';
4
4
  import { RequireSystemUser } from '../directives/RequireSystemUser.js';
5
5
  import { MJQueryCategoryEntity, MJQueryPermissionEntity } from '@memberjunction/core-entities';
6
- import { MJQueryResolver } from '../generated/generated.js';
7
- import { GetReadOnlyProvider, GetReadWriteProvider } from '../util.js';
6
+ import { MJQueryResolver, MJQuery_, MJQueryField_, MJQueryParameter_, MJQueryEntity_, MJQueryPermission_ } from '../generated/generated.js';
7
+ import { GetReadWriteProvider } from '../util.js';
8
8
  import { DeleteOptionsInput } from '../generic/DeleteOptionsInput.js';
9
9
  import { MJQueryEntityServer } from '@memberjunction/core-entities-server';
10
10
 
11
+ /**
12
+ * Minimal shape of a query row returned by RunView lookups (plain object, not entity instance).
13
+ */
14
+ interface QueryViewRow {
15
+ ID: string;
16
+ Name: string;
17
+ Description: string;
18
+ CategoryID: string;
19
+ Category: string;
20
+ SQL: string;
21
+ Status: string;
22
+ QualityRank: number;
23
+ }
24
+
11
25
  /**
12
26
  * Query status enumeration for GraphQL
13
27
  */
@@ -146,227 +160,24 @@ export class UpdateQuerySystemUserInput {
146
160
  Permissions?: QueryPermissionInputType[];
147
161
  }
148
162
 
163
+ /**
164
+ * Consolidated result type for Create and Update query mutations.
165
+ * Composes the CodeGen-generated MJQuery_ type so that new entity fields
166
+ * are automatically available in the GraphQL schema without manual sync.
167
+ *
168
+ * On success, Query contains the full query data (scalars + related entities).
169
+ * On failure, Query is null and ErrorMessage describes the problem.
170
+ */
149
171
  @ObjectType()
150
- export class QueryFieldType {
151
- @Field(() => String)
152
- ID!: string;
153
-
154
- @Field(() => String)
155
- QueryID!: string;
156
-
157
- @Field(() => String)
158
- Name!: string;
159
-
160
- @Field(() => String, { nullable: true })
161
- Description?: string;
162
-
163
- @Field(() => Number)
164
- Sequence!: number;
165
-
166
- @Field(() => String, { nullable: true })
167
- SQLBaseType?: string;
168
-
169
- @Field(() => String, { nullable: true })
170
- SQLFullType?: string;
171
-
172
- @Field(() => String, { nullable: true })
173
- SourceEntityID?: string;
174
-
175
- @Field(() => String, { nullable: true })
176
- SourceEntity?: string;
177
-
178
- @Field(() => String, { nullable: true })
179
- SourceFieldName?: string;
180
-
181
- @Field(() => Boolean)
182
- IsComputed!: boolean;
183
-
184
- @Field(() => String, { nullable: true })
185
- ComputationDescription?: string;
186
-
187
- @Field(() => Boolean, { nullable: true })
188
- IsSummary?: boolean;
189
-
190
- @Field(() => String, { nullable: true })
191
- SummaryDescription?: string;
192
- }
193
-
194
- @ObjectType()
195
- export class QueryParameterType {
196
- @Field(() => String)
197
- ID!: string;
198
-
199
- @Field(() => String)
200
- QueryID!: string;
201
-
202
- @Field(() => String)
203
- Name!: string;
204
-
205
- @Field(() => String, { nullable: true })
206
- Description?: string;
207
-
208
- @Field(() => String)
209
- Type!: string;
210
-
211
- @Field(() => Boolean)
212
- IsRequired!: boolean;
213
-
214
- @Field(() => String, { nullable: true })
215
- DefaultValue?: string;
216
-
217
- @Field(() => String, { nullable: true })
218
- SampleValue?: string;
219
-
220
- @Field(() => String, { nullable: true })
221
- ValidationFilters?: string;
222
- }
223
-
224
- @ObjectType()
225
- export class MJQueryEntityType {
226
- @Field(() => String)
227
- ID!: string;
228
-
229
- @Field(() => String)
230
- QueryID!: string;
231
-
232
- @Field(() => String)
233
- EntityID!: string;
234
-
235
- @Field(() => String, { nullable: true })
236
- Entity?: string;
237
- }
238
-
239
- @ObjectType()
240
- export class QueryPermissionType {
241
- @Field(() => String)
242
- ID!: string;
243
-
244
- @Field(() => String)
245
- QueryID!: string;
246
-
247
- @Field(() => String)
248
- RoleID!: string;
249
-
250
- @Field(() => String, { nullable: true })
251
- Role?: string;
252
- }
253
-
254
- @ObjectType()
255
- export class CreateQueryResultType {
256
- @Field(() => Boolean)
257
- Success!: boolean;
258
-
259
- @Field(() => String, { nullable: true })
260
- ErrorMessage?: string;
261
-
262
- // Core query properties
263
- @Field(() => String, { nullable: true })
264
- ID?: string;
265
-
266
- @Field(() => String, { nullable: true })
267
- Name?: string;
268
-
269
- @Field(() => String, { nullable: true })
270
- Description?: string;
271
-
272
- @Field(() => String, { nullable: true })
273
- CategoryID?: string;
274
-
275
- @Field(() => String, { nullable: true })
276
- Category?: string;
277
-
278
- @Field(() => String, { nullable: true })
279
- SQL?: string;
280
-
281
- @Field(() => String, { nullable: true })
282
- Status?: string;
283
-
284
- @Field(() => Number, { nullable: true })
285
- QualityRank?: number;
286
-
287
- @Field(() => String, { nullable: true })
288
- EmbeddingVector?: string;
289
-
290
- @Field(() => String, { nullable: true })
291
- EmbeddingModelID?: string;
292
-
293
- @Field(() => String, { nullable: true })
294
- EmbeddingModelName?: string;
295
-
296
- @Field(() => String, { nullable: true })
297
- TechnicalDescription?: string;
298
-
299
- // Related collections
300
- @Field(() => [QueryFieldType], { nullable: true })
301
- Fields?: QueryFieldType[];
302
-
303
- @Field(() => [QueryParameterType], { nullable: true })
304
- Parameters?: QueryParameterType[];
305
-
306
- @Field(() => [MJQueryEntityType], { nullable: true })
307
- Entities?: MJQueryEntityType[];
308
-
309
- @Field(() => [QueryPermissionType], { nullable: true })
310
- Permissions?: QueryPermissionType[];
311
- }
312
-
313
- @ObjectType()
314
- export class UpdateQueryResultType {
172
+ export class QueryMutationResultType {
315
173
  @Field(() => Boolean)
316
174
  Success!: boolean;
317
175
 
318
176
  @Field(() => String, { nullable: true })
319
177
  ErrorMessage?: string;
320
178
 
321
- // Core query properties
322
- @Field(() => String, { nullable: true })
323
- ID?: string;
324
-
325
- @Field(() => String, { nullable: true })
326
- Name?: string;
327
-
328
- @Field(() => String, { nullable: true })
329
- Description?: string;
330
-
331
- @Field(() => String, { nullable: true })
332
- CategoryID?: string;
333
-
334
- @Field(() => String, { nullable: true })
335
- Category?: string;
336
-
337
- @Field(() => String, { nullable: true })
338
- SQL?: string;
339
-
340
- @Field(() => String, { nullable: true })
341
- Status?: string;
342
-
343
- @Field(() => Number, { nullable: true })
344
- QualityRank?: number;
345
-
346
- @Field(() => String, { nullable: true })
347
- EmbeddingVector?: string;
348
-
349
- @Field(() => String, { nullable: true })
350
- EmbeddingModelID?: string;
351
-
352
- @Field(() => String, { nullable: true })
353
- EmbeddingModelName?: string;
354
-
355
- @Field(() => String, { nullable: true })
356
- TechnicalDescription?: string;
357
-
358
- // Related collections
359
- @Field(() => [QueryFieldType], { nullable: true })
360
- Fields?: QueryFieldType[];
361
-
362
- @Field(() => [QueryParameterType], { nullable: true })
363
- Parameters?: QueryParameterType[];
364
-
365
- @Field(() => [MJQueryEntityType], { nullable: true })
366
- Entities?: MJQueryEntityType[];
367
-
368
- @Field(() => [QueryPermissionType], { nullable: true })
369
- Permissions?: QueryPermissionType[];
179
+ @Field(() => MJQuery_, { nullable: true })
180
+ Query?: MJQuery_;
370
181
  }
371
182
 
372
183
  @ObjectType()
@@ -406,12 +217,12 @@ export class MJQueryResolverExtended extends MJQueryResolver {
406
217
  * @returns CreateQueryResultType with success status and query data
407
218
  */
408
219
  @RequireSystemUser()
409
- @Mutation(() => CreateQueryResultType)
220
+ @Mutation(() => QueryMutationResultType)
410
221
  async CreateQuerySystemUser(
411
222
  @Arg('input', () => CreateQuerySystemUserInput) input: CreateQuerySystemUserInput,
412
223
  @Ctx() context: AppContext,
413
224
  @PubSub() pubSub: PubSubEngine
414
- ): Promise<CreateQueryResultType> {
225
+ ): Promise<QueryMutationResultType> {
415
226
  try {
416
227
  // Handle CategoryPath if provided
417
228
  let finalCategoryID = input.CategoryID;
@@ -434,8 +245,8 @@ export class MJQueryResolverExtended extends MJQueryResolver {
434
245
  // Use MJQueryEntityServer which handles AI processing
435
246
  const record = await provider.GetEntityObject<MJQueryEntityServer>("MJ: Queries", context.userPayload.userRecord);
436
247
 
437
- // Set the fields from input, handling CategoryPath resolution
438
- const fieldsToSet = {
248
+ // Destructure out non-database fields, keep only fields to persist
249
+ const { Permissions: _permissions, CategoryPath: _categoryPath, ...fieldsToSet } = {
439
250
  ...input,
440
251
  CategoryID: finalCategoryID || input.CategoryID,
441
252
  Status: input.Status || 'Approved',
@@ -446,9 +257,6 @@ export class MJQueryResolverExtended extends MJQueryResolver {
446
257
  CacheTTLMinutes: input.CacheTTLMinutes || null,
447
258
  CacheMaxSize: input.CacheMaxSize || null
448
259
  };
449
- // Remove non-database fields that we handle separately or are input-only
450
- delete (fieldsToSet as any).Permissions; // Handled separately via createPermissions
451
- delete (fieldsToSet as any).CategoryPath; // Input-only field, resolved to CategoryID
452
260
 
453
261
  record.SetMany(fieldsToSet, true);
454
262
  this.ListenForEntityMessages(record, pubSub, context.userPayload.userRecord);
@@ -470,60 +278,7 @@ export class MJQueryResolverExtended extends MJQueryResolver {
470
278
  // This ensures subsequent operations can find the query without additional DB calls
471
279
  await provider.Refresh();
472
280
 
473
- return {
474
- Success: true,
475
- ID: record.ID,
476
- Name: record.Name,
477
- Description: record.Description,
478
- CategoryID: record.CategoryID,
479
- Category: record.Category,
480
- SQL: record.SQL,
481
- Status: record.Status,
482
- QualityRank: record.QualityRank,
483
- EmbeddingVector: record.EmbeddingVector,
484
- EmbeddingModelID: record.EmbeddingModelID,
485
- EmbeddingModelName: record.EmbeddingModel,
486
- TechnicalDescription: record.TechnicalDescription,
487
- Fields: record.QueryFields.map(f => ({
488
- ID: f.ID,
489
- QueryID: f.QueryID,
490
- Name: f.Name,
491
- Description: f.Description,
492
- Sequence: f.Sequence,
493
- SQLBaseType: f.SQLBaseType,
494
- SQLFullType: f.SQLFullType,
495
- SourceEntityID: f.SourceEntityID,
496
- SourceEntity: f.SourceEntity,
497
- SourceFieldName: f.SourceFieldName,
498
- IsComputed: f.IsComputed,
499
- ComputationDescription: f.ComputationDescription,
500
- IsSummary: f.IsSummary,
501
- SummaryDescription: f.SummaryDescription
502
- })),
503
- Parameters: record.QueryParameters.map(p => ({
504
- ID: p.ID,
505
- QueryID: p.QueryID,
506
- Name: p.Name,
507
- Description: p.Description,
508
- Type: p.Type,
509
- IsRequired: p.IsRequired,
510
- DefaultValue: p.DefaultValue,
511
- SampleValue: p.SampleValue,
512
- ValidationFilters: p.ValidationFilters
513
- })),
514
- Entities: record.QueryEntities.map(e => ({
515
- ID: e.ID,
516
- QueryID: e.QueryID,
517
- EntityID: e.EntityID,
518
- Entity: e.Entity
519
- })),
520
- Permissions: record.QueryPermissions.map(p => ({
521
- ID: p.ID,
522
- QueryID: p.QueryID,
523
- RoleID: p.RoleID,
524
- Role: p.Role
525
- }))
526
- };
281
+ return this.buildSuccessResult(record);
527
282
  }
528
283
  else {
529
284
  // Save failed - check if another request created the same query (race condition)
@@ -531,62 +286,17 @@ export class MJQueryResolverExtended extends MJQueryResolver {
531
286
  const existingQuery = await this.findExistingQuery(provider, input.Name, finalCategoryID, context.userPayload.userRecord);
532
287
 
533
288
  if (existingQuery) {
534
- // Found the query that was created by another request
535
- // Return it as if we created it (it has the same name/category)
289
+ // Found the query that was created by another request — load it as a full entity
290
+ // so that related metadata (Fields, Parameters, Entities, Permissions) is populated
536
291
  LogStatus(`[CreateQuery] Unique constraint detected for query '${input.Name}'. Using existing query (ID: ${existingQuery.ID}) created by concurrent request.`);
292
+ const existingEntity = await provider.GetEntityObject<MJQueryEntityServer>('MJ: Queries', context.userPayload.userRecord);
293
+ if (await existingEntity.Load(existingQuery.ID)) {
294
+ return this.buildSuccessResult(existingEntity);
295
+ }
296
+ // Entity load failed after confirming the row exists — extremely rare
537
297
  return {
538
- Success: true,
539
- ID: existingQuery.ID,
540
- Name: existingQuery.Name,
541
- Description: existingQuery.Description,
542
- CategoryID: existingQuery.CategoryID,
543
- Category: existingQuery.Category,
544
- SQL: existingQuery.SQL,
545
- Status: existingQuery.Status,
546
- QualityRank: existingQuery.QualityRank,
547
- EmbeddingVector: existingQuery.EmbeddingVector,
548
- EmbeddingModelID: existingQuery.EmbeddingModelID,
549
- EmbeddingModelName: existingQuery.EmbeddingModel,
550
- TechnicalDescription: existingQuery.TechnicalDescription,
551
- Fields: existingQuery.Fields?.map((f: any) => ({
552
- ID: f.ID,
553
- QueryID: f.QueryID,
554
- Name: f.Name,
555
- Description: f.Description,
556
- Sequence: f.Sequence,
557
- SQLBaseType: f.SQLBaseType,
558
- SQLFullType: f.SQLFullType,
559
- SourceEntityID: f.SourceEntityID,
560
- SourceEntity: f.SourceEntity,
561
- SourceFieldName: f.SourceFieldName,
562
- IsComputed: f.IsComputed,
563
- ComputationDescription: f.ComputationDescription,
564
- IsSummary: f.IsSummary,
565
- SummaryDescription: f.SummaryDescription
566
- })) || [],
567
- Parameters: existingQuery.Parameters?.map((p: any) => ({
568
- ID: p.ID,
569
- QueryID: p.QueryID,
570
- Name: p.Name,
571
- Description: p.Description,
572
- Type: p.Type,
573
- IsRequired: p.IsRequired,
574
- DefaultValue: p.DefaultValue,
575
- SampleValue: p.SampleValue,
576
- ValidationFilters: p.ValidationFilters
577
- })) || [],
578
- Entities: existingQuery.Entities?.map((e: any) => ({
579
- ID: e.ID,
580
- QueryID: e.QueryID,
581
- EntityID: e.EntityID,
582
- Entity: e.Entity
583
- })) || [],
584
- Permissions: existingQuery.Permissions?.map((p: any) => ({
585
- ID: p.ID,
586
- QueryID: p.QueryID,
587
- RoleID: p.RoleID,
588
- Role: p.Role
589
- })) || []
298
+ Success: false,
299
+ ErrorMessage: `Found query '${input.Name}' created by concurrent request (ID: ${existingQuery.ID}) but failed to load it as entity`
590
300
  };
591
301
  }
592
302
 
@@ -607,29 +317,102 @@ export class MJQueryResolverExtended extends MJQueryResolver {
607
317
  }
608
318
  }
609
319
 
610
- protected async createPermissions(p: DatabaseProviderBase, permissions: QueryPermissionInputType[], queryID: string, contextUser: UserInfo): Promise<QueryPermissionType[]> {
611
- // Create permissions if provided
612
- const createdPermissions: QueryPermissionType[] = [];
613
- if (permissions && permissions.length > 0) {
614
- for (const perm of permissions) {
615
- const permissionEntity = await p.GetEntityObject<MJQueryPermissionEntity>('MJ: Query Permissions', contextUser);
616
- if (permissionEntity) {
617
- permissionEntity.QueryID = queryID;
618
- permissionEntity.RoleID = perm.RoleID;
619
-
620
- const saveResult = await permissionEntity.Save();
621
- if (saveResult) {
622
- createdPermissions.push({
623
- ID: permissionEntity.ID,
624
- QueryID: permissionEntity.QueryID,
625
- RoleID: permissionEntity.RoleID,
626
- Role: permissionEntity.Role
627
- });
628
- }
629
- }
320
+ /**
321
+ * Maps an MJQueryEntityServer (with loaded related metadata) to a QueryMutationResultType.
322
+ * Uses entity.GetAll() for scalar fields so that new CodeGen-generated fields are included
323
+ * automatically without manual updates. Related entity arrays are mapped explicitly.
324
+ */
325
+ private buildSuccessResult(entity: MJQueryEntityServer): QueryMutationResultType {
326
+ return {
327
+ Success: true,
328
+ Query: {
329
+ ...entity.GetAll(),
330
+ MJQueryFields_QueryIDArray: this.mapFields(entity.QueryFields),
331
+ MJQueryParameters_QueryIDArray: this.mapParameters(entity.QueryParameters),
332
+ MJQueryEntities_QueryIDArray: this.mapEntities(entity.QueryEntities),
333
+ MJQueryPermissions_QueryIDArray: this.mapPermissions(entity.QueryPermissions),
334
+ } as MJQuery_
335
+ };
336
+ }
337
+
338
+ private mapFields(fields: QueryFieldInfo[]): MJQueryField_[] {
339
+ return fields.map(f => ({
340
+ ID: f.ID,
341
+ QueryID: f.QueryID,
342
+ Name: f.Name,
343
+ Description: f.Description,
344
+ Sequence: f.Sequence,
345
+ SQLBaseType: f.SQLBaseType,
346
+ SQLFullType: f.SQLFullType,
347
+ SourceEntityID: f.SourceEntityID,
348
+ SourceEntity: f.SourceEntity,
349
+ SourceFieldName: f.SourceFieldName,
350
+ IsComputed: f.IsComputed,
351
+ ComputationDescription: f.ComputationDescription,
352
+ IsSummary: f.IsSummary,
353
+ SummaryDescription: f.SummaryDescription,
354
+ _mj__CreatedAt: f.__mj_CreatedAt,
355
+ _mj__UpdatedAt: f.__mj_UpdatedAt,
356
+ DetectionMethod: f.DetectionMethod,
357
+ AutoDetectConfidenceScore: f.AutoDetectConfidenceScore,
358
+ Query: '',
359
+ }) as MJQueryField_);
360
+ }
361
+
362
+ private mapParameters(params: QueryParameterInfo[]): MJQueryParameter_[] {
363
+ return params.map(p => ({
364
+ ID: p.ID,
365
+ QueryID: p.QueryID,
366
+ Name: p.Name,
367
+ Description: p.Description,
368
+ Type: p.Type,
369
+ IsRequired: p.IsRequired,
370
+ DefaultValue: p.DefaultValue,
371
+ SampleValue: p.SampleValue,
372
+ ValidationFilters: p.ValidationFilters,
373
+ _mj__CreatedAt: p.__mj_CreatedAt,
374
+ _mj__UpdatedAt: p.__mj_UpdatedAt,
375
+ DetectionMethod: p.DetectionMethod,
376
+ AutoDetectConfidenceScore: p.AutoDetectConfidenceScore,
377
+ Query: p.Query ?? '',
378
+ }) as MJQueryParameter_);
379
+ }
380
+
381
+ private mapEntities(entities: QueryEntityInfo[]): MJQueryEntity_[] {
382
+ return entities.map(e => ({
383
+ ID: e.ID,
384
+ QueryID: e.QueryID,
385
+ EntityID: e.EntityID,
386
+ Entity: e.Entity,
387
+ _mj__CreatedAt: e.__mj_CreatedAt,
388
+ _mj__UpdatedAt: e.__mj_UpdatedAt,
389
+ DetectionMethod: e.DetectionMethod,
390
+ AutoDetectConfidenceScore: e.AutoDetectConfidenceScore,
391
+ Query: e.Query ?? '',
392
+ }) as MJQueryEntity_);
393
+ }
394
+
395
+ private mapPermissions(permissions: QueryPermissionInfo[]): MJQueryPermission_[] {
396
+ return permissions.map(p => ({
397
+ ID: p.ID,
398
+ QueryID: p.QueryID,
399
+ RoleID: p.RoleID,
400
+ Role: p.Role,
401
+ _mj__CreatedAt: new Date(),
402
+ _mj__UpdatedAt: new Date(),
403
+ Query: p.Query ?? '',
404
+ }) as MJQueryPermission_);
405
+ }
406
+
407
+ protected async createPermissions(p: DatabaseProviderBase, permissions: QueryPermissionInputType[], queryID: string, contextUser: UserInfo): Promise<void> {
408
+ for (const perm of permissions) {
409
+ const permissionEntity = await p.GetEntityObject<MJQueryPermissionEntity>('MJ: Query Permissions', contextUser);
410
+ if (permissionEntity) {
411
+ permissionEntity.QueryID = queryID;
412
+ permissionEntity.RoleID = perm.RoleID;
413
+ await permissionEntity.Save();
630
414
  }
631
415
  }
632
- return createdPermissions;
633
416
  }
634
417
 
635
418
  /**
@@ -639,12 +422,12 @@ export class MJQueryResolverExtended extends MJQueryResolver {
639
422
  * @returns UpdateQueryResultType with success status and updated query data including related entities
640
423
  */
641
424
  @RequireSystemUser()
642
- @Mutation(() => UpdateQueryResultType)
425
+ @Mutation(() => QueryMutationResultType)
643
426
  async UpdateQuerySystemUser(
644
427
  @Arg('input', () => UpdateQuerySystemUserInput) input: UpdateQuerySystemUserInput,
645
428
  @Ctx() context: AppContext,
646
429
  @PubSub() pubSub: PubSubEngine
647
- ): Promise<UpdateQueryResultType> {
430
+ ): Promise<QueryMutationResultType> {
648
431
  try {
649
432
  // Load the existing query using MJQueryEntityServer
650
433
  const provider = GetReadWriteProvider(context.providers);
@@ -676,7 +459,7 @@ export class MJQueryResolverExtended extends MJQueryResolver {
676
459
  }
677
460
 
678
461
  // Update fields that were provided
679
- const updateFields: Record<string, any> = {};
462
+ const updateFields: Record<string, string | number | boolean | undefined> = {};
680
463
  if (input.Name !== undefined) updateFields.Name = input.Name;
681
464
  if (finalCategoryID !== undefined) updateFields.CategoryID = finalCategoryID;
682
465
  if (input.UserQuestion !== undefined) updateFields.UserQuestion = input.UserQuestion;
@@ -731,60 +514,7 @@ export class MJQueryResolverExtended extends MJQueryResolver {
731
514
  await queryEntity.RefreshRelatedMetadata(true);
732
515
  }
733
516
 
734
- return {
735
- Success: true,
736
- ID: queryEntity.ID,
737
- Name: queryEntity.Name,
738
- Description: queryEntity.Description,
739
- CategoryID: queryEntity.CategoryID,
740
- Category: queryEntity.Category,
741
- SQL: queryEntity.SQL,
742
- Status: queryEntity.Status,
743
- QualityRank: queryEntity.QualityRank,
744
- EmbeddingVector: queryEntity.EmbeddingVector,
745
- EmbeddingModelID: queryEntity.EmbeddingModelID,
746
- EmbeddingModelName: queryEntity.EmbeddingModel,
747
- TechnicalDescription: queryEntity.TechnicalDescription,
748
- Fields: queryEntity.QueryFields.map(f => ({
749
- ID: f.ID,
750
- QueryID: f.QueryID,
751
- Name: f.Name,
752
- Description: f.Description,
753
- Sequence: f.Sequence,
754
- SQLBaseType: f.SQLBaseType,
755
- SQLFullType: f.SQLFullType,
756
- SourceEntityID: f.SourceEntityID,
757
- SourceEntity: f.SourceEntity,
758
- SourceFieldName: f.SourceFieldName,
759
- IsComputed: f.IsComputed,
760
- ComputationDescription: f.ComputationDescription,
761
- IsSummary: f.IsSummary,
762
- SummaryDescription: f.SummaryDescription
763
- })),
764
- Parameters: queryEntity.QueryParameters.map(p => ({
765
- ID: p.ID,
766
- QueryID: p.QueryID,
767
- Name: p.Name,
768
- Description: p.Description,
769
- Type: p.Type,
770
- IsRequired: p.IsRequired,
771
- DefaultValue: p.DefaultValue,
772
- SampleValue: p.SampleValue,
773
- ValidationFilters: p.ValidationFilters
774
- })),
775
- Entities: queryEntity.QueryEntities.map(e => ({
776
- ID: e.ID,
777
- QueryID: e.QueryID,
778
- EntityID: e.EntityID,
779
- Entity: e.Entity
780
- })),
781
- Permissions: queryEntity.QueryPermissions.map(p => ({
782
- ID: p.ID,
783
- QueryID: p.QueryID,
784
- RoleID: p.RoleID,
785
- Role: p.Role
786
- }))
787
- };
517
+ return this.buildSuccessResult(queryEntity);
788
518
 
789
519
  } catch (err) {
790
520
  LogError(err);
@@ -949,20 +679,20 @@ export class MJQueryResolverExtended extends MJQueryResolver {
949
679
  * @param queryName - Name of the query to find
950
680
  * @param categoryID - Category ID (can be null)
951
681
  * @param contextUser - User context for database operations
952
- * @returns The matching query info or null if not found
682
+ * @returns The matching query row or null if not found
953
683
  */
954
684
  private async findExistingQuery(
955
685
  provider: DatabaseProviderBase,
956
686
  queryName: string,
957
687
  categoryID: string | null,
958
688
  contextUser: UserInfo
959
- ): Promise<any | null> {
689
+ ): Promise<QueryViewRow | null> {
960
690
  try {
961
691
  // Query database directly to avoid cache staleness issues
962
692
  const categoryFilter = categoryID ? `CategoryID='${categoryID}'` : 'CategoryID IS NULL';
963
693
  const nameFilter = `LOWER(Name) = LOWER('${queryName.replace(/'/g, "''")}')`;
964
694
 
965
- const result = await provider.RunView({
695
+ const result = await provider.RunView<QueryViewRow>({
966
696
  EntityName: 'MJ: Queries',
967
697
  ExtraFilter: `${nameFilter} AND ${categoryFilter}`
968
698
  }, contextUser);