@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.
- package/dist/src/analyzeQuery/resolveVariables.d.ts.map +1 -1
- package/dist/src/config/helpers/operationHelper.d.ts +60 -6
- package/dist/src/config/helpers/operationHelper.d.ts.map +1 -1
- package/dist/src/config/helpers/operationHelper.test.d.ts +2 -0
- package/dist/src/config/helpers/operationHelper.test.d.ts.map +1 -0
- package/dist/src/config/index.d.ts +65 -60
- package/dist/src/config/index.d.ts.map +1 -1
- package/dist/src/config/types/ai.d.ts +9 -3
- package/dist/src/config/types/ai.d.ts.map +1 -1
- package/dist/src/config/types/auth.d.ts +55 -15
- package/dist/src/config/types/auth.d.ts.map +1 -1
- package/dist/src/config/types/cron.d.ts +96 -2
- package/dist/src/config/types/cron.d.ts.map +1 -1
- package/dist/src/config/types/db.d.ts +102 -29
- package/dist/src/config/types/db.d.ts.map +1 -1
- package/dist/src/config/types/index.d.ts +1 -1
- package/dist/src/config/types/index.d.ts.map +1 -1
- package/dist/src/config/types/operation.d.ts +49 -17
- package/dist/src/config/types/operation.d.ts.map +1 -1
- package/dist/src/config/types/queue.d.ts +59 -12
- package/dist/src/config/types/queue.d.ts.map +1 -1
- package/dist/src/config/types/remote-rest.d.ts +10 -1
- package/dist/src/config/types/remote-rest.d.ts.map +1 -1
- package/dist/src/config/types/remote-schema.d.ts +13 -3
- package/dist/src/config/types/remote-schema.d.ts.map +1 -1
- package/dist/src/config/types/virtual-columns.d.ts +1 -1
- package/dist/src/config/types/virtual-columns.d.ts.map +1 -1
- package/dist/src/configuration/getSchemas/index.d.ts +4 -4
- package/dist/src/configuration/getSchemas/mergeEntities/index.d.ts +2 -2
- package/dist/src/configuration/getSchemas/mergeEntities/index.d.ts.map +1 -1
- package/dist/src/configuration/getSchemas/mergeEntities/index.test.d.ts +2 -0
- package/dist/src/configuration/getSchemas/mergeEntities/index.test.d.ts.map +1 -0
- package/dist/src/configuration/getSchemas/type-definition-generator/index.d.ts.map +1 -1
- package/dist/src/configuration/index.d.ts +4 -4
- package/dist/src/configuration/rest/generateOpenAPI.d.ts.map +1 -1
- package/dist/src/configuration/rest/handleRESTRequestFactory.d.ts.map +1 -1
- package/dist/src/databases/common.d.ts +3 -1
- package/dist/src/databases/common.d.ts.map +1 -1
- package/dist/src/databases/core/query-builder.d.ts +1 -1
- package/dist/src/databases/directives.d.ts.map +1 -1
- package/dist/src/databases/engines/mssql/connection.d.ts +1 -1
- package/dist/src/databases/engines/mssql/connection.d.ts.map +1 -1
- package/dist/src/databases/engines/mssql/query/index.d.ts.map +1 -1
- package/dist/src/databases/engines/mysql/connection.d.ts.map +1 -1
- package/dist/src/databases/engines/mysql/getStructure.d.ts.map +1 -1
- package/dist/src/databases/engines/mysql/placeholders.d.ts +22 -0
- package/dist/src/databases/engines/mysql/placeholders.d.ts.map +1 -0
- package/dist/src/databases/engines/mysql/placeholders.test.d.ts +2 -0
- package/dist/src/databases/engines/mysql/placeholders.test.d.ts.map +1 -0
- package/dist/src/databases/engines/mysql/query/index.d.ts.map +1 -1
- package/dist/src/databases/engines/postgresql/query/index.d.ts.map +1 -1
- package/dist/src/databases/high-level-operations.d.ts +1 -1
- package/dist/src/databases/sqlTypeUtils.d.ts.map +1 -1
- package/dist/src/databases/transformers/data-transformers.d.ts +2 -2
- package/dist/src/singletons/env.d.ts +3 -3
- package/dist/src/types/env.d.ts +52 -52
- package/dist/src/types/resolver.d.ts +12 -2
- package/dist/src/types/resolver.d.ts.map +1 -1
- package/dist/src/types/zod/auth.d.ts +6 -6
- package/dist/src/types/zod/configuration.d.ts +46 -46
- package/dist/src/types/zod/configuration.d.ts.map +1 -1
- package/dist/src/types/zod/cron.d.ts +1 -1
- package/dist/src/types/zod/cron.d.ts.map +1 -1
- package/dist/src/types/zod/db.d.ts +12 -12
- package/dist/src/types/zod/queue.d.ts +26 -26
- package/dist/src/types/zod/strict-keys.test.d.ts +2 -0
- package/dist/src/types/zod/strict-keys.test.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/analyzeQuery/resolveVariables.ts +37 -7
- package/src/config/helpers/operationHelper.ts +146 -5
- package/src/config/index.ts +139 -62
- package/src/config/types/ai.ts +2 -2
- package/src/config/types/auth.ts +37 -6
- package/src/config/types/cron.ts +1 -1
- package/src/config/types/db.ts +10 -10
- package/src/config/types/index.ts +2 -0
- package/src/config/types/operation.ts +49 -9
- package/src/config/types/queue.ts +8 -8
- package/src/config/types/remote-rest.ts +1 -1
- package/src/config/types/remote-schema.ts +2 -2
- package/src/config/types/virtual-columns.ts +1 -1
- package/src/configuration/getSchemas/mergeEntities/index.ts +6 -0
- package/src/configuration/getSchemas/type-definition-generator/index.ts +13 -4
- package/src/configuration/rest/generateOpenAPI.ts +5 -9
- package/src/configuration/rest/handleRESTRequestFactory.ts +40 -14
- package/src/databases/common.ts +151 -65
- package/src/databases/directives.ts +26 -8
- package/src/databases/engines/mssql/connection.ts +1 -1
- package/src/databases/engines/mssql/query/index.ts +29 -11
- package/src/databases/engines/mysql/auth.ts +2 -2
- package/src/databases/engines/mysql/connection.ts +5 -4
- package/src/databases/engines/mysql/getStructure.ts +61 -30
- package/src/databases/engines/mysql/placeholders.ts +84 -0
- package/src/databases/engines/mysql/query/index.ts +49 -4
- package/src/databases/engines/postgresql/query/index.ts +40 -2
- package/src/databases/sqlTypeUtils.ts +3 -1
- package/src/subscriptions/strategies/index.ts +1 -1
- package/src/subscriptions/strategies/queue.ts +1 -1
- package/src/types/resolver.ts +17 -1
- package/src/types/zod/configuration.ts +1 -1
package/src/config/types/cron.ts
CHANGED
|
@@ -42,7 +42,7 @@ export type CronTickCallback<TVariables = Record<string, unknown>> = (
|
|
|
42
42
|
* This allows TypeScript to infer the variables type
|
|
43
43
|
*/
|
|
44
44
|
export const createTypedCronJobZod = <TVariables extends DefaultVariables>() =>
|
|
45
|
-
z.
|
|
45
|
+
z.strictObject({
|
|
46
46
|
/**
|
|
47
47
|
* Unique name/identifier for the cron job
|
|
48
48
|
*/
|
package/src/config/types/db.ts
CHANGED
|
@@ -42,7 +42,7 @@ export const RELATIONSHIP_CONDITION_OPERATORS = [
|
|
|
42
42
|
* A `value` is required for every operator except `is_null` / `is_not_null`.
|
|
43
43
|
*/
|
|
44
44
|
export const RelationshipConditionZod = z
|
|
45
|
-
.
|
|
45
|
+
.strictObject({
|
|
46
46
|
/** Column on the declaring (source) table */
|
|
47
47
|
source: z.string().optional(),
|
|
48
48
|
/** Column on the referenced (target) table */
|
|
@@ -67,10 +67,10 @@ export type RelationshipCondition = z.input<typeof RelationshipConditionZod>;
|
|
|
67
67
|
// Table Relationship (config shape, no transform)
|
|
68
68
|
// ============================================================================
|
|
69
69
|
|
|
70
|
-
export const TableRelationshipZod = z.
|
|
70
|
+
export const TableRelationshipZod = z.strictObject({
|
|
71
71
|
schema: z.string(),
|
|
72
72
|
name: z.string(),
|
|
73
|
-
columns: z.array(z.
|
|
73
|
+
columns: z.array(z.strictObject({ source: z.string(), target: z.string() })),
|
|
74
74
|
/** Static-value predicates ANDed into the JOIN condition */
|
|
75
75
|
conditions: z.array(RelationshipConditionZod).optional(),
|
|
76
76
|
});
|
|
@@ -81,7 +81,7 @@ export type TableRelationship = z.input<typeof TableRelationshipZod>;
|
|
|
81
81
|
// Table Schema Config
|
|
82
82
|
// ============================================================================
|
|
83
83
|
|
|
84
|
-
export const TableSchemaConfigZod = z.
|
|
84
|
+
export const TableSchemaConfigZod = z.strictObject({
|
|
85
85
|
columns: z.array(VirtualColumnZod).optional().default([]),
|
|
86
86
|
relationships: z.array(TableRelationshipZod).optional().default([]),
|
|
87
87
|
/** Overrides the table description from the database */
|
|
@@ -96,7 +96,7 @@ export type TableSchemaConfig = z.input<typeof TableSchemaConfigZod>;
|
|
|
96
96
|
// Database Schema Config
|
|
97
97
|
// ============================================================================
|
|
98
98
|
|
|
99
|
-
export const DatabaseSchemaConfigZod = z.
|
|
99
|
+
export const DatabaseSchemaConfigZod = z.strictObject({
|
|
100
100
|
database: z
|
|
101
101
|
.record(
|
|
102
102
|
z.string(),
|
|
@@ -127,7 +127,7 @@ export type DatabaseSchemaConfig = z.input<typeof DatabaseSchemaConfigZod>;
|
|
|
127
127
|
* Connection pool and transport options for PostgreSQL and MySQL (Bun SQL).
|
|
128
128
|
* All timeout values are in seconds.
|
|
129
129
|
*/
|
|
130
|
-
export const BunSQLConnectionOptionsZod = z.
|
|
130
|
+
export const BunSQLConnectionOptionsZod = z.strictObject({
|
|
131
131
|
/** Maximum number of connections in the pool */
|
|
132
132
|
max: z.number().int().positive().default(10),
|
|
133
133
|
/** Maximum time in seconds a connection can be idle before being closed */
|
|
@@ -150,10 +150,10 @@ export type BunSQLConnectionOptions = z.input<typeof BunSQLConnectionOptionsZod>
|
|
|
150
150
|
* Connection pool and transport options for MSSQL.
|
|
151
151
|
* All timeout values are in seconds (converted to milliseconds internally).
|
|
152
152
|
*/
|
|
153
|
-
export const MSSQLConnectionOptionsZod = z.
|
|
153
|
+
export const MSSQLConnectionOptionsZod = z.strictObject({
|
|
154
154
|
/** Connection pool options */
|
|
155
155
|
pool: z
|
|
156
|
-
.
|
|
156
|
+
.strictObject({
|
|
157
157
|
/** Maximum number of connections in the pool */
|
|
158
158
|
max: z.number().int().positive().default(10),
|
|
159
159
|
/** Minimum number of connections in the pool */
|
|
@@ -228,7 +228,7 @@ export type ConnectionOptionsForType<T extends DatabaseType> = T extends "pg" |
|
|
|
228
228
|
// Database Connection Zod Schema
|
|
229
229
|
// ============================================================================
|
|
230
230
|
|
|
231
|
-
export const DatabaseConnectionZod = z.
|
|
231
|
+
export const DatabaseConnectionZod = z.strictObject({
|
|
232
232
|
/** Unique name for the database connection */
|
|
233
233
|
name: z.string(),
|
|
234
234
|
/** Whether the database is enabled */
|
|
@@ -236,7 +236,7 @@ export const DatabaseConnectionZod = z.object({
|
|
|
236
236
|
/** Database type */
|
|
237
237
|
type: z.union([z.literal("mssql"), z.literal("pg"), z.literal("mysql")]),
|
|
238
238
|
/** Connection configuration */
|
|
239
|
-
connection: z.
|
|
239
|
+
connection: z.strictObject({
|
|
240
240
|
host: z.string(),
|
|
241
241
|
port: z.number(),
|
|
242
242
|
user: z.string(),
|
|
@@ -5,6 +5,25 @@ import { z } from "zod";
|
|
|
5
5
|
*/
|
|
6
6
|
export type DefaultInput = Record<string, unknown>;
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* A gql.tada / `TypedDocumentNode` query document. Every GraphQL AST node has a
|
|
10
|
+
* `kind`, which distinguishes a document from a plain string query; documents
|
|
11
|
+
* additionally carry their result type on the `__apiType` phantom property.
|
|
12
|
+
*/
|
|
13
|
+
export type AnyQueryDocument = { readonly kind: string };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extracts a query document's result type (the GraphQL `data` payload) from its
|
|
17
|
+
* gql.tada / TypedDocumentNode `__apiType` phantom. A plain string query (or a
|
|
18
|
+
* document without embedded types) resolves to `unknown`.
|
|
19
|
+
*/
|
|
20
|
+
export type QueryResultOf<TQuery> = TQuery extends string
|
|
21
|
+
? unknown
|
|
22
|
+
: // oxlint-disable-next-line typescript/no-explicit-any
|
|
23
|
+
TQuery extends { __apiType?: (variables: any) => infer TResult }
|
|
24
|
+
? TResult
|
|
25
|
+
: unknown;
|
|
26
|
+
|
|
8
27
|
// ============================================================================
|
|
9
28
|
// Zod Schemas for Runtime Validation
|
|
10
29
|
// ============================================================================
|
|
@@ -12,7 +31,7 @@ export type DefaultInput = Record<string, unknown>;
|
|
|
12
31
|
/**
|
|
13
32
|
* Zod schema for REST configuration
|
|
14
33
|
*/
|
|
15
|
-
export const OperationRestConfigZod = z.
|
|
34
|
+
export const OperationRestConfigZod = z.strictObject({
|
|
16
35
|
path: z.string(),
|
|
17
36
|
method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET"),
|
|
18
37
|
pathParams: z.custom<z.ZodType>().optional(),
|
|
@@ -23,7 +42,7 @@ export const OperationRestConfigZod = z.object({
|
|
|
23
42
|
/**
|
|
24
43
|
* Zod schema for GraphQL configuration
|
|
25
44
|
*/
|
|
26
|
-
export const OperationGraphQLConfigZod = z.
|
|
45
|
+
export const OperationGraphQLConfigZod = z.strictObject({
|
|
27
46
|
enabled: z.boolean().optional().default(true),
|
|
28
47
|
name: z.string().optional(),
|
|
29
48
|
});
|
|
@@ -31,7 +50,7 @@ export const OperationGraphQLConfigZod = z.object({
|
|
|
31
50
|
/**
|
|
32
51
|
* Zod schema for cache configuration
|
|
33
52
|
*/
|
|
34
|
-
export const OperationCacheConfigZod = z.
|
|
53
|
+
export const OperationCacheConfigZod = z.strictObject({
|
|
35
54
|
max: z.number().int().positive().optional(),
|
|
36
55
|
maxSize: z.number().int().positive().optional(),
|
|
37
56
|
ttl: z.number().int().positive().optional(),
|
|
@@ -65,7 +84,7 @@ export type OperationCacheConfig = z.input<typeof OperationCacheConfigZod>;
|
|
|
65
84
|
// ============================================================================
|
|
66
85
|
|
|
67
86
|
export const OperationZod = z
|
|
68
|
-
.
|
|
87
|
+
.strictObject({
|
|
69
88
|
description: z.string().optional(),
|
|
70
89
|
query: z.string().optional(),
|
|
71
90
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
@@ -75,7 +94,7 @@ export const OperationZod = z
|
|
|
75
94
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
76
95
|
output: z.custom<z.ZodType<any>>().optional(),
|
|
77
96
|
hooks: z
|
|
78
|
-
.
|
|
97
|
+
.strictObject({
|
|
79
98
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
80
99
|
init: z.custom<OperationInitHook<any>>().optional(),
|
|
81
100
|
beforeRequest: z
|
|
@@ -108,14 +127,30 @@ export const OperationsZod = z.record(z.string(), OperationZod);
|
|
|
108
127
|
export type Operations = z.input<typeof OperationsZod>;
|
|
109
128
|
|
|
110
129
|
/**
|
|
111
|
-
* Context passed to beforeRequest hook
|
|
130
|
+
* Context passed to beforeRequest hook.
|
|
131
|
+
*
|
|
132
|
+
* `input` is the merged view of every REST parameter source. `pathParams`,
|
|
133
|
+
* `queryParams`, and `body` expose each source separately (parsed with its own
|
|
134
|
+
* schema), or `undefined` when that source has no schema configured.
|
|
112
135
|
*/
|
|
113
|
-
export type BeforeRequestContext<
|
|
136
|
+
export type BeforeRequestContext<
|
|
137
|
+
TInput = DefaultInput,
|
|
138
|
+
TPathParams = unknown,
|
|
139
|
+
TQueryParams = unknown,
|
|
140
|
+
TBody = unknown,
|
|
141
|
+
> = {
|
|
114
142
|
input: TInput;
|
|
143
|
+
pathParams?: TPathParams;
|
|
144
|
+
queryParams?: TQueryParams;
|
|
145
|
+
body?: TBody;
|
|
115
146
|
};
|
|
116
147
|
|
|
117
148
|
/**
|
|
118
|
-
* Context passed to afterRequest hook
|
|
149
|
+
* Context passed to the afterRequest hook.
|
|
150
|
+
*
|
|
151
|
+
* `output` is the operation's output payload — for query-based operations the
|
|
152
|
+
* unwrapped GraphQL `data` (the hook's return replaces `data` in the response
|
|
153
|
+
* envelope); for handler-based operations the value the handler returned.
|
|
119
154
|
*/
|
|
120
155
|
export type AfterRequestContext<TOutput = unknown> = {
|
|
121
156
|
output: TOutput;
|
|
@@ -173,7 +208,12 @@ export type OperationBeforeRequestHook<
|
|
|
173
208
|
) => TVariables | Promise<TVariables>;
|
|
174
209
|
|
|
175
210
|
/**
|
|
176
|
-
* AfterRequest hook
|
|
211
|
+
* AfterRequest hook — transforms the output after a successful query/handler.
|
|
212
|
+
*
|
|
213
|
+
* Receives the output payload and returns the (possibly transformed) payload.
|
|
214
|
+
* Runs on success only; throw to turn a successful response into an error. For
|
|
215
|
+
* cached query operations it runs on the cache miss and the transformed result
|
|
216
|
+
* is cached, so cache hits reuse it without re-invoking the hook.
|
|
177
217
|
*/
|
|
178
218
|
export type OperationAfterRequestHook<TOutput = unknown> = (
|
|
179
219
|
context: AfterRequestContext<TOutput>,
|
|
@@ -10,7 +10,7 @@ import { z } from "zod";
|
|
|
10
10
|
/**
|
|
11
11
|
* Reconnection configuration
|
|
12
12
|
*/
|
|
13
|
-
export const ReconnectConfigZod = z.
|
|
13
|
+
export const ReconnectConfigZod = z.strictObject({
|
|
14
14
|
/** Initial delay before first reconnect attempt (ms) */
|
|
15
15
|
initialDelay: z.number().int().positive().default(1000),
|
|
16
16
|
/** Maximum delay between reconnect attempts (ms) */
|
|
@@ -26,7 +26,7 @@ export type ReconnectConfig = z.input<typeof ReconnectConfigZod>;
|
|
|
26
26
|
/**
|
|
27
27
|
* Publisher configuration — defines how to send messages
|
|
28
28
|
*/
|
|
29
|
-
export const PublisherConfigZod = z.
|
|
29
|
+
export const PublisherConfigZod = z.strictObject({
|
|
30
30
|
/** Topic/Exchange name to publish to */
|
|
31
31
|
topic: z.string(),
|
|
32
32
|
/** Routing key (RabbitMQ) or message key (Kafka) */
|
|
@@ -58,7 +58,7 @@ export type SubscriberHandler = (
|
|
|
58
58
|
/**
|
|
59
59
|
* Subscriber configuration — defines how to receive messages
|
|
60
60
|
*/
|
|
61
|
-
export const SubscriberConfigZod = z.
|
|
61
|
+
export const SubscriberConfigZod = z.strictObject({
|
|
62
62
|
/** Topic/Exchange name to subscribe to */
|
|
63
63
|
topic: z.string(),
|
|
64
64
|
/** Pattern for filtering messages (routing key pattern for RabbitMQ) */
|
|
@@ -80,7 +80,7 @@ export type SubscriberConfig = z.input<typeof SubscriberConfigZod>;
|
|
|
80
80
|
/**
|
|
81
81
|
* Topic/Exchange configuration — for auto-setup
|
|
82
82
|
*/
|
|
83
|
-
export const TopicConfigZod = z.
|
|
83
|
+
export const TopicConfigZod = z.strictObject({
|
|
84
84
|
/** Exchange type (RabbitMQ only) */
|
|
85
85
|
type: z.enum(["direct", "fanout", "topic", "headers"]).optional().default("topic"),
|
|
86
86
|
/** Whether exchange should survive broker restart */
|
|
@@ -94,7 +94,7 @@ export type TopicConfig = z.input<typeof TopicConfigZod>;
|
|
|
94
94
|
/**
|
|
95
95
|
* Base queue configuration shared by all providers
|
|
96
96
|
*/
|
|
97
|
-
export const BaseQueueConfigZod = z.
|
|
97
|
+
export const BaseQueueConfigZod = z.strictObject({
|
|
98
98
|
/** Unique name for this queue connection */
|
|
99
99
|
name: z.string(),
|
|
100
100
|
/** Whether to auto-create topics/exchanges/queues */
|
|
@@ -118,7 +118,7 @@ export const BaseQueueConfigZod = z.object({
|
|
|
118
118
|
/**
|
|
119
119
|
* RabbitMQ connection configuration (object form)
|
|
120
120
|
*/
|
|
121
|
-
export const RabbitMQConnectionZod = z.
|
|
121
|
+
export const RabbitMQConnectionZod = z.strictObject({
|
|
122
122
|
hostname: z.string().default("localhost"),
|
|
123
123
|
port: z.number().default(5672),
|
|
124
124
|
username: z.string().optional(),
|
|
@@ -131,7 +131,7 @@ export type RabbitMQConnection = z.input<typeof RabbitMQConnectionZod>;
|
|
|
131
131
|
/**
|
|
132
132
|
* Kafka connection configuration
|
|
133
133
|
*/
|
|
134
|
-
export const KafkaConnectionZod = z.
|
|
134
|
+
export const KafkaConnectionZod = z.strictObject({
|
|
135
135
|
/** Broker addresses — "host:port" or ["host1:port1", "host2:port2"] */
|
|
136
136
|
brokers: z.union([z.string(), z.array(z.string())]),
|
|
137
137
|
/** Client identifier sent to the broker (default: "datagraph-<queue name>") */
|
|
@@ -140,7 +140,7 @@ export const KafkaConnectionZod = z.object({
|
|
|
140
140
|
ssl: z.boolean().optional().default(false),
|
|
141
141
|
/** SASL authentication (optional) */
|
|
142
142
|
sasl: z
|
|
143
|
-
.
|
|
143
|
+
.strictObject({
|
|
144
144
|
mechanism: z.enum(["plain", "scram-sha-256", "scram-sha-512"]).optional().default("plain"),
|
|
145
145
|
username: z.string(),
|
|
146
146
|
password: z.string(),
|
|
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
/**
|
|
4
4
|
* Remote REST API configuration (OpenAPI-based)
|
|
5
5
|
*/
|
|
6
|
-
export const RemoteRESTConfigZod = z.
|
|
6
|
+
export const RemoteRESTConfigZod = z.strictObject({
|
|
7
7
|
/** Unique identifier for this remote REST API */
|
|
8
8
|
name: z.string().min(1),
|
|
9
9
|
/** Base URL of the remote API for proxying requests */
|
|
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
* Remote GraphQL schema introspection configuration
|
|
5
5
|
*/
|
|
6
6
|
export const RemoteSchemaIntrospectionConfigZod = z
|
|
7
|
-
.
|
|
7
|
+
.strictObject({
|
|
8
8
|
/** Re-introspect interval in ms (0 = only at startup, default: 0) */
|
|
9
9
|
interval: z.number().int().min(0).optional().default(0),
|
|
10
10
|
})
|
|
@@ -16,7 +16,7 @@ export type RemoteSchemaIntrospectionConfig = z.input<typeof RemoteSchemaIntrosp
|
|
|
16
16
|
/**
|
|
17
17
|
* Remote GraphQL schema configuration
|
|
18
18
|
*/
|
|
19
|
-
export const RemoteSchemaConfigZod = z.
|
|
19
|
+
export const RemoteSchemaConfigZod = z.strictObject({
|
|
20
20
|
/** Unique identifier for this remote schema */
|
|
21
21
|
name: z.string().min(1),
|
|
22
22
|
/** Remote GraphQL endpoint URL */
|
|
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
/**
|
|
4
4
|
* Virtual column schema for computed/virtual columns in database schema
|
|
5
5
|
*/
|
|
6
|
-
export const VirtualColumnZod = z.
|
|
6
|
+
export const VirtualColumnZod = z.strictObject({
|
|
7
7
|
name: z.string(),
|
|
8
8
|
dataType: z.string(),
|
|
9
9
|
isNullable: z.boolean(),
|
|
@@ -87,6 +87,12 @@ export const mergeEntities = (
|
|
|
87
87
|
acc[publisher.resolverName] = publisher;
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
|
+
// Subscribers become subscriptions (not mutations): register in the resolver
|
|
91
|
+
// registry only — not queuesMap — so getResolverSource routes them to the queue strategy.
|
|
92
|
+
queue.queues.forEach((subscriber) => {
|
|
93
|
+
resolverRegistry[`${queue.name}_${subscriber.name}`] =
|
|
94
|
+
createResolverEntry.queueSubscriber(subscriber);
|
|
95
|
+
});
|
|
90
96
|
return acc;
|
|
91
97
|
}, {});
|
|
92
98
|
|
|
@@ -143,7 +143,14 @@ export const generateOrderByInputType = (mergedEntities: MergedEntities) =>
|
|
|
143
143
|
),
|
|
144
144
|
);
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
// An average is fractional whatever the column holds, so it cannot inherit the
|
|
147
|
+
// column's type the way min/max/sum do.
|
|
148
|
+
const aggTypes: { name: string; graphQLType?: string }[] = [
|
|
149
|
+
{ name: "Min" },
|
|
150
|
+
{ name: "Max" },
|
|
151
|
+
{ name: "Sum" },
|
|
152
|
+
{ name: "Avg", graphQLType: "Float" },
|
|
153
|
+
];
|
|
147
154
|
|
|
148
155
|
// Generate aggregation types for each table
|
|
149
156
|
export const generateAggregationTypes = (mergedEntities: MergedEntities) => {
|
|
@@ -155,10 +162,10 @@ export const generateAggregationTypes = (mergedEntities: MergedEntities) => {
|
|
|
155
162
|
numericColumns.length
|
|
156
163
|
? aggTypes
|
|
157
164
|
.map(
|
|
158
|
-
(agg) =>
|
|
165
|
+
({ name: agg, graphQLType }) =>
|
|
159
166
|
`
|
|
160
167
|
type ${resolverName}${agg} {
|
|
161
|
-
${lj(numericColumns, ({ name }) => `${name}: ${mapSQLTypeToGraphQLType(columns.find((c) => c.name === name)?.dataType || "")}`)}
|
|
168
|
+
${lj(numericColumns, ({ name }) => `${name}: ${graphQLType ?? mapSQLTypeToGraphQLType(columns.find((c) => c.name === name)?.dataType || "")}`)}
|
|
162
169
|
}
|
|
163
170
|
`,
|
|
164
171
|
)
|
|
@@ -171,7 +178,9 @@ export const generateAggregationTypes = (mergedEntities: MergedEntities) => {
|
|
|
171
178
|
count: Int
|
|
172
179
|
${
|
|
173
180
|
numericColumns.length
|
|
174
|
-
? aggTypes
|
|
181
|
+
? aggTypes
|
|
182
|
+
.map(({ name: agg }) => `${agg.toLowerCase()}: ${resolverName}${agg}`)
|
|
183
|
+
.join("\n")
|
|
175
184
|
: ""
|
|
176
185
|
}
|
|
177
186
|
items: [${resolverName}]
|
|
@@ -189,23 +189,19 @@ export const generateOpenAPI = ({
|
|
|
189
189
|
parameters: [
|
|
190
190
|
...(Object.entries(
|
|
191
191
|
pathParametersPOJO.properties ?? ({} as OpenAPIV3_1.SchemaObject),
|
|
192
|
-
).map(([key,
|
|
192
|
+
).map(([key, paramSchema]) => ({
|
|
193
193
|
name: key,
|
|
194
194
|
in: "path",
|
|
195
195
|
required: true,
|
|
196
|
-
schema:
|
|
197
|
-
type,
|
|
198
|
-
},
|
|
196
|
+
schema: paramSchema,
|
|
199
197
|
})) ?? []),
|
|
200
198
|
...Object.entries(
|
|
201
199
|
queryParametersPOJO.properties ?? ({} as OpenAPIV3_1.SchemaObject),
|
|
202
|
-
).map(([key,
|
|
200
|
+
).map(([key, paramSchema]) => ({
|
|
203
201
|
name: key,
|
|
204
202
|
in: "query",
|
|
205
|
-
required,
|
|
206
|
-
schema:
|
|
207
|
-
type,
|
|
208
|
-
},
|
|
203
|
+
required: queryParametersPOJO.required?.includes(key) ?? false,
|
|
204
|
+
schema: paramSchema,
|
|
209
205
|
})),
|
|
210
206
|
],
|
|
211
207
|
...(hasBody && ["POST", "PUT", "PATCH"].includes(value.rest!.method!)
|
|
@@ -114,24 +114,28 @@ export const handleRESTRequestFactory = (
|
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
// Validate and parse
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
// Validate and parse each REST parameter source with Zod. A source is
|
|
118
|
+
// left `undefined` when its schema is not configured; that `undefined` is
|
|
119
|
+
// forwarded to `beforeRequest` so the hook can tell the sources apart,
|
|
120
|
+
// while `allVariables` stays identical because spreading `undefined` is a
|
|
121
|
+
// no-op.
|
|
122
|
+
const pathVariables = route.rest!.pathParams?.parse(pathParameters) as
|
|
123
|
+
| Record<string, unknown>
|
|
124
|
+
| undefined;
|
|
122
125
|
|
|
123
126
|
const paramsQuery = new URLSearchParams(url.search);
|
|
124
127
|
const paramsQueryDictionary = Object.fromEntries(paramsQuery.entries());
|
|
125
128
|
|
|
126
|
-
const queryVariables =
|
|
127
|
-
|
|
129
|
+
const queryVariables = route.rest!.queryParams?.parse(paramsQueryDictionary) as
|
|
130
|
+
| Record<string, unknown>
|
|
131
|
+
| undefined;
|
|
128
132
|
|
|
129
|
-
let bodyVariables: Record<string, unknown>
|
|
133
|
+
let bodyVariables: Record<string, unknown> | undefined;
|
|
130
134
|
|
|
131
135
|
if (req.method === "POST") {
|
|
132
136
|
const body = req.body ? await req.json() : {};
|
|
133
137
|
|
|
134
|
-
bodyVariables =
|
|
138
|
+
bodyVariables = route.rest!.body?.parse(body) as Record<string, unknown> | undefined;
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
// Prepare all variables for the request
|
|
@@ -150,6 +154,9 @@ export const handleRESTRequestFactory = (
|
|
|
150
154
|
(await route.hooks?.beforeRequest?.(
|
|
151
155
|
{
|
|
152
156
|
input: allVariables,
|
|
157
|
+
pathParams: pathVariables,
|
|
158
|
+
queryParams: queryVariables,
|
|
159
|
+
body: bodyVariables,
|
|
153
160
|
},
|
|
154
161
|
routesInitDataPromises[route.routeKey],
|
|
155
162
|
)) ?? allVariables;
|
|
@@ -286,6 +293,19 @@ export const handleRESTRequestFactory = (
|
|
|
286
293
|
}
|
|
287
294
|
}
|
|
288
295
|
|
|
296
|
+
// Apply the operation's afterRequest hook (if configured) to a GraphQL
|
|
297
|
+
// result. The hook receives the unwrapped `data` payload; its return
|
|
298
|
+
// replaces `data`, leaving the rest of the envelope (e.g. `sqlQueries`)
|
|
299
|
+
// intact.
|
|
300
|
+
const applyAfterRequest = async (result: Awaited<ReturnType<typeof gql.handler>>) => {
|
|
301
|
+
if (!route.hooks?.afterRequest) return result;
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
...result,
|
|
305
|
+
data: await route.hooks.afterRequest({ output: result.data }),
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
|
|
289
309
|
// Check if this route has caching enabled
|
|
290
310
|
const cache = route.routeKey ? getCache(route.routeKey) : undefined;
|
|
291
311
|
|
|
@@ -299,7 +319,8 @@ export const handleRESTRequestFactory = (
|
|
|
299
319
|
role: session?.role,
|
|
300
320
|
});
|
|
301
321
|
|
|
302
|
-
// Try to get from cache first
|
|
322
|
+
// Try to get from cache first. A cached entry has already been through
|
|
323
|
+
// afterRequest, so serve it directly without re-running the hook.
|
|
303
324
|
const cachedResult = await cache.get(cacheKey);
|
|
304
325
|
if (cachedResult) {
|
|
305
326
|
log.debug({ route: route.routeKey }, "rest cache hit");
|
|
@@ -308,10 +329,13 @@ export const handleRESTRequestFactory = (
|
|
|
308
329
|
log.debug({ route: route.routeKey }, "rest cache miss");
|
|
309
330
|
|
|
310
331
|
try {
|
|
311
|
-
// Execute the GraphQL request
|
|
312
|
-
|
|
332
|
+
// Execute the GraphQL request, then transform via afterRequest before
|
|
333
|
+
// caching so hits and misses return the same shape.
|
|
334
|
+
const result = await applyAfterRequest(
|
|
335
|
+
await gql.handler(queryAnalysis!, variables, req, session),
|
|
336
|
+
);
|
|
313
337
|
|
|
314
|
-
// Cache the result
|
|
338
|
+
// Cache the (already transformed) result
|
|
315
339
|
await cache.set(cacheKey, result);
|
|
316
340
|
|
|
317
341
|
return new S200(result);
|
|
@@ -321,7 +345,9 @@ export const handleRESTRequestFactory = (
|
|
|
321
345
|
}
|
|
322
346
|
} else if (queryAnalysis) {
|
|
323
347
|
// No caching for this route, execute normally
|
|
324
|
-
return new S200(
|
|
348
|
+
return new S200(
|
|
349
|
+
await applyAfterRequest(await gql.handler(queryAnalysis, variables, req, session)),
|
|
350
|
+
);
|
|
325
351
|
}
|
|
326
352
|
|
|
327
353
|
// Fallback - should not reach here if endpoint is properly configured
|