Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement →
Sign In

@nasriya/overwatch

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nasriya/overwatch - npm Package Compare versions

Comparing version
1.0.6
to
1.1.0
+1
-0
dist/@types/overwatch.d.ts
import Watcher from "./watcher/Watcher";
import { WatchOptions } from "./docs/docs";
export type { WatchOptions, OnWatchedDataUpdateHandler, onWatchedDataRemoveHandler, onWatchedDataRenameHandler, onWatchedDataAddHandler, WatchedDataChangeEvent, onWatchedDataChangeHandler, RenameEvent, UpdateEvent, RemoveEvent, AddEvent, ChangeEvent, WatchedFile, WatchedFolder, WatchedData } from './docs/docs';
declare class Overwatch {

@@ -4,0 +5,0 @@ #private;

+0
-38
import { WatchedData, WatchedFile, WatchedFolder } from "../docs/docs";
declare class Utils {
/**
* Converts a glob expression to a regular expression.
* @param glob The glob expression to convert.
* @param options Options to customize the conversion.
* @param options.globstar Whether or not to enable globstar matching (e.g. `** /foo`).
* @param options.flags A string containing additional flags to pass to the created RegExp.
* @returns The regular expression representation of the glob expression.
*/
globToRegExp(glob: string, options?: {
globstar?: boolean;
flags?: string;
}): RegExp;
/**
* Checks if the given pattern is a glob-like pattern, meaning it contains at least one of the
* characters `*` or `?`.
* @param pattern The pattern to check.
* @returns true if the given pattern is a glob-like pattern, false otherwise.
*/
isGlobLike(pattern: string): boolean;
/**
* Checks if the given WatchedData is a WatchedFile.

@@ -35,9 +16,2 @@ * @param data The WatchedData to check.

/**
* Checks if the given object has the given property.
* @param object The object to check.
* @param property The property to check for.
* @returns true if the object has the property, false otherwise.
*/
hasOwnProperty(object: any, property: string): boolean;
/**
* Checks if the given `WatchedData` objects are of the same type (i.e. both are `WatchedFolder`, or both are `WatchedFile`).

@@ -49,16 +23,4 @@ * @param a The first `WatchedData` object to compare.

areSameWatchedDataTypes(a: WatchedData, b: WatchedData): boolean;
/**
* Checks if the current platform is Windows.
* @returns true if the platform is Windows, false otherwise.
*/
isWindows(): boolean;
/**
* Normalizes the given path by resolving it to an absolute path and converting
* it to lowercase if the current platform is Windows.
* @param path_ The path to normalize.
* @returns The normalized path.
*/
normalizePath(path_: string): string;
}
declare const utils: Utils;
export default utils;

@@ -6,8 +6,8 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const atomix_1 = __importDefault(require("@nasriya/atomix"));
const vom_1 = __importDefault(require("./vom/vom"));
const Watcher_1 = __importDefault(require("./watcher/Watcher"));
const manager_1 = __importDefault(require("./events/manager"));
const utils_1 = __importDefault(require("./utils/utils"));
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
class Overwatch {

@@ -43,3 +43,4 @@ #_vom = new vom_1.default();

}
if ('include' in options && utils_1.default.hasOwnProperty(options, 'include')) {
const hasOwnProperty = atomix_1.default.dataTypes.record.hasOwnProperty.bind(atomix_1.default.dataTypes.record);
if ('include' in options && hasOwnProperty(options, 'include')) {
if (!Array.isArray(options.include)) {

@@ -55,3 +56,3 @@ throw new TypeError('options.include (when provided) must be an array.');

}
if ('exclude' in options && utils_1.default.hasOwnProperty(options, 'exclude')) {
if ('exclude' in options && hasOwnProperty(options, 'exclude')) {
if (!Array.isArray(options.exclude)) {

@@ -167,3 +168,3 @@ throw new TypeError('options.exclude (when provided) must be an array.');

try {
const resolvedPath = utils_1.default.normalizePath(path_);
const resolvedPath = atomix_1.default.path.normalizePath(path_);
const isFile = fs_1.default.statSync(resolvedPath).isFile();

@@ -201,3 +202,3 @@ const directory = isFile ? path_1.default.dirname(resolvedPath) : resolvedPath;

try {
const watcher = await this.watch(path_);
const watcher = await this.watch(path_, options);
return watcher;

@@ -204,0 +205,0 @@ }

@@ -6,105 +6,6 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const atomix_1 = __importDefault(require("@nasriya/atomix"));
const hasOwnProperty = atomix_1.default.dataTypes.record.hasOwnProperty.bind(atomix_1.default.dataTypes.record);
class Utils {
/**
* Converts a glob expression to a regular expression.
* @param glob The glob expression to convert.
* @param options Options to customize the conversion.
* @param options.globstar Whether or not to enable globstar matching (e.g. `** /foo`).
* @param options.flags A string containing additional flags to pass to the created RegExp.
* @returns The regular expression representation of the glob expression.
*/
globToRegExp(glob, options) {
const globstar = options?.globstar ?? true;
const flags = options?.flags ?? '';
let re = '';
let inGroup = false;
let escaping = false;
for (let i = 0; i < glob.length; i++) {
const char = glob[i];
// Handle escaped characters (e.g., \*, \?, \{, etc.)
if (escaping) {
re += '\\' + char;
escaping = false;
continue;
}
if (char === '\\') {
escaping = true;
continue;
}
switch (char) {
case '/':
re += '\\/';
break;
case '.':
case '+':
case '^':
case '$':
case '!':
case '=':
case '|':
case '(':
case ')':
re += '\\' + char;
break;
case '*': {
const prev = glob[i - 1];
let starCount = 1;
while (glob[i + 1] === '*') {
i++;
starCount++;
}
const next = glob[i + 1];
const isGlobstar = globstar &&
starCount > 1 &&
(prev === '/' || prev === undefined) &&
(next === '/' || next === undefined);
if (isGlobstar) {
re += '((?:[^\\/]*(?:\\/|$))*)';
if (next === '/')
i++; // skip slash after globstar
}
else {
re += '[^\\/]*';
}
break;
}
case '?':
re += '.';
break;
case '{':
inGroup = true;
re += '(';
break;
case '}':
inGroup = false;
re += ')';
break;
case ',':
re += inGroup ? '|' : ',';
break;
// allow valid character classes (e.g., [jt])
case '[':
case ']':
re += char;
break;
default:
re += char;
break;
}
}
// Only anchor if not global, to behave like globs
const anchored = flags.includes('g') ? re : `^${re}$`;
return new RegExp(anchored, flags);
}
/**
* Checks if the given pattern is a glob-like pattern, meaning it contains at least one of the
* characters `*` or `?`.
* @param pattern The pattern to check.
* @returns true if the given pattern is a glob-like pattern, false otherwise.
*/
isGlobLike(pattern) {
return /[*?]/.test(pattern);
}
/**
* Checks if the given WatchedData is a WatchedFile.

@@ -118,6 +19,6 @@ * @param data The WatchedData to check.

}
if (!(this.hasOwnProperty(data, 'path') &&
this.hasOwnProperty(data, 'name') &&
this.hasOwnProperty(data, 'size') &&
this.hasOwnProperty(data, 'mTime'))) {
if (!(hasOwnProperty(data, 'path') &&
hasOwnProperty(data, 'name') &&
hasOwnProperty(data, 'size') &&
hasOwnProperty(data, 'mTime'))) {
return false;

@@ -139,14 +40,5 @@ }

isWatchedFolder(data) {
return this.isWatchedFile(data) && 'children' in data && this.hasOwnProperty(data, 'children') && data.children instanceof Map;
return this.isWatchedFile(data) && 'children' in data && hasOwnProperty(data, 'children') && data.children instanceof Map;
}
/**
* Checks if the given object has the given property.
* @param object The object to check.
* @param property The property to check for.
* @returns true if the object has the property, false otherwise.
*/
hasOwnProperty(object, property) {
return typeof object === 'object' && Object.prototype.hasOwnProperty.call(object, property);
}
/**
* Checks if the given `WatchedData` objects are of the same type (i.e. both are `WatchedFolder`, or both are `WatchedFile`).

@@ -162,21 +54,4 @@ * @param a The first `WatchedData` object to compare.

}
/**
* Checks if the current platform is Windows.
* @returns true if the platform is Windows, false otherwise.
*/
isWindows() {
return process.platform === 'win32';
}
/**
* Normalizes the given path by resolving it to an absolute path and converting
* it to lowercase if the current platform is Windows.
* @param path_ The path to normalize.
* @returns The normalized path.
*/
normalizePath(path_) {
const resolved = path_1.default.resolve(path_);
return this.isWindows() ? resolved.toLowerCase() : resolved;
}
}
const utils = new Utils();
exports.default = utils;

@@ -6,6 +6,6 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const atomix_1 = __importDefault(require("@nasriya/atomix"));
const snapshot_1 = __importDefault(require("./snapshot"));
const manager_1 = __importDefault(require("../events/manager"));
const path_1 = __importDefault(require("path"));
const utils_1 = __importDefault(require("../utils/utils"));
const manager_1 = __importDefault(require("../events/manager"));
class VirtualObjectManager {

@@ -49,3 +49,3 @@ #_snapshots = new Map();

getCoveringSnapshot: (path_) => {
const isWin = utils_1.default.isWindows();
const isWin = atomix_1.default.runtime.platform.isWindows();
let currentPath = path_;

@@ -75,3 +75,3 @@ while (true) {

async snapshot(path_) {
const originalPath = utils_1.default.normalizePath(path_);
const originalPath = atomix_1.default.path.normalizePath(path_);
const snapshot = this.#_helpers.getCoveringSnapshot(originalPath);

@@ -78,0 +78,0 @@ if (snapshot) {

@@ -6,3 +6,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = __importDefault(require("../utils/utils"));
const atomix_1 = __importDefault(require("@nasriya/atomix"));
class Watcher {

@@ -28,7 +28,7 @@ #_path;

if (typeof rule === 'string') {
if (utils_1.default.isGlobLike(rule)) {
return utils_1.default.globToRegExp(rule);
if (atomix_1.default.dataTypes.regex.guard.isGlobLike(rule)) {
return atomix_1.default.dataTypes.regex.globToRegExp(rule);
}
else {
return utils_1.default.normalizePath(rule);
return atomix_1.default.path.normalizePath(rule);
}

@@ -48,20 +48,23 @@ }

this.#_filters.exclude = this.#_filters.exclude.map(rule => this.#_helpers.normalizeFilterRule(rule));
if (utils_1.default.hasOwnProperty(watchOptions, 'onChange')) {
this.onChange(watchOptions?.onChange);
if (atomix_1.default.dataTypes.record.guard.isRecord(watchOptions)) {
const hasOwnProperty = atomix_1.default.dataTypes.record.hasOwnProperty.bind(atomix_1.default.dataTypes.record);
if (hasOwnProperty(watchOptions, 'onChange')) {
this.onChange(watchOptions?.onChange);
}
if (hasOwnProperty(watchOptions, 'onUpdate')) {
this.onUpdate(watchOptions?.onUpdate);
}
if (hasOwnProperty(watchOptions, 'onRemove')) {
this.onRemove(watchOptions?.onRemove);
}
if (hasOwnProperty(watchOptions, 'onRename')) {
this.onRename(watchOptions?.onRename);
}
if (hasOwnProperty(watchOptions, 'onAdd')) {
this.onAdd(watchOptions?.onAdd);
}
if (hasOwnProperty(watchOptions, 'onRootRemoved')) {
this.onRootRemoved(watchOptions?.onRootRemoved);
}
}
if (utils_1.default.hasOwnProperty(watchOptions, 'onUpdate')) {
this.onUpdate(watchOptions?.onUpdate);
}
if (utils_1.default.hasOwnProperty(watchOptions, 'onRemove')) {
this.onRemove(watchOptions?.onRemove);
}
if (utils_1.default.hasOwnProperty(watchOptions, 'onRename')) {
this.onRename(watchOptions?.onRename);
}
if (utils_1.default.hasOwnProperty(watchOptions, 'onAdd')) {
this.onAdd(watchOptions?.onAdd);
}
if (utils_1.default.hasOwnProperty(watchOptions, 'onRootRemoved')) {
this.onRootRemoved(watchOptions?.onRootRemoved);
}
}

@@ -68,0 +71,0 @@ /**

@@ -0,7 +1,7 @@

import atomix from "@nasriya/atomix";
import VirtualObjectManager from "./vom/vom.js";
import Watcher from "./watcher/Watcher.js";
import eventsManager from "./events/manager.js";
import utils from "./utils/utils.js";
import path from "path";
import fs from "fs";
import path from "path";
class Overwatch {

@@ -37,3 +37,4 @@ #_vom = new VirtualObjectManager();

}
if ('include' in options && utils.hasOwnProperty(options, 'include')) {
const hasOwnProperty = atomix.dataTypes.record.hasOwnProperty.bind(atomix.dataTypes.record);
if ('include' in options && hasOwnProperty(options, 'include')) {
if (!Array.isArray(options.include)) {

@@ -49,3 +50,3 @@ throw new TypeError('options.include (when provided) must be an array.');

}
if ('exclude' in options && utils.hasOwnProperty(options, 'exclude')) {
if ('exclude' in options && hasOwnProperty(options, 'exclude')) {
if (!Array.isArray(options.exclude)) {

@@ -161,3 +162,3 @@ throw new TypeError('options.exclude (when provided) must be an array.');

try {
const resolvedPath = utils.normalizePath(path_);
const resolvedPath = atomix.path.normalizePath(path_);
const isFile = fs.statSync(resolvedPath).isFile();

@@ -195,3 +196,3 @@ const directory = isFile ? path.dirname(resolvedPath) : resolvedPath;

try {
const watcher = await this.watch(path_);
const watcher = await this.watch(path_, options);
return watcher;

@@ -198,0 +199,0 @@ }

@@ -1,104 +0,5 @@

import path from 'path';
import atomix from "@nasriya/atomix";
const hasOwnProperty = atomix.dataTypes.record.hasOwnProperty.bind(atomix.dataTypes.record);
class Utils {
/**
* Converts a glob expression to a regular expression.
* @param glob The glob expression to convert.
* @param options Options to customize the conversion.
* @param options.globstar Whether or not to enable globstar matching (e.g. `** /foo`).
* @param options.flags A string containing additional flags to pass to the created RegExp.
* @returns The regular expression representation of the glob expression.
*/
globToRegExp(glob, options) {
const globstar = options?.globstar ?? true;
const flags = options?.flags ?? '';
let re = '';
let inGroup = false;
let escaping = false;
for (let i = 0; i < glob.length; i++) {
const char = glob[i];
// Handle escaped characters (e.g., \*, \?, \{, etc.)
if (escaping) {
re += '\\' + char;
escaping = false;
continue;
}
if (char === '\\') {
escaping = true;
continue;
}
switch (char) {
case '/':
re += '\\/';
break;
case '.':
case '+':
case '^':
case '$':
case '!':
case '=':
case '|':
case '(':
case ')':
re += '\\' + char;
break;
case '*': {
const prev = glob[i - 1];
let starCount = 1;
while (glob[i + 1] === '*') {
i++;
starCount++;
}
const next = glob[i + 1];
const isGlobstar = globstar &&
starCount > 1 &&
(prev === '/' || prev === undefined) &&
(next === '/' || next === undefined);
if (isGlobstar) {
re += '((?:[^\\/]*(?:\\/|$))*)';
if (next === '/')
i++; // skip slash after globstar
}
else {
re += '[^\\/]*';
}
break;
}
case '?':
re += '.';
break;
case '{':
inGroup = true;
re += '(';
break;
case '}':
inGroup = false;
re += ')';
break;
case ',':
re += inGroup ? '|' : ',';
break;
// allow valid character classes (e.g., [jt])
case '[':
case ']':
re += char;
break;
default:
re += char;
break;
}
}
// Only anchor if not global, to behave like globs
const anchored = flags.includes('g') ? re : `^${re}$`;
return new RegExp(anchored, flags);
}
/**
* Checks if the given pattern is a glob-like pattern, meaning it contains at least one of the
* characters `*` or `?`.
* @param pattern The pattern to check.
* @returns true if the given pattern is a glob-like pattern, false otherwise.
*/
isGlobLike(pattern) {
return /[*?]/.test(pattern);
}
/**
* Checks if the given WatchedData is a WatchedFile.

@@ -112,6 +13,6 @@ * @param data The WatchedData to check.

}
if (!(this.hasOwnProperty(data, 'path') &&
this.hasOwnProperty(data, 'name') &&
this.hasOwnProperty(data, 'size') &&
this.hasOwnProperty(data, 'mTime'))) {
if (!(hasOwnProperty(data, 'path') &&
hasOwnProperty(data, 'name') &&
hasOwnProperty(data, 'size') &&
hasOwnProperty(data, 'mTime'))) {
return false;

@@ -133,14 +34,5 @@ }

isWatchedFolder(data) {
return this.isWatchedFile(data) && 'children' in data && this.hasOwnProperty(data, 'children') && data.children instanceof Map;
return this.isWatchedFile(data) && 'children' in data && hasOwnProperty(data, 'children') && data.children instanceof Map;
}
/**
* Checks if the given object has the given property.
* @param object The object to check.
* @param property The property to check for.
* @returns true if the object has the property, false otherwise.
*/
hasOwnProperty(object, property) {
return typeof object === 'object' && Object.prototype.hasOwnProperty.call(object, property);
}
/**
* Checks if the given `WatchedData` objects are of the same type (i.e. both are `WatchedFolder`, or both are `WatchedFile`).

@@ -156,21 +48,4 @@ * @param a The first `WatchedData` object to compare.

}
/**
* Checks if the current platform is Windows.
* @returns true if the platform is Windows, false otherwise.
*/
isWindows() {
return process.platform === 'win32';
}
/**
* Normalizes the given path by resolving it to an absolute path and converting
* it to lowercase if the current platform is Windows.
* @param path_ The path to normalize.
* @returns The normalized path.
*/
normalizePath(path_) {
const resolved = path.resolve(path_);
return this.isWindows() ? resolved.toLowerCase() : resolved;
}
}
const utils = new Utils();
export default utils;

@@ -0,5 +1,5 @@

import atomix from "@nasriya/atomix";
import Snapshot from "./snapshot.js";
import eventsManager from "../events/manager.js";
import path from "path";
import utils from "../utils/utils.js";
import eventsManager from "../events/manager.js";
class VirtualObjectManager {

@@ -43,3 +43,3 @@ #_snapshots = new Map();

getCoveringSnapshot: (path_) => {
const isWin = utils.isWindows();
const isWin = atomix.runtime.platform.isWindows();
let currentPath = path_;

@@ -69,3 +69,3 @@ while (true) {

async snapshot(path_) {
const originalPath = utils.normalizePath(path_);
const originalPath = atomix.path.normalizePath(path_);
const snapshot = this.#_helpers.getCoveringSnapshot(originalPath);

@@ -72,0 +72,0 @@ if (snapshot) {

@@ -1,2 +0,2 @@

import utils from "../utils/utils.js";
import atomix from "@nasriya/atomix";
class Watcher {

@@ -22,7 +22,7 @@ #_path;

if (typeof rule === 'string') {
if (utils.isGlobLike(rule)) {
return utils.globToRegExp(rule);
if (atomix.dataTypes.regex.guard.isGlobLike(rule)) {
return atomix.dataTypes.regex.globToRegExp(rule);
}
else {
return utils.normalizePath(rule);
return atomix.path.normalizePath(rule);
}

@@ -42,20 +42,23 @@ }

this.#_filters.exclude = this.#_filters.exclude.map(rule => this.#_helpers.normalizeFilterRule(rule));
if (utils.hasOwnProperty(watchOptions, 'onChange')) {
this.onChange(watchOptions?.onChange);
if (atomix.dataTypes.record.guard.isRecord(watchOptions)) {
const hasOwnProperty = atomix.dataTypes.record.hasOwnProperty.bind(atomix.dataTypes.record);
if (hasOwnProperty(watchOptions, 'onChange')) {
this.onChange(watchOptions?.onChange);
}
if (hasOwnProperty(watchOptions, 'onUpdate')) {
this.onUpdate(watchOptions?.onUpdate);
}
if (hasOwnProperty(watchOptions, 'onRemove')) {
this.onRemove(watchOptions?.onRemove);
}
if (hasOwnProperty(watchOptions, 'onRename')) {
this.onRename(watchOptions?.onRename);
}
if (hasOwnProperty(watchOptions, 'onAdd')) {
this.onAdd(watchOptions?.onAdd);
}
if (hasOwnProperty(watchOptions, 'onRootRemoved')) {
this.onRootRemoved(watchOptions?.onRootRemoved);
}
}
if (utils.hasOwnProperty(watchOptions, 'onUpdate')) {
this.onUpdate(watchOptions?.onUpdate);
}
if (utils.hasOwnProperty(watchOptions, 'onRemove')) {
this.onRemove(watchOptions?.onRemove);
}
if (utils.hasOwnProperty(watchOptions, 'onRename')) {
this.onRename(watchOptions?.onRename);
}
if (utils.hasOwnProperty(watchOptions, 'onAdd')) {
this.onAdd(watchOptions?.onAdd);
}
if (utils.hasOwnProperty(watchOptions, 'onRootRemoved')) {
this.onRootRemoved(watchOptions?.onRootRemoved);
}
}

@@ -62,0 +65,0 @@ /**

{
"name": "@nasriya/overwatch",
"version": "1.0.6",
"version": "1.1.0",
"description": "A high-performance, dependency-free file system watcher that monitors file and directory changes efficiently across platforms.",

@@ -40,3 +40,3 @@ "keywords": [

"postbuild-init": "postbuild-init",
"test": "jest"
"test": "jest --detectOpenHandles"
},

@@ -60,6 +60,9 @@ "repository": {

"@types/jest": "^30.0.0",
"@types/node": "^24.0.3",
"jest": "^30.0.1",
"@types/node": "^24.0.4",
"jest": "^30.0.3",
"ts-jest": "^29.4.0"
},
"dependencies": {
"@nasriya/atomix": "^1.0.1"
}
}
}
[![N|Solid](https://static.wixstatic.com/media/72ffe6_da8d2142d49c42b29c96ba80c8a91a6c~mv2.png)](https://nasriya.net)
# Overwatch.
[![NPM License](https://img.shields.io/npm/l/%40nasriya%2Foverwatch?color=lightgreen)](https://github.com/nasriyasoftware/Overwatch?tab=License-1-ov-file) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nasriyasoftware/Overwatch/npm-publish.yml) ![NPM Version](https://img.shields.io/npm/v/%40nasriya%2Foverwatch) ![NPM Unpacked Size](https://img.shields.io/npm/unpacked-size/%40nasriya%2Foverwatch) ![Last Commit](https://img.shields.io/github/last-commit/nasriyasoftware/Overwatch.svg) [![Status](https://img.shields.io/badge/Status-Stable-lightgreen.svg)](link-to-your-status-page)
[![NPM License](https://img.shields.io/npm/l/%40nasriya%2Foverwatch?color=lightgreen)](https://github.com/nasriyasoftware/Overwatch?tab=License-1-ov-file) ![NPM Version](https://img.shields.io/npm/v/%40nasriya%2Foverwatch) ![NPM Unpacked Size](https://img.shields.io/npm/unpacked-size/%40nasriya%2Foverwatch) ![Last Commit](https://img.shields.io/github/last-commit/nasriyasoftware/Overwatch.svg) [![Status](https://img.shields.io/badge/Status-Stable-lightgreen.svg)](link-to-your-status-page)

@@ -6,0 +6,0 @@ ##### Visit us at [www.nasriya.net](https://nasriya.net).