lancer-shared 1.2.329 → 1.2.330

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.
@@ -7826,6 +7826,7 @@ const SearchFieldsSchema = z.object({
7826
7826
  exactPhrase: z.string().default(''),
7827
7827
  titleSearch: z.string().default(''), // AND logic for titles
7828
7828
  titleAny: z.string().default(''), // OR logic for titles
7829
+ titleNoneAny: z.string().default(''), // NOT logic for titles
7829
7830
  skillsSearch: z.string().default(''),
7830
7831
  });
7831
7832
  class SearchQueryBuilder {
@@ -7897,6 +7898,14 @@ class SearchQueryBuilder {
7897
7898
  parts.push(`(${titleParts.join(' OR ')})`);
7898
7899
  }
7899
7900
  }
7901
+ if (validatedFields.titleNoneAny.trim()) {
7902
+ const titleTerms = this.splitTerms(validatedFields.titleNoneAny);
7903
+ const titleNotParts = titleTerms.map((term) => {
7904
+ const isQuoted = term.startsWith('"') && term.endsWith('"');
7905
+ return isQuoted ? `NOT title:${term}` : `NOT title:"${term}"`;
7906
+ });
7907
+ parts.push(...titleNotParts);
7908
+ }
7900
7909
  if (validatedFields.skillsSearch.trim()) {
7901
7910
  const skillsTerms = this.splitTerms(validatedFields.skillsSearch);
7902
7911
  if (skillsTerms.length === 1) {
@@ -7932,6 +7941,8 @@ class SearchQueryBuilder {
7932
7941
  descriptions.push(`Title (all): ${validatedFields.titleSearch}`);
7933
7942
  if (validatedFields.titleAny)
7934
7943
  descriptions.push(`Title (any): ${validatedFields.titleAny}`);
7944
+ if (validatedFields.titleNoneAny)
7945
+ descriptions.push(`Title (excluding any): ${validatedFields.titleNoneAny}`);
7935
7946
  if (validatedFields.skillsSearch)
7936
7947
  descriptions.push(`Skills: ${validatedFields.skillsSearch}`);
7937
7948
  return descriptions.length > 0 ? descriptions.join(' • ') : 'All results';
@@ -7961,6 +7972,7 @@ class SearchQueryBuilder {
7961
7972
  exactPhrase: '',
7962
7973
  titleSearch: '',
7963
7974
  titleAny: '',
7975
+ titleNoneAny: '',
7964
7976
  skillsSearch: '',
7965
7977
  };
7966
7978
  if (!query || query.trim() === '*') {
@@ -7993,7 +8005,35 @@ class SearchQueryBuilder {
7993
8005
  // Remove the matched group from remaining query
7994
8006
  remainingQuery = remainingQuery.replace(match[0], '').trim();
7995
8007
  }
7996
- // 2. THEN: Extract individual title: and skills: patterns
8008
+ // 2. THEN: Extract NOT title groups like NOT (title:"a" OR title:"b")
8009
+ const titleNotGroupPattern = /NOT\s+\((title:"[^"]+"(?:\s+OR\s+title:"[^"]+")+?)\)/g;
8010
+ const titleNotGroupMatches = [...remainingQuery.matchAll(titleNotGroupPattern)];
8011
+ for (const match of titleNotGroupMatches) {
8012
+ const terms = match[1]
8013
+ .split(/\s+OR\s+/)
8014
+ .map((term) => term.replace(/^title:"([^"]+)"$/, '"$1"'))
8015
+ .filter(Boolean);
8016
+ result.titleNoneAny = result.titleNoneAny
8017
+ ? `${result.titleNoneAny} ${terms.join(' ')}`
8018
+ : terms.join(' ');
8019
+ remainingQuery = remainingQuery.replace(match[0], '').trim();
8020
+ }
8021
+ // 3. Extract individual NOT title: terms before generic title extraction
8022
+ const titleNotPattern = /NOT\s+title:("([^"]+)"|([^\s)]+))/g;
8023
+ const titleNotMatches = [...remainingQuery.matchAll(titleNotPattern)];
8024
+ if (titleNotMatches.length > 0) {
8025
+ const terms = titleNotMatches.map((match) => {
8026
+ if (match[2]) {
8027
+ return `"${match[2]}"`;
8028
+ }
8029
+ return match[3];
8030
+ });
8031
+ result.titleNoneAny = result.titleNoneAny
8032
+ ? `${result.titleNoneAny} ${terms.join(' ')}`
8033
+ : terms.join(' ');
8034
+ remainingQuery = remainingQuery.replace(titleNotPattern, '').trim();
8035
+ }
8036
+ // 4. THEN: Extract individual title: and skills: patterns
7997
8037
  const fieldPatterns = [
7998
8038
  { field: 'titleSearch', pattern: /title:("([^"]+)"|([^\s)]+))/g },
7999
8039
  { field: 'skillsSearch', pattern: /skills:("([^"]+)"|([^\s)]+))/g },
@@ -8018,7 +8058,7 @@ class SearchQueryBuilder {
8018
8058
  remainingQuery = remainingQuery.replace(pattern, '').trim();
8019
8059
  }
8020
8060
  }
8021
- // 3. Handle legacy grouped field searches like title:(term1 AND term2)
8061
+ // 5. Handle legacy grouped field searches like title:(term1 AND term2)
8022
8062
  const groupedFieldPattern = /(title|skills):\(([^)]+)\)/g;
8023
8063
  let groupMatch;
8024
8064
  while ((groupMatch = groupedFieldPattern.exec(remainingQuery)) !== null) {
@@ -8046,7 +8086,7 @@ class SearchQueryBuilder {
8046
8086
  }
8047
8087
  remainingQuery = remainingQuery.replace(groupMatch[0], '').trim();
8048
8088
  }
8049
- // 4. Extract grouped NOT expressions: NOT (term1 OR term2 OR ...)
8089
+ // 6. Extract grouped NOT expressions: NOT (term1 OR term2 OR ...)
8050
8090
  const groupedNotPattern = /NOT\s+\(([^)]+(?:\s+OR\s+[^)]+)+)\)/g;
8051
8091
  const groupedNotMatches = [...remainingQuery.matchAll(groupedNotPattern)];
8052
8092
  if (groupedNotMatches.length > 0) {
@@ -8056,9 +8096,9 @@ class SearchQueryBuilder {
8056
8096
  .replace(groupedNotMatches[0][0], '')
8057
8097
  .trim();
8058
8098
  }
8059
- // 5. Extract individual NOT terms (only if no grouped NOT was found)
8099
+ // 7. Extract individual NOT terms (only if no grouped NOT was found)
8060
8100
  if (!result.noneWords) {
8061
- const notPattern = /NOT\s+("([^"]+)"|([^\s)]+))/g;
8101
+ const notPattern = /NOT\s+(?!title:)("([^"]+)"|([^\s)]+))/g;
8062
8102
  const notMatches = [...remainingQuery.matchAll(notPattern)];
8063
8103
  if (notMatches.length > 0) {
8064
8104
  const noneTerms = notMatches.map((match) => {
@@ -8071,7 +8111,7 @@ class SearchQueryBuilder {
8071
8111
  remainingQuery = remainingQuery.replace(notPattern, '').trim();
8072
8112
  }
8073
8113
  }
8074
- // 6. Process grouped expressions - DON'T clean up connectors first!
8114
+ // 8. Process grouped expressions - DON'T clean up connectors first!
8075
8115
  // Extract OR groups (anyWords) - PROCESS FIRST
8076
8116
  const orGroupPattern = /\(([^)]+(?:\s+OR\s+[^)]+)+)\)/g;
8077
8117
  const orMatches = [...remainingQuery.matchAll(orGroupPattern)];
@@ -8090,14 +8130,14 @@ class SearchQueryBuilder {
8090
8130
  }
8091
8131
  // NOW clean up connectors after group processing
8092
8132
  remainingQuery = this.cleanupConnectors(remainingQuery);
8093
- // 7. Extract standalone quoted phrases (exactPhrase) - ONLY after all groups processed
8133
+ // 9. Extract standalone quoted phrases (exactPhrase) - ONLY after all groups processed
8094
8134
  const standaloneQuotePattern = /(?:^|\s)("([^"]+)")(?=\s|$)/g;
8095
8135
  const quoteMatches = [...remainingQuery.matchAll(standaloneQuotePattern)];
8096
8136
  if (quoteMatches.length > 0) {
8097
8137
  result.exactPhrase = quoteMatches[0][2];
8098
8138
  remainingQuery = remainingQuery.replace(quoteMatches[0][1], '').trim();
8099
8139
  }
8100
- // 8. Handle remaining simple terms as allWords
8140
+ // 10. Handle remaining simple terms as allWords
8101
8141
  if (remainingQuery) {
8102
8142
  const remainingTerms = this.splitTermsWithQuotes(remainingQuery);
8103
8143
  if (remainingTerms.length > 0) {