@memberjunction/server 5.34.0 → 5.34.1

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.
@@ -110,6 +110,9 @@ export class CreateQuerySystemUserInput {
110
110
 
111
111
  @Field(() => [QueryParameterHintInput], { nullable: true })
112
112
  ParameterHints?: QueryParameterHintInput[];
113
+
114
+ @Field(() => Boolean, { nullable: true, defaultValue: false })
115
+ AutoResolveCollision?: boolean;
113
116
  }
114
117
 
115
118
  @InputType()
@@ -250,18 +253,24 @@ export class MJQueryResolverExtended extends MJQueryResolver {
250
253
  const existingQuery = await this.findExistingQuery(provider, input.Name, finalCategoryID, context.userPayload.userRecord);
251
254
 
252
255
  if (existingQuery) {
253
- const categoryInfo = input.CategoryPath ? `category path '${input.CategoryPath}'` : `category ID '${finalCategoryID}'`;
254
- return {
255
- Success: false,
256
- ErrorMessage: `Query with name '${input.Name}' already exists in ${categoryInfo}`
257
- };
256
+ if (input.AutoResolveCollision) {
257
+ // Server-side collision resolution: find a unique name using sequential numeric suffixes
258
+ input.Name = await this.findUniqueName(provider, input.Name, finalCategoryID, context.userPayload.userRecord);
259
+ LogStatus(`[CreateQuery] Auto-resolved name collision: "${existingQuery.Name}" -> "${input.Name}"`);
260
+ } else {
261
+ const categoryInfo = input.CategoryPath ? `category path '${input.CategoryPath}'` : `category ID '${finalCategoryID}'`;
262
+ return {
263
+ Success: false,
264
+ ErrorMessage: `Query with name '${input.Name}' already exists in ${categoryInfo}`
265
+ };
266
+ }
258
267
  }
259
268
 
260
269
  // Use MJQueryEntityServer which handles AI processing
261
270
  const record = await provider.GetEntityObject<MJQueryEntityServer>("MJ: Queries", context.userPayload.userRecord);
262
271
 
263
272
  // Destructure out non-database fields, keep only fields to persist
264
- const { Permissions: _permissions, CategoryPath: _categoryPath, ParameterHints: _parameterHints, ...fieldsToSet } = {
273
+ const { Permissions: _permissions, CategoryPath: _categoryPath, ParameterHints: _parameterHints, AutoResolveCollision: _autoResolve, ...fieldsToSet } = {
265
274
  ...input,
266
275
  CategoryID: finalCategoryID || input.CategoryID,
267
276
  Status: input.Status || 'Approved',
@@ -734,6 +743,33 @@ export class MJQueryResolverExtended extends MJQueryResolver {
734
743
  }
735
744
  }
736
745
 
746
+ /**
747
+ * Finds a unique query name by appending sequential numeric suffixes.
748
+ * Format: "Name (1)", "Name (2)", etc.
749
+ * Uses direct DB queries (not cache) for authoritative uniqueness checks.
750
+ * @param provider - Database provider for direct DB queries
751
+ * @param baseName - The original desired name that is already taken
752
+ * @param categoryID - Category ID to check uniqueness within
753
+ * @param contextUser - User context for database operations
754
+ * @returns A unique name in the format "baseName (N)"
755
+ */
756
+ private async findUniqueName(
757
+ provider: DatabaseProviderBase,
758
+ baseName: string,
759
+ categoryID: string | null,
760
+ contextUser: UserInfo
761
+ ): Promise<string> {
762
+ const MAX_ATTEMPTS = 50;
763
+ for (let i = 1; i <= MAX_ATTEMPTS; i++) {
764
+ const candidateName = `${baseName} (${i})`;
765
+ const existing = await this.findExistingQuery(provider, candidateName, categoryID, contextUser);
766
+ if (!existing) {
767
+ return candidateName;
768
+ }
769
+ }
770
+ throw new Error(`Unable to find unique name for "${baseName}" after ${MAX_ATTEMPTS} attempts`);
771
+ }
772
+
737
773
  /**
738
774
  * Finds a category by name and parent ID using RunView.
739
775
  * Bypasses metadata cache to ensure we get the latest data from database.