@n8n/utils 1.32.0 → 1.33.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.
package/dist/index.cjs CHANGED
@@ -12,6 +12,7 @@ const require_sanitize = require('./sanitize.cjs');
12
12
  const require_path = require('./path.cjs');
13
13
  const require_placeholder = require('./placeholder2.cjs');
14
14
  const require_jwt = require('./jwt2.cjs');
15
+ const require_scrub_secrets = require('./scrub-secrets2.cjs');
15
16
 
16
17
  exports.DEFAULT_KEYS = require_sublimeSearch.DEFAULT_KEYS;
17
18
  exports.assert = require_assert.assert;
@@ -29,6 +30,7 @@ exports.isWindowsFilePath = require_path.isWindowsFilePath;
29
30
  exports.reRankSearchResults = require_reRankSearchResults.reRankSearchResults;
30
31
  exports.retry = require_retry.retry;
31
32
  exports.sanitizeFilename = require_sanitize.sanitizeFilename;
33
+ exports.scrubSecretsInText = require_scrub_secrets.scrubSecretsInText;
32
34
  exports.smartDecimal = require_smartDecimal.smartDecimal;
33
35
  exports.sortByProperty = require_sortByProperty.sortByProperty;
34
36
  exports.sublimeSearch = require_sublimeSearch.sublimeSearch;
package/dist/index.d.cts CHANGED
@@ -12,4 +12,5 @@ import { t as sortByProperty } from "./sortByProperty.cjs";
12
12
  import { n as truncateBeforeLast, t as truncate } from "./truncate.cjs";
13
13
  import { a as hasPlaceholderDeep, i as formatPlaceholderPath, n as extractPlaceholderLabels, o as isPlaceholderString, r as findPlaceholderDetails, s as isPlaceholderValue, t as PlaceholderDetail } from "./placeholder2.cjs";
14
14
  import { t as getJwtExpiry } from "./jwt2.cjs";
15
- export { CallbackFn, DEFAULT_KEYS, EventBus, PlaceholderDetail, assert, createEventBus, createEventQueue, extractPlaceholderLabels, findPlaceholderDetails, formatPlaceholderPath, generateNanoId, getJwtExpiry, hasPlaceholderDeep, isPlaceholderString, isPlaceholderValue, isWindowsFilePath, reRankSearchResults, retry, sanitizeFilename, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
15
+ import { t as scrubSecretsInText } from "./scrub-secrets2.cjs";
16
+ export { CallbackFn, DEFAULT_KEYS, EventBus, PlaceholderDetail, assert, createEventBus, createEventQueue, extractPlaceholderLabels, findPlaceholderDetails, formatPlaceholderPath, generateNanoId, getJwtExpiry, hasPlaceholderDeep, isPlaceholderString, isPlaceholderValue, isWindowsFilePath, reRankSearchResults, retry, sanitizeFilename, scrubSecretsInText, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
package/dist/index.d.mts CHANGED
@@ -12,4 +12,5 @@ import { t as sortByProperty } from "./sortByProperty.mjs";
12
12
  import { n as truncateBeforeLast, t as truncate } from "./truncate.mjs";
13
13
  import { a as hasPlaceholderDeep, i as formatPlaceholderPath, n as extractPlaceholderLabels, o as isPlaceholderString, r as findPlaceholderDetails, s as isPlaceholderValue, t as PlaceholderDetail } from "./placeholder2.mjs";
14
14
  import { t as getJwtExpiry } from "./jwt2.mjs";
15
- export { CallbackFn, DEFAULT_KEYS, EventBus, PlaceholderDetail, assert, createEventBus, createEventQueue, extractPlaceholderLabels, findPlaceholderDetails, formatPlaceholderPath, generateNanoId, getJwtExpiry, hasPlaceholderDeep, isPlaceholderString, isPlaceholderValue, isWindowsFilePath, reRankSearchResults, retry, sanitizeFilename, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
15
+ import { t as scrubSecretsInText } from "./scrub-secrets2.mjs";
16
+ export { CallbackFn, DEFAULT_KEYS, EventBus, PlaceholderDetail, assert, createEventBus, createEventQueue, extractPlaceholderLabels, findPlaceholderDetails, formatPlaceholderPath, generateNanoId, getJwtExpiry, hasPlaceholderDeep, isPlaceholderString, isPlaceholderValue, isWindowsFilePath, reRankSearchResults, retry, sanitizeFilename, scrubSecretsInText, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
package/dist/index.mjs CHANGED
@@ -12,5 +12,6 @@ import { t as sanitizeFilename } from "./sanitize.mjs";
12
12
  import { t as isWindowsFilePath } from "./path.mjs";
13
13
  import { a as isPlaceholderString, i as hasPlaceholderDeep, n as findPlaceholderDetails, o as isPlaceholderValue, r as formatPlaceholderPath, t as extractPlaceholderLabels } from "./placeholder2.mjs";
14
14
  import { t as getJwtExpiry } from "./jwt2.mjs";
15
+ import { t as scrubSecretsInText } from "./scrub-secrets2.mjs";
15
16
 
16
- export { DEFAULT_KEYS, assert, createEventBus, createEventQueue, extractPlaceholderLabels, findPlaceholderDetails, formatPlaceholderPath, generateNanoId, getJwtExpiry, hasPlaceholderDeep, isPlaceholderString, isPlaceholderValue, isWindowsFilePath, reRankSearchResults, retry, sanitizeFilename, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
17
+ export { DEFAULT_KEYS, assert, createEventBus, createEventQueue, extractPlaceholderLabels, findPlaceholderDetails, formatPlaceholderPath, generateNanoId, getJwtExpiry, hasPlaceholderDeep, isPlaceholderString, isPlaceholderValue, isWindowsFilePath, reRankSearchResults, retry, sanitizeFilename, scrubSecretsInText, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
@@ -0,0 +1,3 @@
1
+ const require_scrub_secrets = require('./scrub-secrets2.cjs');
2
+
3
+ exports.scrubSecretsInText = require_scrub_secrets.scrubSecretsInText;
@@ -0,0 +1,2 @@
1
+ import { t as scrubSecretsInText } from "./scrub-secrets2.cjs";
2
+ export { scrubSecretsInText };
@@ -0,0 +1,2 @@
1
+ import { t as scrubSecretsInText } from "./scrub-secrets2.mjs";
2
+ export { scrubSecretsInText };
@@ -0,0 +1,3 @@
1
+ import { t as scrubSecretsInText } from "./scrub-secrets2.mjs";
2
+
3
+ export { scrubSecretsInText };
@@ -0,0 +1,41 @@
1
+
2
+ //#region src/scrub-secrets.ts
3
+ /**
4
+ * Replace common credential patterns in free-form text with `[REDACTED]`.
5
+ *
6
+ * Used before persisting or transmitting user-supplied text (telemetry
7
+ * excerpts, eval report HTML, free-form feedback) where keys/tokens
8
+ * accidentally pasted into prompts or command lines could otherwise leak
9
+ * downstream.
10
+ *
11
+ * Conservative by design: matches well-known prefixed tokens, explicit
12
+ * `key=value` pairs, and quoted JSON/JS-object fields with sensitive
13
+ * names. We don't attempt to redact arbitrary long opaque strings — false
14
+ * positives on file paths, IDs, or base64 payloads would make the output
15
+ * unreadable.
16
+ */
17
+ const SECRET_KEYS = "password|passwd|secret|credentials?|api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token";
18
+ const SECRET_VALUE_PATTERNS = [
19
+ /\b(?:Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{12,}/gi,
20
+ /\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{16,}/g,
21
+ /\bxox[abprso]-[A-Za-z0-9-]{10,}/g,
22
+ /\bgh[psoru]_[A-Za-z0-9]{20,}/g,
23
+ /\bAKIA[0-9A-Z]{16}\b/g,
24
+ new RegExp(`"(?:${SECRET_KEYS})"\\s*:\\s*"(?!\\[(?:redacted|REDACTED)\\]")(?:\\\\.|[^"\\r\\n])*"`, "gi"),
25
+ new RegExp(`'(?:${SECRET_KEYS})'\\s*:\\s*'(?!\\[(?:redacted|REDACTED)\\]')(?:\\\\.|[^'\\r\\n])*'`, "gi"),
26
+ new RegExp(`\\b(?:${SECRET_KEYS})\\s*[:=]\\s*\\S+`, "gi")
27
+ ];
28
+ function scrubSecretsInText(input) {
29
+ let out = input;
30
+ for (const pattern of SECRET_VALUE_PATTERNS) out = out.replace(pattern, "[REDACTED]");
31
+ return out;
32
+ }
33
+
34
+ //#endregion
35
+ Object.defineProperty(exports, 'scrubSecretsInText', {
36
+ enumerable: true,
37
+ get: function () {
38
+ return scrubSecretsInText;
39
+ }
40
+ });
41
+ //# sourceMappingURL=scrub-secrets2.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrub-secrets2.cjs","names":["SECRET_VALUE_PATTERNS: readonly RegExp[]"],"sources":["../src/scrub-secrets.ts"],"sourcesContent":["/**\n * Replace common credential patterns in free-form text with `[REDACTED]`.\n *\n * Used before persisting or transmitting user-supplied text (telemetry\n * excerpts, eval report HTML, free-form feedback) where keys/tokens\n * accidentally pasted into prompts or command lines could otherwise leak\n * downstream.\n *\n * Conservative by design: matches well-known prefixed tokens, explicit\n * `key=value` pairs, and quoted JSON/JS-object fields with sensitive\n * names. We don't attempt to redact arbitrary long opaque strings — false\n * positives on file paths, IDs, or base64 payloads would make the output\n * unreadable.\n */\nconst SECRET_KEYS =\n\t'password|passwd|secret|credentials?|api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token';\n\nconst SECRET_VALUE_PATTERNS: readonly RegExp[] = [\n\t// Authorization-header substrings: `Bearer <token>`, `Basic <token>`, `Token <token>`\n\t/\\b(?:Bearer|Basic|Token)\\s+[A-Za-z0-9._~+/=-]{12,}/gi,\n\t// OpenAI / Anthropic API keys\n\t/\\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{16,}/g,\n\t// Slack tokens (xoxb, xoxp, xoxa, xoxr, xoxs, xoxo)\n\t/\\bxox[abprso]-[A-Za-z0-9-]{10,}/g,\n\t// GitHub tokens (ghp, ghs, gho, ghr, ghu)\n\t/\\bgh[psoru]_[A-Za-z0-9]{20,}/g,\n\t// AWS access key id\n\t/\\bAKIA[0-9A-Z]{16}\\b/g,\n\t// JSON-shaped `\"key\": \"value\"` — matches the quoted field as a whole.\n\t// Run before the loose pattern so nested objects like\n\t// `{\"credentials\": {\"apiKey\": \"...\"}}` don't have the outer key consume\n\t// the inner key on its way to a non-quoted (object) value. The value\n\t// body uses the standard JSON-string idiom `(?:\\\\.|[^\"\\r\\n])*` so an\n\t// escaped quote inside the value (`\"abc\\\"def\"`) doesn't end the match\n\t// early and leak the rest of the secret. The negative lookahead skips\n\t// values that are already a `[redacted]` / `[REDACTED]` placeholder so\n\t// this stays idempotent when chained behind upstream object-walking\n\t// redaction (e.g. langsmith trace payloads).\n\tnew RegExp(\n\t\t`\"(?:${SECRET_KEYS})\"\\\\s*:\\\\s*\"(?!\\\\[(?:redacted|REDACTED)\\\\]\")(?:\\\\\\\\.|[^\"\\\\r\\\\n])*\"`,\n\t\t'gi',\n\t),\n\t// JS-object-shaped `'key': 'value'`\n\tnew RegExp(\n\t\t`'(?:${SECRET_KEYS})'\\\\s*:\\\\s*'(?!\\\\[(?:redacted|REDACTED)\\\\]')(?:\\\\\\\\.|[^'\\\\r\\\\n])*'`,\n\t\t'gi',\n\t),\n\t// Generic `password=...` / `api_key=...` / `secret=...` style assignments\n\tnew RegExp(`\\\\b(?:${SECRET_KEYS})\\\\s*[:=]\\\\s*\\\\S+`, 'gi'),\n];\n\nexport function scrubSecretsInText(input: string): string {\n\tlet out = input;\n\tfor (const pattern of SECRET_VALUE_PATTERNS) {\n\t\tout = out.replace(pattern, '[REDACTED]');\n\t}\n\treturn out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAcA,MAAM,cACL;AAED,MAAMA,wBAA2C;CAEhD;CAEA;CAEA;CAEA;CAEA;CAWA,IAAI,OACH,OAAO,YAAY,qEACnB,KACA;CAED,IAAI,OACH,OAAO,YAAY,qEACnB,KACA;CAED,IAAI,OAAO,SAAS,YAAY,oBAAoB,KAAK;CACzD;AAED,SAAgB,mBAAmB,OAAuB;CACzD,IAAI,MAAM;AACV,MAAK,MAAM,WAAW,sBACrB,OAAM,IAAI,QAAQ,SAAS,aAAa;AAEzC,QAAO"}
@@ -0,0 +1,5 @@
1
+ //#region src/scrub-secrets.d.ts
2
+ declare function scrubSecretsInText(input: string): string;
3
+ //#endregion
4
+ export { scrubSecretsInText as t };
5
+ //# sourceMappingURL=scrub-secrets2.d.cts.map
@@ -0,0 +1,5 @@
1
+ //#region src/scrub-secrets.d.ts
2
+ declare function scrubSecretsInText(input: string): string;
3
+ //#endregion
4
+ export { scrubSecretsInText as t };
5
+ //# sourceMappingURL=scrub-secrets2.d.mts.map
@@ -0,0 +1,35 @@
1
+ //#region src/scrub-secrets.ts
2
+ /**
3
+ * Replace common credential patterns in free-form text with `[REDACTED]`.
4
+ *
5
+ * Used before persisting or transmitting user-supplied text (telemetry
6
+ * excerpts, eval report HTML, free-form feedback) where keys/tokens
7
+ * accidentally pasted into prompts or command lines could otherwise leak
8
+ * downstream.
9
+ *
10
+ * Conservative by design: matches well-known prefixed tokens, explicit
11
+ * `key=value` pairs, and quoted JSON/JS-object fields with sensitive
12
+ * names. We don't attempt to redact arbitrary long opaque strings — false
13
+ * positives on file paths, IDs, or base64 payloads would make the output
14
+ * unreadable.
15
+ */
16
+ const SECRET_KEYS = "password|passwd|secret|credentials?|api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token";
17
+ const SECRET_VALUE_PATTERNS = [
18
+ /\b(?:Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{12,}/gi,
19
+ /\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{16,}/g,
20
+ /\bxox[abprso]-[A-Za-z0-9-]{10,}/g,
21
+ /\bgh[psoru]_[A-Za-z0-9]{20,}/g,
22
+ /\bAKIA[0-9A-Z]{16}\b/g,
23
+ new RegExp(`"(?:${SECRET_KEYS})"\\s*:\\s*"(?!\\[(?:redacted|REDACTED)\\]")(?:\\\\.|[^"\\r\\n])*"`, "gi"),
24
+ new RegExp(`'(?:${SECRET_KEYS})'\\s*:\\s*'(?!\\[(?:redacted|REDACTED)\\]')(?:\\\\.|[^'\\r\\n])*'`, "gi"),
25
+ new RegExp(`\\b(?:${SECRET_KEYS})\\s*[:=]\\s*\\S+`, "gi")
26
+ ];
27
+ function scrubSecretsInText(input) {
28
+ let out = input;
29
+ for (const pattern of SECRET_VALUE_PATTERNS) out = out.replace(pattern, "[REDACTED]");
30
+ return out;
31
+ }
32
+
33
+ //#endregion
34
+ export { scrubSecretsInText as t };
35
+ //# sourceMappingURL=scrub-secrets2.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrub-secrets2.mjs","names":["SECRET_VALUE_PATTERNS: readonly RegExp[]"],"sources":["../src/scrub-secrets.ts"],"sourcesContent":["/**\n * Replace common credential patterns in free-form text with `[REDACTED]`.\n *\n * Used before persisting or transmitting user-supplied text (telemetry\n * excerpts, eval report HTML, free-form feedback) where keys/tokens\n * accidentally pasted into prompts or command lines could otherwise leak\n * downstream.\n *\n * Conservative by design: matches well-known prefixed tokens, explicit\n * `key=value` pairs, and quoted JSON/JS-object fields with sensitive\n * names. We don't attempt to redact arbitrary long opaque strings — false\n * positives on file paths, IDs, or base64 payloads would make the output\n * unreadable.\n */\nconst SECRET_KEYS =\n\t'password|passwd|secret|credentials?|api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token';\n\nconst SECRET_VALUE_PATTERNS: readonly RegExp[] = [\n\t// Authorization-header substrings: `Bearer <token>`, `Basic <token>`, `Token <token>`\n\t/\\b(?:Bearer|Basic|Token)\\s+[A-Za-z0-9._~+/=-]{12,}/gi,\n\t// OpenAI / Anthropic API keys\n\t/\\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{16,}/g,\n\t// Slack tokens (xoxb, xoxp, xoxa, xoxr, xoxs, xoxo)\n\t/\\bxox[abprso]-[A-Za-z0-9-]{10,}/g,\n\t// GitHub tokens (ghp, ghs, gho, ghr, ghu)\n\t/\\bgh[psoru]_[A-Za-z0-9]{20,}/g,\n\t// AWS access key id\n\t/\\bAKIA[0-9A-Z]{16}\\b/g,\n\t// JSON-shaped `\"key\": \"value\"` — matches the quoted field as a whole.\n\t// Run before the loose pattern so nested objects like\n\t// `{\"credentials\": {\"apiKey\": \"...\"}}` don't have the outer key consume\n\t// the inner key on its way to a non-quoted (object) value. The value\n\t// body uses the standard JSON-string idiom `(?:\\\\.|[^\"\\r\\n])*` so an\n\t// escaped quote inside the value (`\"abc\\\"def\"`) doesn't end the match\n\t// early and leak the rest of the secret. The negative lookahead skips\n\t// values that are already a `[redacted]` / `[REDACTED]` placeholder so\n\t// this stays idempotent when chained behind upstream object-walking\n\t// redaction (e.g. langsmith trace payloads).\n\tnew RegExp(\n\t\t`\"(?:${SECRET_KEYS})\"\\\\s*:\\\\s*\"(?!\\\\[(?:redacted|REDACTED)\\\\]\")(?:\\\\\\\\.|[^\"\\\\r\\\\n])*\"`,\n\t\t'gi',\n\t),\n\t// JS-object-shaped `'key': 'value'`\n\tnew RegExp(\n\t\t`'(?:${SECRET_KEYS})'\\\\s*:\\\\s*'(?!\\\\[(?:redacted|REDACTED)\\\\]')(?:\\\\\\\\.|[^'\\\\r\\\\n])*'`,\n\t\t'gi',\n\t),\n\t// Generic `password=...` / `api_key=...` / `secret=...` style assignments\n\tnew RegExp(`\\\\b(?:${SECRET_KEYS})\\\\s*[:=]\\\\s*\\\\S+`, 'gi'),\n];\n\nexport function scrubSecretsInText(input: string): string {\n\tlet out = input;\n\tfor (const pattern of SECRET_VALUE_PATTERNS) {\n\t\tout = out.replace(pattern, '[REDACTED]');\n\t}\n\treturn out;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,MAAM,cACL;AAED,MAAMA,wBAA2C;CAEhD;CAEA;CAEA;CAEA;CAEA;CAWA,IAAI,OACH,OAAO,YAAY,qEACnB,KACA;CAED,IAAI,OACH,OAAO,YAAY,qEACnB,KACA;CAED,IAAI,OAAO,SAAS,YAAY,oBAAoB,KAAK;CACzD;AAED,SAAgB,mBAAmB,OAAuB;CACzD,IAAI,MAAM;AACV,MAAK,MAAM,WAAW,sBACrB,OAAM,IAAI,QAAQ,SAAS,aAAa;AAEzC,QAAO"}
@@ -6,7 +6,7 @@ const CAMEL_BONUS = 30;
6
6
  const FIRST_LETTER_BONUS = 15;
7
7
  const LEADING_LETTER_PENALTY = -20;
8
8
  const MAX_LEADING_LETTER_PENALTY = -200;
9
- const UNMATCHED_LETTER_PENALTY = -5;
9
+ const UNMATCHED_LETTER_PENALTY = -2.5;
10
10
  const DEFAULT_KEYS = [{
11
11
  key: "properties.displayName",
12
12
  weight: 1.3
@@ -1 +1 @@
1
- {"version":3,"file":"sublimeSearch.cjs","names":["bestRecursiveMatches: number[]","recursiveMatches: number[]","values: Array<{ value: string; weight: number }>"],"sources":["../src/search/sublimeSearch.ts"],"sourcesContent":["/*\n * Constants and utility functions used for searching for node types in node creator component\n * based on https://github.com/forrestthewoods/lib_fts/blob/master/code/fts_fuzzy_match.js\n */\n\nconst SEQUENTIAL_BONUS = 60; // bonus for adjacent matches\nconst SEPARATOR_BONUS = 38; // bonus if match occurs after a separator\nconst CAMEL_BONUS = 30; // bonus if match is uppercase and prev is lower\nconst FIRST_LETTER_BONUS = 15; // bonus if the first letter is matched\n\nconst LEADING_LETTER_PENALTY = -20; // penalty applied for every letter in str before the first match\nconst MAX_LEADING_LETTER_PENALTY = -200; // maximum penalty for leading letters\nconst UNMATCHED_LETTER_PENALTY = -5;\n\nexport const DEFAULT_KEYS = [\n\t{ key: 'properties.displayName', weight: 1.3 },\n\t{ key: 'properties.codex.alias', weight: 1 },\n];\n\n/**\n * Returns true if each character in pattern is found sequentially within target\n * @param {*} pattern string\n * @param {*} target string\n */\nfunction fuzzyMatchSimple(pattern: string, target: string): boolean {\n\tlet patternIdx = 0;\n\tlet strIdx = 0;\n\n\twhile (patternIdx < pattern.length && strIdx < target.length) {\n\t\tconst patternChar = pattern.charAt(patternIdx).toLowerCase();\n\t\tconst targetChar = target.charAt(strIdx).toLowerCase();\n\t\tif (patternChar === targetChar) {\n\t\t\tpatternIdx++;\n\t\t}\n\t\t++strIdx;\n\t}\n\n\treturn pattern.length !== 0 && target.length !== 0 && patternIdx === pattern.length;\n}\n\nfunction fuzzyMatchRecursive(\n\tpattern: string,\n\ttarget: string,\n\tpatternCurIndex: number,\n\ttargetCurrIndex: number,\n\ttargetMatches: null | number[],\n\tmatches: number[],\n\tmaxMatches: number,\n\tnextMatch: number,\n\trecursionCount: number,\n\trecursionLimit: number,\n): { matched: boolean; outScore: number } {\n\tlet outScore = 0;\n\n\t// Return if recursion limit is reached.\n\tif (++recursionCount >= recursionLimit) {\n\t\treturn { matched: false, outScore };\n\t}\n\n\t// Return if we reached ends of strings.\n\tif (patternCurIndex === pattern.length || targetCurrIndex === target.length) {\n\t\treturn { matched: false, outScore };\n\t}\n\n\t// Recursion params\n\tlet recursiveMatch = false;\n\tlet bestRecursiveMatches: number[] = [];\n\tlet bestRecursiveScore = 0;\n\n\t// Loop through pattern and str looking for a match.\n\tlet firstMatch = true;\n\twhile (patternCurIndex < pattern.length && targetCurrIndex < target.length) {\n\t\t// Match found.\n\t\tif (pattern[patternCurIndex].toLowerCase() === target[targetCurrIndex].toLowerCase()) {\n\t\t\tif (nextMatch >= maxMatches) {\n\t\t\t\treturn { matched: false, outScore };\n\t\t\t}\n\n\t\t\tif (firstMatch && targetMatches) {\n\t\t\t\tmatches = [...targetMatches];\n\t\t\t\tfirstMatch = false;\n\t\t\t}\n\n\t\t\tconst recursiveMatches: number[] = [];\n\t\t\tconst recursiveResult = fuzzyMatchRecursive(\n\t\t\t\tpattern,\n\t\t\t\ttarget,\n\t\t\t\tpatternCurIndex,\n\t\t\t\ttargetCurrIndex + 1,\n\t\t\t\tmatches,\n\t\t\t\trecursiveMatches,\n\t\t\t\tmaxMatches,\n\t\t\t\tnextMatch,\n\t\t\t\trecursionCount,\n\t\t\t\trecursionLimit,\n\t\t\t);\n\n\t\t\tconst recursiveScore = recursiveResult.outScore;\n\t\t\tif (recursiveResult.matched) {\n\t\t\t\t// Pick best recursive score.\n\t\t\t\tif (!recursiveMatch || recursiveScore > bestRecursiveScore) {\n\t\t\t\t\tbestRecursiveMatches = [...recursiveMatches];\n\t\t\t\t\tbestRecursiveScore = recursiveScore;\n\t\t\t\t}\n\t\t\t\trecursiveMatch = true;\n\t\t\t}\n\n\t\t\tmatches[nextMatch++] = targetCurrIndex;\n\t\t\t++patternCurIndex;\n\t\t}\n\t\t++targetCurrIndex;\n\t}\n\n\tconst matched = patternCurIndex === pattern.length;\n\n\tif (matched) {\n\t\toutScore = 100;\n\n\t\t// Apply leading letter penalty (if not n8n-prefixed)\n\t\tif (!target.toLowerCase().startsWith('n8n')) {\n\t\t\tlet penalty = LEADING_LETTER_PENALTY * matches[0];\n\t\t\tpenalty = penalty < MAX_LEADING_LETTER_PENALTY ? MAX_LEADING_LETTER_PENALTY : penalty;\n\t\t\toutScore += penalty;\n\t\t}\n\n\t\t//Apply unmatched penalty\n\t\tconst unmatched = target.length - nextMatch;\n\t\toutScore += UNMATCHED_LETTER_PENALTY * unmatched;\n\n\t\t// Apply ordering bonuses\n\t\tfor (let i = 0; i < nextMatch; i++) {\n\t\t\tconst currIdx = matches[i];\n\n\t\t\tif (i > 0) {\n\t\t\t\tconst prevIdx = matches[i - 1];\n\t\t\t\tif (currIdx === prevIdx + 1) {\n\t\t\t\t\toutScore += SEQUENTIAL_BONUS;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check for bonuses based on neighbor character value.\n\t\t\tif (currIdx > 0) {\n\t\t\t\t// Camel case\n\t\t\t\tconst neighbor = target[currIdx - 1];\n\t\t\t\tconst curr = target[currIdx];\n\t\t\t\tif (neighbor !== neighbor.toUpperCase() && curr !== curr.toLowerCase()) {\n\t\t\t\t\toutScore += CAMEL_BONUS;\n\t\t\t\t}\n\t\t\t\tconst isNeighbourSeparator = neighbor === '_' || neighbor === ' ';\n\t\t\t\tif (isNeighbourSeparator) {\n\t\t\t\t\toutScore += SEPARATOR_BONUS;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// First letter\n\t\t\t\toutScore += FIRST_LETTER_BONUS;\n\t\t\t}\n\t\t}\n\n\t\t// Return best result\n\t\tif (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {\n\t\t\t// Recursive score is better than \"this\"\n\t\t\tmatches = [...bestRecursiveMatches];\n\t\t\toutScore = bestRecursiveScore;\n\t\t\treturn { matched: true, outScore };\n\t\t} else if (matched) {\n\t\t\t// \"this\" score is better than recursive\n\t\t\treturn { matched: true, outScore };\n\t\t} else {\n\t\t\treturn { matched: false, outScore };\n\t\t}\n\t}\n\treturn { matched: false, outScore };\n}\n\n/**\n * Does a fuzzy search to find pattern inside a string.\n * @param {*} pattern string pattern to search for\n * @param {*} target string string which is being searched\n * @returns [boolean, number] a boolean which tells if pattern was\n * found or not and a search score\n */\nfunction fuzzyMatch(pattern: string, target: string): { matched: boolean; outScore: number } {\n\tconst recursionCount = 0;\n\tconst recursionLimit = 5;\n\tconst matches: number[] = [];\n\tconst maxMatches = 256;\n\n\treturn fuzzyMatchRecursive(\n\t\tpattern,\n\t\ttarget,\n\t\t0 /* patternCurIndex */,\n\t\t0 /* strCurrIndex */,\n\t\tnull /* srcMatces */,\n\t\tmatches,\n\t\tmaxMatches,\n\t\t0 /* nextMatch */,\n\t\trecursionCount,\n\t\trecursionLimit,\n\t);\n}\n\n// prop = 'key'\n// prop = 'key1.key2'\n// prop = ['key1', 'key2']\nfunction getValue<T extends object>(obj: T, prop: string): unknown {\n\tif (obj.hasOwnProperty(prop)) {\n\t\treturn obj[prop as keyof T];\n\t}\n\n\tconst segments = prop.split('.');\n\n\tlet result = obj;\n\tlet i = 0;\n\twhile (result && i < segments.length) {\n\t\tconst key = segments[i] as keyof T;\n\t\tresult = result[key] as T;\n\t\ti++;\n\t}\n\treturn result;\n}\n\nexport function sublimeSearch<T extends object>(\n\tfilter: string,\n\tdata: readonly T[],\n\tkeys: Array<{ key: string; weight: number }> = DEFAULT_KEYS,\n): Array<{ score: number; item: T }> {\n\tconst results = data.reduce((accu: Array<{ score: number; item: T }>, item: T) => {\n\t\tlet values: Array<{ value: string; weight: number }> = [];\n\t\tkeys.forEach(({ key, weight }) => {\n\t\t\tconst value = getValue(item, key);\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tvalues = values.concat(value.map((v) => ({ value: v, weight })));\n\t\t\t} else if (typeof value === 'string') {\n\t\t\t\tvalues.push({\n\t\t\t\t\tvalue,\n\t\t\t\t\tweight,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\t// for each item, check every key and get maximum score\n\t\tconst itemMatch = values.reduce(\n\t\t\t(\n\t\t\t\tresult: null | { matched: boolean; outScore: number },\n\t\t\t\t{ value, weight }: { value: string; weight: number },\n\t\t\t) => {\n\t\t\t\tif (!fuzzyMatchSimple(filter, value)) {\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\n\t\t\t\tconst match = fuzzyMatch(filter, value);\n\t\t\t\tmatch.outScore *= weight;\n\n\t\t\t\tconst { matched, outScore } = match;\n\t\t\t\tif (!result && matched) {\n\t\t\t\t\treturn match;\n\t\t\t\t}\n\t\t\t\tif (matched && result && outScore > result.outScore) {\n\t\t\t\t\treturn match;\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t},\n\t\t\tnull,\n\t\t);\n\n\t\tif (itemMatch) {\n\t\t\taccu.push({\n\t\t\t\tscore: itemMatch.outScore,\n\t\t\t\titem,\n\t\t\t});\n\t\t}\n\n\t\treturn accu;\n\t}, []);\n\n\tresults.sort((a, b) => {\n\t\treturn b.score - a.score;\n\t});\n\n\treturn results;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sublimeSearch.cjs","names":["bestRecursiveMatches: number[]","recursiveMatches: number[]","values: Array<{ value: string; weight: number }>"],"sources":["../src/search/sublimeSearch.ts"],"sourcesContent":["/*\n * Constants and utility functions used for searching for node types in node creator component\n * based on https://github.com/forrestthewoods/lib_fts/blob/master/code/fts_fuzzy_match.js\n */\n\nconst SEQUENTIAL_BONUS = 60; // bonus for adjacent matches\nconst SEPARATOR_BONUS = 38; // bonus if match occurs after a separator\nconst CAMEL_BONUS = 30; // bonus if match is uppercase and prev is lower\nconst FIRST_LETTER_BONUS = 15; // bonus if the first letter is matched\n\nconst LEADING_LETTER_PENALTY = -20; // penalty applied for every letter in str before the first match\nconst MAX_LEADING_LETTER_PENALTY = -200; // maximum penalty for leading letters\nconst UNMATCHED_LETTER_PENALTY = -2.5;\n\nexport const DEFAULT_KEYS = [\n\t{ key: 'properties.displayName', weight: 1.3 },\n\t{ key: 'properties.codex.alias', weight: 1 },\n];\n\n/**\n * Returns true if each character in pattern is found sequentially within target\n * @param {*} pattern string\n * @param {*} target string\n */\nfunction fuzzyMatchSimple(pattern: string, target: string): boolean {\n\tlet patternIdx = 0;\n\tlet strIdx = 0;\n\n\twhile (patternIdx < pattern.length && strIdx < target.length) {\n\t\tconst patternChar = pattern.charAt(patternIdx).toLowerCase();\n\t\tconst targetChar = target.charAt(strIdx).toLowerCase();\n\t\tif (patternChar === targetChar) {\n\t\t\tpatternIdx++;\n\t\t}\n\t\t++strIdx;\n\t}\n\n\treturn pattern.length !== 0 && target.length !== 0 && patternIdx === pattern.length;\n}\n\nfunction fuzzyMatchRecursive(\n\tpattern: string,\n\ttarget: string,\n\tpatternCurIndex: number,\n\ttargetCurrIndex: number,\n\ttargetMatches: null | number[],\n\tmatches: number[],\n\tmaxMatches: number,\n\tnextMatch: number,\n\trecursionCount: number,\n\trecursionLimit: number,\n): { matched: boolean; outScore: number } {\n\tlet outScore = 0;\n\n\t// Return if recursion limit is reached.\n\tif (++recursionCount >= recursionLimit) {\n\t\treturn { matched: false, outScore };\n\t}\n\n\t// Return if we reached ends of strings.\n\tif (patternCurIndex === pattern.length || targetCurrIndex === target.length) {\n\t\treturn { matched: false, outScore };\n\t}\n\n\t// Recursion params\n\tlet recursiveMatch = false;\n\tlet bestRecursiveMatches: number[] = [];\n\tlet bestRecursiveScore = 0;\n\n\t// Loop through pattern and str looking for a match.\n\tlet firstMatch = true;\n\twhile (patternCurIndex < pattern.length && targetCurrIndex < target.length) {\n\t\t// Match found.\n\t\tif (pattern[patternCurIndex].toLowerCase() === target[targetCurrIndex].toLowerCase()) {\n\t\t\tif (nextMatch >= maxMatches) {\n\t\t\t\treturn { matched: false, outScore };\n\t\t\t}\n\n\t\t\tif (firstMatch && targetMatches) {\n\t\t\t\tmatches = [...targetMatches];\n\t\t\t\tfirstMatch = false;\n\t\t\t}\n\n\t\t\tconst recursiveMatches: number[] = [];\n\t\t\tconst recursiveResult = fuzzyMatchRecursive(\n\t\t\t\tpattern,\n\t\t\t\ttarget,\n\t\t\t\tpatternCurIndex,\n\t\t\t\ttargetCurrIndex + 1,\n\t\t\t\tmatches,\n\t\t\t\trecursiveMatches,\n\t\t\t\tmaxMatches,\n\t\t\t\tnextMatch,\n\t\t\t\trecursionCount,\n\t\t\t\trecursionLimit,\n\t\t\t);\n\n\t\t\tconst recursiveScore = recursiveResult.outScore;\n\t\t\tif (recursiveResult.matched) {\n\t\t\t\t// Pick best recursive score.\n\t\t\t\tif (!recursiveMatch || recursiveScore > bestRecursiveScore) {\n\t\t\t\t\tbestRecursiveMatches = [...recursiveMatches];\n\t\t\t\t\tbestRecursiveScore = recursiveScore;\n\t\t\t\t}\n\t\t\t\trecursiveMatch = true;\n\t\t\t}\n\n\t\t\tmatches[nextMatch++] = targetCurrIndex;\n\t\t\t++patternCurIndex;\n\t\t}\n\t\t++targetCurrIndex;\n\t}\n\n\tconst matched = patternCurIndex === pattern.length;\n\n\tif (matched) {\n\t\toutScore = 100;\n\n\t\t// Apply leading letter penalty (if not n8n-prefixed)\n\t\tif (!target.toLowerCase().startsWith('n8n')) {\n\t\t\tlet penalty = LEADING_LETTER_PENALTY * matches[0];\n\t\t\tpenalty = penalty < MAX_LEADING_LETTER_PENALTY ? MAX_LEADING_LETTER_PENALTY : penalty;\n\t\t\toutScore += penalty;\n\t\t}\n\n\t\t//Apply unmatched penalty\n\t\tconst unmatched = target.length - nextMatch;\n\t\toutScore += UNMATCHED_LETTER_PENALTY * unmatched;\n\n\t\t// Apply ordering bonuses\n\t\tfor (let i = 0; i < nextMatch; i++) {\n\t\t\tconst currIdx = matches[i];\n\n\t\t\tif (i > 0) {\n\t\t\t\tconst prevIdx = matches[i - 1];\n\t\t\t\tif (currIdx === prevIdx + 1) {\n\t\t\t\t\toutScore += SEQUENTIAL_BONUS;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check for bonuses based on neighbor character value.\n\t\t\tif (currIdx > 0) {\n\t\t\t\t// Camel case\n\t\t\t\tconst neighbor = target[currIdx - 1];\n\t\t\t\tconst curr = target[currIdx];\n\t\t\t\tif (neighbor !== neighbor.toUpperCase() && curr !== curr.toLowerCase()) {\n\t\t\t\t\toutScore += CAMEL_BONUS;\n\t\t\t\t}\n\t\t\t\tconst isNeighbourSeparator = neighbor === '_' || neighbor === ' ';\n\t\t\t\tif (isNeighbourSeparator) {\n\t\t\t\t\toutScore += SEPARATOR_BONUS;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// First letter\n\t\t\t\toutScore += FIRST_LETTER_BONUS;\n\t\t\t}\n\t\t}\n\n\t\t// Return best result\n\t\tif (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {\n\t\t\t// Recursive score is better than \"this\"\n\t\t\tmatches = [...bestRecursiveMatches];\n\t\t\toutScore = bestRecursiveScore;\n\t\t\treturn { matched: true, outScore };\n\t\t} else if (matched) {\n\t\t\t// \"this\" score is better than recursive\n\t\t\treturn { matched: true, outScore };\n\t\t} else {\n\t\t\treturn { matched: false, outScore };\n\t\t}\n\t}\n\treturn { matched: false, outScore };\n}\n\n/**\n * Does a fuzzy search to find pattern inside a string.\n * @param {*} pattern string pattern to search for\n * @param {*} target string string which is being searched\n * @returns [boolean, number] a boolean which tells if pattern was\n * found or not and a search score\n */\nfunction fuzzyMatch(pattern: string, target: string): { matched: boolean; outScore: number } {\n\tconst recursionCount = 0;\n\tconst recursionLimit = 5;\n\tconst matches: number[] = [];\n\tconst maxMatches = 256;\n\n\treturn fuzzyMatchRecursive(\n\t\tpattern,\n\t\ttarget,\n\t\t0 /* patternCurIndex */,\n\t\t0 /* strCurrIndex */,\n\t\tnull /* srcMatces */,\n\t\tmatches,\n\t\tmaxMatches,\n\t\t0 /* nextMatch */,\n\t\trecursionCount,\n\t\trecursionLimit,\n\t);\n}\n\n// prop = 'key'\n// prop = 'key1.key2'\n// prop = ['key1', 'key2']\nfunction getValue<T extends object>(obj: T, prop: string): unknown {\n\tif (obj.hasOwnProperty(prop)) {\n\t\treturn obj[prop as keyof T];\n\t}\n\n\tconst segments = prop.split('.');\n\n\tlet result = obj;\n\tlet i = 0;\n\twhile (result && i < segments.length) {\n\t\tconst key = segments[i] as keyof T;\n\t\tresult = result[key] as T;\n\t\ti++;\n\t}\n\treturn result;\n}\n\nexport function sublimeSearch<T extends object>(\n\tfilter: string,\n\tdata: readonly T[],\n\tkeys: Array<{ key: string; weight: number }> = DEFAULT_KEYS,\n): Array<{ score: number; item: T }> {\n\tconst results = data.reduce((accu: Array<{ score: number; item: T }>, item: T) => {\n\t\tlet values: Array<{ value: string; weight: number }> = [];\n\t\tkeys.forEach(({ key, weight }) => {\n\t\t\tconst value = getValue(item, key);\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tvalues = values.concat(value.map((v) => ({ value: v, weight })));\n\t\t\t} else if (typeof value === 'string') {\n\t\t\t\tvalues.push({\n\t\t\t\t\tvalue,\n\t\t\t\t\tweight,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\t// for each item, check every key and get maximum score\n\t\tconst itemMatch = values.reduce(\n\t\t\t(\n\t\t\t\tresult: null | { matched: boolean; outScore: number },\n\t\t\t\t{ value, weight }: { value: string; weight: number },\n\t\t\t) => {\n\t\t\t\tif (!fuzzyMatchSimple(filter, value)) {\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\n\t\t\t\tconst match = fuzzyMatch(filter, value);\n\t\t\t\tmatch.outScore *= weight;\n\n\t\t\t\tconst { matched, outScore } = match;\n\t\t\t\tif (!result && matched) {\n\t\t\t\t\treturn match;\n\t\t\t\t}\n\t\t\t\tif (matched && result && outScore > result.outScore) {\n\t\t\t\t\treturn match;\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t},\n\t\t\tnull,\n\t\t);\n\n\t\tif (itemMatch) {\n\t\t\taccu.push({\n\t\t\t\tscore: itemMatch.outScore,\n\t\t\t\titem,\n\t\t\t});\n\t\t}\n\n\t\treturn accu;\n\t}, []);\n\n\tresults.sort((a, b) => {\n\t\treturn b.score - a.score;\n\t});\n\n\treturn results;\n}\n"],"mappings":"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"}
@@ -5,7 +5,7 @@ const CAMEL_BONUS = 30;
5
5
  const FIRST_LETTER_BONUS = 15;
6
6
  const LEADING_LETTER_PENALTY = -20;
7
7
  const MAX_LEADING_LETTER_PENALTY = -200;
8
- const UNMATCHED_LETTER_PENALTY = -5;
8
+ const UNMATCHED_LETTER_PENALTY = -2.5;
9
9
  const DEFAULT_KEYS = [{
10
10
  key: "properties.displayName",
11
11
  weight: 1.3
@@ -1 +1 @@
1
- {"version":3,"file":"sublimeSearch.mjs","names":["bestRecursiveMatches: number[]","recursiveMatches: number[]","values: Array<{ value: string; weight: number }>"],"sources":["../src/search/sublimeSearch.ts"],"sourcesContent":["/*\n * Constants and utility functions used for searching for node types in node creator component\n * based on https://github.com/forrestthewoods/lib_fts/blob/master/code/fts_fuzzy_match.js\n */\n\nconst SEQUENTIAL_BONUS = 60; // bonus for adjacent matches\nconst SEPARATOR_BONUS = 38; // bonus if match occurs after a separator\nconst CAMEL_BONUS = 30; // bonus if match is uppercase and prev is lower\nconst FIRST_LETTER_BONUS = 15; // bonus if the first letter is matched\n\nconst LEADING_LETTER_PENALTY = -20; // penalty applied for every letter in str before the first match\nconst MAX_LEADING_LETTER_PENALTY = -200; // maximum penalty for leading letters\nconst UNMATCHED_LETTER_PENALTY = -5;\n\nexport const DEFAULT_KEYS = [\n\t{ key: 'properties.displayName', weight: 1.3 },\n\t{ key: 'properties.codex.alias', weight: 1 },\n];\n\n/**\n * Returns true if each character in pattern is found sequentially within target\n * @param {*} pattern string\n * @param {*} target string\n */\nfunction fuzzyMatchSimple(pattern: string, target: string): boolean {\n\tlet patternIdx = 0;\n\tlet strIdx = 0;\n\n\twhile (patternIdx < pattern.length && strIdx < target.length) {\n\t\tconst patternChar = pattern.charAt(patternIdx).toLowerCase();\n\t\tconst targetChar = target.charAt(strIdx).toLowerCase();\n\t\tif (patternChar === targetChar) {\n\t\t\tpatternIdx++;\n\t\t}\n\t\t++strIdx;\n\t}\n\n\treturn pattern.length !== 0 && target.length !== 0 && patternIdx === pattern.length;\n}\n\nfunction fuzzyMatchRecursive(\n\tpattern: string,\n\ttarget: string,\n\tpatternCurIndex: number,\n\ttargetCurrIndex: number,\n\ttargetMatches: null | number[],\n\tmatches: number[],\n\tmaxMatches: number,\n\tnextMatch: number,\n\trecursionCount: number,\n\trecursionLimit: number,\n): { matched: boolean; outScore: number } {\n\tlet outScore = 0;\n\n\t// Return if recursion limit is reached.\n\tif (++recursionCount >= recursionLimit) {\n\t\treturn { matched: false, outScore };\n\t}\n\n\t// Return if we reached ends of strings.\n\tif (patternCurIndex === pattern.length || targetCurrIndex === target.length) {\n\t\treturn { matched: false, outScore };\n\t}\n\n\t// Recursion params\n\tlet recursiveMatch = false;\n\tlet bestRecursiveMatches: number[] = [];\n\tlet bestRecursiveScore = 0;\n\n\t// Loop through pattern and str looking for a match.\n\tlet firstMatch = true;\n\twhile (patternCurIndex < pattern.length && targetCurrIndex < target.length) {\n\t\t// Match found.\n\t\tif (pattern[patternCurIndex].toLowerCase() === target[targetCurrIndex].toLowerCase()) {\n\t\t\tif (nextMatch >= maxMatches) {\n\t\t\t\treturn { matched: false, outScore };\n\t\t\t}\n\n\t\t\tif (firstMatch && targetMatches) {\n\t\t\t\tmatches = [...targetMatches];\n\t\t\t\tfirstMatch = false;\n\t\t\t}\n\n\t\t\tconst recursiveMatches: number[] = [];\n\t\t\tconst recursiveResult = fuzzyMatchRecursive(\n\t\t\t\tpattern,\n\t\t\t\ttarget,\n\t\t\t\tpatternCurIndex,\n\t\t\t\ttargetCurrIndex + 1,\n\t\t\t\tmatches,\n\t\t\t\trecursiveMatches,\n\t\t\t\tmaxMatches,\n\t\t\t\tnextMatch,\n\t\t\t\trecursionCount,\n\t\t\t\trecursionLimit,\n\t\t\t);\n\n\t\t\tconst recursiveScore = recursiveResult.outScore;\n\t\t\tif (recursiveResult.matched) {\n\t\t\t\t// Pick best recursive score.\n\t\t\t\tif (!recursiveMatch || recursiveScore > bestRecursiveScore) {\n\t\t\t\t\tbestRecursiveMatches = [...recursiveMatches];\n\t\t\t\t\tbestRecursiveScore = recursiveScore;\n\t\t\t\t}\n\t\t\t\trecursiveMatch = true;\n\t\t\t}\n\n\t\t\tmatches[nextMatch++] = targetCurrIndex;\n\t\t\t++patternCurIndex;\n\t\t}\n\t\t++targetCurrIndex;\n\t}\n\n\tconst matched = patternCurIndex === pattern.length;\n\n\tif (matched) {\n\t\toutScore = 100;\n\n\t\t// Apply leading letter penalty (if not n8n-prefixed)\n\t\tif (!target.toLowerCase().startsWith('n8n')) {\n\t\t\tlet penalty = LEADING_LETTER_PENALTY * matches[0];\n\t\t\tpenalty = penalty < MAX_LEADING_LETTER_PENALTY ? MAX_LEADING_LETTER_PENALTY : penalty;\n\t\t\toutScore += penalty;\n\t\t}\n\n\t\t//Apply unmatched penalty\n\t\tconst unmatched = target.length - nextMatch;\n\t\toutScore += UNMATCHED_LETTER_PENALTY * unmatched;\n\n\t\t// Apply ordering bonuses\n\t\tfor (let i = 0; i < nextMatch; i++) {\n\t\t\tconst currIdx = matches[i];\n\n\t\t\tif (i > 0) {\n\t\t\t\tconst prevIdx = matches[i - 1];\n\t\t\t\tif (currIdx === prevIdx + 1) {\n\t\t\t\t\toutScore += SEQUENTIAL_BONUS;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check for bonuses based on neighbor character value.\n\t\t\tif (currIdx > 0) {\n\t\t\t\t// Camel case\n\t\t\t\tconst neighbor = target[currIdx - 1];\n\t\t\t\tconst curr = target[currIdx];\n\t\t\t\tif (neighbor !== neighbor.toUpperCase() && curr !== curr.toLowerCase()) {\n\t\t\t\t\toutScore += CAMEL_BONUS;\n\t\t\t\t}\n\t\t\t\tconst isNeighbourSeparator = neighbor === '_' || neighbor === ' ';\n\t\t\t\tif (isNeighbourSeparator) {\n\t\t\t\t\toutScore += SEPARATOR_BONUS;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// First letter\n\t\t\t\toutScore += FIRST_LETTER_BONUS;\n\t\t\t}\n\t\t}\n\n\t\t// Return best result\n\t\tif (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {\n\t\t\t// Recursive score is better than \"this\"\n\t\t\tmatches = [...bestRecursiveMatches];\n\t\t\toutScore = bestRecursiveScore;\n\t\t\treturn { matched: true, outScore };\n\t\t} else if (matched) {\n\t\t\t// \"this\" score is better than recursive\n\t\t\treturn { matched: true, outScore };\n\t\t} else {\n\t\t\treturn { matched: false, outScore };\n\t\t}\n\t}\n\treturn { matched: false, outScore };\n}\n\n/**\n * Does a fuzzy search to find pattern inside a string.\n * @param {*} pattern string pattern to search for\n * @param {*} target string string which is being searched\n * @returns [boolean, number] a boolean which tells if pattern was\n * found or not and a search score\n */\nfunction fuzzyMatch(pattern: string, target: string): { matched: boolean; outScore: number } {\n\tconst recursionCount = 0;\n\tconst recursionLimit = 5;\n\tconst matches: number[] = [];\n\tconst maxMatches = 256;\n\n\treturn fuzzyMatchRecursive(\n\t\tpattern,\n\t\ttarget,\n\t\t0 /* patternCurIndex */,\n\t\t0 /* strCurrIndex */,\n\t\tnull /* srcMatces */,\n\t\tmatches,\n\t\tmaxMatches,\n\t\t0 /* nextMatch */,\n\t\trecursionCount,\n\t\trecursionLimit,\n\t);\n}\n\n// prop = 'key'\n// prop = 'key1.key2'\n// prop = ['key1', 'key2']\nfunction getValue<T extends object>(obj: T, prop: string): unknown {\n\tif (obj.hasOwnProperty(prop)) {\n\t\treturn obj[prop as keyof T];\n\t}\n\n\tconst segments = prop.split('.');\n\n\tlet result = obj;\n\tlet i = 0;\n\twhile (result && i < segments.length) {\n\t\tconst key = segments[i] as keyof T;\n\t\tresult = result[key] as T;\n\t\ti++;\n\t}\n\treturn result;\n}\n\nexport function sublimeSearch<T extends object>(\n\tfilter: string,\n\tdata: readonly T[],\n\tkeys: Array<{ key: string; weight: number }> = DEFAULT_KEYS,\n): Array<{ score: number; item: T }> {\n\tconst results = data.reduce((accu: Array<{ score: number; item: T }>, item: T) => {\n\t\tlet values: Array<{ value: string; weight: number }> = [];\n\t\tkeys.forEach(({ key, weight }) => {\n\t\t\tconst value = getValue(item, key);\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tvalues = values.concat(value.map((v) => ({ value: v, weight })));\n\t\t\t} else if (typeof value === 'string') {\n\t\t\t\tvalues.push({\n\t\t\t\t\tvalue,\n\t\t\t\t\tweight,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\t// for each item, check every key and get maximum score\n\t\tconst itemMatch = values.reduce(\n\t\t\t(\n\t\t\t\tresult: null | { matched: boolean; outScore: number },\n\t\t\t\t{ value, weight }: { value: string; weight: number },\n\t\t\t) => {\n\t\t\t\tif (!fuzzyMatchSimple(filter, value)) {\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\n\t\t\t\tconst match = fuzzyMatch(filter, value);\n\t\t\t\tmatch.outScore *= weight;\n\n\t\t\t\tconst { matched, outScore } = match;\n\t\t\t\tif (!result && matched) {\n\t\t\t\t\treturn match;\n\t\t\t\t}\n\t\t\t\tif (matched && result && outScore > result.outScore) {\n\t\t\t\t\treturn match;\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t},\n\t\t\tnull,\n\t\t);\n\n\t\tif (itemMatch) {\n\t\t\taccu.push({\n\t\t\t\tscore: itemMatch.outScore,\n\t\t\t\titem,\n\t\t\t});\n\t\t}\n\n\t\treturn accu;\n\t}, []);\n\n\tresults.sort((a, b) => {\n\t\treturn b.score - a.score;\n\t});\n\n\treturn results;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sublimeSearch.mjs","names":["bestRecursiveMatches: number[]","recursiveMatches: number[]","values: Array<{ value: string; weight: number }>"],"sources":["../src/search/sublimeSearch.ts"],"sourcesContent":["/*\n * Constants and utility functions used for searching for node types in node creator component\n * based on https://github.com/forrestthewoods/lib_fts/blob/master/code/fts_fuzzy_match.js\n */\n\nconst SEQUENTIAL_BONUS = 60; // bonus for adjacent matches\nconst SEPARATOR_BONUS = 38; // bonus if match occurs after a separator\nconst CAMEL_BONUS = 30; // bonus if match is uppercase and prev is lower\nconst FIRST_LETTER_BONUS = 15; // bonus if the first letter is matched\n\nconst LEADING_LETTER_PENALTY = -20; // penalty applied for every letter in str before the first match\nconst MAX_LEADING_LETTER_PENALTY = -200; // maximum penalty for leading letters\nconst UNMATCHED_LETTER_PENALTY = -2.5;\n\nexport const DEFAULT_KEYS = [\n\t{ key: 'properties.displayName', weight: 1.3 },\n\t{ key: 'properties.codex.alias', weight: 1 },\n];\n\n/**\n * Returns true if each character in pattern is found sequentially within target\n * @param {*} pattern string\n * @param {*} target string\n */\nfunction fuzzyMatchSimple(pattern: string, target: string): boolean {\n\tlet patternIdx = 0;\n\tlet strIdx = 0;\n\n\twhile (patternIdx < pattern.length && strIdx < target.length) {\n\t\tconst patternChar = pattern.charAt(patternIdx).toLowerCase();\n\t\tconst targetChar = target.charAt(strIdx).toLowerCase();\n\t\tif (patternChar === targetChar) {\n\t\t\tpatternIdx++;\n\t\t}\n\t\t++strIdx;\n\t}\n\n\treturn pattern.length !== 0 && target.length !== 0 && patternIdx === pattern.length;\n}\n\nfunction fuzzyMatchRecursive(\n\tpattern: string,\n\ttarget: string,\n\tpatternCurIndex: number,\n\ttargetCurrIndex: number,\n\ttargetMatches: null | number[],\n\tmatches: number[],\n\tmaxMatches: number,\n\tnextMatch: number,\n\trecursionCount: number,\n\trecursionLimit: number,\n): { matched: boolean; outScore: number } {\n\tlet outScore = 0;\n\n\t// Return if recursion limit is reached.\n\tif (++recursionCount >= recursionLimit) {\n\t\treturn { matched: false, outScore };\n\t}\n\n\t// Return if we reached ends of strings.\n\tif (patternCurIndex === pattern.length || targetCurrIndex === target.length) {\n\t\treturn { matched: false, outScore };\n\t}\n\n\t// Recursion params\n\tlet recursiveMatch = false;\n\tlet bestRecursiveMatches: number[] = [];\n\tlet bestRecursiveScore = 0;\n\n\t// Loop through pattern and str looking for a match.\n\tlet firstMatch = true;\n\twhile (patternCurIndex < pattern.length && targetCurrIndex < target.length) {\n\t\t// Match found.\n\t\tif (pattern[patternCurIndex].toLowerCase() === target[targetCurrIndex].toLowerCase()) {\n\t\t\tif (nextMatch >= maxMatches) {\n\t\t\t\treturn { matched: false, outScore };\n\t\t\t}\n\n\t\t\tif (firstMatch && targetMatches) {\n\t\t\t\tmatches = [...targetMatches];\n\t\t\t\tfirstMatch = false;\n\t\t\t}\n\n\t\t\tconst recursiveMatches: number[] = [];\n\t\t\tconst recursiveResult = fuzzyMatchRecursive(\n\t\t\t\tpattern,\n\t\t\t\ttarget,\n\t\t\t\tpatternCurIndex,\n\t\t\t\ttargetCurrIndex + 1,\n\t\t\t\tmatches,\n\t\t\t\trecursiveMatches,\n\t\t\t\tmaxMatches,\n\t\t\t\tnextMatch,\n\t\t\t\trecursionCount,\n\t\t\t\trecursionLimit,\n\t\t\t);\n\n\t\t\tconst recursiveScore = recursiveResult.outScore;\n\t\t\tif (recursiveResult.matched) {\n\t\t\t\t// Pick best recursive score.\n\t\t\t\tif (!recursiveMatch || recursiveScore > bestRecursiveScore) {\n\t\t\t\t\tbestRecursiveMatches = [...recursiveMatches];\n\t\t\t\t\tbestRecursiveScore = recursiveScore;\n\t\t\t\t}\n\t\t\t\trecursiveMatch = true;\n\t\t\t}\n\n\t\t\tmatches[nextMatch++] = targetCurrIndex;\n\t\t\t++patternCurIndex;\n\t\t}\n\t\t++targetCurrIndex;\n\t}\n\n\tconst matched = patternCurIndex === pattern.length;\n\n\tif (matched) {\n\t\toutScore = 100;\n\n\t\t// Apply leading letter penalty (if not n8n-prefixed)\n\t\tif (!target.toLowerCase().startsWith('n8n')) {\n\t\t\tlet penalty = LEADING_LETTER_PENALTY * matches[0];\n\t\t\tpenalty = penalty < MAX_LEADING_LETTER_PENALTY ? MAX_LEADING_LETTER_PENALTY : penalty;\n\t\t\toutScore += penalty;\n\t\t}\n\n\t\t//Apply unmatched penalty\n\t\tconst unmatched = target.length - nextMatch;\n\t\toutScore += UNMATCHED_LETTER_PENALTY * unmatched;\n\n\t\t// Apply ordering bonuses\n\t\tfor (let i = 0; i < nextMatch; i++) {\n\t\t\tconst currIdx = matches[i];\n\n\t\t\tif (i > 0) {\n\t\t\t\tconst prevIdx = matches[i - 1];\n\t\t\t\tif (currIdx === prevIdx + 1) {\n\t\t\t\t\toutScore += SEQUENTIAL_BONUS;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check for bonuses based on neighbor character value.\n\t\t\tif (currIdx > 0) {\n\t\t\t\t// Camel case\n\t\t\t\tconst neighbor = target[currIdx - 1];\n\t\t\t\tconst curr = target[currIdx];\n\t\t\t\tif (neighbor !== neighbor.toUpperCase() && curr !== curr.toLowerCase()) {\n\t\t\t\t\toutScore += CAMEL_BONUS;\n\t\t\t\t}\n\t\t\t\tconst isNeighbourSeparator = neighbor === '_' || neighbor === ' ';\n\t\t\t\tif (isNeighbourSeparator) {\n\t\t\t\t\toutScore += SEPARATOR_BONUS;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// First letter\n\t\t\t\toutScore += FIRST_LETTER_BONUS;\n\t\t\t}\n\t\t}\n\n\t\t// Return best result\n\t\tif (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {\n\t\t\t// Recursive score is better than \"this\"\n\t\t\tmatches = [...bestRecursiveMatches];\n\t\t\toutScore = bestRecursiveScore;\n\t\t\treturn { matched: true, outScore };\n\t\t} else if (matched) {\n\t\t\t// \"this\" score is better than recursive\n\t\t\treturn { matched: true, outScore };\n\t\t} else {\n\t\t\treturn { matched: false, outScore };\n\t\t}\n\t}\n\treturn { matched: false, outScore };\n}\n\n/**\n * Does a fuzzy search to find pattern inside a string.\n * @param {*} pattern string pattern to search for\n * @param {*} target string string which is being searched\n * @returns [boolean, number] a boolean which tells if pattern was\n * found or not and a search score\n */\nfunction fuzzyMatch(pattern: string, target: string): { matched: boolean; outScore: number } {\n\tconst recursionCount = 0;\n\tconst recursionLimit = 5;\n\tconst matches: number[] = [];\n\tconst maxMatches = 256;\n\n\treturn fuzzyMatchRecursive(\n\t\tpattern,\n\t\ttarget,\n\t\t0 /* patternCurIndex */,\n\t\t0 /* strCurrIndex */,\n\t\tnull /* srcMatces */,\n\t\tmatches,\n\t\tmaxMatches,\n\t\t0 /* nextMatch */,\n\t\trecursionCount,\n\t\trecursionLimit,\n\t);\n}\n\n// prop = 'key'\n// prop = 'key1.key2'\n// prop = ['key1', 'key2']\nfunction getValue<T extends object>(obj: T, prop: string): unknown {\n\tif (obj.hasOwnProperty(prop)) {\n\t\treturn obj[prop as keyof T];\n\t}\n\n\tconst segments = prop.split('.');\n\n\tlet result = obj;\n\tlet i = 0;\n\twhile (result && i < segments.length) {\n\t\tconst key = segments[i] as keyof T;\n\t\tresult = result[key] as T;\n\t\ti++;\n\t}\n\treturn result;\n}\n\nexport function sublimeSearch<T extends object>(\n\tfilter: string,\n\tdata: readonly T[],\n\tkeys: Array<{ key: string; weight: number }> = DEFAULT_KEYS,\n): Array<{ score: number; item: T }> {\n\tconst results = data.reduce((accu: Array<{ score: number; item: T }>, item: T) => {\n\t\tlet values: Array<{ value: string; weight: number }> = [];\n\t\tkeys.forEach(({ key, weight }) => {\n\t\t\tconst value = getValue(item, key);\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tvalues = values.concat(value.map((v) => ({ value: v, weight })));\n\t\t\t} else if (typeof value === 'string') {\n\t\t\t\tvalues.push({\n\t\t\t\t\tvalue,\n\t\t\t\t\tweight,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\t// for each item, check every key and get maximum score\n\t\tconst itemMatch = values.reduce(\n\t\t\t(\n\t\t\t\tresult: null | { matched: boolean; outScore: number },\n\t\t\t\t{ value, weight }: { value: string; weight: number },\n\t\t\t) => {\n\t\t\t\tif (!fuzzyMatchSimple(filter, value)) {\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\n\t\t\t\tconst match = fuzzyMatch(filter, value);\n\t\t\t\tmatch.outScore *= weight;\n\n\t\t\t\tconst { matched, outScore } = match;\n\t\t\t\tif (!result && matched) {\n\t\t\t\t\treturn match;\n\t\t\t\t}\n\t\t\t\tif (matched && result && outScore > result.outScore) {\n\t\t\t\t\treturn match;\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t},\n\t\t\tnull,\n\t\t);\n\n\t\tif (itemMatch) {\n\t\t\taccu.push({\n\t\t\t\tscore: itemMatch.outScore,\n\t\t\t\titem,\n\t\t\t});\n\t\t}\n\n\t\treturn accu;\n\t}, []);\n\n\tresults.sort((a, b) => {\n\t\treturn b.score - a.score;\n\t});\n\n\treturn results;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@n8n/utils",
3
3
  "type": "module",
4
- "version": "1.32.0",
4
+ "version": "1.33.0",
5
5
  "files": [
6
6
  "dist",
7
7
  "LICENSE_EE.md",
@@ -39,8 +39,8 @@
39
39
  "vite": "^8.0.2",
40
40
  "vitest": "^4.1.1",
41
41
  "@n8n/eslint-config": "0.0.1",
42
- "@n8n/typescript-config": "1.4.0",
43
- "@n8n/vitest-config": "1.12.0"
42
+ "@n8n/vitest-config": "1.13.0",
43
+ "@n8n/typescript-config": "1.4.0"
44
44
  },
45
45
  "license": "SEE LICENSE IN LICENSE.md",
46
46
  "homepage": "https://n8n.io",