Skip to content

Instantly share code, notes, and snippets.

@kevinmichaelchen
Last active December 8, 2025 20:03
Show Gist options
  • Select an option

  • Save kevinmichaelchen/0350b8344a05065803b8f2044adefb0f to your computer and use it in GitHub Desktop.

Select an option

Save kevinmichaelchen/0350b8344a05065803b8f2044adefb0f to your computer and use it in GitHub Desktop.
tsyringe interface noncompliance runtime demo

tsyringe isn't type-safe

  • Token 'Greeter' is supposed to match Greeter (needs greet).
  • We register WrongGreeter (only has sayHello).
  • TypeScript passes (npx tsc --noEmit) because the string token is untyped.
  • Runtime blows up: this.greeter.greet is not a function.

Run:

npm install
npx tsc --noEmit
npm run start
import { Context, Effect, Layer } from 'effect';
// Modern class-based Context.Tag pattern.
// The class IS the tag — no separate interface needed.
class Greeter extends Context.Tag('Greeter')<
Greeter,
{ readonly greet: (name: string) => string }
>() {
static Live = Layer.succeed(this, {
greet: (name: string) => `Hello, ${name} (from Effect)`,
});
}
// This would be a compile-time error:
// static Live = Layer.succeed(this, { sayHello: ... });
const program = Effect.gen(function* () {
const greeter = yield* Greeter;
return greeter.greet('tsyringe + Effect');
});
Effect.runPromise(Effect.provide(program, Greeter.Live)).then(console.log);
import 'reflect-metadata';
import { container, inject, injectable } from 'tsyringe';
// Contract we want the container to satisfy.
interface Greeter {
greet(name: string): string;
}
@injectable()
class WrongGreeter {
// Does not implement Greeter.greet; wrong method name/signature.
sayHello(name: string): string {
return `Hello, ${name}. (from the wrong greeter)`;
}
}
@injectable()
class App {
constructor(@inject('Greeter') private readonly greeter: Greeter) {}
run(): void {
// At runtime this will explode, because the container hands us WrongGreeter.
console.log(this.greeter.greet('tsyringe'));
}
}
// Using a string token without generics means register() defaults to T = any,
// so TypeScript cannot tell that WrongGreeter does not satisfy Greeter.
container.register('Greeter', { useClass: WrongGreeter });
try {
container.resolve(App).run();
} catch (error) {
console.error('Runtime failure because the implementation does not satisfy Greeter:', error);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment