@futdevpro/fsm-dynamo 1.15.13 → 1.15.15

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.
@@ -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
@@ -11,10 +11,12 @@ export * from './_collections/constants/times.const';
11
11
  export * from './_collections/utils/array.util';
12
12
  export * from './_collections/utils/async.util';
13
13
  export * from './_collections/utils/data.util';
14
+ export * from './_collections/utils/extract-error-message.util';
14
15
  export * from './_collections/utils/json-error-helper.util';
15
16
  export * from './_collections/utils/log.util';
16
17
  export * from './_collections/utils/round-list.util';
17
18
  export * from './_collections/utils/object.util';
19
+ export * from './_collections/utils/require-env.util';
18
20
  export * from './_collections/utils/stack.util';
19
21
  export * from './_collections/utils/string.util';
20
22
  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
- });