Skip to content

feat: literal to brand parser - #8263

Open
devanshj wants to merge 3 commits into
Effect-TS:mainfrom
devanshj:literal-to-brand-parser
Open

devanshj wants to merge 3 commits into
Effect-TS:mainfrom
devanshj:literal-to-brand-parser

Conversation

@devanshj

Copy link
Copy Markdown

Type

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update

Description

I think we can agree the whole point of static types is to not have runtime exceptions. But sometimes it's easy to introduce them...

import { Brand } from "effect"
import { isPrime } from "mathjs"

type PrimeNumber = number & Brand.Brand<"PrimeNumber">
const PrimeNumber = Brand.make<PrimeNumber>((n) => isPrime(n))

PrimeNumber(10) // no compile error but throws in runtime

Now obviously I understand why this exists and it can't be avoided, it's because you can't check if a number literal in the program is prime or not at compile time... Sure, but how about an integer...

import { Brand } from "effect"

type Int = number & Brand.Brand<"Int">
const Int = Brand.make<Int>((n) => Number.isInteger(n))

Int(3.14) // no compile error but throws in runtime

In this case we can produce a compilation error like this...

import { Brand } from "effect"

type Int = number & Brand.Brand<"Int">
const intFromLiteral =
  <T extends number>(literal: `${T}` extends `${bigint}` ? T : { IntParserError: "Expected an integer" }) =>
    literal as Int

intFromLiteral(3.14)
//             ~~~~ Argument of type '3.14' is not assignable to parameter of type '{ IntParserError: "Expected an integer" }'

That's nice.

So that's what this PR brings in Effect. The Brand type can now take a "parser" which is a higher kinded type that takes a value and returns a result and there's now a literal function on the brand constructor that uses this parser...

import { Brand } from "effect"

type Int = number & Brand.Brand<"Int", IntParser>
interface IntParser {
  value: unknown
  result:
    this["value"] extends number
      ? `${this["value"]}` extends `${bigint}`
          ? this["value"]
          : { IntParserError: "Expected an integer" }
      : number
}
const Int = Brand.make<Int>((n) => Number.isInteger(n))

Int.literal(3.14)
//          ~~~~ Argument of type 'number' is not assignable to parameter of type 'number & { IntParserError: "Expected an Integer"; }'

Int(3.14) // no compile error here throws as usual

Perhaps we should have some built-in brands/schema-filters (eg Int, NonZero, etc) so that the users don't have to write parsers themselves.

Let me know if you guys think this is a good idea. No problems if we don't want to merge this, I just wanted to throw this idea out there.

Thanks for reading!

Further reading: microsoft/TypeScript#52088

@changeset-bot

changeset-bot Bot commented Sep 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0c2dfd1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 30 packages
Name Type
effect Minor
@effect/opentelemetry Minor
@effect/vitest Minor
@effect/ai-anthropic Minor
@effect/ai-openai-compat Minor
@effect/ai-openai Minor
@effect/ai-openrouter Minor
@effect/atom-react Minor
@effect/atom-solid Minor
@effect/atom-vue Minor
@effect/platform-browser Minor
@effect/platform-bun Minor
@effect/platform-deno Minor
@effect/platform-node-shared Minor
@effect/platform-node Minor
@effect/sql-clickhouse Minor
@effect/sql-d1 Minor
@effect/sql-libsql Minor
@effect/sql-mssql Minor
@effect/sql-mysql2 Minor
@effect/sql-pg Minor
@effect/sql-pglite Minor
@effect/sql-sqlite-bun Minor
@effect/sql-sqlite-do Minor
@effect/sql-sqlite-node Minor
@effect/sql-sqlite-react-native Minor
@effect/sql-sqlite-wasm Minor
@effect/docgen Minor
@effect/doctest Minor
@effect/openapi-generator Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devanshj devanshj changed the title feat: Brand.literal feat: literal to brand parser Sep 16, 2026
@effect-janitor effect-janitor Bot added enhancement New feature or request 4.0 labels Sep 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
arbitrary-combinators.ts 34.58 KB 34.58 KB 0.00 KB (0.00%)
basic.ts 6.87 KB 6.87 KB 0.00 KB (0.00%)
batching.ts 9.95 KB 9.95 KB 0.00 KB (0.00%)
brand.ts 6.45 KB 6.45 KB +0.01 KB (+0.09%)
cache.ts 10.77 KB 10.77 KB 0.00 KB (0.00%)
config.ts 21.51 KB 21.51 KB 0.00 KB (0.00%)
differ.ts 20.32 KB 20.32 KB 0.00 KB (0.00%)
http-client.ts 21.93 KB 21.93 KB 0.00 KB (0.00%)
http-router.ts 36.92 KB 36.92 KB 0.00 KB (0.00%)
logger.ts 10.88 KB 10.88 KB 0.00 KB (0.00%)
metric.ts 9.02 KB 9.02 KB 0.00 KB (0.00%)
optic.ts 6.70 KB 6.70 KB 0.00 KB (0.00%)
pubsub.ts 15.10 KB 15.10 KB 0.00 KB (0.00%)
queue.ts 11.85 KB 11.85 KB 0.00 KB (0.00%)
schedule.ts 10.96 KB 10.96 KB 0.00 KB (0.00%)
schema-binary.ts 39.51 KB 39.51 KB 0.00 KB (0.00%)
schema-class.ts 20.06 KB 20.06 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 30.93 KB 30.93 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 26.34 KB 26.34 KB 0.00 KB (0.00%)
schema-string-transformation.ts 13.63 KB 13.63 KB 0.00 KB (0.00%)
schema-string.ts 11.12 KB 11.12 KB 0.00 KB (0.00%)
schema-template-literal.ts 15.41 KB 15.41 KB 0.00 KB (0.00%)
schema-toArbitrary.ts 34.10 KB 34.10 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 24.56 KB 24.56 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 19.27 KB 19.27 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 19.38 KB 19.38 KB 0.00 KB (0.00%)
schema-toFormatter.ts 19.49 KB 19.49 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 23.77 KB 23.77 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 19.54 KB 19.54 KB 0.00 KB (0.00%)
schema.ts 19.27 KB 19.27 KB 0.00 KB (0.00%)
stm.ts 12.80 KB 12.80 KB 0.00 KB (0.00%)
stream.ts 9.83 KB 9.83 KB 0.00 KB (0.00%)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant