@teqfw/di 2.5.0 → 2.6.0

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.
@@ -1,19 +1,19 @@
1
1
  // @ts-check
2
2
 
3
3
  /**
4
- * @namespace TeqFw_Di_Def_Parser
4
+ * @namespace TeqFw_Di_Parser
5
5
  * @description CDC parser that builds dependency identity DTOs.
6
6
  */
7
7
 
8
- import TeqFw_Di_Enum_Composition from '../Enum/Composition.mjs';
9
- import TeqFw_Di_Enum_Life from '../Enum/Life.mjs';
10
- import TeqFw_Di_Enum_Platform from '../Enum/Platform.mjs';
11
- import {Factory as TeqFw_Di_Dto_DepId_Factory} from '../Dto/DepId.mjs';
8
+ import TeqFw_Di_Enum_Composition from './Enum/Composition.mjs';
9
+ import TeqFw_Di_Enum_Life from './Enum/Life.mjs';
10
+ import TeqFw_Di_Enum_Platform from './Enum/Platform.mjs';
11
+ import {Factory as TeqFw_Di_Dto_DepId_Factory} from './Dto/DepId.mjs';
12
12
 
13
13
  /**
14
14
  * Parser for CDC identifiers into frozen dependency identity DTO.
15
15
  */
16
- export default class TeqFw_Di_Def_Parser {
16
+ export default class TeqFw_Di_Parser {
17
17
  /**
18
18
  * Creates parser instance.
19
19
  */
@@ -24,35 +24,36 @@ export default class TeqFw_Di_Def_Parser {
24
24
  let logger = null;
25
25
 
26
26
  /**
27
- * Parses one CDC identifier and returns normalized frozen dependency DTO.
27
+ * Detects platform prefix and strips it from the source string.
28
28
  *
29
- * @param {string} cdc CDC identifier string.
30
- * @returns {TeqFw_Di_DepId__DTO}
29
+ * @param {string} source CDC source without validation.
30
+ * @returns {{platform: typeof TeqFw_Di_Enum_Platform[keyof typeof TeqFw_Di_Enum_Platform], source: string}}
31
31
  */
32
- this.parse = function (cdc) {
33
- if (logger) logger.log(`Parser.parse: input='${cdc}'.`);
34
- if (typeof cdc !== 'string') throw new Error('CDC must be a string.');
35
- if (cdc.length === 0) throw new Error('CDC must be non-empty.');
36
- if (!/^[\x00-\x7F]+$/.test(cdc)) throw new Error('CDC must be ASCII.');
37
-
38
- /** @type {string} */
39
- const origin = cdc;
40
- let source = cdc;
32
+ const detectPlatform = function (source) {
41
33
  /** @type {typeof TeqFw_Di_Enum_Platform[keyof typeof TeqFw_Di_Enum_Platform]} */
42
34
  let platform = TeqFw_Di_Enum_Platform.TEQ;
43
-
44
35
  if (source.startsWith('node:')) {
45
36
  platform = TeqFw_Di_Enum_Platform.NODE;
46
- source = source.slice(5);
47
- } else if (source.startsWith('npm:')) {
37
+ return {platform, source: source.slice(5)};
38
+ }
39
+ if (source.startsWith('npm:')) {
48
40
  platform = TeqFw_Di_Enum_Platform.NPM;
49
- source = source.slice(4);
50
- } else if (source.startsWith('teq:')) {
41
+ return {platform, source: source.slice(4)};
42
+ }
43
+ if (source.startsWith('teq:')) {
51
44
  throw new Error('Explicit teq: prefix is forbidden.');
52
45
  }
46
+ return {platform, source};
47
+ };
53
48
 
54
- if (source.length === 0) throw new Error('moduleName must be non-empty.');
55
-
49
+ /**
50
+ * Parses lifecycle and wrapper suffix from the source string.
51
+ *
52
+ * @param {string} source CDC source without platform prefix.
53
+ * @param {typeof TeqFw_Di_Enum_Platform[keyof typeof TeqFw_Di_Enum_Platform]} platform
54
+ * @returns {{core: string, life: typeof TeqFw_Di_Enum_Life[keyof typeof TeqFw_Di_Enum_Life] | null, lifecycleDeclared: boolean, wrappers: string[]}}
55
+ */
56
+ const parseLifecycle = function (source, platform) {
56
57
  /** @type {typeof TeqFw_Di_Enum_Life[keyof typeof TeqFw_Di_Enum_Life] | null} */
57
58
  let life = null;
58
59
  let lifecycleDeclared = false;
@@ -74,13 +75,24 @@ export default class TeqFw_Di_Def_Parser {
74
75
  if (suffix.length > 0) {
75
76
  wrappers = suffix.slice(1).split('_');
76
77
  }
77
- } else {
78
- if (source.includes('$')) throw new Error('Invalid lifecycle encoding.');
79
- if ((platform !== TeqFw_Di_Enum_Platform.NODE) && /(?:^|[^_])_[a-z][0-9A-Za-z]*$/.test(source)) {
80
- throw new Error('Wrapper without lifecycle is forbidden.');
81
- }
78
+ return {core, life, lifecycleDeclared, wrappers};
82
79
  }
83
80
 
81
+ if (source.includes('$')) throw new Error('Invalid lifecycle encoding.');
82
+ if ((platform !== TeqFw_Di_Enum_Platform.NODE) && /(?:^|[^_])_[a-z][0-9A-Za-z]*$/.test(source)) {
83
+ throw new Error('Wrapper without lifecycle is forbidden.');
84
+ }
85
+
86
+ return {core, life, lifecycleDeclared, wrappers};
87
+ };
88
+
89
+ /**
90
+ * Splits module and export names from canonical core string.
91
+ *
92
+ * @param {string} core CDC core without lifecycle suffix.
93
+ * @returns {{moduleName: string, exportName: string|null}}
94
+ */
95
+ const parseModuleExport = function (core) {
84
96
  const firstDelim = core.indexOf('__');
85
97
  const lastDelim = core.lastIndexOf('__');
86
98
  if ((firstDelim !== -1) && (firstDelim !== lastDelim)) throw new Error('Export delimiter must appear at most once.');
@@ -98,6 +110,17 @@ export default class TeqFw_Di_Def_Parser {
98
110
  if (exportName.includes('$')) throw new Error('Export must not contain $.');
99
111
  }
100
112
 
113
+ return {moduleName, exportName};
114
+ };
115
+
116
+ /**
117
+ * Validates canonical module name for platform-specific rules.
118
+ *
119
+ * @param {string} moduleName
120
+ * @param {typeof TeqFw_Di_Enum_Platform[keyof typeof TeqFw_Di_Enum_Platform]} platform
121
+ * @returns {void}
122
+ */
123
+ const assertModuleName = function (moduleName, platform) {
101
124
  if (!moduleName) throw new Error('moduleName must be non-empty.');
102
125
  if (moduleName.startsWith('_') || moduleName.startsWith('$')) throw new Error('moduleName must not start with _ or $.');
103
126
  if (moduleName.includes('__')) throw new Error('moduleName must not contain __.');
@@ -113,23 +136,47 @@ export default class TeqFw_Di_Def_Parser {
113
136
  } else if (!/^[@A-Za-z_][$0-9A-Za-z_./-]*$/.test(moduleName)) {
114
137
  throw new Error('npm moduleName must satisfy the package specifier form.');
115
138
  }
139
+ };
140
+
141
+ /**
142
+ * Parses one CDC identifier and returns normalized frozen dependency DTO.
143
+ *
144
+ * @param {string} cdc CDC identifier string.
145
+ * @returns {TeqFw_Di_DepId__DTO}
146
+ */
147
+ this.parse = function (cdc) {
148
+ if (logger) logger.log(`Parser.parse: input='${cdc}'.`);
149
+ if (typeof cdc !== 'string') throw new Error('CDC must be a string.');
150
+ if (cdc.length === 0) throw new Error('CDC must be non-empty.');
151
+ if (!/^[\x00-\x7F]+$/.test(cdc)) throw new Error('CDC must be ASCII.');
152
+
153
+ /** @type {string} */
154
+ const origin = cdc;
155
+ const detected = detectPlatform(cdc);
156
+ const platform = detected.platform;
157
+ const source = detected.source;
158
+ if (source.length === 0) throw new Error('moduleName must be non-empty.');
159
+ const lifecycle = parseLifecycle(source, platform);
160
+ const split = parseModuleExport(lifecycle.core);
161
+ assertModuleName(split.moduleName, platform);
116
162
 
117
163
  /** @type {typeof TeqFw_Di_Enum_Composition[keyof typeof TeqFw_Di_Enum_Composition]} */
118
164
  let composition = TeqFw_Di_Enum_Composition.AS_IS;
165
+ let exportName = split.exportName;
119
166
  if (exportName !== null) {
120
167
  composition = TeqFw_Di_Enum_Composition.FACTORY;
121
- } else if (lifecycleDeclared) {
122
- composition = TeqFw_Di_Enum_Composition.FACTORY;
168
+ } else if (lifecycle.lifecycleDeclared) {
123
169
  exportName = 'default';
170
+ composition = TeqFw_Di_Enum_Composition.FACTORY;
124
171
  }
125
172
 
126
173
  const depId = depIdFactory.create({
127
- moduleName,
174
+ moduleName: split.moduleName,
128
175
  platform,
129
176
  exportName,
130
177
  composition,
131
- life,
132
- wrappers,
178
+ life: lifecycle.life,
179
+ wrappers: lifecycle.wrappers,
133
180
  origin,
134
181
  });
135
182
  if (logger) logger.log(`Parser.parse: produced='${depId.platform}::${depId.moduleName}'.`);
@@ -1,44 +0,0 @@
1
- // @ts-check
2
-
3
- /**
4
- * @namespace TeqFw_Di_Container_Instantiate_ExportSelector
5
- * @description Selects target export from loaded module namespace.
6
- */
7
-
8
- /**
9
- * Instantiate-stage export selector.
10
- *
11
- * Selects exactly one export from an already loaded ES module namespace object
12
- * using only `depId.exportName`.
13
- */
14
- export default class TeqFw_Di_Container_Instantiate_ExportSelector {
15
- /**
16
- * Creates export selector instance.
17
- */
18
- constructor() {
19
- /**
20
- * Selects a raw export value from module namespace.
21
- *
22
- * @param {object} namespace Loaded ES module namespace object.
23
- * @param {TeqFw_Di_DepId__DTO} depId Dependency identity DTO.
24
- * @returns {unknown} Raw selected export value.
25
- */
26
- this.select = function (namespace, depId) {
27
- if (!namespace || (typeof namespace !== 'object')) {
28
- throw new Error('Namespace must be an object.');
29
- }
30
-
31
- /** @type {string|null} */
32
- const exportName = depId.exportName;
33
- if (exportName === null) {
34
- throw new Error('Export name must not be null for export selection.');
35
- }
36
-
37
- if (!(exportName in namespace)) {
38
- throw new Error(`Export '${exportName}' is not found in module namespace.`);
39
- }
40
-
41
- return namespace[exportName];
42
- };
43
- }
44
- }