ts-switch-case
Version:
A TypeScript-first switch-case utility with object-based and chainable syntax
178 lines (176 loc) • 5.89 kB
JavaScript
// src/utils.ts
function logCyclicError(obj, path, context) {
console.warn("[Cyclic Reference]", {
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
context,
path: path.join(" -> "),
objectType: obj?.constructor?.name || "unknown"
});
}
function isCyclic(obj, currentPath = /* @__PURE__ */ new WeakSet(), pathArray = [], path = ["root"]) {
if (obj && typeof obj === "object") {
if (currentPath.has(obj)) {
return true;
}
currentPath.add(obj);
pathArray.push(obj);
for (const key in obj) {
const newPath = /* @__PURE__ */ new WeakSet();
pathArray.forEach((item) => newPath.add(item));
if (isCyclic(obj[key], newPath, [...pathArray], [...path, key])) {
return true;
}
}
}
return false;
}
// src/switchCase.ts
function switchCase(value, discriminatorOrCases, casesOrDefault, defaultHandler) {
if (discriminatorOrCases === void 0) {
const cases = [];
let defaultFn;
const builder = {
case(match, handler) {
cases.push({ match, handler });
return this;
},
default(handler) {
defaultFn = handler;
return this;
},
exhaustive() {
return this.run();
},
run() {
for (const { match, handler } of cases) {
const isMatch = typeof match === "function" ? match(value) : match === value;
if (isMatch) {
return typeof handler === "function" ? handler(value) : handler;
}
}
if (defaultFn) {
return defaultFn(value);
}
throw new Error(`No matching case for value: ${JSON.stringify(value)}`);
}
};
return builder;
}
if (typeof discriminatorOrCases === "string" || typeof discriminatorOrCases === "number" || typeof discriminatorOrCases === "symbol") {
let createDiscriminatedUnionBuilder2 = function(value2, discriminator2) {
const handlers = /* @__PURE__ */ new Map();
let defaultFn;
function caseFn(valueKey, handler) {
handlers.set(
valueKey,
handler
);
return builder;
}
function defaultFnSetter(handler) {
defaultFn = handler;
return builder;
}
function run() {
const discriminatorValue = value2[discriminator2];
if (handlers.has(discriminatorValue)) {
return handlers.get(discriminatorValue)(value2);
}
if (defaultFn) {
return defaultFn(value2);
}
throw new Error(
`No matching case for ${String(discriminator2)}: ${String(discriminatorValue)}`
);
}
const builder = {
case: caseFn,
default: defaultFnSetter,
run
};
return builder;
};
var createDiscriminatedUnionBuilder = createDiscriminatedUnionBuilder2;
if (casesOrDefault !== void 0) {
const discriminator2 = discriminatorOrCases;
const cases = casesOrDefault;
const discriminatorValue = String(value[discriminator2]);
const handlerOrValue = cases[discriminatorValue];
if (handlerOrValue !== void 0) {
const result = typeof handlerOrValue === "function" ? handlerOrValue(value) : handlerOrValue;
if (isCyclic(result))
logCyclicError(casesOrDefault, ["root"], "Cycle detected");
return result;
}
if (defaultHandler) {
const result = defaultHandler(value);
if (isCyclic(result))
logCyclicError(casesOrDefault, ["root"], "Cycle detected");
return result;
}
throw new Error(
`No matching case for ${String(discriminator2)}: ${discriminatorValue}`
);
}
const discriminator = discriminatorOrCases;
return createDiscriminatedUnionBuilder2(value, discriminator);
}
if (Array.isArray(discriminatorOrCases)) {
if (isCyclic(discriminatorOrCases)) {
logCyclicError(discriminatorOrCases, ["root"], "Cycle detected");
}
const cases = discriminatorOrCases;
const defaultFn = casesOrDefault;
for (const { match, handler } of cases) {
if (match(value)) {
return typeof handler === "function" ? handler(value) : handler;
}
}
if (defaultFn) {
return defaultFn(value);
}
throw new Error(`No matching case for value: ${JSON.stringify(value)}`);
}
if (typeof discriminatorOrCases === "object") {
if (isCyclic(discriminatorOrCases))
throw new Error(
"Cyclic cases detected. See ts-switch-case README for handling cycles (e.g., sanitizeNode for React)."
);
const cases = discriminatorOrCases;
const defaultFn = casesOrDefault;
const literalHandler = cases[String(value)];
if (literalHandler !== void 0) {
const result = typeof literalHandler === "function" ? literalHandler() : literalHandler;
if (isCyclic(result))
logCyclicError(discriminatorOrCases, ["root"], "Cycle detected");
return result;
}
for (const key of Object.keys(cases)) {
const c = cases[key];
if (typeof c !== "function") {
const match = typeof c.match === "function" ? c.match(value) : c.match === value;
if (match) {
const result = typeof c.handler === "function" ? c.handler(value) : c.handler;
if (isCyclic(result))
logCyclicError(discriminatorOrCases, ["root"], "Cycle detected");
return result;
}
}
}
if (defaultFn) {
const result = defaultFn(value);
if (isCyclic(result))
logCyclicError(discriminatorOrCases, ["root"], "Cycle detected");
return result;
}
throw new Error(`No matching case for value: ${JSON.stringify(value)}`);
}
throw new Error("Invalid arguments to switchCase");
}
function assertUnreachable(value) {
throw new Error(`Unreachable case: ${value}`);
}
export {
assertUnreachable,
switchCase
};