@idiosync/react-observable
Version:
State management control layer for React projects
175 lines (174 loc) • 6.74 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.shallowEqualArrays = exports.isPlainObject = exports.isObject = exports.isFunction = exports.identity = exports.tryCatchSync = exports.tryCatch = void 0;
exports.uuid = uuid;
exports.isDevEnv = isDevEnv;
exports.getModuleFunctionName = getModuleFunctionName;
exports.getCallsiteName = getCallsiteName;
const tryCatch = async (fn, errorMessage) => {
try {
const result = await fn();
return [result, undefined];
}
catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
if (errorMessage) {
err.message = `${errorMessage}\n${err.message}`;
}
return [undefined, err];
}
};
exports.tryCatch = tryCatch;
const tryCatchSync = (fn, errorMessage) => {
try {
const result = fn();
return [result, undefined];
}
catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
if (errorMessage) {
err.message = `${errorMessage}\n${err.message}`;
}
return [undefined, err];
}
};
exports.tryCatchSync = tryCatchSync;
const identity = (value) => value;
exports.identity = identity;
const isFunction = (value) => typeof value === 'function';
exports.isFunction = isFunction;
const isObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
exports.isObject = isObject;
const isPlainObject = (value) => (0, exports.isObject)(value) && value.constructor === Object;
exports.isPlainObject = isPlainObject;
const shallowEqualArrays = (a, b) => {
if (!Array.isArray(a) || !Array.isArray(b))
return false;
if (a.length !== b.length)
return false;
return a.every((item, index) => item === b[index]);
};
exports.shallowEqualArrays = shallowEqualArrays;
let uuidCounter = 0;
function uuid() {
if (uuidCounter >= Number.MAX_SAFE_INTEGER) {
uuidCounter = 0;
}
return `${Date.now()}-${++uuidCounter}`;
}
function isDevEnv() {
const g = typeof globalThis !== 'undefined' ? globalThis : {};
return ((typeof g.__DEV__ !== 'undefined' && !!g.__DEV__) ||
(g.process && g.process.env && g.process.env.NODE_ENV !== 'production'));
}
// Specialized version for command streams. Filters out Metro module loading frames
// and just returns the top-level function name where the stream was created.
function getModuleFunctionName(options) {
var _a, _b;
const libHint = (_a = options === null || options === void 0 ? void 0 : options.libHint) !== null && _a !== void 0 ? _a : /(react-observable|node_modules\/react-observable|src\/utils\/|src\/factories\/|src\/store\/|src\/hooks\/|metroRequire|guardedLoadModule|loadModuleImplementation)/;
const fallback = (_b = options === null || options === void 0 ? void 0 : options.fallback) !== null && _b !== void 0 ? _b : 'command-stream';
if (!isDevEnv())
return fallback;
const error = new Error();
const raw = String(error.stack || '');
const lines = raw.split(/\r?\n/);
if (!lines.length)
return fallback;
const metroNames = new Set([
'metroRequire',
'guardedLoadModule',
'loadModuleImplementation',
'anonymous',
]);
for (const line of lines.map((l) => l.trim())) {
if (!line)
continue;
const v8 = line.match(/at\s+([^\s(]+)\s*\(([^)]+)\)/);
if (v8) {
const fn = v8[1];
if (!libHint.test(line) &&
!metroNames.has(fn) &&
fn !== 'getModuleFunctionName') {
return fn;
}
}
const fnAtLoc = line.match(/at\s+([^\s(]+)@(.+)/);
if (fnAtLoc) {
const fn = fnAtLoc[1];
if (!libHint.test(line) &&
!metroNames.has(fn) &&
fn !== 'getModuleFunctionName') {
return fn;
}
}
}
return fallback;
}
// Dev-only helper that returns a human-friendly callsite label based on the
// first user-land stack frame. It avoids library frames using libHint.
// Returns fallback when not in dev or when a stack cannot be parsed.
function getCallsiteName(options) {
var _a, _b;
const libHint = (_a = options === null || options === void 0 ? void 0 : options.libHint) !== null && _a !== void 0 ? _a : /(react-observable|node_modules\/react-observable|src\/utils\/|src\/factories\/|src\/store\/|src\/hooks\/)/;
const fallback = (_b = options === null || options === void 0 ? void 0 : options.fallback) !== null && _b !== void 0 ? _b : 'observable';
// Only attempt stack parsing in dev to avoid runtime overhead and engine variance
if (!isDevEnv())
return fallback;
// Capture stack
const error = new Error();
const raw = String(error.stack || '');
const lines = raw.split(/\r?\n/);
if (!lines.length)
return fallback;
const reactInternalNames = new Set([
'renderWithHooks',
'updateFunctionComponent',
'beginWork',
'performUnitOfWork',
'workLoopSync',
'performSyncWorkOnRoot',
'flushSyncCallbacks',
]);
const parsed = lines
.map((l) => l.trim())
.filter((l) => !!l)
.map((l) => {
const v8 = l.match(/at\s+([^\s(]+)\s+\(([^)]+)\)/);
if (v8)
return { raw: l, functionName: v8[1], location: v8[2] };
const fnAtLoc = l.match(/at\s+([^\s(]+)@(.+)/);
if (fnAtLoc)
return { raw: l, functionName: fnAtLoc[1], location: fnAtLoc[2] };
const plain = l.match(/at\s+(.+):(\d+):(\d+)/);
if (plain)
return { raw: l, location: `${plain[1]}:${plain[2]}:${plain[3]}` };
return { raw: l };
})
.filter((f) => !libHint.test(f.raw));
const getIdx = parsed.findIndex((f) => f.functionName === 'getCallsiteName');
const afterGet = getIdx >= 0 ? parsed.slice(getIdx + 1) : parsed;
const names = [];
for (const f of afterGet) {
const name = f.functionName;
if (!name)
continue;
if (reactInternalNames.has(name))
break;
names.push(name);
if (/^[A-Z][A-Za-z0-9_$]*$/.test(name))
break;
}
if (names.length > 0) {
return names.reverse().join('->');
}
const first = afterGet.find((f) => f.location || f.functionName);
if (first) {
if (first.functionName && first.location)
return `${first.functionName} @ ${first.location}`;
if (first.functionName)
return first.functionName;
if (first.location)
return first.location;
}
return fallback;
}