# Spearwolf Signalize Signalize Decorators

> Signalize Decorators (EXPERIMENTAL)

- Skill: `tomevault-io/spearwolf-signalize-signalize-decorators` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/spearwolf-signalize-signalize-decorators`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/spearwolf-signalize-signalize-decorators/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/spearwolf-signalize-signalize-decorators

---


# Signalize Decorators (EXPERIMENTAL)

> **WARNING**: These decorators are **experimental** and should be used with caution.
> The decorator API may change in future versions.
>
> **For production code, prefer manual signal creation:**
>
> - `createSignal()` instead of `@signal()`
> - `createMemo()` instead of `@memo()`

## When to Use Decorators

**Consider using when:**

- Prototyping or personal projects
- Decorator syntax significantly improves readability
- You accept the risk of API changes

**Avoid using when:**

- Production applications requiring long-term stability
- Libraries consumed by others
- You need fine-grained control over signal behavior

## Import

```typescript
import {signal, memo} from '@spearwolf/signalize/decorators';
```

## @signal() Decorator

Creates a reactive property using TC39 class accessor decorators:

```typescript
class Counter {
  @signal() accessor count = 0;
}

const counter = new Counter();
counter.count; // 0 (reactive read)
counter.count = 5; // (reactive write)
```

**Note:** Must use the `accessor` keyword - this is a TC39 decorator requirement.

### Options

```typescript
@signal(options?)
```

| Option        | Type                | Description                                 |
| ------------- | ------------------- | ------------------------------------------- |
| `name`        | `string \| symbol`  | Custom signal name (default: property name) |
| `compare`     | `(a, b) => boolean` | Custom equality comparator                  |
| `readAsValue` | `boolean`           | Read without tracking (non-reactive)        |

### Custom Name

```typescript
class Foo {
  @signal({name: 'internalCount'}) accessor count = 0;
}

// Signal stored as 'internalCount', not 'count'
const group = SignalGroup.get(foo);
group.signal('internalCount'); // Access by name
```

### Custom Comparator

```typescript
class Foo {
  @signal({
    compare: (a, b) => Math.abs(a - b) < 0.01,
  })
  accessor threshold = 0.5;
}
```

## @memo() Decorator

Creates a memoized computed property:

```typescript
class Counter {
  @signal() accessor count = 0;

  @memo() doubled() {
    return this.count * 2;
  }
}

const counter = new Counter();
counter.doubled(); // 0
counter.count = 5;
counter.doubled(); // 10
```

### Options

```typescript
@memo(options?)
```

| Option | Type               | Description                             |
| ------ | ------------------ | --------------------------------------- |
| `name` | `string \| symbol` | Custom memo name (default: method name) |

### Memo Behavior

- Always **lazy** - computed on first call, not on creation
- Cached until dependencies change
- Re-computed on next call after dependency change

```typescript
class Foo {
  @signal() accessor value = 1;
  computeCount = 0;

  @memo() expensive() {
    this.computeCount++;
    return heavyCalculation(this.value);
  }
}

const foo = new Foo();
foo.expensive(); // computeCount = 1
foo.expensive(); // computeCount = 1 (cached)

foo.value = 2;
// computeCount still 1 - lazy, not recomputed yet

foo.expensive(); // computeCount = 2 (recomputed)
```

## Accessing Underlying Signals

```typescript
import {
  findObjectSignalByName,
  findObjectSignalNames,
  destroyObjectSignals,
  value,
} from '@spearwolf/signalize';

class Foo {
  @signal() accessor count = 0;
  @signal({name: 'custom'}) accessor other = '';
}

const foo = new Foo();

// Get signal by name
const countSignal = findObjectSignalByName(foo, 'count');
countSignal.get();
countSignal.set(10);

// List all signal names
findObjectSignalNames(foo); // ['count', 'custom']

// Non-reactive read
value(countSignal);
value([foo, 'count']); // Tuple syntax

// Cleanup
destroyObjectSignals(foo);
```

## SignalGroup Integration

Decorated signals are automatically attached to a SignalGroup:

```typescript
class Foo {
  @signal() accessor count = 0;
}

const foo = new Foo();
const group = SignalGroup.get(foo);

group.signal('count'); // Access signal
group.clear(); // Destroys all signals
```

## Cleanup

```typescript
// Option 1: Destroy all object signals
destroyObjectSignals(foo);

// Option 2: Via SignalGroup
SignalGroup.get(foo)?.clear();
SignalGroup.delete(foo);
```

## Alternative: Manual Approach

For production code, prefer manual signal creation:

```typescript
// Instead of decorators:
class Counter {
  @signal() accessor count = 0;
  @memo() doubled() {
    return this.count * 2;
  }
}

// Use manual approach:
class Counter {
  count = createSignal(0, {attach: this});
  doubled = createMemo(() => this.count.get() * 2, {attach: this});

  destroy() {
    SignalGroup.delete(this);
  }
}
```

**Benefits of manual approach:**

- Full control over signal options
- Explicit lifecycle management
- Stable API
- Works in all environments

## Instance Isolation

Each class instance gets its own signal instances:

```typescript
class Foo {
  @signal() accessor count = 0;
}

const a = new Foo();
const b = new Foo();

a.count = 10;
b.count; // 0 - independent instance
```

## Pitfalls

### 1. Missing `accessor` keyword

```typescript
// BAD - won't work!
@signal() count = 0;

// GOOD
@signal() accessor count = 0;
```

### 2. Custom comparator surprises

```typescript
@signal({
  compare: (a, b) => a === b || a === b + 1
}) accessor value = 1;

foo.value = 2;
foo.value;  // Still 1! (2 === 1 + 1, considered equal)
```

### 3. Expecting memo to auto-update

```typescript
@memo() computed() {
  return this.value * 2;
}

foo.value = 5;
// Memo not recomputed yet - it's lazy!

foo.computed();  // NOW it recomputes
```

### 4. Forgetting cleanup

```typescript
// Always clean up when object is disposed
destroyObjectSignals(foo);
```

## See Also

- [Developer Guide](https://raw.githubusercontent.com/spearwolf/signalize/refs/heads/main/docs/guide.md) - Comprehensive guide to all features
- [Full API Reference](https://raw.githubusercontent.com/spearwolf/signalize/refs/heads/main/docs/full-api.md) - Complete API documentation
- [Cheat Sheet](https://raw.githubusercontent.com/spearwolf/signalize/refs/heads/main/docs/cheat-sheet.md) - Quick reference

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/spearwolf) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-11 -->

