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

@anilkumarthakur/match

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

@anilkumarthakur/match - npm Package Compare versions

Comparing version
0.0.8
to
0.1.1
+105
dist/index.d.ts
/**
* Handler function type for match expression results
*
* A handler is a function that takes no parameters and returns a value
* of type T. Used in match expressions to define what happens when a case matches.
*
* @template T The return type of the handler function
*
* @example
* ```typescript
* const handler: Handler<string> = () => 'matched'
* const numHandler: Handler<number> = () => 42
* ```
*/
export declare type Handler<T> = () => T;
export declare function match<TSubject, TResult = unknown>(subject: TSubject): Matcher<TSubject, TResult>;
/**
* Interface representing a chainable match expression
*
* Provides the API contract for method chaining in match expressions.
* Implementations should support fluent interface patterns.
*
* @template TSubject The type of the value being matched against
* @template TResult The return type of handler functions
*
* @example
* ```typescript
* interface MatchChain<string, number> {
* on: (value: string, handler: Handler<number>) => MatchChain<string, number>
* otherwise: (handler: Handler<number>) => number
* }
* ```
*/
export declare interface MatchChain<TSubject, TResult> {
/**
* Add a case to match against the subject
*
* @param {TSubject} value The value to match
* @param {Handler<TResult>} handler Function to execute if matched
* @returns {MatchChain<TSubject, TResult>} The matcher for chaining
*/
on: (value: TSubject, handler: Handler<TResult>) => MatchChain<TSubject, TResult>;
/**
* Set default handler and execute the match
*
* @param {Handler<TResult>} handler Function to execute if no cases match
* @returns {TResult} The result from matched handler or default
*/
otherwise: (handler: Handler<TResult>) => TResult;
}
/**
* Matcher class implementing PHP-style match expressions for TypeScript/JavaScript
* Supports exhaustive matching with type safety and O(1) lookup performance
*/
export declare class Matcher<TSubject, TResult> {
private readonly subject;
private readonly matches;
private defaultHandler?;
constructor(subject: TSubject);
on(value: TSubject, handler: Handler<TResult>): this;
onAny(values: readonly TSubject[], handler: Handler<TResult>): this;
otherwise(handler: Handler<TResult>): TResult;
default(handler: Handler<TResult>): TResult;
valueOf(): TResult;
private evaluate;
}
/**
* Alias for Handler<T>
*
* @template T The return type of the handler function
* @deprecated Use Handler<T> instead
*/
export declare type MatcherHandler<T> = Handler<T>;
/**
* Error thrown when a match expression has no matching case and no default handler
*
* @class UnhandledMatchError
* @extends Error
*
* @example
* try {
* match('foo')
* .on('bar', () => 'never matches')
* .valueOf()
* } catch (error) {
* if (error instanceof UnhandledMatchError) {
* console.error('No match found')
* }
* }
*/
export declare class UnhandledMatchError extends Error {
/**
* Create an UnhandledMatchError
*
* @param {unknown} value The value that could not be matched
*/
constructor(value: unknown);
}
export { }
+0
-120

@@ -12,143 +12,23 @@ class a extends Error {

class h {
/**
* The value being matched against
* @private
*/
subject;
/**
* Map of values to their corresponding handler functions
* Uses Map for O(1) lookup performance
* @private
*/
matches = /* @__PURE__ */ new Map();
/**
* Default handler to execute if no cases match
* @private
*/
defaultHandler;
/**
* Create a new Matcher instance
*
* @param {TSubject} subject The value to match against
*
* @internal Use the `match()` function instead of instantiating directly
*/
constructor(t) {
this.subject = t;
}
/**
* Add a case to match against the subject
* Uses strict equality (===) for comparison
*
* @param {TSubject} value The value to match against
* @param {MatcherHandler<TResult>} handler Function to execute if this value matches
* @returns {this} The matcher instance for method chaining
*
* @example
* ```typescript
* match('hello')
* .on('hello', () => 'matched')
* .on('goodbye', () => 'not matched')
* ```
*/
on(t, s) {
return this.matches.set(t, s), this;
}
/**
* Add multiple values that map to the same handler
* Simulates PHP's comma-separated case syntax
*
* @param {readonly TSubject[]} values Array of values to match
* @param {MatcherHandler<TResult>} handler Function to execute if any value matches
* @returns {this} The matcher instance for method chaining
*
* @example HTTP Status Codes
* ```typescript
* match(statusCode)
* .onAny([200, 201, 202], () => 'Success')
* .onAny([400, 401, 403], () => 'Client Error')
* .otherwise(() => 'Unknown')
* ```
*
* @see on For matching a single value
*/
onAny(t, s) {
return t.forEach((r) => this.matches.set(r, s)), this;
}
/**
* Set the default handler and execute the match expression
* This method triggers evaluation of all accumulated cases
*
* @param {MatcherHandler<TResult>} handler Function to execute if no cases match
* @returns {TResult} The result from the matched handler or the default handler
* @throws {UnhandledMatchError} If no case matches and no handler catches it
*
* @example
* ```typescript
* const result = match(status)
* .on('active', () => 'Active')
* .on('inactive', () => 'Inactive')
* .otherwise(() => 'Unknown status')
* ```
*
* @see default For PHP-compatible alias
* @see valueOf For executing without a default handler
*/
otherwise(t) {
return this.defaultHandler = t, this.evaluate();
}
/**
* PHP-compatible alias for otherwise()
* Identical behavior - sets default handler and executes
*
* @param {MatcherHandler<TResult>} handler Function to execute if no cases match
* @returns {TResult} The result from the matched handler or the default handler
* @throws {UnhandledMatchError} If no case matches
*
* @example
* ```typescript
* // PHP-style syntax
* const result = match(value)
* .on('case1', () => 'Result1')
* .default(() => 'Default')
* ```
*
* @see otherwise For the standard method
*/
default(t) {
return this.otherwise(t);
}
/**
* Execute the match expression without a default handler
* Throws if no case matches
*
* @returns {TResult} The result from the matched handler
* @throws {UnhandledMatchError} If no case matches
*
* @example
* ```typescript
* try {
* const result = match(code)
* .on(200, () => 'OK')
* .on(404, () => 'Not Found')
* .valueOf() // Must have matched
* } catch (error) {
* if (error instanceof UnhandledMatchError) {
* console.error('Invalid code:', error.message)
* }
* }
* ```
*
* @see otherwise For safe execution with default handler
*/
valueOf() {
return this.evaluate();
}
/**
* Evaluate the match expression by finding the matching case
*
* @private
* @returns {TResult} The result from matched handler or default
* @throws {UnhandledMatchError} If no match and no default handler
*/
evaluate() {

@@ -155,0 +35,0 @@ if (this.matches.has(this.subject))

+1
-1

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

{"version":3,"file":"index.es.js","sources":["../src/Matcher.ts"],"sourcesContent":["import type { MatcherHandler } from './types/main'\n\n/**\n * Error thrown when a match expression has no matching case and no default handler\n *\n * @class UnhandledMatchError\n * @extends Error\n *\n * @example\n * try {\n * match('foo')\n * .on('bar', () => 'never matches')\n * .valueOf()\n * } catch (error) {\n * if (error instanceof UnhandledMatchError) {\n * console.error('No match found')\n * }\n * }\n */\nclass UnhandledMatchError extends Error {\n /**\n * Create an UnhandledMatchError\n *\n * @param {unknown} value The value that could not be matched\n */\n constructor(value: unknown) {\n super(`Unhandled match value: ${JSON.stringify(value)}`)\n this.name = 'UnhandledMatchError'\n }\n}\n\n/**\n * Matcher class implementing PHP-style match expressions for TypeScript/JavaScript\n * Supports exhaustive matching with type safety and O(1) lookup performance\n *\n * @template TSubject The type of values being matched against\n * @template TResult The return type of the match expression\n *\n * @class Matcher\n *\n * @example\n * ```typescript\n * const result = match('foo')\n * .on('foo', () => 'matched foo')\n * .on('bar', () => 'matched bar')\n * .otherwise(() => 'default')\n * ```\n *\n * @example HTTP Status Codes\n * ```typescript\n * const message = match(statusCode)\n * .on(200, () => 'OK')\n * .onAny([201, 202], () => 'Accepted')\n * .on(404, () => 'Not Found')\n * .otherwise(() => 'Unknown')\n * ```\n */\nclass Matcher<TSubject, TResult> {\n /**\n * The value being matched against\n * @private\n */\n private readonly subject: TSubject\n\n /**\n * Map of values to their corresponding handler functions\n * Uses Map for O(1) lookup performance\n * @private\n */\n private readonly matches: Map<TSubject, MatcherHandler<TResult>> = new Map()\n\n /**\n * Default handler to execute if no cases match\n * @private\n */\n private defaultHandler?: MatcherHandler<TResult>\n\n /**\n * Create a new Matcher instance\n *\n * @param {TSubject} subject The value to match against\n *\n * @internal Use the `match()` function instead of instantiating directly\n */\n constructor(subject: TSubject) {\n this.subject = subject\n }\n\n /**\n * Add a case to match against the subject\n * Uses strict equality (===) for comparison\n *\n * @param {TSubject} value The value to match against\n * @param {MatcherHandler<TResult>} handler Function to execute if this value matches\n * @returns {this} The matcher instance for method chaining\n *\n * @example\n * ```typescript\n * match('hello')\n * .on('hello', () => 'matched')\n * .on('goodbye', () => 'not matched')\n * ```\n */\n on(value: TSubject, handler: MatcherHandler<TResult>): this {\n this.matches.set(value, handler)\n return this\n }\n\n /**\n * Add multiple values that map to the same handler\n * Simulates PHP's comma-separated case syntax\n *\n * @param {readonly TSubject[]} values Array of values to match\n * @param {MatcherHandler<TResult>} handler Function to execute if any value matches\n * @returns {this} The matcher instance for method chaining\n *\n * @example HTTP Status Codes\n * ```typescript\n * match(statusCode)\n * .onAny([200, 201, 202], () => 'Success')\n * .onAny([400, 401, 403], () => 'Client Error')\n * .otherwise(() => 'Unknown')\n * ```\n *\n * @see on For matching a single value\n */\n onAny(values: readonly TSubject[], handler: MatcherHandler<TResult>): this {\n values.forEach((value) => this.matches.set(value, handler))\n return this\n }\n\n /**\n * Set the default handler and execute the match expression\n * This method triggers evaluation of all accumulated cases\n *\n * @param {MatcherHandler<TResult>} handler Function to execute if no cases match\n * @returns {TResult} The result from the matched handler or the default handler\n * @throws {UnhandledMatchError} If no case matches and no handler catches it\n *\n * @example\n * ```typescript\n * const result = match(status)\n * .on('active', () => 'Active')\n * .on('inactive', () => 'Inactive')\n * .otherwise(() => 'Unknown status')\n * ```\n *\n * @see default For PHP-compatible alias\n * @see valueOf For executing without a default handler\n */\n otherwise(handler: MatcherHandler<TResult>): TResult {\n this.defaultHandler = handler\n return this.evaluate()\n }\n\n /**\n * PHP-compatible alias for otherwise()\n * Identical behavior - sets default handler and executes\n *\n * @param {MatcherHandler<TResult>} handler Function to execute if no cases match\n * @returns {TResult} The result from the matched handler or the default handler\n * @throws {UnhandledMatchError} If no case matches\n *\n * @example\n * ```typescript\n * // PHP-style syntax\n * const result = match(value)\n * .on('case1', () => 'Result1')\n * .default(() => 'Default')\n * ```\n *\n * @see otherwise For the standard method\n */\n default(handler: MatcherHandler<TResult>): TResult {\n return this.otherwise(handler)\n }\n\n /**\n * Execute the match expression without a default handler\n * Throws if no case matches\n *\n * @returns {TResult} The result from the matched handler\n * @throws {UnhandledMatchError} If no case matches\n *\n * @example\n * ```typescript\n * try {\n * const result = match(code)\n * .on(200, () => 'OK')\n * .on(404, () => 'Not Found')\n * .valueOf() // Must have matched\n * } catch (error) {\n * if (error instanceof UnhandledMatchError) {\n * console.error('Invalid code:', error.message)\n * }\n * }\n * ```\n *\n * @see otherwise For safe execution with default handler\n */\n valueOf(): TResult {\n return this.evaluate()\n }\n\n /**\n * Evaluate the match expression by finding the matching case\n *\n * @private\n * @returns {TResult} The result from matched handler or default\n * @throws {UnhandledMatchError} If no match and no default handler\n */\n private evaluate(): TResult {\n if (this.matches.has(this.subject)) {\n return this.matches.get(this.subject)!()\n } else if (this.defaultHandler) {\n return this.defaultHandler()\n }\n throw new UnhandledMatchError(this.subject)\n }\n}\n\n/**\n * Create a new PHP-style match expression\n *\n * @template TSubject The type of the value being matched\n * @template TResult The return type of the match expression handlers\n *\n * @param {TSubject} subject The value to match against (any type)\n * @returns {Matcher<TSubject, TResult>} A Matcher instance for method chaining\n *\n * @example Basic String Matching\n * ```typescript\n * const status = match(statusCode)\n * .on(200, () => 'success')\n * .on(404, () => 'not found')\n * .otherwise(() => 'error')\n * ```\n *\n * @example HTTP Status Codes\n * ```typescript\n * const message = match(code)\n * .onAny([200, 201, 202], () => 'Success')\n * .onAny([400, 401, 403], () => 'Client Error')\n * .on(500, () => 'Server Error')\n * .otherwise(() => 'Unknown')\n * ```\n *\n * @example Conditional Logic\n * ```typescript\n * const result = match(true)\n * .on(age < 18, () => 'Minor')\n * .on(age >= 18 && age < 65, () => 'Adult')\n * .on(age >= 65, () => 'Senior')\n * .otherwise(() => 'Unknown')\n * ```\n *\n * @see Matcher For complete API documentation\n */\nfunction match<TSubject, TResult>(subject: TSubject): Matcher<TSubject, TResult> {\n return new Matcher<TSubject, TResult>(subject)\n}\n\nexport { match, UnhandledMatchError, Matcher }\n"],"names":["UnhandledMatchError","value","Matcher","subject","handler","values","match"],"mappings":"AAmBA,MAAMA,UAA4B,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtC,YAAYC,GAAgB;AAC1B,UAAM,0BAA0B,KAAK,UAAUA,CAAK,CAAC,EAAE,GACvD,KAAK,OAAO;AAAA,EACd;AACF;AA4BA,MAAMC,EAA2B;AAAA;AAAA;AAAA;AAAA;AAAA,EAKd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,8BAAsD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASR,YAAYC,GAAmB;AAC7B,SAAK,UAAUA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,GAAGF,GAAiBG,GAAwC;AAC1D,gBAAK,QAAQ,IAAIH,GAAOG,CAAO,GACxB;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAMC,GAA6BD,GAAwC;AACzE,WAAAC,EAAO,QAAQ,CAACJ,MAAU,KAAK,QAAQ,IAAIA,GAAOG,CAAO,CAAC,GACnD;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,UAAUA,GAA2C;AACnD,gBAAK,iBAAiBA,GACf,KAAK,SAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,QAAQA,GAA2C;AACjD,WAAO,KAAK,UAAUA,CAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBA,UAAmB;AACjB,WAAO,KAAK,SAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,WAAoB;AAC1B,QAAI,KAAK,QAAQ,IAAI,KAAK,OAAO;AAC/B,aAAO,KAAK,QAAQ,IAAI,KAAK,OAAO,EAAA;AACtC,QAAW,KAAK;AACd,aAAO,KAAK,eAAA;AAEd,UAAM,IAAIJ,EAAoB,KAAK,OAAO;AAAA,EAC5C;AACF;AAuCA,SAASM,EAAyBH,GAA+C;AAC/E,SAAO,IAAID,EAA2BC,CAAO;AAC/C;"}
{"version":3,"file":"index.es.js","sources":["../src/errors.ts","../src/matcher.ts"],"sourcesContent":["/**\n * Error thrown when a match expression has no matching case and no default handler\n *\n * @class UnhandledMatchError\n * @extends Error\n *\n * @example\n * try {\n * match('foo')\n * .on('bar', () => 'never matches')\n * .valueOf()\n * } catch (error) {\n * if (error instanceof UnhandledMatchError) {\n * console.error('No match found')\n * }\n * }\n */\nexport class UnhandledMatchError extends Error {\n /**\n * Create an UnhandledMatchError\n *\n * @param {unknown} value The value that could not be matched\n */\n constructor(value: unknown) {\n super(`Unhandled match value: ${JSON.stringify(value)}`)\n this.name = 'UnhandledMatchError'\n }\n}\n","import type { Handler } from './types'\nimport { UnhandledMatchError } from './errors'\n\n/**\n * Matcher class implementing PHP-style match expressions for TypeScript/JavaScript\n * Supports exhaustive matching with type safety and O(1) lookup performance\n */\nexport class Matcher<TSubject, TResult> {\n private readonly subject: TSubject\n private readonly matches: Map<TSubject, Handler<TResult>> = new Map()\n private defaultHandler?: Handler<TResult>\n\n constructor(subject: TSubject) {\n this.subject = subject\n }\n\n on(value: TSubject, handler: Handler<TResult>): this {\n this.matches.set(value, handler)\n return this\n }\n\n onAny(values: readonly TSubject[], handler: Handler<TResult>): this {\n values.forEach((value) => this.matches.set(value, handler))\n return this\n }\n\n otherwise(handler: Handler<TResult>): TResult {\n this.defaultHandler = handler\n return this.evaluate()\n }\n\n default(handler: Handler<TResult>): TResult {\n return this.otherwise(handler)\n }\n\n valueOf(): TResult {\n return this.evaluate()\n }\n\n private evaluate(): TResult {\n if (this.matches.has(this.subject)) {\n return this.matches.get(this.subject)!()\n } else if (this.defaultHandler) {\n return this.defaultHandler()\n }\n throw new UnhandledMatchError(this.subject)\n }\n}\n\nexport function match<TSubject, TResult = unknown>(subject: TSubject): Matcher<TSubject, TResult> {\n return new Matcher<TSubject, TResult>(subject)\n}\n"],"names":["UnhandledMatchError","value","Matcher","subject","handler","values","match"],"mappings":"AAiBO,MAAMA,UAA4B,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM7C,YAAYC,GAAgB;AAC1B,UAAM,0BAA0B,KAAK,UAAUA,CAAK,CAAC,EAAE,GACvD,KAAK,OAAO;AAAA,EACd;AACF;ACpBO,MAAMC,EAA2B;AAAA,EACrB;AAAA,EACA,8BAA+C,IAAA;AAAA,EACxD;AAAA,EAER,YAAYC,GAAmB;AAC7B,SAAK,UAAUA;AAAA,EACjB;AAAA,EAEA,GAAGF,GAAiBG,GAAiC;AACnD,gBAAK,QAAQ,IAAIH,GAAOG,CAAO,GACxB;AAAA,EACT;AAAA,EAEA,MAAMC,GAA6BD,GAAiC;AAClE,WAAAC,EAAO,QAAQ,CAACJ,MAAU,KAAK,QAAQ,IAAIA,GAAOG,CAAO,CAAC,GACnD;AAAA,EACT;AAAA,EAEA,UAAUA,GAAoC;AAC5C,gBAAK,iBAAiBA,GACf,KAAK,SAAA;AAAA,EACd;AAAA,EAEA,QAAQA,GAAoC;AAC1C,WAAO,KAAK,UAAUA,CAAO;AAAA,EAC/B;AAAA,EAEA,UAAmB;AACjB,WAAO,KAAK,SAAA;AAAA,EACd;AAAA,EAEQ,WAAoB;AAC1B,QAAI,KAAK,QAAQ,IAAI,KAAK,OAAO;AAC/B,aAAO,KAAK,QAAQ,IAAI,KAAK,OAAO,EAAA;AACtC,QAAW,KAAK;AACd,aAAO,KAAK,eAAA;AAEd,UAAM,IAAIJ,EAAoB,KAAK,OAAO;AAAA,EAC5C;AACF;AAEO,SAASM,EAAmCH,GAA+C;AAChG,SAAO,IAAID,EAA2BC,CAAO;AAC/C;"}

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

{"version":3,"file":"index.umd.js","sources":["../src/Matcher.ts"],"sourcesContent":["import type { MatcherHandler } from './types/main'\n\n/**\n * Error thrown when a match expression has no matching case and no default handler\n *\n * @class UnhandledMatchError\n * @extends Error\n *\n * @example\n * try {\n * match('foo')\n * .on('bar', () => 'never matches')\n * .valueOf()\n * } catch (error) {\n * if (error instanceof UnhandledMatchError) {\n * console.error('No match found')\n * }\n * }\n */\nclass UnhandledMatchError extends Error {\n /**\n * Create an UnhandledMatchError\n *\n * @param {unknown} value The value that could not be matched\n */\n constructor(value: unknown) {\n super(`Unhandled match value: ${JSON.stringify(value)}`)\n this.name = 'UnhandledMatchError'\n }\n}\n\n/**\n * Matcher class implementing PHP-style match expressions for TypeScript/JavaScript\n * Supports exhaustive matching with type safety and O(1) lookup performance\n *\n * @template TSubject The type of values being matched against\n * @template TResult The return type of the match expression\n *\n * @class Matcher\n *\n * @example\n * ```typescript\n * const result = match('foo')\n * .on('foo', () => 'matched foo')\n * .on('bar', () => 'matched bar')\n * .otherwise(() => 'default')\n * ```\n *\n * @example HTTP Status Codes\n * ```typescript\n * const message = match(statusCode)\n * .on(200, () => 'OK')\n * .onAny([201, 202], () => 'Accepted')\n * .on(404, () => 'Not Found')\n * .otherwise(() => 'Unknown')\n * ```\n */\nclass Matcher<TSubject, TResult> {\n /**\n * The value being matched against\n * @private\n */\n private readonly subject: TSubject\n\n /**\n * Map of values to their corresponding handler functions\n * Uses Map for O(1) lookup performance\n * @private\n */\n private readonly matches: Map<TSubject, MatcherHandler<TResult>> = new Map()\n\n /**\n * Default handler to execute if no cases match\n * @private\n */\n private defaultHandler?: MatcherHandler<TResult>\n\n /**\n * Create a new Matcher instance\n *\n * @param {TSubject} subject The value to match against\n *\n * @internal Use the `match()` function instead of instantiating directly\n */\n constructor(subject: TSubject) {\n this.subject = subject\n }\n\n /**\n * Add a case to match against the subject\n * Uses strict equality (===) for comparison\n *\n * @param {TSubject} value The value to match against\n * @param {MatcherHandler<TResult>} handler Function to execute if this value matches\n * @returns {this} The matcher instance for method chaining\n *\n * @example\n * ```typescript\n * match('hello')\n * .on('hello', () => 'matched')\n * .on('goodbye', () => 'not matched')\n * ```\n */\n on(value: TSubject, handler: MatcherHandler<TResult>): this {\n this.matches.set(value, handler)\n return this\n }\n\n /**\n * Add multiple values that map to the same handler\n * Simulates PHP's comma-separated case syntax\n *\n * @param {readonly TSubject[]} values Array of values to match\n * @param {MatcherHandler<TResult>} handler Function to execute if any value matches\n * @returns {this} The matcher instance for method chaining\n *\n * @example HTTP Status Codes\n * ```typescript\n * match(statusCode)\n * .onAny([200, 201, 202], () => 'Success')\n * .onAny([400, 401, 403], () => 'Client Error')\n * .otherwise(() => 'Unknown')\n * ```\n *\n * @see on For matching a single value\n */\n onAny(values: readonly TSubject[], handler: MatcherHandler<TResult>): this {\n values.forEach((value) => this.matches.set(value, handler))\n return this\n }\n\n /**\n * Set the default handler and execute the match expression\n * This method triggers evaluation of all accumulated cases\n *\n * @param {MatcherHandler<TResult>} handler Function to execute if no cases match\n * @returns {TResult} The result from the matched handler or the default handler\n * @throws {UnhandledMatchError} If no case matches and no handler catches it\n *\n * @example\n * ```typescript\n * const result = match(status)\n * .on('active', () => 'Active')\n * .on('inactive', () => 'Inactive')\n * .otherwise(() => 'Unknown status')\n * ```\n *\n * @see default For PHP-compatible alias\n * @see valueOf For executing without a default handler\n */\n otherwise(handler: MatcherHandler<TResult>): TResult {\n this.defaultHandler = handler\n return this.evaluate()\n }\n\n /**\n * PHP-compatible alias for otherwise()\n * Identical behavior - sets default handler and executes\n *\n * @param {MatcherHandler<TResult>} handler Function to execute if no cases match\n * @returns {TResult} The result from the matched handler or the default handler\n * @throws {UnhandledMatchError} If no case matches\n *\n * @example\n * ```typescript\n * // PHP-style syntax\n * const result = match(value)\n * .on('case1', () => 'Result1')\n * .default(() => 'Default')\n * ```\n *\n * @see otherwise For the standard method\n */\n default(handler: MatcherHandler<TResult>): TResult {\n return this.otherwise(handler)\n }\n\n /**\n * Execute the match expression without a default handler\n * Throws if no case matches\n *\n * @returns {TResult} The result from the matched handler\n * @throws {UnhandledMatchError} If no case matches\n *\n * @example\n * ```typescript\n * try {\n * const result = match(code)\n * .on(200, () => 'OK')\n * .on(404, () => 'Not Found')\n * .valueOf() // Must have matched\n * } catch (error) {\n * if (error instanceof UnhandledMatchError) {\n * console.error('Invalid code:', error.message)\n * }\n * }\n * ```\n *\n * @see otherwise For safe execution with default handler\n */\n valueOf(): TResult {\n return this.evaluate()\n }\n\n /**\n * Evaluate the match expression by finding the matching case\n *\n * @private\n * @returns {TResult} The result from matched handler or default\n * @throws {UnhandledMatchError} If no match and no default handler\n */\n private evaluate(): TResult {\n if (this.matches.has(this.subject)) {\n return this.matches.get(this.subject)!()\n } else if (this.defaultHandler) {\n return this.defaultHandler()\n }\n throw new UnhandledMatchError(this.subject)\n }\n}\n\n/**\n * Create a new PHP-style match expression\n *\n * @template TSubject The type of the value being matched\n * @template TResult The return type of the match expression handlers\n *\n * @param {TSubject} subject The value to match against (any type)\n * @returns {Matcher<TSubject, TResult>} A Matcher instance for method chaining\n *\n * @example Basic String Matching\n * ```typescript\n * const status = match(statusCode)\n * .on(200, () => 'success')\n * .on(404, () => 'not found')\n * .otherwise(() => 'error')\n * ```\n *\n * @example HTTP Status Codes\n * ```typescript\n * const message = match(code)\n * .onAny([200, 201, 202], () => 'Success')\n * .onAny([400, 401, 403], () => 'Client Error')\n * .on(500, () => 'Server Error')\n * .otherwise(() => 'Unknown')\n * ```\n *\n * @example Conditional Logic\n * ```typescript\n * const result = match(true)\n * .on(age < 18, () => 'Minor')\n * .on(age >= 18 && age < 65, () => 'Adult')\n * .on(age >= 65, () => 'Senior')\n * .otherwise(() => 'Unknown')\n * ```\n *\n * @see Matcher For complete API documentation\n */\nfunction match<TSubject, TResult>(subject: TSubject): Matcher<TSubject, TResult> {\n return new Matcher<TSubject, TResult>(subject)\n}\n\nexport { match, UnhandledMatchError, Matcher }\n"],"names":["UnhandledMatchError","value","Matcher","subject","handler","values","match"],"mappings":"8NAmBA,MAAMA,UAA4B,KAAM,CAMtC,YAAYC,EAAgB,CAC1B,MAAM,0BAA0B,KAAK,UAAUA,CAAK,CAAC,EAAE,EACvD,KAAK,KAAO,qBACd,CACF,CA4BA,MAAMC,CAA2B,CAKd,QAOA,YAAsD,IAM/D,eASR,YAAYC,EAAmB,CAC7B,KAAK,QAAUA,CACjB,CAiBA,GAAGF,EAAiBG,EAAwC,CAC1D,YAAK,QAAQ,IAAIH,EAAOG,CAAO,EACxB,IACT,CAoBA,MAAMC,EAA6BD,EAAwC,CACzE,OAAAC,EAAO,QAASJ,GAAU,KAAK,QAAQ,IAAIA,EAAOG,CAAO,CAAC,EACnD,IACT,CAqBA,UAAUA,EAA2C,CACnD,YAAK,eAAiBA,EACf,KAAK,SAAA,CACd,CAoBA,QAAQA,EAA2C,CACjD,OAAO,KAAK,UAAUA,CAAO,CAC/B,CAyBA,SAAmB,CACjB,OAAO,KAAK,SAAA,CACd,CASQ,UAAoB,CAC1B,GAAI,KAAK,QAAQ,IAAI,KAAK,OAAO,EAC/B,OAAO,KAAK,QAAQ,IAAI,KAAK,OAAO,EAAA,EACtC,GAAW,KAAK,eACd,OAAO,KAAK,eAAA,EAEd,MAAM,IAAIJ,EAAoB,KAAK,OAAO,CAC5C,CACF,CAuCA,SAASM,EAAyBH,EAA+C,CAC/E,OAAO,IAAID,EAA2BC,CAAO,CAC/C"}
{"version":3,"file":"index.umd.js","sources":["../src/errors.ts","../src/matcher.ts"],"sourcesContent":["/**\n * Error thrown when a match expression has no matching case and no default handler\n *\n * @class UnhandledMatchError\n * @extends Error\n *\n * @example\n * try {\n * match('foo')\n * .on('bar', () => 'never matches')\n * .valueOf()\n * } catch (error) {\n * if (error instanceof UnhandledMatchError) {\n * console.error('No match found')\n * }\n * }\n */\nexport class UnhandledMatchError extends Error {\n /**\n * Create an UnhandledMatchError\n *\n * @param {unknown} value The value that could not be matched\n */\n constructor(value: unknown) {\n super(`Unhandled match value: ${JSON.stringify(value)}`)\n this.name = 'UnhandledMatchError'\n }\n}\n","import type { Handler } from './types'\nimport { UnhandledMatchError } from './errors'\n\n/**\n * Matcher class implementing PHP-style match expressions for TypeScript/JavaScript\n * Supports exhaustive matching with type safety and O(1) lookup performance\n */\nexport class Matcher<TSubject, TResult> {\n private readonly subject: TSubject\n private readonly matches: Map<TSubject, Handler<TResult>> = new Map()\n private defaultHandler?: Handler<TResult>\n\n constructor(subject: TSubject) {\n this.subject = subject\n }\n\n on(value: TSubject, handler: Handler<TResult>): this {\n this.matches.set(value, handler)\n return this\n }\n\n onAny(values: readonly TSubject[], handler: Handler<TResult>): this {\n values.forEach((value) => this.matches.set(value, handler))\n return this\n }\n\n otherwise(handler: Handler<TResult>): TResult {\n this.defaultHandler = handler\n return this.evaluate()\n }\n\n default(handler: Handler<TResult>): TResult {\n return this.otherwise(handler)\n }\n\n valueOf(): TResult {\n return this.evaluate()\n }\n\n private evaluate(): TResult {\n if (this.matches.has(this.subject)) {\n return this.matches.get(this.subject)!()\n } else if (this.defaultHandler) {\n return this.defaultHandler()\n }\n throw new UnhandledMatchError(this.subject)\n }\n}\n\nexport function match<TSubject, TResult = unknown>(subject: TSubject): Matcher<TSubject, TResult> {\n return new Matcher<TSubject, TResult>(subject)\n}\n"],"names":["UnhandledMatchError","value","Matcher","subject","handler","values","match"],"mappings":"8NAiBO,MAAMA,UAA4B,KAAM,CAM7C,YAAYC,EAAgB,CAC1B,MAAM,0BAA0B,KAAK,UAAUA,CAAK,CAAC,EAAE,EACvD,KAAK,KAAO,qBACd,CACF,CCpBO,MAAMC,CAA2B,CACrB,QACA,YAA+C,IACxD,eAER,YAAYC,EAAmB,CAC7B,KAAK,QAAUA,CACjB,CAEA,GAAGF,EAAiBG,EAAiC,CACnD,YAAK,QAAQ,IAAIH,EAAOG,CAAO,EACxB,IACT,CAEA,MAAMC,EAA6BD,EAAiC,CAClE,OAAAC,EAAO,QAASJ,GAAU,KAAK,QAAQ,IAAIA,EAAOG,CAAO,CAAC,EACnD,IACT,CAEA,UAAUA,EAAoC,CAC5C,YAAK,eAAiBA,EACf,KAAK,SAAA,CACd,CAEA,QAAQA,EAAoC,CAC1C,OAAO,KAAK,UAAUA,CAAO,CAC/B,CAEA,SAAmB,CACjB,OAAO,KAAK,SAAA,CACd,CAEQ,UAAoB,CAC1B,GAAI,KAAK,QAAQ,IAAI,KAAK,OAAO,EAC/B,OAAO,KAAK,QAAQ,IAAI,KAAK,OAAO,EAAA,EACtC,GAAW,KAAK,eACd,OAAO,KAAK,eAAA,EAEd,MAAM,IAAIJ,EAAoB,KAAK,OAAO,CAC5C,CACF,CAEO,SAASM,EAAmCH,EAA+C,CAChG,OAAO,IAAID,EAA2BC,CAAO,CAC/C"}

@@ -5,3 +5,3 @@ {

"private": false,
"version": "0.0.8",
"version": "0.1.1",
"type": "module",

@@ -8,0 +8,0 @@ "license": "MIT",

@@ -5,3 +5,3 @@ # @anilkumarthakur/match

[![license](https://img.shields.io/npm/l/@anilkumarthakur/match)](LICENSE)
[![tests](https://img.shields.io/badge/tests-245-brightgreen)](test/)
[![tests](https://img.shields.io/badge/tests-127-brightgreen)](test/)
[![coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)](#)

@@ -18,4 +18,4 @@

🚀 **Fast**: Efficient equality-based matching using JavaScript's Map
📦 **Lightweight**: Zero dependencies, ~1.2KB gzipped
🧪 **Well-Tested**: 245 comprehensive tests with 100% code coverage
📦 **Lightweight**: Zero dependencies, only ~0.5KB gzipped (ES module)
🧪 **Well-Tested**: 127 comprehensive tests with 100% code coverage
🔗 **Chainable**: Fluent API for method chaining

@@ -389,7 +389,7 @@ 🌍 **Cross-Platform**: Works in Node.js and browsers (ESM + UMD)

- 💾 Lazy evaluation - only matched handler executes
- 📦 ~1.2KB gzipped bundle size
- 📦 Bundle sizes: ES (1.03 kB / 0.46 kB gzipped), UMD (0.99 kB / 0.51 kB gzipped)
## Testing
The library includes 245 comprehensive tests with 100% code coverage:
The library includes 127 comprehensive tests with 100% code coverage:

@@ -396,0 +396,0 @@ ```bash

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

import { match, UnhandledMatchError, Matcher } from '../src/Matcher'
import { match, Matcher } from '../src/matcher'
import { UnhandledMatchError } from '../src/errors'

@@ -1073,2 +1074,50 @@ beforeEach(() => {

})
test('typed handleCheck example', () => {
type CheckResult = { ok: boolean; code: number; warn?: boolean }
const handleCheck = (check: string): CheckResult => {
return match<string, CheckResult>(check)
.on('error', () => ({ ok: false, code: 500 }))
.on('warn', () => ({ ok: true, code: 200, warn: true }))
.on('ok', () => ({ ok: true, code: 200 }))
.otherwise(() => ({ ok: false, code: 400 }))
}
const errorResult = handleCheck('error')
expect(errorResult.ok).toBe(false)
expect(errorResult.code).toBe(500)
const warnResult = handleCheck('warn')
expect(warnResult.ok).toBe(true)
expect(warnResult.warn).toBe(true)
const okResult = handleCheck('ok')
expect(okResult.ok).toBe(true)
expect(okResult.code).toBe(200)
const unknownResult = handleCheck('unknown')
expect(unknownResult.ok).toBe(false)
expect(unknownResult.code).toBe(400)
})
test('type safety with object return type', () => {
interface ApiResponse {
status: string
data?: unknown
error?: string
}
const getResponse = (code: number): ApiResponse => {
return match<number, ApiResponse>(code)
.on(200, () => ({ status: 'success', data: {} }))
.on(404, () => ({ status: 'error', error: 'Not found' }))
.on(500, () => ({ status: 'error', error: 'Server error' }))
.otherwise(() => ({ status: 'unknown' }))
}
expect(getResponse(200).status).toBe('success')
expect(getResponse(404).error).toBe('Not found')
expect(getResponse(999).status).toBe('unknown')
})
})

@@ -1146,9 +1195,12 @@

test('FizzBuzz example', () => {
test('FizzBuzz example using modulo matching', () => {
// Note: This pattern uses match(0) to check if remainder is 0
// Order matters - more specific checks must come last since Map overwrites
const fizzbuzz = (num: number) =>
match(0)
.on(num % 15, () => 'FizzBuzz')
.on(num % 3, () => 'Fizz')
.on(num % 5, () => 'Buzz')
match(num % 15 === 0 ? 'fizzbuzz' : num % 3 === 0 ? 'fizz' : num % 5 === 0 ? 'buzz' : 'num')
.on('fizzbuzz', () => 'FizzBuzz')
.on('fizz', () => 'Fizz')
.on('buzz', () => 'Buzz')
.otherwise(() => num.toString())
expect(fizzbuzz(15)).toBe('FizzBuzz')
expect(fizzbuzz(3)).toBe('Fizz')

@@ -1159,2 +1211,38 @@ expect(fizzbuzz(5)).toBe('Buzz')

test('FizzBuzz with conditional match(true)', () => {
// When using match(true), only one condition should be true at a time
// Use early return pattern or most specific check first
const fizzBuzz = (n: number): string => {
// Check most specific first
if (n % 15 === 0) {
return match<boolean, string>(true)
.on(true, () => 'FizzBuzz')
.otherwise(() => '')
}
return match<boolean, string>(true)
.on(n % 3 === 0, () => 'Fizz')
.on(n % 5 === 0, () => 'Buzz')
.otherwise(() => n.toString())
}
expect(fizzBuzz(15)).toBe('FizzBuzz')
expect(fizzBuzz(9)).toBe('Fizz')
expect(fizzBuzz(5)).toBe('Buzz')
expect(fizzBuzz(7)).toBe('7')
})
test('FizzBuzz with simple if-else (for comparison)', () => {
const fizzBuzz = (n: number): string => {
if (n % 15 === 0) return 'FizzBuzz'
if (n % 3 === 0) return 'Fizz'
if (n % 5 === 0) return 'Buzz'
return n.toString()
}
expect(fizzBuzz(15)).toBe('FizzBuzz')
expect(fizzBuzz(9)).toBe('Fizz')
expect(fizzBuzz(5)).toBe('Buzz')
expect(fizzBuzz(7)).toBe('7')
})
test('days in month example', () => {

@@ -1161,0 +1249,0 @@ const isLeap = (year: number) => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)

import { match } from './match';
/**
* Create a new match expression
*
* @template TSubject The type of the value being matched
* @template TResult The return type of handler functions
*
* @param {TSubject} subject The value to match against
* @returns {Matcher<TSubject, TResult>} A Matcher instance
*
* @see {@link Matcher} For complete API documentation
*/
export { match };
/**
* Core classes and types for the match expression library
*/
export { Matcher, UnhandledMatchError } from './Matcher';
/**
* Type for match handler functions
*
* @template T The return type of the handler
*
* @example
* ```typescript
* const handler: Handler<string> = () => 'result'
* ```
*/
export type { Handler, MatchChain, MatcherHandler } from './types/main';
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAE/B;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,EAAE,CAAA;AAEhB;;GAEG;AACH,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAExD;;;;;;;;;GASG;AACH,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA"}
/**
* Re-export module for backwards compatibility
*
* This module maintains backwards compatibility by re-exporting
* from the main Matcher module. New code should import from
* the root index.ts or directly from Matcher.ts
*
* @example
* ```typescript
* // Backwards compatible (legacy)
* import { match } from './match'
*
* // Recommended
* import { match } from '@anilkumarthakur/match'
* ```
*/
export { match } from './Matcher';
/**
* Re-export all types from the types module
*/
export type { MatchChain, Handler, MatcherHandler } from './types/main';
//# sourceMappingURL=match.d.ts.map
{"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../src/match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AAEjC;;GAEG;AACH,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA"}
import { MatcherHandler } from './types/main';
/**
* Error thrown when a match expression has no matching case and no default handler
*
* @class UnhandledMatchError
* @extends Error
*
* @example
* try {
* match('foo')
* .on('bar', () => 'never matches')
* .valueOf()
* } catch (error) {
* if (error instanceof UnhandledMatchError) {
* console.error('No match found')
* }
* }
*/
declare class UnhandledMatchError extends Error {
/**
* Create an UnhandledMatchError
*
* @param {unknown} value The value that could not be matched
*/
constructor(value: unknown);
}
/**
* Matcher class implementing PHP-style match expressions for TypeScript/JavaScript
* Supports exhaustive matching with type safety and O(1) lookup performance
*
* @template TSubject The type of values being matched against
* @template TResult The return type of the match expression
*
* @class Matcher
*
* @example
* ```typescript
* const result = match('foo')
* .on('foo', () => 'matched foo')
* .on('bar', () => 'matched bar')
* .otherwise(() => 'default')
* ```
*
* @example HTTP Status Codes
* ```typescript
* const message = match(statusCode)
* .on(200, () => 'OK')
* .onAny([201, 202], () => 'Accepted')
* .on(404, () => 'Not Found')
* .otherwise(() => 'Unknown')
* ```
*/
declare class Matcher<TSubject, TResult> {
/**
* The value being matched against
* @private
*/
private readonly subject;
/**
* Map of values to their corresponding handler functions
* Uses Map for O(1) lookup performance
* @private
*/
private readonly matches;
/**
* Default handler to execute if no cases match
* @private
*/
private defaultHandler?;
/**
* Create a new Matcher instance
*
* @param {TSubject} subject The value to match against
*
* @internal Use the `match()` function instead of instantiating directly
*/
constructor(subject: TSubject);
/**
* Add a case to match against the subject
* Uses strict equality (===) for comparison
*
* @param {TSubject} value The value to match against
* @param {MatcherHandler<TResult>} handler Function to execute if this value matches
* @returns {this} The matcher instance for method chaining
*
* @example
* ```typescript
* match('hello')
* .on('hello', () => 'matched')
* .on('goodbye', () => 'not matched')
* ```
*/
on(value: TSubject, handler: MatcherHandler<TResult>): this;
/**
* Add multiple values that map to the same handler
* Simulates PHP's comma-separated case syntax
*
* @param {readonly TSubject[]} values Array of values to match
* @param {MatcherHandler<TResult>} handler Function to execute if any value matches
* @returns {this} The matcher instance for method chaining
*
* @example HTTP Status Codes
* ```typescript
* match(statusCode)
* .onAny([200, 201, 202], () => 'Success')
* .onAny([400, 401, 403], () => 'Client Error')
* .otherwise(() => 'Unknown')
* ```
*
* @see on For matching a single value
*/
onAny(values: readonly TSubject[], handler: MatcherHandler<TResult>): this;
/**
* Set the default handler and execute the match expression
* This method triggers evaluation of all accumulated cases
*
* @param {MatcherHandler<TResult>} handler Function to execute if no cases match
* @returns {TResult} The result from the matched handler or the default handler
* @throws {UnhandledMatchError} If no case matches and no handler catches it
*
* @example
* ```typescript
* const result = match(status)
* .on('active', () => 'Active')
* .on('inactive', () => 'Inactive')
* .otherwise(() => 'Unknown status')
* ```
*
* @see default For PHP-compatible alias
* @see valueOf For executing without a default handler
*/
otherwise(handler: MatcherHandler<TResult>): TResult;
/**
* PHP-compatible alias for otherwise()
* Identical behavior - sets default handler and executes
*
* @param {MatcherHandler<TResult>} handler Function to execute if no cases match
* @returns {TResult} The result from the matched handler or the default handler
* @throws {UnhandledMatchError} If no case matches
*
* @example
* ```typescript
* // PHP-style syntax
* const result = match(value)
* .on('case1', () => 'Result1')
* .default(() => 'Default')
* ```
*
* @see otherwise For the standard method
*/
default(handler: MatcherHandler<TResult>): TResult;
/**
* Execute the match expression without a default handler
* Throws if no case matches
*
* @returns {TResult} The result from the matched handler
* @throws {UnhandledMatchError} If no case matches
*
* @example
* ```typescript
* try {
* const result = match(code)
* .on(200, () => 'OK')
* .on(404, () => 'Not Found')
* .valueOf() // Must have matched
* } catch (error) {
* if (error instanceof UnhandledMatchError) {
* console.error('Invalid code:', error.message)
* }
* }
* ```
*
* @see otherwise For safe execution with default handler
*/
valueOf(): TResult;
/**
* Evaluate the match expression by finding the matching case
*
* @private
* @returns {TResult} The result from matched handler or default
* @throws {UnhandledMatchError} If no match and no default handler
*/
private evaluate;
}
/**
* Create a new PHP-style match expression
*
* @template TSubject The type of the value being matched
* @template TResult The return type of the match expression handlers
*
* @param {TSubject} subject The value to match against (any type)
* @returns {Matcher<TSubject, TResult>} A Matcher instance for method chaining
*
* @example Basic String Matching
* ```typescript
* const status = match(statusCode)
* .on(200, () => 'success')
* .on(404, () => 'not found')
* .otherwise(() => 'error')
* ```
*
* @example HTTP Status Codes
* ```typescript
* const message = match(code)
* .onAny([200, 201, 202], () => 'Success')
* .onAny([400, 401, 403], () => 'Client Error')
* .on(500, () => 'Server Error')
* .otherwise(() => 'Unknown')
* ```
*
* @example Conditional Logic
* ```typescript
* const result = match(true)
* .on(age < 18, () => 'Minor')
* .on(age >= 18 && age < 65, () => 'Adult')
* .on(age >= 65, () => 'Senior')
* .otherwise(() => 'Unknown')
* ```
*
* @see Matcher For complete API documentation
*/
declare function match<TSubject, TResult>(subject: TSubject): Matcher<TSubject, TResult>;
export { match, UnhandledMatchError, Matcher };
//# sourceMappingURL=Matcher.d.ts.map
{"version":3,"file":"Matcher.d.ts","sourceRoot":"","sources":["../../src/Matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAElD;;;;;;;;;;;;;;;;GAgBG;AACH,cAAM,mBAAoB,SAAQ,KAAK;IACrC;;;;OAIG;gBACS,KAAK,EAAE,OAAO;CAI3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,cAAM,OAAO,CAAC,QAAQ,EAAE,OAAO;IAC7B;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAElC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoD;IAE5E;;;OAGG;IACH,OAAO,CAAC,cAAc,CAAC,CAAyB;IAEhD;;;;;;OAMG;gBACS,OAAO,EAAE,QAAQ;IAI7B;;;;;;;;;;;;;;OAcG;IACH,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,IAAI;IAK3D;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,IAAI;IAK1E;;;;;;;;;;;;;;;;;;OAkBG;IACH,SAAS,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,OAAO;IAKpD;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,OAAO;IAIlD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,IAAI,OAAO;IAIlB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ;CAQjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,iBAAS,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAE/E;AAED,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,CAAA"}
/**
* Internal handler function type for match expression results
*
* Used internally by the Matcher class to store and execute handler functions.
* This is an alias for Handler<T> with the same signature.
*
* @template T The return type of the handler function
*
* @internal Internal use only
*/
export type MatcherHandler<T> = () => T;
/**
* Handler function type for match expression results
*
* A handler is a function that takes no parameters and returns a value
* of type T. Used in match expressions to define what happens when a case matches.
*
* @template T The return type of the handler function
*
* @example
* ```typescript
* const handler: Handler<string> = () => 'matched'
* const numHandler: Handler<number> = () => 42
* ```
*/
export type Handler<T> = () => T;
/**
* Interface representing a chainable match expression
*
* Provides the API contract for method chaining in match expressions.
* Implementations should support fluent interface patterns.
*
* @template TSubject The type of the value being matched against
* @template TResult The return type of handler functions
*
* @example
* ```typescript
* interface MatchChain<string, number> {
* on: (value: string, handler: Handler<number>) => MatchChain<string, number>
* otherwise: (handler: Handler<number>) => number
* }
* ```
*/
export interface MatchChain<TSubject, TResult> {
/**
* Add a case to match against the subject
*
* @param {TSubject} value The value to match
* @param {Handler<TResult>} handler Function to execute if matched
* @returns {MatchChain<TSubject, TResult>} The matcher for chaining
*/
on: (value: TSubject, handler: Handler<TResult>) => MatchChain<TSubject, TResult>;
/**
* Set default handler and execute the match
*
* @param {Handler<TResult>} handler Function to execute if no cases match
* @returns {TResult} The result from matched handler or default
*/
otherwise: (handler: Handler<TResult>) => TResult;
}
//# sourceMappingURL=main.d.ts.map
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/types/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,MAAM,CAAC,CAAA;AAEvC;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,MAAM,CAAC,CAAA;AAEhC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,UAAU,CAAC,QAAQ,EAAE,OAAO;IAC3C;;;;;;OAMG;IACH,EAAE,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAEjF;;;;;OAKG;IACH,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAA;CAClD"}
export {};
//# sourceMappingURL=comprehensive.test.d.ts.map
{"version":3,"file":"comprehensive.test.d.ts","sourceRoot":"","sources":["../../test/comprehensive.test.ts"],"names":[],"mappings":""}
export {};
//# sourceMappingURL=coverage.test.d.ts.map
{"version":3,"file":"coverage.test.d.ts","sourceRoot":"","sources":["../../test/coverage.test.ts"],"names":[],"mappings":""}
export {};
//# sourceMappingURL=match.test.d.ts.map
{"version":3,"file":"match.test.d.ts","sourceRoot":"","sources":["../../test/match.test.ts"],"names":[],"mappings":""}
export {};
//# sourceMappingURL=match1.test.d.ts.map
{"version":3,"file":"match1.test.d.ts","sourceRoot":"","sources":["../../test/match1.test.ts"],"names":[],"mappings":""}
export {};
//# sourceMappingURL=match2.test.d.ts.map
{"version":3,"file":"match2.test.d.ts","sourceRoot":"","sources":["../../test/match2.test.ts"],"names":[],"mappings":""}
import { match, UnhandledMatchError, Matcher } from '../src/Matcher'
describe('Complete Coverage Tests', () => {
describe('onAny method', () => {
test('onAny - matches multiple values to same handler', () => {
const result = match('a')
.onAny(['a', 'b', 'c'], () => 'matched')
.otherwise(() => 'default')
expect(result).toBe('matched')
})
test('onAny - matches second value in array', () => {
const result = match('b')
.onAny(['a', 'b', 'c'], () => 'matched')
.otherwise(() => 'default')
expect(result).toBe('matched')
})
test('onAny - matches last value in array', () => {
const result = match('c')
.onAny(['a', 'b', 'c'], () => 'matched')
.otherwise(() => 'default')
expect(result).toBe('matched')
})
test('onAny - does not match value outside array', () => {
const result = match('d')
.onAny(['a', 'b', 'c'], () => 'matched')
.otherwise(() => 'default')
expect(result).toBe('default')
})
test('onAny - works with numbers', () => {
const result = match(2)
.onAny([1, 2, 3], () => 'matched')
.otherwise(() => 'default')
expect(result).toBe('matched')
})
test('onAny - empty array does not match', () => {
const result = match('a')
.onAny([], () => 'matched')
.otherwise(() => 'default')
expect(result).toBe('default')
})
test('onAny - chaining with multiple onAny calls', () => {
const result = match('x')
.onAny(['a', 'b'], () => 'first')
.onAny(['x', 'y'], () => 'second')
.otherwise(() => 'default')
expect(result).toBe('second')
})
test('onAny - readonly array support', () => {
const values: readonly string[] = ['foo', 'bar']
const result = match('foo')
.onAny(values, () => 'matched')
.otherwise(() => 'default')
expect(result).toBe('matched')
})
test('onAny - mixed with on() method', () => {
const result = match('b')
.on('a', () => 'single')
.onAny(['b', 'c'], () => 'multiple')
.otherwise(() => 'default')
expect(result).toBe('multiple')
})
test('onAny - handler with side effects', () => {
const fn = jest.fn(() => 'matched')
const result = match('b')
.onAny(['a', 'b', 'c'], fn)
.otherwise(() => 'default')
expect(result).toBe('matched')
expect(fn).toHaveBeenCalledTimes(1)
})
test('onAny - returns this for chaining', () => {
const matcher = match('a')
.onAny(['a', 'b'], () => 'test')
expect(typeof matcher.on).toBe('function')
expect(typeof matcher.otherwise).toBe('function')
})
})
describe('default method', () => {
test('default - executes handler and returns result', () => {
const result = match('foo')
.on('bar', () => 'bar')
.default(() => 'default result')
expect(result).toBe('default result')
})
test('default - matches case if found', () => {
const result = match('foo')
.on('foo', () => 'matched')
.default(() => 'default')
expect(result).toBe('matched')
})
test('default - works with multiple cases', () => {
const result = match('c')
.on('a', () => 'a')
.on('b', () => 'b')
.default(() => 'default')
expect(result).toBe('default')
})
test('default - returns various types', () => {
expect(
match('x')
.on('y', () => 'str')
.default(() => 'default')
).toBe('default')
expect(
match('x')
.on('y', () => 123)
.default(() => 456)
).toBe(456)
expect(
match('x')
.on('y', () => true)
.default(() => false)
).toBe(false)
})
test('default - is equivalent to otherwise', () => {
const matcher1 = match('test')
.on('other', () => 'other')
const matcher2 = match('test')
.on('other', () => 'other')
const defaultResult = matcher1.default(() => 'default')
const otherwiseResult = matcher2.otherwise(() => 'default')
expect(defaultResult).toBe(otherwiseResult)
})
test('default - executes handler with side effects', () => {
const fn = jest.fn(() => 'result')
const result = match('no-match')
.on('something', () => 'something')
.default(fn)
expect(result).toBe('result')
expect(fn).toHaveBeenCalledTimes(1)
})
test('default - can override previous default', () => {
const matcher = match('x').on('y', () => 'y')
expect(matcher.default(() => 'first')).toBe('first')
expect(matcher.default(() => 'second')).toBe('second')
})
test('default - throws when no match and handler throws', () => {
expect(() =>
match('x')
.on('y', () => 'y')
.default(() => {
throw new Error('Custom error')
})
).toThrow('Custom error')
})
test('default - with null result', () => {
const result = match('x')
.on('y', () => 'y')
.default(() => null)
expect(result).toBeNull()
})
test('default - with undefined result', () => {
const result = match('x')
.on('y', () => 'y')
.default(() => undefined)
expect(result).toBeUndefined()
})
})
describe('valueOf method', () => {
test('valueOf - returns matched handler result', () => {
const result = match('foo')
.on('foo', () => 'matched')
.valueOf()
expect(result).toBe('matched')
})
test('valueOf - throws UnhandledMatchError when no match', () => {
expect(() => {
match('foo')
.on('bar', () => 'bar')
.valueOf()
}).toThrow(UnhandledMatchError)
})
test('valueOf - throws with correct error message', () => {
expect(() => {
match('test-value')
.on('other', () => 'other')
.valueOf()
}).toThrow('Unhandled match value: "test-value"')
})
test('valueOf - with multiple cases, first match wins', () => {
const result = match('b')
.on('a', () => 'first')
.on('b', () => 'second')
.on('c', () => 'third')
.valueOf()
expect(result).toBe('second')
})
test('valueOf - returns various types', () => {
expect(
match('str')
.on('str', () => 'string result')
.valueOf()
).toBe('string result')
expect(
match('num')
.on('num', () => 42)
.valueOf()
).toBe(42)
expect(
match('bool')
.on('bool', () => true)
.valueOf()
).toBe(true)
})
test('valueOf - with object result', () => {
const obj = { key: 'value' }
const result = match('obj')
.on('obj', () => obj)
.valueOf()
expect(result).toBe(obj)
})
test('valueOf - with array result', () => {
const arr = [1, 2, 3]
const result = match('arr')
.on('arr', () => arr)
.valueOf()
expect(result).toBe(arr)
})
test('valueOf - with function result', () => {
const fn = () => 'test'
const result = match('fn')
.on('fn', () => fn)
.valueOf()
expect(result).toBe(fn)
})
test('valueOf - with null result', () => {
const result = match('null')
.on('null', () => null)
.valueOf()
expect(result).toBeNull()
})
test('valueOf - with undefined result', () => {
const result = match('undef')
.on('undef', () => undefined)
.valueOf()
expect(result).toBeUndefined()
})
test('valueOf - called multiple times returns same result', () => {
const matcher = match('a')
.on('a', () => 'result')
expect(matcher.valueOf()).toBe('result')
expect(matcher.valueOf()).toBe('result')
})
test('valueOf - throws error from handler', () => {
expect(() => {
match('error')
.on('error', () => {
throw new Error('Handler error')
})
.valueOf()
}).toThrow('Handler error')
})
test('valueOf - with complex nested matching', () => {
const result = match('status')
.on('loading', () => 'loading')
.on('status', () => {
return match('details')
.on('details', () => 'detailed status')
.valueOf()
})
.valueOf()
expect(result).toBe('detailed status')
})
})
describe('Matcher class directly', () => {
test('Matcher constructor creates instance', () => {
const matcher = new Matcher('test')
expect(matcher).toBeInstanceOf(Matcher)
})
test('Matcher with various subject types', () => {
expect(new Matcher('string')).toBeInstanceOf(Matcher)
expect(new Matcher(123)).toBeInstanceOf(Matcher)
expect(new Matcher(true)).toBeInstanceOf(Matcher)
expect(new Matcher(null)).toBeInstanceOf(Matcher)
expect(new Matcher(undefined)).toBeInstanceOf(Matcher)
expect(new Matcher({})).toBeInstanceOf(Matcher)
expect(new Matcher([])).toBeInstanceOf(Matcher)
})
test('Matcher.on returns this', () => {
const matcher = new Matcher('test')
const result = matcher.on('test', () => 'result')
expect(result).toBe(matcher)
})
test('Matcher.onAny returns this', () => {
const matcher = new Matcher('test')
const result = matcher.onAny(['test'], () => 'result')
expect(result).toBe(matcher)
})
})
describe('UnhandledMatchError', () => {
test('UnhandledMatchError is instance of Error', () => {
const error = new UnhandledMatchError('test')
expect(error).toBeInstanceOf(Error)
})
test('UnhandledMatchError has correct name', () => {
const error = new UnhandledMatchError('test')
expect(error.name).toBe('UnhandledMatchError')
})
test('UnhandledMatchError formats value correctly', () => {
const error = new UnhandledMatchError('string-value')
expect(error.message).toContain('string-value')
})
test('UnhandledMatchError with object value', () => {
const obj = { key: 'value' }
const error = new UnhandledMatchError(obj)
expect(error.message).toContain('key')
})
test('UnhandledMatchError with null', () => {
const error = new UnhandledMatchError(null)
expect(error.message).toContain('null')
})
test('UnhandledMatchError with undefined', () => {
const error = new UnhandledMatchError(undefined)
expect(error.message).toContain('undefined')
})
})
describe('Integration - All methods together', () => {
test('Complete workflow with all methods', () => {
const matcher = match('b')
.on('a', () => 'a')
.onAny(['b', 'c'], () => 'bc')
.on('d', () => 'd')
expect(matcher.valueOf()).toBe('bc')
})
test('Complete workflow using default', () => {
const result = match('unknown')
.on('a', () => 'a')
.onAny(['b', 'c'], () => 'bc')
.default(() => 'unknown value')
expect(result).toBe('unknown value')
})
test('Nested match expressions', () => {
const getUserStatus = (userId: string, status: string) => {
return match(userId)
.on('admin', () => {
return match(status)
.on('active', () => 'admin is active')
.on('inactive', () => 'admin is inactive')
.default(() => 'admin status unknown')
})
.on('user', () => {
return match(status)
.on('active', () => 'user is active')
.default(() => 'user is inactive')
})
.default(() => 'user not found')
}
expect(getUserStatus('admin', 'active')).toBe('admin is active')
expect(getUserStatus('user', 'active')).toBe('user is active')
expect(getUserStatus('guest', 'active')).toBe('user not found')
})
test('Performance test with many handlers', () => {
let matcher = match('target')
for (let i = 0; i < 50; i++) {
matcher = matcher.on(`case-${i}`, () => `result-${i}`)
}
matcher = matcher.on('target', () => 'found target')
const result = matcher.valueOf()
expect(result).toBe('found target')
})
})
})
import { match } from '../src/Matcher'
describe('match function', () => {
it('should return the correct action for the matched case', () => {
const result = match('test')
.on('test', () => 'matched')
.on('not matched', () => 'not matched')
.otherwise(() => 'otherwise')
expect(result).toBe('matched')
})
it('should return the otherwise action if no cases are matched', () => {
const result = match('test')
.on('not matched', () => 'not matched')
.otherwise(() => 'otherwise')
expect(result).toBe('otherwise')
expect(result).not.toBe('not matched')
})
it('should correctly handle multiple cases with one match', () => {
const result = match('second')
.on('first', () => 'first case')
.on('second', () => 'second case')
.on('third', () => 'third case')
.otherwise(() => 'otherwise')
expect(result).toBe('second case')
expect(result).not.toBe('first case')
expect(result).not.toBe('third case')
expect(result).not.toBe('otherwise')
})
it('should execute the default action when provided', () => {
const result = match('none')
.on('first', () => 'first case')
.on('second', () => 'second case')
.otherwise(() => 'default action')
expect(result).toBe('default action')
expect(result).not.toBe('first case')
expect(result).not.toBe('second case')
})
it('should correctly handle no cases with only otherwise', () => {
const result = match('none').otherwise(() => 'default action')
expect(result).toBe('default action')
expect(result).not.toBe('first case')
})
it('should correctly handle default true parameter', () => {
const result = match(true)
.on(true, () => true)
.otherwise(() => 'default action')
expect(result).toBe(true)
expect(result).not.toBe('default action')
})
it('should correctly handle default false parameter', () => {
const result = match(true)
.on(true, () => 'true case')
.otherwise(() => 'default action')
expect(result).toBe('true case')
expect(result).not.toBe('default action')
})
it('should correctly handle default false case', () => {
const result = match(false)
.on(true, () => true)
.otherwise(() => 'default action')
expect(result).toBe('default action')
expect(result).not.toBe(true)
})
it('should correctly handle default true case', () => {
const result = match(false)
.on(false, () => false)
.otherwise(() => 'default action')
expect(result).toBe(false)
expect(result).not.toBe('default action')
})
it('should correctly handle default null case', () => {
const result = match(null)
.on(null, () => null)
.otherwise(() => 'default action')
expect(result).toBe(null)
expect(result).not.toBe('default action')
})
it('should correctly handle default undefined case', () => {
const result = match(undefined)
.on(undefined, () => undefined)
.otherwise(() => 'default action')
expect(result).toBe(undefined)
expect(result).not.toBe('default action')
})
it('Match function test', () => {
expect(() =>
match(1)
.on(1, () => 1)
.otherwise(() => 'default action')
).not.toThrow()
expect(() =>
match(2)
.on(1, () => 1)
.otherwise(() => 'default action')
).not.toThrow()
})
})
import { match } from '../src/Matcher'
describe('match function', () => {
test('executes the matching handler when subject matches an on condition', () => {
const result = match('success')
.on('success', () => 'success-handler')
.on('error', () => 'error-handler')
.otherwise(() => 'otherwise-handler')
expect(result).toBe('success-handler')
})
test('executes the correct handler when multiple .on are provided and matches the later one', () => {
const result = match('error')
.on('info', () => 'info-handler')
.on('success', () => 'success-handler')
.on('error', () => 'error-handler')
.on('warning', () => 'warning-handler')
.otherwise(() => 'otherwise-handler')
expect(result).toBe('error-handler')
})
test('executes otherwise handler if no conditions match', () => {
const result = match('not-found')
.on('success', () => 'success-handler')
.on('error', () => 'error-handler')
.otherwise(() => 'otherwise-handler')
expect(result).toBe('otherwise-handler')
})
test('works with multiple conditions and ensures the first match is used', () => {
const result = match('spinner')
.on('success', () => 'success-handler')
.on('error', () => 'error-handler')
.on('warning', () => 'warning-handler')
.on('info', () => 'info-handler')
.on('defaultNotify', () => 'defaultNotify-handler')
.on('dark', () => 'dark-handler')
.on('light', () => 'light-handler')
.on('spinner', () => 'spinner-handler')
.otherwise(() => 'otherwise-handler')
expect(result).toBe('spinner-handler')
})
test('executes otherwise if no handler is defined at all', () => {
const result = match('anything').otherwise(() => 'no-cases')
expect(result).toBe('no-cases')
})
test('check that console logs or side effects can happen inside handlers', () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {})
const result = match('warning')
.on('success', () => {
console.log('success log')
return 'success-handler'
})
.on('warning', () => {
console.log('warning log')
return 'warning-handler'
})
.otherwise(() => {
console.log('otherwise log')
return 'otherwise-handler'
})
expect(result).toBe('warning-handler')
expect(consoleSpy).toHaveBeenCalledWith('warning log')
expect(consoleSpy).not.toHaveBeenCalledWith('success log')
expect(consoleSpy).not.toHaveBeenCalledWith('otherwise log')
consoleSpy.mockRestore()
})
})
import { match, UnhandledMatchError } from '../src/Matcher'
const consoleLogMock = jest.spyOn(console, 'log').mockImplementation()
beforeEach(() => {
jest.clearAllMocks()
})
describe('match utility comprehensive tests', () => {
// 1. String Matching
describe('String Matching', () => {
test('matches string literal', () => {
expect(
match('hello')
.on('hello', () => 'matched')
.otherwise(() => 'default')
).toBe('matched')
expect(match('hello').otherwise(() => 'default')).toBe('default')
expect(
match('world')
.on('hello', () => 'matched')
.on('world', () => 'world')
.otherwise(() => 'default')
).toBe('world')
expect(
match('nope')
.on('something', () => 'something')
.on('nope', () => 'nope')
.valueOf()
).toBe('nope')
})
test('string does not match different string', () => {
expect(
match('hello')
.on('world', () => 'world')
.on('hellos', () => 'hellos')
.otherwise(() => 'default')
).toBe('default')
})
test('empty string matches', () => {
expect(
match('')
.on('', () => 'empty')
.otherwise(() => 'default')
).toBe('empty')
})
})
// 2. Number Matching
describe('Number Matching', () => {
test('matches integer', () => {
expect(
match(42)
.on(42, () => 'forty-two')
.otherwise(() => 'default')
).toBe('forty-two')
})
test('matches zero', () => {
expect(
match(0)
.on(0, () => 'zero')
.otherwise(() => 'default')
).toBe('zero')
})
test('+0 matches -0', () => {
expect(
match(+0)
.on(-0, () => 'zero matched')
.otherwise(() => 'default')
).toBe('zero matched')
})
test('matches negative number', () => {
expect(
match(-1)
.on(-1, () => 'negative one')
.otherwise(() => 'default')
).toBe('negative one')
})
test('matches decimal number', () => {
expect(
match(3.14)
.on(3.14, () => 'pi')
.otherwise(() => 'default')
).toBe('pi')
})
test('does not match different number', () => {
expect(
match(10)
.on(9, () => 'nine')
.otherwise(() => 'default')
).toBe('default')
})
test('0 subject matches -0 key', () => {
expect(
match(0)
.on(-0, () => 'minus zero')
.otherwise(() => 'default')
).toBe('minus zero')
})
test('matches Infinity', () => {
expect(
match(Infinity)
.on(Infinity, () => 'infinity matched')
.otherwise(() => 'default')
).toBe('infinity matched')
})
test('matches -Infinity', () => {
expect(
match(-Infinity)
.on(-Infinity, () => 'minus infinity matched')
.otherwise(() => 'default')
).toBe('minus infinity matched')
})
})
// 3. Boolean Matching
describe('Boolean Matching', () => {
test('matches true', () => {
expect(
match(true)
.on(true, () => 'yes')
.otherwise(() => 'no')
).toBe('yes')
})
test('matches false', () => {
expect(
match(false)
.on(false, () => 'no')
.otherwise(() => 'yes')
).toBe('no')
})
test('false does not match true', () => {
expect(
match(false)
.on(true, () => 'yes')
.otherwise(() => 'no')
).toBe('no')
})
})
// 4. Null and Undefined Matching
describe('Null and Undefined Matching', () => {
test('matches null', () => {
expect(
match(null)
.on(null, () => 'null matched')
.otherwise(() => 'default')
).toBe('null matched')
})
test('matches undefined', () => {
expect(
match(undefined)
.on(undefined, () => 'undefined matched')
.otherwise(() => 'default')
).toBe('undefined matched')
})
test('subject null matches correctly with side effect', () => {
const fn = jest.fn(() => 'matched')
const result = match(null)
.on(null, fn)
.otherwise(() => 'default')
expect(result).toBe('matched')
expect(fn).toHaveBeenCalledTimes(1)
})
})
// 5. Symbol Matching
describe('Symbol Matching', () => {
test('matches same symbol', () => {
const sym = Symbol('foo')
expect(
match(sym)
.on(sym, () => 'symbol matched')
.otherwise(() => 'default')
).toBe('symbol matched')
})
test('does not match different symbols', () => {
expect(
match(Symbol('foo'))
.on(Symbol('foo'), () => 'symbol matched')
.otherwise(() => 'default')
).toBe('default')
})
})
// 6. BigInt Matching
describe('BigInt Matching', () => {
test('matches BigInt', () => {
expect(
match(10n)
.on(10n, () => 'bigint matched')
.otherwise(() => 'default')
).toBe('bigint matched')
})
test('does not match different BigInt', () => {
expect(
match(10n)
.on(20n, () => 'bigint matched')
.otherwise(() => 'default')
).toBe('default')
})
})
// 7. Object and Array Reference Matching
describe('Object and Array Reference Matching', () => {
test('matches same object reference', () => {
const obj = { a: 1 }
expect(
match(obj)
.on(obj, () => 'matched object')
.otherwise(() => 'default')
).toBe('matched object')
})
test('does not match identical object by value', () => {
expect(
match({ a: 1 })
.on({ a: 1 }, () => 'matched object')
.otherwise(() => 'default')
).toBe('default')
})
test('matches same array reference', () => {
const arr = [1, 2]
expect(
match(arr)
.on(arr, () => 'matched array')
.otherwise(() => 'default')
).toBe('matched array')
})
test('does not match identical array by value', () => {
expect(
match([1, 2])
.on([1, 2], () => 'matched array')
.otherwise(() => 'default')
).toBe('default')
})
test('object with toString does not affect matching', () => {
const obj = {
toString() {
return 'foo'
}
}
expect(
match(obj)
.on(obj, () => 'matched')
.otherwise(() => 'default')
).toBe('matched')
})
})
// 8. Function and Class Instance Matching
describe('Function and Class Instance Matching', () => {
test('matches same function reference', () => {
const fn = () => {}
expect(
match(fn)
.on(fn, () => 'matched fn')
.otherwise(() => 'default')
).toBe('matched fn')
})
test('does not match different function with same implementation', () => {
expect(
match(() => {})
.on(
() => {},
() => 'matched fn'
)
.otherwise(() => 'default')
).toBe('default')
})
test('class instance matching by reference', () => {
class A {}
const a = new A()
expect(
match(a)
.on(a, () => 'matched instance')
.otherwise(() => 'default')
).toBe('matched instance')
})
test('different class instance no match', () => {
class A {}
expect(
match(new A())
.on(new A(), () => 'matched instance')
.otherwise(() => 'default')
).toBe('default')
})
})
// 9. Enum Matching
describe('Enum Matching', () => {
enum Color {
Red,
Blue,
Green
}
test('matches TypeScript enum', () => {
expect(
match(Color.Blue)
.on(Color.Red, () => 'red')
.on(Color.Blue, () => 'blue')
.on(Color.Green, () => 'green')
.otherwise(() => 'unknown')
).toBe('blue')
})
})
// // 10. Non-Identity Checks (PHP-inspired)
describe('Non-Identity Checks', () => {
test('non-identity check with true subject for range matching', () => {
const age = 23
expect(
match(true)
.on(age >= 65, () => 'senior')
.on(age >= 25, () => 'adult')
.on(age >= 18, () => 'young adult')
.otherwise(() => 'kid')
).toBe('young adult')
})
test('non-identity check with true subject for string content', () => {
const text = 'Bienvenue chez nous'
expect(
match(true)
.on(text.includes('Welcome') || text.includes('Hello'), () => 'en')
.on(text.includes('Bienvenue') || text.includes('Bonjour'), () => 'fr')
.otherwise(() => 'unknown')
).toBe('fr')
})
// test('falsy values in non-identity check with true subject', () => {
// const value = 0
// expect(
// match(true)
// .on(value === 0, () => 'zero')
// .on(value === 1, () => 'one')
// .otherwise(() => 'other')
// ).toBe('zero')
// })
})
// 11. Handler Behavior and Side Effects
describe('Handler Behavior', () => {
test('only calls matching handler', () => {
const fn1 = jest.fn(() => 'foo')
const fn2 = jest.fn(() => 'bar')
const fnDefault = jest.fn(() => 'default')
const result = match('bar').on('foo', fn1).on('bar', fn2).otherwise(fnDefault)
expect(result).toBe('bar')
expect(fn1).not.toHaveBeenCalled()
expect(fn2).toHaveBeenCalledTimes(1)
expect(fnDefault).not.toHaveBeenCalled()
})
test('handler modifies external variable', () => {
let called = false
const result = match('test')
.on('test', () => {
called = true
return 'ok'
})
.otherwise(() => 'fail')
expect(result).toBe('ok')
expect(called).toBe(true)
})
test('handlers return various types', () => {
expect(
match('str')
.on('str', () => 'string')
.otherwise(() => 'default')
).toBe('string')
expect(
match('num')
.on('num', () => 123)
.otherwise(() => 0)
).toBe(123)
expect(
match('bool')
.on('bool', () => true)
.otherwise(() => false)
).toBe(true)
const obj = { foo: 'bar' }
expect(
match('obj')
.on('obj', () => obj)
.otherwise(() => ({}))
).toBe(obj)
expect(
match('undef')
.on('undef', () => undefined)
.otherwise(() => 'default')
).toBeUndefined()
})
test('async handler returns Promise', async () => {
const result = match('async')
.on('async', async () => 'resolved')
.otherwise(() => 'default')
await expect(result).resolves.toBe('resolved')
})
test('handler throws exception', () => {
expect(() =>
match('test')
.on('test', () => {
throw new Error('Handler error')
})
.otherwise(() => 'default')
).toThrow('Handler error')
})
})
// 12. Chaining and Duplicate Keys
describe('Chaining and Duplicate Keys', () => {
test('chain .on returns this for chaining', () => {
const matcher = match('a')
.on('a', () => 'A')
.on('b', () => 'B')
expect(typeof matcher.otherwise).toBe('function')
})
test('many chained .on calls', () => {
const result = match('x')
.on('a', () => 'A')
.on('b', () => 'B')
.on('c', () => 'C')
.on('x', () => 'X')
.otherwise(() => 'default')
expect(result).toBe('X')
})
test('duplicate keys overwrite previous handlers', () => {
const result = match('key')
.on('key', () => 'first')
.on('key', () => 'second')
.otherwise(() => 'default')
expect(result).toBe('second')
})
test('same handler used for multiple keys', () => {
const handler = jest.fn(() => 'handled')
const result = match('bar')
.on('foo', handler)
.on('bar', handler)
.otherwise(() => 'default')
expect(result).toBe('handled')
expect(handler).toHaveBeenCalledTimes(1)
})
})
// 13. Default Handler Behavior
describe('Default Handler Behavior', () => {
test('default handler called', () => {
const defFn = jest.fn(() => 'default')
const result = match('nope')
.on('something', () => 'something')
.otherwise(defFn)
expect(result).toBe('default')
expect(defFn).toHaveBeenCalledTimes(1)
})
test('multiple otherwise calls use last handler', () => {
const matcher = match('foo').on('bar', () => 'bar')
expect(matcher.otherwise(() => 'first')).toBe('first')
expect(matcher.otherwise(() => 'second')).toBe('second')
})
})
// // 14. Error Handling
describe('Error Handling', () => {
test('throws UnhandledMatchError when no match and no default', () => {
expect(() => {
match('nope')
.on('something', () => 'something')
.otherwise(() => {
throw new UnhandledMatchError('nope')
})
}).toThrow(UnhandledMatchError)
expect(() => {
match('nope')
.on('something', () => 'something')
.otherwise(() => {
throw new UnhandledMatchError('nope')
})
}).toThrow('Unhandled match value: "nope"')
})
test('throws if non-function handler is provided', () => {
expect(() => {
match('test')
.on('tests', () => 'not a function')
.otherwise(() => {
throw new UnhandledMatchError('nope')
})
}).toThrow() // TypeScript should catch this, or runtime error
})
})
// 15. Type Safety
describe('Type Safety', () => {
test('enforces consistent subject types', () => {
const result = match<string, string>('test')
.on('test', () => 'matched')
.otherwise(() => 'default')
expect(result).toBe('matched')
})
test('enforces consistent return types', () => {
const result = match<string, number>('test')
.on('test', () => 1)
.otherwise(() => 2)
expect(result).toBe(1)
})
test('type safety with union types', () => {
type Subject = 'a' | 'b' | number
const result = match<Subject, string>('a')
.on('a', () => 'A')
.on('b', () => 'B')
.on(42, () => 'Number')
.otherwise(() => 'default')
expect(result).toBe('A')
})
})
// 16. Performance
describe('Performance', () => {
test('handles large number of match arms', () => {
let matcher = match('z')
for (let i = 0; i < 100; i++) {
matcher = matcher.on(`key${i}`, () => `matched ${i}`)
}
const result = matcher.otherwise(() => 'default')
expect(result).toBe('default')
})
})
// 17. Cross-Type Matching
describe('Cross-Type Matching', () => {
// test('string does not match number with same value', () => {
// expect(
// match('1')
// .on(1, () => 'number one')
// .otherwise(() => 'default')
// ).toBe('default')
// })
})
// 18. Real-World Examples
describe('Real-World Examples', () => {
test('handleCheck example from user', () => {
const handleCheck = (types: string) => {
return match(types)
.on('success', () => {
console.log('----------------success output--', 'success')
return 'success'
})
.on('error', () => {
console.log('----------------error output--', 'error')
return 'error'
})
.on('warning', () => {
console.log('----------------warning output--', 'warning')
return 'warning'
})
.on('info', () => {
console.log('----------------info output--', 'info')
return 'info'
})
.on('defaultNotify', () => {
console.log('----------------defaultNotify output--', 'defaultNotify')
return 'defaultNotify'
})
.on('dark', () => {
console.log('----------------dark output--', 'dark')
return 'dark'
})
.on('light', () => {
console.log('----------------light output--', 'light')
return 'light'
})
.on('spinner', () => {
console.log('----------------spinner output--', 'spinner')
return 'spinner'
})
.otherwise(() => {
console.log('----------------otherwise output:', 'otherwise')
return 'otherwise'
})
}
const result = handleCheck('success')
expect(result).toBe('success')
expect(consoleLogMock).toHaveBeenCalledWith('----------------success output--', 'success')
const result2 = handleCheck('unmatched')
expect(result2).toBe('otherwise')
expect(consoleLogMock).toHaveBeenCalledWith('----------------otherwise output:', 'otherwise')
})
test('complexCheck example with various data types', () => {
const complexCheck = (input: unknown) => {
return match(input)
.on('hello', () => 'Matched hello')
.on(42, () => 'Matched number 42')
.on(true, () => 'Matched true')
.on(null, () => 'Matched null')
.on(undefined, () => 'Matched undefined')
.otherwise(() => 'No match found')
}
expect(complexCheck('hello')).toBe('Matched hello')
expect(complexCheck(42)).toBe('Matched number 42')
expect(complexCheck(true)).toBe('Matched true')
expect(complexCheck(null)).toBe('Matched null')
expect(complexCheck(undefined)).toBe('Matched undefined')
expect(complexCheck('unmatched')).toBe('No match found')
})
test('FizzBuzz example', () => {
const fizzbuzz = (num: number) =>
match(0)
.on(num % 15, () => 'FizzBuzz')
.on(num % 3, () => 'Fizz')
.on(num % 5, () => 'Buzz')
.otherwise(() => num.toString())
expect(fizzbuzz(3)).toBe('Fizz')
expect(fizzbuzz(5)).toBe('Buzz')
// expect(fizzbuzz(15)).toBe('FizzBuzz')
expect(fizzbuzz(7)).toBe('7')
})
test('days in month example', () => {
const isLeap = (year: number) => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
const daysInMonth = (month: string, year: number) =>
match(month.toLowerCase().slice(0, 3))
.on('jan', () => 31)
.on('feb', () => (isLeap(year) ? 29 : 28))
.on('mar', () => 31)
.on('apr', () => 30)
.on('may', () => 31)
.on('jun', () => 30)
.on('jul', () => 31)
.on('aug', () => 31)
.on('sep', () => 30)
.on('oct', () => 31)
.on('nov', () => 30)
.on('dec', () => 31)
.otherwise(() => {
throw new Error('Bogus month')
})
expect(daysInMonth('January', 2025)).toBe(31)
expect(daysInMonth('February', 2024)).toBe(29)
expect(daysInMonth('February', 2025)).toBe(28)
expect(daysInMonth('April', 2025)).toBe(30)
expect(() => daysInMonth('Invalid', 2025)).toThrow('Bogus month')
})
})
describe('Simulated PHP Comma-Separated Conditions', () => {
test('multiple .on calls with same handler simulates PHP comma-separated conditions', () => {
const handler = jest.fn(() => 'one or two')
const result = match(2)
.on(1, handler)
.on(2, handler)
.otherwise(() => 'default')
expect(result).toBe('one or two')
expect(handler).toHaveBeenCalledTimes(1)
})
})
})