Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/schema-facade-identifier-structfacade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"effect-app": minor
---

Schema/Class facades: add `StructFacade` and carry `identifier` on the class facades.

- Add `StructFacade<Self, Encoded, MakeIn, DecodingServices, EncodingServices, Fields>` = `Omit<S.Struct<Fields>, "Type" | "Encoded" | "~type.make.in" | "DecodingServices" | "EncodingServices"> & { pinned type-level members }`. A top-level `const X = S.Struct(...)` / `S.TaggedStruct(...)` model faceted by the `.d.ts`-emit compiler is retyped to it: still a real `S.Struct<Fields>` (so `Workflow.AnyStructSchema`, the `Struct<Fields & Context>` reconstruction, `Union`, `.fields.x` keep working) while `Type`/`Encoded`/make/services resolve to the named namespace interfaces.
- Add `readonly identifier: string` to `OpaqueClassFacade` and `OpaqueErrorFacadeClass` (an `S.Class` static lost from the bare emitted interface, which the compiler otherwise had to hand-emit per model). `OpaqueFacade` is intentionally left untouched — `S.Opaque` and requests build on `S.Bottom`, not `S.Class`, so they have no `identifier`.
37 changes: 37 additions & 0 deletions packages/effect-app/src/Schema/Class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,8 @@ export interface OpaqueFacade<
{
new(_: never): Brand
readonly copy: ReturnType<typeof copyOrigin<new(_: MakeIn) => Self>>
// NOTE: no `identifier` here — `S.Opaque` (and requests) build on `S.Bottom`, not `S.Class`,
// so they have no `identifier`. It lives only on the class facades below.
// NOTE: `fields` / `mapFields` are intentionally NOT redeclared here. They are
// carried (precise) from the underlying schema via `OpaqueFacadeStatics`. A wide
// `mapFields(f: (fields: S.Struct.Fields) => To)` override would win overload
Expand Down Expand Up @@ -519,6 +521,7 @@ export interface OpaqueClassFacade<
{
new(...args: OpaqueFacadeConstructorArgs<MakeIn>): Brand
readonly copy: ReturnType<typeof copyOrigin<new(_: MakeIn) => Self>>
readonly identifier: string
}

export function OpaqueFacade<
Expand Down Expand Up @@ -600,6 +603,7 @@ export interface OpaqueErrorFacadeClass<
// makes `yield* new X()` / `Effect.fail` / `instanceof` work without losing data.
new(...args: OpaqueFacadeConstructorArgs<MakeIn>): Cause.YieldableError & Brand
readonly copy: ReturnType<typeof copyOrigin<new(_: MakeIn) => Self>>
readonly identifier: string
}

export function OpaqueErrorFacadeClass<
Expand Down Expand Up @@ -627,3 +631,36 @@ export function OpaqueErrorFacadeClass<
throw new TypeError("OpaqueErrorFacadeClass requires a class schema")
}
}

/**
* Workflow-compatible struct facade.
*
* A top-level `const X = S.Struct(...)` / `S.TaggedStruct(...)` schema model whose `.d.ts`
* is produced by the schema-aware declaration emitter is retyped from the giant inline
* `S.Struct<{ ...fields... }>` to this facade.
*
* Unlike {@link OpaqueFacade}, `StructFacade` **extends `S.Struct<Fields>`**, so the value is
* still a real struct schema: it satisfies `Workflow.AnyStructSchema` and the
* `Struct<Fields & Context>` reconstruction the workflow machinery performs, and `Union` /
* `.fields.x` keep working. At the same time the type-level members (`Type` / `Encoded` /
* make-input / services) are pinned to the named interfaces the emitter generates in
* `namespace X`, so consumers reference those by name instead of re-deriving them from
* `Fields` on every use. `Fields` is the model's own generated `X.Fields` interface — a single
* materialization of the (expensive) field shape that every reference site points at.
*/
export type StructFacade<
Self,
Encoded,
MakeIn,
DecodingServices,
EncodingServices,
Fields extends S.Struct.Fields
> =
& Omit<S.Struct<Fields>, "Type" | "Encoded" | "~type.make.in" | "DecodingServices" | "EncodingServices">
& {
readonly "Type": Self
readonly "Encoded": Encoded
readonly "~type.make.in": MakeIn
readonly "DecodingServices": DecodingServices
readonly "EncodingServices": EncodingServices
}
Loading