Skip to content

Commit 7100f33

Browse files
fix(redis,mongodb): address PR review comments
- redis.ts: move resolveTlsOptions call outside the try/catch in getRedisClient so config errors surface instead of being swallowed into a silent null return. - mongodb.ts: re-attach mongoUsernamePasswordPaired .refine after each of the five downstream .extend()s. Mirrors the confluence pattern and restores the pairing constraint that the original chain dropped.
1 parent 4bf0d85 commit 7100f33

2 files changed

Lines changed: 107 additions & 94 deletions

File tree

apps/sim/lib/api/contracts/tools/databases/mongodb.ts

Lines changed: 104 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,8 @@ import {
1010
defineRouteContract,
1111
} from '@/lib/api/contracts/types'
1212

13-
// .refine returns ZodEffects which has no .extend method, so the downstream
14-
// MongoDB operation schemas have to extend the un-refined base. (Note: the
15-
// resolved Zod is v3 despite package.json declaring v4 — same root issue as
16-
// confluence and storage-transfer.) The five downstream extensions previously
17-
// threw at module-init time in the TD bundle, so no consumer was actually
18-
// running the username/password pairing constraint; not adding it back here
19-
// preserves observed behavior.
13+
// Un-refined base so the downstream operation schemas can .extend it; each
14+
// reattaches mongoUsernamePasswordPaired after its own .extend.
2015
const mongoConnectionBaseSchema = z.object({
2116
host: z.string().min(1, 'Host is required'),
2217
port: z.coerce.number().int().positive('Port must be a positive integer'),
@@ -27,6 +22,13 @@ const mongoConnectionBaseSchema = z.object({
2722
ssl: sslModeSchema,
2823
})
2924

25+
const mongoUsernamePasswordPaired = (data: { username?: string; password?: string }) =>
26+
Boolean(data.username) === Boolean(data.password)
27+
const mongoUsernamePasswordPairedError = {
28+
message: 'Username and password must be provided together',
29+
path: ['password' as const],
30+
}
31+
3032
const mongoJsonStringOrObjectSchema = (message: string) =>
3133
z
3234
.union([z.string(), z.object({}).passthrough()])
@@ -47,92 +49,102 @@ const booleanStringSchema = z
4749
return false
4850
})
4951

50-
export const mongodbQueryBodySchema = mongoConnectionBaseSchema.extend({
51-
collection: z.string().min(1, 'Collection name is required'),
52-
query: z
53-
.union([z.string(), z.object({}).passthrough()])
54-
.optional()
55-
.default('{}')
56-
.transform((val) => {
57-
if (typeof val === 'object' && val !== null) {
58-
return JSON.stringify(val)
59-
}
60-
return val || '{}'
61-
}),
62-
limit: z
63-
.union([z.coerce.number().int().positive(), z.literal(''), z.undefined()])
64-
.optional()
65-
.transform((val) => {
66-
if (val === '' || val === undefined || val === null) {
67-
return 100
68-
}
69-
return val
70-
}),
71-
sort: z
72-
.union([z.string(), z.object({}).passthrough(), z.null()])
73-
.optional()
74-
.transform((val) => {
75-
if (typeof val === 'object' && val !== null) {
76-
return JSON.stringify(val)
77-
}
78-
return val
79-
}),
80-
})
81-
82-
export const mongodbExecuteBodySchema = mongoConnectionBaseSchema.extend({
83-
collection: z.string().min(1, 'Collection name is required'),
84-
pipeline: z
85-
.union([z.string(), z.array(z.object({}).passthrough())])
86-
.transform((val) => {
87-
if (Array.isArray(val)) {
88-
return JSON.stringify(val)
89-
}
90-
return val
91-
})
92-
.refine((val) => val && val.trim() !== '', {
93-
message: 'Pipeline is required',
94-
}),
95-
})
96-
97-
export const mongodbInsertBodySchema = mongoConnectionBaseSchema.extend({
98-
collection: z.string().min(1, 'Collection name is required'),
99-
documents: z
100-
.union([z.array(z.record(z.string(), z.unknown())), z.string()])
101-
.transform((val) => {
102-
if (typeof val === 'string') {
103-
try {
104-
const parsed = JSON.parse(val)
105-
return Array.isArray(parsed) ? parsed : [parsed]
106-
} catch {
107-
throw new Error('Invalid JSON in documents field')
52+
export const mongodbQueryBodySchema = mongoConnectionBaseSchema
53+
.extend({
54+
collection: z.string().min(1, 'Collection name is required'),
55+
query: z
56+
.union([z.string(), z.object({}).passthrough()])
57+
.optional()
58+
.default('{}')
59+
.transform((val) => {
60+
if (typeof val === 'object' && val !== null) {
61+
return JSON.stringify(val)
10862
}
109-
}
110-
return val
111-
})
112-
.refine((val) => Array.isArray(val) && val.length > 0, {
113-
message: 'At least one document is required',
114-
}),
115-
})
116-
117-
export const mongodbUpdateBodySchema = mongoConnectionBaseSchema.extend({
118-
collection: z.string().min(1, 'Collection name is required'),
119-
filter: mongoJsonStringOrObjectSchema('Filter is required for MongoDB Update').refine(
120-
(val) => val !== '{}',
121-
{ message: 'Filter is required for MongoDB Update' }
122-
),
123-
update: mongoJsonStringOrObjectSchema('Update is required'),
124-
upsert: booleanStringSchema,
125-
multi: booleanStringSchema,
126-
})
127-
128-
export const mongodbDeleteBodySchema = mongoConnectionBaseSchema.extend({
129-
collection: z.string().min(1, 'Collection name is required'),
130-
filter: mongoJsonStringOrObjectSchema('Filter is required for MongoDB Delete').refine(
131-
(val) => val !== '{}',
132-
{ message: 'Filter is required for MongoDB Delete' }
133-
),
134-
multi: booleanStringSchema,
135-
})
63+
return val || '{}'
64+
}),
65+
limit: z
66+
.union([z.coerce.number().int().positive(), z.literal(''), z.undefined()])
67+
.optional()
68+
.transform((val) => {
69+
if (val === '' || val === undefined || val === null) {
70+
return 100
71+
}
72+
return val
73+
}),
74+
sort: z
75+
.union([z.string(), z.object({}).passthrough(), z.null()])
76+
.optional()
77+
.transform((val) => {
78+
if (typeof val === 'object' && val !== null) {
79+
return JSON.stringify(val)
80+
}
81+
return val
82+
}),
83+
})
84+
.refine(mongoUsernamePasswordPaired, mongoUsernamePasswordPairedError)
85+
86+
export const mongodbExecuteBodySchema = mongoConnectionBaseSchema
87+
.extend({
88+
collection: z.string().min(1, 'Collection name is required'),
89+
pipeline: z
90+
.union([z.string(), z.array(z.object({}).passthrough())])
91+
.transform((val) => {
92+
if (Array.isArray(val)) {
93+
return JSON.stringify(val)
94+
}
95+
return val
96+
})
97+
.refine((val) => val && val.trim() !== '', {
98+
message: 'Pipeline is required',
99+
}),
100+
})
101+
.refine(mongoUsernamePasswordPaired, mongoUsernamePasswordPairedError)
102+
103+
export const mongodbInsertBodySchema = mongoConnectionBaseSchema
104+
.extend({
105+
collection: z.string().min(1, 'Collection name is required'),
106+
documents: z
107+
.union([z.array(z.record(z.string(), z.unknown())), z.string()])
108+
.transform((val) => {
109+
if (typeof val === 'string') {
110+
try {
111+
const parsed = JSON.parse(val)
112+
return Array.isArray(parsed) ? parsed : [parsed]
113+
} catch {
114+
throw new Error('Invalid JSON in documents field')
115+
}
116+
}
117+
return val
118+
})
119+
.refine((val) => Array.isArray(val) && val.length > 0, {
120+
message: 'At least one document is required',
121+
}),
122+
})
123+
.refine(mongoUsernamePasswordPaired, mongoUsernamePasswordPairedError)
124+
125+
export const mongodbUpdateBodySchema = mongoConnectionBaseSchema
126+
.extend({
127+
collection: z.string().min(1, 'Collection name is required'),
128+
filter: mongoJsonStringOrObjectSchema('Filter is required for MongoDB Update').refine(
129+
(val) => val !== '{}',
130+
{ message: 'Filter is required for MongoDB Update' }
131+
),
132+
update: mongoJsonStringOrObjectSchema('Update is required'),
133+
upsert: booleanStringSchema,
134+
multi: booleanStringSchema,
135+
})
136+
.refine(mongoUsernamePasswordPaired, mongoUsernamePasswordPairedError)
137+
138+
export const mongodbDeleteBodySchema = mongoConnectionBaseSchema
139+
.extend({
140+
collection: z.string().min(1, 'Collection name is required'),
141+
filter: mongoJsonStringOrObjectSchema('Filter is required for MongoDB Delete').refine(
142+
(val) => val !== '{}',
143+
{ message: 'Filter is required for MongoDB Delete' }
144+
),
145+
multi: booleanStringSchema,
146+
})
147+
.refine(mongoUsernamePasswordPaired, mongoUsernamePasswordPairedError)
136148

137149
export const mongodbIntrospectBodySchema = z
138150
.object({

apps/sim/lib/core/config/redis.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,12 @@ export function getRedisClient(): Redis | null {
116116
if (!redisUrl) return null
117117
if (globalRedisClient) return globalRedisClient
118118

119+
// Outside the try/catch so config errors aren't silently swallowed.
120+
const tls = resolveTlsOptions(redisUrl)
121+
119122
try {
120123
logger.info('Initializing Redis client')
121124

122-
const tls = resolveTlsOptions(redisUrl)
123-
124125
globalRedisClient = new Redis(redisUrl, {
125126
keepAlive: 1000,
126127
connectTimeout: 10000,

0 commit comments

Comments
 (0)