Skip to content

Commit df1cad3

Browse files
committed
fix(core): drop unique-symbol brand on LocalsKey to fix dual-package builds
LocalsKey<T> was branded with a module-level `declare const __local: unique symbol`. tshy emits separate .d.ts files for the ESM and CJS outputs, and each gets its own `declare const __local: unique symbol` — TypeScript treats every such declaration as a nominally distinct type. Under certain pnpm hoisting layouts a single TypeScript compilation can resolve LocalsKey from both the ESM source path and the CJS dist path within the same call site. With unique-symbol brands the two variants are structurally incompatible — TS rejects passing one to a function that expects the other, with a misleading 'Property [__local] is missing' error. Replace the symbol brand with an optional phantom value-type field. T is still carried at the type level, the runtime shape is unchanged, and the ESM and CJS .d.ts outputs are now identical.
1 parent 15b7cde commit df1cad3

3 files changed

Lines changed: 25 additions & 8 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/core": patch
3+
---
4+
5+
Fix `LocalsKey<T>` type incompatibility across dual-package builds. The phantom value-type brand no longer uses a module-level `unique symbol`, so a single TypeScript compilation that resolves the type from both the ESM and CJS outputs (which can happen under certain pnpm hoisting layouts) no longer sees two structurally-incompatible variants of the same type.

packages/core/src/v3/locals/manager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export class NoopLocalsManager implements LocalsManager {
55
return {
66
__type: Symbol(),
77
id,
8-
} as unknown as LocalsKey<T>;
8+
};
99
}
1010

1111
getLocal<T>(key: LocalsKey<T>): T | undefined {
@@ -23,7 +23,7 @@ export class StandardLocalsManager implements LocalsManager {
2323
return {
2424
__type: key,
2525
id,
26-
} as unknown as LocalsKey<T>;
26+
};
2727
}
2828

2929
getLocal<T>(key: LocalsKey<T>): T | undefined {

packages/core/src/v3/locals/types.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1-
declare const __local: unique symbol;
2-
type BrandLocal<T> = { [__local]: T };
3-
4-
// Create a type-safe store for your locals
5-
export type LocalsKey<T> = BrandLocal<T> & {
1+
/**
2+
* A type-safe key for `locals`. Carries the value type `T` as a phantom
3+
* marker on the optional `__valueType` field so two keys with different
4+
* value types are distinguishable at the type level.
5+
*
6+
* The phantom field is intentionally not anchored to a `unique symbol`:
7+
* dual-package builds (`tshy`) emit separate `.d.ts` files for ESM and
8+
* CJS outputs, and each `unique symbol` declaration in a `.d.ts` is its
9+
* own nominal type. If a single compilation ever resolves `LocalsKey`
10+
* from both the ESM and CJS paths — which happens under certain pnpm
11+
* hoisting layouts — `unique symbol` brands produce structurally
12+
* incompatible variants of the same type. A plain string brand avoids
13+
* the hazard.
14+
*/
15+
export type LocalsKey<T> = {
616
readonly id: string;
7-
readonly __type: unique symbol;
17+
readonly __type: symbol;
18+
/** Phantom carrier for the value type — never read at runtime. */
19+
readonly __valueType?: T;
820
};
921

1022
export interface LocalsManager {

0 commit comments

Comments
 (0)