From df1cad3d4ffd9b2fd4e89383c07e90d149da38b2 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Fri, 15 May 2026 07:43:59 +0100 Subject: [PATCH] fix(core): drop unique-symbol brand on LocalsKey to fix dual-package builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LocalsKey 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. --- .changeset/locals-key-dual-package-fix.md | 5 +++++ packages/core/src/v3/locals/manager.ts | 4 ++-- packages/core/src/v3/locals/types.ts | 24 +++++++++++++++++------ 3 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 .changeset/locals-key-dual-package-fix.md diff --git a/.changeset/locals-key-dual-package-fix.md b/.changeset/locals-key-dual-package-fix.md new file mode 100644 index 00000000000..38d42e19dfb --- /dev/null +++ b/.changeset/locals-key-dual-package-fix.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/core": patch +--- + +Fix `LocalsKey` 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. diff --git a/packages/core/src/v3/locals/manager.ts b/packages/core/src/v3/locals/manager.ts index 6984d9f4146..3cd0f80d84a 100644 --- a/packages/core/src/v3/locals/manager.ts +++ b/packages/core/src/v3/locals/manager.ts @@ -5,7 +5,7 @@ export class NoopLocalsManager implements LocalsManager { return { __type: Symbol(), id, - } as unknown as LocalsKey; + }; } getLocal(key: LocalsKey): T | undefined { @@ -23,7 +23,7 @@ export class StandardLocalsManager implements LocalsManager { return { __type: key, id, - } as unknown as LocalsKey; + }; } getLocal(key: LocalsKey): T | undefined { diff --git a/packages/core/src/v3/locals/types.ts b/packages/core/src/v3/locals/types.ts index aab683df091..84abc4c70f5 100644 --- a/packages/core/src/v3/locals/types.ts +++ b/packages/core/src/v3/locals/types.ts @@ -1,10 +1,22 @@ -declare const __local: unique symbol; -type BrandLocal = { [__local]: T }; - -// Create a type-safe store for your locals -export type LocalsKey = BrandLocal & { +/** + * A type-safe key for `locals`. Carries the value type `T` as a phantom + * marker on the optional `__valueType` field so two keys with different + * value types are distinguishable at the type level. + * + * The phantom field is intentionally not anchored to a `unique symbol`: + * dual-package builds (`tshy`) emit separate `.d.ts` files for ESM and + * CJS outputs, and each `unique symbol` declaration in a `.d.ts` is its + * own nominal type. If a single compilation ever resolves `LocalsKey` + * from both the ESM and CJS paths — which happens under certain pnpm + * hoisting layouts — `unique symbol` brands produce structurally + * incompatible variants of the same type. A plain string brand avoids + * the hazard. + */ +export type LocalsKey = { readonly id: string; - readonly __type: unique symbol; + readonly __type: symbol; + /** Phantom carrier for the value type — never read at runtime. */ + readonly __valueType?: T; }; export interface LocalsManager {