Schema: a public way to derive a canonical codec, as `toCodecJson` and `toCodecStringTree` do
I maintainer di solito rispondono entro 1 giorno
@effect-bot ci sta già lavorando.
Dal 7/11/2026.
Valutazione
Questa issue non è ancora stata valutata.
Descrizione
What is the problem this feature would solve?
This is one focused use case split out of #8690, as requested there. It is related to ask 7 ("A public structural AST map/rebuild"), but it covers more.
We write a Firestore integration for Effect. Firestore stores a fixed set of values: null, booleans, numbers, strings, timestamps, bytes, geo points, document references, arrays and maps. That makes it a canonical format in the same way JSON is one. So we derive toCodecFirestore(schema) the way Effect derives toCodecJson(schema):
- A declaration is looked up by a
toCodecFirestoreannotation, thentoCodecJson, thentoCodec, the same fallbacktoCodecStringTreeuses. Date,DateTimeUtcandUint8Arraymap to Firestore's own timestamp and bytes types instead of strings.Unknown,AnyandObjectKeywordaccept only values Firestore can store.- Keywords, objects, unions, arrays and suspends are handled as
toCodecJsonhandles them.
The step function is ours, but the machinery around it is only available internally. toCodecFirestore is a copy of the internal makeCodecDerivation in internal/schema/toCodec.ts plus our own step function. Building it needs these exports, all of which exist at runtime but are @internal and stripped from the .d.ts:
| Internal API | What we use it for |
|---|---|
SchemaAST.applyToSelfOrLastLinkEncodingIdempotent |
Run the step on a node or on its last link's target, memoized |
SchemaAST.replaceEncoding |
Give a declaration or keyword its canonical link |
SchemaAST.mapLink |
Recurse into the target of a declaration's toCodec* link |
SchemaAST.replaceContextLastLink |
Move the context, minus the constructor default, onto the last link |
SchemaAST.parameterFromString |
Read object keys back from strings |
ast.recur(f, onKey?) |
Recurse into Objects, Arrays, Union and Suspend |
ast.toCodecJson() / ast.toCodecStringTree() on keyword nodes |
Reuse Effect's canonical form for undefined, literals, bigint and symbols |
Today we type these by hand behind a cast and pin effect to an exact version. This is fragile:
- A patch release can change or remove any of them without notice. That is fair, since they are internal.
- Our copy drifts from Effect's.
makeCodecDerivationnow derives each body once per context owner (getContextOwner+replaceContext) so that shared schema references stay shared. Our copy predates that change and doesn't get it. - Every library that wants its own format (a database's value model, a wire format, a config tree) has to reimplement the same few dozen lines against internals.
What is the feature you are proposing to solve the problem?
Make the derivation machinery public, so that a library supplies only the step function and Effect owns the rest. Roughly:
// SchemaAST
export function makeCodecDerivation(
step: (ast: AST, recur: (ast: AST) => AST) => AST,
options?: { readonly stopAt?: (link: Link) => boolean }
): (ast: AST) => AST
The step function also needs a few public building blocks:
replaceEncoding(ast, encoding)andmapLink(link, f)parameterFromString- A typed
ast.recur(f, onKey?), or aSchemaAST.recur(ast, f, onKey?)function. This overlaps with ask 7 of #8690. - Public access to the canonical JSON and StringTree forms of a keyword node, for example
SchemaAST.keywordToCodecJson(ast)andSchemaAST.keywordToCodecStringTree(ast), or simply publictoCodecJson()/toCodecStringTree()methods
With these, toCodecFirestore comes down to:
import { Schema, SchemaAST } from "effect"
const toCodecFirestoreAST = SchemaAST.makeCodecDerivation((ast, recur) => {
switch (ast._tag) {
case "Declaration": /* look up toCodecFirestore ?? toCodecJson ?? toCodec, mapLink(link, recur) */
case "Unknown":
case "Any":
case "ObjectKeyword":
return SchemaAST.replaceEncoding(ast, [unknownToFirestore])
case "Objects":
return SchemaAST.recur(ast, recur, SchemaAST.parameterFromString)
// …
}
return ast
})
export const toCodecFirestore = <S extends Schema.Constraint>(schema: S) =>
Schema.make(toCodecFirestoreAST(schema.ast), { schema })
Effect's own toCodecJson, toCodecStringTree and toCodecGoJson would be built on the same public API, so the API gets tested by Effect itself and keeps up with fixes such as the context-owner sharing.
A smaller first step would also help us: publishing the individual helpers listed above, even without makeCodecDerivation.
What alternatives have you considered?
- Use
toCodecJsonand convert JSON to Firestore values afterwards. This loses Firestore's own types. ADatebecomes a string instead of a timestamp, and bytes become base64. Reading back can't tell a timestamp-as-string from a real string. - Build the codec through the public node constructors. This needs a hand-written rebuild of every node kind. As #8690 ask 7 notes, it breaks whenever a constructor gains a field (
encodingCheckswas added during the RCs). It also still can't reachparameterFromStringor the keywords' canonical forms. - Keep the internal helpers behind a cast with
effectpinned exactly (what we do now). It works, but consumers can't take Effect patch releases without us re-checking the internals first.
- Lingua principale
- TypeScript
- Stelle
- 16.7k
- Fork
- 808
- Merge medio
- 12h 17m
- PR unite (30g)
- 805
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di Effect-TS/effect
-
From Discord: Return Stream.empty for HTTP requests with an empty bodyForse già presa @pujitha24 l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 30/100
I maintainer di solito rispondono entro 1 giorno
-
sql-pg: scoped connection shutdown retains a socket when the peer withholds FINForse già presa @effect-bot l’ha presa oggi. Aperta
Effect-TS/effect#10458 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
McpServer.layerHttp: optional legacy GET SSE for idle list-change notificationsForse già presa @effect-bot l’ha presa 1 giorno fa. Aperta
Effect-TS/effect#10436 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
SqlClient: a fiber forked inside a transaction keeps using its connection after the transaction endsForse già presa @effect-bot l’ha presa 1 giorno fa. Apertabug
Effect-TS/effect#10431 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
CLI completions stop offering repeatable flags after their first useForse già presa @effect-bot l’ha presa 1 giorno fa. Aperta
Effect-TS/effect#10427 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di Effect-TS/effect
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
backnotprop/plannotator#2914 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
microsoft/vscode-containers#655 ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
dsh-tauri-desk/deepseek-harness-desktop#1477 · 1 commento ·