Omid Sayfun
Omid SayfunComputer Geek
Home
Notebook
Journey

Online

Github
Linkedin
Hevy
Plyatomic
Notebook
The trap of making everything dynamic
March 01, 2024
A try at type-safe groupBy function in TypeScript
April 10, 2025
Email special headers
November 20, 2024
Adding prettier to eslint
April 10, 2025
Storing Vector in Postgres with Drizzle ORM
March 21, 2024
Upgrading my blog to Next 15
April 05, 2025
Canvas macOS issue
February 20, 2024
tsx doesn’t support decorators
March 26, 2025
Validating NestJS env vars with zod
February 06, 2025
Extending Window: types vs interfaces
March 21, 2025
Loading env file into Node process
February 06, 2025
Add update date field to Postgres
February 27, 2024
Using node API for delay
February 06, 2025
React Component Lifecycle
November 28, 2024
How CQRS is different than Event Sourcing
August 18, 2024
RabbitMQ exchange vs queue
August 14, 2024
PgVector similarity search distance functions
August 13, 2024
PgVector indexing options for vector similarity search
July 31, 2024
Using puppeteer executable for GSTS
June 08, 2024
Why EQ is Your Next Career Upgrade
May 13, 2024
Counting GPT tokens
June 30, 2024
Logging route handler responses in Next.js 14
June 19, 2024
Redirect www subdomain with Cloudflare
June 17, 2024
Logging requests in Express app
June 16, 2024
Move Docker volume to bind mount
June 12, 2024
Next.js Hydration Window Issue
May 29, 2024
Using Git rebase without creating chaos in your repo
May 16, 2024
Implementing RPC Calls with RabbitMQ in TypeScript
March 16, 2024
Optimize webpage load with special tags
March 15, 2024
What the hell is Open Graph?
March 13, 2024
My go-to Next.js ESlint config
March 10, 2024
List of useful Chrome args
March 10, 2024
Combining RxJS observables - Part 1
February 20, 2024

The trap of making everything dynamic

March 01, 2024 · Updated on February 20, 2026

I often try to make everything as dynamic as possible, until TypeScript reminds me that’s not always a good idea.

One of the best things about TypeScript is that it pushes you to make things known at compile time, not at runtime. In practice, that means types work best when they can be derived from static, fixed values.

A common place this shows up is computed property names in types and interfaces: the key has to be something TypeScript can treat as a literal (or a unique symbol).

Computed keys must be literals

Wrong way

let myKey = "id"; // TypeScript sees this as type 'string', not specifically "id"
 
type User = {
	[myKey]: number; // ❌ Error: myKey is a general string, not a literal.
};

Because myKey is let, its type is widened to string. TypeScript can’t use a general string as a computed key in a type literal.

Right way

To fix it, you need myKey to be a value that TypeScript can treat as fixed.

You can make it a const:

const myKey = "id"; // Because it's a const, the type is "id", not 'string'
 
type User

Continue Reading

=
{
[myKey]: number; // ✅ Works!
};

Or you can use a unique symbol:

const MyIdentifier = Symbol("id"); // This is a unique symbol
 
type Data = {
	[MyIdentifier]: string; // ✅ Works!
};

Where I hit this in a plugin system

I was adding multiple plugins to a system, and each plugin had its own unique identifier key. I started with a structure like this:

const Plugins = {
	Auth: {
		internalKey: "user_token",
		version: 1,
	},
	Analytics: {
		internalKey: "session_id",
		version: 2,
	},
};

The goal was to create a type that uses each plugin’s internalKey as the property name.

Something like this:

type PluginState = {
	[Plugins.Auth.internalKey]: string;
};

That’s when TypeScript gave me this error:

A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.

Even though Plugins is a const, without as const the nested internalKey values get widened to string. From the type system’s perspective, Plugins.Auth.internalKey is just string, not the literal type "user_token".

The fix: as const

const Plugins = {
	Auth: {
		internalKey: "user_token",
		version: 1,
	},
	Analytics: {
		internalKey: "session_id",
		version: 2,
	},
} as const; // <--- The magic wand
 
// Now this is perfectly valid!
type PluginState = {
	[Plugins.Auth.internalKey]: string;
};
 
// Resulting type is effectively: { "user_token": string }

Final result: deriving keys from a config object

In the end, I wanted to put configurations for multiple items in one config object, then derive a centralized type from the id fields.

const AppConfig = {
	featureA: { id: "feat_a", active: true },
	featureB: { id: "feat_b", active: false },
} as const;
 
// Create a union of all 'id' values: "feat_a" | "feat_b"
type AllFeatureIds = (typeof AppConfig)[keyof typeof AppConfig]["id"];
 
// Use that union to drive an object type
type FeaturePermissions = {
	[K in AllFeatureIds]: boolean;
};
 
/*
Resulting Type:
type FeaturePermissions = {
    feat_a: boolean;
    feat_b: boolean;
}
*/

The main takeaway for me: when you want TypeScript to “lift” values into types (especially for computed keys), you need to keep those values literal—const + as const is often the difference.

  • 04-11-2025

    A try at type-safe groupBy function in TypeScript

  • 04-10-2025

    Email special headers

  • 04-10-2025

    Adding prettier to eslint

  • 04-09-2025

    Storing Vector in Postgres with Drizzle ORM

  • 04-09-2025

    Upgrading my blog to Next 15