@arkstack/common 0.14.18 → 0.14.19

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
@@ -172,7 +172,75 @@ declare class Logger {
172
172
  //#endregion
173
173
  //#region src/types.d.ts
174
174
  interface ConfigRegistry {}
175
- interface EnvRegistry {}
175
+ /**
176
+ * Map of known environment variables to their (coerced) value types.
177
+ *
178
+ * Used to give {@link GlobalEnv | env()} precise return types. Unknown keys fall
179
+ * back to `string`. Augment this interface (declaration merging) to register
180
+ * application-specific variables:
181
+ *
182
+ * ```ts
183
+ * declare module '@arkstack/common' {
184
+ * interface EnvRegistry { MY_FLAG: boolean }
185
+ * }
186
+ * ```
187
+ */
188
+ interface EnvRegistry {
189
+ APP_NAME: string;
190
+ APP_ENV: 'development' | 'production' | 'staging' | 'local';
191
+ APP_KEY: string;
192
+ APP_URL: string;
193
+ APP_HOST: string;
194
+ APP_PORT: number;
195
+ NODE_ENV: 'development' | 'production' | 'test';
196
+ PORT: number;
197
+ HOST: string;
198
+ FRONTEND_URL: string;
199
+ OUTPUT_DIR: string;
200
+ OUTPUT_DIR_DEV: string;
201
+ CONFIG_PATH: string;
202
+ TUNNEL: boolean;
203
+ FILESYSTEM_DISK: string;
204
+ CACHE_STORE: string;
205
+ CACHE_PREFIX: string;
206
+ CACHE_TABLE: string;
207
+ QUEUE_CONNECTION: string;
208
+ QUEUE_TABLE: string;
209
+ QUEUE_NAME: string;
210
+ QUEUE_RETRY_AFTER: number;
211
+ REDIS_HOST: string;
212
+ REDIS_PORT: number;
213
+ REDIS_PASSWORD: string;
214
+ REDIS_CACHE_DB: number;
215
+ REDIS_QUEUE_DB: number;
216
+ JWT_EXPIRES_IN: string;
217
+ SESSION_LIFETIME: number;
218
+ TWO_FACTOR_SMS_TTL_MINUTES: number;
219
+ DATABASE_URL: string;
220
+ DB_CONNECTION: string;
221
+ DB_HOST: string;
222
+ DB_PORT: number;
223
+ DB_DATABASE: string;
224
+ DB_USERNAME: string;
225
+ DB_PASSWORD: string;
226
+ MAIL_HOST: string;
227
+ MAIL_PORT: number;
228
+ MAIL_SECURE: boolean;
229
+ MAIL_USERNAME: string;
230
+ MAIL_PASSWORD: string;
231
+ MAIL_FROM_ADDRESS: string;
232
+ MAIL_TEST_ADDRESS: string;
233
+ AWS_ACCESS_KEY_ID: string;
234
+ AWS_SECRET_ACCESS_KEY: string;
235
+ AWS_DEFAULT_REGION: string;
236
+ AWS_BUCKET: string;
237
+ AWS_URL: string;
238
+ AWS_ENDPOINT: string;
239
+ }
240
+ /** Known environment variable names. */
241
+ type EnvKey = keyof EnvRegistry & string;
242
+ /** The registered type for a known key, or `string` for an unknown one. */
243
+ type EnvLookup<K extends string> = [K] extends [EnvKey] ? EnvRegistry[K] : string;
176
244
  type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never;
177
245
  type MergedConfig<X> = UnionToIntersection<X>;
178
246
  type Primitive = string | number | boolean | null | undefined | Function;
@@ -196,8 +264,17 @@ interface LoggerLog {
196
264
  <L extends boolean>(config: LoggerParseSignature, joiner?: string, log?: L, sc?: LoggerChalk): L extends true ? void : string;
197
265
  <L extends boolean>(config?: LoggerParseSignature, joiner?: string, log?: L, sc?: LoggerChalk): L extends true ? void : string | Logger;
198
266
  }
267
+ /**
268
+ * Return type of {@link GlobalEnv | env()}.
269
+ *
270
+ * When an explicit value type `X` is given it wins (backward compatible with
271
+ * `env<boolean>('FLAG')`). Otherwise the type registered for the key `K` is used
272
+ * — falling back to `string` for unknown keys. A provided default `D` is unioned
273
+ * into the result.
274
+ */
275
+ type EnvReturn<X, K extends string, D> = [X] extends [never] ? [D] extends [undefined] ? EnvLookup<K> : EnvLookup<K> | D : [D] extends [undefined] ? X : X | D;
199
276
  interface GlobalEnv {
200
- <X = string, Y = undefined | X>(env: string, defaultValue?: Y): Y extends undefined ? X : Y;
277
+ <X = never, D = undefined, K extends string = string>(env: K, defaultValue?: D): EnvReturn<X, K, D>;
201
278
  }
202
279
  type ConfigShape = keyof ConfigRegistry extends never ? Record<string, any> : ConfigRegistry;
203
280
  interface GlobalConfig {
@@ -285,7 +362,6 @@ declare const env: GlobalEnv;
285
362
  * @returns
286
363
  */
287
364
  declare const appUrl: (link?: string) => string;
288
- declare const CONFIG_KEY: unique symbol;
289
365
  /**
290
366
  * Gets the application configuration.
291
367
  *
@@ -589,4 +665,81 @@ declare class Hook {
589
665
  static clear: () => void;
590
666
  }
591
667
  //#endregion
592
- export { AbstractModelConstructor, AppException, ArkstackErrorPayload, ArkstackErrorShape, CONFIG_KEY, ConfigRegistry, ConfigShape, DotPath, DotPathValue, Encryption, EnvRegistry, ErrorHandler, Exception, FileImporter, GlobalConfig, GlobalEnv, Hash, Hook, HookArgs, HookFor, HookName, HookPos, HookPositions, HookRegistry, IHook, Logger, LoggerChalk, LoggerLog, LoggerParseSignature, MergedConfig, ModelConstructor, ModelRegistry, Primitive, PublishEntry, PublishFilter, PublishGroup, Publisher, RequestException, UnionToIntersection, abort, abortIf, appKey, appUrl, assertFound, bindGracefulShutdown, bootWithDetectedPort, config, createErrorPayload, discoverCommands, env, getErrorLogger, getModel, getPrimaryError, getValidationErrors, importFile, initializeGlobalContext, interopDefault, isClass, isModelNotFoundError, isValidationError, loadPrototypes, logUnhandledError, nodeEnv, normalizeStatusCode, outputDir, perPage, rebuildOutput, renderError, resolveRuntimeDir, resolveRuntimeModule, serializeError, shouldHideStack, shouldLogError, toErrorShape, toOutputPath };
668
+ //#region src/EnvLoader.d.ts
669
+ /**
670
+ * Loads environment variables, reading the `.env` file on first access.
671
+ *
672
+ * The `.env` file is loaded lazily the first time a variable is read, so
673
+ * environment access never depends on a side-effect `import 'dotenv/config'`
674
+ * running first. Import ordering — which linters and bundlers may rewrite —
675
+ * could otherwise place an env-reading module before dotenv has populated
676
+ * `process.env`, leaving that module with default/stale values.
677
+ * `dotenv.config()` never overrides variables already set, so the lazy load is
678
+ * safe alongside other loaders.
679
+ */
680
+ declare class EnvLoader {
681
+ private loaded;
682
+ /**
683
+ * Load the `.env` file once.
684
+ *
685
+ * @returns
686
+ */
687
+ private ensureLoaded;
688
+ /**
689
+ * Read an environment variable, coercing booleans, numbers and `null`, and
690
+ * falling back to `defaultValue` when it is unset.
691
+ *
692
+ * @param name The variable name.
693
+ * @param defaultValue Returned when the variable is unset.
694
+ */
695
+ get<X = never, D = undefined, K extends string = string>(name: K, defaultValue?: D): EnvReturn<X, K, D>;
696
+ }
697
+ /**
698
+ * Shared environment loader backing {@link env}.
699
+ */
700
+ declare const envLoader: EnvLoader;
701
+ //#endregion
702
+ //#region src/ConfigLoader.d.ts
703
+ declare const CONFIG_KEY: unique symbol;
704
+ /**
705
+ * Loads and resolves application configuration from the config directory.
706
+ *
707
+ * Config modules are read once (lazily) from the output directory and cached on
708
+ * a global symbol, then queried by dot-path. A partial config object can also be
709
+ * merged in at runtime.
710
+ */
711
+ declare class ConfigLoader {
712
+ /**
713
+ * The cached config store, shared across instances via a global symbol.
714
+ */
715
+ private get store();
716
+ private set store(value);
717
+ /**
718
+ * Read and cache config modules from the config directory on first use.
719
+ */
720
+ private load;
721
+ /**
722
+ * Resolve the directory to load config modules from.
723
+ *
724
+ * Prefers an explicit `CONFIG_PATH`, then the environment-selected output
725
+ * directory. Falls back to the other build output (`dist` ⇄
726
+ * `.arkstack/build`) so config still loads if the selected directory is
727
+ * missing or transiently emptied — e.g. a concurrent rebuild (`clean: true`)
728
+ * during a test run.
729
+ */
730
+ private resolveConfigDir;
731
+ /**
732
+ * Resolve configuration: read a dot-path value, merge a partial config
733
+ * object, or return the whole config.
734
+ *
735
+ * @param key Dot-path to read, or an object to merge in.
736
+ * @param defaultValue Returned when a string key is not found.
737
+ */
738
+ resolve<X extends ConfigRegistry | unknown = unknown, P extends DotPath<X> | undefined = undefined>(key?: P, defaultValue?: any): any;
739
+ }
740
+ /**
741
+ * Shared config loader backing {@link config}.
742
+ */
743
+ declare const configLoader: ConfigLoader;
744
+ //#endregion
745
+ export { AbstractModelConstructor, AppException, ArkstackErrorPayload, ArkstackErrorShape, CONFIG_KEY, ConfigLoader, ConfigRegistry, ConfigShape, DotPath, DotPathValue, Encryption, EnvKey, EnvLoader, EnvLookup, EnvRegistry, EnvReturn, ErrorHandler, Exception, FileImporter, GlobalConfig, GlobalEnv, Hash, Hook, HookArgs, HookFor, HookName, HookPos, HookPositions, HookRegistry, IHook, Logger, LoggerChalk, LoggerLog, LoggerParseSignature, MergedConfig, ModelConstructor, ModelRegistry, Primitive, PublishEntry, PublishFilter, PublishGroup, Publisher, RequestException, UnionToIntersection, abort, abortIf, appKey, appUrl, assertFound, bindGracefulShutdown, bootWithDetectedPort, config, configLoader, createErrorPayload, discoverCommands, env, envLoader, getErrorLogger, getModel, getPrimaryError, getValidationErrors, importFile, initializeGlobalContext, interopDefault, isClass, isModelNotFoundError, isValidationError, loadPrototypes, logUnhandledError, nodeEnv, normalizeStatusCode, outputDir, perPage, rebuildOutput, renderError, resolveRuntimeDir, resolveRuntimeModule, serializeError, shouldHideStack, shouldLogError, toErrorShape, toOutputPath };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { A as resolveRuntimeDir, C as discoverCommands, D as nodeEnv, E as interopDefault, M as toOutputPath, O as outputDir, S as config, T as importFile, _ as Hash, b as appKey, c as abortIf, d as initializeGlobalContext, f as isClass, g as Exception, h as AppException, j as resolveRuntimeModule, k as rebuildOutput, l as assertFound, m as RequestException, p as perPage, s as abort, u as getModel, v as Encryption, w as env, x as appUrl, y as CONFIG_KEY } from "./utils-DJQAOLbx.js";
1
+ import { A as resolveRuntimeModule, C as env, D as outputDir, E as nodeEnv, F as ConfigLoader, I as configLoader, M as EnvLoader, N as envLoader, O as rebuildOutput, P as CONFIG_KEY, S as discoverCommands, T as interopDefault, _ as Hash, b as appUrl, c as abortIf, d as initializeGlobalContext, f as isClass, g as Exception, h as AppException, j as toOutputPath, k as resolveRuntimeDir, l as assertFound, m as RequestException, p as perPage, s as abort, u as getModel, v as Encryption, w as importFile, x as config, y as appKey } from "./utils-DEflBOXp.js";
2
2
  import { Hook as Hook$1 } from "@arkstack/foundry";
3
- import { str } from "@h3ravel/support";
4
3
  import { Arkstack } from "@arkstack/contract";
4
+ import { str } from "@h3ravel/support";
5
5
  import path from "node:path";
6
6
  import { ModelNotFoundException } from "arkormx";
7
7
  import { detect } from "detect-port";
@@ -529,4 +529,4 @@ var Hook = class {
529
529
  };
530
530
  };
531
531
  //#endregion
532
- export { AppException, CONFIG_KEY, Encryption, ErrorHandler, Exception, Hash, Hook, Logger, Publisher, RequestException, abort, abortIf, appKey, appUrl, assertFound, bindGracefulShutdown, bootWithDetectedPort, config, createErrorPayload, discoverCommands, env, getErrorLogger, getModel, getPrimaryError, getValidationErrors, importFile, initializeGlobalContext, interopDefault, isClass, isModelNotFoundError, isValidationError, loadPrototypes, logUnhandledError, nodeEnv, normalizeStatusCode, outputDir, perPage, rebuildOutput, renderError, resolveRuntimeDir, resolveRuntimeModule, serializeError, shouldHideStack, shouldLogError, toErrorShape, toOutputPath };
532
+ export { AppException, CONFIG_KEY, ConfigLoader, Encryption, EnvLoader, ErrorHandler, Exception, Hash, Hook, Logger, Publisher, RequestException, abort, abortIf, appKey, appUrl, assertFound, bindGracefulShutdown, bootWithDetectedPort, config, configLoader, createErrorPayload, discoverCommands, env, envLoader, getErrorLogger, getModel, getPrimaryError, getValidationErrors, importFile, initializeGlobalContext, interopDefault, isClass, isModelNotFoundError, isValidationError, loadPrototypes, logUnhandledError, nodeEnv, normalizeStatusCode, outputDir, perPage, rebuildOutput, renderError, resolveRuntimeDir, resolveRuntimeModule, serializeError, shouldHideStack, shouldLogError, toErrorShape, toOutputPath };
@@ -1,2 +1,2 @@
1
- import { _ as Hash, a as use, c as abortIf, d as initializeGlobalContext, f as isClass, i as trait, l as assertFound, n as crc32, o as uses, p as perPage, r as getTraitMethods, s as abort, t as callTraitMethods, u as getModel, v as Encryption } from "../utils-DJQAOLbx.js";
1
+ import { _ as Hash, a as use, c as abortIf, d as initializeGlobalContext, f as isClass, i as trait, l as assertFound, n as crc32, o as uses, p as perPage, r as getTraitMethods, s as abort, t as callTraitMethods, u as getModel, v as Encryption } from "../utils-DEflBOXp.js";
2
2
  export { Encryption, Hash, abort, abortIf, assertFound, callTraitMethods, crc32, getModel, getTraitMethods, initializeGlobalContext, isClass, perPage, trait, use, uses };
@@ -1,16 +1,170 @@
1
1
  import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
2
- import { Arr, Obj, undot } from "@h3ravel/support";
3
2
  import { createJiti } from "jiti";
4
3
  import { existsSync, readdirSync } from "fs";
5
4
  import { Arkstack } from "@arkstack/contract";
5
+ import { Arr, Obj, undot } from "@h3ravel/support";
6
+ import { readdirSync as readdirSync$1 } from "node:fs";
6
7
  import { createRequire } from "module";
7
8
  import path, { resolve } from "node:path";
9
+ import { config } from "dotenv";
8
10
  import { pathToFileURL } from "node:url";
9
11
  import { rm } from "node:fs/promises";
10
12
  import { spawn } from "node:child_process";
11
13
  import { Secret, TOTP } from "otpauth";
12
14
  import { compare, genSalt, hash } from "bcryptjs";
13
15
  import { getUserConfig } from "arkormx";
16
+ //#region src/ConfigLoader.ts
17
+ const CONFIG_KEY = Symbol("globalConfig");
18
+ globalThis[CONFIG_KEY] = {};
19
+ /**
20
+ * Loads and resolves application configuration from the config directory.
21
+ *
22
+ * Config modules are read once (lazily) from the output directory and cached on
23
+ * a global symbol, then queried by dot-path. A partial config object can also be
24
+ * merged in at runtime.
25
+ */
26
+ var ConfigLoader = class {
27
+ /**
28
+ * The cached config store, shared across instances via a global symbol.
29
+ */
30
+ get store() {
31
+ return globalThis[CONFIG_KEY];
32
+ }
33
+ set store(value) {
34
+ globalThis[CONFIG_KEY] = value;
35
+ }
36
+ /**
37
+ * Read and cache config modules from the config directory on first use.
38
+ */
39
+ load() {
40
+ if (Object.entries(this.store).length >= 1) return;
41
+ let files;
42
+ const require = createRequire(import.meta.url);
43
+ const configDir = this.resolveConfigDir();
44
+ try {
45
+ files = readdirSync$1(configDir, { withFileTypes: true }).filter((file) => {
46
+ if (file.name.includes("middleware") && globalThis.arkctx?.runtime === "CLI") return false;
47
+ return file.isFile() && (file.name.endsWith(".js") || file.name.endsWith(".ts"));
48
+ });
49
+ } catch {
50
+ files = [];
51
+ }
52
+ Object.assign(this.store, files.reduce((configs, file) => {
53
+ const configName = path.basename(file.name, path.extname(file.name));
54
+ try {
55
+ configs[configName] = require(path.join(file.parentPath, file.name)).default(typeof globalThis.app === "function" ? globalThis.app() : {});
56
+ } catch (error) {
57
+ console.warn(`[arkstack] Skipped config "${configName}": ${error.message}`);
58
+ }
59
+ return configs;
60
+ }, {}));
61
+ }
62
+ /**
63
+ * Resolve the directory to load config modules from.
64
+ *
65
+ * Prefers an explicit `CONFIG_PATH`, then the environment-selected output
66
+ * directory. Falls back to the other build output (`dist` ⇄
67
+ * `.arkstack/build`) so config still loads if the selected directory is
68
+ * missing or transiently emptied — e.g. a concurrent rebuild (`clean: true`)
69
+ * during a test run.
70
+ */
71
+ resolveConfigDir() {
72
+ const root = Arkstack.rootDir();
73
+ const explicit = env("CONFIG_PATH");
74
+ if (explicit) return explicit;
75
+ const candidates = [
76
+ path.join(outputDir(), "config"),
77
+ path.join(root, env("OUTPUT_DIR", "dist"), "config"),
78
+ path.join(root, env("OUTPUT_DIR_DEV", ".arkstack/build"), "config")
79
+ ];
80
+ return candidates.find((dir) => {
81
+ try {
82
+ return readdirSync$1(dir).some((file) => file.endsWith(".js") || file.endsWith(".ts"));
83
+ } catch {
84
+ return false;
85
+ }
86
+ }) ?? candidates[0];
87
+ }
88
+ /**
89
+ * Resolve configuration: read a dot-path value, merge a partial config
90
+ * object, or return the whole config.
91
+ *
92
+ * @param key Dot-path to read, or an object to merge in.
93
+ * @param defaultValue Returned when a string key is not found.
94
+ */
95
+ resolve(key, defaultValue) {
96
+ if (typeof globalThis.env === "undefined") globalThis.env = (k, def) => k ? process.env[k] ?? def : process.env;
97
+ this.load();
98
+ if (typeof key === "object" && key !== null) this.store = undot(Object.assign({}, Arr.dot(this.store), Arr.dot(key)));
99
+ else if (typeof key === "string") return Obj.get(this.store, key, defaultValue);
100
+ return this.store;
101
+ }
102
+ };
103
+ /**
104
+ * Shared config loader backing {@link config}.
105
+ */
106
+ const configLoader = new ConfigLoader();
107
+ //#endregion
108
+ //#region src/EnvLoader.ts
109
+ /**
110
+ * Loads environment variables, reading the `.env` file on first access.
111
+ *
112
+ * The `.env` file is loaded lazily the first time a variable is read, so
113
+ * environment access never depends on a side-effect `import 'dotenv/config'`
114
+ * running first. Import ordering — which linters and bundlers may rewrite —
115
+ * could otherwise place an env-reading module before dotenv has populated
116
+ * `process.env`, leaving that module with default/stale values.
117
+ * `dotenv.config()` never overrides variables already set, so the lazy load is
118
+ * safe alongside other loaders.
119
+ */
120
+ var EnvLoader = class {
121
+ loaded = false;
122
+ /**
123
+ * Load the `.env` file once.
124
+ *
125
+ * @returns
126
+ */
127
+ ensureLoaded() {
128
+ if (this.loaded) return;
129
+ this.loaded = true;
130
+ try {
131
+ config({ quiet: true });
132
+ } catch {}
133
+ }
134
+ /**
135
+ * Read an environment variable, coercing booleans, numbers and `null`, and
136
+ * falling back to `defaultValue` when it is unset.
137
+ *
138
+ * @param name The variable name.
139
+ * @param defaultValue Returned when the variable is unset.
140
+ */
141
+ get(name, defaultValue) {
142
+ this.ensureLoaded();
143
+ let val = process.env[name] ?? "";
144
+ if ([
145
+ true,
146
+ "true",
147
+ "on",
148
+ false,
149
+ "false",
150
+ "off"
151
+ ].includes(val)) val = [
152
+ true,
153
+ "true",
154
+ "on"
155
+ ].includes(val);
156
+ if (!isNaN(Number(val)) && typeof val !== "boolean" && typeof val !== "undefined" && val !== "") val = Number(val);
157
+ if (val === "") val = void 0;
158
+ if (val === "null") val = null;
159
+ val ??= defaultValue;
160
+ return val;
161
+ }
162
+ };
163
+ /**
164
+ * Shared environment loader backing {@link env}.
165
+ */
166
+ const envLoader = new EnvLoader();
167
+ //#endregion
14
168
  //#region src/system.ts
15
169
  /**
16
170
  * Read the .env file
@@ -19,26 +173,7 @@ import { getUserConfig } from "arkormx";
19
173
  * @param def
20
174
  * @returns
21
175
  */
22
- const env = (env, defaultValue) => {
23
- let val = process.env[env] ?? "";
24
- if ([
25
- true,
26
- "true",
27
- "on",
28
- false,
29
- "false",
30
- "off"
31
- ].includes(val)) val = [
32
- true,
33
- "true",
34
- "on"
35
- ].includes(val);
36
- if (!isNaN(Number(val)) && typeof val !== "boolean" && typeof val !== "undefined" && val !== "") val = Number(val);
37
- if (val === "") val = void 0;
38
- if (val === "null") val = null;
39
- val ??= defaultValue;
40
- return val;
41
- };
176
+ const env$1 = (env, defaultValue) => envLoader.get(env, defaultValue);
42
177
  /**
43
178
  * Build the app url
44
179
  *
@@ -46,12 +181,12 @@ const env = (env, defaultValue) => {
46
181
  * @returns
47
182
  */
48
183
  const appUrl = (link) => {
49
- const port = env("PORT", env("APP_PORT", "3000"));
184
+ const port = env$1("PORT", env$1("APP_PORT", "3000"));
50
185
  const defaultUrl = `http://localhost:${port}`;
51
- const appUrl = env("APP_URL", `http://localhost:${port}`);
186
+ const appUrl = env$1("APP_URL", `http://localhost:${port}`);
52
187
  try {
53
188
  const url = new URL(appUrl);
54
- if (url.port || url.hostname === "localhost") url.port = port;
189
+ if (url.port || url.hostname === "localhost") url.port = String(port);
55
190
  const baseUrl = url.toString().replace(/\/$/, "");
56
191
  if (link) return `${baseUrl}${`/${link.replace(/^\/+/, "")}`}`;
57
192
  return baseUrl;
@@ -59,8 +194,6 @@ const appUrl = (link) => {
59
194
  return link ? `${defaultUrl}/${link.replace(/^\/+/, "")}` : defaultUrl;
60
195
  }
61
196
  };
62
- const CONFIG_KEY = Symbol("globalConfig");
63
- globalThis[CONFIG_KEY] = {};
64
197
  /**
65
198
  * Gets the application configuration.
66
199
  *
@@ -68,35 +201,7 @@ globalThis[CONFIG_KEY] = {};
68
201
  * @param defaultValue The default value to return if the key is not found.
69
202
  * @returns The configuration value.
70
203
  */
71
- const config = (key, defaultValue) => {
72
- if (typeof globalThis.env === "undefined") globalThis.env = (key, def) => key ? process.env[key] ?? def : process.env;
73
- const config = globalThis[CONFIG_KEY];
74
- if (Object.entries(config).length < 1) {
75
- let files;
76
- const dist = path.relative(Arkstack.rootDir(), outputDir());
77
- const require = createRequire(import.meta.url);
78
- const configDir = env("CONFIG_PATH", path.join(Arkstack.rootDir(), `${dist}/config`));
79
- try {
80
- files = readdirSync(configDir, { withFileTypes: true }).filter((file) => {
81
- if (file.name.includes("middleware") && globalThis.arkctx?.runtime === "CLI") return false;
82
- return file.isFile() && (file.name.endsWith(".js") || file.name.endsWith(".ts"));
83
- });
84
- } catch {
85
- files = [];
86
- }
87
- Object.assign(config, files.reduce((configs, file) => {
88
- const configName = path.basename(file.name, path.extname(file.name));
89
- configs[configName] = require(path.join(file.parentPath, file.name)).default(typeof globalThis.app === "function" ? globalThis.app() : {});
90
- return configs;
91
- }, {}));
92
- globalThis[CONFIG_KEY] = config;
93
- }
94
- if (typeof key === "object" && key !== null) {
95
- const config = Object.assign({}, Arr.dot(globalThis[CONFIG_KEY]), Arr.dot(key));
96
- globalThis[CONFIG_KEY] = undot(config);
97
- } else if (typeof key === "string") return Obj.get(globalThis[CONFIG_KEY], key, defaultValue);
98
- return globalThis[CONFIG_KEY];
99
- };
204
+ const config$1 = (key, defaultValue) => configLoader.resolve(key, defaultValue);
100
205
  /**
101
206
  * Resolve the unified application key.
102
207
  *
@@ -114,13 +219,13 @@ const config = (key, defaultValue) => {
114
219
  * @returns The resolved key, or `undefined` when none is configured.
115
220
  */
116
221
  const appKey = (legacy = []) => {
117
- const explicit = env("APP_KEY");
222
+ const explicit = env$1("APP_KEY");
118
223
  if (explicit) return explicit;
119
224
  for (const name of Array.isArray(legacy) ? legacy : [legacy]) {
120
- const value = env(name);
225
+ const value = env$1(name);
121
226
  if (value) return value;
122
227
  }
123
- return config("app.key") || void 0;
228
+ return config$1("app.key") || void 0;
124
229
  };
125
230
  /**
126
231
  * Gets the current Node environment (development or production).
@@ -128,7 +233,7 @@ const appKey = (legacy = []) => {
128
233
  * @returns
129
234
  */
130
235
  const nodeEnv = () => {
131
- let envValue = env("NODE_ENV", "development");
236
+ let envValue = env$1("NODE_ENV", "development");
132
237
  if (envValue !== "development" && envValue !== "production") envValue = "development";
133
238
  return envValue === "production" ? "prod" : "dev";
134
239
  };
@@ -142,8 +247,8 @@ const outputDir = (cwd) => {
142
247
  cwd ??= Arkstack.rootDir();
143
248
  const NODE_ENV = nodeEnv();
144
249
  const output = {
145
- dev: env("OUTPUT_DIR_DEV", ".arkstack/build"),
146
- prod: env("OUTPUT_DIR", "dist")
250
+ dev: env$1("OUTPUT_DIR_DEV", ".arkstack/build"),
251
+ prod: env$1("OUTPUT_DIR", "dist")
147
252
  };
148
253
  return path.isAbsolute(output[NODE_ENV] ?? output.dev) ? output[NODE_ENV] ?? output.dev : path.join(cwd, output[NODE_ENV] ?? output.dev);
149
254
  };
@@ -431,13 +536,13 @@ var Hash = class {
431
536
  return new TOTP({
432
537
  label,
433
538
  digits,
434
- issuer: env("APP_NAME", "Roseed"),
539
+ issuer: env$1("APP_NAME", "Roseed"),
435
540
  algorithm: "SHA1",
436
541
  period,
437
542
  secret: "US3WHSG7X5KAPV27VANWKQHF3SH3HULL"
438
543
  });
439
544
  }
440
- static totp(secret, label, issuer = env("APP_NAME", "Roseed"), period = 30) {
545
+ static totp(secret, label, issuer = env$1("APP_NAME", "Roseed"), period = 30) {
441
546
  return new TOTP({
442
547
  issuer,
443
548
  label,
@@ -800,4 +905,4 @@ function uses(instance, trait) {
800
905
  return false;
801
906
  }
802
907
  //#endregion
803
- export { resolveRuntimeDir as A, discoverCommands as C, nodeEnv as D, interopDefault as E, toOutputPath as M, outputDir as O, config as S, importFile as T, Hash as _, use as a, appKey as b, abortIf as c, initializeGlobalContext as d, isClass as f, Exception as g, AppException as h, trait as i, resolveRuntimeModule as j, rebuildOutput as k, assertFound as l, RequestException as m, crc32 as n, uses as o, perPage as p, getTraitMethods as r, abort as s, callTraitMethods as t, getModel as u, Encryption as v, env as w, appUrl as x, CONFIG_KEY as y };
908
+ export { resolveRuntimeModule as A, env$1 as C, outputDir as D, nodeEnv as E, ConfigLoader as F, configLoader as I, EnvLoader as M, envLoader as N, rebuildOutput as O, CONFIG_KEY as P, discoverCommands as S, interopDefault as T, Hash as _, use as a, appUrl as b, abortIf as c, initializeGlobalContext as d, isClass as f, Exception as g, AppException as h, trait as i, toOutputPath as j, resolveRuntimeDir as k, assertFound as l, RequestException as m, crc32 as n, uses as o, perPage as p, getTraitMethods as r, abort as s, callTraitMethods as t, getModel as u, Encryption as v, importFile as w, config$1 as x, appKey as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/common",
3
- "version": "0.14.18",
3
+ "version": "0.14.19",
4
4
  "type": "module",
5
5
  "description": "Core utilities, primitives, and shared infrastructure for the Arkstack ecosystem.",
6
6
  "homepage": "https://arkstack.toneflix.net",
@@ -36,14 +36,15 @@
36
36
  "bcryptjs": "^3.0.3",
37
37
  "chalk": "^5.6.2",
38
38
  "detect-port": "^2.1.0",
39
+ "dotenv": "^17.4.2",
39
40
  "otpauth": "^9.5.1",
40
41
  "pino": "^10.3.1"
41
42
  },
42
43
  "peerDependencies": {
43
44
  "@h3ravel/support": "^2.2.0",
44
45
  "arkormx": "^2.10.1",
45
- "@arkstack/foundry": "^0.14.18",
46
- "@arkstack/contract": "^0.14.18"
46
+ "@arkstack/foundry": "^0.14.19",
47
+ "@arkstack/contract": "^0.14.19"
47
48
  },
48
49
  "scripts": {
49
50
  "build": "tsdown --config-loader unrun",