@octocodeai/octocode-engine 16.5.0 → 16.6.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.
@@ -1,4 +1,4 @@
1
- import type { ExactPosition, FuzzyPosition } from './types.js';
1
+ import type { CodeSnippet, ExactPosition, FuzzyPosition } from './types.js';
2
2
  export declare class SymbolResolutionError extends Error {
3
3
  readonly symbolName: string;
4
4
  readonly lineHint: number;
@@ -15,6 +15,11 @@ interface ResolvedSymbol {
15
15
  lineOffset: number;
16
16
  lineContent: string;
17
17
  }
18
+ export interface ImportAliasDefinitionInput {
19
+ anchorUri: string;
20
+ symbolName: string;
21
+ locations: CodeSnippet[];
22
+ }
18
23
  export declare function resolveSymbolPosition(filePath: string, symbolName: string, lineHint?: number, orderHint?: number): Promise<ResolvedSymbol>;
19
24
  export declare function resolveSymbolPosition(content: string, fuzzy: FuzzyPosition): ResolvedSymbol;
20
25
  export declare class SymbolResolver {
@@ -23,4 +28,5 @@ export declare class SymbolResolver {
23
28
  resolvePosition(filePath: string, fuzzy: FuzzyPosition): Promise<ResolvedSymbol>;
24
29
  resolvePositionFromContent(content: string, fuzzy: FuzzyPosition): ResolvedSymbol;
25
30
  }
31
+ export declare function resolveImportAliasDefinitions({ anchorUri, symbolName, locations, }: ImportAliasDefinitionInput): Promise<CodeSnippet[]>;
26
32
  export {};
@@ -1,3 +1,5 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
1
3
  import { nativeBinding } from './native.js';
2
4
  export class SymbolResolutionError extends Error {
3
5
  symbolName;
@@ -71,3 +73,118 @@ export class SymbolResolver {
71
73
  }
72
74
  }
73
75
  }
76
+ export async function resolveImportAliasDefinitions({ anchorUri, symbolName, locations, }) {
77
+ const resolved = await Promise.all(locations.map(location => resolveImportAliasDefinition(anchorUri, symbolName, location)));
78
+ return resolved;
79
+ }
80
+ async function resolveImportAliasDefinition(anchorUri, symbolName, location) {
81
+ const locationPath = snippetPath(location.uri, anchorUri);
82
+ if (!isSamePath(locationPath, anchorUri))
83
+ return location;
84
+ if (!isImportSnippet(location.content))
85
+ return location;
86
+ const importTarget = importTargetForSymbol(location.content, symbolName);
87
+ if (!importTarget?.moduleSpecifier.startsWith('.'))
88
+ return location;
89
+ const targetPath = await resolveLocalModulePath(locationPath, importTarget.moduleSpecifier);
90
+ if (!targetPath)
91
+ return location;
92
+ const content = await readFile(targetPath, 'utf-8');
93
+ const declaration = findExportedDeclaration(content, importTarget.exportedName);
94
+ if (!declaration)
95
+ return location;
96
+ return {
97
+ uri: targetPath,
98
+ range: {
99
+ start: { line: declaration.line - 1, character: declaration.character },
100
+ end: { line: declaration.line - 1, character: declaration.character },
101
+ },
102
+ displayRange: { startLine: declaration.line, endLine: declaration.line },
103
+ content: declaration.content,
104
+ };
105
+ }
106
+ function isImportSnippet(content) {
107
+ return /^\s*import\s/.test(content.trim());
108
+ }
109
+ function importTargetForSymbol(importLine, symbolName) {
110
+ const namedImport = importLine.match(/import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/);
111
+ const namedImports = namedImport?.[1];
112
+ const namedModulePath = namedImport?.[2];
113
+ if (namedImports && namedModulePath) {
114
+ for (const part of namedImports.split(',')) {
115
+ const [original, alias] = part
116
+ .trim()
117
+ .split(/\s+as\s+/)
118
+ .map(value => value.trim());
119
+ if (alias === symbolName || original === symbolName) {
120
+ return {
121
+ moduleSpecifier: namedModulePath,
122
+ exportedName: original,
123
+ };
124
+ }
125
+ }
126
+ }
127
+ const defaultImport = importLine.match(/import\s+([A-Za-z_$][\w$]*)\s+from\s*['"]([^'"]+)['"]/);
128
+ const defaultName = defaultImport?.[1];
129
+ const defaultModulePath = defaultImport?.[2];
130
+ if (defaultName === symbolName && defaultModulePath) {
131
+ return { moduleSpecifier: defaultModulePath, exportedName: symbolName };
132
+ }
133
+ return undefined;
134
+ }
135
+ async function resolveLocalModulePath(importerPath, moduleSpecifier) {
136
+ const basePath = path.resolve(path.dirname(filePathFromUri(importerPath)), moduleSpecifier);
137
+ const extension = path.extname(basePath);
138
+ const sourcePath = extension ? basePath.slice(0, -extension.length) : basePath;
139
+ const candidates = [
140
+ ...(extension === '.js' || extension === '.jsx'
141
+ ? [`${sourcePath}.ts`, `${sourcePath}.tsx`]
142
+ : []),
143
+ basePath,
144
+ `${basePath}.ts`,
145
+ `${basePath}.tsx`,
146
+ `${basePath}.js`,
147
+ `${basePath}.jsx`,
148
+ path.join(basePath, 'index.ts'),
149
+ path.join(basePath, 'index.tsx'),
150
+ path.join(basePath, 'index.js'),
151
+ path.join(basePath, 'index.jsx'),
152
+ ];
153
+ for (const candidate of candidates) {
154
+ try {
155
+ await readFile(candidate, 'utf-8');
156
+ return candidate;
157
+ }
158
+ catch {
159
+ // Try the next TypeScript/JavaScript resolution candidate.
160
+ }
161
+ }
162
+ return undefined;
163
+ }
164
+ function findExportedDeclaration(content, symbolName) {
165
+ const escaped = symbolName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
166
+ const declarationPattern = new RegExp(`^\\s*export\\s+(?:default\\s+)?(?:async\\s+)?(?:function|class|interface|type|const|let|var|enum)\\s+${escaped}\\b`);
167
+ const lines = content.split(/\r?\n/);
168
+ for (const [index, line] of lines.entries()) {
169
+ if (!declarationPattern.test(line))
170
+ continue;
171
+ return {
172
+ line: index + 1,
173
+ character: Math.max(0, line.indexOf(symbolName)),
174
+ content: line.trim(),
175
+ };
176
+ }
177
+ return undefined;
178
+ }
179
+ function isSamePath(left, right) {
180
+ return (path.resolve(filePathFromUri(left)) === path.resolve(filePathFromUri(right)));
181
+ }
182
+ function snippetPath(uri, anchorUri) {
183
+ const filePath = filePathFromUri(uri);
184
+ if (path.isAbsolute(filePath))
185
+ return filePath;
186
+ return path.resolve(path.dirname(filePathFromUri(anchorUri)), filePath);
187
+ }
188
+ function filePathFromUri(uri) {
189
+ return uri.startsWith('file://') ? new URL(uri).pathname : uri;
190
+ }
@@ -1,2 +1,2 @@
1
- export declare function resolveWorkspaceRootForFile(filePath: string, _workspaceRoot?: string): Promise<string>;
1
+ export declare function resolveWorkspaceRootForFile(filePath: string, workspaceRoot?: string): Promise<string>;
2
2
  export declare function findWorkspaceRoot(filePath: string): Promise<string>;
@@ -1,5 +1,7 @@
1
1
  import { nativeBinding } from './native.js';
2
- export async function resolveWorkspaceRootForFile(filePath, _workspaceRoot) {
2
+ export async function resolveWorkspaceRootForFile(filePath, workspaceRoot) {
3
+ if (workspaceRoot)
4
+ return workspaceRoot;
3
5
  return nativeBinding.resolveWorkspaceRootForFile(filePath);
4
6
  }
5
7
  export async function findWorkspaceRoot(filePath) {
@@ -0,0 +1,11 @@
1
+ export declare const DISCOVERY_IGNORED_FOLDER_NAMES: string[];
2
+ export declare const DISCOVERY_IGNORED_FILE_NAMES: string[];
3
+ export declare const DISCOVERY_IGNORED_FILE_EXTENSIONS: string[];
4
+ export interface DiscoveryExtensionOptions {
5
+ lowercase?: boolean;
6
+ fallback?: string;
7
+ leadingDot?: boolean;
8
+ }
9
+ export declare function shouldIgnoreDiscoveryDir(folderName: string): boolean;
10
+ export declare function shouldIgnoreDiscoveryFile(filePath: string): boolean;
11
+ export declare function getDiscoveryExtension(filePath: string, options?: DiscoveryExtensionOptions): string;
@@ -0,0 +1,261 @@
1
+ export const DISCOVERY_IGNORED_FOLDER_NAMES = [
2
+ '.github',
3
+ '.git',
4
+ '.vscode',
5
+ '.devcontainer',
6
+ '.config',
7
+ '.cargo',
8
+ '.changeset',
9
+ '.husky',
10
+ '.aspect',
11
+ '.eslint-plugin-local',
12
+ '.yarn',
13
+ '.gemini',
14
+ '.ng-dev',
15
+ '.configurations',
16
+ '.tx',
17
+ 'dist',
18
+ 'build',
19
+ 'out',
20
+ 'output',
21
+ 'target',
22
+ 'release',
23
+ 'node_modules',
24
+ 'vendor',
25
+ 'third_party',
26
+ 'tmp',
27
+ 'temp',
28
+ 'cache',
29
+ '.cache',
30
+ '.tmp',
31
+ '.pytest_cache',
32
+ '.tox',
33
+ '.venv',
34
+ '.mypy_cache',
35
+ '.next',
36
+ '.svelte-kit',
37
+ '.turbo',
38
+ '.angular',
39
+ '.dart_tool',
40
+ '__pycache__',
41
+ '.ruff_cache',
42
+ '.nox',
43
+ 'htmlcov',
44
+ 'cover',
45
+ '.gradle',
46
+ '.m2',
47
+ '.sbt',
48
+ '.bloop',
49
+ '.metals',
50
+ '.bsp',
51
+ 'bin',
52
+ 'obj',
53
+ 'TestResults',
54
+ 'BenchmarkDotNet.Artifacts',
55
+ '.vendor-new',
56
+ 'Godeps',
57
+ 'composer.phar',
58
+ '.phpunit.result.cache',
59
+ '.bundle',
60
+ '.byebug_history',
61
+ '.rspec_status',
62
+ '.mvn',
63
+ '.aws',
64
+ '.gcp',
65
+ 'fastlane',
66
+ 'DerivedData',
67
+ 'xcuserdata',
68
+ 'local.properties',
69
+ '.navigation',
70
+ 'captures',
71
+ '.externalNativeBuild',
72
+ '.cxx',
73
+ '.idea',
74
+ '.idea_modules',
75
+ '.vs',
76
+ '.history',
77
+ 'coverage',
78
+ '.nyc_output',
79
+ '.DS_Store',
80
+ ];
81
+ export const DISCOVERY_IGNORED_FILE_NAMES = [
82
+ 'package-lock.json',
83
+ '.secrets',
84
+ '.secret',
85
+ 'secrets.json',
86
+ 'secrets.yaml',
87
+ 'secrets.yml',
88
+ 'credentials.json',
89
+ 'credentials.yaml',
90
+ 'credentials.yml',
91
+ 'auth.json',
92
+ 'auth.yaml',
93
+ 'auth.yml',
94
+ 'api-keys.json',
95
+ 'api_keys.json',
96
+ 'service-account.json',
97
+ 'service_account.json',
98
+ 'private-key.pem',
99
+ 'private_key.pem',
100
+ 'id_rsa',
101
+ 'id_dsa',
102
+ 'id_ecdsa',
103
+ 'id_ed25519',
104
+ 'keyfile',
105
+ 'keyfile.json',
106
+ 'gcloud-service-key.json',
107
+ 'firebase-adminsdk.json',
108
+ 'google-services.json',
109
+ 'GoogleService-Info.plist',
110
+ '.DS_Store',
111
+ 'Thumbs.db',
112
+ 'db.sqlite3',
113
+ 'db.sqlite3-journal',
114
+ '.eslintcache',
115
+ '.stylelintcache',
116
+ '.node_repl_history',
117
+ '.yarn-integrity',
118
+ 'celerybeat-schedule',
119
+ 'celerybeat.pid',
120
+ 'ThirdPartyNoticeText.txt',
121
+ 'ThirdPartyNotices.txt',
122
+ 'cglicenses.json',
123
+ 'cgmanifest.json',
124
+ ];
125
+ export const DISCOVERY_IGNORED_FILE_EXTENSIONS = [
126
+ '.lock',
127
+ '.tmp',
128
+ '.temp',
129
+ '.cache',
130
+ '.bak',
131
+ '.backup',
132
+ '.orig',
133
+ '.swp',
134
+ '.swo',
135
+ '.rej',
136
+ '.pid',
137
+ '.seed',
138
+ '.old',
139
+ '.save',
140
+ '.temporary',
141
+ '.exe',
142
+ '.dll',
143
+ '.so',
144
+ '.dylib',
145
+ '.a',
146
+ '.lib',
147
+ '.o',
148
+ '.obj',
149
+ '.bin',
150
+ '.class',
151
+ '.pdb',
152
+ '.dSYM',
153
+ '.pyc',
154
+ '.pyo',
155
+ '.pyd',
156
+ '.jar',
157
+ '.war',
158
+ '.ear',
159
+ '.nar',
160
+ '.db',
161
+ '.sqlite',
162
+ '.sqlite3',
163
+ '.mdb',
164
+ '.accdb',
165
+ '.zip',
166
+ '.tar',
167
+ '.gz',
168
+ '.bz2',
169
+ '.xz',
170
+ '.lz',
171
+ '.lzma',
172
+ '.Z',
173
+ '.tgz',
174
+ '.rar',
175
+ '.7z',
176
+ '.deb',
177
+ '.rpm',
178
+ '.pkg',
179
+ '.dmg',
180
+ '.msi',
181
+ '.appx',
182
+ '.snap',
183
+ '.map',
184
+ '.d.ts.map',
185
+ '.min.js',
186
+ '.min.css',
187
+ '.key',
188
+ '.pem',
189
+ '.p12',
190
+ '.pfx',
191
+ '.crt',
192
+ '.cer',
193
+ '.der',
194
+ '.csr',
195
+ '.jks',
196
+ '.keystore',
197
+ '.truststore',
198
+ '.kate-swp',
199
+ '.gnome-desktop',
200
+ '.sublime-project',
201
+ '.sublime-workspace',
202
+ '.iml',
203
+ '.iws',
204
+ '.ipr',
205
+ '.patch',
206
+ '.diff',
207
+ '.prof',
208
+ '.profile',
209
+ '.trace',
210
+ '.perf',
211
+ '.coverage',
212
+ '.egg-info',
213
+ '.egg',
214
+ '.mo',
215
+ '.pot',
216
+ '.setup',
217
+ '.paket.template',
218
+ ];
219
+ export function shouldIgnoreDiscoveryDir(folderName) {
220
+ const normalized = folderName.replace(/\\/g, '/');
221
+ const name = normalized.split('/').filter(Boolean).pop() ?? normalized;
222
+ return DISCOVERY_IGNORED_FOLDER_NAMES.includes(name);
223
+ }
224
+ export function shouldIgnoreDiscoveryFile(filePath) {
225
+ const normalizedPath = filePath.replace(/\\/g, '/');
226
+ const fileName = normalizedPath.split('/').pop() || '';
227
+ for (const ext of DISCOVERY_IGNORED_FILE_EXTENSIONS) {
228
+ if (fileName.endsWith(ext)) {
229
+ return true;
230
+ }
231
+ }
232
+ if (DISCOVERY_IGNORED_FILE_NAMES.includes(fileName)) {
233
+ return true;
234
+ }
235
+ const pathParts = normalizedPath.split('/');
236
+ for (const part of pathParts) {
237
+ if (DISCOVERY_IGNORED_FOLDER_NAMES.includes(part)) {
238
+ return true;
239
+ }
240
+ }
241
+ return false;
242
+ }
243
+ export function getDiscoveryExtension(filePath, options) {
244
+ const basename = filePath.split(/[\\/]/).pop() ?? filePath;
245
+ const fallback = options?.fallback ?? '';
246
+ let ext = fallback;
247
+ if (basename.startsWith('.')) {
248
+ const dotfileExt = basename.slice(1);
249
+ ext = dotfileExt.includes('.')
250
+ ? (basename.split('.').pop() ?? fallback)
251
+ : dotfileExt;
252
+ }
253
+ else {
254
+ const lastDot = basename.lastIndexOf('.');
255
+ ext = lastDot === -1 ? fallback : basename.slice(lastDot + 1);
256
+ }
257
+ const normalized = options?.lowercase ? ext.toLowerCase() : ext;
258
+ if (!normalized || !options?.leadingDot)
259
+ return normalized;
260
+ return normalized.startsWith('.') ? normalized : `.${normalized}`;
261
+ }
@@ -7,6 +7,8 @@ export { withSecurityValidation, withBasicSecurityValidation, configureSecurity,
7
7
  export type { SecurityDepsConfig } from './withSecurityValidation.js';
8
8
  export { extractResearchFields, extractRepoOwnerFromParams, } from './paramExtractors.js';
9
9
  export { shouldIgnore, shouldIgnorePath, shouldIgnoreFile, } from './ignoredPathFilter.js';
10
+ export { DISCOVERY_IGNORED_FILE_EXTENSIONS, DISCOVERY_IGNORED_FILE_NAMES, DISCOVERY_IGNORED_FOLDER_NAMES, getDiscoveryExtension, shouldIgnoreDiscoveryDir, shouldIgnoreDiscoveryFile, } from './discoveryFilter.js';
11
+ export type { DiscoveryExtensionOptions } from './discoveryFilter.js';
10
12
  export { redactPath } from './pathUtils.js';
11
13
  export { ALLOWED_COMMANDS, DANGEROUS_PATTERNS, PATTERN_DANGEROUS_PATTERNS, } from './securityConstants.js';
12
14
  export { IGNORED_PATH_PATTERNS } from './pathPatterns.js';
@@ -5,6 +5,7 @@ export { validateCommand } from './commandValidator.js';
5
5
  export { withSecurityValidation, withBasicSecurityValidation, configureSecurity, } from './withSecurityValidation.js';
6
6
  export { extractResearchFields, extractRepoOwnerFromParams, } from './paramExtractors.js';
7
7
  export { shouldIgnore, shouldIgnorePath, shouldIgnoreFile, } from './ignoredPathFilter.js';
8
+ export { DISCOVERY_IGNORED_FILE_EXTENSIONS, DISCOVERY_IGNORED_FILE_NAMES, DISCOVERY_IGNORED_FOLDER_NAMES, getDiscoveryExtension, shouldIgnoreDiscoveryDir, shouldIgnoreDiscoveryFile, } from './discoveryFilter.js';
8
9
  export { redactPath } from './pathUtils.js';
9
10
  export { ALLOWED_COMMANDS, DANGEROUS_PATTERNS, PATTERN_DANGEROUS_PATTERNS, } from './securityConstants.js';
10
11
  export { IGNORED_PATH_PATTERNS } from './pathPatterns.js';
@@ -2,9 +2,6 @@ import type { ISanitizer, ToolResult } from './types.js';
2
2
  export interface SecurityDepsConfig {
3
3
  sanitizer?: ISanitizer;
4
4
  defaultTimeoutMs?: number;
5
- logToolCall?: (toolName: string, repos: string[], goal?: string, rGoal?: string, reasoning?: string) => Promise<void>;
6
- logSessionError?: (toolName: string, errorCode: string) => Promise<void>;
7
- isLoggingEnabled?: () => boolean;
8
5
  }
9
6
  export declare function configureSecurity(deps: SecurityDepsConfig): void;
10
7
  export declare function withSecurityValidation<T extends Record<string, unknown>, TAuth = unknown>(toolName: string, toolHandler: (sanitizedArgs: T, authInfo?: TAuth, sessionId?: string) => Promise<ToolResult>, options?: {
@@ -1,6 +1,4 @@
1
1
  import { ContentSanitizer } from './contentSanitizer.js';
2
- import { extractResearchFields, extractRepoOwnerFromParams, } from './paramExtractors.js';
3
- const SECURITY_VALIDATION_FAILED_CODE = 'TOOL_SECURITY_VALIDATION_FAILED';
4
2
  const DEFAULT_TOOL_TIMEOUT_MS = 60_000;
5
3
  let _deps = {};
6
4
  function getSanitizer() {
@@ -58,15 +56,9 @@ async function runSecure(opts) {
58
56
  }
59
57
  const sanitizedParams = validation.sanitizedParams;
60
58
  const rawResult = await withToolTimeout(toolName, handler(sanitizedParams, authInfo, sessionId), signal, timeoutMs);
61
- if (!rawResult.isError && _deps.isLoggingEnabled?.()) {
62
- handleBulk(toolName, sanitizedParams);
63
- }
64
59
  return rawResult;
65
60
  }
66
61
  catch (error) {
67
- _deps
68
- .logSessionError?.(toolName, SECURITY_VALIDATION_FAILED_CODE)
69
- .catch(() => { });
70
62
  return createErrorResult(`Security validation error: ${error instanceof Error ? error.message : 'Unknown error'}`);
71
63
  }
72
64
  }
@@ -92,16 +84,3 @@ export function withBasicSecurityValidation(toolHandler, toolName, options) {
92
84
  timeoutMs: options?.timeoutMs,
93
85
  });
94
86
  }
95
- function handleBulk(toolName, params) {
96
- const queries = params.queries;
97
- const items = queries && Array.isArray(queries) && queries.length > 0
98
- ? queries
99
- : [params];
100
- for (const item of items) {
101
- const repos = extractRepoOwnerFromParams(item);
102
- const fields = extractResearchFields(item);
103
- _deps
104
- .logToolCall?.(toolName, repos, fields.mainResearchGoal, fields.researchGoal, fields.reasoning)
105
- .catch(() => { });
106
- }
107
- }
package/index.cjs CHANGED
@@ -75,13 +75,38 @@ function loadNativeBinding() {
75
75
  join(__dirname, '..', '..', 'runtime', 'engine', `${binaryName}.${key}.node`),
76
76
  ]
77
77
 
78
+ const loadErrors = []
78
79
  for (const candidate of candidates) {
79
80
  if (existsSync(candidate)) {
80
- return require(candidate)
81
+ try {
82
+ return require(candidate)
83
+ } catch (err) {
84
+ // The .node file exists but the host refused to load it — most often a
85
+ // hardened/sandboxed runtime (e.g. an editor/app-embedded Node) that
86
+ // blocks native addons, or an ABI/arch mismatch. Distinguish this from
87
+ // "binary missing" so callers don't report a generic tool failure.
88
+ loadErrors.push(`${candidate}: ${err && err.message ? err.message : err}`)
89
+ }
81
90
  }
82
91
  }
83
92
 
84
- return require(`${packageName}-${key}`)
93
+ try {
94
+ return require(`${packageName}-${key}`)
95
+ } catch (err) {
96
+ loadErrors.push(`${packageName}-${key}: ${err && err.message ? err.message : err}`)
97
+ }
98
+
99
+ const detail = loadErrors.length
100
+ ? `\nNative load attempts:\n - ${loadErrors.join('\n - ')}`
101
+ : `\nNo native binary found for ${key} (looked in ${candidates.length} locations and the ${packageName}-${key} package).`
102
+ const error = new Error(
103
+ `@octocodeai/octocode-engine: could not load the native ${binaryName} addon for ${key}.` +
104
+ detail +
105
+ `\nIf a .node file exists above but failed to load, the current Node runtime is likely sandboxed and rejects ` +
106
+ `native addons (e.g. an editor/app-embedded Node). Re-run with system Node (\`which node\`).`
107
+ )
108
+ error.code = 'OCTOCODE_ENGINE_NATIVE_LOAD_FAILED'
109
+ throw error
85
110
  }
86
111
 
87
112
  const nativeBinding = loadNativeBinding()
@@ -90,6 +115,11 @@ nativeBinding.MINIFY_CONFIG = nativeBinding.getMINIFY_CONFIG()
90
115
  nativeBinding.SUPPORTED_SIGNATURE_EXTENSIONS = Object.freeze(
91
116
  nativeBinding.getSupportedSignatureExtensions().sort()
92
117
  )
118
+ nativeBinding.SUPPORTED_GRAPH_FACT_EXTENSIONS = Object.freeze(
119
+ typeof nativeBinding.getSupportedGraphFactExtensions === 'function'
120
+ ? nativeBinding.getSupportedGraphFactExtensions().sort()
121
+ : []
122
+ )
93
123
  nativeBinding.SUPPORTED_STRUCTURAL_EXTENSIONS = Object.freeze(
94
124
  nativeBinding.getSupportedStructuralExtensions().sort()
95
125
  )