@futdevpro/fsm-dynamo 1.15.12 → 1.15.14
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/_specifications/BACKLOG.md +43 -0
- package/build/_collections/utils/require-env.util.d.ts +86 -0
- package/build/_collections/utils/require-env.util.d.ts.map +1 -0
- package/build/_collections/utils/require-env.util.js +94 -0
- package/build/_collections/utils/require-env.util.js.map +1 -0
- package/build/_models/interfaces/environment/global-settings.interface.d.ts +22 -0
- package/build/_models/interfaces/environment/global-settings.interface.d.ts.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/package.json +2 -2
- package/src/_collections/utils/object.util.spec.ts +1 -1
- package/src/_collections/utils/require-env.util.spec.ts +231 -0
- package/src/_collections/utils/require-env.util.ts +138 -0
- package/src/_models/interfaces/environment/global-settings.interface.ts +23 -0
- package/src/index.ts +1 -0
- package/src/_collections/utils/math/box-bounds.util.spec.ts +0 -124
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# BACKLOG.md
|
|
2
|
+
|
|
3
|
+
> STRICT PATTERN (MUST FOLLOW EXACTLY)
|
|
4
|
+
> - Entry header: "- [TYPE] (BL-YYYYMMDD-###) TITLE"
|
|
5
|
+
> - Immediately followed by (exact keys, colon-space, indented 2 spaces):
|
|
6
|
+
> - " status: <EMOJI status-key>"
|
|
7
|
+
> - " priority: <low|medium|high|urgent>"
|
|
8
|
+
> - " source: <user|system|assistant>"
|
|
9
|
+
> - " area: <ui|backend|infra|docs|tests|ux|general>"
|
|
10
|
+
> - " details: <single-line summary>"
|
|
11
|
+
> - NO blank line between header and fields (parser reads i+1..i+5).
|
|
12
|
+
> - One blank line between entries (readability).
|
|
13
|
+
> - TYPE values: FEATURE, BUG, IMPROVEMENT, QUESTION, RESEARCH, TASK
|
|
14
|
+
> - STATUS EMOJI: ❌ ⏳ 🔄 ✅ ⚠️ ❓
|
|
15
|
+
> - IDs: unique, format BL-YYYYMMDD-### (increment ###).
|
|
16
|
+
|
|
17
|
+
- [IMPROVEMENT] (BL-20260518-001) DyFM_StateMachine: explicit `_FailureResult` return type a `_failure()` helper-en (TS narrowing)
|
|
18
|
+
status: ⏳ pending
|
|
19
|
+
priority: low
|
|
20
|
+
source: assistant
|
|
21
|
+
area: backend
|
|
22
|
+
details: A `DyFM_StateMachine._failure()` jelenleg `DyFM_StateMachineTransition_Result<TState>` union-t ad vissza. A TS inference emiatt nem narrow-ol az `if (!r.ok) {...}` blokkban (a private helper visszatérése a union mind két ágát beilleszti, és a `_failure()` belső object literal `{ ok: false, ... }` típusa nem narrow-ódik le `false` literal-re). Konzumens kódban `as DyFM_StateMachineTransition_FailureResult<TState>` cast szükséges. Fix: `_failure()` return type-ja explicit `DyFM_StateMachineTransition_FailureResult<TState>` (NEM union). 1-line type-annotation change, 0 viselkedés-változás.
|
|
23
|
+
|
|
24
|
+
- [IMPROVEMENT] (BL-20260518-002) DyFM_StateMachine: `DyFM_Error` wrapping a result.error mezőben
|
|
25
|
+
status: ⏳ pending
|
|
26
|
+
priority: low
|
|
27
|
+
source: assistant
|
|
28
|
+
area: backend
|
|
29
|
+
details: A jelenlegi `DyFM_StateMachineTransition_FailureResult.error` `DyFM_Error | Error` típusú, de a `_failure()` raw `Error`-t propagál (callback throw-jából). Konzisztencia a többi `DyFM_*` modullal: `DyFM_Error({ status: 422, errorCode: 'DyFM-SM-<subcode>', message, error: originalError, issuerService: 'DyFM_StateMachine' })` wrappingot adni a 4 throw-elhető helyen (guard/onLeave/onEnter/persist). Subcode-ok pl. 'DyFM-SM-GR' (guard-rejected throw), 'DyFM-SM-OL' (on-leave), 'DyFM-SM-OE' (on-enter), 'DyFM-SM-PE' (persist). Spec: minden 4 error-path-on `DyFM_Error.getErrorCode(f.error)` assertion.
|
|
30
|
+
|
|
31
|
+
- [TASK] (BL-20260518-003) DyFM_StateMachine: guard-throw + self-loop spec coverage
|
|
32
|
+
status: ⏳ pending
|
|
33
|
+
priority: low
|
|
34
|
+
source: assistant
|
|
35
|
+
area: tests
|
|
36
|
+
details: Két explicit spec hiányzik a FR-006 (52354ba) 22-spec szettjéből: (1) guard callback throw esetén `transition()` `guard-rejected` reason-t ad-e, és a `canTransition()` `false`-t (mindkét helyen catch-eli a impl, de NEM bizonyított teszttel); (2) self-loop transition (`from === to`) explicit spec — a `_findTransition` matchel rá, de nincs lefedve. ~30 LOC spec, no impl change.
|
|
37
|
+
|
|
38
|
+
- [FEATURE] (BL-20260518-004) DyFM_StateMachine: Mongoose persist adapter
|
|
39
|
+
status: ⏳ pending
|
|
40
|
+
priority: medium
|
|
41
|
+
source: assistant
|
|
42
|
+
area: backend
|
|
43
|
+
details: Az MVP-ben (FR-006 / 52354ba) a `persist` callback caller-supplied (CCAP MP1-D maga írja a Mongo updateOne logikát stateVersion optimistic-lock-kal). Egy genericikus adapter ami `DyFM_dataModel`-kötött (Mongoose ControlModel) state-document-et frissít automatikusan, kevesebb boilerplate-tel. API: `createMongoosePersistAdapter({ collection: Model<T>, docFilter: { _id: 'rag-status' } }): DyFM_StateMachineConfig['persist']`. Optimistic-lock: `findOneAndUpdate({ ...filter, stateVersion: prev }, { $set: { state, stateVersion: next, lastTransitionAt: timestamp } })` — match nélkül `'persist-failed'`. Tervek szerint a consumer real-world tapasztalat (MP1-D landolása) után írandó, hogy a tényleges minta visszahatáson alapuljon.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for `DyFM_requireEnv`. See the helper's JSDoc for usage.
|
|
3
|
+
*/
|
|
4
|
+
export interface DyFM_RequireEnv_Options {
|
|
5
|
+
/**
|
|
6
|
+
* Stable error code emitted on the DyFM_Error when the env-var is missing
|
|
7
|
+
* and no fallback is configured. Convention: `<PACKAGE>-ENV-MISSING-<KEY>`,
|
|
8
|
+
* e.g. `DyNM-ENV-MISSING-STORAGE-KEY`, `FDP-ENV-MISSING-AUTH-KEY`.
|
|
9
|
+
*/
|
|
10
|
+
errorCode: string;
|
|
11
|
+
/**
|
|
12
|
+
* Short-code name of the package responsible for the env-var. Used as the
|
|
13
|
+
* DyFM_Error `___issuerService` for downstream filtering / dashboards.
|
|
14
|
+
*/
|
|
15
|
+
issuerService: string;
|
|
16
|
+
/**
|
|
17
|
+
* Transition fallback. When set, returned if BOTH `process.env[name]` AND
|
|
18
|
+
* `DyFM_global_settings.envOverrides[name]` are missing.
|
|
19
|
+
*
|
|
20
|
+
* Use only during the additive phase of an env-var migration to keep the
|
|
21
|
+
* package backward-compatible while every deploy target rolls the new
|
|
22
|
+
* env-var out. Remove the `fallback` argument once the rollout is verified
|
|
23
|
+
* everywhere — then a missing env-var fail-fast crashes the consumer at
|
|
24
|
+
* first access with a structured DyFM_Error.
|
|
25
|
+
*/
|
|
26
|
+
fallback?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Admin-actionable user-facing message attached to the DyFM_Error. Defaults
|
|
29
|
+
* to a generic config-missing string referencing the env-var name.
|
|
30
|
+
*/
|
|
31
|
+
userMessage?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* FR-015 Phase 2 — isomorphic env-var requirement helper.
|
|
35
|
+
*
|
|
36
|
+
* Universal source of truth for "read this configuration key from an env-var
|
|
37
|
+
* across Node and browser". Replaces per-package `requireEnv` helpers (the
|
|
38
|
+
* Phase 1 local implementation in fdp-templates-nts is being refactored to
|
|
39
|
+
* delegate here).
|
|
40
|
+
*
|
|
41
|
+
* Lookup order:
|
|
42
|
+
* 1. `process.env[envVarName]` — Node-side (server const files at module load).
|
|
43
|
+
* 2. `DyFM_global_settings.envOverrides[envVarName]` — browser-side, populated
|
|
44
|
+
* by the consumer at app bootstrap from its `environment.ts`. See
|
|
45
|
+
* DyFM_Global_Settings.envOverrides docs for the pattern.
|
|
46
|
+
* 3. `options.fallback` if provided — used during migration's additive phase.
|
|
47
|
+
* 4. throw a structured DyFM_Error with the full deploy-target checklist.
|
|
48
|
+
*
|
|
49
|
+
* The throw shape is rich on purpose (per the rich-error rule):
|
|
50
|
+
* - `___status` = 500 (configuration error)
|
|
51
|
+
* - `_errorCode` = caller-supplied stable code
|
|
52
|
+
* - `_message` = debug-level description with the env-var name + deploy-target
|
|
53
|
+
* checklist (host .env, docker-compose env-block, Overseer secrets-store,
|
|
54
|
+
* client environment.ts → bootstrap envOverrides)
|
|
55
|
+
* - `__userMessage` = admin-actionable message
|
|
56
|
+
* - `___issuerService` = caller-supplied package short-code
|
|
57
|
+
*
|
|
58
|
+
* @example Node server const file (module load):
|
|
59
|
+
* ```ts
|
|
60
|
+
* import { DyFM_requireEnv } from '@futdevpro/fsm-dynamo';
|
|
61
|
+
*
|
|
62
|
+
* export const FDP_keysEnv_settingsBase = {
|
|
63
|
+
* authKey: DyFM_requireEnv('FDP_AUTH_KEY', {
|
|
64
|
+
* errorCode: 'FDP-ENV-MISSING-AUTH-KEY',
|
|
65
|
+
* issuerService: 'fdp-templates',
|
|
66
|
+
* fallback: '<inherit literal>', // remove for Wave 3
|
|
67
|
+
* }),
|
|
68
|
+
* };
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @example Browser-side lazy getter (deferred to first access, after consumer
|
|
72
|
+
* bootstrap has populated DyFM_global_settings.envOverrides):
|
|
73
|
+
* ```ts
|
|
74
|
+
* let _cached: string | undefined;
|
|
75
|
+
* function _resolveKey(): string {
|
|
76
|
+
* if (_cached !== undefined) return _cached;
|
|
77
|
+
* _cached = DyFM_requireEnv('DyNM_STORAGE_ENCRYPTION_KEY', { ... });
|
|
78
|
+
* return _cached;
|
|
79
|
+
* }
|
|
80
|
+
* export const DyNM_global_settings = {
|
|
81
|
+
* get storageEncryptionKey(): string { return _resolveKey(); },
|
|
82
|
+
* };
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export declare function DyFM_requireEnv(envVarName: string, options: DyFM_RequireEnv_Options): string;
|
|
86
|
+
//# sourceMappingURL=require-env.util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-env.util.d.ts","sourceRoot":"","sources":["../../../src/_collections/utils/require-env.util.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,uBAAuB,GAC/B,MAAM,CAyCR"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DyFM_requireEnv = DyFM_requireEnv;
|
|
4
|
+
const error_control_model_1 = require("../../_models/control-models/error.control-model");
|
|
5
|
+
const global_settings_const_1 = require("../constants/global-settings.const");
|
|
6
|
+
/**
|
|
7
|
+
* FR-015 Phase 2 — isomorphic env-var requirement helper.
|
|
8
|
+
*
|
|
9
|
+
* Universal source of truth for "read this configuration key from an env-var
|
|
10
|
+
* across Node and browser". Replaces per-package `requireEnv` helpers (the
|
|
11
|
+
* Phase 1 local implementation in fdp-templates-nts is being refactored to
|
|
12
|
+
* delegate here).
|
|
13
|
+
*
|
|
14
|
+
* Lookup order:
|
|
15
|
+
* 1. `process.env[envVarName]` — Node-side (server const files at module load).
|
|
16
|
+
* 2. `DyFM_global_settings.envOverrides[envVarName]` — browser-side, populated
|
|
17
|
+
* by the consumer at app bootstrap from its `environment.ts`. See
|
|
18
|
+
* DyFM_Global_Settings.envOverrides docs for the pattern.
|
|
19
|
+
* 3. `options.fallback` if provided — used during migration's additive phase.
|
|
20
|
+
* 4. throw a structured DyFM_Error with the full deploy-target checklist.
|
|
21
|
+
*
|
|
22
|
+
* The throw shape is rich on purpose (per the rich-error rule):
|
|
23
|
+
* - `___status` = 500 (configuration error)
|
|
24
|
+
* - `_errorCode` = caller-supplied stable code
|
|
25
|
+
* - `_message` = debug-level description with the env-var name + deploy-target
|
|
26
|
+
* checklist (host .env, docker-compose env-block, Overseer secrets-store,
|
|
27
|
+
* client environment.ts → bootstrap envOverrides)
|
|
28
|
+
* - `__userMessage` = admin-actionable message
|
|
29
|
+
* - `___issuerService` = caller-supplied package short-code
|
|
30
|
+
*
|
|
31
|
+
* @example Node server const file (module load):
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { DyFM_requireEnv } from '@futdevpro/fsm-dynamo';
|
|
34
|
+
*
|
|
35
|
+
* export const FDP_keysEnv_settingsBase = {
|
|
36
|
+
* authKey: DyFM_requireEnv('FDP_AUTH_KEY', {
|
|
37
|
+
* errorCode: 'FDP-ENV-MISSING-AUTH-KEY',
|
|
38
|
+
* issuerService: 'fdp-templates',
|
|
39
|
+
* fallback: '<inherit literal>', // remove for Wave 3
|
|
40
|
+
* }),
|
|
41
|
+
* };
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example Browser-side lazy getter (deferred to first access, after consumer
|
|
45
|
+
* bootstrap has populated DyFM_global_settings.envOverrides):
|
|
46
|
+
* ```ts
|
|
47
|
+
* let _cached: string | undefined;
|
|
48
|
+
* function _resolveKey(): string {
|
|
49
|
+
* if (_cached !== undefined) return _cached;
|
|
50
|
+
* _cached = DyFM_requireEnv('DyNM_STORAGE_ENCRYPTION_KEY', { ... });
|
|
51
|
+
* return _cached;
|
|
52
|
+
* }
|
|
53
|
+
* export const DyNM_global_settings = {
|
|
54
|
+
* get storageEncryptionKey(): string { return _resolveKey(); },
|
|
55
|
+
* };
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
function DyFM_requireEnv(envVarName, options) {
|
|
59
|
+
// Node path: process.env at module-load time. Guarded for browser bundles
|
|
60
|
+
// where `process` may be undefined or shimmed without runtime values.
|
|
61
|
+
if (typeof process !== 'undefined' && process.env && process.env[envVarName]) {
|
|
62
|
+
return process.env[envVarName];
|
|
63
|
+
}
|
|
64
|
+
// Browser path: consumer-populated envOverrides map. Populated at app
|
|
65
|
+
// bootstrap from environment.ts — see DyFM_Global_Settings.envOverrides.
|
|
66
|
+
const override = global_settings_const_1.DyFM_global_settings.envOverrides?.[envVarName];
|
|
67
|
+
if (override) {
|
|
68
|
+
return override;
|
|
69
|
+
}
|
|
70
|
+
// Transition fallback (Wave 1 of an FR-015-style migration).
|
|
71
|
+
if (options.fallback !== undefined) {
|
|
72
|
+
return options.fallback;
|
|
73
|
+
}
|
|
74
|
+
// Missing — fail fast with a rich, debug-friendly DyFM_Error.
|
|
75
|
+
throw new error_control_model_1.DyFM_Error({
|
|
76
|
+
status: 500,
|
|
77
|
+
errorCode: options.errorCode,
|
|
78
|
+
message: `Environment variable '${envVarName}' is required but was not found. ` +
|
|
79
|
+
`Checked: process.env.${envVarName} (Node) and ` +
|
|
80
|
+
`DyFM_global_settings.envOverrides['${envVarName}'] (browser). ` +
|
|
81
|
+
`Both unset, and no transition fallback configured. ` +
|
|
82
|
+
`Set '${envVarName}' on every deploy target before deploying this ` +
|
|
83
|
+
`package version: (1) host .env on every host running a backend, ` +
|
|
84
|
+
`(2) docker-compose env-block on every consumer service, ` +
|
|
85
|
+
`(3) Overseer secrets-store entry for CI builds, ` +
|
|
86
|
+
`(4) client environment.ts plus app-bootstrap copy into ` +
|
|
87
|
+
`DyFM_global_settings.envOverrides for any browser consumer.`,
|
|
88
|
+
userMessage: options.userMessage
|
|
89
|
+
?? `Configuration error — environment variable '${envVarName}' is missing. `
|
|
90
|
+
+ `Contact the responsible operator to set it on this deploy target.`,
|
|
91
|
+
issuerService: options.issuerService,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=require-env.util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-env.util.js","sourceRoot":"","sources":["../../../src/_collections/utils/require-env.util.ts"],"names":[],"mappings":";;AA6FA,0CA4CC;AAzID,0FAA8E;AAC9E,8EAA0E;AAwC1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,SAAgB,eAAe,CAC7B,UAAkB,EAClB,OAAgC;IAEhC,0EAA0E;IAC1E,sEAAsE;IACtE,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7E,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,CAAW,CAAC;IAC3C,CAAC;IAED,sEAAsE;IACtE,yEAAyE;IACzE,MAAM,QAAQ,GACZ,4CAAoB,CAAC,YAAY,EAAE,CAAC,UAAU,CAAC,CAAC;IAClD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,6DAA6D;IAC7D,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACnC,OAAO,OAAO,CAAC,QAAQ,CAAC;IAC1B,CAAC;IAED,8DAA8D;IAC9D,MAAM,IAAI,gCAAU,CAAC;QACnB,MAAM,EAAE,GAAG;QACX,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,OAAO,EACL,yBAAyB,UAAU,mCAAmC;YACtE,wBAAwB,UAAU,cAAc;YAChD,sCAAsC,UAAU,gBAAgB;YAChE,qDAAqD;YACrD,QAAQ,UAAU,iDAAiD;YACnE,kEAAkE;YAClE,0DAA0D;YAC1D,kDAAkD;YAClD,yDAAyD;YACzD,6DAA6D;QAC/D,WAAW,EACT,OAAO,CAAC,WAAW;eAChB,+CAA+C,UAAU,gBAAgB;kBACvE,mEAAmE;QAC1E,aAAa,EAAE,OAAO,CAAC,aAAa;KACrC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -17,5 +17,27 @@ export interface DyFM_Global_Settings {
|
|
|
17
17
|
* this setting will set which logs will be shown
|
|
18
18
|
*/
|
|
19
19
|
log_settings: DyFM_GlobalLog_Settings;
|
|
20
|
+
/**
|
|
21
|
+
* FR-015 Phase 2 — isomorphic env-var overrides surface.
|
|
22
|
+
*
|
|
23
|
+
* On Node, `DyFM_requireEnv(name, ...)` reads `process.env[name]` directly.
|
|
24
|
+
* On browser there is no `process.env` at runtime, so consumers populate
|
|
25
|
+
* this map at app bootstrap from `environment.ts`:
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* import { DyFM_global_settings } from '@futdevpro/fsm-dynamo';
|
|
29
|
+
* import { environment } from './environments/environment';
|
|
30
|
+
*
|
|
31
|
+
* DyFM_global_settings.envOverrides = {
|
|
32
|
+
* FDP_AUTH_KEY: environment.authKey,
|
|
33
|
+
* FDP_EXTRA_AUTH_STORAGE_KEY: environment.extraAuthStorageKey,
|
|
34
|
+
* DyNM_STORAGE_ENCRYPTION_KEY: environment.storageEncryptionKey,
|
|
35
|
+
* };
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* `DyFM_requireEnv` checks `process.env` first, then this map, then
|
|
39
|
+
* the caller's optional `fallback`, then throws a structured DyFM_Error.
|
|
40
|
+
*/
|
|
41
|
+
envOverrides?: Record<string, string>;
|
|
20
42
|
}
|
|
21
43
|
//# sourceMappingURL=global-settings.interface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"global-settings.interface.d.ts","sourceRoot":"","sources":["../../../../src/_models/interfaces/environment/global-settings.interface.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE1E;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,mBAAmB,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,YAAY,EAAE,uBAAuB,CAAC;CACvC"}
|
|
1
|
+
{"version":3,"file":"global-settings.interface.d.ts","sourceRoot":"","sources":["../../../../src/_models/interfaces/environment/global-settings.interface.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE1E;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,mBAAmB,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,YAAY,EAAE,uBAAuB,CAAC;IAEtC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC"}
|
package/build/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from './_collections/utils/json-error-helper.util';
|
|
|
10
10
|
export * from './_collections/utils/log.util';
|
|
11
11
|
export * from './_collections/utils/round-list.util';
|
|
12
12
|
export * from './_collections/utils/object.util';
|
|
13
|
+
export * from './_collections/utils/require-env.util';
|
|
13
14
|
export * from './_collections/utils/stack.util';
|
|
14
15
|
export * from './_collections/utils/string.util';
|
|
15
16
|
export * from './_collections/utils/string-case.util';
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,2CAA2C,CAAC;AAC1D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,wCAAwC,CAAC;AACvD,cAAc,sCAAsC,CAAC;AAGrD,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sCAAsC,CAAC;AACrD,cAAc,kCAAkC,CAAC;AACjD,cAAc,iCAAiC,CAAC;AAChD,cAAc,kCAAkC,CAAC;AACjD,cAAc,uCAAuC,CAAC;AACtD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iDAAiD,CAAC;AAChE,cAAc,qCAAqC,CAAC;AACpD,cAAc,gCAAgC,CAAC;AAG/C,cAAc,2CAA2C,CAAC;AAC1D,cAAc,qCAAqC,CAAC;AACpD,cAAc,uCAAuC,CAAC;AACtD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,wCAAwC,CAAC;AAGvD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,uCAAuC,CAAC;AACtD,cAAc,gDAAgD,CAAC;AAI/D,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wCAAwC,CAAC;AAGvD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,uCAAuC,CAAC;AAGtD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,kCAAkC,CAAC;AAKjD,cAAc,0DAA0D,CAAC;AACzE,cAAc,6DAA6D,CAAC;AAC5E,cAAc,8CAA8C,CAAC;AAC7D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,oDAAoD,CAAC;AACnE,cAAc,sDAAsD,CAAC;AACrE,cAAc,uEAAuE,CAAC;AAGtF,cAAc,iEAAiE,CAAC;AAChF,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AAGvE,cAAc,yCAAyC,CAAC;AACxD,cAAc,2CAA2C,CAAC;AAG1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,sCAAsC,CAAC;AACrD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,wDAAwD,CAAC;AACvE,cAAc,wCAAwC,CAAC;AAGvD,cAAc,gEAAgE,CAAC;AAC/E,cAAc,4DAA4D,CAAC;AAG3E,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,2CAA2C,CAAC;AAC1D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,wCAAwC,CAAC;AACvD,cAAc,sCAAsC,CAAC;AAGrD,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sCAAsC,CAAC;AACrD,cAAc,kCAAkC,CAAC;AACjD,cAAc,uCAAuC,CAAC;AACtD,cAAc,iCAAiC,CAAC;AAChD,cAAc,kCAAkC,CAAC;AACjD,cAAc,uCAAuC,CAAC;AACtD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iDAAiD,CAAC;AAChE,cAAc,qCAAqC,CAAC;AACpD,cAAc,gCAAgC,CAAC;AAG/C,cAAc,2CAA2C,CAAC;AAC1D,cAAc,qCAAqC,CAAC;AACpD,cAAc,uCAAuC,CAAC;AACtD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,wCAAwC,CAAC;AAGvD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,uCAAuC,CAAC;AACtD,cAAc,gDAAgD,CAAC;AAI/D,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wCAAwC,CAAC;AAGvD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,uCAAuC,CAAC;AAGtD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,kCAAkC,CAAC;AAKjD,cAAc,0DAA0D,CAAC;AACzE,cAAc,6DAA6D,CAAC;AAC5E,cAAc,8CAA8C,CAAC;AAC7D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,oDAAoD,CAAC;AACnE,cAAc,sDAAsD,CAAC;AACrE,cAAc,uEAAuE,CAAC;AAGtF,cAAc,iEAAiE,CAAC;AAChF,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AAGvE,cAAc,yCAAyC,CAAC;AACxD,cAAc,2CAA2C,CAAC;AAG1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,sCAAsC,CAAC;AACrD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,wDAAwD,CAAC;AACvE,cAAc,wCAAwC,CAAC;AAGvD,cAAc,gEAAgE,CAAC;AAC/E,cAAc,4DAA4D,CAAC;AAG3E,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC"}
|
package/build/index.js
CHANGED
|
@@ -16,6 +16,7 @@ tslib_1.__exportStar(require("./_collections/utils/json-error-helper.util"), exp
|
|
|
16
16
|
tslib_1.__exportStar(require("./_collections/utils/log.util"), exports);
|
|
17
17
|
tslib_1.__exportStar(require("./_collections/utils/round-list.util"), exports);
|
|
18
18
|
tslib_1.__exportStar(require("./_collections/utils/object.util"), exports);
|
|
19
|
+
tslib_1.__exportStar(require("./_collections/utils/require-env.util"), exports);
|
|
19
20
|
tslib_1.__exportStar(require("./_collections/utils/stack.util"), exports);
|
|
20
21
|
tslib_1.__exportStar(require("./_collections/utils/string.util"), exports);
|
|
21
22
|
tslib_1.__exportStar(require("./_collections/utils/string-case.util"), exports);
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AACA,cAAc;AACd,wBAAwB;AACxB,oFAA0D;AAC1D,wFAA8D;AAC9D,yFAA+D;AAC/D,iFAAuD;AACvD,+EAAqD;AAErD,oBAAoB;AACpB,0EAAgD;AAChD,0EAAgD;AAChD,yEAA+C;AAC/C,sFAA4D;AAC5D,wEAA8C;AAC9C,+EAAqD;AACrD,2EAAiD;AACjD,0EAAgD;AAChD,2EAAiD;AACjD,gFAAsD;AACtD,yEAA+C;AAC/C,0FAAgE;AAChE,8EAAoD;AACpD,yEAA+C;AAE/C,yBAAyB;AACzB,oFAA0D;AAC1D,8EAAoD;AACpD,gFAAsD;AACtD,sFAA4D;AAC5D,iFAAuD;AAEvD,2BAA2B;AAC3B,yFAA+D;AAC/D,gFAAsD;AACtD,yFAA+D;AAG/D,QAAQ;AACR,4EAAkD;AAClD,wEAA8C;AAC9C,yEAA+C;AAC/C,oEAA0C;AAC1C,kEAAwC;AACxC,iFAAuD;AAEvD,aAAa;AACb,4EAAkD;AAClD,6EAAmD;AACnD,gFAAsD;AAEtD,aAAa;AACb,yEAA+C;AAC/C,mEAAyC;AACzC,2EAAiD;AAGjD,SAAS;AACT,wBAAwB;AACxB,mGAAyE;AACzE,sGAA4E;AAC5E,uFAA6D;AAC7D,sFAA4D;AAC5D,6FAAmE;AACnE,+FAAqE;AACrE,gHAAsF;AAEtF,6BAA6B;AAC7B,0GAAgF;AAChF,mGAAyE;AACzE,iGAAuE;AAEvE,qBAAqB;AACrB,kFAAwD;AACxD,oFAA0D;AAE1D,oBAAoB;AACpB,oFAA0D;AAC1D,wFAA8D;AAC9D,+EAAqD;AACrD,uFAA6D;AAC7D,wFAA8D;AAC9D,sFAA4D;AAC5D,uFAA6D;AAC7D,iGAAuE;AACvE,iFAAuD;AAEvD,gCAAgC;AAChC,yGAA+E;AAC/E,qGAA2E;AAE3E,eAAe;AACf,yEAA+C;AAC/C,uEAA6C;AAC7C,yEAA+C;AAC/C,uEAA6C"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AACA,cAAc;AACd,wBAAwB;AACxB,oFAA0D;AAC1D,wFAA8D;AAC9D,yFAA+D;AAC/D,iFAAuD;AACvD,+EAAqD;AAErD,oBAAoB;AACpB,0EAAgD;AAChD,0EAAgD;AAChD,yEAA+C;AAC/C,sFAA4D;AAC5D,wEAA8C;AAC9C,+EAAqD;AACrD,2EAAiD;AACjD,gFAAsD;AACtD,0EAAgD;AAChD,2EAAiD;AACjD,gFAAsD;AACtD,yEAA+C;AAC/C,0FAAgE;AAChE,8EAAoD;AACpD,yEAA+C;AAE/C,yBAAyB;AACzB,oFAA0D;AAC1D,8EAAoD;AACpD,gFAAsD;AACtD,sFAA4D;AAC5D,iFAAuD;AAEvD,2BAA2B;AAC3B,yFAA+D;AAC/D,gFAAsD;AACtD,yFAA+D;AAG/D,QAAQ;AACR,4EAAkD;AAClD,wEAA8C;AAC9C,yEAA+C;AAC/C,oEAA0C;AAC1C,kEAAwC;AACxC,iFAAuD;AAEvD,aAAa;AACb,4EAAkD;AAClD,6EAAmD;AACnD,gFAAsD;AAEtD,aAAa;AACb,yEAA+C;AAC/C,mEAAyC;AACzC,2EAAiD;AAGjD,SAAS;AACT,wBAAwB;AACxB,mGAAyE;AACzE,sGAA4E;AAC5E,uFAA6D;AAC7D,sFAA4D;AAC5D,6FAAmE;AACnE,+FAAqE;AACrE,gHAAsF;AAEtF,6BAA6B;AAC7B,0GAAgF;AAChF,mGAAyE;AACzE,iGAAuE;AAEvE,qBAAqB;AACrB,kFAAwD;AACxD,oFAA0D;AAE1D,oBAAoB;AACpB,oFAA0D;AAC1D,wFAA8D;AAC9D,+EAAqD;AACrD,uFAA6D;AAC7D,wFAA8D;AAC9D,sFAA4D;AAC5D,uFAA6D;AAC7D,iGAAuE;AACvE,iFAAuD;AAEvD,gCAAgC;AAChC,yGAA+E;AAC/E,qGAA2E;AAE3E,eAAe;AACf,yEAA+C;AAC/C,uEAA6C;AAC7C,yEAA+C;AAC/C,uEAA6C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@futdevpro/fsm-dynamo",
|
|
3
|
-
"version": "01.15.
|
|
3
|
+
"version": "01.15.14",
|
|
4
4
|
"description": "Full Stack Model Collection for Dynamic (NodeJS-Typescript) Framework called Dynamo, by Future Development Ltd.",
|
|
5
5
|
"DyBu_settings": {
|
|
6
6
|
"packageType": "full-stack-package",
|
|
@@ -256,7 +256,7 @@
|
|
|
256
256
|
"uuid": "11.1.0"
|
|
257
257
|
},
|
|
258
258
|
"devDependencies": {
|
|
259
|
-
"@futdevpro/dynamo-eslint": "1.15.
|
|
259
|
+
"@futdevpro/dynamo-eslint": "1.15.10",
|
|
260
260
|
"@types/jasmine": "~4.3.5",
|
|
261
261
|
"@typescript-eslint/eslint-plugin": "^8.41.0",
|
|
262
262
|
"@typescript-eslint/parser": "^8.41.0",
|
|
@@ -114,7 +114,7 @@ describe('| DyFM_Object', () => {
|
|
|
114
114
|
expect(result).toEqual(expected);
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
xit('should resolve circular
|
|
117
|
+
xit('should resolve self-circular root reference', () => {
|
|
118
118
|
const obj: any = {};
|
|
119
119
|
obj.self = obj;
|
|
120
120
|
const expected = { self: 'CIRCULATION:ROOT;Object.self' };
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { DyFM_global_settings } from '../constants/global-settings.const';
|
|
2
|
+
import { DyFM_Error } from '../../_models/control-models/error.control-model';
|
|
3
|
+
import { DyFM_requireEnv } from './require-env.util';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* FR-015 Phase 2 Wave 0 — DyFM_requireEnv specs.
|
|
8
|
+
*
|
|
9
|
+
* Coverage:
|
|
10
|
+
* - Node path: returns process.env value when set
|
|
11
|
+
* - Browser path: returns DyFM_global_settings.envOverrides value when
|
|
12
|
+
* process.env is unset
|
|
13
|
+
* - Fallback path: returns options.fallback when both above unset
|
|
14
|
+
* - Throw path: structured DyFM_Error when nothing resolves and no
|
|
15
|
+
* fallback. Verifies errorCode, message contains the env-var name +
|
|
16
|
+
* deploy-target checklist, userMessage is admin-actionable,
|
|
17
|
+
* issuerService matches.
|
|
18
|
+
* - Override priority: process.env wins over envOverrides; envOverrides
|
|
19
|
+
* wins over fallback.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Cleans the test fixture between specs so leak-state from one test doesn't
|
|
25
|
+
* pollute another. We touch the live `DyFM_global_settings.envOverrides`
|
|
26
|
+
* because the spec exercises the same surface the production code reads.
|
|
27
|
+
*/
|
|
28
|
+
function clearOverrides(): void {
|
|
29
|
+
DyFM_global_settings.envOverrides = undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
describe('| DyFM_requireEnv', (): void => {
|
|
34
|
+
|
|
35
|
+
afterEach((): void => {
|
|
36
|
+
clearOverrides();
|
|
37
|
+
delete process.env['DyFM_TEST_REQUIRE_ENV_KEY'];
|
|
38
|
+
delete process.env['DyFM_TEST_REQUIRE_ENV_OTHER'];
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
describe('| Node path (process.env)', (): void => {
|
|
43
|
+
it('| returns process.env value when set', (): void => {
|
|
44
|
+
process.env['DyFM_TEST_REQUIRE_ENV_KEY'] = 'node-value';
|
|
45
|
+
const value: string = DyFM_requireEnv('DyFM_TEST_REQUIRE_ENV_KEY', {
|
|
46
|
+
errorCode: 'TEST-001',
|
|
47
|
+
issuerService: 'spec',
|
|
48
|
+
});
|
|
49
|
+
expect(value).toBe('node-value');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('| empty-string process.env value is treated as unset', (): void => {
|
|
53
|
+
// Truthy guard — empty string means "not configured".
|
|
54
|
+
process.env['DyFM_TEST_REQUIRE_ENV_KEY'] = '';
|
|
55
|
+
DyFM_global_settings.envOverrides = { 'DyFM_TEST_REQUIRE_ENV_KEY': 'browser-value' };
|
|
56
|
+
const value: string = DyFM_requireEnv('DyFM_TEST_REQUIRE_ENV_KEY', {
|
|
57
|
+
errorCode: 'TEST-002',
|
|
58
|
+
issuerService: 'spec',
|
|
59
|
+
});
|
|
60
|
+
expect(value).toBe('browser-value');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
describe('| browser path (envOverrides)', (): void => {
|
|
66
|
+
it('| returns envOverrides value when process.env unset', (): void => {
|
|
67
|
+
DyFM_global_settings.envOverrides = { 'DyFM_TEST_REQUIRE_ENV_KEY': 'browser-value' };
|
|
68
|
+
const value: string = DyFM_requireEnv('DyFM_TEST_REQUIRE_ENV_KEY', {
|
|
69
|
+
errorCode: 'TEST-003',
|
|
70
|
+
issuerService: 'spec',
|
|
71
|
+
});
|
|
72
|
+
expect(value).toBe('browser-value');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('| handles other entries in the same map without interference', (): void => {
|
|
76
|
+
DyFM_global_settings.envOverrides = {
|
|
77
|
+
'DyFM_TEST_REQUIRE_ENV_KEY': 'value-a',
|
|
78
|
+
'DyFM_TEST_REQUIRE_ENV_OTHER': 'value-b',
|
|
79
|
+
};
|
|
80
|
+
expect(DyFM_requireEnv('DyFM_TEST_REQUIRE_ENV_KEY', { errorCode: 'X', issuerService: 's' }))
|
|
81
|
+
.toBe('value-a');
|
|
82
|
+
expect(DyFM_requireEnv('DyFM_TEST_REQUIRE_ENV_OTHER', { errorCode: 'X', issuerService: 's' }))
|
|
83
|
+
.toBe('value-b');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
describe('| fallback path', (): void => {
|
|
89
|
+
it('| returns fallback when neither process.env nor envOverrides resolves', (): void => {
|
|
90
|
+
const value: string = DyFM_requireEnv('DyFM_TEST_REQUIRE_ENV_KEY', {
|
|
91
|
+
errorCode: 'TEST-004',
|
|
92
|
+
issuerService: 'spec',
|
|
93
|
+
fallback: 'fallback-value',
|
|
94
|
+
});
|
|
95
|
+
expect(value).toBe('fallback-value');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('| empty-string fallback is honored (explicit empty intent)', (): void => {
|
|
99
|
+
const value: string = DyFM_requireEnv('DyFM_TEST_REQUIRE_ENV_KEY', {
|
|
100
|
+
errorCode: 'TEST-005',
|
|
101
|
+
issuerService: 'spec',
|
|
102
|
+
fallback: '',
|
|
103
|
+
});
|
|
104
|
+
expect(value).toBe('');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
describe('| throw path — rich DyFM_Error', (): void => {
|
|
110
|
+
it('| throws DyFM_Error when nothing resolves and no fallback', (): void => {
|
|
111
|
+
let caught: unknown = null;
|
|
112
|
+
try {
|
|
113
|
+
DyFM_requireEnv('DyFM_TEST_REQUIRE_ENV_KEY', {
|
|
114
|
+
errorCode: 'TEST-MISSING-001',
|
|
115
|
+
issuerService: 'dynamo-fsm-spec',
|
|
116
|
+
});
|
|
117
|
+
} catch (error: unknown) {
|
|
118
|
+
caught = error;
|
|
119
|
+
}
|
|
120
|
+
expect(caught).toBeInstanceOf(DyFM_Error);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('| error has the caller-supplied errorCode', (): void => {
|
|
124
|
+
try {
|
|
125
|
+
DyFM_requireEnv('DyFM_TEST_REQUIRE_ENV_KEY', {
|
|
126
|
+
errorCode: 'CUSTOM-CODE-XYZ',
|
|
127
|
+
issuerService: 's',
|
|
128
|
+
});
|
|
129
|
+
fail('should have thrown');
|
|
130
|
+
} catch (error: unknown) {
|
|
131
|
+
const err: DyFM_Error = error as DyFM_Error;
|
|
132
|
+
expect(err._errorCode).toBe('CUSTOM-CODE-XYZ');
|
|
133
|
+
// Also present in the aggregated _errorCodes list.
|
|
134
|
+
expect(err._errorCodes).toContain('CUSTOM-CODE-XYZ');
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('| error has status 500 (config error)', (): void => {
|
|
139
|
+
try {
|
|
140
|
+
DyFM_requireEnv('DyFM_TEST_REQUIRE_ENV_KEY', {
|
|
141
|
+
errorCode: 'C', issuerService: 's',
|
|
142
|
+
});
|
|
143
|
+
fail('should have thrown');
|
|
144
|
+
} catch (error: unknown) {
|
|
145
|
+
const err: DyFM_Error = error as DyFM_Error;
|
|
146
|
+
expect(err.___status).toBe(500);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('| error message names the env-var + lists deploy targets', (): void => {
|
|
151
|
+
try {
|
|
152
|
+
DyFM_requireEnv('SOME_SPECIFIC_VAR_NAME', {
|
|
153
|
+
errorCode: 'C', issuerService: 's',
|
|
154
|
+
});
|
|
155
|
+
fail('should have thrown');
|
|
156
|
+
} catch (error: unknown) {
|
|
157
|
+
const err: DyFM_Error = error as DyFM_Error;
|
|
158
|
+
const msg: string = err._message || '';
|
|
159
|
+
// Names the specific var
|
|
160
|
+
expect(msg).toContain('SOME_SPECIFIC_VAR_NAME');
|
|
161
|
+
// Lists each deploy target so an operator knows where to look
|
|
162
|
+
expect(msg).toContain('host .env');
|
|
163
|
+
expect(msg).toContain('docker-compose');
|
|
164
|
+
expect(msg).toContain('Overseer');
|
|
165
|
+
expect(msg).toContain('environment.ts');
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('| error has admin-actionable userMessage (custom or default)', (): void => {
|
|
170
|
+
// Default userMessage
|
|
171
|
+
try {
|
|
172
|
+
DyFM_requireEnv('DEFAULT_MSG_VAR', { errorCode: 'C', issuerService: 's' });
|
|
173
|
+
fail('should have thrown');
|
|
174
|
+
} catch (error: unknown) {
|
|
175
|
+
const err: DyFM_Error = error as DyFM_Error;
|
|
176
|
+
expect(err.__userMessage).toContain('DEFAULT_MSG_VAR');
|
|
177
|
+
expect(err.__userMessage).toContain('operator');
|
|
178
|
+
}
|
|
179
|
+
// Custom userMessage
|
|
180
|
+
try {
|
|
181
|
+
DyFM_requireEnv('CUSTOM_MSG_VAR', {
|
|
182
|
+
errorCode: 'C',
|
|
183
|
+
issuerService: 's',
|
|
184
|
+
userMessage: 'My custom admin instruction',
|
|
185
|
+
});
|
|
186
|
+
fail('should have thrown');
|
|
187
|
+
} catch (error: unknown) {
|
|
188
|
+
const err: DyFM_Error = error as DyFM_Error;
|
|
189
|
+
expect(err.__userMessage).toBe('My custom admin instruction');
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('| error has issuerService set', (): void => {
|
|
194
|
+
try {
|
|
195
|
+
DyFM_requireEnv('V', { errorCode: 'C', issuerService: 'my-package' });
|
|
196
|
+
fail('should have thrown');
|
|
197
|
+
} catch (error: unknown) {
|
|
198
|
+
const err: DyFM_Error = error as DyFM_Error;
|
|
199
|
+
expect(err.___issuerService).toBe('my-package');
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
describe('| lookup priority', (): void => {
|
|
206
|
+
it('| process.env wins over envOverrides', (): void => {
|
|
207
|
+
process.env['DyFM_TEST_REQUIRE_ENV_KEY'] = 'from-process';
|
|
208
|
+
DyFM_global_settings.envOverrides = { 'DyFM_TEST_REQUIRE_ENV_KEY': 'from-overrides' };
|
|
209
|
+
const value: string = DyFM_requireEnv('DyFM_TEST_REQUIRE_ENV_KEY', {
|
|
210
|
+
errorCode: 'C', issuerService: 's',
|
|
211
|
+
});
|
|
212
|
+
expect(value).toBe('from-process');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('| envOverrides wins over fallback', (): void => {
|
|
216
|
+
DyFM_global_settings.envOverrides = { 'DyFM_TEST_REQUIRE_ENV_KEY': 'from-overrides' };
|
|
217
|
+
const value: string = DyFM_requireEnv('DyFM_TEST_REQUIRE_ENV_KEY', {
|
|
218
|
+
errorCode: 'C', issuerService: 's', fallback: 'from-fallback',
|
|
219
|
+
});
|
|
220
|
+
expect(value).toBe('from-overrides');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('| process.env wins over fallback', (): void => {
|
|
224
|
+
process.env['DyFM_TEST_REQUIRE_ENV_KEY'] = 'from-process';
|
|
225
|
+
const value: string = DyFM_requireEnv('DyFM_TEST_REQUIRE_ENV_KEY', {
|
|
226
|
+
errorCode: 'C', issuerService: 's', fallback: 'from-fallback',
|
|
227
|
+
});
|
|
228
|
+
expect(value).toBe('from-process');
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { DyFM_Error } from '../../_models/control-models/error.control-model';
|
|
2
|
+
import { DyFM_global_settings } from '../constants/global-settings.const';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for `DyFM_requireEnv`. See the helper's JSDoc for usage.
|
|
7
|
+
*/
|
|
8
|
+
export interface DyFM_RequireEnv_Options {
|
|
9
|
+
/**
|
|
10
|
+
* Stable error code emitted on the DyFM_Error when the env-var is missing
|
|
11
|
+
* and no fallback is configured. Convention: `<PACKAGE>-ENV-MISSING-<KEY>`,
|
|
12
|
+
* e.g. `DyNM-ENV-MISSING-STORAGE-KEY`, `FDP-ENV-MISSING-AUTH-KEY`.
|
|
13
|
+
*/
|
|
14
|
+
errorCode: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Short-code name of the package responsible for the env-var. Used as the
|
|
18
|
+
* DyFM_Error `___issuerService` for downstream filtering / dashboards.
|
|
19
|
+
*/
|
|
20
|
+
issuerService: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Transition fallback. When set, returned if BOTH `process.env[name]` AND
|
|
24
|
+
* `DyFM_global_settings.envOverrides[name]` are missing.
|
|
25
|
+
*
|
|
26
|
+
* Use only during the additive phase of an env-var migration to keep the
|
|
27
|
+
* package backward-compatible while every deploy target rolls the new
|
|
28
|
+
* env-var out. Remove the `fallback` argument once the rollout is verified
|
|
29
|
+
* everywhere — then a missing env-var fail-fast crashes the consumer at
|
|
30
|
+
* first access with a structured DyFM_Error.
|
|
31
|
+
*/
|
|
32
|
+
fallback?: string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Admin-actionable user-facing message attached to the DyFM_Error. Defaults
|
|
36
|
+
* to a generic config-missing string referencing the env-var name.
|
|
37
|
+
*/
|
|
38
|
+
userMessage?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* FR-015 Phase 2 — isomorphic env-var requirement helper.
|
|
44
|
+
*
|
|
45
|
+
* Universal source of truth for "read this configuration key from an env-var
|
|
46
|
+
* across Node and browser". Replaces per-package `requireEnv` helpers (the
|
|
47
|
+
* Phase 1 local implementation in fdp-templates-nts is being refactored to
|
|
48
|
+
* delegate here).
|
|
49
|
+
*
|
|
50
|
+
* Lookup order:
|
|
51
|
+
* 1. `process.env[envVarName]` — Node-side (server const files at module load).
|
|
52
|
+
* 2. `DyFM_global_settings.envOverrides[envVarName]` — browser-side, populated
|
|
53
|
+
* by the consumer at app bootstrap from its `environment.ts`. See
|
|
54
|
+
* DyFM_Global_Settings.envOverrides docs for the pattern.
|
|
55
|
+
* 3. `options.fallback` if provided — used during migration's additive phase.
|
|
56
|
+
* 4. throw a structured DyFM_Error with the full deploy-target checklist.
|
|
57
|
+
*
|
|
58
|
+
* The throw shape is rich on purpose (per the rich-error rule):
|
|
59
|
+
* - `___status` = 500 (configuration error)
|
|
60
|
+
* - `_errorCode` = caller-supplied stable code
|
|
61
|
+
* - `_message` = debug-level description with the env-var name + deploy-target
|
|
62
|
+
* checklist (host .env, docker-compose env-block, Overseer secrets-store,
|
|
63
|
+
* client environment.ts → bootstrap envOverrides)
|
|
64
|
+
* - `__userMessage` = admin-actionable message
|
|
65
|
+
* - `___issuerService` = caller-supplied package short-code
|
|
66
|
+
*
|
|
67
|
+
* @example Node server const file (module load):
|
|
68
|
+
* ```ts
|
|
69
|
+
* import { DyFM_requireEnv } from '@futdevpro/fsm-dynamo';
|
|
70
|
+
*
|
|
71
|
+
* export const FDP_keysEnv_settingsBase = {
|
|
72
|
+
* authKey: DyFM_requireEnv('FDP_AUTH_KEY', {
|
|
73
|
+
* errorCode: 'FDP-ENV-MISSING-AUTH-KEY',
|
|
74
|
+
* issuerService: 'fdp-templates',
|
|
75
|
+
* fallback: '<inherit literal>', // remove for Wave 3
|
|
76
|
+
* }),
|
|
77
|
+
* };
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @example Browser-side lazy getter (deferred to first access, after consumer
|
|
81
|
+
* bootstrap has populated DyFM_global_settings.envOverrides):
|
|
82
|
+
* ```ts
|
|
83
|
+
* let _cached: string | undefined;
|
|
84
|
+
* function _resolveKey(): string {
|
|
85
|
+
* if (_cached !== undefined) return _cached;
|
|
86
|
+
* _cached = DyFM_requireEnv('DyNM_STORAGE_ENCRYPTION_KEY', { ... });
|
|
87
|
+
* return _cached;
|
|
88
|
+
* }
|
|
89
|
+
* export const DyNM_global_settings = {
|
|
90
|
+
* get storageEncryptionKey(): string { return _resolveKey(); },
|
|
91
|
+
* };
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export function DyFM_requireEnv(
|
|
95
|
+
envVarName: string,
|
|
96
|
+
options: DyFM_RequireEnv_Options,
|
|
97
|
+
): string {
|
|
98
|
+
// Node path: process.env at module-load time. Guarded for browser bundles
|
|
99
|
+
// where `process` may be undefined or shimmed without runtime values.
|
|
100
|
+
if (typeof process !== 'undefined' && process.env && process.env[envVarName]) {
|
|
101
|
+
return process.env[envVarName] as string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Browser path: consumer-populated envOverrides map. Populated at app
|
|
105
|
+
// bootstrap from environment.ts — see DyFM_Global_Settings.envOverrides.
|
|
106
|
+
const override: string | undefined =
|
|
107
|
+
DyFM_global_settings.envOverrides?.[envVarName];
|
|
108
|
+
if (override) {
|
|
109
|
+
return override;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Transition fallback (Wave 1 of an FR-015-style migration).
|
|
113
|
+
if (options.fallback !== undefined) {
|
|
114
|
+
return options.fallback;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Missing — fail fast with a rich, debug-friendly DyFM_Error.
|
|
118
|
+
throw new DyFM_Error({
|
|
119
|
+
status: 500,
|
|
120
|
+
errorCode: options.errorCode,
|
|
121
|
+
message:
|
|
122
|
+
`Environment variable '${envVarName}' is required but was not found. ` +
|
|
123
|
+
`Checked: process.env.${envVarName} (Node) and ` +
|
|
124
|
+
`DyFM_global_settings.envOverrides['${envVarName}'] (browser). ` +
|
|
125
|
+
`Both unset, and no transition fallback configured. ` +
|
|
126
|
+
`Set '${envVarName}' on every deploy target before deploying this ` +
|
|
127
|
+
`package version: (1) host .env on every host running a backend, ` +
|
|
128
|
+
`(2) docker-compose env-block on every consumer service, ` +
|
|
129
|
+
`(3) Overseer secrets-store entry for CI builds, ` +
|
|
130
|
+
`(4) client environment.ts plus app-bootstrap copy into ` +
|
|
131
|
+
`DyFM_global_settings.envOverrides for any browser consumer.`,
|
|
132
|
+
userMessage:
|
|
133
|
+
options.userMessage
|
|
134
|
+
?? `Configuration error — environment variable '${envVarName}' is missing. `
|
|
135
|
+
+ `Contact the responsible operator to set it on this deploy target.`,
|
|
136
|
+
issuerService: options.issuerService,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
@@ -21,4 +21,27 @@ export interface DyFM_Global_Settings {
|
|
|
21
21
|
* this setting will set which logs will be shown
|
|
22
22
|
*/
|
|
23
23
|
log_settings: DyFM_GlobalLog_Settings;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* FR-015 Phase 2 — isomorphic env-var overrides surface.
|
|
27
|
+
*
|
|
28
|
+
* On Node, `DyFM_requireEnv(name, ...)` reads `process.env[name]` directly.
|
|
29
|
+
* On browser there is no `process.env` at runtime, so consumers populate
|
|
30
|
+
* this map at app bootstrap from `environment.ts`:
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { DyFM_global_settings } from '@futdevpro/fsm-dynamo';
|
|
34
|
+
* import { environment } from './environments/environment';
|
|
35
|
+
*
|
|
36
|
+
* DyFM_global_settings.envOverrides = {
|
|
37
|
+
* FDP_AUTH_KEY: environment.authKey,
|
|
38
|
+
* FDP_EXTRA_AUTH_STORAGE_KEY: environment.extraAuthStorageKey,
|
|
39
|
+
* DyNM_STORAGE_ENCRYPTION_KEY: environment.storageEncryptionKey,
|
|
40
|
+
* };
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* `DyFM_requireEnv` checks `process.env` first, then this map, then
|
|
44
|
+
* the caller's optional `fallback`, then throws a structured DyFM_Error.
|
|
45
|
+
*/
|
|
46
|
+
envOverrides?: Record<string, string>;
|
|
24
47
|
}
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from './_collections/utils/json-error-helper.util';
|
|
|
15
15
|
export * from './_collections/utils/log.util';
|
|
16
16
|
export * from './_collections/utils/round-list.util';
|
|
17
17
|
export * from './_collections/utils/object.util';
|
|
18
|
+
export * from './_collections/utils/require-env.util';
|
|
18
19
|
export * from './_collections/utils/stack.util';
|
|
19
20
|
export * from './_collections/utils/string.util';
|
|
20
21
|
export * from './_collections/utils/string-case.util';
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { DyFM_Vector2 } from '../../../_models/interfaces/vector2.interface';
|
|
2
|
-
import { DyFM_BoxBounds_Util } from './box-bounds.util';
|
|
3
|
-
import { DyFM_Vector2_Util } from './vector2.util';
|
|
4
|
-
|
|
5
|
-
xdescribe('| DyFM_BoxBounds', () => {
|
|
6
|
-
let boxBounds: DyFM_BoxBounds_Util;
|
|
7
|
-
const mockPosition = new DyFM_Vector2_Util({ x: 0, y: 0 });
|
|
8
|
-
const mockSize = new DyFM_Vector2_Util({ x: 10, y: 10 });
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
boxBounds = new DyFM_BoxBounds_Util(mockPosition, mockSize);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('| should set position correctly ()', () => {
|
|
15
|
-
const newPosition = new DyFM_Vector2_Util({ x: 5, y: 5 });
|
|
16
|
-
|
|
17
|
-
boxBounds.pos = newPosition;
|
|
18
|
-
expect(boxBounds.pos).toEqual(newPosition);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('| should get position correctly', () => {
|
|
22
|
-
const expectedPosition = new DyFM_Vector2_Util({ x: 5, y: 5 });
|
|
23
|
-
|
|
24
|
-
expect(boxBounds.pos).toEqual(expectedPosition);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('| should set size correctly', () => {
|
|
28
|
-
const newSize = new DyFM_Vector2_Util({ x: 20, y: 20 });
|
|
29
|
-
|
|
30
|
-
boxBounds.size = newSize;
|
|
31
|
-
expect(boxBounds.size).toEqual(newSize);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('| should get size correctly', () => {
|
|
35
|
-
const expectedSize = new DyFM_Vector2_Util({ x: 20, y: 20 });
|
|
36
|
-
|
|
37
|
-
expect(boxBounds.size).toEqual(expectedSize);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
xit('| should calculate center correctly', () => {
|
|
41
|
-
const expectedCenter = new DyFM_Vector2_Util({ x: 10, y: 10 });
|
|
42
|
-
expect(boxBounds.center).toEqual(expectedCenter);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('| should calculate center margin correctly', () => {
|
|
46
|
-
const expectedCenterMargin = new DyFM_Vector2_Util({ x: -5, y: -5 });
|
|
47
|
-
expect(boxBounds.centerMargin).toEqual(expectedCenterMargin);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('| should return true for constructed method', () => {
|
|
51
|
-
expect(boxBounds.constructed()).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('| should throw error when newValues has undefined position', () => {
|
|
55
|
-
expect(() => {
|
|
56
|
-
boxBounds.newValues(undefined as any, { x: 10, y: 10 });
|
|
57
|
-
}).toThrow(Error);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('| should throw error when newValues has undefined size', () => {
|
|
61
|
-
expect(() => {
|
|
62
|
-
boxBounds.newValues({ x: 0, y: 0 }, undefined as any);
|
|
63
|
-
}).toThrow(Error);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('| should clone and return a new instance', () => {
|
|
67
|
-
const clonedBoxBounds = boxBounds.clone();
|
|
68
|
-
|
|
69
|
-
expect(clonedBoxBounds).toBeInstanceOf(DyFM_BoxBounds_Util);
|
|
70
|
-
expect(clonedBoxBounds).toEqual(boxBounds);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
it('| should initialize with given position and size', () => {
|
|
75
|
-
expect(boxBounds.pos.x).toBe(mockPosition.x);
|
|
76
|
-
expect(boxBounds.pos.y).toBe(mockPosition.y);
|
|
77
|
-
expect(boxBounds.size.x).toBe(mockSize.x);
|
|
78
|
-
expect(boxBounds.size.y).toBe(mockSize.y);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('| should calculate center correctly', () => {
|
|
82
|
-
const expectedCenter = DyFM_Vector2_Util.plus(mockPosition, DyFM_Vector2_Util.divide(mockSize, 2));
|
|
83
|
-
expect(boxBounds.center.x).toBe(expectedCenter.x);
|
|
84
|
-
expect(boxBounds.center.y).toBe(expectedCenter.y);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('| should update position and size with newValues method', () => {
|
|
88
|
-
const newPosition: DyFM_Vector2 = { x: 50, y: 60 };
|
|
89
|
-
const newSize: DyFM_Vector2 = { x: 70, y: 80 };
|
|
90
|
-
boxBounds.newValues(newPosition, newSize);
|
|
91
|
-
|
|
92
|
-
expect(boxBounds.pos.x).toBe(newPosition.x);
|
|
93
|
-
expect(boxBounds.pos.y).toBe(newPosition.y);
|
|
94
|
-
expect(boxBounds.size.x).toBe(newSize.x);
|
|
95
|
-
expect(boxBounds.size.y).toBe(newSize.y);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('| should throw error if newValues is called with undefined position', () => {
|
|
99
|
-
expect(() => boxBounds.newValues(undefined as any, mockSize)).toThrowError('new position is undefined!');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('| should throw error if newValues is called with undefined size', () => {
|
|
103
|
-
expect(() => boxBounds.newValues(mockPosition, undefined as any)).toThrowError('new size is undefined!');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('| should correctly determine if a position is within bounds', () => {
|
|
107
|
-
const insidePosition: DyFM_Vector2 = { x: 20, y: 30 };
|
|
108
|
-
const outsidePosition: DyFM_Vector2 = { x: 100, y: 100 };
|
|
109
|
-
|
|
110
|
-
expect(boxBounds.bounds(insidePosition)).toBeTrue();
|
|
111
|
-
expect(boxBounds.bounds(outsidePosition)).toBeFalse();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('| should clone itself correctly', () => {
|
|
115
|
-
const clone = boxBounds.clone();
|
|
116
|
-
expect(clone.pos.x).toBe(boxBounds.pos.x);
|
|
117
|
-
expect(clone.pos.y).toBe(boxBounds.pos.y);
|
|
118
|
-
expect(clone.size.x).toBe(boxBounds.size.x);
|
|
119
|
-
expect(clone.size.y).toBe(boxBounds.size.y);
|
|
120
|
-
expect(clone).not.toBe(boxBounds);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
});
|