core-services-sdk 1.3.89 → 1.3.91

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "core-services-sdk",
3
- "version": "1.3.89",
3
+ "version": "1.3.91",
4
4
  "main": "src/index.js",
5
5
  "type": "module",
6
6
  "types": "types/index.d.ts",
@@ -4,6 +4,10 @@ import { mask as defaultMask } from '../util/mask-sensitive.js'
4
4
  /**
5
5
  * Converts a single field definition into a Zod schema.
6
6
  *
7
+ * NOTE: This function does NOT apply .default() — defaults are handled
8
+ * manually in validateEnv so that they work correctly even when other
9
+ * fields fail validation.
10
+ *
7
11
  * @param {Object} def
8
12
  * @param {'string'|'number'|'boolean'} def.type
9
13
  *
@@ -130,10 +134,6 @@ export function defToZod(def) {
130
134
  }
131
135
  }
132
136
 
133
- if (def.default !== undefined) {
134
- schema = schema.default(def.default)
135
- }
136
-
137
137
  if (!def.required) {
138
138
  schema = schema.optional()
139
139
  }
@@ -163,39 +163,56 @@ export function createZodSchema(definition) {
163
163
  /**
164
164
  * Validates values using a JSON definition and Zod.
165
165
  *
166
+ * Defaults are applied manually (not via Zod .default()) so that:
167
+ * - defaults fill in when value is undefined
168
+ * - existing values are always validated (never replaced by defaults)
169
+ * - invalid values are kept in data (with errors reported)
170
+ * - data is ALWAYS returned, even on failure
171
+ *
166
172
  * @param {Record<string, Object>} definition
167
173
  * @param {Record<string, any>} values
168
174
  *
169
175
  * @returns {{
170
- * success: true,
171
- * data: Record<string, any>
172
- * } | {
173
- * success: false,
174
- * summary: Record<string, string[]>
176
+ * success: boolean,
177
+ * data: Record<string, any>,
178
+ * summary?: Record<string, string[]>
175
179
  * }}
176
180
  */
177
181
  export function validateEnv(definition, values) {
178
- const schema = createZodSchema(definition)
179
- const result = schema.safeParse(values)
182
+ const data = {}
183
+ const summary = {}
180
184
 
181
- if (result.success) {
182
- return {
183
- success: true,
184
- data: result.data,
185
+ for (const [key, def] of Object.entries(definition)) {
186
+ const raw = values[key]
187
+
188
+ // Validate the field individually using its Zod schema
189
+ const fieldSchema = defToZod(def)
190
+
191
+ // Apply default when value is undefined
192
+ if (raw === undefined && def.default !== undefined) {
193
+ const defaultResult = fieldSchema.safeParse(def.default)
194
+ data[key] = defaultResult.success ? defaultResult.data : def.default
195
+ continue
196
+ }
197
+
198
+ const result = fieldSchema.safeParse(raw)
199
+
200
+ if (result.success) {
201
+ data[key] = result.data
202
+ } else {
203
+ // Keep original value in data, record errors
204
+ data[key] = raw
205
+ summary[key] = result.error.issues.map((issue) => issue.message)
185
206
  }
186
207
  }
187
208
 
188
- const summary = result.error.issues.reduce((acc, issue) => {
189
- const key = issue.path[0] || 'root'
190
- acc[key] = acc[key] || []
191
- acc[key].push(issue.message)
192
- return acc
193
- }, {})
209
+ const success = Object.keys(summary).length === 0
194
210
 
195
- return {
196
- success: false,
197
- summary,
211
+ if (success) {
212
+ return { success: true, data }
198
213
  }
214
+
215
+ return { success: false, data, summary }
199
216
  }
200
217
 
201
218
  /**
@@ -232,13 +249,11 @@ export function buildEnvReport(definition, values, validationResult, mask) {
232
249
  const def = definition[key]
233
250
  const isSecret = Boolean(def.secret)
234
251
 
235
- // value precedence:
236
- // 1. validated & parsed value
237
- // 2. raw input value
238
- const rawValue = values[key]
239
- const value = validationResult.success
252
+ // Always prefer validated data (includes defaults and parsed values)
253
+ // Fall back to raw values only for backward compat when data is missing
254
+ const value = validationResult.data
240
255
  ? validationResult.data[key]
241
- : rawValue
256
+ : values[key]
242
257
 
243
258
  const displayValue = isSecret ? maskValue(value, '.', 3) : String(value)
244
259
 
@@ -330,7 +345,7 @@ export function formatEnvReport(report) {
330
345
  * success: boolean,
331
346
  * validation: {
332
347
  * success: boolean,
333
- * data?: Record<string, any>,
348
+ * data: Record<string, any>,
334
349
  * summary?: Record<string, string[]>
335
350
  * },
336
351
  * report: {
@@ -379,7 +394,7 @@ export function validateAndReportEnv(definition, values, options = {}) {
379
394
  * table: string,
380
395
  * validation: {
381
396
  * success: boolean,
382
- * data?: Record<string, any>,
397
+ * data: Record<string, any>,
383
398
  * summary?: Record<string, string[]>
384
399
  * },
385
400
  * report: {
@@ -392,7 +407,8 @@ export function validateAndReportEnv(definition, values, options = {}) {
392
407
  * valid: boolean,
393
408
  * errors?: string[]
394
409
  * }>
395
- * }
410
+ * },
411
+ * env: Record<string, any>
396
412
  * }}
397
413
  */
398
414
  export function prepareEnvValidation(definition, values, options = {}) {
@@ -407,6 +423,7 @@ export function prepareEnvValidation(definition, values, options = {}) {
407
423
  table,
408
424
  validation,
409
425
  report,
426
+ env: validation.data,
410
427
  }
411
428
  }
412
429
 
@@ -140,17 +140,14 @@ function waitForRabbitHealthy(containerName, dockerRunCmd, ports) {
140
140
  } catch {}
141
141
 
142
142
  try {
143
- execSync(
144
- `docker exec ${containerName} rabbitmq-diagnostics -q ping`,
145
- { stdio: 'ignore' },
146
- )
143
+ execSync(`docker exec ${containerName} rabbitmq-diagnostics -q ping`, {
144
+ stdio: 'ignore',
145
+ })
147
146
  console.log(`[RabbitTest] RabbitMQ is ready.`)
148
147
  return
149
148
  } catch {
150
149
  if (retries % 10 === 0 && retries > 0) {
151
- console.log(
152
- `[RabbitTest] Still waiting... (${retries}/${maxRetries})`,
153
- )
150
+ console.log(`[RabbitTest] Still waiting... (${retries}/${maxRetries})`)
154
151
  }
155
152
  }
156
153
 
@@ -18,18 +18,19 @@ describe('createZodSchema', () => {
18
18
  it('parses valid values successfully', () => {
19
19
  const definition = {
20
20
  PORT: { type: 'number', required: true, min: 1 },
21
- DEBUG: { type: 'boolean', default: false },
21
+ DEBUG: { type: 'boolean' },
22
22
  }
23
23
 
24
24
  const schema = createZodSchema(definition)
25
25
 
26
26
  const result = schema.parse({
27
27
  PORT: '3000',
28
+ DEBUG: true,
28
29
  })
29
30
 
30
31
  expect(result).toEqual({
31
32
  PORT: 3000,
32
- DEBUG: false,
33
+ DEBUG: true,
33
34
  })
34
35
  })
35
36
 
@@ -68,6 +68,43 @@ describe('prepareEnvValidation', () => {
68
68
  expect(result.validation.data.PORT).toBe(8080)
69
69
  })
70
70
 
71
+ it('returns env with parsed values and defaults applied', () => {
72
+ const def = {
73
+ PORT: { type: 'number', default: 3000 },
74
+ DEBUG: { type: 'boolean', default: 'false' },
75
+ MODE: {
76
+ type: 'string',
77
+ enum: ['development', 'production'],
78
+ required: true,
79
+ },
80
+ }
81
+
82
+ const values = { MODE: 'production' }
83
+
84
+ const result = prepareEnvValidation(def, values)
85
+
86
+ expect(result.success).toBe(true)
87
+ expect(result.env).toBeDefined()
88
+ expect(result.env.PORT).toBe(3000)
89
+ expect(result.env.DEBUG).toBe(false)
90
+ expect(result.env.MODE).toBe('production')
91
+ })
92
+
93
+ it('returns env with defaults and invalid originals on failure', () => {
94
+ const def = {
95
+ PORT: { type: 'number', default: 3000 },
96
+ HOST: { type: 'string', format: 'url', required: true },
97
+ }
98
+
99
+ const values = { HOST: 'not-a-url' }
100
+
101
+ const result = prepareEnvValidation(def, values)
102
+
103
+ expect(result.success).toBe(false)
104
+ expect(result.env.PORT).toBe(3000)
105
+ expect(result.env.HOST).toBe('not-a-url')
106
+ })
107
+
71
108
  it('returns a table string even on failure', () => {
72
109
  const values = {
73
110
  PORT: 0,
@@ -90,6 +90,121 @@ describe('validateEnv', () => {
90
90
  expect(result.summary).toHaveProperty('NODE_ENV')
91
91
  })
92
92
 
93
+ it('always returns data even when validation fails', () => {
94
+ const definition = {
95
+ PORT: { type: 'number', default: 3000 },
96
+ HOST: { type: 'string', format: 'url' },
97
+ }
98
+
99
+ const values = {
100
+ HOST: 'invalid-url',
101
+ }
102
+
103
+ const result = validateEnv(definition, values)
104
+
105
+ expect(result.success).toBe(false)
106
+ expect(result.data).toBeDefined()
107
+ expect(result.data.PORT).toBe(3000)
108
+ expect(result.data.HOST).toBe('invalid-url')
109
+ expect(result.summary).toHaveProperty('HOST')
110
+ })
111
+
112
+ it('applies default when value is undefined', () => {
113
+ const definition = {
114
+ PORT: { type: 'number', default: 3000 },
115
+ DEBUG: { type: 'boolean', default: false },
116
+ }
117
+
118
+ const values = {}
119
+
120
+ const result = validateEnv(definition, values)
121
+
122
+ expect(result.success).toBe(true)
123
+ expect(result.data.PORT).toBe(3000)
124
+ expect(result.data.DEBUG).toBe(false)
125
+ })
126
+
127
+ it('does not replace existing value with default', () => {
128
+ const definition = {
129
+ PORT: { type: 'number', default: 3000 },
130
+ }
131
+
132
+ const values = { PORT: '8080' }
133
+
134
+ const result = validateEnv(definition, values)
135
+
136
+ expect(result.success).toBe(true)
137
+ expect(result.data.PORT).toBe(8080)
138
+ })
139
+
140
+ it('does not replace invalid value with default', () => {
141
+ const definition = {
142
+ PORT: { type: 'number', min: 1, default: 3000 },
143
+ }
144
+
145
+ const values = { PORT: 'not-a-number' }
146
+
147
+ const result = validateEnv(definition, values)
148
+
149
+ expect(result.success).toBe(false)
150
+ expect(result.data.PORT).toBe('not-a-number')
151
+ expect(result.summary).toHaveProperty('PORT')
152
+ })
153
+
154
+ it('parses boolean string "false" to false', () => {
155
+ const definition = {
156
+ ENABLED: { type: 'boolean' },
157
+ }
158
+
159
+ const values = { ENABLED: 'false' }
160
+
161
+ const result = validateEnv(definition, values)
162
+
163
+ expect(result.success).toBe(true)
164
+ expect(result.data.ENABLED).toBe(false)
165
+ })
166
+
167
+ it('parses boolean string "true" to true', () => {
168
+ const definition = {
169
+ ENABLED: { type: 'boolean' },
170
+ }
171
+
172
+ const values = { ENABLED: 'true' }
173
+
174
+ const result = validateEnv(definition, values)
175
+
176
+ expect(result.success).toBe(true)
177
+ expect(result.data.ENABLED).toBe(true)
178
+ })
179
+
180
+ it('parses boolean string "1" to true and "0" to false', () => {
181
+ const definition = {
182
+ A: { type: 'boolean' },
183
+ B: { type: 'boolean' },
184
+ }
185
+
186
+ const values = { A: '1', B: '0' }
187
+
188
+ const result = validateEnv(definition, values)
189
+
190
+ expect(result.success).toBe(true)
191
+ expect(result.data.A).toBe(true)
192
+ expect(result.data.B).toBe(false)
193
+ })
194
+
195
+ it('parses boolean default string "false" to false', () => {
196
+ const definition = {
197
+ DIGITAL_SIGNING_ENABLED: { type: 'boolean', default: 'false' },
198
+ }
199
+
200
+ const values = {}
201
+
202
+ const result = validateEnv(definition, values)
203
+
204
+ expect(result.success).toBe(true)
205
+ expect(result.data.DIGITAL_SIGNING_ENABLED).toBe(false)
206
+ })
207
+
93
208
  it('does not include summary when validation succeeds', () => {
94
209
  const definition = {
95
210
  PORT: { type: 'number', required: true },
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Converts a single field definition into a Zod schema.
3
3
  *
4
+ * NOTE: This function does NOT apply .default() — defaults are handled
5
+ * manually in validateEnv so that they work correctly even when other
6
+ * fields fail validation.
7
+ *
4
8
  * @param {Object} def
5
9
  * @param {'string'|'number'|'boolean'} def.type
6
10
  *
@@ -59,29 +63,29 @@ export function createZodSchema(
59
63
  /**
60
64
  * Validates values using a JSON definition and Zod.
61
65
  *
66
+ * Defaults are applied manually (not via Zod .default()) so that:
67
+ * - defaults fill in when value is undefined
68
+ * - existing values are always validated (never replaced by defaults)
69
+ * - invalid values are kept in data (with errors reported)
70
+ * - data is ALWAYS returned, even on failure
71
+ *
62
72
  * @param {Record<string, Object>} definition
63
73
  * @param {Record<string, any>} values
64
74
  *
65
75
  * @returns {{
66
- * success: true,
67
- * data: Record<string, any>
68
- * } | {
69
- * success: false,
70
- * summary: Record<string, string[]>
76
+ * success: boolean,
77
+ * data: Record<string, any>,
78
+ * summary?: Record<string, string[]>
71
79
  * }}
72
80
  */
73
81
  export function validateEnv(
74
82
  definition: Record<string, any>,
75
83
  values: Record<string, any>,
76
- ):
77
- | {
78
- success: true
79
- data: Record<string, any>
80
- }
81
- | {
82
- success: false
83
- summary: Record<string, string[]>
84
- }
84
+ ): {
85
+ success: boolean
86
+ data: Record<string, any>
87
+ summary?: Record<string, string[]>
88
+ }
85
89
  /**
86
90
  * Builds a structured environment validation report.
87
91
  *
@@ -177,7 +181,7 @@ export function formatEnvReport(report: {
177
181
  * success: boolean,
178
182
  * validation: {
179
183
  * success: boolean,
180
- * data?: Record<string, any>,
184
+ * data: Record<string, any>,
181
185
  * summary?: Record<string, string[]>
182
186
  * },
183
187
  * report: {
@@ -204,7 +208,7 @@ export function validateAndReportEnv(
204
208
  success: boolean
205
209
  validation: {
206
210
  success: boolean
207
- data?: Record<string, any>
211
+ data: Record<string, any>
208
212
  summary?: Record<string, string[]>
209
213
  }
210
214
  report: {
@@ -237,7 +241,7 @@ export function validateAndReportEnv(
237
241
  * table: string,
238
242
  * validation: {
239
243
  * success: boolean,
240
- * data?: Record<string, any>,
244
+ * data: Record<string, any>,
241
245
  * summary?: Record<string, string[]>
242
246
  * },
243
247
  * report: {
@@ -250,7 +254,8 @@ export function validateAndReportEnv(
250
254
  * valid: boolean,
251
255
  * errors?: string[]
252
256
  * }>
253
- * }
257
+ * },
258
+ * env: Record<string, any>
254
259
  * }}
255
260
  */
256
261
  export function prepareEnvValidation(
@@ -264,7 +269,7 @@ export function prepareEnvValidation(
264
269
  table: string
265
270
  validation: {
266
271
  success: boolean
267
- data?: Record<string, any>
272
+ data: Record<string, any>
268
273
  summary?: Record<string, string[]>
269
274
  }
270
275
  report: {
@@ -278,6 +283,7 @@ export function prepareEnvValidation(
278
283
  errors?: string[]
279
284
  }>
280
285
  }
286
+ env: Record<string, any>
281
287
  }
282
288
  /**
283
289
  * Builds a console-ready table output for environment validation.