@graphoria/server 0.1.5 → 0.2.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.
Files changed (101) hide show
  1. package/dist/src/analyzeQuery/resolveVariables.d.ts.map +1 -1
  2. package/dist/src/config/helpers/operationHelper.d.ts +60 -6
  3. package/dist/src/config/helpers/operationHelper.d.ts.map +1 -1
  4. package/dist/src/config/helpers/operationHelper.test.d.ts +2 -0
  5. package/dist/src/config/helpers/operationHelper.test.d.ts.map +1 -0
  6. package/dist/src/config/index.d.ts +65 -60
  7. package/dist/src/config/index.d.ts.map +1 -1
  8. package/dist/src/config/types/ai.d.ts +9 -3
  9. package/dist/src/config/types/ai.d.ts.map +1 -1
  10. package/dist/src/config/types/auth.d.ts +55 -15
  11. package/dist/src/config/types/auth.d.ts.map +1 -1
  12. package/dist/src/config/types/cron.d.ts +96 -2
  13. package/dist/src/config/types/cron.d.ts.map +1 -1
  14. package/dist/src/config/types/db.d.ts +102 -29
  15. package/dist/src/config/types/db.d.ts.map +1 -1
  16. package/dist/src/config/types/index.d.ts +1 -1
  17. package/dist/src/config/types/index.d.ts.map +1 -1
  18. package/dist/src/config/types/operation.d.ts +49 -17
  19. package/dist/src/config/types/operation.d.ts.map +1 -1
  20. package/dist/src/config/types/queue.d.ts +59 -12
  21. package/dist/src/config/types/queue.d.ts.map +1 -1
  22. package/dist/src/config/types/remote-rest.d.ts +10 -1
  23. package/dist/src/config/types/remote-rest.d.ts.map +1 -1
  24. package/dist/src/config/types/remote-schema.d.ts +13 -3
  25. package/dist/src/config/types/remote-schema.d.ts.map +1 -1
  26. package/dist/src/config/types/virtual-columns.d.ts +1 -1
  27. package/dist/src/config/types/virtual-columns.d.ts.map +1 -1
  28. package/dist/src/configuration/getSchemas/index.d.ts +4 -4
  29. package/dist/src/configuration/getSchemas/mergeEntities/index.d.ts +2 -2
  30. package/dist/src/configuration/getSchemas/mergeEntities/index.d.ts.map +1 -1
  31. package/dist/src/configuration/getSchemas/mergeEntities/index.test.d.ts +2 -0
  32. package/dist/src/configuration/getSchemas/mergeEntities/index.test.d.ts.map +1 -0
  33. package/dist/src/configuration/getSchemas/type-definition-generator/index.d.ts.map +1 -1
  34. package/dist/src/configuration/index.d.ts +4 -4
  35. package/dist/src/configuration/rest/generateOpenAPI.d.ts.map +1 -1
  36. package/dist/src/configuration/rest/handleRESTRequestFactory.d.ts.map +1 -1
  37. package/dist/src/databases/common.d.ts +3 -1
  38. package/dist/src/databases/common.d.ts.map +1 -1
  39. package/dist/src/databases/core/query-builder.d.ts +1 -1
  40. package/dist/src/databases/directives.d.ts.map +1 -1
  41. package/dist/src/databases/engines/mssql/connection.d.ts +1 -1
  42. package/dist/src/databases/engines/mssql/connection.d.ts.map +1 -1
  43. package/dist/src/databases/engines/mssql/query/index.d.ts.map +1 -1
  44. package/dist/src/databases/engines/mysql/connection.d.ts.map +1 -1
  45. package/dist/src/databases/engines/mysql/getStructure.d.ts.map +1 -1
  46. package/dist/src/databases/engines/mysql/placeholders.d.ts +22 -0
  47. package/dist/src/databases/engines/mysql/placeholders.d.ts.map +1 -0
  48. package/dist/src/databases/engines/mysql/placeholders.test.d.ts +2 -0
  49. package/dist/src/databases/engines/mysql/placeholders.test.d.ts.map +1 -0
  50. package/dist/src/databases/engines/mysql/query/index.d.ts.map +1 -1
  51. package/dist/src/databases/engines/postgresql/query/index.d.ts.map +1 -1
  52. package/dist/src/databases/high-level-operations.d.ts +1 -1
  53. package/dist/src/databases/sqlTypeUtils.d.ts.map +1 -1
  54. package/dist/src/databases/transformers/data-transformers.d.ts +2 -2
  55. package/dist/src/singletons/env.d.ts +3 -3
  56. package/dist/src/types/env.d.ts +52 -52
  57. package/dist/src/types/resolver.d.ts +12 -2
  58. package/dist/src/types/resolver.d.ts.map +1 -1
  59. package/dist/src/types/zod/auth.d.ts +6 -6
  60. package/dist/src/types/zod/configuration.d.ts +46 -46
  61. package/dist/src/types/zod/configuration.d.ts.map +1 -1
  62. package/dist/src/types/zod/cron.d.ts +1 -1
  63. package/dist/src/types/zod/cron.d.ts.map +1 -1
  64. package/dist/src/types/zod/db.d.ts +12 -12
  65. package/dist/src/types/zod/queue.d.ts +26 -26
  66. package/dist/src/types/zod/strict-keys.test.d.ts +2 -0
  67. package/dist/src/types/zod/strict-keys.test.d.ts.map +1 -0
  68. package/dist/tsconfig.tsbuildinfo +1 -1
  69. package/package.json +1 -1
  70. package/src/analyzeQuery/resolveVariables.ts +37 -7
  71. package/src/config/helpers/operationHelper.ts +146 -5
  72. package/src/config/index.ts +139 -62
  73. package/src/config/types/ai.ts +2 -2
  74. package/src/config/types/auth.ts +37 -6
  75. package/src/config/types/cron.ts +1 -1
  76. package/src/config/types/db.ts +10 -10
  77. package/src/config/types/index.ts +2 -0
  78. package/src/config/types/operation.ts +49 -9
  79. package/src/config/types/queue.ts +8 -8
  80. package/src/config/types/remote-rest.ts +1 -1
  81. package/src/config/types/remote-schema.ts +2 -2
  82. package/src/config/types/virtual-columns.ts +1 -1
  83. package/src/configuration/getSchemas/mergeEntities/index.ts +6 -0
  84. package/src/configuration/getSchemas/type-definition-generator/index.ts +13 -4
  85. package/src/configuration/rest/generateOpenAPI.ts +5 -9
  86. package/src/configuration/rest/handleRESTRequestFactory.ts +40 -14
  87. package/src/databases/common.ts +151 -65
  88. package/src/databases/directives.ts +26 -8
  89. package/src/databases/engines/mssql/connection.ts +1 -1
  90. package/src/databases/engines/mssql/query/index.ts +29 -11
  91. package/src/databases/engines/mysql/auth.ts +2 -2
  92. package/src/databases/engines/mysql/connection.ts +5 -4
  93. package/src/databases/engines/mysql/getStructure.ts +61 -30
  94. package/src/databases/engines/mysql/placeholders.ts +84 -0
  95. package/src/databases/engines/mysql/query/index.ts +49 -4
  96. package/src/databases/engines/postgresql/query/index.ts +40 -2
  97. package/src/databases/sqlTypeUtils.ts +3 -1
  98. package/src/subscriptions/strategies/index.ts +1 -1
  99. package/src/subscriptions/strategies/queue.ts +1 -1
  100. package/src/types/resolver.ts +17 -1
  101. package/src/types/zod/configuration.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphoria/server",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "description": "Auto-generated GraphQL and REST APIs from database schema - Connect your database to modern APIs instantly",
5
5
  "keywords": [
6
6
  "auto-generated",
@@ -226,6 +226,37 @@ const resolveSessionInArguments = (
226
226
  return result;
227
227
  };
228
228
 
229
+ /**
230
+ * Recursively replaces `$varName` references with their resolved (already-flattened) values from
231
+ * `resolvedMap`, walking nested objects/arrays so a reference nested inside an inline argument
232
+ * object (e.g. `where: { rel: { sub: $where } }`) is substituted like a top-level one. The resolved
233
+ * value is inserted as-is — it is not walked further, so its own `$static_N` leaves survive for SQL
234
+ * parameter binding. Refs absent from `resolvedMap` (scalar vars, `$session.*`) pass through.
235
+ */
236
+ const substituteResolvedRefs = (value: unknown, resolvedMap: Map<string, unknown>): unknown => {
237
+ if (isString(value)) {
238
+ if (value.startsWith("$")) {
239
+ const varName = value.substring(1);
240
+ if (resolvedMap.has(varName)) return resolvedMap.get(varName);
241
+ }
242
+ return value;
243
+ }
244
+
245
+ if (Array.isArray(value)) {
246
+ return value.map((item) => substituteResolvedRefs(item, resolvedMap));
247
+ }
248
+
249
+ if (value !== null && typeof value === "object") {
250
+ const result: Record<string, unknown> = {};
251
+ for (const [key, nested] of Object.entries(value)) {
252
+ result[key] = substituteResolvedRefs(nested, resolvedMap);
253
+ }
254
+ return result;
255
+ }
256
+
257
+ return value;
258
+ };
259
+
229
260
  /**
230
261
  * Returns a new field tree with:
231
262
  * 1. `$varName` argument references replaced with their resolved objects from `resolvedMap`
@@ -245,13 +276,12 @@ export const resolveFieldArguments = (
245
276
  // Shallow-copy arguments so we don't mutate originals
246
277
  newArguments = { ...field.arguments };
247
278
 
248
- // Replace object variable references in arguments
249
- for (const [key, value] of Object.entries(newArguments)) {
250
- if (isString(value) && value.startsWith("$")) {
251
- const varName = value.substring(1);
252
- if (resolvedMap.has(varName)) {
253
- newArguments[key] = resolvedMap.get(varName);
254
- }
279
+ // Replace object variable references anywhere in arguments — top-level (`where: $where`) or
280
+ // nested inside an inline object (`where: { rel: { sub: $where } }`). Only walk when there is
281
+ // something to substitute so the session-only path stays a no-op.
282
+ if (resolvedMap.size > 0) {
283
+ for (const [key, value] of Object.entries(newArguments)) {
284
+ newArguments[key] = substituteResolvedRefs(value, resolvedMap);
255
285
  }
256
286
  }
257
287
 
@@ -1,10 +1,15 @@
1
+ import { print } from "graphql";
1
2
  import { z } from "zod";
2
3
 
4
+ import type { DocumentNode } from "graphql";
3
5
  import type {
6
+ AnyQueryDocument,
4
7
  DefaultInput,
5
8
  HandlerOperation,
6
9
  OperationHandler,
10
+ OperationOptions,
7
11
  QueryOperation,
12
+ QueryResultOf,
8
13
  TypedOperation,
9
14
  } from "../types/operation";
10
15
 
@@ -22,6 +27,70 @@ type InferInput<TInputSchema> = TInputSchema extends z.ZodType<infer T> ? T : De
22
27
  */
23
28
  type InferOutput<TOutputSchema> = TOutputSchema extends z.ZodType<infer T> ? T : unknown;
24
29
 
30
+ /**
31
+ * Infer the parsed shape of an optional REST parameter schema.
32
+ * Resolves to `undefined` when that source has no schema configured.
33
+ *
34
+ * Mirrors the helpers on the `OperationFn` authoring surface so both
35
+ * `operation()` entry points expose identical `beforeRequest` context types.
36
+ */
37
+ type InferRestParam<T> = T extends z.ZodType<infer O> ? O : undefined;
38
+
39
+ /**
40
+ * REST exposure config with per-source schema generics, so `pathParams`,
41
+ * `queryParams`, and `body` can be inferred into the `beforeRequest` context.
42
+ */
43
+ type RestConfigInput<
44
+ TPathParams extends z.ZodType | undefined,
45
+ TQueryParams extends z.ZodType | undefined,
46
+ TBody extends z.ZodType | undefined,
47
+ > = {
48
+ /** Route path (e.g. `/users/:id`) */
49
+ path: string;
50
+ /** HTTP method (defaults to GET) */
51
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
52
+ /** Zod schema for path parameters */
53
+ pathParams?: TPathParams;
54
+ /** Zod schema for query-string parameters */
55
+ queryParams?: TQueryParams;
56
+ /** Zod schema for the request body */
57
+ body?: TBody;
58
+ };
59
+
60
+ /**
61
+ * Hooks config where `beforeRequest` receives the merged `input` plus each REST
62
+ * source (`pathParams` / `queryParams` / `body`) typed from its schema, or
63
+ * `undefined` when that source's schema is omitted.
64
+ */
65
+ type OperationHooksConfig<
66
+ TInput,
67
+ TOutput,
68
+ TInitData,
69
+ TPathParams extends z.ZodType | undefined,
70
+ TQueryParams extends z.ZodType | undefined,
71
+ TBody extends z.ZodType | undefined,
72
+ TAfterInput = TOutput,
73
+ > = {
74
+ init?: (options: OperationOptions) => TInitData | Promise<TInitData>;
75
+ beforeRequest?: (
76
+ context: {
77
+ input: TInput;
78
+ pathParams: InferRestParam<TPathParams>;
79
+ queryParams: InferRestParam<TQueryParams>;
80
+ body: InferRestParam<TBody>;
81
+ },
82
+ initData: TInitData | undefined,
83
+ ) => Record<string, unknown> | Promise<Record<string, unknown>>;
84
+ /**
85
+ * Transforms the result after a successful query/handler. `output` is the
86
+ * upstream value: for query operations the query result (typed from a gql.tada
87
+ * `query` document, otherwise `unknown`); for handler operations the handler's
88
+ * return. The value you return becomes the response payload, typed by the
89
+ * operation's `output` schema.
90
+ */
91
+ afterRequest?: (context: { output: TAfterInput }) => TOutput | Promise<TOutput>;
92
+ };
93
+
25
94
  /**
26
95
  * Config type for handler-based operations with inference
27
96
  */
@@ -30,13 +99,25 @@ type HandlerOperationConfig<
30
99
  TInputSchema extends z.ZodType | undefined,
31
100
  TOutputSchema extends z.ZodType | undefined,
32
101
  TInitData,
102
+ TPathParams extends z.ZodType | undefined,
103
+ TQueryParams extends z.ZodType | undefined,
104
+ TBody extends z.ZodType | undefined,
33
105
  > = Omit<
34
106
  HandlerOperation<InferInput<TInputSchema>, InferOutput<TOutputSchema>, TInitData, TRepository>,
35
- "handler" | "input" | "output"
107
+ "handler" | "input" | "output" | "hooks" | "rest"
36
108
  > & {
37
109
  input?: TInputSchema;
38
110
  output?: TOutputSchema;
39
111
  handler: OperationHandler<InferInput<TInputSchema>, InferOutput<TOutputSchema>, TRepository>;
112
+ hooks?: OperationHooksConfig<
113
+ InferInput<TInputSchema>,
114
+ InferOutput<TOutputSchema>,
115
+ TInitData,
116
+ TPathParams,
117
+ TQueryParams,
118
+ TBody
119
+ >;
120
+ rest?: RestConfigInput<TPathParams, TQueryParams, TBody>;
40
121
  };
41
122
 
42
123
  /**
@@ -46,12 +127,28 @@ type QueryOperationConfig<
46
127
  TInputSchema extends z.ZodType | undefined,
47
128
  TOutputSchema extends z.ZodType | undefined,
48
129
  TInitData,
130
+ TPathParams extends z.ZodType | undefined,
131
+ TQueryParams extends z.ZodType | undefined,
132
+ TBody extends z.ZodType | undefined,
133
+ TQuery extends string | AnyQueryDocument,
49
134
  > = Omit<
50
135
  QueryOperation<InferInput<TInputSchema>, InferOutput<TOutputSchema>, TInitData>,
51
- "input" | "output"
136
+ "input" | "output" | "hooks" | "rest" | "query"
52
137
  > & {
138
+ /** GraphQL query to execute (a string or a gql.tada `graphql()` document) */
139
+ query: TQuery;
53
140
  input?: TInputSchema;
54
141
  output?: TOutputSchema;
142
+ hooks?: OperationHooksConfig<
143
+ InferInput<TInputSchema>,
144
+ InferOutput<TOutputSchema>,
145
+ TInitData,
146
+ TPathParams,
147
+ TQueryParams,
148
+ TBody,
149
+ QueryResultOf<TQuery>
150
+ >;
151
+ rest?: RestConfigInput<TPathParams, TQueryParams, TBody>;
55
152
  };
56
153
 
57
154
  // Overload 1: Query-based operation (has `query`, no `handler`)
@@ -59,8 +156,20 @@ export function operation<
59
156
  TInputSchema extends z.ZodType | undefined = undefined,
60
157
  TOutputSchema extends z.ZodType | undefined = undefined,
61
158
  TInitData = unknown,
159
+ TPathParams extends z.ZodType | undefined = undefined,
160
+ TQueryParams extends z.ZodType | undefined = undefined,
161
+ TBody extends z.ZodType | undefined = undefined,
162
+ TQuery extends string | AnyQueryDocument = string,
62
163
  >(
63
- config: QueryOperationConfig<TInputSchema, TOutputSchema, TInitData>,
164
+ config: QueryOperationConfig<
165
+ TInputSchema,
166
+ TOutputSchema,
167
+ TInitData,
168
+ TPathParams,
169
+ TQueryParams,
170
+ TBody,
171
+ TQuery
172
+ >,
64
173
  ): QueryOperation<InferInput<TInputSchema>, InferOutput<TOutputSchema>, TInitData>;
65
174
 
66
175
  // Overload 2: Handler-based operation without custom repository type (no generic provided)
@@ -68,12 +177,33 @@ export function operation<
68
177
  TInputSchema extends z.ZodType | undefined = undefined,
69
178
  TOutputSchema extends z.ZodType | undefined = undefined,
70
179
  TInitData = unknown,
180
+ TPathParams extends z.ZodType | undefined = undefined,
181
+ TQueryParams extends z.ZodType | undefined = undefined,
182
+ TBody extends z.ZodType | undefined = undefined,
71
183
  >(
72
- config: HandlerOperationConfig<unknown, TInputSchema, TOutputSchema, TInitData>,
184
+ config: HandlerOperationConfig<
185
+ unknown,
186
+ TInputSchema,
187
+ TOutputSchema,
188
+ TInitData,
189
+ TPathParams,
190
+ TQueryParams,
191
+ TBody
192
+ >,
73
193
  ): HandlerOperation<InferInput<TInputSchema>, InferOutput<TOutputSchema>, TInitData, unknown>;
74
194
 
75
195
  // Implementation
76
196
  export function operation(config: unknown): unknown {
197
+ // gql.tada's `graphql()` yields a DocumentNode; normalize it to a string so
198
+ // the rest of the pipeline (validation, analyzeQuery) keeps working on strings.
199
+ const query = (config as { query?: unknown } | null | undefined)?.query;
200
+ if (
201
+ query != null &&
202
+ typeof query === "object" &&
203
+ (query as { kind?: unknown }).kind === "Document"
204
+ ) {
205
+ return { ...(config as object), query: print(query as DocumentNode) };
206
+ }
77
207
  return config;
78
208
  }
79
209
 
@@ -106,8 +236,19 @@ operation.typed = <TRepository>() => {
106
236
  TInputSchema extends z.ZodType | undefined = undefined,
107
237
  TOutputSchema extends z.ZodType | undefined = undefined,
108
238
  TInitData = unknown,
239
+ TPathParams extends z.ZodType | undefined = undefined,
240
+ TQueryParams extends z.ZodType | undefined = undefined,
241
+ TBody extends z.ZodType | undefined = undefined,
109
242
  >(
110
- config: HandlerOperationConfig<TRepository, TInputSchema, TOutputSchema, TInitData>,
243
+ config: HandlerOperationConfig<
244
+ TRepository,
245
+ TInputSchema,
246
+ TOutputSchema,
247
+ TInitData,
248
+ TPathParams,
249
+ TQueryParams,
250
+ TBody
251
+ >,
111
252
  ): HandlerOperation<
112
253
  InferInput<TInputSchema>,
113
254
  InferOutput<TOutputSchema>,
@@ -22,6 +22,7 @@
22
22
  import { z } from "zod";
23
23
 
24
24
  import type {
25
+ AnyQueryDocument,
25
26
  ConfigurationInput,
26
27
  CreateOneToBooleanMSSQLFn,
27
28
  CreateYAndNToBooleanMSSQLFn,
@@ -30,7 +31,7 @@ import type {
30
31
  OperationGraphQLConfig,
31
32
  OperationHandler,
32
33
  OperationOptions,
33
- OperationRestConfig,
34
+ QueryResultOf,
34
35
  TypedOperation,
35
36
  VirtualColumnExpressionFn,
36
37
  VirtualColumnFunctionFn,
@@ -52,6 +53,67 @@ type InferZodOutput<T> = T extends z.ZodType<infer O, any, any> ? O : never;
52
53
  type InferOutput<TOutputSchema> =
53
54
  TOutputSchema extends z.ZodType<any> ? InferZodOutput<TOutputSchema> : unknown;
54
55
 
56
+ /**
57
+ * Infer the parsed shape of an optional REST parameter schema.
58
+ * Resolves to `undefined` when that source has no schema configured.
59
+ */
60
+ type InferRestParam<T> = T extends z.ZodType<any> ? z.infer<T> : undefined;
61
+
62
+ /**
63
+ * REST exposure config with per-source schema generics, so `pathParams`,
64
+ * `queryParams`, and `body` can be inferred into the `beforeRequest` context.
65
+ */
66
+ type RestConfigInput<
67
+ TPathParams extends z.ZodType<any> | undefined,
68
+ TQueryParams extends z.ZodType<any> | undefined,
69
+ TBody extends z.ZodType<any> | undefined,
70
+ > = {
71
+ /** Route path (e.g. `/users/:id`) */
72
+ path: string;
73
+ /** HTTP method (defaults to GET) */
74
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
75
+ /** Zod schema for path parameters */
76
+ pathParams?: TPathParams;
77
+ /** Zod schema for query-string parameters */
78
+ queryParams?: TQueryParams;
79
+ /** Zod schema for the request body */
80
+ body?: TBody;
81
+ };
82
+
83
+ /**
84
+ * Hooks config shared by every operation overload. `beforeRequest` receives the
85
+ * merged `input` plus each REST source (`pathParams` / `queryParams` / `body`)
86
+ * typed from its schema, or `undefined` when that source's schema is omitted.
87
+ */
88
+ type OperationHooks<
89
+ TInput,
90
+ TOutput,
91
+ TInitData,
92
+ TPathParams extends z.ZodType<any> | undefined,
93
+ TQueryParams extends z.ZodType<any> | undefined,
94
+ TBody extends z.ZodType<any> | undefined,
95
+ TAfterInput = TOutput,
96
+ > = {
97
+ init?: (options: OperationOptions) => TInitData | Promise<TInitData>;
98
+ beforeRequest?: (
99
+ context: {
100
+ input: TInput;
101
+ pathParams: InferRestParam<TPathParams>;
102
+ queryParams: InferRestParam<TQueryParams>;
103
+ body: InferRestParam<TBody>;
104
+ },
105
+ initData: TInitData | undefined,
106
+ ) => Record<string, unknown> | Promise<Record<string, unknown>>;
107
+ /**
108
+ * Transforms the result after a successful query/handler. `output` is the
109
+ * upstream value: for query operations the query result (typed from a gql.tada
110
+ * `query` document, otherwise `unknown`); for handler operations the handler's
111
+ * return. The value you return becomes the response payload, typed by the
112
+ * operation's `output` schema.
113
+ */
114
+ afterRequest?: (context: { output: TAfterInput }) => TOutput | Promise<TOutput>;
115
+ };
116
+
55
117
  /**
56
118
  * Type for the operation helper function provided by @graphoria/server.
57
119
  *
@@ -64,9 +126,13 @@ export type OperationFn = {
64
126
  TInputSchema extends z.ZodObject<any>,
65
127
  TOutputSchema extends z.ZodType<any> | undefined = undefined,
66
128
  TInitData = unknown,
129
+ TPathParams extends z.ZodType<any> | undefined = undefined,
130
+ TQueryParams extends z.ZodType<any> | undefined = undefined,
131
+ TBody extends z.ZodType<any> | undefined = undefined,
132
+ TQuery extends string | AnyQueryDocument = string,
67
133
  >(config: {
68
- /** GraphQL query to execute */
69
- query: string;
134
+ /** GraphQL query to execute (a string or a gql.tada `graphql()` document) */
135
+ query: TQuery;
70
136
  /** Custom handler is not allowed with query */
71
137
  handler?: never;
72
138
  /** Description for documentation */
@@ -76,18 +142,17 @@ export type OperationFn = {
76
142
  /** Output schema (Zod) */
77
143
  output?: TOutputSchema;
78
144
  /** Hooks for initialization and request transformation */
79
- hooks?: {
80
- init?: (options: OperationOptions) => TInitData | Promise<TInitData>;
81
- beforeRequest?: (
82
- context: { input: z.infer<TInputSchema> },
83
- initData: TInitData | undefined,
84
- ) => Record<string, unknown> | Promise<Record<string, unknown>>;
85
- afterRequest?: (context: {
86
- output: InferOutput<TOutputSchema>;
87
- }) => InferOutput<TOutputSchema> | Promise<InferOutput<TOutputSchema>>;
88
- };
145
+ hooks?: OperationHooks<
146
+ z.infer<TInputSchema>,
147
+ InferOutput<TOutputSchema>,
148
+ TInitData,
149
+ TPathParams,
150
+ TQueryParams,
151
+ TBody,
152
+ QueryResultOf<TQuery>
153
+ >;
89
154
  /** REST exposure configuration */
90
- rest?: OperationRestConfig;
155
+ rest?: RestConfigInput<TPathParams, TQueryParams, TBody>;
91
156
  /** GraphQL exposure configuration (enabled by default) */
92
157
  graphql?: OperationGraphQLConfig;
93
158
  /** Cache configuration */
@@ -95,9 +160,16 @@ export type OperationFn = {
95
160
  }): TypedOperation<z.infer<TInputSchema>, InferOutput<TOutputSchema>, TInitData, unknown>;
96
161
 
97
162
  // Query operation overload - without input schema
98
- <TOutputSchema extends z.ZodType<any> | undefined = undefined, TInitData = unknown>(config: {
99
- /** GraphQL query to execute */
100
- query: string;
163
+ <
164
+ TOutputSchema extends z.ZodType<any> | undefined = undefined,
165
+ TInitData = unknown,
166
+ TPathParams extends z.ZodType<any> | undefined = undefined,
167
+ TQueryParams extends z.ZodType<any> | undefined = undefined,
168
+ TBody extends z.ZodType<any> | undefined = undefined,
169
+ TQuery extends string | AnyQueryDocument = string,
170
+ >(config: {
171
+ /** GraphQL query to execute (a string or a gql.tada `graphql()` document) */
172
+ query: TQuery;
101
173
  /** Custom handler is not allowed with query */
102
174
  handler?: never;
103
175
  /** Description for documentation */
@@ -107,18 +179,17 @@ export type OperationFn = {
107
179
  /** Output schema (Zod) */
108
180
  output?: TOutputSchema;
109
181
  /** Hooks for initialization and request transformation */
110
- hooks?: {
111
- init?: (options: OperationOptions) => TInitData | Promise<TInitData>;
112
- beforeRequest?: (
113
- context: { input: DefaultInput },
114
- initData: TInitData | undefined,
115
- ) => Record<string, unknown> | Promise<Record<string, unknown>>;
116
- afterRequest?: (context: {
117
- output: InferOutput<TOutputSchema>;
118
- }) => InferOutput<TOutputSchema> | Promise<InferOutput<TOutputSchema>>;
119
- };
182
+ hooks?: OperationHooks<
183
+ DefaultInput,
184
+ InferOutput<TOutputSchema>,
185
+ TInitData,
186
+ TPathParams,
187
+ TQueryParams,
188
+ TBody,
189
+ QueryResultOf<TQuery>
190
+ >;
120
191
  /** REST exposure configuration */
121
- rest?: OperationRestConfig;
192
+ rest?: RestConfigInput<TPathParams, TQueryParams, TBody>;
122
193
  /** GraphQL exposure configuration (enabled by default) */
123
194
  graphql?: OperationGraphQLConfig;
124
195
  /** Cache configuration */
@@ -130,6 +201,9 @@ export type OperationFn = {
130
201
  TInputSchema extends z.ZodObject<any>,
131
202
  TOutputSchema extends z.ZodType<any> | undefined = undefined,
132
203
  TInitData = unknown,
204
+ TPathParams extends z.ZodType<any> | undefined = undefined,
205
+ TQueryParams extends z.ZodType<any> | undefined = undefined,
206
+ TBody extends z.ZodType<any> | undefined = undefined,
133
207
  >(config: {
134
208
  /** Query is not allowed with handler */
135
209
  query?: never;
@@ -142,18 +216,16 @@ export type OperationFn = {
142
216
  /** Output schema (Zod) */
143
217
  output?: TOutputSchema;
144
218
  /** Hooks for initialization and request transformation */
145
- hooks?: {
146
- init?: (options: OperationOptions) => TInitData | Promise<TInitData>;
147
- beforeRequest?: (
148
- context: { input: z.infer<TInputSchema> },
149
- initData: TInitData | undefined,
150
- ) => Record<string, unknown> | Promise<Record<string, unknown>>;
151
- afterRequest?: (context: {
152
- output: InferOutput<TOutputSchema>;
153
- }) => InferOutput<TOutputSchema> | Promise<InferOutput<TOutputSchema>>;
154
- };
219
+ hooks?: OperationHooks<
220
+ z.infer<TInputSchema>,
221
+ InferOutput<TOutputSchema>,
222
+ TInitData,
223
+ TPathParams,
224
+ TQueryParams,
225
+ TBody
226
+ >;
155
227
  /** REST exposure configuration */
156
- rest?: OperationRestConfig;
228
+ rest?: RestConfigInput<TPathParams, TQueryParams, TBody>;
157
229
  /** GraphQL exposure configuration (enabled by default) */
158
230
  graphql?: OperationGraphQLConfig;
159
231
  /** Cache configuration */
@@ -161,7 +233,13 @@ export type OperationFn = {
161
233
  }): TypedOperation<z.infer<TInputSchema>, InferOutput<TOutputSchema>, TInitData, unknown>;
162
234
 
163
235
  // Handler operation overload - without input schema
164
- <TOutputSchema extends z.ZodType<any> | undefined = undefined, TInitData = unknown>(config: {
236
+ <
237
+ TOutputSchema extends z.ZodType<any> | undefined = undefined,
238
+ TInitData = unknown,
239
+ TPathParams extends z.ZodType<any> | undefined = undefined,
240
+ TQueryParams extends z.ZodType<any> | undefined = undefined,
241
+ TBody extends z.ZodType<any> | undefined = undefined,
242
+ >(config: {
165
243
  /** Query is not allowed with handler */
166
244
  query?: never;
167
245
  /** Custom handler function */
@@ -173,18 +251,16 @@ export type OperationFn = {
173
251
  /** Output schema (Zod) */
174
252
  output?: TOutputSchema;
175
253
  /** Hooks for initialization and request transformation */
176
- hooks?: {
177
- init?: (options: OperationOptions) => TInitData | Promise<TInitData>;
178
- beforeRequest?: (
179
- context: { input: DefaultInput },
180
- initData: TInitData | undefined,
181
- ) => Record<string, unknown> | Promise<Record<string, unknown>>;
182
- afterRequest?: (context: {
183
- output: InferOutput<TOutputSchema>;
184
- }) => InferOutput<TOutputSchema> | Promise<InferOutput<TOutputSchema>>;
185
- };
254
+ hooks?: OperationHooks<
255
+ DefaultInput,
256
+ InferOutput<TOutputSchema>,
257
+ TInitData,
258
+ TPathParams,
259
+ TQueryParams,
260
+ TBody
261
+ >;
186
262
  /** REST exposure configuration */
187
- rest?: OperationRestConfig;
263
+ rest?: RestConfigInput<TPathParams, TQueryParams, TBody>;
188
264
  /** GraphQL exposure configuration (enabled by default) */
189
265
  graphql?: OperationGraphQLConfig;
190
266
  /** Cache configuration */
@@ -203,6 +279,9 @@ export type OperationFn = {
203
279
  TInputSchema extends z.ZodObject<any>,
204
280
  TOutputSchema extends z.ZodType<any> | undefined = undefined,
205
281
  TInitData = unknown,
282
+ TPathParams extends z.ZodType<any> | undefined = undefined,
283
+ TQueryParams extends z.ZodType<any> | undefined = undefined,
284
+ TBody extends z.ZodType<any> | undefined = undefined,
206
285
  >(config: {
207
286
  /** Query is not allowed with handler */
208
287
  query?: never;
@@ -215,18 +294,16 @@ export type OperationFn = {
215
294
  /** Output schema (Zod) */
216
295
  output?: TOutputSchema;
217
296
  /** Hooks for initialization and request transformation */
218
- hooks?: {
219
- init?: (options: OperationOptions) => TInitData | Promise<TInitData>;
220
- beforeRequest?: (
221
- context: { input: z.infer<TInputSchema> },
222
- initData: TInitData | undefined,
223
- ) => Record<string, unknown> | Promise<Record<string, unknown>>;
224
- afterRequest?: (context: {
225
- output: InferOutput<TOutputSchema>;
226
- }) => InferOutput<TOutputSchema> | Promise<InferOutput<TOutputSchema>>;
227
- };
297
+ hooks?: OperationHooks<
298
+ z.infer<TInputSchema>,
299
+ InferOutput<TOutputSchema>,
300
+ TInitData,
301
+ TPathParams,
302
+ TQueryParams,
303
+ TBody
304
+ >;
228
305
  /** REST exposure configuration */
229
- rest?: OperationRestConfig;
306
+ rest?: RestConfigInput<TPathParams, TQueryParams, TBody>;
230
307
  /** GraphQL exposure configuration (enabled by default) */
231
308
  graphql?: OperationGraphQLConfig;
232
309
  /** Cache configuration */
@@ -4,7 +4,7 @@ import { z } from "zod";
4
4
  * Model Context Protocol (MCP) server configuration, nested under `ai.mcp`.
5
5
  * When enabled, exposes a `/mcp` endpoint for LLM agents.
6
6
  */
7
- export const MCPZod = z.object({
7
+ export const MCPZod = z.strictObject({
8
8
  /** Whether the MCP server is enabled. Off by default. */
9
9
  enabled: z.boolean().default(false),
10
10
  });
@@ -20,7 +20,7 @@ export type MCPConfig = z.input<typeof MCPZod>;
20
20
  * (`LLM_PROVIDER`, `LLM_MODEL`, `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,
21
21
  * `DEEPSEEK_API_KEY`, `OLLAMA_HOST`), not from this config.
22
22
  */
23
- export const AIZod = z.object({
23
+ export const AIZod = z.strictObject({
24
24
  /** Whether the AI agent is enabled. Off by default. */
25
25
  enabled: z.boolean().default(false),
26
26
  /** Overrides the built-in system prompt sent to the LLM. */
@@ -28,18 +28,49 @@ export type DirectionUnion = z.input<typeof DirectionUnionZod>;
28
28
  /**
29
29
  * Order by clause for role-based default ordering
30
30
  */
31
- export const OrderByClauseZod = z.object({
31
+ export const OrderByClauseZod = z.strictObject({
32
32
  column: z.string(),
33
33
  direction: DirectionUnionZod,
34
34
  });
35
35
 
36
36
  export type OrderByClause = z.input<typeof OrderByClauseZod>;
37
37
 
38
+ /**
39
+ * Every operator the query builder implements. `databases/common.ts` imports this
40
+ * list rather than keeping its own: an operator that exists in one place and not
41
+ * the other is how a role filter ends up matching every row.
42
+ */
43
+ export const FILTER_OPERATORS = [
44
+ "eq",
45
+ "neq",
46
+ "gt",
47
+ "gte",
48
+ "lt",
49
+ "lte",
50
+ "like",
51
+ "in",
52
+ "between",
53
+ "is_null",
54
+ "is_not_null",
55
+ "not_null",
56
+ ] as const;
57
+
58
+ export type FilterOperator = (typeof FILTER_OPERATORS)[number];
59
+
38
60
  /**
39
61
  * Filter condition — matches GraphQL where argument structure.
40
- * Supports operators: eq, neq, gt, gte, lt, lte, like, in, is_null, not_null.
62
+ *
63
+ * A value is either a column filter (`{ eq: … }`) or a nested relation filter
64
+ * holding more of the same. Operators are constrained to the implemented set so
65
+ * a typo fails at boot instead of evaluating to no condition, which would hand
66
+ * the role the whole table.
41
67
  */
42
- export const FilterConditionZod = z.record(z.string(), z.record(z.string(), z.unknown()));
68
+ export const FilterConditionZod: z.ZodType<Record<string, unknown>> = z.lazy(() =>
69
+ z.record(
70
+ z.string(),
71
+ z.union([z.record(z.enum(FILTER_OPERATORS), z.unknown()), FilterConditionZod]),
72
+ ),
73
+ );
43
74
 
44
75
  export type FilterCondition = z.input<typeof FilterConditionZod>;
45
76
 
@@ -50,7 +81,7 @@ export type FilterCondition = z.input<typeof FilterConditionZod>;
50
81
  /**
51
82
  * Table-level permission configuration
52
83
  */
53
- export const TablePermissionZod = z.object({
84
+ export const TablePermissionZod = z.strictObject({
54
85
  /** Allowed columns — "ALL" or array of column names */
55
86
  columns: z.union([z.literal("ALL"), z.array(z.string())]),
56
87
  /** Role-based query filtering (WHERE clause) */
@@ -65,7 +96,7 @@ export type TablePermission = z.input<typeof TablePermissionZod>;
65
96
  * Permission configuration for a role
66
97
  */
67
98
  export const RolePermissionZod = z
68
- .object({
99
+ .strictObject({
69
100
  /** Tables accessible by this role */
70
101
  tables: z
71
102
  .union([
@@ -120,7 +151,7 @@ export type RolePermission = z.input<typeof RolePermissionZod>;
120
151
  * Authentication configuration
121
152
  */
122
153
  export const AuthConfigZod = z
123
- .object({
154
+ .strictObject({
124
155
  /** Whether authentication is enabled */
125
156
  enabled: z.boolean(),
126
157
  /** Database name where auth tables are stored */