Skip to content

Mapper constructor parameters must be manually deep-frozen (no docs/error) #1151

@hubyrod

Description

@hubyrod

Bug: Mutable mapper parameters cause silent data inconsistencies

Version: @skipruntime/core@0.0.19

Problem

When passing arrays (or other mutable objects) as constructor parameters to Mapper implementations, the reactive graph produces subtle, hard-to-debug data inconsistencies. There is no error, no warning, and no documentation of this requirement.

Reproduction

class PhotoVisibilityFilter implements Mapper<string, Photo, string, Photo> {
  constructor(
    private userId: string,
    private groupIds: string[] // mutable array
  ) {}

  mapEntry(key: string, values: Values<Photo>): Iterable<[string, Photo]> {
    const photo = values.getUnique();
    if (this.groupIds.includes(photo.groupId)) {
      return [[key, photo]];
    }
    return [];
  }
}

// Later, when instantiating:
const groupIds = ["group-1", "group-2"];

// This SILENTLY causes issues — groupIds is mutable
const filtered = collection.map(PhotoVisibilityFilter, userId, groupIds);

Impact

  • Manifests as intermittent, non-deterministic data inconsistencies in the reactive graph
  • Extremely difficult to debug — no error message points to mutability as the cause
  • Discovered only through trial and error after ruling out other possible causes

Current workaround

Freezing all array/object parameters before passing them to mappers:

const groupIds = Object.freeze(["group-1", "group-2"]);
const filtered = collection.map(PhotoVisibilityFilter, userId, groupIds);

And marking the types as readonly to prevent accidental mutation:

class PhotoVisibilityFilter implements Mapper<string, Photo, string, Photo> {
  constructor(
    private userId: string,
    private groupIds: readonly string[]
  ) {}
  // ...
}

Expected behavior

One of:

  1. Auto-freeze parameters internally when they are captured by the reactive graph
  2. Throw a clear error at construction time if mutable objects are passed as mapper parameters (e.g., TypeError: Mapper parameters must be frozen — call Object.freeze() on arrays/objects before passing them)
  3. Document the requirement prominently in the Mapper API docs with examples

Option 1 or 2 would be strongly preferred over documentation alone, since the failure mode is completely silent.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions