zenstack-trpc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,284 @@
1
+ # zenstack-trpc
2
+
3
+ Auto-generate fully type-safe tRPC routers from [ZenStack V3](https://zenstack.dev) schemas.
4
+
5
+ ## Features
6
+
7
+ - **Zero codegen** - Router generated at runtime from schema metadata
8
+ - **Full type inference** - Input AND output types from your ZenStack schema
9
+ - **Dynamic result typing** - `include`/`select` options reflected in return types
10
+ - **Zod validation** - Runtime input validation built-in
11
+ - **All CRUD operations** - findMany, findUnique, create, update, delete, and more
12
+ - **Standard tRPC** - Works with all tRPC adapters (HTTP, WebSocket, Next.js, etc.)
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install zenstack-trpc @trpc/server @zenstackhq/orm zod
18
+ # or
19
+ pnpm add zenstack-trpc @trpc/server @zenstackhq/orm zod
20
+ # or
21
+ yarn add zenstack-trpc @trpc/server @zenstackhq/orm zod
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. Define your ZenStack schema
27
+
28
+ ```prisma
29
+ // schema.zmodel
30
+ model User {
31
+ id String @id @default(cuid())
32
+ email String @unique
33
+ name String?
34
+ posts Post[]
35
+ createdAt DateTime @default(now())
36
+ updatedAt DateTime @updatedAt
37
+ }
38
+
39
+ model Post {
40
+ id String @id @default(cuid())
41
+ title String
42
+ content String?
43
+ published Boolean @default(false)
44
+ author User @relation(fields: [authorId], references: [id])
45
+ authorId String
46
+ createdAt DateTime @default(now())
47
+ updatedAt DateTime @updatedAt
48
+ }
49
+ ```
50
+
51
+ ### 2. Generate ZenStack artifacts
52
+
53
+ ```bash
54
+ npx zenstack generate
55
+ ```
56
+
57
+ ### 3. Create the tRPC router
58
+
59
+ ```typescript
60
+ import { ZenStackClient } from "@zenstackhq/orm";
61
+ import { schema, SchemaType } from "./zenstack/schema.js";
62
+ import {
63
+ createTRPC,
64
+ createZenStackRouter,
65
+ type Context,
66
+ type TypedRouterCaller,
67
+ } from "zenstack-trpc";
68
+
69
+ // Create your database client
70
+ const db = new ZenStackClient(schema, {
71
+ dialect: yourDialect, // Kysely dialect (SQLite, PostgreSQL, MySQL, etc.)
72
+ });
73
+
74
+ // Create tRPC instance
75
+ const t = createTRPC<Context>();
76
+
77
+ // Generate the router from your schema
78
+ const appRouter = createZenStackRouter(schema, t);
79
+
80
+ // Export for client usage
81
+ export type AppRouter = typeof appRouter;
82
+ ```
83
+
84
+ ### 4. Use the router
85
+
86
+ ```typescript
87
+ // Create a typed caller
88
+ const caller = appRouter.createCaller({ db }) as TypedRouterCaller<SchemaType>;
89
+
90
+ // All operations are fully typed!
91
+ const users = await caller.user.findMany();
92
+ // ^? { id: string, email: string, name: string | null, ... }[]
93
+
94
+ // Include relations - return type automatically includes them
95
+ const usersWithPosts = await caller.user.findMany({
96
+ include: { posts: true }
97
+ });
98
+ // ^? { id: string, ..., posts: Post[] }[]
99
+
100
+ // Select specific fields
101
+ const emails = await caller.user.findMany({
102
+ select: { id: true, email: true }
103
+ });
104
+ // ^? { id: string, email: string }[]
105
+
106
+ // Create with full validation
107
+ const user = await caller.user.create({
108
+ data: {
109
+ email: "alice@example.com",
110
+ name: "Alice",
111
+ },
112
+ });
113
+
114
+ // Update with type-safe where clause
115
+ await caller.user.update({
116
+ where: { id: user.id },
117
+ data: { name: "Alice Smith" },
118
+ });
119
+ ```
120
+
121
+ ## API Reference
122
+
123
+ ### `createTRPC<Context>()`
124
+
125
+ Creates a tRPC instance with the given context type.
126
+
127
+ ```typescript
128
+ import { createTRPC, type Context } from "zenstack-trpc";
129
+
130
+ const t = createTRPC<Context>();
131
+ ```
132
+
133
+ ### `createZenStackRouter(schema, t)`
134
+
135
+ Generates a tRPC router from a ZenStack schema.
136
+
137
+ ```typescript
138
+ import { createZenStackRouter } from "zenstack-trpc";
139
+
140
+ const appRouter = createZenStackRouter(schema, t);
141
+ ```
142
+
143
+ ### `TypedRouterCaller<SchemaType>`
144
+
145
+ Type helper for fully typed caller with dynamic input/output inference.
146
+
147
+ ```typescript
148
+ import type { TypedRouterCaller } from "zenstack-trpc";
149
+ import type { SchemaType } from "./zenstack/schema.js";
150
+
151
+ const caller = appRouter.createCaller({ db }) as TypedRouterCaller<SchemaType>;
152
+ ```
153
+
154
+ ### `TypedModelProcedures<Schema, Model>`
155
+
156
+ Type helper for a single model's procedures.
157
+
158
+ ```typescript
159
+ import type { TypedModelProcedures } from "zenstack-trpc";
160
+
161
+ type UserProcedures = TypedModelProcedures<SchemaType, "User">;
162
+ ```
163
+
164
+ ## Generated Router Structure
165
+
166
+ For each model in your schema, the following procedures are generated:
167
+
168
+ ```
169
+ router
170
+ ├── user
171
+ │ ├── findMany (query)
172
+ │ ├── findUnique (query)
173
+ │ ├── findFirst (query)
174
+ │ ├── create (mutation)
175
+ │ ├── createMany (mutation)
176
+ │ ├── update (mutation)
177
+ │ ├── updateMany (mutation)
178
+ │ ├── upsert (mutation)
179
+ │ ├── delete (mutation)
180
+ │ ├── deleteMany (mutation)
181
+ │ ├── count (query)
182
+ │ ├── aggregate (query)
183
+ │ └── groupBy (query)
184
+ ├── post
185
+ │ └── ... (same operations)
186
+ ```
187
+
188
+ ## Using with tRPC Adapters
189
+
190
+ ### Standalone HTTP Server
191
+
192
+ ```typescript
193
+ import { createHTTPServer } from "@trpc/server/adapters/standalone";
194
+
195
+ const server = createHTTPServer({
196
+ router: appRouter,
197
+ createContext: () => ({ db }),
198
+ });
199
+
200
+ server.listen(3000);
201
+ ```
202
+
203
+ ### Next.js App Router
204
+
205
+ ```typescript
206
+ // app/api/trpc/[trpc]/route.ts
207
+ import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
208
+
209
+ const handler = (req: Request) =>
210
+ fetchRequestHandler({
211
+ endpoint: "/api/trpc",
212
+ req,
213
+ router: appRouter,
214
+ createContext: () => ({ db }),
215
+ });
216
+
217
+ export { handler as GET, handler as POST };
218
+ ```
219
+
220
+ ### Express
221
+
222
+ ```typescript
223
+ import express from "express";
224
+ import { createExpressMiddleware } from "@trpc/server/adapters/express";
225
+
226
+ const app = express();
227
+
228
+ app.use(
229
+ "/trpc",
230
+ createExpressMiddleware({
231
+ router: appRouter,
232
+ createContext: () => ({ db }),
233
+ })
234
+ );
235
+ ```
236
+
237
+ ## Advanced Usage
238
+
239
+ ### Custom Context
240
+
241
+ Extend the context with authentication or other data:
242
+
243
+ ```typescript
244
+ interface MyContext extends Context {
245
+ db: any;
246
+ userId?: string;
247
+ }
248
+
249
+ const t = createTRPC<MyContext>();
250
+ const appRouter = createZenStackRouter(schema, t);
251
+
252
+ // In your adapter
253
+ createContext: (opts) => ({
254
+ db: getEnhancedDb(opts.req), // ZenStack enhanced client with access control
255
+ userId: getUserFromRequest(opts.req),
256
+ });
257
+ ```
258
+
259
+ ### Zod Schema Access
260
+
261
+ Access the generated Zod schemas for custom validation:
262
+
263
+ ```typescript
264
+ import {
265
+ createModelSchemas,
266
+ createWhereSchema,
267
+ createCreateDataSchema,
268
+ } from "zenstack-trpc";
269
+
270
+ const userSchemas = createModelSchemas(schema, "User");
271
+ const whereSchema = createWhereSchema(schema, "User");
272
+ ```
273
+
274
+ ## Requirements
275
+
276
+ - Node.js >= 18
277
+ - TypeScript >= 5.0
278
+ - ZenStack V3 (`@zenstackhq/orm` >= 3.0.0)
279
+ - tRPC >= 11.0.0
280
+ - Zod >= 3.0.0
281
+
282
+ ## License
283
+
284
+ MIT
@@ -0,0 +1,32 @@
1
+ /**
2
+ * zenstack-trpc
3
+ *
4
+ * Auto-generate fully type-safe tRPC routers from ZenStack V3 schemas.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { ZenStackClient } from "@zenstackhq/orm";
9
+ * import { schema, SchemaType } from "./zenstack/schema.js";
10
+ * import {
11
+ * createTRPC,
12
+ * createZenStackRouter,
13
+ * type Context,
14
+ * type TypedRouterCaller,
15
+ * } from "zenstack-trpc";
16
+ *
17
+ * const db = new ZenStackClient(schema, { dialect: yourDialect });
18
+ * const t = createTRPC<Context>();
19
+ * const appRouter = createZenStackRouter(schema, t);
20
+ *
21
+ * // Create a typed caller with full type inference
22
+ * const caller = appRouter.createCaller({ db }) as TypedRouterCaller<SchemaType>;
23
+ *
24
+ * // All operations are fully typed!
25
+ * const users = await caller.user.findMany({ include: { posts: true } });
26
+ * ```
27
+ *
28
+ * @packageDocumentation
29
+ */
30
+ export { createTRPC, createZenStackRouter, type Context, type ZenStackRouter, type TypedRouterCaller, type TypedModelProcedures, type TRPCInstance, } from "./router-generator.js";
31
+ export { createModelSchemas, createWhereSchema, createCreateDataSchema, createUpdateDataSchema, createUniqueWhereSchema, createSelectSchema, createIncludeSchema, createOrderBySchema, } from "./zod-schemas.js";
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,KAAK,OAAO,EACZ,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,YAAY,GAClB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * zenstack-trpc
3
+ *
4
+ * Auto-generate fully type-safe tRPC routers from ZenStack V3 schemas.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { ZenStackClient } from "@zenstackhq/orm";
9
+ * import { schema, SchemaType } from "./zenstack/schema.js";
10
+ * import {
11
+ * createTRPC,
12
+ * createZenStackRouter,
13
+ * type Context,
14
+ * type TypedRouterCaller,
15
+ * } from "zenstack-trpc";
16
+ *
17
+ * const db = new ZenStackClient(schema, { dialect: yourDialect });
18
+ * const t = createTRPC<Context>();
19
+ * const appRouter = createZenStackRouter(schema, t);
20
+ *
21
+ * // Create a typed caller with full type inference
22
+ * const caller = appRouter.createCaller({ db }) as TypedRouterCaller<SchemaType>;
23
+ *
24
+ * // All operations are fully typed!
25
+ * const users = await caller.user.findMany({ include: { posts: true } });
26
+ * ```
27
+ *
28
+ * @packageDocumentation
29
+ */
30
+ // Router generator and types
31
+ export { createTRPC, createZenStackRouter, } from "./router-generator.js";
32
+ // Zod schema generators (for advanced usage)
33
+ export { createModelSchemas, createWhereSchema, createCreateDataSchema, createUpdateDataSchema, createUniqueWhereSchema, createSelectSchema, createIncludeSchema, createOrderBySchema, } from "./zod-schemas.js";
@@ -0,0 +1,284 @@
1
+ import type { SchemaDef, GetModels } from "@zenstackhq/orm/schema";
2
+ import type { FindManyArgs, FindUniqueArgs, FindFirstArgs, CreateArgs, CreateManyArgs, UpdateArgs, UpdateManyArgs, UpsertArgs, DeleteArgs, DeleteManyArgs, CountArgs, AggregateArgs, GroupByArgs, SimplifiedPlainResult } from "@zenstackhq/orm";
3
+ /**
4
+ * Context type for the tRPC router
5
+ * The db property accepts any ZenStack client instance
6
+ */
7
+ export interface Context {
8
+ db: any;
9
+ }
10
+ declare const _initTRPCResult: import("@trpc/server").TRPCRootObject<Context, object, import("@trpc/server").TRPCRuntimeConfigOptions<Context, object>, {
11
+ ctx: Context;
12
+ meta: object;
13
+ errorShape: import("@trpc/server").TRPCDefaultErrorShape;
14
+ transformer: false;
15
+ }>;
16
+ /**
17
+ * Type representing the result of initTRPC.context().create()
18
+ */
19
+ export type TRPCInstance = typeof _initTRPCResult;
20
+ /**
21
+ * Creates a tRPC instance with the given context
22
+ * @returns A tRPC instance configured with your context type
23
+ */
24
+ export declare function createTRPC<TContext extends Context>(): TRPCInstance;
25
+ /**
26
+ * Type helper to convert model names to lowercase
27
+ */
28
+ type Uncapitalize<S extends string> = S extends `${infer First}${infer Rest}` ? `${Lowercase<First>}${Rest}` : S;
29
+ /**
30
+ * Type for a single model's procedures - provides FULL dynamic input AND output typing
31
+ *
32
+ * When you pass `include` or `select`, the return type automatically includes those fields!
33
+ */
34
+ export interface TypedModelProcedures<Schema extends SchemaDef, Model extends GetModels<Schema>> {
35
+ /**
36
+ * Find multiple records
37
+ * @example
38
+ * // Returns User[] with posts included
39
+ * const users = await caller.user.findMany({ include: { posts: true } });
40
+ */
41
+ findMany<T extends FindManyArgs<Schema, Model>>(input?: T): Promise<SimplifiedPlainResult<Schema, Model, T>[]>;
42
+ /**
43
+ * Find a unique record by ID or unique field
44
+ */
45
+ findUnique<T extends FindUniqueArgs<Schema, Model>>(input: T): Promise<SimplifiedPlainResult<Schema, Model, T> | null>;
46
+ /**
47
+ * Find the first matching record
48
+ */
49
+ findFirst<T extends FindFirstArgs<Schema, Model>>(input?: T): Promise<SimplifiedPlainResult<Schema, Model, T> | null>;
50
+ /**
51
+ * Create a new record
52
+ */
53
+ create<T extends CreateArgs<Schema, Model>>(input: T): Promise<SimplifiedPlainResult<Schema, Model, T>>;
54
+ /**
55
+ * Create multiple records
56
+ */
57
+ createMany(input: CreateManyArgs<Schema, Model>): Promise<{
58
+ count: number;
59
+ }>;
60
+ /**
61
+ * Update a record
62
+ */
63
+ update<T extends UpdateArgs<Schema, Model>>(input: T): Promise<SimplifiedPlainResult<Schema, Model, T>>;
64
+ /**
65
+ * Update multiple records
66
+ */
67
+ updateMany(input: UpdateManyArgs<Schema, Model>): Promise<{
68
+ count: number;
69
+ }>;
70
+ /**
71
+ * Create or update a record
72
+ */
73
+ upsert<T extends UpsertArgs<Schema, Model>>(input: T): Promise<SimplifiedPlainResult<Schema, Model, T>>;
74
+ /**
75
+ * Delete a record
76
+ */
77
+ delete<T extends DeleteArgs<Schema, Model>>(input: T): Promise<SimplifiedPlainResult<Schema, Model, T>>;
78
+ /**
79
+ * Delete multiple records
80
+ */
81
+ deleteMany(input: DeleteManyArgs<Schema, Model>): Promise<{
82
+ count: number;
83
+ }>;
84
+ /**
85
+ * Count records
86
+ */
87
+ count(input?: CountArgs<Schema, Model>): Promise<number>;
88
+ /**
89
+ * Aggregate records
90
+ */
91
+ aggregate(input: AggregateArgs<Schema, Model>): Promise<any>;
92
+ /**
93
+ * Group records
94
+ */
95
+ groupBy(input: GroupByArgs<Schema, Model>): Promise<any[]>;
96
+ }
97
+ /**
98
+ * Type for the generated router caller - maps model names to their typed procedures
99
+ *
100
+ * This provides FULL type inference including:
101
+ * - Input types (where, data, include, select, etc.)
102
+ * - Output types that reflect include/select options
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * const caller = appRouter.createCaller({ db }) as TypedRouterCaller<SchemaType>;
107
+ *
108
+ * // Basic query - returns User[]
109
+ * const users = await caller.user.findMany();
110
+ *
111
+ * // With include - returns (User & { posts: Post[] })[]
112
+ * const usersWithPosts = await caller.user.findMany({ include: { posts: true } });
113
+ *
114
+ * // With select - returns { id: string, email: string }[]
115
+ * const userEmails = await caller.user.findMany({ select: { id: true, email: true } });
116
+ * ```
117
+ */
118
+ export type TypedRouterCaller<Schema extends SchemaDef> = {
119
+ [K in GetModels<Schema> as Uncapitalize<K>]: TypedModelProcedures<Schema, K>;
120
+ };
121
+ /**
122
+ * Creates a tRPC router from a ZenStack schema.
123
+ *
124
+ * The router follows the pattern: router.modelName.operation
125
+ * Example: router.user.findMany(), router.post.create()
126
+ *
127
+ * For proper typing on the caller, use the TypedRouterCaller type:
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * import { createZenStackRouter, createTRPC, Context, TypedRouterCaller } from './trpc';
132
+ * import { schema, SchemaType } from '../zenstack/schema';
133
+ *
134
+ * const t = createTRPC<Context>();
135
+ * const appRouter = createZenStackRouter(schema, t);
136
+ *
137
+ * // Create a typed caller with FULL type inference
138
+ * const caller = appRouter.createCaller({ db }) as TypedRouterCaller<SchemaType>;
139
+ *
140
+ * // Types are fully inferred based on your query!
141
+ * const users = await caller.user.findMany();
142
+ * // ^? { id: string, email: string, name: string | null, ... }[]
143
+ *
144
+ * const usersWithPosts = await caller.user.findMany({ include: { posts: true } });
145
+ * // ^? { id: string, email: string, ..., posts: Post[] }[]
146
+ *
147
+ * export type AppRouter = typeof appRouter;
148
+ * ```
149
+ */
150
+ export declare function createZenStackRouter<Schema extends SchemaDef>(schema: Schema, t: TRPCInstance): import("@trpc/server").TRPCBuiltRouter<{
151
+ ctx: Context;
152
+ meta: object;
153
+ errorShape: import("@trpc/server").TRPCDefaultErrorShape;
154
+ transformer: false;
155
+ }, import("@trpc/server").TRPCDecorateCreateRouterOptions<Record<string, import("@trpc/server").TRPCBuiltRouter<{
156
+ ctx: Context;
157
+ meta: object;
158
+ errorShape: import("@trpc/server").TRPCDefaultErrorShape;
159
+ transformer: false;
160
+ }, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
161
+ findMany: import("@trpc/server").TRPCQueryProcedure<{
162
+ input: unknown;
163
+ output: any;
164
+ meta: object;
165
+ }> | import("@trpc/server").TRPCMutationProcedure<{
166
+ input: unknown;
167
+ output: any;
168
+ meta: object;
169
+ }>;
170
+ findUnique: import("@trpc/server").TRPCQueryProcedure<{
171
+ input: unknown;
172
+ output: any;
173
+ meta: object;
174
+ }> | import("@trpc/server").TRPCMutationProcedure<{
175
+ input: unknown;
176
+ output: any;
177
+ meta: object;
178
+ }>;
179
+ findFirst: import("@trpc/server").TRPCQueryProcedure<{
180
+ input: unknown;
181
+ output: any;
182
+ meta: object;
183
+ }> | import("@trpc/server").TRPCMutationProcedure<{
184
+ input: unknown;
185
+ output: any;
186
+ meta: object;
187
+ }>;
188
+ count: import("@trpc/server").TRPCQueryProcedure<{
189
+ input: unknown;
190
+ output: any;
191
+ meta: object;
192
+ }> | import("@trpc/server").TRPCMutationProcedure<{
193
+ input: unknown;
194
+ output: any;
195
+ meta: object;
196
+ }>;
197
+ aggregate: import("@trpc/server").TRPCQueryProcedure<{
198
+ input: unknown;
199
+ output: any;
200
+ meta: object;
201
+ }> | import("@trpc/server").TRPCMutationProcedure<{
202
+ input: unknown;
203
+ output: any;
204
+ meta: object;
205
+ }>;
206
+ groupBy: import("@trpc/server").TRPCQueryProcedure<{
207
+ input: unknown;
208
+ output: any;
209
+ meta: object;
210
+ }> | import("@trpc/server").TRPCMutationProcedure<{
211
+ input: unknown;
212
+ output: any;
213
+ meta: object;
214
+ }>;
215
+ create: import("@trpc/server").TRPCQueryProcedure<{
216
+ input: unknown;
217
+ output: any;
218
+ meta: object;
219
+ }> | import("@trpc/server").TRPCMutationProcedure<{
220
+ input: unknown;
221
+ output: any;
222
+ meta: object;
223
+ }>;
224
+ createMany: import("@trpc/server").TRPCQueryProcedure<{
225
+ input: unknown;
226
+ output: any;
227
+ meta: object;
228
+ }> | import("@trpc/server").TRPCMutationProcedure<{
229
+ input: unknown;
230
+ output: any;
231
+ meta: object;
232
+ }>;
233
+ update: import("@trpc/server").TRPCQueryProcedure<{
234
+ input: unknown;
235
+ output: any;
236
+ meta: object;
237
+ }> | import("@trpc/server").TRPCMutationProcedure<{
238
+ input: unknown;
239
+ output: any;
240
+ meta: object;
241
+ }>;
242
+ updateMany: import("@trpc/server").TRPCQueryProcedure<{
243
+ input: unknown;
244
+ output: any;
245
+ meta: object;
246
+ }> | import("@trpc/server").TRPCMutationProcedure<{
247
+ input: unknown;
248
+ output: any;
249
+ meta: object;
250
+ }>;
251
+ upsert: import("@trpc/server").TRPCQueryProcedure<{
252
+ input: unknown;
253
+ output: any;
254
+ meta: object;
255
+ }> | import("@trpc/server").TRPCMutationProcedure<{
256
+ input: unknown;
257
+ output: any;
258
+ meta: object;
259
+ }>;
260
+ delete: import("@trpc/server").TRPCQueryProcedure<{
261
+ input: unknown;
262
+ output: any;
263
+ meta: object;
264
+ }> | import("@trpc/server").TRPCMutationProcedure<{
265
+ input: unknown;
266
+ output: any;
267
+ meta: object;
268
+ }>;
269
+ deleteMany: import("@trpc/server").TRPCQueryProcedure<{
270
+ input: unknown;
271
+ output: any;
272
+ meta: object;
273
+ }> | import("@trpc/server").TRPCMutationProcedure<{
274
+ input: unknown;
275
+ output: any;
276
+ meta: object;
277
+ }>;
278
+ }>>>>>;
279
+ /**
280
+ * Type helper to extract the router type for a given schema
281
+ */
282
+ export type ZenStackRouter<Schema extends SchemaDef> = ReturnType<typeof createZenStackRouter<Schema>>;
283
+ export {};
284
+ //# sourceMappingURL=router-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router-generator.d.ts","sourceRoot":"","sources":["../src/router-generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ import { initTRPC, TRPCError } from "@trpc/server";
2
+ import { createModelSchemas } from "./zod-schemas.js";
3
+ // Internal type for initTRPC result
4
+ const _initTRPCResult = initTRPC.context().create();
5
+ /**
6
+ * Creates a tRPC instance with the given context
7
+ * @returns A tRPC instance configured with your context type
8
+ */
9
+ export function createTRPC() {
10
+ return initTRPC.context().create();
11
+ }
12
+ /**
13
+ * Creates procedures for a single model
14
+ */
15
+ function createModelProcedures(schema, modelName, t) {
16
+ const schemas = createModelSchemas(schema, modelName);
17
+ const modelNameLower = modelName.charAt(0).toLowerCase() + modelName.slice(1);
18
+ const createHandler = (op, isQuery) => {
19
+ const inputSchema = schemas[op];
20
+ const handler = async ({ ctx, input }) => {
21
+ const db = ctx.db;
22
+ const model = db[modelNameLower];
23
+ if (!model || typeof model[op] !== "function") {
24
+ throw new TRPCError({
25
+ code: "INTERNAL_SERVER_ERROR",
26
+ message: `Operation ${op} not found on model ${modelName}`,
27
+ });
28
+ }
29
+ return model[op](input);
30
+ };
31
+ if (isQuery) {
32
+ return t.procedure.input(inputSchema).query(handler);
33
+ }
34
+ else {
35
+ return t.procedure.input(inputSchema).mutation(handler);
36
+ }
37
+ };
38
+ const procedures = {
39
+ findMany: createHandler("findMany", true),
40
+ findUnique: createHandler("findUnique", true),
41
+ findFirst: createHandler("findFirst", true),
42
+ count: createHandler("count", true),
43
+ aggregate: createHandler("aggregate", true),
44
+ groupBy: createHandler("groupBy", true),
45
+ create: createHandler("create", false),
46
+ createMany: createHandler("createMany", false),
47
+ update: createHandler("update", false),
48
+ updateMany: createHandler("updateMany", false),
49
+ upsert: createHandler("upsert", false),
50
+ delete: createHandler("delete", false),
51
+ deleteMany: createHandler("deleteMany", false),
52
+ };
53
+ return t.router(procedures);
54
+ }
55
+ /**
56
+ * Creates a tRPC router from a ZenStack schema.
57
+ *
58
+ * The router follows the pattern: router.modelName.operation
59
+ * Example: router.user.findMany(), router.post.create()
60
+ *
61
+ * For proper typing on the caller, use the TypedRouterCaller type:
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * import { createZenStackRouter, createTRPC, Context, TypedRouterCaller } from './trpc';
66
+ * import { schema, SchemaType } from '../zenstack/schema';
67
+ *
68
+ * const t = createTRPC<Context>();
69
+ * const appRouter = createZenStackRouter(schema, t);
70
+ *
71
+ * // Create a typed caller with FULL type inference
72
+ * const caller = appRouter.createCaller({ db }) as TypedRouterCaller<SchemaType>;
73
+ *
74
+ * // Types are fully inferred based on your query!
75
+ * const users = await caller.user.findMany();
76
+ * // ^? { id: string, email: string, name: string | null, ... }[]
77
+ *
78
+ * const usersWithPosts = await caller.user.findMany({ include: { posts: true } });
79
+ * // ^? { id: string, email: string, ..., posts: Post[] }[]
80
+ *
81
+ * export type AppRouter = typeof appRouter;
82
+ * ```
83
+ */
84
+ export function createZenStackRouter(schema, t) {
85
+ const modelRouters = {};
86
+ // Get all model names from the schema
87
+ const modelNames = Object.keys(schema.models);
88
+ for (const modelName of modelNames) {
89
+ const modelNameLower = modelName.charAt(0).toLowerCase() + modelName.slice(1);
90
+ modelRouters[modelNameLower] = createModelProcedures(schema, modelName, t);
91
+ }
92
+ return t.router(modelRouters);
93
+ }
@@ -0,0 +1,128 @@
1
+ import { z } from "zod";
2
+ import type { SchemaDef } from "@zenstackhq/orm/schema";
3
+ /**
4
+ * Creates a Zod schema for a model's where input
5
+ */
6
+ export declare function createWhereSchema<Schema extends SchemaDef>(schema: Schema, modelName: keyof Schema["models"]): z.ZodTypeAny;
7
+ /**
8
+ * Creates a Zod schema for a model's create data input
9
+ */
10
+ export declare function createCreateDataSchema<Schema extends SchemaDef>(schema: Schema, modelName: keyof Schema["models"]): z.ZodTypeAny;
11
+ /**
12
+ * Creates a Zod schema for a model's update data input
13
+ */
14
+ export declare function createUpdateDataSchema<Schema extends SchemaDef>(schema: Schema, modelName: keyof Schema["models"]): z.ZodTypeAny;
15
+ /**
16
+ * Creates a Zod schema for unique where input (id or unique fields)
17
+ */
18
+ export declare function createUniqueWhereSchema<Schema extends SchemaDef>(schema: Schema, modelName: keyof Schema["models"]): z.ZodTypeAny;
19
+ /**
20
+ * Creates a Zod schema for select input
21
+ */
22
+ export declare function createSelectSchema<Schema extends SchemaDef>(schema: Schema, modelName: keyof Schema["models"]): z.ZodTypeAny;
23
+ /**
24
+ * Creates a Zod schema for include input
25
+ */
26
+ export declare function createIncludeSchema<Schema extends SchemaDef>(schema: Schema, modelName: keyof Schema["models"]): z.ZodTypeAny;
27
+ /**
28
+ * Creates a Zod schema for orderBy input
29
+ */
30
+ export declare function createOrderBySchema<Schema extends SchemaDef>(schema: Schema, modelName: keyof Schema["models"]): z.ZodTypeAny;
31
+ /**
32
+ * Creates all Zod schemas for a model's CRUD operations
33
+ */
34
+ export declare function createModelSchemas<Schema extends SchemaDef>(schema: Schema, modelName: keyof Schema["models"]): {
35
+ findMany: z.ZodOptional<z.ZodObject<{
36
+ where: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
37
+ select: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
38
+ include: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
39
+ orderBy: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
40
+ skip: z.ZodOptional<z.ZodNumber>;
41
+ take: z.ZodOptional<z.ZodNumber>;
42
+ cursor: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
43
+ distinct: z.ZodOptional<z.ZodArray<z.ZodString>>;
44
+ }, z.core.$loose>>;
45
+ findUnique: z.ZodObject<{
46
+ where: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
47
+ select: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
48
+ include: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
49
+ }, z.core.$loose>;
50
+ findFirst: z.ZodOptional<z.ZodObject<{
51
+ where: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
52
+ select: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
53
+ include: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
54
+ orderBy: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
55
+ skip: z.ZodOptional<z.ZodNumber>;
56
+ cursor: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
57
+ }, z.core.$loose>>;
58
+ create: z.ZodObject<{
59
+ data: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
60
+ select: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
61
+ include: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
62
+ }, z.core.$loose>;
63
+ createMany: z.ZodOptional<z.ZodObject<{
64
+ data: z.ZodUnion<readonly [z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodArray<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>]>;
65
+ skipDuplicates: z.ZodOptional<z.ZodBoolean>;
66
+ }, z.core.$loose>>;
67
+ update: z.ZodObject<{
68
+ where: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
69
+ data: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
70
+ select: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
71
+ include: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
72
+ }, z.core.$loose>;
73
+ updateMany: z.ZodObject<{
74
+ where: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
75
+ data: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
76
+ limit: z.ZodOptional<z.ZodNumber>;
77
+ }, z.core.$loose>;
78
+ upsert: z.ZodObject<{
79
+ where: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
80
+ create: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
81
+ update: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
82
+ select: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
83
+ include: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
84
+ }, z.core.$loose>;
85
+ delete: z.ZodObject<{
86
+ where: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
87
+ select: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
88
+ include: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
89
+ }, z.core.$loose>;
90
+ deleteMany: z.ZodOptional<z.ZodObject<{
91
+ where: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
92
+ limit: z.ZodOptional<z.ZodNumber>;
93
+ }, z.core.$loose>>;
94
+ count: z.ZodOptional<z.ZodObject<{
95
+ where: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
96
+ select: z.ZodOptional<z.ZodAny>;
97
+ cursor: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
98
+ skip: z.ZodOptional<z.ZodNumber>;
99
+ take: z.ZodOptional<z.ZodNumber>;
100
+ orderBy: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
101
+ }, z.core.$loose>>;
102
+ aggregate: z.ZodObject<{
103
+ where: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
104
+ cursor: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
105
+ skip: z.ZodOptional<z.ZodNumber>;
106
+ take: z.ZodOptional<z.ZodNumber>;
107
+ orderBy: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
108
+ _count: z.ZodOptional<z.ZodAny>;
109
+ _avg: z.ZodOptional<z.ZodAny>;
110
+ _sum: z.ZodOptional<z.ZodAny>;
111
+ _min: z.ZodOptional<z.ZodAny>;
112
+ _max: z.ZodOptional<z.ZodAny>;
113
+ }, z.core.$loose>;
114
+ groupBy: z.ZodObject<{
115
+ by: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
116
+ where: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
117
+ having: z.ZodOptional<z.ZodAny>;
118
+ orderBy: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
119
+ skip: z.ZodOptional<z.ZodNumber>;
120
+ take: z.ZodOptional<z.ZodNumber>;
121
+ _count: z.ZodOptional<z.ZodAny>;
122
+ _avg: z.ZodOptional<z.ZodAny>;
123
+ _sum: z.ZodOptional<z.ZodAny>;
124
+ _min: z.ZodOptional<z.ZodAny>;
125
+ _max: z.ZodOptional<z.ZodAny>;
126
+ }, z.core.$loose>;
127
+ };
128
+ //# sourceMappingURL=zod-schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-schemas.d.ts","sourceRoot":"","sources":["../src/zod-schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAsDxD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,SAAS,SAAS,EACxD,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,GAChC,CAAC,CAAC,UAAU,CAmDd;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,SAAS,SAAS,EAC7D,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,GAChC,CAAC,CAAC,UAAU,CAyCd;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,SAAS,SAAS,EAC7D,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,GAChC,CAAC,CAAC,UAAU,CAuCd;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,SAAS,SAAS,EAC9D,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,GAChC,CAAC,CAAC,UAAU,CAed;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,SAAS,SAAS,EACzD,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,GAChC,CAAC,CAAC,UAAU,CAcd;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,SAAS,SAAS,EAC1D,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,GAChC,CAAC,CAAC,UAAU,CAiBd;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,SAAS,SAAS,EAC1D,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,GAChC,CAAC,CAAC,UAAU,CAkBd;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,SAAS,SAAS,EACzD,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,MAAM,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmIlC"}
@@ -0,0 +1,362 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Maps ZenStack field types to Zod schemas
4
+ */
5
+ function getZodTypeForField(fieldType, isOptional, isArray) {
6
+ let baseSchema;
7
+ switch (fieldType) {
8
+ case "String":
9
+ baseSchema = z.string();
10
+ break;
11
+ case "Int":
12
+ case "Float":
13
+ baseSchema = z.number();
14
+ break;
15
+ case "BigInt":
16
+ baseSchema = z.bigint();
17
+ break;
18
+ case "Boolean":
19
+ baseSchema = z.boolean();
20
+ break;
21
+ case "DateTime":
22
+ baseSchema = z.union([z.date(), z.string().datetime()]);
23
+ break;
24
+ case "Json":
25
+ baseSchema = z.any();
26
+ break;
27
+ case "Bytes":
28
+ baseSchema = z.instanceof(Uint8Array);
29
+ break;
30
+ case "Decimal":
31
+ baseSchema = z.union([z.number(), z.string()]);
32
+ break;
33
+ default:
34
+ // For relation types or unknown types, use any
35
+ baseSchema = z.any();
36
+ }
37
+ if (isArray) {
38
+ baseSchema = z.array(baseSchema);
39
+ }
40
+ if (isOptional) {
41
+ baseSchema = baseSchema.optional().nullable();
42
+ }
43
+ return baseSchema;
44
+ }
45
+ /**
46
+ * Creates a Zod schema for a model's where input
47
+ */
48
+ export function createWhereSchema(schema, modelName) {
49
+ const model = schema.models[modelName];
50
+ if (!model) {
51
+ return z.object({}).passthrough();
52
+ }
53
+ const fields = model.fields;
54
+ const shape = {};
55
+ for (const [fieldName, fieldDef] of Object.entries(fields)) {
56
+ const isRelation = fieldDef.relation !== undefined;
57
+ if (isRelation) {
58
+ // For relations, allow nested where clauses
59
+ shape[fieldName] = z.any().optional();
60
+ }
61
+ else {
62
+ // For scalar fields, allow direct value or filter object
63
+ const fieldType = fieldDef.type;
64
+ const baseType = getZodTypeForField(fieldType, false, false);
65
+ shape[fieldName] = z
66
+ .union([
67
+ baseType,
68
+ z.object({
69
+ equals: baseType.optional(),
70
+ not: baseType.optional(),
71
+ in: z.array(baseType).optional(),
72
+ notIn: z.array(baseType).optional(),
73
+ lt: baseType.optional(),
74
+ lte: baseType.optional(),
75
+ gt: baseType.optional(),
76
+ gte: baseType.optional(),
77
+ contains: z.string().optional(),
78
+ startsWith: z.string().optional(),
79
+ endsWith: z.string().optional(),
80
+ }).passthrough(),
81
+ ])
82
+ .optional();
83
+ }
84
+ }
85
+ // Add logical operators
86
+ const baseSchema = z.object(shape).passthrough();
87
+ return z
88
+ .object({
89
+ AND: z.union([baseSchema, z.array(baseSchema)]).optional(),
90
+ OR: z.array(baseSchema).optional(),
91
+ NOT: z.union([baseSchema, z.array(baseSchema)]).optional(),
92
+ })
93
+ .merge(baseSchema)
94
+ .partial();
95
+ }
96
+ /**
97
+ * Creates a Zod schema for a model's create data input
98
+ */
99
+ export function createCreateDataSchema(schema, modelName) {
100
+ const model = schema.models[modelName];
101
+ if (!model) {
102
+ return z.object({}).passthrough();
103
+ }
104
+ const fields = model.fields;
105
+ const shape = {};
106
+ for (const [fieldName, fieldDef] of Object.entries(fields)) {
107
+ const isRelation = fieldDef.relation !== undefined;
108
+ const hasDefault = fieldDef.default !== undefined;
109
+ const isId = fieldDef.id === true;
110
+ const isUpdatedAt = fieldDef.updatedAt === true;
111
+ const isOptional = fieldDef.optional === true || hasDefault || isId || isUpdatedAt;
112
+ const isForeignKey = fieldDef.foreignKeyFor !== undefined;
113
+ if (isRelation) {
114
+ // For relations, allow create/connect/connectOrCreate
115
+ shape[fieldName] = z
116
+ .object({
117
+ create: z.any().optional(),
118
+ connect: z.any().optional(),
119
+ connectOrCreate: z.any().optional(),
120
+ })
121
+ .passthrough()
122
+ .optional();
123
+ }
124
+ else {
125
+ // Regular fields including foreign keys
126
+ const fieldType = fieldDef.type;
127
+ // Foreign keys are optional if a relation is used instead
128
+ const isFkOptional = isForeignKey ? true : isOptional;
129
+ shape[fieldName] = getZodTypeForField(fieldType, isFkOptional, fieldDef.array === true);
130
+ }
131
+ }
132
+ return z.object(shape).passthrough();
133
+ }
134
+ /**
135
+ * Creates a Zod schema for a model's update data input
136
+ */
137
+ export function createUpdateDataSchema(schema, modelName) {
138
+ const model = schema.models[modelName];
139
+ if (!model) {
140
+ return z.object({}).passthrough();
141
+ }
142
+ const fields = model.fields;
143
+ const shape = {};
144
+ for (const [fieldName, fieldDef] of Object.entries(fields)) {
145
+ const isRelation = fieldDef.relation !== undefined;
146
+ const isId = fieldDef.id === true;
147
+ if (isId)
148
+ continue; // Skip id fields for updates
149
+ if (isRelation) {
150
+ // For relations, allow full nested operations
151
+ shape[fieldName] = z
152
+ .object({
153
+ create: z.any().optional(),
154
+ connect: z.any().optional(),
155
+ connectOrCreate: z.any().optional(),
156
+ disconnect: z.any().optional(),
157
+ delete: z.any().optional(),
158
+ update: z.any().optional(),
159
+ updateMany: z.any().optional(),
160
+ deleteMany: z.any().optional(),
161
+ set: z.any().optional(),
162
+ upsert: z.any().optional(),
163
+ })
164
+ .passthrough()
165
+ .optional();
166
+ }
167
+ else {
168
+ const fieldType = fieldDef.type;
169
+ shape[fieldName] = getZodTypeForField(fieldType, true, fieldDef.array === true);
170
+ }
171
+ }
172
+ return z.object(shape).passthrough();
173
+ }
174
+ /**
175
+ * Creates a Zod schema for unique where input (id or unique fields)
176
+ */
177
+ export function createUniqueWhereSchema(schema, modelName) {
178
+ const model = schema.models[modelName];
179
+ if (!model) {
180
+ return z.object({}).passthrough();
181
+ }
182
+ const uniqueFields = model.uniqueFields;
183
+ const shape = {};
184
+ for (const [fieldName, fieldDef] of Object.entries(uniqueFields)) {
185
+ const fieldType = fieldDef.type;
186
+ shape[fieldName] = getZodTypeForField(fieldType, true, false);
187
+ }
188
+ return z.object(shape).passthrough();
189
+ }
190
+ /**
191
+ * Creates a Zod schema for select input
192
+ */
193
+ export function createSelectSchema(schema, modelName) {
194
+ const model = schema.models[modelName];
195
+ if (!model) {
196
+ return z.object({}).passthrough();
197
+ }
198
+ const fields = model.fields;
199
+ const shape = {};
200
+ for (const fieldName of Object.keys(fields)) {
201
+ shape[fieldName] = z.union([z.boolean(), z.any()]).optional();
202
+ }
203
+ return z.object(shape).passthrough().optional();
204
+ }
205
+ /**
206
+ * Creates a Zod schema for include input
207
+ */
208
+ export function createIncludeSchema(schema, modelName) {
209
+ const model = schema.models[modelName];
210
+ if (!model) {
211
+ return z.object({}).passthrough();
212
+ }
213
+ const fields = model.fields;
214
+ const shape = {};
215
+ for (const [fieldName, fieldDef] of Object.entries(fields)) {
216
+ const isRelation = fieldDef.relation !== undefined;
217
+ if (isRelation) {
218
+ shape[fieldName] = z.union([z.boolean(), z.any()]).optional();
219
+ }
220
+ }
221
+ return z.object(shape).passthrough().optional();
222
+ }
223
+ /**
224
+ * Creates a Zod schema for orderBy input
225
+ */
226
+ export function createOrderBySchema(schema, modelName) {
227
+ const model = schema.models[modelName];
228
+ if (!model) {
229
+ return z.any();
230
+ }
231
+ const fields = model.fields;
232
+ const shape = {};
233
+ const sortOrder = z.enum(["asc", "desc"]);
234
+ for (const [fieldName, fieldDef] of Object.entries(fields)) {
235
+ const isRelation = fieldDef.relation !== undefined;
236
+ if (!isRelation) {
237
+ shape[fieldName] = sortOrder.optional();
238
+ }
239
+ }
240
+ return z.union([z.object(shape).passthrough(), z.array(z.object(shape).passthrough())]).optional();
241
+ }
242
+ /**
243
+ * Creates all Zod schemas for a model's CRUD operations
244
+ */
245
+ export function createModelSchemas(schema, modelName) {
246
+ const whereSchema = createWhereSchema(schema, modelName);
247
+ const uniqueWhereSchema = createUniqueWhereSchema(schema, modelName);
248
+ const createDataSchema = createCreateDataSchema(schema, modelName);
249
+ const updateDataSchema = createUpdateDataSchema(schema, modelName);
250
+ const selectSchema = createSelectSchema(schema, modelName);
251
+ const includeSchema = createIncludeSchema(schema, modelName);
252
+ const orderBySchema = createOrderBySchema(schema, modelName);
253
+ return {
254
+ findMany: z
255
+ .object({
256
+ where: whereSchema.optional(),
257
+ select: selectSchema,
258
+ include: includeSchema,
259
+ orderBy: orderBySchema,
260
+ skip: z.number().optional(),
261
+ take: z.number().optional(),
262
+ cursor: uniqueWhereSchema.optional(),
263
+ distinct: z.array(z.string()).optional(),
264
+ })
265
+ .passthrough()
266
+ .optional(),
267
+ findUnique: z.object({
268
+ where: uniqueWhereSchema,
269
+ select: selectSchema,
270
+ include: includeSchema,
271
+ }).passthrough(),
272
+ findFirst: z
273
+ .object({
274
+ where: whereSchema.optional(),
275
+ select: selectSchema,
276
+ include: includeSchema,
277
+ orderBy: orderBySchema,
278
+ skip: z.number().optional(),
279
+ cursor: uniqueWhereSchema.optional(),
280
+ })
281
+ .passthrough()
282
+ .optional(),
283
+ create: z.object({
284
+ data: createDataSchema,
285
+ select: selectSchema,
286
+ include: includeSchema,
287
+ }).passthrough(),
288
+ createMany: z
289
+ .object({
290
+ data: z.union([createDataSchema, z.array(createDataSchema)]),
291
+ skipDuplicates: z.boolean().optional(),
292
+ })
293
+ .passthrough()
294
+ .optional(),
295
+ update: z.object({
296
+ where: uniqueWhereSchema,
297
+ data: updateDataSchema,
298
+ select: selectSchema,
299
+ include: includeSchema,
300
+ }).passthrough(),
301
+ updateMany: z.object({
302
+ where: whereSchema.optional(),
303
+ data: updateDataSchema,
304
+ limit: z.number().optional(),
305
+ }).passthrough(),
306
+ upsert: z.object({
307
+ where: uniqueWhereSchema,
308
+ create: createDataSchema,
309
+ update: updateDataSchema,
310
+ select: selectSchema,
311
+ include: includeSchema,
312
+ }).passthrough(),
313
+ delete: z.object({
314
+ where: uniqueWhereSchema,
315
+ select: selectSchema,
316
+ include: includeSchema,
317
+ }).passthrough(),
318
+ deleteMany: z
319
+ .object({
320
+ where: whereSchema.optional(),
321
+ limit: z.number().optional(),
322
+ })
323
+ .passthrough()
324
+ .optional(),
325
+ count: z
326
+ .object({
327
+ where: whereSchema.optional(),
328
+ select: z.any().optional(),
329
+ cursor: uniqueWhereSchema.optional(),
330
+ skip: z.number().optional(),
331
+ take: z.number().optional(),
332
+ orderBy: orderBySchema,
333
+ })
334
+ .passthrough()
335
+ .optional(),
336
+ aggregate: z.object({
337
+ where: whereSchema.optional(),
338
+ cursor: uniqueWhereSchema.optional(),
339
+ skip: z.number().optional(),
340
+ take: z.number().optional(),
341
+ orderBy: orderBySchema,
342
+ _count: z.any().optional(),
343
+ _avg: z.any().optional(),
344
+ _sum: z.any().optional(),
345
+ _min: z.any().optional(),
346
+ _max: z.any().optional(),
347
+ }).passthrough(),
348
+ groupBy: z.object({
349
+ by: z.union([z.string(), z.array(z.string())]),
350
+ where: whereSchema.optional(),
351
+ having: z.any().optional(),
352
+ orderBy: orderBySchema,
353
+ skip: z.number().optional(),
354
+ take: z.number().optional(),
355
+ _count: z.any().optional(),
356
+ _avg: z.any().optional(),
357
+ _sum: z.any().optional(),
358
+ _min: z.any().optional(),
359
+ _max: z.any().optional(),
360
+ }).passthrough(),
361
+ };
362
+ }
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "zenstack-trpc",
3
+ "version": "0.1.0",
4
+ "description": "Auto-generate type-safe tRPC routers from ZenStack V3 schemas",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.build.json",
22
+ "prepublishOnly": "pnpm run build",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "typecheck": "tsc --noEmit"
26
+ },
27
+ "keywords": [
28
+ "zenstack",
29
+ "trpc",
30
+ "typescript",
31
+ "orm",
32
+ "prisma",
33
+ "typesafe",
34
+ "api",
35
+ "rpc",
36
+ "router",
37
+ "codegen"
38
+ ],
39
+ "author": "",
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/olup/zenstack-trpc"
44
+ },
45
+ "homepage": "https://github.com/olup/zenstack-trpc#readme",
46
+ "bugs": {
47
+ "url": "https://github.com/olup/zenstack-trpc/issues"
48
+ },
49
+ "engines": {
50
+ "node": ">=18"
51
+ },
52
+ "peerDependencies": {
53
+ "@trpc/server": ">=11.0.0",
54
+ "@zenstackhq/orm": ">=3.0.0",
55
+ "zod": ">=3.0.0 || >=4.0.0"
56
+ },
57
+ "devDependencies": {
58
+ "@trpc/server": "^11.8.1",
59
+ "@types/better-sqlite3": "^7.6.13",
60
+ "@types/node": "^25.0.3",
61
+ "@zenstackhq/cli": "^3.1.0",
62
+ "@zenstackhq/orm": "^3.1.0",
63
+ "better-sqlite3": "^12.5.0",
64
+ "kysely": "^0.28.9",
65
+ "prisma": "^7.2.0",
66
+ "tsx": "^4.21.0",
67
+ "typescript": "^5.9.3",
68
+ "vitest": "^4.0.16",
69
+ "zod": "^4.3.5"
70
+ }
71
+ }