Skip to content

No base class for ContentBlock #24

@zastrowm

Description

@zastrowm

Overview

Add a base class for all content blocks to enable runtime type checking using instanceof checks. This will allow developers to easily determine if a value is any type of ContentBlock without needing to check against each individual block type.

Implementation Requirements

Technical Approach

Based on clarification discussion and repository analysis:

  • Pattern: Abstract base class with type discriminator
  • Naming: Use ContentBlockBase as the base class name, keep ContentBlock as the union type
  • Backward Compatibility: MUST maintain - this is a non-breaking, additive change
  • Files Affected: src/types/messages.ts, src/types/media.ts, test files, exports

Base Class Design

Create an abstract base class that all content blocks will extend:

/**
 * Base class for all content blocks.
 * Enables runtime type checking using instanceof.
 * 
 * @example
 * ```typescript
 * if (someValue instanceof ContentBlockBase) {
 *   // someValue is definitely a ContentBlock
 *   console.log(someValue.type)
 * }
 * ```
 */
export abstract class ContentBlockBase {
  /**
   * Discriminator for the content block type.
   * Each subclass defines this as a specific string literal.
   */
  abstract readonly type: string
}

Content Block Classes to Update

In src/types/messages.ts (7 classes):

  • TextBlock
  • ToolUseBlock
  • ToolResultBlock
  • ReasoningBlock
  • CachePointBlock
  • GuardContentBlock
  • JsonBlock

In src/types/media.ts (3 classes):

  • ImageBlock
  • VideoBlock
  • DocumentBlock

Each class MUST:

  1. Extend ContentBlockBase
  2. Keep existing readonly type = '{name}Block' as const (TypeScript allows const string literals for abstract string properties)
  3. Maintain all existing properties and methods
  4. Preserve existing constructor signatures

Example Implementation

// Before:
export class TextBlock implements TextBlockData {
  readonly type = 'textBlock' as const
  readonly text: string
  constructor(data: string) { this.text = data }
}

// After:
export class TextBlock extends ContentBlockBase implements TextBlockData {
  readonly type = 'textBlock' as const  // Still works with abstract type: string
  readonly text: string
  constructor(data: string) { 
    super()
    this.text = data 
  }
}

Type Union (No Changes)

The existing ContentBlock type union MUST remain unchanged for backward compatibility:

export type ContentBlock =
  | TextBlock
  | ToolUseBlock
  | ToolResultBlock
  | ReasoningBlock
  | CachePointBlock
  | GuardContentBlock
  | ImageBlock
  | VideoBlock
  | DocumentBlock

Export Updates

In src/index.ts, add export for the base class:

// Message classes
export {
  ContentBlockBase,  // NEW
  TextBlock,
  ToolUseBlock,
  // ... rest unchanged
} from './types/messages.js'

Testing Requirements

In src/types/__tests__/messages.test.ts:

  • Add test suite for ContentBlockBase
  • Test instanceof checks for each block type
  • Verify type property is accessible
  • Test that non-block objects fail instanceof check

In src/types/__tests__/media.test.ts:

  • Test instanceof checks for media blocks
  • Verify media blocks are instanceof ContentBlockBase

Example test:

describe('ContentBlockBase', () => {
  it('allows instanceof checks for TextBlock', () => {
    const block = new TextBlock('test')
    expect(block instanceof ContentBlockBase).toBe(true)
  })

  it('allows instanceof checks for all block types', () => {
    const blocks = [
      new TextBlock('test'),
      new ToolUseBlock({ name: 'tool', toolUseId: '1', input: {} }),
      new ImageBlock({ format: 'png', source: { bytes: new Uint8Array() } }),
      // ... etc for all block types
    ]
    
    blocks.forEach(block => {
      expect(block instanceof ContentBlockBase).toBe(true)
    })
  })

  it('rejects non-ContentBlock objects', () => {
    const notABlock = { type: 'something', data: 'test' }
    expect(notABlock instanceof ContentBlockBase).toBe(false)
  })
})

Documentation Requirements

  • Add TSDoc documentation to ContentBlockBase class
  • Include @example showing instanceof usage
  • Document the relationship between ContentBlockBase and ContentBlock type union
  • Update any existing documentation that references content block type checking patterns

Acceptance Criteria

  • ContentBlockBase abstract class is created with abstract type property
  • All 9 content block classes extend ContentBlockBase
  • All existing tests pass (backward compatibility verified)
  • New tests verify instanceof checks work for all block types
  • ContentBlockBase is exported from main SDK entry point
  • TSDoc documentation is complete with examples
  • Type checking passes (npm run type-check)
  • Linting passes (npm run lint)
  • Test coverage maintained at 80%+
  • No breaking changes to public API

Files to Modify

  • src/types/messages.ts - Add base class, update 7 block classes
  • src/types/media.ts - Update 3 media block classes to extend base
  • src/types/__tests__/messages.test.ts - Add tests for instanceof checks
  • src/types/__tests__/media.test.ts - Add tests for media block instanceof checks
  • src/index.ts - Export ContentBlockBase

Technical Notes

  • TypeScript allows a subclass with readonly type = 'textBlock' as const to satisfy an abstract type: string property
  • This is a non-breaking change because it only adds functionality, doesn't remove or change existing APIs
  • The ContentBlock type union remains the primary type for type annotations
  • ContentBlockBase is used specifically for runtime instanceof checks

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions