@graphoria/server 0.2.1 → 0.2.3

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 (175) hide show
  1. package/dist/src/ai/agent/providers/ollama.d.ts.map +1 -1
  2. package/dist/src/ai/mcp/create-server.d.ts.map +1 -1
  3. package/dist/src/ai/mcp/index.d.ts +5 -2
  4. package/dist/src/ai/mcp/index.d.ts.map +1 -1
  5. package/dist/src/ai/tools/agent.d.ts.map +1 -1
  6. package/dist/src/ai/tools/core.d.ts.map +1 -1
  7. package/dist/src/ai/tools/query-data.d.ts +2 -2
  8. package/dist/src/ai/tools/query-data.d.ts.map +1 -1
  9. package/dist/src/analyzeQuery/analyzers/fragmentAnalyzer.d.ts.map +1 -1
  10. package/dist/src/analyzeQuery/analyzers/operationAnalyzer.d.ts.map +1 -1
  11. package/dist/src/analyzeQuery/analyzers/selectionAnalyzer.d.ts.map +1 -1
  12. package/dist/src/analyzeQuery/analyzers/variableAnalyzer.d.ts.map +1 -1
  13. package/dist/src/analyzeQuery/depthLimit.d.ts.map +1 -1
  14. package/dist/src/analyzeQuery/directiveUtils.d.ts.map +1 -1
  15. package/dist/src/analyzeQuery/resolveVariables.d.ts +17 -2
  16. package/dist/src/analyzeQuery/resolveVariables.d.ts.map +1 -1
  17. package/dist/src/analyzeQuery/typeUtils.d.ts.map +1 -1
  18. package/dist/src/analyzeQuery/valueExtractors.d.ts.map +1 -1
  19. package/dist/src/authentication/duration.d.ts.map +1 -1
  20. package/dist/src/authentication/index.d.ts.map +1 -1
  21. package/dist/src/authentication/jwt.d.ts.map +1 -1
  22. package/dist/src/authentication/paseto.d.ts.map +1 -1
  23. package/dist/src/authentication/tokenRepository.d.ts.map +1 -1
  24. package/dist/src/cli/seedAuth.d.ts.map +1 -1
  25. package/dist/src/cli/seedAuthArgs.d.ts.map +1 -1
  26. package/dist/src/config/helpers/cronHelper.d.ts.map +1 -1
  27. package/dist/src/config/helpers/operationHelper.d.ts +1 -1
  28. package/dist/src/config/helpers/operationHelper.d.ts.map +1 -1
  29. package/dist/src/config/helpers/queueHelper.d.ts.map +1 -1
  30. package/dist/src/config/index.d.ts.map +1 -1
  31. package/dist/src/config/types/ai.d.ts +0 -6
  32. package/dist/src/config/types/ai.d.ts.map +1 -1
  33. package/dist/src/config/types/auth.d.ts +11 -34
  34. package/dist/src/config/types/auth.d.ts.map +1 -1
  35. package/dist/src/config/types/auth.test.d.ts +2 -0
  36. package/dist/src/config/types/auth.test.d.ts.map +1 -0
  37. package/dist/src/config/types/cron.d.ts +0 -94
  38. package/dist/src/config/types/cron.d.ts.map +1 -1
  39. package/dist/src/config/types/db.d.ts +22 -93
  40. package/dist/src/config/types/db.d.ts.map +1 -1
  41. package/dist/src/config/types/operation.d.ts +6 -6
  42. package/dist/src/config/types/queue.d.ts +4 -51
  43. package/dist/src/config/types/queue.d.ts.map +1 -1
  44. package/dist/src/config/types/remote-rest.d.ts +0 -9
  45. package/dist/src/config/types/remote-rest.d.ts.map +1 -1
  46. package/dist/src/config/types/remote-schema.d.ts +0 -10
  47. package/dist/src/config/types/remote-schema.d.ts.map +1 -1
  48. package/dist/src/configuration/getSchemas/index.d.ts +103 -99
  49. package/dist/src/configuration/getSchemas/index.d.ts.map +1 -1
  50. package/dist/src/configuration/getSchemas/mergeEntities/index.d.ts +50 -48
  51. package/dist/src/configuration/getSchemas/mergeEntities/index.d.ts.map +1 -1
  52. package/dist/src/configuration/getSchemas/type-definition-generator/index.d.ts.map +1 -1
  53. package/dist/src/configuration/gql/buildExecute.d.ts.map +1 -1
  54. package/dist/src/configuration/gql/gqlAuthOperations.d.ts.map +1 -1
  55. package/dist/src/configuration/gql/handleGraphQLRequestFactory.d.ts.map +1 -1
  56. package/dist/src/configuration/gql/handleGraphQLSubscriptionFactory.d.ts.map +1 -1
  57. package/dist/src/configuration/index.d.ts +95 -91
  58. package/dist/src/configuration/index.d.ts.map +1 -1
  59. package/dist/src/configuration/rest/generateOpenAPI.d.ts.map +1 -1
  60. package/dist/src/configuration/rest/handleRESTRequestFactory.d.ts.map +1 -1
  61. package/dist/src/configuration/rest/index.d.ts.map +1 -1
  62. package/dist/src/configuration/rest/openApiErrors.d.ts.map +1 -1
  63. package/dist/src/configuration/rest/parseStringParams.d.ts.map +1 -1
  64. package/dist/src/console/api.d.ts.map +1 -1
  65. package/dist/src/cron/index.d.ts +1 -1
  66. package/dist/src/cron/index.d.ts.map +1 -1
  67. package/dist/src/cron/index.test.d.ts +2 -0
  68. package/dist/src/cron/index.test.d.ts.map +1 -0
  69. package/dist/src/databases/auth/auth-operations.d.ts.map +1 -1
  70. package/dist/src/databases/auth/password.d.ts.map +1 -1
  71. package/dist/src/databases/common.d.ts +1 -3
  72. package/dist/src/databases/common.d.ts.map +1 -1
  73. package/dist/src/databases/core/executor.d.ts.map +1 -1
  74. package/dist/src/databases/core/identifier.d.ts.map +1 -1
  75. package/dist/src/databases/core/query-builder.d.ts +3 -2
  76. package/dist/src/databases/core/query-builder.d.ts.map +1 -1
  77. package/dist/src/databases/directives.d.ts.map +1 -1
  78. package/dist/src/databases/engines/mssql/auth.d.ts.map +1 -1
  79. package/dist/src/databases/engines/mssql/connection.d.ts.map +1 -1
  80. package/dist/src/databases/engines/mssql/format.d.ts.map +1 -1
  81. package/dist/src/databases/engines/mssql/getStructure.d.ts +12 -12
  82. package/dist/src/databases/engines/mssql/getStructure.d.ts.map +1 -1
  83. package/dist/src/databases/engines/mssql/getViews.d.ts.map +1 -1
  84. package/dist/src/databases/engines/mssql/query/index.d.ts.map +1 -1
  85. package/dist/src/databases/engines/mysql/auth.d.ts.map +1 -1
  86. package/dist/src/databases/engines/mysql/connection.d.ts.map +1 -1
  87. package/dist/src/databases/engines/mysql/format.d.ts.map +1 -1
  88. package/dist/src/databases/engines/mysql/getStructure.d.ts +12 -12
  89. package/dist/src/databases/engines/mysql/getStructure.d.ts.map +1 -1
  90. package/dist/src/databases/engines/mysql/getViews.d.ts.map +1 -1
  91. package/dist/src/databases/engines/mysql/placeholders.d.ts.map +1 -1
  92. package/dist/src/databases/engines/mysql/query/index.d.ts.map +1 -1
  93. package/dist/src/databases/engines/postgresql/auth.d.ts.map +1 -1
  94. package/dist/src/databases/engines/postgresql/connection.d.ts.map +1 -1
  95. package/dist/src/databases/engines/postgresql/format.d.ts.map +1 -1
  96. package/dist/src/databases/engines/postgresql/getStructure.d.ts +12 -12
  97. package/dist/src/databases/engines/postgresql/getStructure.d.ts.map +1 -1
  98. package/dist/src/databases/engines/postgresql/getViews.d.ts.map +1 -1
  99. package/dist/src/databases/engines/postgresql/query/index.d.ts.map +1 -1
  100. package/dist/src/databases/engines/shared/claims.d.ts.map +1 -1
  101. package/dist/src/databases/high-level-operations.d.ts +10 -59
  102. package/dist/src/databases/high-level-operations.d.ts.map +1 -1
  103. package/dist/src/databases/metadata/structure.d.ts +12 -12
  104. package/dist/src/databases/metadata/structure.d.ts.map +1 -1
  105. package/dist/src/databases/schemaBuilder/generateCreateTable/index.d.ts.map +1 -1
  106. package/dist/src/databases/schemaBuilder/generateCreateView/index.d.ts.map +1 -1
  107. package/dist/src/databases/schemaBuilder/generateInsert/index.d.ts.map +1 -1
  108. package/dist/src/databases/schemaBuilder/generateSchemaCreation/index.d.ts.map +1 -1
  109. package/dist/src/databases/schemaBuilder/mssqlToPostgres/convertView.d.ts.map +1 -1
  110. package/dist/src/databases/schemaBuilder/utils.d.ts +2 -2
  111. package/dist/src/databases/schemaBuilder/utils.d.ts.map +1 -1
  112. package/dist/src/databases/sqlTypeUtils.d.ts.map +1 -1
  113. package/dist/src/databases/transformers/data-transformers.d.ts +34 -32
  114. package/dist/src/databases/transformers/data-transformers.d.ts.map +1 -1
  115. package/dist/src/databases/transformers/genResolverName.d.ts.map +1 -1
  116. package/dist/src/databases/transformers/graphqlName.d.ts.map +1 -1
  117. package/dist/src/index.d.ts +6 -5
  118. package/dist/src/index.d.ts.map +1 -1
  119. package/dist/src/logging/index.d.ts.map +1 -1
  120. package/dist/src/queues/kafka.d.ts.map +1 -1
  121. package/dist/src/queues/rabbitmq.d.ts.map +1 -1
  122. package/dist/src/remoteREST/index.d.ts.map +1 -1
  123. package/dist/src/remoteREST/parse.d.ts.map +1 -1
  124. package/dist/src/remoteREST/proxy.d.ts.map +1 -1
  125. package/dist/src/remoteREST/transform.d.ts.map +1 -1
  126. package/dist/src/remoteSchemas/index.d.ts.map +1 -1
  127. package/dist/src/remoteSchemas/introspect.d.ts.map +1 -1
  128. package/dist/src/remoteSchemas/proxy.d.ts.map +1 -1
  129. package/dist/src/remoteSchemas/transform.d.ts.map +1 -1
  130. package/dist/src/singletons/ai.d.ts.map +1 -1
  131. package/dist/src/singletons/authentication.d.ts.map +1 -1
  132. package/dist/src/singletons/cache/lruCacheStore.d.ts.map +1 -1
  133. package/dist/src/singletons/cache/redisCacheStore.d.ts.map +1 -1
  134. package/dist/src/singletons/cache/registry.d.ts.map +1 -1
  135. package/dist/src/singletons/cron.d.ts.map +1 -1
  136. package/dist/src/singletons/databases.d.ts.map +1 -1
  137. package/dist/src/singletons/env.d.ts +190 -187
  138. package/dist/src/singletons/env.d.ts.map +1 -1
  139. package/dist/src/singletons/queues.d.ts.map +1 -1
  140. package/dist/src/subscriptions/strategies/index.d.ts.map +1 -1
  141. package/dist/src/subscriptions/utils/polling.d.ts.map +1 -1
  142. package/dist/src/types/db.d.ts.map +1 -1
  143. package/dist/src/types/env.d.ts +641 -630
  144. package/dist/src/types/env.d.ts.map +1 -1
  145. package/dist/src/types/resolver.d.ts.map +1 -1
  146. package/dist/src/types/zod/auth.d.ts +18 -18
  147. package/dist/src/types/zod/configuration.d.ts +298 -293
  148. package/dist/src/types/zod/configuration.d.ts.map +1 -1
  149. package/dist/src/types/zod/db.d.ts +80 -80
  150. package/dist/src/types/zod/db.d.ts.map +1 -1
  151. package/dist/src/types/zod/queue.d.ts +80 -80
  152. package/dist/src/types/zod/queue.d.ts.map +1 -1
  153. package/dist/src/utils/benchmark.d.ts.map +1 -1
  154. package/dist/src/utils/responses.d.ts.map +1 -1
  155. package/dist/src/utils/safeJSONParse.d.ts.map +1 -1
  156. package/dist/src/utils/selection.d.ts.map +1 -1
  157. package/dist/src/utils/sessionVariables.d.ts +5 -1
  158. package/dist/src/utils/sessionVariables.d.ts.map +1 -1
  159. package/dist/src/utils/writeSchema.d.ts.map +1 -1
  160. package/dist/tsconfig.tsbuildinfo +1 -1
  161. package/package.json +17 -17
  162. package/playgrounds/console/index.html +18 -15
  163. package/playgrounds/graphiql/index.html +855 -624
  164. package/playgrounds/scalar/index.html +5 -5
  165. package/src/analyzeQuery/analyzers/selectionAnalyzer.ts +7 -1
  166. package/src/analyzeQuery/resolveVariables.ts +130 -16
  167. package/src/config/types/auth.ts +7 -1
  168. package/src/config/types/db.ts +8 -0
  169. package/src/cron/index.ts +6 -2
  170. package/src/databases/engines/mysql/connection.ts +1 -0
  171. package/src/logging/index.ts +3 -1
  172. package/src/types/db.ts +0 -1
  173. package/src/types/zod/db.ts +1 -7
  174. package/src/types/zod/queue.ts +2 -10
  175. package/src/utils/sessionVariables.ts +7 -1
@@ -59,7 +59,13 @@ export const analyzeSelections = (
59
59
  (dir) => dir.name === "include" || dir.name === "skip" || dir.name === "when",
60
60
  ).length === 0;
61
61
 
62
- if (isArray && entities.queriesMap[fieldName]?.rolePermission?.filter) {
62
+ // Gated on the field resolving to a table type, not on it being a list.
63
+ // A role filter has to reach every field backed by the table it
64
+ // restricts: the `_single` root field and any to-one relationship both
65
+ // return one object, and both accept a `where` the builders honour, so
66
+ // skipping them let a caller read a row the filter excludes by asking
67
+ // for it singly or by traversing into it from an unfiltered table.
68
+ if (fieldObjType && entities.queriesMap[fieldName]?.rolePermission?.filter) {
63
69
  if (!args) {
64
70
  args = {
65
71
  where: entities.queriesMap[fieldName].rolePermission?.filter,
@@ -72,6 +72,7 @@ export const extractRuntimePrimitivesToVariables = (
72
72
  runtimeVariables: Record<string, unknown>,
73
73
  startIndex: number,
74
74
  referencedVariables: Set<string> = new Set(),
75
+ declaredVariableNames?: Set<string>,
75
76
  ): unknown => {
76
77
  // Handle null/undefined
77
78
  if (obj === null || obj === undefined) {
@@ -87,13 +88,23 @@ export const extractRuntimePrimitivesToVariables = (
87
88
  runtimeVariables,
88
89
  startIndex,
89
90
  referencedVariables,
91
+ declaredVariableNames,
90
92
  ),
91
93
  );
92
94
  }
93
95
 
94
96
  // Handle primitives - convert to static variable references
95
97
  if (typeof obj === "string") {
96
- if (obj.startsWith("$")) {
98
+ // A `$name` string is a reference only when the document declares that
99
+ // variable. These values arrive from the request body, so anything else
100
+ // beginning with `$` is data the client sent: `$1` resolved to index -1 and
101
+ // emitted the placeholder `$0`, and a value naming a real generated
102
+ // variable would have read that parameter's value instead of matching on
103
+ // its own text.
104
+ if (
105
+ obj.startsWith("$") &&
106
+ (!declaredVariableNames || declaredVariableNames.has(obj.substring(1)))
107
+ ) {
97
108
  referencedVariables.add(obj.substring(1));
98
109
  return obj;
99
110
  }
@@ -143,6 +154,7 @@ export const extractRuntimePrimitivesToVariables = (
143
154
  runtimeVariables,
144
155
  startIndex,
145
156
  referencedVariables,
157
+ declaredVariableNames,
146
158
  );
147
159
  }
148
160
  return result;
@@ -166,6 +178,7 @@ export const flattenObjectVariables = (
166
178
  const resolvedObjectVarNames = new Set<string>();
167
179
  const nestedReferencedVars = new Set<string>();
168
180
  const resolvedMap = new Map<string, unknown>();
181
+ const declaredVariableNames = new Set(variables.map((variable) => variable.name));
169
182
 
170
183
  for (const varDef of variables) {
171
184
  if (varDef.name.startsWith("static_")) continue;
@@ -184,6 +197,7 @@ export const flattenObjectVariables = (
184
197
  resolvedRuntimeValues,
185
198
  existingStaticCount,
186
199
  nestedReferencedVars,
200
+ declaredVariableNames,
187
201
  );
188
202
 
189
203
  resolvedMap.set(varDef.name, transformed);
@@ -200,15 +214,67 @@ export const flattenObjectVariables = (
200
214
 
201
215
  // ─── Step 3: Immutable Field Resolution ─────────────────────────────────────
202
216
 
217
+ /**
218
+ * Where late-resolved values are turned into bound parameters.
219
+ *
220
+ * Client-supplied argument values are hoisted into `static_N` variables while
221
+ * the document is analyzed, so by the time they reach the query builders they
222
+ * are `$name` references the builders emit as placeholders. Two classes of
223
+ * value never went through that hoisting, because they are not in the document:
224
+ * the constants written into a role's permission filter, and the claim values
225
+ * `$session.*` resolves to. Both were interpolated into the SQL text verbatim.
226
+ */
227
+ export type VariableSink = {
228
+ generatedVariables: VariableDefinition[];
229
+ runtimeVariables: Record<string, unknown>;
230
+ startIndex: number;
231
+ };
232
+
233
+ const typeOfValue = (value: unknown): string => {
234
+ if (typeof value === "number") return Number.isInteger(value) ? "Int" : "Float";
235
+ if (typeof value === "boolean") return "Boolean";
236
+ return "String";
237
+ };
238
+
239
+ /** Binds one value as a fresh `static_N` variable and returns its reference. */
240
+ const parameterize = (sink: VariableSink, value: unknown): unknown => {
241
+ if (value === null || value === undefined) return value;
242
+
243
+ // An array operand (`in`) is bound element-wise: the builders emit one
244
+ // placeholder per element.
245
+ if (Array.isArray(value)) return value.map((item) => parameterize(sink, item));
246
+
247
+ if (typeof value === "object") return value;
248
+
249
+ const name = `static_${sink.startIndex + sink.generatedVariables.length}`;
250
+
251
+ sink.generatedVariables.push({
252
+ name,
253
+ type: typeOfValue(value),
254
+ required: false,
255
+ defaultValue: value as never,
256
+ });
257
+ sink.runtimeVariables[name] = value;
258
+
259
+ return `$${name}`;
260
+ };
261
+
203
262
  /**
204
263
  * Replaces `$session.*` placeholders in any argument value.
205
264
  * Walks all arguments, not just `where`.
265
+ *
266
+ * Substituted claim values are bound as parameters on the way out. Doing it
267
+ * here rather than in the `where` pass below matters: a claim whose value
268
+ * happens to begin with `$` would otherwise be indistinguishable from a
269
+ * variable reference, and would resolve to some other parameter's value.
206
270
  */
207
271
  const resolveSessionInArguments = (
208
272
  args: Record<string, unknown>,
209
273
  session: SessionContext,
274
+ sink?: VariableSink,
210
275
  ): Record<string, unknown> => {
211
276
  const result: Record<string, unknown> = {};
277
+ const onResolved = sink ? (value: unknown) => parameterize(sink, value) : undefined;
212
278
 
213
279
  for (const [key, value] of Object.entries(args)) {
214
280
  if (
@@ -217,7 +283,7 @@ const resolveSessionInArguments = (
217
283
  !Array.isArray(value) &&
218
284
  hasSessionVariables(value as Record<string, unknown>)
219
285
  ) {
220
- result[key] = replaceSessionVariables(value as Record<string, unknown>, session);
286
+ result[key] = replaceSessionVariables(value as Record<string, unknown>, session, onResolved);
221
287
  } else {
222
288
  result[key] = value;
223
289
  }
@@ -226,6 +292,33 @@ const resolveSessionInArguments = (
226
292
  return result;
227
293
  };
228
294
 
295
+ /**
296
+ * Binds every literal still sitting in a `where` argument.
297
+ *
298
+ * Only `where` is walked. `orderBy` carries direction enums and `limit`/`offset`
299
+ * are consumed by the pagination builder, and neither is emitted as a bound
300
+ * value, so parameterizing them would change the SQL rather than protect it.
301
+ */
302
+ const parameterizeWhereLiterals = (value: unknown, sink: VariableSink): unknown => {
303
+ if (value === null || value === undefined) return value;
304
+
305
+ if (Array.isArray(value)) return value.map((item) => parameterizeWhereLiterals(item, sink));
306
+
307
+ if (typeof value === "object") {
308
+ return Object.fromEntries(
309
+ Object.entries(value as Record<string, unknown>).map(([key, nested]) => [
310
+ key,
311
+ parameterizeWhereLiterals(nested, sink),
312
+ ]),
313
+ );
314
+ }
315
+
316
+ // Already a reference — the analyzer hoisted it out of the document.
317
+ if (isString(value) && value.startsWith("$")) return value;
318
+
319
+ return parameterize(sink, value);
320
+ };
321
+
229
322
  /**
230
323
  * Recursively replaces `$varName` references with their resolved (already-flattened) values from
231
324
  * `resolvedMap`, walking nested objects/arrays so a reference nested inside an inline argument
@@ -268,6 +361,7 @@ export const resolveFieldArguments = (
268
361
  fields: SelectionAnalysis[],
269
362
  resolvedMap: Map<string, unknown>,
270
363
  session?: SessionContext,
364
+ sink?: VariableSink,
271
365
  ): SelectionAnalysis[] => {
272
366
  return fields.map((field) => {
273
367
  let newArguments = field.arguments;
@@ -287,13 +381,19 @@ export const resolveFieldArguments = (
287
381
 
288
382
  // Replace session variables in ALL argument values (not just where)
289
383
  if (session) {
290
- newArguments = resolveSessionInArguments(newArguments, session);
384
+ newArguments = resolveSessionInArguments(newArguments, session, sink);
385
+ }
386
+
387
+ // Bind whatever literals remain in `where`. After the two passes above the
388
+ // only ones left are the constants a role filter was configured with.
389
+ if (sink && newArguments["where"] !== undefined) {
390
+ newArguments["where"] = parameterizeWhereLiterals(newArguments["where"], sink);
291
391
  }
292
392
  }
293
393
 
294
394
  // Recurse into nested selections
295
395
  const newSelections = field.selections
296
- ? resolveFieldArguments(field.selections, resolvedMap, session)
396
+ ? resolveFieldArguments(field.selections, resolvedMap, session, sink)
297
397
  : field.selections;
298
398
 
299
399
  // Only create a new field object if something changed
@@ -381,15 +481,20 @@ export const resolveVariables = (
381
481
  const hasVars = operation.variables && operation.variables.length > 0;
382
482
 
383
483
  if (!hasVars) {
384
- // No variable definitions still need to resolve session variables in fields
385
- const fields = session
386
- ? resolveFieldArguments(operation.fields, new Map(), session)
387
- : operation.fields;
484
+ // A document with no variables of its own can still carry a role filter,
485
+ // whose constants and session values both need binding.
486
+ const sink: VariableSink = {
487
+ generatedVariables: [],
488
+ runtimeVariables: {},
489
+ startIndex: 0,
490
+ };
491
+
492
+ const fields = resolveFieldArguments(operation.fields, new Map(), session, sink);
388
493
 
389
494
  return {
390
495
  fields,
391
- variables: operation.variables ?? [],
392
- allVariables: { ...variables },
496
+ variables: [...(operation.variables ?? []), ...sink.generatedVariables],
497
+ allVariables: { ...variables, ...sink.runtimeVariables },
393
498
  };
394
499
  }
395
500
 
@@ -404,10 +509,15 @@ export const resolveVariables = (
404
509
  );
405
510
 
406
511
  // 3. Resolve field arguments (immutable)
407
- const fields =
408
- flattenResult.resolvedMap.size > 0 || session
409
- ? resolveFieldArguments(operation.fields, flattenResult.resolvedMap, session)
410
- : operation.fields;
512
+ // Numbering continues past the statics flattening just produced, so the two
513
+ // passes cannot mint the same name.
514
+ const sink: VariableSink = {
515
+ generatedVariables: [],
516
+ runtimeVariables: {},
517
+ startIndex: existingStaticCount + flattenResult.newStaticVariables.length,
518
+ };
519
+
520
+ const fields = resolveFieldArguments(operation.fields, flattenResult.resolvedMap, session, sink);
411
521
 
412
522
  // 4. Build final variable list and merged values
413
523
  const { variables: resolvedVarDefs, allVariables } = buildFinalVariables(
@@ -416,9 +526,13 @@ export const resolveVariables = (
416
526
  variables,
417
527
  );
418
528
 
529
+ const finalVarDefs = resolvedVarDefs.length > 0 ? resolvedVarDefs : operation.variables!;
530
+
419
531
  return {
420
532
  fields,
421
- variables: resolvedVarDefs.length > 0 ? resolvedVarDefs : operation.variables!,
422
- allVariables,
533
+ // Appended, never reordered: the builders emit `$n` from a definition's
534
+ // index in this list, and the driver binds it from the same list.
535
+ variables: [...finalVarDefs, ...sink.generatedVariables],
536
+ allVariables: { ...allVariables, ...sink.runtimeVariables },
423
537
  };
424
538
  };
@@ -64,11 +64,17 @@ export type FilterOperator = (typeof FILTER_OPERATORS)[number];
64
64
  * holding more of the same. Operators are constrained to the implemented set so
65
65
  * a typo fails at boot instead of evaluating to no condition, which would hand
66
66
  * the role the whole table.
67
+ *
68
+ * `partialRecord`, not `record`: an enum-keyed `z.record` is exhaustive, and
69
+ * `z.unknown()` accepts `undefined`, so the plain spelling parses
70
+ * `{ eq: … }` into an object carrying all twelve operator keys. The extra keys
71
+ * are invisible to JSON.stringify but reach buildCondition through
72
+ * Object.entries, which then evaluates operators the filter never declared.
67
73
  */
68
74
  export const FilterConditionZod: z.ZodType<Record<string, unknown>> = z.lazy(() =>
69
75
  z.record(
70
76
  z.string(),
71
- z.union([z.record(z.enum(FILTER_OPERATORS), z.unknown()), FilterConditionZod]),
77
+ z.union([z.partialRecord(z.enum(FILTER_OPERATORS), z.unknown()), FilterConditionZod]),
72
78
  ),
73
79
  );
74
80
 
@@ -138,6 +138,14 @@ export const BunSQLConnectionOptionsZod = z.strictObject({
138
138
  maxLifetime: z.number().nonnegative().default(3600),
139
139
  /** Whether to use TLS/SSL for the connection */
140
140
  tls: z.boolean().default(false),
141
+ /**
142
+ * MySQL only: allow the client to fetch the server's RSA public key over a
143
+ * plain connection when caching_sha2_password asks for full authentication.
144
+ * Off by default, as it is in Bun: without TLS a man-in-the-middle can answer
145
+ * with its own key and read the password. Needed for MySQL 8 servers that are
146
+ * reachable only over plain TCP.
147
+ */
148
+ allowPublicKeyRetrieval: z.boolean().default(false),
141
149
  /** Automatic creation of prepared statements (default: true) */
142
150
  prepare: z.boolean().default(true),
143
151
  /** Return values outside i32 range as BigInts instead of strings (default: false) */
package/src/cron/index.ts CHANGED
@@ -13,7 +13,7 @@ import { logger } from "../logging";
13
13
  */
14
14
  export interface ScheduledCronJob {
15
15
  name: string;
16
- job: Cron;
16
+ job: Cron<NonNullable<CronJob["context"]>>;
17
17
  config: CronJob;
18
18
  executionCount: number;
19
19
  pause: () => boolean;
@@ -106,7 +106,11 @@ const createScheduledJob = (config: CronJob, gqlQuery: GqlQueryFn<true>): Schedu
106
106
  name: config.name,
107
107
  job,
108
108
  config,
109
- executionCount,
109
+ // Getter, not a copy: the counter lives in the tick closure and callers
110
+ // (console status, getSummary) read it after ticks have happened.
111
+ get executionCount() {
112
+ return executionCount;
113
+ },
110
114
  pause: () => {
111
115
  job.pause();
112
116
  log.info("paused");
@@ -22,6 +22,7 @@ export const getPool = async (db: Database) => {
22
22
  connectionTimeout: opts?.connectionTimeout,
23
23
  maxLifetime: opts?.maxLifetime,
24
24
  tls: opts?.tls,
25
+ allowPublicKeyRetrieval: opts?.allowPublicKeyRetrieval,
25
26
  prepare: opts?.prepare,
26
27
  bigint: opts?.bigint,
27
28
  });
@@ -3,7 +3,9 @@ import pino from "pino";
3
3
  const DEFAULT_LEVEL =
4
4
  process.env.LOG_LEVEL || (process.env.NODE_ENV === "production" ? "info" : "debug");
5
5
 
6
- const isDev = process.env.NODE_ENV !== "production";
6
+ // `bun test` sets NODE_ENV=test: pino-pretty runs on a worker thread, and
7
+ // Bun's test runner cannot construct one (thread-stream crashes on import).
8
+ const isDev = process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test";
7
9
 
8
10
  const DEFAULT_OPTIONS: pino.LoggerOptions = {
9
11
  level: DEFAULT_LEVEL,
package/src/types/db.ts CHANGED
@@ -15,7 +15,6 @@ import {
15
15
  TableColumnZod,
16
16
  TableZod,
17
17
  ViewZod,
18
- VirtualColumnZod,
19
18
  } from "./zod/db";
20
19
 
21
20
  // Re-export VirtualColumnType from the config module
@@ -1,12 +1,6 @@
1
1
  import { z } from "zod";
2
2
 
3
- import {
4
- BunSQLConnectionOptionsZod,
5
- DatabaseConnectionZod,
6
- DatabaseSchemaConfigZod,
7
- MSSQLConnectionOptionsZod,
8
- TableRelationshipZod as TableRelationshipBaseZod,
9
- } from "../../config";
3
+ import { TableRelationshipZod as TableRelationshipBaseZod } from "../../config";
10
4
  import { genResolverName } from "../../databases/transformers/genResolverName";
11
5
  import { sanitizeGraphQLName } from "../../databases/transformers/graphqlName";
12
6
 
@@ -1,15 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
- import {
4
- BaseQueueConfigZod,
5
- KafkaConnectionZod,
6
- PublisherConfigZod,
7
- RabbitMQConnectionZod,
8
- ReconnectConfigZod,
9
- SubscriberConfigZod,
10
- TopicConfigZod,
11
- } from "../../config";
12
- import type { CacheContext, SubscriberHandler } from "../../config";
3
+ import { BaseQueueConfigZod, KafkaConnectionZod, RabbitMQConnectionZod } from "../../config";
4
+ import type { SubscriberHandler } from "../../config";
13
5
 
14
6
  // Re-export base types and schemas from the config module
15
7
  export type {
@@ -37,6 +37,10 @@ const resolveDotPath = (obj: Record<string, unknown>, path: string): unknown =>
37
37
  *
38
38
  * @param filter - Filter object with potential session variables
39
39
  * @param session - JWT payload containing user session data
40
+ * @param onResolved - Called with each substituted claim value; its return value
41
+ * is what lands in the filter. The caller uses this to parameterize the value
42
+ * rather than letting it be inlined into SQL — a claim is attacker-influenced
43
+ * whenever users can change their own profile data.
40
44
  * @returns Filter object with session variables replaced
41
45
  *
42
46
  * @example
@@ -54,6 +58,7 @@ const resolveDotPath = (obj: Record<string, unknown>, path: string): unknown =>
54
58
  export const replaceSessionVariables = (
55
59
  filter: Record<string, unknown>,
56
60
  session: SessionContext | null,
61
+ onResolved?: (value: unknown) => unknown,
57
62
  ): Record<string, unknown> => {
58
63
  if (!filter || !session) {
59
64
  return filter;
@@ -79,7 +84,7 @@ export const replaceSessionVariables = (
79
84
  );
80
85
  }
81
86
 
82
- processedOperators[operator] = sessionValue;
87
+ processedOperators[operator] = onResolved ? onResolved(sessionValue) : sessionValue;
83
88
  }
84
89
  // Recursively handle nested objects
85
90
  else if (
@@ -90,6 +95,7 @@ export const replaceSessionVariables = (
90
95
  processedOperators[operator] = replaceSessionVariables(
91
96
  operatorValue as Record<string, unknown>,
92
97
  session,
98
+ onResolved,
93
99
  );
94
100
  }
95
101
  // Keep other values as-is