# @tsimpl/runtime

Traits that behave like plain JavaScript.

Use a trait once, implement it anywhere, and dispatch without ceremony. When you want runtime tagging for plain objects, plug in a Standard Schema (Zod, Joi, or your own) and tsimpl takes it from there.

## POP example: Zod + trait dispatch

```ts
import { z } from "zod";
import { struct, trait, method, impl } from "@tsimpl/runtime";
import { Self } from "@tsimpl/core";

type User = { id: string; handle: string };

const UserSchema = z.object({
  id: z.string(),
  handle: z.string()
});

const UserStruct = struct(UserSchema);

const Display = trait({
  display: method<Self, string>()
});

impl(Display).for(UserStruct, {
  display: method((self) => `@${self.handle} (${self.id})`)
});

const ada = UserStruct.from({ id: "u1", handle: "ada" });
console.log(Display.display(ada));
```

Why it pops:

- That value is a plain object created by Zod, not a class instance.
- The trait impl lives alongside your domain type, not inside it.
- Dispatch reads like normal JS: `Display.display(ada)`.

## The mental model

1. `trait(...)` defines behavior.
2. `struct(...)` tags plain values using Standard Schema.
3. `impl(...)` wires the two together.

### trait

```ts
const Display = trait({
  display: method<Self, string>()
});
```

Trait tokens expose static dispatch (`Display.display(value)`) and implement Standard Schema via the `~standard` key for interoperability.

### struct

```ts
const UserStruct = struct(UserSchema);
```

Struct tokens validate and tag values with `from(...)`, and they can host associated statics:

```ts
import { StructSelf, associated, method, impl } from "@tsimpl/runtime";

type Self = StructSelf<typeof UserStruct>;

impl(UserStruct, {
  new: associated((id: string, handle: string) => ({ id, handle })),
  shout: method((self: Self) => self.handle.toUpperCase())
});

const tagged = UserStruct.new("u2", "grace");
console.log(UserStruct.shout(tagged));
```

### impl

```ts
impl(Display).for(UserStruct, {
  display: method((self) => `@${self.handle} (${self.id})`)
});
```

Register the implementation against a struct token, a class constructor, or a plain object.
Trait implementations can also use `associated(...)` to define static-style helpers (the `self` argument is ignored).

## Dispatch styles

- Static: `Display.display(value)`.
- Class/prototype: `value.as(Display).display()` (from `impl(Display).for(MyClass, ...)`).
- Explicit: `traitView(value, Display)` or `traitProxy(value, Display)`.
- Symbol slot: `Reflect.get(value, Display)` after `attachTraitSlot(...)`.

## Colliding method names

Two traits can safely share method names because implementations are keyed per trait token. When names collide, prefer explicit dispatch (`Display.display(value)` or `value.as(Display).display()`) instead of attaching methods directly to the instance.
