addons/isl-server/src/__generated__/node-edenfs-notifications-client/index.jsblame
View source
b69ab311/**
b69ab312 * This software contains information and intellectual property that is
b69ab313 * confidential and proprietary to Facebook, Inc. and its affiliates.
b69ab314 *
b69ab315 * @generated
b69ab316 */
b69ab317
b69ab318/* eslint-disable */
b69ab319
b69ab3110/*
b69ab3111 * This file is synced between fbcode/eden/fs/facebook/prototypes/node-edenfs-notifications-client/index.js.
b69ab3112 * The authoritative copy is the one in eden/fs/.
b69ab3113 * Use `yarn sync-edenfs-notifications` to perform the sync.
b69ab3114 *
b69ab3115 * This file is intended to be self contained so it may be copied/referenced from other extensions,
b69ab3116 * which is why it should not import anything and why it reimplements many types.
b69ab3117 */
b69ab3118
b69ab3119/**
b69ab3120 * JavaScript interface for EdenFS CLI notify endpoint
b69ab3121 *
b69ab3122 * This module provides a JavaScript wrapper around the EdenFS CLI notify commands,
b69ab3123 * allowing you to monitor filesystem changes in EdenFS mounts.
b69ab3124 *
b69ab3125 * @author cqd
b69ab3126 * @version 1.0.0
b69ab3127 *
b69ab3128 * @format
b69ab3129 * @flow
b69ab3130 * @ts-check
b69ab3131 */
b69ab3132
b69ab3133const {spawn, execFile} = require('child_process');
b69ab3134const {EventEmitter} = require('events');
b69ab3135const path = require('path');
b69ab3136
b69ab3137/**
b69ab3138 * EdenFS Notifications Client
b69ab3139 * Provides methods to interact with EdenFS notifications via the EdenFS CLI
b69ab3140 */
b69ab3141class EdenFSNotificationsClient extends EventEmitter {
b69ab3142 /** @type {string} */
b69ab3143 mountPoint;
b69ab3144 /** @type {number} */
b69ab3145 timeout;
b69ab3146 /** @type {string} */
b69ab3147 edenBinaryPath;
b69ab3148
b69ab3149 DEFAULT_EDENFS_RECONNECT_DELAY_MS = 100;
b69ab3150 MAXIMUM_EDENFS_RECONNECT_DELAY_MS = 60 * 1000;
b69ab3151
b69ab3152 constructor(options) {
b69ab3153 super();
b69ab3154 this.mountPoint = options?.mountPoint ?? null;
b69ab3155 this.timeout = options?.timeout ?? 30000; // 30 seconds default timeout
b69ab3156 this.edenBinaryPath = options?.edenBinaryPath ?? 'eden';
b69ab3157 }
b69ab3158
b69ab3159 /**
b69ab3160 * Get the current EdenFS status
b69ab3161 * @returns {boolean} Edenfs running/not running
b69ab3162 */
b69ab3163 async getStatus(options = {}) {
b69ab3164 const args = ['status'];
b69ab3165
b69ab3166 if (options.useCase) {
b69ab3167 args.push('--use-case', options.useCase);
b69ab3168 } else {
b69ab3169 args.push('--use-case', 'node-client');
b69ab3170 }
b69ab3171
b69ab3172 return new Promise((resolve, reject) => {
b69ab3173 execFile(this.edenBinaryPath, args, {timeout: this.timeout}, (error, stdout, stderr) => {
b69ab3174 if (error) {
b69ab3175 reject(
b69ab3176 new Error(
b69ab3177 `Failed to get status: ${error.message}\nStdout: ${stdout}\nStderr: ${stderr}`,
b69ab3178 ),
b69ab3179 );
b69ab3180 return;
b69ab3181 }
b69ab3182
b69ab3183 try {
b69ab3184 const result = stdout.trim();
b69ab3185 resolve(result);
b69ab3186 } catch (parseError) {
b69ab3187 reject(new Error(`Failed to parse response: ${parseError.message}\nStdout: ${stdout}`));
b69ab3188 }
b69ab3189 });
b69ab3190 });
b69ab3191 }
b69ab3192
b69ab3193 /**
b69ab3194 * Wait until EdenFS is ready
b69ab3195 * @returns {Promise<boolean>} True=Healthy, False=Timeout
b69ab3196 */
b69ab3197 async waitReady(options={}) {
b69ab3198 const maxDelay = this.MAXIMUM_EDENFS_RECONNECT_DELAY_MS;
b69ab3199 let delay = this.DEFAULT_EDENFS_RECONNECT_DELAY_MS;
b69ab31100 const start = Date.now();
b69ab31101 const deadline = start + (options.timeout ?? this.timeout);
b69ab31102
b69ab31103 // Helper: sleep for ms
b69ab31104 const sleep = ms => new Promise(res => setTimeout(res, ms));
b69ab31105
b69ab31106 // If timeout=0, wait forever
b69ab31107 while (options.timeout == 0 || Date.now() < deadline) {
b69ab31108 try {
b69ab31109 const status = await this.getStatus({useCase: options.useCase ?? undefined});
b69ab31110 // Consider any truthy/non-empty status string as "healthy"
b69ab31111 if (status && typeof status === 'string' && status.trim().length > 0) {
b69ab31112 return true;
b69ab31113 }
b69ab31114 } catch (e) {
b69ab31115 // Swallow and retry with backoff
b69ab31116 }
b69ab31117
b69ab31118 // Exponential backoff (capped)
b69ab31119 await sleep(delay);
b69ab31120 delay = Math.min(delay * 2, maxDelay);
b69ab31121 }
b69ab31122
b69ab31123 return false;
b69ab31124
b69ab31125 }
b69ab31126
b69ab31127 /**
b69ab31128 * Get the current EdenFS journal position
b69ab31129 * @param {Object} options - Options for changes-since command
b69ab31130 * @param {string} options.useCase - Use case for the command
b69ab31131 * @param {string} options.mountPoint - Path to the mount point (optional if set in constructor)
b69ab31132 * @returns {Promise<JournalPosition|string>} Journal position
b69ab31133 */
b69ab31134 async getPosition(options = {}) {
b69ab31135 const mountPoint = options?.mountPoint ?? this.mountPoint;
b69ab31136 const args = ['notify', 'get-position'];
b69ab31137
b69ab31138 if (options.useCase) {
b69ab31139 args.push('--use-case', options.useCase);
b69ab31140 } else {
b69ab31141 args.push('--use-case', 'node-client');
b69ab31142 }
b69ab31143
b69ab31144 if (mountPoint) {
b69ab31145 args.push(mountPoint);
b69ab31146 }
b69ab31147
b69ab31148 return new Promise((resolve, reject) => {
b69ab31149 execFile(this.edenBinaryPath, args, {timeout: this.timeout}, (error, stdout, stderr) => {
b69ab31150 if (error) {
b69ab31151 reject(
b69ab31152 new Error(
b69ab31153 `Failed to get position: ${error.message}\nStdout: ${stdout}\nStderr: ${stderr}`,
b69ab31154 ),
b69ab31155 );
b69ab31156 return;
b69ab31157 }
b69ab31158
b69ab31159 try {
b69ab31160 const result = stdout.trim();
b69ab31161 resolve(result);
b69ab31162 } catch (parseError) {
b69ab31163 reject(new Error(`Failed to parse response: ${parseError.message}\nStdout: ${stdout}`));
b69ab31164 }
b69ab31165 });
b69ab31166 });
b69ab31167 }
b69ab31168
b69ab31169 /**
b69ab31170 * Get changes since a specific journal position
b69ab31171 * @param {Object} options - Options for changes-since command
b69ab31172 * @param {string} options.position - Journal position to start from
b69ab31173 * @param {string} options.useCase - Use case for the command
b69ab31174 * @param {string} options.mountPoint - Path to the mount point (optional if set in constructor)
b69ab31175 * @param {string} options.relativeRoot - Relative root to scope results
b69ab31176 * @param {boolean} options.includeVcsRoots - Include VCS roots in output
b69ab31177 * @param {string[]} options.includedRoots - Included roots in output
b69ab31178 * @param {string[]} options.excludedRoots - Excluded roots in output
b69ab31179 * @param {string[]} options.includedSuffixes - Included suffixes in output
b69ab31180 * @param {string[]} options.excludedSuffixes - Excluded suffixes in output
b69ab31181 * @param {boolean} options.json - Return JSON format (default: true)
b69ab31182 * @param {string[]} options.deferredStates - States to wait for deassertion
b69ab31183 * @returns {Promise<Object|string>} Changes since position
b69ab31184 */
b69ab31185 async getChangesSince(options = {}) {
b69ab31186 const mountPoint = options?.mountPoint ?? this.mountPoint;
b69ab31187 const args = ['notify', 'changes-since'];
b69ab31188
b69ab31189 if (options.useCase) {
b69ab31190 args.push('--use-case', options.useCase);
b69ab31191 } else {
b69ab31192 args.push('--use-case', 'node-client');
b69ab31193 }
b69ab31194
b69ab31195 if (options.position) {
b69ab31196 args.push(
b69ab31197 '--position',
b69ab31198 typeof options.position === 'string' ? options.position : JSON.stringify(options.position),
b69ab31199 );
b69ab31200 }
b69ab31201
b69ab31202 if (options.relativeRoot) {
b69ab31203 args.push('--relative-root', options.relativeRoot);
b69ab31204 }
b69ab31205
b69ab31206 if (options.includeVcsRoots) {
b69ab31207 args.push('--include-vcs-roots');
b69ab31208 }
b69ab31209
b69ab31210 if (options.includedRoots) {
b69ab31211 options.includedRoots.forEach(root => {
b69ab31212 args.push('--included-roots', root);
b69ab31213 });
b69ab31214 }
b69ab31215
b69ab31216 if (options.excludedRoots) {
b69ab31217 options.excludedRoots.forEach(root => {
b69ab31218 args.push('--excluded-roots', root);
b69ab31219 });
b69ab31220 }
b69ab31221
b69ab31222 if (options.includedSuffixes) {
b69ab31223 options.includedSuffixes.forEach(suffix => {
b69ab31224 args.push('--included-suffixes', suffix);
b69ab31225 });
b69ab31226 }
b69ab31227
b69ab31228 if (options.excludedSuffixes) {
b69ab31229 options.excludedSuffixes.forEach(suffix => {
b69ab31230 args.push('--excluded-suffixes', suffix);
b69ab31231 });
b69ab31232 }
b69ab31233
b69ab31234 if (options.deferredStates) {
b69ab31235 options.deferredStates.forEach(state => {
b69ab31236 args.push('--deferred-states', state);
b69ab31237 });
b69ab31238 }
b69ab31239
b69ab31240 args.push('--json');
b69ab31241 args.push('--formatted-position');
b69ab31242
b69ab31243 if (mountPoint) {
b69ab31244 args.push(mountPoint);
b69ab31245 }
b69ab31246
b69ab31247 return new Promise((resolve, reject) => {
b69ab31248 execFile(this.edenBinaryPath, args, {timeout: this.timeout}, (error, stdout, stderr) => {
b69ab31249 if (error) {
b69ab31250 reject(new Error(`Failed to get changes: ${error.message}\nStderr: ${stderr}`));
b69ab31251 return;
b69ab31252 }
b69ab31253
b69ab31254 try {
b69ab31255 const result = JSON.parse(stdout.trim());
b69ab31256 resolve(result);
b69ab31257 } catch (parseError) {
b69ab31258 reject(new Error(`Failed to parse response: ${parseError.message}`));
b69ab31259 }
b69ab31260 });
b69ab31261 });
b69ab31262 }
b69ab31263
b69ab31264 /**
b69ab31265 * Subscribe to filesystem changes
b69ab31266 * @param {Object} options - Options for subscription
b69ab31267 * @param {string} options.position - Journal position to start from (optional)
b69ab31268 * @param {string} options.useCase - Use case for the command
b69ab31269 * @param {string} options.mountPoint - Path to the mount point (optional if set in constructor)
b69ab31270 * @param {number} options.throttle - Throttle in milliseconds between events (default: 0)
b69ab31271 * @param {string} options.relativeRoot - Relative root to scope results
b69ab31272 * @param {boolean} options.includeVcsRoots - Include VCS roots in output
b69ab31273 * @param {string[]} options.includedRoots - Included roots in output
b69ab31274 * @param {string[]} options.excludedRoots - Excluded roots in output
b69ab31275 * @param {string[]} options.includedSuffixes - Included suffixes in output
b69ab31276 * @param {string[]} options.excludedSuffixes - Excluded suffixes in output
b69ab31277 * @param {string[]} options.deferredStates - States to wait for deassertion
b69ab31278 * @param {CommandCallback} callback
b69ab31279 * @returns {EdenFSSubscription} Subscription object
b69ab31280 */
b69ab31281 subscribe(options = {}, callback = () => {}) {
b69ab31282 options['edenBinaryPath'] = this.edenBinaryPath;
b69ab31283 let sub = new EdenFSSubscription(this, options, callback);
b69ab31284 sub.on('change', change => {
b69ab31285 callback(null, change);
b69ab31286 });
b69ab31287 sub.on('error', error => {
b69ab31288 callback(error, null);
b69ab31289 });
b69ab31290 sub.on('close', () => {
b69ab31291 // Received when the underlying gets killed, pass double null to indicate
b69ab31292 // this since no error or message is available
b69ab31293 callback(null, null);
b69ab31294 });
b69ab31295 return sub;
b69ab31296 }
b69ab31297
b69ab31298 /**
b69ab31299 * Enter a specific state
b69ab31300 * @param {string} state - State name to enter
b69ab31301 * @param {Object} options - Options for enterState command
b69ab31302 * @param {number} [options.duration] - Duration in seconds to maintain state
b69ab31303 * @param {string} [options.useCase] - Use case for the command
b69ab31304 * @param {string} options.mountPoint - Path to the mount point (optional if set in constructor)
b69ab31305 * @returns {Promise<void>}
b69ab31306 */
b69ab31307 async enterState(state, options = {}) {
b69ab31308 const mountPoint = options?.mountPoint ?? this.mountPoint;
b69ab31309 if (!state || typeof state !== 'string') {
b69ab31310 throw new Error('State name must be a non-empty string');
b69ab31311 }
b69ab31312
b69ab31313 const args = ['notify', 'enter-state', state];
b69ab31314 if (options.duration !== undefined) {
b69ab31315 args.push('--duration', options.duration.toString());
b69ab31316 }
b69ab31317
b69ab31318 if (options.useCase) {
b69ab31319 args.push('--use-case', options.useCase);
b69ab31320 } else {
b69ab31321 args.push('--use-case', 'node-client');
b69ab31322 }
b69ab31323
b69ab31324 if (mountPoint) {
b69ab31325 args.push(mountPoint);
b69ab31326 }
b69ab31327
b69ab31328 return new Promise((resolve, reject) => {
b69ab31329 execFile(this.edenBinaryPath, args, {timeout: this.timeout}, (error, stdout, stderr) => {
b69ab31330 if (error) {
b69ab31331 reject(
b69ab31332 new Error(
b69ab31333 `Failed to enter state: ${error.message}\nStdout: ${stdout}\nStderr: ${stderr}`,
b69ab31334 ),
b69ab31335 );
b69ab31336 return;
b69ab31337 }
b69ab31338 resolve();
b69ab31339 });
b69ab31340 });
b69ab31341 }
b69ab31342}
b69ab31343
b69ab31344/**
b69ab31345 * EdenFS Subscription
b69ab31346 * Handles real-time filesystem change notifications
b69ab31347 */
b69ab31348class EdenFSSubscription extends EventEmitter {
b69ab31349 /** @type {EdenFSNotificationsClient} */
b69ab31350 client;
b69ab31351 /** @type {Object} */
b69ab31352 options;
b69ab31353 /** @type {any} */
b69ab31354 process;
b69ab31355 /** @type {string} */
b69ab31356 edenBinaryPath;
b69ab31357 /** @type {string} */
b69ab31358 errData;
b69ab31359 /** @type {NodeJS.Timeout | null} */
b69ab31360 killTimeout;
b69ab31361
b69ab31362 constructor(client, options = {}) {
b69ab31363 super();
b69ab31364 this.client = client;
b69ab31365 this.options = options;
b69ab31366 this.process = null;
b69ab31367 this.edenBinaryPath = options?.edenBinaryPath ?? 'eden';
b69ab31368 this.errData = '';
b69ab31369 this.killTimeout = null;
b69ab31370 }
b69ab31371
b69ab31372 /**
b69ab31373 * Start the subscription
b69ab31374 * @returns {Promise<void>}
b69ab31375 */
b69ab31376 async start() {
b69ab31377 const mountPoint = this.options.mountPoint || this.client.mountPoint;
b69ab31378 const args = ['notify', 'changes-since', '--subscribe', '--json', '--formatted-position'];
b69ab31379
b69ab31380 if (this.options.useCase) {
b69ab31381 args.push('--use-case', this.options.useCase);
b69ab31382 } else {
b69ab31383 args.push('--use-case', 'node-client');
b69ab31384 }
b69ab31385
b69ab31386 if (this.options.position) {
b69ab31387 args.push(
b69ab31388 '--position',
b69ab31389 typeof this.options.position === 'string'
b69ab31390 ? this.options.position
b69ab31391 : JSON.stringify(this.options.position),
b69ab31392 );
b69ab31393 }
b69ab31394
b69ab31395 if (this.options.throttle !== undefined) {
b69ab31396 args.push('--throttle', this.options.throttle.toString());
b69ab31397 }
b69ab31398
b69ab31399 if (this.options.relativeRoot) {
b69ab31400 args.push('--relative-root', this.options.relativeRoot);
b69ab31401 }
b69ab31402
b69ab31403 if (this.options.includeVcsRoots) {
b69ab31404 args.push('--include-vcs-roots');
b69ab31405 }
b69ab31406
b69ab31407 if (this.options.includedRoots) {
b69ab31408 this.options.includedRoots.forEach(root => {
b69ab31409 args.push('--included-roots', root);
b69ab31410 });
b69ab31411 }
b69ab31412
b69ab31413 if (this.options.excludedRoots) {
b69ab31414 this.options.excludedRoots.forEach(root => {
b69ab31415 args.push('--excluded-roots', root);
b69ab31416 });
b69ab31417 }
b69ab31418
b69ab31419 if (this.options.includedSuffixes) {
b69ab31420 this.options.includedSuffixes.forEach(suffix => {
b69ab31421 args.push('--included-suffixes', suffix);
b69ab31422 });
b69ab31423 }
b69ab31424
b69ab31425 if (this.options.excludedSuffixes) {
b69ab31426 this.options.excludedSuffixes.forEach(suffix => {
b69ab31427 args.push('--excluded-suffixes', suffix);
b69ab31428 });
b69ab31429 }
b69ab31430
b69ab31431 if (this.options.deferredStates) {
b69ab31432 this.options.deferredStates.forEach(state => {
b69ab31433 args.push('--deferred-states', state);
b69ab31434 });
b69ab31435 }
b69ab31436
b69ab31437 if (mountPoint) {
b69ab31438 args.push(mountPoint);
b69ab31439 }
b69ab31440
b69ab31441 return new Promise((resolve, reject) => {
b69ab31442 this.process = spawn(this.edenBinaryPath, args, {
b69ab31443 stdio: ['pipe', 'pipe', 'pipe'],
b69ab31444 });
b69ab31445
b69ab31446 let buffer = '';
b69ab31447
b69ab31448 const readline = require('readline');
b69ab31449 const rl = readline.createInterface({input: this.process.stdout});
b69ab31450
b69ab31451 rl.on('line', line => {
b69ab31452 if (line.trim()) {
b69ab31453 try {
b69ab31454 const event = JSON.parse(line);
b69ab31455 this.emit('change', event);
b69ab31456 } catch (error) {
b69ab31457 this.emit('error', new Error(`Failed to parse event ${line}: ${error.message}`));
b69ab31458 }
b69ab31459 }
b69ab31460 });
b69ab31461
b69ab31462 this.process.stderr.on('data', data => {
b69ab31463 this.errData += data.toString() + '\n';
b69ab31464 });
b69ab31465
b69ab31466 this.process.on('close', (code, signal) => {
b69ab31467 if (code !== 0 && code !== null) {
b69ab31468 this.emit(
b69ab31469 'error',
b69ab31470 new Error(`EdenFS process exited with code ${code}\nstderr: ${this.errData}`),
b69ab31471 );
b69ab31472 } else if (signal !== null && signal !== 'SIGTERM') {
b69ab31473 this.emit('error', new Error(`EdenFS process killed with signal ${signal}`));
b69ab31474 } else {
b69ab31475 this.emit('close');
b69ab31476 }
b69ab31477 });
b69ab31478
b69ab31479 this.process.on('error', error => {
b69ab31480 this.emit('error', error);
b69ab31481 reject(error);
b69ab31482 });
b69ab31483
b69ab31484 this.process.on('spawn', () => {
b69ab31485 resolve();
b69ab31486 });
b69ab31487
b69ab31488 this.process.on('exit', (code, signal) => {
b69ab31489 if (this.killTimeout !== null) {
b69ab31490 clearTimeout(this.killTimeout);
b69ab31491 this.killTimeout = null;
b69ab31492 }
b69ab31493 this.emit('exit');
b69ab31494 });
b69ab31495 });
b69ab31496 }
b69ab31497
b69ab31498 /**
b69ab31499 * Stop the subscription
b69ab31500 */
b69ab31501 stop() {
b69ab31502 if (this.process) {
b69ab31503 this.process.kill('SIGTERM');
b69ab31504 this.killTimeout = setTimeout(() => {
b69ab31505 this.process.kill('SIGKILL');
b69ab31506 }, 500);
b69ab31507 }
b69ab31508 }
b69ab31509}
b69ab31510
b69ab31511/**
b69ab31512 * Utility functions for working with EdenFS notify data
b69ab31513 */
b69ab31514class EdenFSUtils {
b69ab31515 /**
b69ab31516 * Convert byte array path to string
b69ab31517 * @param {number[]} pathBytes - Array of byte values representing a path
b69ab31518 * @returns {string} Path string
b69ab31519 */
b69ab31520 static bytesToPath(pathBytes) {
b69ab31521 return Buffer.from(pathBytes).toString('utf8');
b69ab31522 }
b69ab31523
b69ab31524 /**
b69ab31525 * Convert byte array to hex string
b69ab31526 * @param {number[]} bytes - Array of byte values
b69ab31527 * @returns {string} Hexadecimal string
b69ab31528 */
b69ab31529 static bytesToHex(bytes) {
b69ab31530 return Buffer.from(bytes).toString('hex');
b69ab31531 }
b69ab31532
b69ab31533 /**
b69ab31534 * Extract file type from change
b69ab31535 * @param {Object} change - change object
b69ab31536 * @returns {{string}} File Type
b69ab31537 */
b69ab31538 static extractFileType(smallChange) {
b69ab31539 if (smallChange.Added && smallChange.Added.file_type) {
b69ab31540 return smallChange.Added.file_type;
b69ab31541 } else if (smallChange.Modified && smallChange.Modified.file_type) {
b69ab31542 return smallChange.Modified.file_type;
b69ab31543 } else if (smallChange.Removed && smallChange.Removed.file_type) {
b69ab31544 return smallChange.Removed.file_type;
b69ab31545 } else if (smallChange.Renamed) {
b69ab31546 return smallChange.Renamed.file_type;
b69ab31547 } else if (smallChange.Replaced) {
b69ab31548 return smallChange.Replaced.file_type;
b69ab31549 }
b69ab31550 }
b69ab31551
b69ab31552 /**
b69ab31553 * Extract file path(s) from change
b69ab31554 * @param {Object} change - change object
b69ab31555 * @returns {{string, string | undefined}} First file path, and possible second file path
b69ab31556 */
b69ab31557 static extractPath(smallChange) {
b69ab31558 if (smallChange.Added && smallChange.Added.path) {
b69ab31559 return [this.bytesToPath(smallChange.Added.path), undefined];
b69ab31560 } else if (smallChange.Modified && smallChange.Modified.path) {
b69ab31561 return [this.bytesToPath(smallChange.Modified.path), undefined];
b69ab31562 } else if (smallChange.Removed && smallChange.Removed.path) {
b69ab31563 return [this.bytesToPath(smallChange.Removed.path), undefined];
b69ab31564 } else if (smallChange.Renamed) {
b69ab31565 return [this.bytesToPath(smallChange.Renamed.from), this.bytesToPath(smallChange.Renamed.to)];
b69ab31566 } else if (smallChange.Replaced) {
b69ab31567 return [
b69ab31568 this.bytesToPath(smallChange.Replaced.from),
b69ab31569 this.bytesToPath(smallChange.Replaced.to),
b69ab31570 ];
b69ab31571 } else {
b69ab31572 return ['', undefined];
b69ab31573 }
b69ab31574 }
b69ab31575
b69ab31576 /**
b69ab31577 * Extract file paths from changes
b69ab31578 * @param {Object[]} changes - Array of change objects
b69ab31579 * @returns {string[]} Array of file paths
b69ab31580 */
b69ab31581 static extractPaths(changes) {
b69ab31582 const paths = [];
b69ab31583
b69ab31584 changes.forEach(change => {
b69ab31585 if (change.SmallChange) {
b69ab31586 let [path1, path2] = this.extractPath(change.SmallChange);
b69ab31587 if (path1) {
b69ab31588 paths.push(path1);
b69ab31589 }
b69ab31590 if (path2) {
b69ab31591 paths.push(path2);
b69ab31592 }
b69ab31593 }
b69ab31594 });
b69ab31595
b69ab31596 return paths;
b69ab31597 }
b69ab31598
b69ab31599 /**
b69ab31600 * Get change type from a change object
b69ab31601 * @param {Object} change - Change object
b69ab31602 * @returns {string} Change type
b69ab31603 */
b69ab31604 static getChangeType(change) {
b69ab31605 if (change.SmallChange) {
b69ab31606 const smallChange = change.SmallChange;
b69ab31607
b69ab31608 if (smallChange.Added) return 'added';
b69ab31609 if (smallChange.Modified) return 'modified';
b69ab31610 if (smallChange.Removed) return 'removed';
b69ab31611 if (smallChange.Renamed) return 'renamed';
b69ab31612 if (smallChange.Replaced) return 'replaced';
b69ab31613 } else if (change.LargeChange) {
b69ab31614 const largeChange = change.LargeChange;
b69ab31615 if (largeChange.DirectoryRenamed) return 'directory renamed';
b69ab31616 if (largeChange.CommitTransition) return 'commit transition';
b69ab31617 if (largeChange.LostChange) return 'lost change';
b69ab31618 } else if (change.StateChange) {
b69ab31619 const stateChange = change.StateChange;
b69ab31620 if (stateChange.StateEntered) return 'state entered';
b69ab31621 if (stateChange.StateLeft) return 'state left';
b69ab31622 }
b69ab31623
b69ab31624 return 'unknown';
b69ab31625 }
b69ab31626}
b69ab31627
b69ab31628module.exports = {
b69ab31629 EdenFSNotificationsClient,
b69ab31630 EdenFSSubscription,
b69ab31631 EdenFSUtils,
b69ab31632};