Schema: a public way to derive a canonical codec, as `toCodecJson` and `toCodecStringTree` do
Maintainer thường phản hồi trong vòng 1 ngày
@effect-bot đang làm issue này rồi.
Từ ngày 7/11/2026.
Đánh giá
Issue này chưa được đánh giá.
Mô tả
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.
- Ngôn ngữ chính
- TypeScript
- Star
- 16.7k
- Fork
- 808
- Merge trung bình
- 12 giờ 17 phút
- Pull request đã merge (30 ngày)
- 805
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của Effect-TS/effect
-
From Discord: Return Stream.empty for HTTP requests with an empty bodyCó thể đã có người làm @pujitha24 đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 30/100
Maintainer thường phản hồi trong vòng 1 ngày
-
sql-pg: scoped connection shutdown retains a socket when the peer withholds FINCó thể đã có người làm @effect-bot đã nhận hôm nay. Đang mở
Effect-TS/effect#10458 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
McpServer.layerHttp: optional legacy GET SSE for idle list-change notificationsCó thể đã có người làm @effect-bot đã nhận 1 ngày trước. Đang mở
Effect-TS/effect#10436 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
SqlClient: a fiber forked inside a transaction keeps using its connection after the transaction endsCó thể đã có người làm @effect-bot đã nhận 1 ngày trước. Đang mởbug
Effect-TS/effect#10431 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
CLI completions stop offering repeatable flags after their first useCó thể đã có người làm @effect-bot đã nhận 1 ngày trước. Đang mở
Effect-TS/effect#10427 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của Effect-TS/effect
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
backnotprop/plannotator#2914 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
microsoft/vscode-containers#655 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 77/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
dsh-tauri-desk/deepseek-harness-desktop#1477 · 1 bình luận ·