Skip to content

feat: implement is() type checking function - #3

Merged
codewizdave merged 2 commits into
devfrom
task/03-is-function
May 29, 2026
Merged

feat: implement is() type checking function#3
codewizdave merged 2 commits into
devfrom
task/03-is-function

Conversation

@martyy-code

Copy link
Copy Markdown
Contributor

Summary

  • Implement is() function for runtime type checking of error instances
  • Support single and multiple inheritance hierarchies
  • Handle native JavaScript errors (SyntaxError, TypeError, etc.)
  • Use class-based error instances for proper instanceof compatibility

Changes

New is() function

constAppError=error({name: 'AppError'});constValidationError=error({name: 'ValidationError',inherits: AppError});consterr=ValidationError();is(err,ValidationError);// trueis(err,AppError);// true (through inheritance)

Breaking change: _factory_name

The previous _factory reference-based comparison was unreliable. Replaced with _name string comparison for stable type identity.

Implementation details

  • Error instances now extend Error class for proper instanceof support
  • _name property stores the factory name for type checking
  • Inheritance chain walking for parent type checks

Test plan

  • All 53 unit tests passing
  • TypeScript strict mode passes
  • ESLint passes

🤖 Generated with Claude Code

Add is() function for checking error type with inheritance support:
- Symbol-based factory identity (prevents name collision issues)
- Proper native error handling via instanceof
- Inheritance chain walking for parent type checks
- Full instanceof compatibility (errors extend native Error)
Key fixes after review:
- Use Symbol.for() for factory identity instead of string names
- Leverage instanceof for native errors instead of hardcoded list
- Store factory reference via Symbol on error instances
- Errors now properly extend Error class for native compatibility
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Key fixes:
- DFS algorithm with stack for proper multiple inheritance support
- Proper type inference: ExtractFields<T> for accurate type narrowing
- Cyclic protection with seen Set
- No array allocation per iteration (reuse stack)
- Instance points to factory, factory holds inheritance metadata
Type safety improvements:
- is(err, SyntaxError) now returns error is SyntaxError
- is(err, CustomError) returns error is ErrorInstance<CustomFields>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@martyy-codemartyy-code left a comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: feat: implement is() type checking function

Summary

Solid implementation overall. The DFS algorithm with cyclic protection is correct, Symbol-based identity using Symbol.for() is appropriate for cross-realm compatibility, and the type inference via ExtractFields<T> is a reasonable approach given TypeScript's constraints. The PR makes good architectural decisions with proper separation of concerns.

What Works Well

  • Symbol.for() usage is correct - creates globally shared symbol for cross-realm error identity (cross-frame iframe scenarios)
  • DFS with seen Set correctly prevents infinite loops in cyclic inheritance without GC pressure from recursive calls
  • Native error handling with instanceof and try-catch for cross-realm errors is well implemented
  • Both single and array inherits support is handled correctly in the traversal
  • Proper instanceof Error behavior on factory-created errors improves debuggability
  • Good test coverage - tests cover single inheritance, multiple inheritance, deep chains, native errors, and edge cases

Blocking Issues

  1. ExtractFields for native errors falls to Record<string, unknown> (non-blocking for correctness, but affects type narrowing precision)

    The type definition:

    typeExtractFields<T>=TextendsErrorFactory<infer F>
    ? F
    : Textendsnew( ...args: unknown[])=> infer E
    ? EextendsErrorInstance<infer F>
    ? F
    : Record<string,unknown>
    : Record<string,unknown>;

    For is(err, SyntaxError), the chain is: SyntaxError extends new (...) => Error but Error is not ErrorInstance<any>, so it returns Record<string, unknown>. This is acceptable - native errors don't have the fields structure anyway.

  2. Type narrowing with multiple inheritance returns only one branch's fields (inherent TypeScript limitation, not a bug)

    When CombinedError inherits from [NetworkError, StorageError], type narrowing via is(err, NetworkError) returns ErrorInstance<{networkField: ...}> rather than a union. This is standard TypeScript behavior for type predicates - you get the fields corresponding to that specific branch. The implementation is correct; this is a limitation of how TypeScript handles union types in narrowing.

Suggestions (Non-blocking)

  1. Consider extracting the inheritance traversal into a dedicated helper function for reusability, especially if .from() or other methods will need similar traversal logic. Current implementation is correct but scattered across the is() function.

  2. The instanceof Error check succeeds because new Error(message) is used internally. However, instanceof checks against the prototype chain, not the FACTORY_SYMBOL. This is intentional and correct - the Symbol enables type-safe is() checking while instanceof Error provides basic Error compatibility. No change needed.

  3. Cyclic protection: The seen Set correctly handles cyclic inheritance by skipping already-visited factories. This is robust.

Answering Key Questions

  1. Is the DFS algorithm correct for multiple inheritance?
    Yes. The stack-based DFS correctly explores all parent branches. Single inherits: Parent and array inherits: [A, B] are both handled. The seen Set prevents revisiting nodes in diamond inheritance patterns.

  2. Is the type inference (ExtractFields) sound?
    Yes, with two caveats: (a) native errors fall back to Record<string, unknown> which is acceptable, (b) multiple inheritance returns single-branch fields which is a TypeScript limitation, not an implementation bug.

  3. Any logic bugs or edge cases missed?
    No. Null/undefined handling, cross-realm instanceof failures, non-error objects, native errors without factory markers, and cyclic protection are all handled correctly.

  4. Is the Symbol-based identity approach correct?
    Yes. Symbol.for('@deessejs/errors/factory') ensures the same symbol is shared across realms (iframes, workers, different JS contexts). This is the correct approach for cross-realm error identity.

Recommendation

Comment Only - The implementation is correct and ready to merge. No changes required.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@martyy-code@codewizdave