@ontrails/config 1.0.0-beta.15 → 1.0.0-beta.16

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 (139) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/package.json +10 -2
  4. package/src/define-config.ts +6 -3
  5. package/src/extensions.ts +1 -1
  6. package/src/index.ts +0 -1
  7. package/src/registry.ts +1 -1
  8. package/src/resolve.ts +4 -2
  9. package/.turbo/turbo-build.log +0 -1
  10. package/.turbo/turbo-lint.log +0 -3
  11. package/.turbo/turbo-typecheck.log +0 -1
  12. package/dist/app-config.d.ts +0 -65
  13. package/dist/app-config.d.ts.map +0 -1
  14. package/dist/app-config.js +0 -177
  15. package/dist/app-config.js.map +0 -1
  16. package/dist/collect.d.ts +0 -11
  17. package/dist/collect.d.ts.map +0 -1
  18. package/dist/collect.js +0 -81
  19. package/dist/collect.js.map +0 -1
  20. package/dist/compose.d.ts +0 -26
  21. package/dist/compose.d.ts.map +0 -1
  22. package/dist/compose.js +0 -19
  23. package/dist/compose.js.map +0 -1
  24. package/dist/config-layer.d.ts +0 -11
  25. package/dist/config-layer.d.ts.map +0 -1
  26. package/dist/config-layer.js +0 -6
  27. package/dist/config-layer.js.map +0 -1
  28. package/dist/config-resource.d.ts +0 -3
  29. package/dist/config-resource.d.ts.map +0 -1
  30. package/dist/config-resource.js +0 -26
  31. package/dist/config-resource.js.map +0 -1
  32. package/dist/define-config.d.ts +0 -61
  33. package/dist/define-config.d.ts.map +0 -1
  34. package/dist/define-config.js +0 -87
  35. package/dist/define-config.js.map +0 -1
  36. package/dist/derive/env.d.ts +0 -15
  37. package/dist/derive/env.d.ts.map +0 -1
  38. package/dist/derive/env.js +0 -65
  39. package/dist/derive/env.js.map +0 -1
  40. package/dist/derive/example.d.ts +0 -16
  41. package/dist/derive/example.d.ts.map +0 -1
  42. package/dist/derive/example.js +0 -138
  43. package/dist/derive/example.js.map +0 -1
  44. package/dist/derive/helpers.d.ts +0 -35
  45. package/dist/derive/helpers.d.ts.map +0 -1
  46. package/dist/derive/helpers.js +0 -116
  47. package/dist/derive/helpers.js.map +0 -1
  48. package/dist/derive/index.d.ts +0 -4
  49. package/dist/derive/index.d.ts.map +0 -1
  50. package/dist/derive/index.js +0 -4
  51. package/dist/derive/index.js.map +0 -1
  52. package/dist/derive/json-schema.d.ts +0 -18
  53. package/dist/derive/json-schema.d.ts.map +0 -1
  54. package/dist/derive/json-schema.js +0 -97
  55. package/dist/derive/json-schema.js.map +0 -1
  56. package/dist/derive-fields.d.ts +0 -25
  57. package/dist/derive-fields.d.ts.map +0 -1
  58. package/dist/derive-fields.js +0 -147
  59. package/dist/derive-fields.js.map +0 -1
  60. package/dist/derive-provenance.d.ts +0 -30
  61. package/dist/derive-provenance.d.ts.map +0 -1
  62. package/dist/derive-provenance.js +0 -101
  63. package/dist/derive-provenance.js.map +0 -1
  64. package/dist/doctor.d.ts +0 -27
  65. package/dist/doctor.d.ts.map +0 -1
  66. package/dist/doctor.js +0 -131
  67. package/dist/doctor.js.map +0 -1
  68. package/dist/extensions.d.ts +0 -38
  69. package/dist/extensions.d.ts.map +0 -1
  70. package/dist/extensions.js +0 -35
  71. package/dist/extensions.js.map +0 -1
  72. package/dist/index.d.ts +0 -20
  73. package/dist/index.d.ts.map +0 -1
  74. package/dist/index.js +0 -20
  75. package/dist/index.js.map +0 -1
  76. package/dist/merge.d.ts +0 -16
  77. package/dist/merge.d.ts.map +0 -1
  78. package/dist/merge.js +0 -34
  79. package/dist/merge.js.map +0 -1
  80. package/dist/ref.d.ts +0 -29
  81. package/dist/ref.d.ts.map +0 -1
  82. package/dist/ref.js +0 -30
  83. package/dist/ref.js.map +0 -1
  84. package/dist/registry.d.ts +0 -24
  85. package/dist/registry.d.ts.map +0 -1
  86. package/dist/registry.js +0 -12
  87. package/dist/registry.js.map +0 -1
  88. package/dist/resolve.d.ts +0 -21
  89. package/dist/resolve.d.ts.map +0 -1
  90. package/dist/resolve.js +0 -177
  91. package/dist/resolve.js.map +0 -1
  92. package/dist/secret-heuristics.d.ts +0 -10
  93. package/dist/secret-heuristics.d.ts.map +0 -1
  94. package/dist/secret-heuristics.js +0 -11
  95. package/dist/secret-heuristics.js.map +0 -1
  96. package/dist/trails/config-check.d.ts +0 -11
  97. package/dist/trails/config-check.d.ts.map +0 -1
  98. package/dist/trails/config-check.js +0 -53
  99. package/dist/trails/config-check.js.map +0 -1
  100. package/dist/trails/config-describe.d.ts +0 -12
  101. package/dist/trails/config-describe.d.ts.map +0 -1
  102. package/dist/trails/config-describe.js +0 -41
  103. package/dist/trails/config-describe.js.map +0 -1
  104. package/dist/trails/config-explain.d.ts +0 -8
  105. package/dist/trails/config-explain.d.ts.map +0 -1
  106. package/dist/trails/config-explain.js +0 -74
  107. package/dist/trails/config-explain.js.map +0 -1
  108. package/dist/trails/config-init.d.ts +0 -9
  109. package/dist/trails/config-init.d.ts.map +0 -1
  110. package/dist/trails/config-init.js +0 -78
  111. package/dist/trails/config-init.js.map +0 -1
  112. package/dist/workspace.d.ts +0 -9
  113. package/dist/workspace.d.ts.map +0 -1
  114. package/dist/workspace.js +0 -49
  115. package/dist/workspace.js.map +0 -1
  116. package/dist/zod-utils.d.ts +0 -14
  117. package/dist/zod-utils.d.ts.map +0 -1
  118. package/dist/zod-utils.js +0 -41
  119. package/dist/zod-utils.js.map +0 -1
  120. package/src/__tests__/app-config.test.ts +0 -342
  121. package/src/__tests__/compose.test.ts +0 -59
  122. package/src/__tests__/config-check.test.ts +0 -171
  123. package/src/__tests__/config-describe.test.ts +0 -154
  124. package/src/__tests__/config-explain.test.ts +0 -167
  125. package/src/__tests__/config-init.test.ts +0 -210
  126. package/src/__tests__/config-resource.test.ts +0 -87
  127. package/src/__tests__/define-config.test.ts +0 -269
  128. package/src/__tests__/derive-fields.test.ts +0 -158
  129. package/src/__tests__/derive-provenance.test.ts +0 -139
  130. package/src/__tests__/derive.test.ts +0 -269
  131. package/src/__tests__/doctor.test.ts +0 -172
  132. package/src/__tests__/extensions.test.ts +0 -134
  133. package/src/__tests__/ref.test.ts +0 -35
  134. package/src/__tests__/resolve.test.ts +0 -246
  135. package/src/__tests__/workspace.test.ts +0 -64
  136. package/src/workspace.ts +0 -56
  137. package/tsconfig.json +0 -9
  138. package/tsconfig.tests.json +0 -10
  139. package/tsconfig.tsbuildinfo +0 -1
@@ -1,269 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { z } from 'zod';
3
-
4
- import { deprecated, env, secret } from '../extensions.js';
5
- import {
6
- deriveConfigEnvExample,
7
- deriveConfigExample,
8
- deriveConfigJsonSchema,
9
- } from '../derive/index.js';
10
-
11
- /** Shared test schema used across generator tests. */
12
- const testSchema = z.object({
13
- host: env(z.string().describe('The server hostname'), 'HOST').default(
14
- 'localhost'
15
- ),
16
- port: env(z.number().describe('The server port'), 'PORT').default(3000),
17
- verbose: z.boolean().describe('Enable verbose logging').default(false),
18
- });
19
-
20
- /** Schema with deprecated and secret fields. */
21
- const annotatedSchema = z.object({
22
- apiKey: secret(env(z.string().describe('API authentication key'), 'API_KEY')),
23
- oldEndpoint: deprecated(
24
- env(z.string().describe('Legacy API endpoint'), 'OLD_ENDPOINT'),
25
- 'Use newEndpoint instead'
26
- ),
27
- });
28
-
29
- /** Schema with nested objects. */
30
- const nestedSchema = z.object({
31
- db: z.object({
32
- host: env(z.string().describe('Database host'), 'DB_HOST').default(
33
- 'localhost'
34
- ),
35
- port: env(z.number().describe('Database port'), 'DB_PORT').default(5432),
36
- }),
37
- server: z.object({
38
- name: z.string().describe('Server name').default('app'),
39
- }),
40
- });
41
-
42
- describe('deriveConfigExample()', () => {
43
- describe('TOML format', () => {
44
- test('produces valid TOML with comments for descriptions', () => {
45
- const result = deriveConfigExample(testSchema, 'toml');
46
-
47
- expect(result).toContain('# The server hostname');
48
- expect(result).toContain('host = "localhost"');
49
- expect(result).toContain('# The server port');
50
- expect(result).toContain('port = 3000');
51
- expect(result).toContain('# Enable verbose logging');
52
- expect(result).toContain('verbose = false');
53
- });
54
-
55
- test('annotates deprecated fields in TOML', () => {
56
- const result = deriveConfigExample(annotatedSchema, 'toml');
57
-
58
- expect(result).toContain('# DEPRECATED: Use newEndpoint instead');
59
- });
60
-
61
- test('handles nested objects as TOML sections', () => {
62
- const result = deriveConfigExample(nestedSchema, 'toml');
63
-
64
- expect(result).toContain('[db]');
65
- expect(result).toContain('[server]');
66
- expect(result).toContain('host = "localhost"');
67
- expect(result).toContain('port = 5432');
68
- });
69
- });
70
-
71
- describe('JSON format', () => {
72
- test('produces valid JSON without comments', () => {
73
- const result = deriveConfigExample(testSchema, 'json');
74
- const parsed = JSON.parse(result);
75
-
76
- expect(parsed).toEqual({
77
- host: 'localhost',
78
- port: 3000,
79
- verbose: false,
80
- });
81
- });
82
-
83
- test('handles nested objects', () => {
84
- const result = deriveConfigExample(nestedSchema, 'json');
85
- const parsed = JSON.parse(result);
86
-
87
- expect(parsed).toHaveProperty('db');
88
- expect(parsed).toHaveProperty('server');
89
- expect(parsed.db.host).toBe('localhost');
90
- });
91
- });
92
-
93
- describe('JSONC format', () => {
94
- test('produces JSON with // comments for descriptions', () => {
95
- const result = deriveConfigExample(testSchema, 'jsonc');
96
-
97
- expect(result).toContain('// The server hostname');
98
- expect(result).toContain('"host"');
99
- expect(result).toContain('"localhost"');
100
- });
101
-
102
- test('annotates deprecated fields in JSONC', () => {
103
- const result = deriveConfigExample(annotatedSchema, 'jsonc');
104
-
105
- expect(result).toContain('// DEPRECATED: Use newEndpoint instead');
106
- });
107
-
108
- test('handles nested objects', () => {
109
- const result = deriveConfigExample(nestedSchema, 'jsonc');
110
-
111
- expect(result).toContain('"db"');
112
- expect(result).toContain('"host"');
113
- expect(result).toContain('"localhost"');
114
- expect(result).not.toContain('"db": ""');
115
- });
116
- });
117
-
118
- describe('YAML format', () => {
119
- test('produces valid YAML with comments for descriptions', () => {
120
- const result = deriveConfigExample(testSchema, 'yaml');
121
-
122
- expect(result).toContain('# The server hostname');
123
- expect(result).toContain('host: "localhost"');
124
- expect(result).toContain('# The server port');
125
- expect(result).toContain('port: 3000');
126
- expect(result).toContain('# Enable verbose logging');
127
- expect(result).toContain('verbose: false');
128
- });
129
-
130
- test('annotates deprecated fields in YAML', () => {
131
- const result = deriveConfigExample(annotatedSchema, 'yaml');
132
-
133
- expect(result).toContain('# DEPRECATED: Use newEndpoint instead');
134
- });
135
-
136
- test('handles nested objects', () => {
137
- const result = deriveConfigExample(nestedSchema, 'yaml');
138
-
139
- expect(result).toContain('db:');
140
- expect(result).toContain(' host: "localhost"');
141
- expect(result).toContain('server:');
142
- });
143
- });
144
- });
145
-
146
- describe('deriveConfigJsonSchema()', () => {
147
- test('produces valid JSON Schema with $schema, type, and properties', () => {
148
- const result = deriveConfigJsonSchema(testSchema);
149
-
150
- expect(result.$schema).toBe('https://json-schema.org/draft/2020-12/schema');
151
- expect(result.type).toBe('object');
152
- expect(result.properties).toBeDefined();
153
- });
154
-
155
- test('includes title and description from options', () => {
156
- const result = deriveConfigJsonSchema(testSchema, {
157
- description: 'Server configuration',
158
- title: 'ServerConfig',
159
- });
160
-
161
- expect(result.title).toBe('ServerConfig');
162
- expect(result.description).toBe('Server configuration');
163
- });
164
-
165
- test('includes descriptions from .describe()', () => {
166
- const result = deriveConfigJsonSchema(testSchema);
167
- const props = result.properties as Record<string, Record<string, unknown>>;
168
-
169
- expect(props['host']?.description).toBe('The server hostname');
170
- expect(props['port']?.description).toBe('The server port');
171
- });
172
-
173
- test('includes defaults', () => {
174
- const result = deriveConfigJsonSchema(testSchema);
175
- const props = result.properties as Record<string, Record<string, unknown>>;
176
-
177
- expect(props['host']?.default).toBe('localhost');
178
- expect(props['port']?.default).toBe(3000);
179
- expect(props['verbose']?.default).toBe(false);
180
- });
181
-
182
- test('maps string, number, boolean, and enum types correctly', () => {
183
- const enumSchema = z.object({
184
- color: z.enum(['red', 'green', 'blue']).describe('The color'),
185
- count: z.number().describe('A count'),
186
- enabled: z.boolean().describe('Toggle'),
187
- name: z.string().describe('A name'),
188
- });
189
-
190
- const result = deriveConfigJsonSchema(enumSchema);
191
- const props = result.properties as Record<string, Record<string, unknown>>;
192
-
193
- expect(props['name']?.type).toBe('string');
194
- expect(props['count']?.type).toBe('number');
195
- expect(props['enabled']?.type).toBe('boolean');
196
- expect(props['color']?.enum).toEqual(['red', 'green', 'blue']);
197
- });
198
-
199
- test('marks deprecated fields', () => {
200
- const result = deriveConfigJsonSchema(annotatedSchema);
201
- const props = result.properties as Record<string, Record<string, unknown>>;
202
-
203
- expect(props['oldEndpoint']?.deprecated).toBe(true);
204
- });
205
-
206
- test('lists required fields (those without defaults or optional)', () => {
207
- const result = deriveConfigJsonSchema(annotatedSchema);
208
-
209
- expect(result.required).toContain('apiKey');
210
- expect(result.required).toContain('oldEndpoint');
211
- });
212
-
213
- test('recurses into nested object fields', () => {
214
- const result = deriveConfigJsonSchema(nestedSchema);
215
- const props = result.properties as Record<string, Record<string, unknown>>;
216
-
217
- expect(props['db']?.type).toBe('object');
218
- const dbProps = props['db']?.properties as Record<
219
- string,
220
- Record<string, unknown>
221
- >;
222
- expect(dbProps['host']?.type).toBe('string');
223
- expect(dbProps['host']?.description).toBe('Database host');
224
- expect(dbProps['port']?.type).toBe('number');
225
-
226
- expect(props['server']?.type).toBe('object');
227
- const serverProps = props['server']?.properties as Record<
228
- string,
229
- Record<string, unknown>
230
- >;
231
- expect(serverProps['name']?.type).toBe('string');
232
- });
233
- });
234
-
235
- describe('deriveConfigEnvExample()', () => {
236
- test('lists env vars with type info', () => {
237
- const result = deriveConfigEnvExample(testSchema);
238
-
239
- expect(result).toContain('HOST=');
240
- expect(result).toContain('PORT=');
241
- expect(result).toContain('string');
242
- expect(result).toContain('number');
243
- });
244
-
245
- test('annotates secrets', () => {
246
- const result = deriveConfigEnvExample(annotatedSchema);
247
-
248
- expect(result).toContain('API_KEY=');
249
- expect(result).toContain('secret');
250
- });
251
-
252
- test('shows defaults as comments', () => {
253
- const result = deriveConfigEnvExample(testSchema);
254
-
255
- expect(result).toContain('default: "localhost"');
256
- expect(result).toContain('default: 3000');
257
- });
258
-
259
- test('returns empty string when no env vars are present', () => {
260
- const noEnvSchema = z.object({
261
- name: z.string(),
262
- verbose: z.boolean().default(false),
263
- });
264
-
265
- const result = deriveConfigEnvExample(noEnvSchema);
266
-
267
- expect(result).toBe('');
268
- });
269
- });
@@ -1,172 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { z } from 'zod';
3
-
4
- import { deprecated, env } from '../extensions.js';
5
- import { checkConfig } from '../doctor.js';
6
-
7
- const schema = z.object({
8
- debug: z.boolean().default(false),
9
- host: z.string(),
10
- port: z.number().default(3000),
11
- });
12
-
13
- describe('checkConfig', () => {
14
- describe('valid fields', () => {
15
- test('reports status "valid" for present and valid fields', () => {
16
- const result = checkConfig(schema, { host: 'localhost', port: 8080 });
17
-
18
- const hostDiag = result.diagnostics.find((d) => d.path === 'host');
19
- expect(hostDiag?.status).toBe('valid');
20
- expect(hostDiag?.value).toBe('localhost');
21
- });
22
- });
23
-
24
- describe('missing required fields', () => {
25
- test('reports status "missing" for absent required fields', () => {
26
- const result = checkConfig(schema, { port: 8080 });
27
-
28
- const hostDiag = result.diagnostics.find((d) => d.path === 'host');
29
- expect(hostDiag?.status).toBe('missing');
30
- expect(hostDiag?.message).toContain('host');
31
- });
32
- });
33
-
34
- describe('default fields', () => {
35
- test('reports status "default" when field uses schema default', () => {
36
- const result = checkConfig(schema, { host: 'localhost' });
37
-
38
- const portDiag = result.diagnostics.find((d) => d.path === 'port');
39
- expect(portDiag?.status).toBe('default');
40
- expect(portDiag?.value).toBe(3000);
41
-
42
- const debugDiag = result.diagnostics.find((d) => d.path === 'debug');
43
- expect(debugDiag?.status).toBe('default');
44
- expect(debugDiag?.value).toBe(false);
45
- });
46
- });
47
-
48
- describe('invalid fields', () => {
49
- test('reports status "invalid" with message for wrong-type values', () => {
50
- const result = checkConfig(schema, {
51
- host: 'localhost',
52
- port: 'not-a-number',
53
- });
54
-
55
- const portDiag = result.diagnostics.find((d) => d.path === 'port');
56
- expect(portDiag?.status).toBe('invalid');
57
- expect(portDiag?.message).toBeDefined();
58
- });
59
- });
60
-
61
- describe('deprecated fields', () => {
62
- test('reports status "deprecated" with migration message', () => {
63
- const deprecatedSchema = z.object({
64
- host: z.string(),
65
- legacyMode: deprecated(z.boolean(), 'Use "mode" instead').default(
66
- false
67
- ),
68
- });
69
-
70
- const result = checkConfig(deprecatedSchema, {
71
- host: 'localhost',
72
- legacyMode: true,
73
- });
74
-
75
- const legacyDiag = result.diagnostics.find(
76
- (d) => d.path === 'legacyMode'
77
- );
78
- expect(legacyDiag?.status).toBe('deprecated');
79
- expect(legacyDiag?.message).toContain('Use "mode" instead');
80
- });
81
- });
82
-
83
- describe('valid flag', () => {
84
- test('returns valid: true when no missing or invalid fields', () => {
85
- const result = checkConfig(schema, { host: 'localhost', port: 8080 });
86
- expect(result.valid).toBe(true);
87
- });
88
-
89
- test('returns valid: false when required field is missing', () => {
90
- const result = checkConfig(schema, { port: 8080 });
91
- expect(result.valid).toBe(false);
92
- });
93
-
94
- test('returns valid: false when field is invalid', () => {
95
- const result = checkConfig(schema, {
96
- host: 'localhost',
97
- port: 'bad',
98
- });
99
- expect(result.valid).toBe(false);
100
- });
101
-
102
- test('returns valid: true when deprecated fields are present', () => {
103
- const deprecatedSchema = z.object({
104
- host: z.string(),
105
- legacyMode: deprecated(z.boolean(), 'Use "mode" instead').default(
106
- false
107
- ),
108
- });
109
-
110
- const result = checkConfig(deprecatedSchema, {
111
- host: 'localhost',
112
- legacyMode: true,
113
- });
114
- expect(result.valid).toBe(true);
115
- });
116
- });
117
-
118
- describe('env resolution', () => {
119
- test('treats env-provided value as valid', () => {
120
- const envSchema = z.object({
121
- host: env(z.string(), 'APP_HOST'),
122
- });
123
-
124
- const result = checkConfig(
125
- envSchema,
126
- {},
127
- { env: { APP_HOST: 'envhost' } }
128
- );
129
- const hostDiag = result.diagnostics.find((d) => d.path === 'host');
130
- expect(hostDiag?.status).toBe('valid');
131
- expect(hostDiag?.value).toBe('envhost');
132
- });
133
-
134
- test('resolves env vars for nested schema fields', () => {
135
- const nestedSchema = z.object({
136
- db: z.object({
137
- host: env(z.string(), 'DB_HOST'),
138
- port: z.number().default(5432),
139
- }),
140
- });
141
-
142
- const result = checkConfig(
143
- nestedSchema,
144
- { db: {} },
145
- { env: { DB_HOST: 'dbhost.local' } }
146
- );
147
- const dbHostDiag = result.diagnostics.find((d) => d.path === 'db.host');
148
- expect(dbHostDiag?.status).toBe('valid');
149
- expect(dbHostDiag?.value).toBe('dbhost.local');
150
- });
151
-
152
- test('walks optional nested object schemas for env overrides', () => {
153
- const nestedSchema = z.object({
154
- db: z
155
- .object({
156
- host: env(z.string(), 'DB_HOST'),
157
- })
158
- .optional(),
159
- });
160
-
161
- const result = checkConfig(
162
- nestedSchema,
163
- {},
164
- { env: { DB_HOST: 'dbhost.local' } }
165
- );
166
- const dbHostDiag = result.diagnostics.find((d) => d.path === 'db.host');
167
-
168
- expect(dbHostDiag?.status).toBe('valid');
169
- expect(dbHostDiag?.value).toBe('dbhost.local');
170
- });
171
- });
172
- });
@@ -1,134 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { z, globalRegistry } from 'zod';
3
-
4
- import { env, secret, deprecated } from '../extensions.js';
5
- import type { ConfigFieldMeta } from '../extensions.js';
6
- import { collectConfigMeta } from '../collect.js';
7
-
8
- describe('env()', () => {
9
- test('attaches env var name to schema metadata', () => {
10
- const schema = env(z.string(), 'DATABASE_URL');
11
- const meta = globalRegistry.get(schema) as ConfigFieldMeta | undefined;
12
-
13
- expect(meta).toBeDefined();
14
- expect(meta?.env).toBe('DATABASE_URL');
15
- });
16
- });
17
-
18
- describe('secret()', () => {
19
- test('attaches secret flag to schema metadata', () => {
20
- const schema = secret(z.string());
21
- const meta = globalRegistry.get(schema) as ConfigFieldMeta | undefined;
22
-
23
- expect(meta).toBeDefined();
24
- expect(meta?.secret).toBe(true);
25
- });
26
- });
27
-
28
- describe('deprecated()', () => {
29
- test('attaches deprecation message to schema metadata', () => {
30
- const schema = deprecated(z.string(), 'Use NEW_VAR instead');
31
- const raw = globalRegistry.get(schema) as
32
- | Record<string, unknown>
33
- | undefined;
34
-
35
- expect(raw).toBeDefined();
36
- // Zod 4 reserves `deprecated` as boolean, so the message is stored
37
- // under `deprecationMessage` and the boolean flag is set.
38
- expect(raw?.['deprecated']).toBe(true);
39
- expect(raw?.['deprecationMessage']).toBe('Use NEW_VAR instead');
40
- });
41
- });
42
-
43
- describe('wrapper ordering', () => {
44
- test('metadata survives through .default() when applied BEFORE the transform', () => {
45
- const schema = env(z.string(), 'HOST').default('localhost');
46
- // Metadata lives on the inner type, not the wrapper
47
- const innerMeta = globalRegistry.get(schema.def.innerType) as
48
- | ConfigFieldMeta
49
- | undefined;
50
-
51
- expect(innerMeta).toBeDefined();
52
- expect(innerMeta?.env).toBe('HOST');
53
- });
54
-
55
- test('metadata is NOT on the wrapper when applied AFTER .default()', () => {
56
- // Applying env() after .default() attaches metadata to the ZodDefault wrapper,
57
- // but collectConfigMeta walks .def.innerType — so the inner string has no metadata.
58
- const schema = env(z.string().default('localhost'), 'HOST');
59
- // The wrapper itself has the metadata
60
- const wrapperMeta = globalRegistry.get(schema) as
61
- | ConfigFieldMeta
62
- | undefined;
63
- expect(wrapperMeta?.env).toBe('HOST');
64
-
65
- // But the inner type does NOT
66
- const innerMeta = globalRegistry.get(schema.def.innerType) as
67
- | ConfigFieldMeta
68
- | undefined;
69
- expect(innerMeta?.env).toBeUndefined();
70
- });
71
- });
72
-
73
- describe('composition', () => {
74
- test('multiple wrappers compose: secret(env()) has both env and secret', () => {
75
- const schema = secret(env(z.string(), 'DB_URL'));
76
- const meta = globalRegistry.get(schema) as ConfigFieldMeta | undefined;
77
-
78
- expect(meta).toBeDefined();
79
- expect(meta?.env).toBe('DB_URL');
80
- expect(meta?.secret).toBe(true);
81
- });
82
- });
83
-
84
- describe('describe() preservation', () => {
85
- test('.describe() text is preserved alongside custom metadata', () => {
86
- const schema = env(z.string().describe('The database host'), 'DB_HOST');
87
- const meta = globalRegistry.get(schema) as
88
- | Record<string, unknown>
89
- | undefined;
90
-
91
- expect(meta).toBeDefined();
92
- expect(meta?.env).toBe('DB_HOST');
93
- expect(meta?.description).toBe('The database host');
94
- });
95
- });
96
-
97
- describe('collectConfigMeta()', () => {
98
- test('walks an object schema and returns all field metadata', () => {
99
- const schema = z.object({
100
- apiKey: secret(env(z.string(), 'API_KEY')),
101
- host: env(z.string(), 'HOST').default('localhost'),
102
- oldVar: deprecated(z.string(), 'Use newVar instead').optional(),
103
- port: env(z.number(), 'PORT'),
104
- });
105
-
106
- const meta = collectConfigMeta(schema);
107
-
108
- expect(meta.get('host')).toEqual({ env: 'HOST' });
109
- expect(meta.get('port')).toEqual({ env: 'PORT' });
110
- expect(meta.get('apiKey')).toEqual({ env: 'API_KEY', secret: true });
111
- expect(meta.get('oldVar')).toEqual({ deprecated: 'Use newVar instead' });
112
- });
113
-
114
- test('handles nested objects with dot-separated paths', () => {
115
- const schema = z.object({
116
- cache: z.object({
117
- ttl: env(z.number(), 'CACHE_TTL').default(3600),
118
- }),
119
- db: z.object({
120
- host: env(z.string(), 'DB_HOST'),
121
- password: secret(env(z.string(), 'DB_PASSWORD')),
122
- }),
123
- });
124
-
125
- const meta = collectConfigMeta(schema);
126
-
127
- expect(meta.get('db.host')).toEqual({ env: 'DB_HOST' });
128
- expect(meta.get('db.password')).toEqual({
129
- env: 'DB_PASSWORD',
130
- secret: true,
131
- });
132
- expect(meta.get('cache.ttl')).toEqual({ env: 'CACHE_TTL' });
133
- });
134
- });
@@ -1,35 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
-
3
- import { configRef, isConfigRef } from '../ref.js';
4
-
5
- describe('configRef', () => {
6
- test('creates a marker object with __configRef flag', () => {
7
- const ref = configRef('db.host');
8
- expect(ref.__configRef).toBe(true);
9
- expect(ref.path).toBe('db.host');
10
- });
11
-
12
- test('creates distinct refs for different paths', () => {
13
- const ref1 = configRef('db.host');
14
- const ref2 = configRef('db.port');
15
- expect(ref1.path).not.toBe(ref2.path);
16
- });
17
- });
18
-
19
- describe('isConfigRef', () => {
20
- test('returns true for configRef markers', () => {
21
- const ref = configRef('db.host');
22
- expect(isConfigRef(ref)).toBe(true);
23
- });
24
-
25
- test('returns false for plain objects', () => {
26
- expect(isConfigRef({ path: 'db.host' })).toBe(false);
27
- });
28
-
29
- test('returns false for non-objects', () => {
30
- expect(isConfigRef('db.host')).toBe(false);
31
- expect(isConfigRef(42)).toBe(false);
32
- expect(isConfigRef(null)).toBe(false);
33
- expect(isConfigRef()).toBe(false);
34
- });
35
- });