@graphoria/server 0.1.5 → 0.2.1
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/authentication/jwt.d.ts.map +1 -1
- package/dist/src/authentication/paseto.d.ts.map +1 -1
- package/dist/src/authentication/tokenRepository.d.ts +2 -1
- package/dist/src/authentication/tokenRepository.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 +61 -14
- 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 +6 -4
- package/dist/src/configuration/getSchemas/index.d.ts.map +1 -1
- package/dist/src/configuration/getSchemas/mergeEntities/index.d.ts +9 -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 +5 -4
- package/dist/src/configuration/index.d.ts.map +1 -1
- 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/configuration/rest/parseStringParams.d.ts +15 -0
- package/dist/src/configuration/rest/parseStringParams.d.ts.map +1 -0
- package/dist/src/configuration/rest/parseStringParams.test.d.ts +2 -0
- package/dist/src/configuration/rest/parseStringParams.test.d.ts.map +1 -0
- package/dist/src/databases/common.d.ts +9 -1
- package/dist/src/databases/common.d.ts.map +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/getStructure.d.ts +1 -0
- package/dist/src/databases/engines/mssql/getStructure.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 +1 -0
- 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/getStructure.d.ts +1 -0
- package/dist/src/databases/engines/postgresql/getStructure.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 -0
- package/dist/src/databases/high-level-operations.d.ts.map +1 -1
- package/dist/src/databases/identifierMapping.test.d.ts +2 -0
- package/dist/src/databases/identifierMapping.test.d.ts.map +1 -0
- package/dist/src/databases/metadata/structure.d.ts +1 -0
- package/dist/src/databases/metadata/structure.d.ts.map +1 -1
- package/dist/src/databases/sqlTypeUtils.d.ts.map +1 -1
- package/dist/src/databases/transformers/data-transformers.d.ts +1 -0
- package/dist/src/databases/transformers/data-transformers.d.ts.map +1 -1
- package/dist/src/databases/transformers/genResolverName.d.ts.map +1 -1
- package/dist/src/databases/transformers/graphqlName.d.ts +23 -0
- package/dist/src/databases/transformers/graphqlName.d.ts.map +1 -0
- package/dist/src/databases/transformers/graphqlName.test.d.ts +2 -0
- package/dist/src/databases/transformers/graphqlName.test.d.ts.map +1 -0
- package/dist/src/singletons/env.d.ts +2 -2
- package/dist/src/types/env.d.ts +56 -56
- 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 +52 -52
- 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 +21 -12
- package/dist/src/types/zod/db.d.ts.map +1 -1
- package/dist/src/types/zod/queue.d.ts +38 -38
- 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 +2 -2
- package/src/analyzeQuery/resolveVariables.ts +37 -7
- package/src/authentication/jwt.ts +3 -1
- package/src/authentication/paseto.ts +3 -1
- package/src/authentication/tokenRepository.ts +12 -4
- 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 +32 -1
- package/src/configuration/getSchemas/type-definition-generator/index.ts +23 -9
- package/src/configuration/rest/generateOpenAPI.ts +5 -9
- package/src/configuration/rest/handleRESTRequestFactory.ts +38 -14
- package/src/configuration/rest/parseStringParams.ts +92 -0
- package/src/databases/common.ts +163 -66
- package/src/databases/directives.ts +26 -8
- package/src/databases/engines/mssql/connection.ts +1 -1
- package/src/databases/engines/mssql/query/index.ts +47 -16
- 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 +68 -10
- package/src/databases/engines/postgresql/query/index.ts +59 -7
- package/src/databases/high-level-operations.ts +2 -2
- package/src/databases/sqlTypeUtils.ts +3 -1
- package/src/databases/transformers/genResolverName.ts +3 -3
- package/src/databases/transformers/graphqlName.ts +28 -0
- 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/types/zod/db.ts +4 -0
package/src/config/types/auth.ts
CHANGED
|
@@ -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.
|
|
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
|
-
*
|
|
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
|
|
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.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
154
|
+
.strictObject({
|
|
124
155
|
/** Whether authentication is enabled */
|
|
125
156
|
enabled: z.boolean(),
|
|
126
157
|
/** Database name where auth tables are stored */
|
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(),
|
|
@@ -6,6 +6,7 @@ import type { ResolverRegistry } from "../../../types/resolver";
|
|
|
6
6
|
import type { TypedOperation } from "../../../types/zod/operation";
|
|
7
7
|
|
|
8
8
|
import { EntitySource, createResolverEntry } from "../../../types/resolver";
|
|
9
|
+
import { columnFieldName } from "../../../databases/transformers/graphqlName";
|
|
9
10
|
import { SqlTypeCategory, categorizeSqlType } from "../../../databases/sqlTypeUtils";
|
|
10
11
|
|
|
11
12
|
const dataTypeToOpenApiType = (dataType: string): string => {
|
|
@@ -33,7 +34,23 @@ export const mergeEntities = (
|
|
|
33
34
|
const resolverRegistry: ResolverRegistry = {};
|
|
34
35
|
|
|
35
36
|
// Register tables (queries)
|
|
37
|
+
// Sanitisation is not injective — `categoría` and `categor_a` both produce
|
|
38
|
+
// `categoria` — so two tables can land on one resolver name. Serving one
|
|
39
|
+
// table's rows under the other's name is worse than not booting.
|
|
40
|
+
const sourceOfResolverName: Record<string, string> = {};
|
|
41
|
+
|
|
36
42
|
const queriesMap = entityOfRole.tables.reduce<Record<string, TableResolver>>((acc, table) => {
|
|
43
|
+
const source = `${table.schema}.${table.name}`;
|
|
44
|
+
const previousSource = sourceOfResolverName[table.resolverName];
|
|
45
|
+
|
|
46
|
+
if (previousSource) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Duplicate GraphQL name "${table.resolverName}": both ${previousSource} and ${source} map to it. Rename one of them, or exclude it with schema.excludedTables.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
sourceOfResolverName[table.resolverName] = source;
|
|
53
|
+
|
|
37
54
|
acc[table.resolverName] = table;
|
|
38
55
|
acc[`${table.resolverName}_single`] = table;
|
|
39
56
|
acc[`${table.resolverName}_aggregate`] = table;
|
|
@@ -87,6 +104,12 @@ export const mergeEntities = (
|
|
|
87
104
|
acc[publisher.resolverName] = publisher;
|
|
88
105
|
});
|
|
89
106
|
});
|
|
107
|
+
// Subscribers become subscriptions (not mutations): register in the resolver
|
|
108
|
+
// registry only — not queuesMap — so getResolverSource routes them to the queue strategy.
|
|
109
|
+
queue.queues.forEach((subscriber) => {
|
|
110
|
+
resolverRegistry[`${queue.name}_${subscriber.name}`] =
|
|
111
|
+
createResolverEntry.queueSubscriber(subscriber);
|
|
112
|
+
});
|
|
90
113
|
return acc;
|
|
91
114
|
}, {});
|
|
92
115
|
|
|
@@ -201,10 +224,18 @@ export const mergeEntities = (
|
|
|
201
224
|
},
|
|
202
225
|
isVirtualColumn: (table: string, column: string): VirtualColumn | undefined => {
|
|
203
226
|
const col = queriesMap[table]?.columns.find(
|
|
204
|
-
(c) => c
|
|
227
|
+
(c) => columnFieldName(c) === column && "virtual" in c && c.virtual,
|
|
205
228
|
);
|
|
206
229
|
return col ? (col as VirtualColumn) : undefined;
|
|
207
230
|
},
|
|
231
|
+
/**
|
|
232
|
+
* The SQL identifier behind a GraphQL field name. Columns whose name GraphQL
|
|
233
|
+
* cannot spell are exposed under a sanitised field name, so anything that
|
|
234
|
+
* emits SQL for a field the client named has to come back through here.
|
|
235
|
+
* Unknown names pass through: a legal name always sanitises to itself.
|
|
236
|
+
*/
|
|
237
|
+
columnSqlName: (table: string, fieldName: string): string =>
|
|
238
|
+
queriesMap[table]?.columns.find((c) => columnFieldName(c) === fieldName)?.name ?? fieldName,
|
|
208
239
|
getColumnTypeForOpenApi: (table: string, column: string) => {
|
|
209
240
|
const tableObj = queriesMap[table];
|
|
210
241
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OpenAPIV3_1 } from "openapi-types";
|
|
2
2
|
|
|
3
3
|
import { SqlTypeCategory, categorizeSqlType, isNumericType } from "../../../databases/sqlTypeUtils";
|
|
4
|
+
import { columnFieldName } from "../../../databases/transformers/graphqlName";
|
|
4
5
|
import { convertFromZod } from "../../rest/openApiErrors";
|
|
5
6
|
import { type MergedEntities } from "../mergeEntities";
|
|
6
7
|
|
|
@@ -91,8 +92,8 @@ export const generateTableType = (mergedEntities: MergedEntities) =>
|
|
|
91
92
|
${lj([
|
|
92
93
|
lj(
|
|
93
94
|
columns,
|
|
94
|
-
(
|
|
95
|
-
`${sdlDesc(description)}${
|
|
95
|
+
(column) =>
|
|
96
|
+
`${sdlDesc(column.description)}${columnFieldName(column)}: ${mapSQLTypeToGraphQLType(column.dataType)}${column.isNullable ? "" : "!"}`,
|
|
96
97
|
),
|
|
97
98
|
lj(
|
|
98
99
|
relationships,
|
|
@@ -115,7 +116,11 @@ export const generateWhereInputType = (mergedEntities: MergedEntities) =>
|
|
|
115
116
|
({ resolverName, columns, relationships, relationshipsReversed }) => `
|
|
116
117
|
input ${resolverName}WhereInput {
|
|
117
118
|
${lj([
|
|
118
|
-
lj(
|
|
119
|
+
lj(
|
|
120
|
+
columns,
|
|
121
|
+
(column) =>
|
|
122
|
+
`${columnFieldName(column)}: ${mapSQLTypeToConditionType(column.dataType)}`,
|
|
123
|
+
),
|
|
119
124
|
lj(
|
|
120
125
|
relationships,
|
|
121
126
|
({ toResolverName, toInternalName }) =>
|
|
@@ -138,12 +143,19 @@ export const generateOrderByInputType = (mergedEntities: MergedEntities) =>
|
|
|
138
143
|
mergedEntities.tables.map(
|
|
139
144
|
({ internalName, columns }) => `
|
|
140
145
|
input ${internalName}OrderByInput {
|
|
141
|
-
${lj(columns, (
|
|
146
|
+
${lj(columns, (column) => `${columnFieldName(column)}: OrderByEnum`)}
|
|
142
147
|
}`,
|
|
143
148
|
),
|
|
144
149
|
);
|
|
145
150
|
|
|
146
|
-
|
|
151
|
+
// An average is fractional whatever the column holds, so it cannot inherit the
|
|
152
|
+
// column's type the way min/max/sum do.
|
|
153
|
+
const aggTypes: { name: string; graphQLType?: string }[] = [
|
|
154
|
+
{ name: "Min" },
|
|
155
|
+
{ name: "Max" },
|
|
156
|
+
{ name: "Sum" },
|
|
157
|
+
{ name: "Avg", graphQLType: "Float" },
|
|
158
|
+
];
|
|
147
159
|
|
|
148
160
|
// Generate aggregation types for each table
|
|
149
161
|
export const generateAggregationTypes = (mergedEntities: MergedEntities) => {
|
|
@@ -155,10 +167,10 @@ export const generateAggregationTypes = (mergedEntities: MergedEntities) => {
|
|
|
155
167
|
numericColumns.length
|
|
156
168
|
? aggTypes
|
|
157
169
|
.map(
|
|
158
|
-
(agg) =>
|
|
170
|
+
({ name: agg, graphQLType }) =>
|
|
159
171
|
`
|
|
160
172
|
type ${resolverName}${agg} {
|
|
161
|
-
${lj(numericColumns, (
|
|
173
|
+
${lj(numericColumns, (column) => `${columnFieldName(column)}: ${graphQLType ?? mapSQLTypeToGraphQLType(column.dataType)}`)}
|
|
162
174
|
}
|
|
163
175
|
`,
|
|
164
176
|
)
|
|
@@ -171,14 +183,16 @@ export const generateAggregationTypes = (mergedEntities: MergedEntities) => {
|
|
|
171
183
|
count: Int
|
|
172
184
|
${
|
|
173
185
|
numericColumns.length
|
|
174
|
-
? aggTypes
|
|
186
|
+
? aggTypes
|
|
187
|
+
.map(({ name: agg }) => `${agg.toLowerCase()}: ${resolverName}${agg}`)
|
|
188
|
+
.join("\n")
|
|
175
189
|
: ""
|
|
176
190
|
}
|
|
177
191
|
items: [${resolverName}]
|
|
178
192
|
}
|
|
179
193
|
|
|
180
194
|
enum ${resolverName}GroupByKeys {
|
|
181
|
-
${columns.map(
|
|
195
|
+
${columns.map(columnFieldName).join("\n")}
|
|
182
196
|
}
|
|
183
197
|
`;
|
|
184
198
|
});
|
|
@@ -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!)
|