Skip to content

Commit 5328b71

Browse files
authored
Merge pull request #31 from MapleTechLabs/feat/export-param-types
fix(types): export ParamEntry and ParamsIn
2 parents ec0a12f + 56a2893 commit 5328b71

5 files changed

Lines changed: 88 additions & 1 deletion

File tree

‎.changeset/export-param-types.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@maple-dev/effect-orm": patch
3+
---
4+
5+
`ParamEntry`, `OptionalParamEntry`, `OptionalParams` and `ParamsIn` are exported from every dialect entry point. A package that exports a query with a `param.*`, or a `defineFn` helper, failed `tsc --declaration` with TS4023 / TS2742: their types name these, and tsc could only reach them through a hashed internal chunk. The package-consumer check now emits declarations for exported queries with plain, optional (`when` / `whenTrue`) and union-named params, joins, CTEs, subqueries, unions and a `defineFn` helper, on TypeScript 7 and 5.

‎docs/reference.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,13 @@ decoded type of a column), `InferEncoded` (its wire type), `ColumnDefs`, `Nullab
186186
over a query's output takes `CHQuery<…, Output> & NeedsSelect<Output>` so it can pass the query on
187187
to `compile`.
188188

189+
**Params a query carries** — `ParamEntry<Name, Value>` (one `param.*` a query needs, in its type),
190+
`OptionalParamEntry<Name, Value>` and `OptionalParams<P>` (the same for a param under `when` /
191+
`whenTrue`, or one whose name is a union, which the params object may leave out) and
192+
`ParamsIn<X>` (the params an expression or argument list carries). You rarely write them, but
193+
`tsc --declaration` names them when a package exports a query with a param or a `defineFn` helper,
194+
so they are public.
195+
189196
**Everything else** — `Table` (what `from` and the write builders accept; every `table` value is
190197
one), `Expr`, `ColumnRef`, `Condition`, `Comparable` (what a value of a type may be compared
191198
against), `MapValueOf`, `Subquery`, `ParamMarker`, `ParamKind`, `CHQuery`, `CHUnionQuery`,

‎scripts/check-package.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,23 @@ const program = Effect.gen(function* () {
9090
yield* runCommand(path.join(temporary, "node_modules/.bin/tsc"), ["-p", ts5Config], temporary)
9191
yield* runCommand("node", [path.join(temporary, "node_modules/typescript-5/bin/tsc"), "-p", ts5Config], temporary)
9292
yield* runCommand("node", ["consumer-ts5.mts"], temporary)
93+
// Exported queries and helpers must survive declaration emit, which is what a
94+
// package that shares its query definitions runs.
95+
yield* fs.copyFile(
96+
path.join(root, "tests/package-consumer-declarations.mts"),
97+
path.join(temporary, "consumer-declarations.mts"),
98+
)
99+
const declarationConfig = path.join(temporary, "tsconfig.declarations.json")
100+
yield* fs.writeFileString(
101+
declarationConfig,
102+
yield* Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown))({
103+
extends: "./tsconfig.json",
104+
compilerOptions: { noEmit: false, declaration: true, emitDeclarationOnly: true, outDir: "declarations" },
105+
include: ["consumer-declarations.mts"],
106+
}),
107+
)
108+
yield* runCommand(path.join(temporary, "node_modules/.bin/tsc"), ["-p", declarationConfig], temporary)
109+
yield* runCommand("node", [path.join(temporary, "node_modules/typescript-5/bin/tsc"), "-p", declarationConfig], temporary)
93110
// The script runs on Bun; the published library must also work in Node.
94111
yield* runCommand("node", ["consumer.mts"], temporary)
95112
// And from CommonJS, through `require(esm)`: every entry, from the real tarball.

‎src/core.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,16 @@ export {
9595
fromQuery,
9696
fromUnion,
9797
} from "./ch/query"
98-
export { type ParamsSatisfied } from "./ch/expr"
98+
// The param types a consumer's exported query or `defineFn` helper names in its
99+
// declarations. Without them public, `tsc --declaration` fails with TS4023 /
100+
// TS2742 pointing into a hashed internal chunk.
101+
export {
102+
type OptionalParamEntry,
103+
type OptionalParams,
104+
type ParamEntry,
105+
type ParamsIn,
106+
type ParamsSatisfied,
107+
} from "./ch/expr"
99108

100109
export {
101110
type CHInsert,
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copied into an isolated directory by check-package.ts and compiled with
2+
// `declaration: true`. A library that defines its tables and queries in one
3+
// package and runs them from another exports them, and tsc must then be able to
4+
// name every type they carry through the public entry points. Each export below
5+
// once failed with TS4023 or TS2742, pointing into a hashed internal chunk.
6+
import type { DateTime } from "effect"
7+
import * as CH from "@maple-dev/effect-orm/clickhouse"
8+
import * as PG from "@maple-dev/effect-orm/postgres"
9+
10+
export const Events = CH.table("events", { external: true, columns: { appId: CH.uint32, name: CH.string } })
11+
12+
// A query with a param: its type carries `ParamEntry`.
13+
export const eventsByApp = CH.from(Events)
14+
.select(($) => ({ name: $.name }))
15+
.where(($) => [$.appId.eq(CH.param.int("appId"))])
16+
17+
// A custom function: its type carries `ParamsIn`.
18+
export const toStartOfDay = CH.defineFn<[CH.Expr<DateTime.Utc>], DateTime.Utc>("toStartOfDay", CH.dateTime)
19+
20+
export const Accounts = PG.table("accounts", { columns: { id: PG.int4, org: PG.text }, primaryKey: ["id"] })
21+
export const accountsByOrg = PG.from(Accounts)
22+
.select(($) => ({ id: $.id }))
23+
.where(($) => [$.org.eq(PG.param.string("org"))])
24+
25+
// An optional filter (`when`, `whenTrue`) and a param named by a union carry
26+
// `OptionalParamEntry` / `OptionalParams`.
27+
export const eventsMaybeByName = (name: string | undefined) =>
28+
CH.from(Events)
29+
.select(($) => ({ appId: $.appId }))
30+
.where(($) => [CH.when(name, () => $.name.eq(CH.param.string("name")))])
31+
export const eventsByAppOptional = CH.from(Events)
32+
.select(($) => ({ name: $.name }))
33+
.where(($) => [CH.whenTrue(true, () => $.appId.eq(CH.param.int("appId")))])
34+
35+
export const byUnionNamedParam = (byApp: boolean) =>
36+
CH.from(Events)
37+
.select(($) => ({ name: $.name }))
38+
.where(($) => [$.appId.eq(CH.param.int(byApp ? "appId" : "fallbackAppId"))])
39+
40+
// Joins, CTEs, subqueries and unions over parameterized queries.
41+
export const Sessions = CH.table("sessions", { external: true, columns: { appId: CH.uint32, id: CH.string } })
42+
export const joined = CH.from(Events)
43+
.innerJoin(Sessions, "s", (e, s) => e.appId.eq(s.appId))
44+
.select(($) => ({ name: $.name }))
45+
.where(($) => [$.appId.eq(CH.param.int("appId"))])
46+
export const Recent = CH.table("recent", { external: true, columns: { name: CH.string } })
47+
export const withRecent = CH.from(Recent).withCTE("recent", eventsByApp).select(($) => ({ name: $.name }))
48+
export const fromSub = CH.fromQuery(eventsByApp, "sub").select(($) => ({ name: $.name }))
49+
export const both = CH.unionAll(eventsByApp, CH.from(Events).select(($) => ({ name: $.name })))

0 commit comments

Comments
 (0)