@optique/config 1.0.0-dev.429 → 1.0.0-dev.432

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/index.d.ts CHANGED
@@ -1,2 +1,244 @@
1
- import { BindConfigOptions, ConfigContext, ConfigContextOptions, ConfigContextRequiredOptions, ConfigMeta, bindConfig, clearActiveConfig, clearActiveConfigMeta, configKey, configMetaKey, createConfigContext, getActiveConfig, getActiveConfigMeta, setActiveConfig, setActiveConfigMeta } from "./index-0XaZnIP1.js";
2
- export { BindConfigOptions, ConfigContext, ConfigContextOptions, ConfigContextRequiredOptions, ConfigMeta, bindConfig, clearActiveConfig, clearActiveConfigMeta, configKey, configMetaKey, createConfigContext, getActiveConfig, getActiveConfigMeta, setActiveConfig, setActiveConfigMeta };
1
+ import { StandardSchemaV1 } from "@standard-schema/spec";
2
+ import { ParserValuePlaceholder, SourceContext } from "@optique/core/context";
3
+ import { Parser } from "@optique/core/parser";
4
+
5
+ //#region src/index.d.ts
6
+
7
+ /**
8
+ * Unique symbol for config data in annotations.
9
+ * @since 0.10.0
10
+ */
11
+ declare const configKey: unique symbol;
12
+ /**
13
+ * Unique symbol for config metadata in annotations.
14
+ * @since 1.0.0
15
+ */
16
+ declare const configMetaKey: unique symbol;
17
+ /**
18
+ * Metadata about the loaded config source.
19
+ *
20
+ * @since 1.0.0
21
+ */
22
+ interface ConfigMeta {
23
+ /**
24
+ * Directory containing the config file.
25
+ */
26
+ readonly configDir: string;
27
+ /**
28
+ * Absolute path to the config file.
29
+ */
30
+ readonly configPath: string;
31
+ }
32
+ /**
33
+ * Sets active config data for a context.
34
+ * @internal
35
+ */
36
+ declare function setActiveConfig<T>(contextId: symbol, data: T): void;
37
+ /**
38
+ * Gets active config data for a context.
39
+ * @internal
40
+ */
41
+ declare function getActiveConfig<T>(contextId: symbol): T | undefined;
42
+ /**
43
+ * Clears active config data for a context.
44
+ * @internal
45
+ */
46
+ declare function clearActiveConfig(contextId: symbol): void;
47
+ /**
48
+ * Sets active config metadata for a context.
49
+ * @internal
50
+ */
51
+ declare function setActiveConfigMeta<T>(contextId: symbol, meta: T): void;
52
+ /**
53
+ * Gets active config metadata for a context.
54
+ * @internal
55
+ */
56
+ declare function getActiveConfigMeta<T>(contextId: symbol): T | undefined;
57
+ /**
58
+ * Clears active config metadata for a context.
59
+ * @internal
60
+ */
61
+ declare function clearActiveConfigMeta(contextId: symbol): void;
62
+ /**
63
+ * Options for creating a config context.
64
+ *
65
+ * @template T The output type of the config schema.
66
+ * @since 0.10.0
67
+ */
68
+ interface ConfigContextOptions<T> {
69
+ /**
70
+ * Standard Schema validator for the config file.
71
+ * Accepts any Standard Schema-compatible library (Zod, Valibot, ArkType, etc.).
72
+ */
73
+ readonly schema: StandardSchemaV1<unknown, T>;
74
+ /**
75
+ * Custom parser function for reading config file contents.
76
+ * If not provided, defaults to JSON.parse.
77
+ *
78
+ * This option is only used in single-file mode (with `getConfigPath`).
79
+ * When using the `load` callback, file parsing is handled by the loader.
80
+ *
81
+ * @param contents The raw file contents as Uint8Array.
82
+ * @returns The parsed config data (will be validated by schema).
83
+ * @since 1.0.0
84
+ */
85
+ readonly fileParser?: (contents: Uint8Array) => unknown;
86
+ }
87
+ /**
88
+ * Result type for custom config loading.
89
+ *
90
+ * @template TConfigMeta Metadata type associated with loaded config data.
91
+ * @since 1.0.0
92
+ */
93
+ interface ConfigLoadResult<TConfigMeta = ConfigMeta> {
94
+ /**
95
+ * Raw config data to validate against the schema.
96
+ */
97
+ readonly config: unknown;
98
+ /**
99
+ * Metadata about where the config came from.
100
+ */
101
+ readonly meta: TConfigMeta;
102
+ }
103
+ /**
104
+ * Required options for ConfigContext when used with `runWith()` or `run()`.
105
+ * The `ParserValuePlaceholder` will be substituted with the actual parser
106
+ * result type by `runWith()`.
107
+ *
108
+ * Provide *either* `getConfigPath` (single-file mode) *or* `load` (custom
109
+ * multi-file mode). At least one must be provided; a runtime error is
110
+ * thrown otherwise.
111
+ *
112
+ * @template TConfigMeta Metadata type for config sources.
113
+ * @since 0.10.0
114
+ */
115
+ interface ConfigContextRequiredOptions<TConfigMeta = ConfigMeta> {
116
+ /**
117
+ * Function to extract config file path from parsed CLI arguments.
118
+ * Used in single-file mode. The `parsed` parameter is typed as the
119
+ * parser's result type.
120
+ *
121
+ * @param parsed The parsed CLI arguments (typed from parser).
122
+ * @returns The config file path, or undefined if not specified.
123
+ */
124
+ readonly getConfigPath?: (parsed: ParserValuePlaceholder) => string | undefined;
125
+ /**
126
+ * Custom loader function that receives the first-pass parse result and
127
+ * returns the config data (or a Promise of it). This allows full control
128
+ * over file discovery, loading, merging, and error handling.
129
+ *
130
+ * The returned data will be validated against the schema.
131
+ *
132
+ * When `load` is provided, `getConfigPath` is ignored.
133
+ *
134
+ * @param parsed The result from the first parse pass.
135
+ * @returns Config data and metadata (config is validated by schema).
136
+ * @since 1.0.0
137
+ */
138
+ readonly load?: (parsed: ParserValuePlaceholder) => Promise<ConfigLoadResult<TConfigMeta>> | ConfigLoadResult<TConfigMeta>;
139
+ }
140
+ /**
141
+ * A config context that provides configuration data via annotations.
142
+ *
143
+ * When used with `runWith()` or `run()`, the options must include either
144
+ * `getConfigPath` or `load` with the correct parser result type. The
145
+ * `ParserValuePlaceholder` in `ConfigContextRequiredOptions` is substituted
146
+ * with the actual parser type.
147
+ *
148
+ * @template T The validated config data type.
149
+ * @template TConfigMeta Metadata type for config sources.
150
+ * @since 0.10.0
151
+ */
152
+ interface ConfigContext<T, TConfigMeta = ConfigMeta> extends SourceContext<ConfigContextRequiredOptions<TConfigMeta>> {
153
+ /**
154
+ * The Standard Schema validator for the config file.
155
+ */
156
+ readonly schema: StandardSchemaV1<unknown, T>;
157
+ }
158
+ /**
159
+ * Creates a config context for use with Optique parsers.
160
+ *
161
+ * The config context implements the `SourceContext` interface and can be used
162
+ * with `runWith()` from *@optique/core* or `run()`/`runAsync()` from
163
+ * *@optique/run* to provide configuration file support.
164
+ *
165
+ * @template T The output type of the config schema.
166
+ * @template TConfigMeta The metadata type for config sources.
167
+ * @param options Configuration options including schema and optional file
168
+ * parser.
169
+ * @returns A config context that can be used with `bindConfig()` and runners.
170
+ * @since 0.10.0
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * import { z } from "zod";
175
+ * import { createConfigContext } from "@optique/config";
176
+ *
177
+ * const schema = z.object({
178
+ * host: z.string(),
179
+ * port: z.number(),
180
+ * });
181
+ *
182
+ * const configContext = createConfigContext({ schema });
183
+ * ```
184
+ */
185
+ declare function createConfigContext<T, TConfigMeta = ConfigMeta>(options: ConfigContextOptions<T>): ConfigContext<T, TConfigMeta>;
186
+ /**
187
+ * Options for binding a parser to config values.
188
+ *
189
+ * @template T The config data type.
190
+ * @template TValue The value type extracted from config.
191
+ * @since 0.10.0
192
+ */
193
+ interface BindConfigOptions<T, TValue, TConfigMeta = ConfigMeta> {
194
+ /**
195
+ * The config context to use for fallback values.
196
+ */
197
+ readonly context: ConfigContext<T, TConfigMeta>;
198
+ /**
199
+ * Key or accessor function to extract the value from config.
200
+ * Can be a property key (for top-level config values) or a function
201
+ * that extracts nested values. Accessor callbacks receive config metadata
202
+ * as the second argument.
203
+ */
204
+ readonly key: keyof T | ((config: T, meta: TConfigMeta) => TValue);
205
+ /**
206
+ * Default value to use when neither CLI nor config provides a value.
207
+ * If not specified, the parser will fail when no value is available.
208
+ */
209
+ readonly default?: TValue;
210
+ }
211
+ /**
212
+ * Binds a parser to configuration values with fallback priority.
213
+ *
214
+ * The binding implements the following priority order:
215
+ * 1. CLI argument (if provided)
216
+ * 2. Config file value (if available)
217
+ * 3. Default value (if specified)
218
+ * 4. Error (if none of the above)
219
+ *
220
+ * @template M The parser mode (sync or async).
221
+ * @template TValue The parser value type.
222
+ * @template TState The parser state type.
223
+ * @template T The config data type.
224
+ * @param parser The parser to bind to config values.
225
+ * @param options Binding options including context, key, and default.
226
+ * @returns A new parser with config fallback behavior.
227
+ * @since 0.10.0
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * import { bindConfig } from "@optique/config";
232
+ * import { option } from "@optique/core/primitives";
233
+ * import { string } from "@optique/core/valueparser";
234
+ *
235
+ * const hostParser = bindConfig(option("--host", string()), {
236
+ * context: configContext,
237
+ * key: "host",
238
+ * default: "localhost",
239
+ * });
240
+ * ```
241
+ */
242
+ declare function bindConfig<M extends "sync" | "async", TValue, TState, T, TConfigMeta = ConfigMeta>(parser: Parser<M, TValue, TState>, options: BindConfigOptions<T, TValue, TConfigMeta>): Parser<M, TValue, TState>;
243
+ //#endregion
244
+ export { BindConfigOptions, ConfigContext, ConfigContextOptions, ConfigContextRequiredOptions, ConfigLoadResult, ConfigMeta, bindConfig, clearActiveConfig, clearActiveConfigMeta, configKey, configMetaKey, createConfigContext, getActiveConfig, getActiveConfigMeta, setActiveConfig, setActiveConfigMeta };
package/dist/index.js CHANGED
@@ -1,3 +1,341 @@
1
- import { bindConfig, clearActiveConfig, clearActiveConfigMeta, configKey, configMetaKey, createConfigContext, getActiveConfig, getActiveConfigMeta, setActiveConfig, setActiveConfigMeta } from "./src-DaFxoeAp.js";
1
+ import { readFile } from "node:fs/promises";
2
+ import { dirname, resolve } from "node:path";
3
+ import { annotationKey, getAnnotations } from "@optique/core/annotations";
4
+ import { message } from "@optique/core/message";
2
5
 
6
+ //#region src/index.ts
7
+ /**
8
+ * Unique symbol for config data in annotations.
9
+ * @since 0.10.0
10
+ */
11
+ const configKey = Symbol.for("@optique/config");
12
+ /**
13
+ * Unique symbol for config metadata in annotations.
14
+ * @since 1.0.0
15
+ */
16
+ const configMetaKey = Symbol.for("@optique/config/meta");
17
+ /**
18
+ * Internal registry for active config data during config context execution.
19
+ * This is a workaround for the limitation that object() doesn't propagate
20
+ * annotations to child field parsers.
21
+ * @internal
22
+ */
23
+ const activeConfigRegistry = /* @__PURE__ */ new Map();
24
+ /**
25
+ * Internal registry for active config metadata during config context execution.
26
+ * @internal
27
+ */
28
+ const activeConfigMetaRegistry = /* @__PURE__ */ new Map();
29
+ /**
30
+ * Sets active config data for a context.
31
+ * @internal
32
+ */
33
+ function setActiveConfig(contextId, data) {
34
+ activeConfigRegistry.set(contextId, data);
35
+ }
36
+ /**
37
+ * Gets active config data for a context.
38
+ * @internal
39
+ */
40
+ function getActiveConfig(contextId) {
41
+ return activeConfigRegistry.get(contextId);
42
+ }
43
+ /**
44
+ * Clears active config data for a context.
45
+ * @internal
46
+ */
47
+ function clearActiveConfig(contextId) {
48
+ activeConfigRegistry.delete(contextId);
49
+ }
50
+ /**
51
+ * Sets active config metadata for a context.
52
+ * @internal
53
+ */
54
+ function setActiveConfigMeta(contextId, meta) {
55
+ activeConfigMetaRegistry.set(contextId, meta);
56
+ }
57
+ /**
58
+ * Gets active config metadata for a context.
59
+ * @internal
60
+ */
61
+ function getActiveConfigMeta(contextId) {
62
+ return activeConfigMetaRegistry.get(contextId);
63
+ }
64
+ /**
65
+ * Clears active config metadata for a context.
66
+ * @internal
67
+ */
68
+ function clearActiveConfigMeta(contextId) {
69
+ activeConfigMetaRegistry.delete(contextId);
70
+ }
71
+ function isErrnoException(error) {
72
+ return typeof error === "object" && error !== null && "code" in error;
73
+ }
74
+ /**
75
+ * Validates raw data against a Standard Schema and returns the validated value.
76
+ * @internal
77
+ */
78
+ async function validateWithSchema(schema, rawData) {
79
+ const validation = schema["~standard"].validate(rawData);
80
+ const validationResult = validation instanceof Promise ? await validation : validation;
81
+ if (validationResult.issues) {
82
+ const firstIssue = validationResult.issues[0];
83
+ throw new Error(`Config validation failed: ${firstIssue?.message ?? "Unknown error"}`);
84
+ }
85
+ return validationResult.value;
86
+ }
87
+ /**
88
+ * Creates a config context for use with Optique parsers.
89
+ *
90
+ * The config context implements the `SourceContext` interface and can be used
91
+ * with `runWith()` from *@optique/core* or `run()`/`runAsync()` from
92
+ * *@optique/run* to provide configuration file support.
93
+ *
94
+ * @template T The output type of the config schema.
95
+ * @template TConfigMeta The metadata type for config sources.
96
+ * @param options Configuration options including schema and optional file
97
+ * parser.
98
+ * @returns A config context that can be used with `bindConfig()` and runners.
99
+ * @since 0.10.0
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * import { z } from "zod";
104
+ * import { createConfigContext } from "@optique/config";
105
+ *
106
+ * const schema = z.object({
107
+ * host: z.string(),
108
+ * port: z.number(),
109
+ * });
110
+ *
111
+ * const configContext = createConfigContext({ schema });
112
+ * ```
113
+ */
114
+ function createConfigContext(options) {
115
+ const contextId = Symbol.for(`@optique/config:${Math.random()}`);
116
+ return {
117
+ id: contextId,
118
+ schema: options.schema,
119
+ async getAnnotations(parsed, runtimeOptions) {
120
+ if (!parsed) return {};
121
+ const opts = runtimeOptions;
122
+ if (!opts || !opts.getConfigPath && !opts.load) throw new TypeError("Either getConfigPath or load must be provided in the runner options when using ConfigContext.");
123
+ let configData;
124
+ let configMeta;
125
+ const parsedValue = parsed;
126
+ if (opts.load) {
127
+ const loaded = await Promise.resolve(opts.load(parsedValue));
128
+ configData = await validateWithSchema(options.schema, loaded.config);
129
+ configMeta = loaded.meta;
130
+ } else if (opts.getConfigPath) {
131
+ const configPath = opts.getConfigPath(parsedValue);
132
+ if (configPath) {
133
+ const absoluteConfigPath = resolve(configPath);
134
+ const singleFileMeta = {
135
+ configDir: dirname(absoluteConfigPath),
136
+ configPath: absoluteConfigPath
137
+ };
138
+ try {
139
+ const contents = await readFile(absoluteConfigPath);
140
+ let rawData;
141
+ if (options.fileParser) rawData = options.fileParser(contents);
142
+ else {
143
+ const text = new TextDecoder().decode(contents);
144
+ rawData = JSON.parse(text);
145
+ }
146
+ configData = await validateWithSchema(options.schema, rawData);
147
+ configMeta = singleFileMeta;
148
+ } catch (error) {
149
+ if (isErrnoException(error) && error.code === "ENOENT") configData = void 0;
150
+ else if (error instanceof SyntaxError) throw new Error(`Failed to parse config file ${absoluteConfigPath}: ${error.message}`);
151
+ else throw error;
152
+ }
153
+ }
154
+ }
155
+ if (configData !== void 0 && configData !== null) {
156
+ setActiveConfig(contextId, configData);
157
+ if (configMeta !== void 0) {
158
+ setActiveConfigMeta(contextId, configMeta);
159
+ return {
160
+ [configKey]: configData,
161
+ [configMetaKey]: configMeta
162
+ };
163
+ }
164
+ return { [configKey]: configData };
165
+ }
166
+ return {};
167
+ },
168
+ [Symbol.dispose]() {
169
+ clearActiveConfig(contextId);
170
+ clearActiveConfigMeta(contextId);
171
+ }
172
+ };
173
+ }
174
+ /**
175
+ * Binds a parser to configuration values with fallback priority.
176
+ *
177
+ * The binding implements the following priority order:
178
+ * 1. CLI argument (if provided)
179
+ * 2. Config file value (if available)
180
+ * 3. Default value (if specified)
181
+ * 4. Error (if none of the above)
182
+ *
183
+ * @template M The parser mode (sync or async).
184
+ * @template TValue The parser value type.
185
+ * @template TState The parser state type.
186
+ * @template T The config data type.
187
+ * @param parser The parser to bind to config values.
188
+ * @param options Binding options including context, key, and default.
189
+ * @returns A new parser with config fallback behavior.
190
+ * @since 0.10.0
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * import { bindConfig } from "@optique/config";
195
+ * import { option } from "@optique/core/primitives";
196
+ * import { string } from "@optique/core/valueparser";
197
+ *
198
+ * const hostParser = bindConfig(option("--host", string()), {
199
+ * context: configContext,
200
+ * key: "host",
201
+ * default: "localhost",
202
+ * });
203
+ * ```
204
+ */
205
+ function bindConfig(parser, options) {
206
+ return {
207
+ $mode: parser.$mode,
208
+ $valueType: parser.$valueType,
209
+ $stateType: parser.$stateType,
210
+ priority: parser.priority,
211
+ usage: options.default !== void 0 ? [{
212
+ type: "optional",
213
+ terms: parser.usage
214
+ }] : parser.usage,
215
+ initialState: parser.initialState,
216
+ parse: (context) => {
217
+ const annotations = getAnnotations(context.state);
218
+ const result = parser.parse(context);
219
+ if (!(result instanceof Promise)) {
220
+ if (result.success) {
221
+ const newState$1 = {
222
+ hasCliValue: true,
223
+ cliState: result.next.state,
224
+ ...annotations && { [annotationKey]: annotations }
225
+ };
226
+ return {
227
+ success: true,
228
+ next: {
229
+ ...result.next,
230
+ state: newState$1
231
+ },
232
+ consumed: result.consumed
233
+ };
234
+ }
235
+ const newState = {
236
+ hasCliValue: false,
237
+ ...annotations && { [annotationKey]: annotations }
238
+ };
239
+ return {
240
+ success: true,
241
+ next: {
242
+ ...context,
243
+ state: newState
244
+ },
245
+ consumed: []
246
+ };
247
+ }
248
+ return result.then((res) => {
249
+ if (res.success) {
250
+ const newState$1 = {
251
+ hasCliValue: true,
252
+ cliState: res.next.state,
253
+ ...annotations && { [annotationKey]: annotations }
254
+ };
255
+ return {
256
+ success: true,
257
+ next: {
258
+ ...res.next,
259
+ state: newState$1
260
+ },
261
+ consumed: res.consumed
262
+ };
263
+ }
264
+ const newState = {
265
+ hasCliValue: false,
266
+ ...annotations && { [annotationKey]: annotations }
267
+ };
268
+ return {
269
+ success: true,
270
+ next: {
271
+ ...context,
272
+ state: newState
273
+ },
274
+ consumed: []
275
+ };
276
+ });
277
+ },
278
+ complete: (state) => {
279
+ const bindState = state;
280
+ if (bindState?.hasCliValue && bindState.cliState !== void 0) {
281
+ const innerResult = parser.complete(bindState.cliState);
282
+ if (innerResult instanceof Promise) return innerResult.then((res) => {
283
+ if (res.success) return {
284
+ success: true,
285
+ value: res.value
286
+ };
287
+ return res;
288
+ });
289
+ if (innerResult.success) return {
290
+ success: true,
291
+ value: innerResult.value
292
+ };
293
+ return innerResult;
294
+ }
295
+ return getConfigOrDefault(state, options);
296
+ },
297
+ suggest: parser.suggest,
298
+ getDocFragments(state, upperDefaultValue) {
299
+ const defaultValue = upperDefaultValue ?? options.default;
300
+ return parser.getDocFragments(state, defaultValue);
301
+ }
302
+ };
303
+ }
304
+ /**
305
+ * Helper function to get value from config or default.
306
+ * Checks both annotations (for top-level parsers) and the active config
307
+ * registry (for parsers nested inside object() when used with context-aware
308
+ * runners).
309
+ */
310
+ function getConfigOrDefault(state, options) {
311
+ const annotations = getAnnotations(state);
312
+ let configData = annotations?.[configKey];
313
+ let configMeta = annotations?.[configMetaKey];
314
+ if (configData === void 0 || configData === null) {
315
+ const contextId = options.context.id;
316
+ configData = getActiveConfig(contextId);
317
+ configMeta = getActiveConfigMeta(contextId);
318
+ }
319
+ let configValue;
320
+ if (configData !== void 0 && configData !== null) if (typeof options.key === "function") try {
321
+ configValue = options.key(configData, configMeta);
322
+ } catch {
323
+ configValue = void 0;
324
+ }
325
+ else configValue = configData[options.key];
326
+ if (configValue !== void 0) return {
327
+ success: true,
328
+ value: configValue
329
+ };
330
+ if (options.default !== void 0) return {
331
+ success: true,
332
+ value: options.default
333
+ };
334
+ return {
335
+ success: false,
336
+ error: message`Missing required configuration value.`
337
+ };
338
+ }
339
+
340
+ //#endregion
3
341
  export { bindConfig, clearActiveConfig, clearActiveConfigMeta, configKey, configMetaKey, createConfigContext, getActiveConfig, getActiveConfigMeta, setActiveConfig, setActiveConfigMeta };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/config",
3
- "version": "1.0.0-dev.429+66edc8ed",
3
+ "version": "1.0.0-dev.432+dddcca3d",
4
4
  "description": "Configuration file support for Optique with Standard Schema validation",
5
5
  "keywords": [
6
6
  "CLI",
@@ -52,14 +52,6 @@
52
52
  },
53
53
  "import": "./dist/index.js",
54
54
  "require": "./dist/index.cjs"
55
- },
56
- "./run": {
57
- "types": {
58
- "import": "./dist/run.d.ts",
59
- "require": "./dist/run.d.cts"
60
- },
61
- "import": "./dist/run.js",
62
- "require": "./dist/run.cjs"
63
55
  }
64
56
  },
65
57
  "sideEffects": false,
@@ -67,7 +59,7 @@
67
59
  "@standard-schema/spec": "^1.1.0"
68
60
  },
69
61
  "dependencies": {
70
- "@optique/core": "1.0.0-dev.429+66edc8ed"
62
+ "@optique/core": "1.0.0-dev.432+dddcca3d"
71
63
  },
72
64
  "devDependencies": {
73
65
  "@standard-schema/spec": "^1.1.0",