@teqfw/di 2.7.0 → 2.8.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.
@@ -0,0 +1,208 @@
1
+ // @ts-check
2
+
3
+ import TeqFw_Di_Node_Registry_Package from './Package.mjs';
4
+
5
+ /**
6
+ * @namespace TeqFw_Di_Node_Registry_Namespace
7
+ * @description Deterministic Node.js namespace registry derived from the runtime package graph.
8
+ */
9
+
10
+ /**
11
+ * @typedef {object} TeqFw_Di_Node_Registry_Namespace_Dependencies
12
+ * @property {{readFile(path: string, encoding: string): Promise<string>, realpath(path: string): Promise<string>, stat(path: string): Promise<{isDirectory(): boolean}>}} fs
13
+ * @property {{join(...paths: string[]): string, dirname(path: string): string, relative(from: string, to: string): string, resolve(...paths: string[]): string, isAbsolute(path: string): boolean}} path
14
+ * @property {string} appRoot
15
+ */
16
+
17
+ /**
18
+ * @typedef {object} TeqFw_Di_Node_Registry_Namespace_Entry
19
+ * @property {string} prefix
20
+ * @property {string} dirAbs
21
+ * @property {string} ext
22
+ */
23
+
24
+ /**
25
+ * Builds a deterministic immutable Node.js namespace registry from runtime package records.
26
+ */
27
+ export default class TeqFw_Di_Node_Registry_Namespace {
28
+ /**
29
+ * @param {TeqFw_Di_Node_Registry_Namespace_Dependencies} deps
30
+ */
31
+ constructor({fs, path, appRoot}) {
32
+ /**
33
+ * @param {string} rootAbs
34
+ * @param {string} candidateAbs
35
+ * @returns {boolean}
36
+ */
37
+ const isInside = function (rootAbs, candidateAbs) {
38
+ const rel = path.relative(rootAbs, candidateAbs);
39
+ return (rel === '') || (!rel.startsWith('..') && !path.isAbsolute(rel));
40
+ };
41
+
42
+ /**
43
+ * @param {unknown} value
44
+ * @returns {value is Record<string, unknown>}
45
+ */
46
+ const isRecord = function (value) {
47
+ return (value !== null) && (typeof value === 'object') && !Array.isArray(value);
48
+ };
49
+
50
+ /**
51
+ * @param {{name: string, rootAbs: string}} publisher
52
+ * @returns {string}
53
+ */
54
+ const publisherLabel = function (publisher) {
55
+ return `package '${publisher.name}' at '${publisher.rootAbs}'`;
56
+ };
57
+
58
+ /**
59
+ * @param {string} absPath
60
+ * @returns {Promise<boolean>}
61
+ */
62
+ const isDirectory = async function (absPath) {
63
+ try {
64
+ return (await fs.stat(absPath)).isDirectory();
65
+ } catch {
66
+ return false;
67
+ }
68
+ };
69
+
70
+ /**
71
+ * @param {string} packageRootAbs
72
+ * @param {string} dirAbs
73
+ * @returns {Promise<void>}
74
+ */
75
+ const assertInsidePackageRoot = async function (packageRootAbs, dirAbs) {
76
+ const packageRootReal = await fs.realpath(packageRootAbs);
77
+ const dirReal = await fs.realpath(dirAbs);
78
+ if (!isInside(packageRootReal, dirReal)) {
79
+ throw new Error('Namespace path resolves outside package root: ' + dirAbs + '.');
80
+ }
81
+ };
82
+
83
+ /**
84
+ * @param {unknown} ext
85
+ * @returns {string}
86
+ */
87
+ const normalizeExt = function (ext) {
88
+ if (ext === undefined) return '.mjs';
89
+ if ((typeof ext !== 'string') || (ext.length === 0)) {
90
+ throw new Error('Namespace extension must be a non-empty string.');
91
+ }
92
+ const normalized = ext.startsWith('.') ? ext : '.' + ext;
93
+ if ((normalized !== '.mjs') && (normalized !== '.js')) {
94
+ throw new Error('Namespace extension is not ESM-compatible: ' + normalized + '.');
95
+ }
96
+ return normalized;
97
+ };
98
+
99
+ /**
100
+ * @param {unknown} raw
101
+ * @param {{name: string, rootAbs: string}} publisher
102
+ * @param {{source: 'canonical'|'legacy', index: number}} origin
103
+ * @returns {Promise<{entry: TeqFw_Di_Node_Registry_Namespace_Entry, origin: {source: 'canonical'|'legacy', index: number}}>}
104
+ */
105
+ const normalizeEntry = async function (raw, publisher, origin) {
106
+ const packageRootAbs = publisher.rootAbs;
107
+ const source = `${publisherLabel(publisher)} ${origin.source} namespace declaration at index ${origin.index}`;
108
+ if (!isRecord(raw)) {
109
+ throw new Error(`DI namespace declared by ${source} must be an object.`);
110
+ }
111
+ const item = raw;
112
+ const prefix = item.prefix;
113
+ if ((typeof prefix !== 'string') || (prefix.length === 0) || !prefix.endsWith('_')) {
114
+ throw new Error(`DI namespace prefix declared by ${source} is invalid: ${String(prefix)}.`);
115
+ }
116
+
117
+ const rawPath = item.path;
118
+ if ((typeof rawPath !== 'string') || (rawPath.length === 0) || path.isAbsolute(rawPath)) {
119
+ throw new Error(`DI namespace path declared by ${source} must be a non-empty relative path: ${String(rawPath)}.`);
120
+ }
121
+ const dirAbs = path.resolve(packageRootAbs, rawPath);
122
+ if (!isInside(packageRootAbs, dirAbs)) {
123
+ throw new Error(`DI namespace path declared by ${source} lexically escapes its package root: ${rawPath}.`);
124
+ }
125
+ if (!(await isDirectory(dirAbs))) {
126
+ throw new Error(`DI namespace path declared by ${source} does not point to an existing directory: ${dirAbs}.`);
127
+ }
128
+ try {
129
+ await assertInsidePackageRoot(packageRootAbs, dirAbs);
130
+ } catch (error) {
131
+ throw new Error(`DI namespace path declared by ${source} resolves outside its package root: ${dirAbs}.`, {cause: error});
132
+ }
133
+ try {
134
+ return {entry: {prefix, dirAbs, ext: normalizeExt(item.ext)}, origin};
135
+ } catch (error) {
136
+ throw new Error(`DI namespace extension declared by ${source} is invalid: ${String(error)}.`, {cause: error});
137
+ }
138
+ };
139
+
140
+ /**
141
+ * Selects one supported declaration set without merging schemas.
142
+ *
143
+ * @param {Readonly<Record<string, unknown>>} packageJson
144
+ * @param {{name: string, rootAbs: string}} publisher
145
+ * @returns {{source: 'canonical'|'legacy', entries: unknown[]}|undefined}
146
+ */
147
+ const selectDeclarationSet = function (packageJson, publisher) {
148
+ const teqfw = packageJson.teqfw;
149
+ if (teqfw === undefined) return undefined;
150
+ if (!isRecord(teqfw)) throw new Error(`TeqFW metadata for ${publisherLabel(publisher)} must be an object.`);
151
+ const fw = teqfw.fw;
152
+ if (fw !== undefined) {
153
+ if (!isRecord(fw)) throw new Error(`TeqFW framework metadata for ${publisherLabel(publisher)} must be an object.`);
154
+ const di = fw.di;
155
+ if (di !== undefined) {
156
+ if (!isRecord(di)) throw new Error(`TeqFW DI metadata for ${publisherLabel(publisher)} must be an object.`);
157
+ if (Object.hasOwn(di, 'namespaces')) {
158
+ if (!Array.isArray(di.namespaces)) {
159
+ throw new Error(`Canonical DI namespaces declared by ${publisherLabel(publisher)} must be an array.`);
160
+ }
161
+ return {source: 'canonical', entries: di.namespaces};
162
+ }
163
+ }
164
+ }
165
+ if (Object.hasOwn(teqfw, 'namespaces')) {
166
+ if (!Array.isArray(teqfw.namespaces)) {
167
+ throw new Error(`Legacy DI namespaces declared by ${publisherLabel(publisher)} must be an array.`);
168
+ }
169
+ return {source: 'legacy', entries: teqfw.namespaces};
170
+ }
171
+ return undefined;
172
+ };
173
+
174
+ /**
175
+ * @returns {Promise<ReadonlyArray<TeqFw_Di_Node_Registry_Namespace_Entry>>}
176
+ */
177
+ this.build = async function () {
178
+ const packages = await new TeqFw_Di_Node_Registry_Package({fs, path, appRoot}).build();
179
+ /** @type {Map<string, {publisher: {name: string, rootAbs: string}, origin: {source: 'canonical'|'legacy', index: number}}>} */
180
+ const publisherByPrefix = new Map();
181
+ /** @type {TeqFw_Di_Node_Registry_Namespace_Entry[]} */
182
+ const entries = [];
183
+
184
+ for (const onePackage of packages) {
185
+ const packageJson = onePackage.packageJson;
186
+ const publisher = {name: onePackage.name, rootAbs: onePackage.rootAbs};
187
+ const declarationSet = selectDeclarationSet(packageJson, publisher);
188
+ if (declarationSet === undefined) continue;
189
+ for (const [index, raw] of declarationSet.entries.entries()) {
190
+ const origin = {source: declarationSet.source, index};
191
+ const normalized = await normalizeEntry(raw, publisher, origin);
192
+ const existing = publisherByPrefix.get(normalized.entry.prefix);
193
+ if (existing) {
194
+ throw new Error(`Duplicate DI namespace prefix '${normalized.entry.prefix}' declared by ${publisherLabel(publisher)} ${origin.source} namespace declaration at index ${origin.index} conflicts with ${publisherLabel(existing.publisher)} ${existing.origin.source} namespace declaration at index ${existing.origin.index}.`);
195
+ }
196
+ publisherByPrefix.set(normalized.entry.prefix, {publisher, origin: normalized.origin});
197
+ entries.push(normalized.entry);
198
+ }
199
+ }
200
+
201
+ entries.sort((a, b) => (b.prefix.length - a.prefix.length) || a.prefix.localeCompare(b.prefix));
202
+ for (const entry of entries) {
203
+ Object.freeze(entry);
204
+ }
205
+ return Object.freeze(entries);
206
+ };
207
+ }
208
+ }
@@ -0,0 +1,215 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @namespace TeqFw_Di_Node_Registry_Package
5
+ * @description Deterministic Node.js composition-stage graph of installed runtime packages.
6
+ */
7
+
8
+ /**
9
+ * @typedef {object} TeqFw_Di_Node_Registry_Package_Dependencies
10
+ * @property {{readFile(path: string, encoding: string): Promise<string>, realpath(path: string): Promise<string>, stat(path: string): Promise<{isDirectory(): boolean}>}} fs
11
+ * @property {{join(...paths: string[]): string, dirname(path: string): string, relative(from: string, to: string): string, resolve(...paths: string[]): string, isAbsolute(path: string): boolean}} path
12
+ * @property {string} appRoot
13
+ */
14
+
15
+ /**
16
+ * Builds a deterministic immutable dependency-first list of root and installed runtime packages.
17
+ */
18
+ export default class TeqFw_Di_Node_Registry_Package {
19
+ /**
20
+ * @param {TeqFw_Di_Node_Registry_Package_Dependencies} deps
21
+ */
22
+ constructor({fs, path, appRoot}) {
23
+ const appRootAbs = path.resolve(appRoot);
24
+
25
+ /**
26
+ * @param {unknown} value
27
+ * @returns {value is Record<string, unknown>}
28
+ */
29
+ const isRecord = function (value) {
30
+ return (value !== null) && (typeof value === 'object') && !Array.isArray(value);
31
+ };
32
+
33
+ /**
34
+ * @param {object} value
35
+ * @returns {object}
36
+ */
37
+ const freezeDeep = function (value) {
38
+ for (const nested of Object.values(value)) {
39
+ if ((nested !== null) && (typeof nested === 'object') && !Object.isFrozen(nested)) {
40
+ freezeDeep(nested);
41
+ }
42
+ }
43
+ return Object.freeze(value);
44
+ };
45
+
46
+ /**
47
+ * @param {string} fileAbs
48
+ * @returns {Promise<Record<string, unknown>>}
49
+ */
50
+ const readJson = async function (fileAbs) {
51
+ let content;
52
+ try {
53
+ content = await fs.readFile(fileAbs, 'utf8');
54
+ } catch (error) {
55
+ throw new Error('Unable to read package metadata ' + fileAbs + ': ' + String(error) + '.');
56
+ }
57
+ try {
58
+ const value = JSON.parse(content);
59
+ if (!isRecord(value)) {
60
+ throw new Error('root must be an object');
61
+ }
62
+ return value;
63
+ } catch (error) {
64
+ throw new Error('Invalid package metadata ' + fileAbs + ': ' + String(error) + '.');
65
+ }
66
+ };
67
+
68
+ /**
69
+ * @param {string} packageRootAbs
70
+ * @returns {Promise<{name: string, packageJson: Record<string, unknown>, dependencies: string[]}>}
71
+ */
72
+ const readPackageMetadata = async function (packageRootAbs) {
73
+ const packageJsonAbs = path.join(packageRootAbs, 'package.json');
74
+ const packageJson = await readJson(packageJsonAbs);
75
+ const name = packageJson.name;
76
+ if ((typeof name !== 'string') || (name.length === 0)) {
77
+ throw new Error('Invalid package metadata ' + packageJsonAbs + ': name must be a non-empty string.');
78
+ }
79
+
80
+ const rawDependencies = packageJson.dependencies;
81
+ if (rawDependencies === undefined) {
82
+ return {name, packageJson, dependencies: []};
83
+ }
84
+ if (!isRecord(rawDependencies)) {
85
+ throw new Error('Invalid dependency metadata ' + packageJsonAbs + ': dependencies must be an object.');
86
+ }
87
+ const dependencies = Object.keys(rawDependencies).sort();
88
+ for (const dependencyName of dependencies) {
89
+ const dependencyRange = rawDependencies[dependencyName];
90
+ if ((dependencyName.length === 0) || (typeof dependencyRange !== 'string') || (dependencyRange.length === 0)) {
91
+ throw new Error('Invalid dependency metadata ' + packageJsonAbs + ': ' + dependencyName + ' must have a non-empty string declaration.');
92
+ }
93
+ }
94
+ return {name, packageJson, dependencies};
95
+ };
96
+
97
+ /**
98
+ * @param {string} absPath
99
+ * @returns {Promise<boolean>}
100
+ */
101
+ const isDirectory = async function (absPath) {
102
+ try {
103
+ return (await fs.stat(absPath)).isDirectory();
104
+ } catch {
105
+ return false;
106
+ }
107
+ };
108
+
109
+ /**
110
+ * @param {string} rootAbs
111
+ * @param {string} candidateAbs
112
+ * @returns {boolean}
113
+ */
114
+ const isInside = function (rootAbs, candidateAbs) {
115
+ const rel = path.relative(rootAbs, candidateAbs);
116
+ return (rel === '') || (!rel.startsWith('..') && !path.isAbsolute(rel));
117
+ };
118
+
119
+ /**
120
+ * @param {string} candidateAbs
121
+ * @returns {Promise<boolean>}
122
+ */
123
+ const hasPackageJson = async function (candidateAbs) {
124
+ try {
125
+ await fs.stat(path.join(candidateAbs, 'package.json'));
126
+ return true;
127
+ } catch {
128
+ return false;
129
+ }
130
+ };
131
+
132
+ /**
133
+ * @param {string} packageName
134
+ * @param {string} fromPackageRootAbs
135
+ * @returns {Promise<string>}
136
+ */
137
+ const resolveDependencyPackageRoot = async function (packageName, fromPackageRootAbs) {
138
+ let cursor = fromPackageRootAbs;
139
+ while (isInside(appRootAbs, cursor)) {
140
+ const candidate = path.join(cursor, 'node_modules', packageName);
141
+ if ((await isDirectory(candidate)) && (await hasPackageJson(candidate))) {
142
+ return path.resolve(candidate);
143
+ }
144
+ if (cursor === appRootAbs) break;
145
+ const parent = path.dirname(cursor);
146
+ if (parent === cursor) break;
147
+ cursor = parent;
148
+ }
149
+ throw new Error('Installed dependency is not found: ' + packageName + ' from ' + fromPackageRootAbs + '.');
150
+ };
151
+
152
+ /**
153
+ * @returns {Promise<ReadonlyArray<TeqFw_Di_Node_Registry_Package_Record>>}
154
+ */
155
+ this.build = async function () {
156
+ /** @type {Map<string, TeqFw_Di_Node_Registry_Package_Record>} */
157
+ const recordsByRoot = new Map();
158
+ /** @type {Set<string>} */
159
+ const visitingRoots = new Set();
160
+ /** @type {TeqFw_Di_Node_Registry_Package_Record[]} */
161
+ const records = [];
162
+ /** @type {string[]} */
163
+ const pathStack = [];
164
+
165
+ /**
166
+ * Visits a package after all of its dependencies.
167
+ *
168
+ * @param {string} rootAbs
169
+ * @returns {Promise<string>}
170
+ */
171
+ const visit = async function (rootAbs) {
172
+ let rootReal;
173
+ try {
174
+ rootReal = await fs.realpath(rootAbs);
175
+ } catch (error) {
176
+ throw new Error('Unable to resolve package root ' + rootAbs + ': ' + String(error) + '.');
177
+ }
178
+ if (recordsByRoot.has(rootReal)) return rootReal;
179
+ if (visitingRoots.has(rootReal)) {
180
+ const cycleStart = pathStack.indexOf(rootReal);
181
+ const cycle = [...pathStack.slice(cycleStart), rootReal].join(' -> ');
182
+ throw new Error('Cyclic package dependency detected: ' + cycle + '.');
183
+ }
184
+ visitingRoots.add(rootReal);
185
+ pathStack.push(rootReal);
186
+
187
+ const metadata = await readPackageMetadata(rootAbs);
188
+ const packageJson = /** @type {Readonly<Record<string, unknown>>} */ (freezeDeep(metadata.packageJson));
189
+ /** @type {string[]} */
190
+ const dependencies = [];
191
+
192
+ for (const dependencyName of metadata.dependencies) {
193
+ const dependencyRootAbs = await resolveDependencyPackageRoot(dependencyName, rootAbs);
194
+ dependencies.push(await visit(dependencyRootAbs));
195
+ }
196
+
197
+ pathStack.pop();
198
+ visitingRoots.delete(rootReal);
199
+ const record = Object.freeze({
200
+ name: metadata.name,
201
+ rootAbs,
202
+ rootReal,
203
+ packageJson,
204
+ dependencies: Object.freeze([...new Set(dependencies)]),
205
+ });
206
+ recordsByRoot.set(rootReal, record);
207
+ records.push(record);
208
+ return rootReal;
209
+ };
210
+
211
+ await visit(appRootAbs);
212
+ return Object.freeze(records);
213
+ };
214
+ }
215
+ }
package/src/Parser.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * @namespace TeqFw_Di_Parser
5
- * @description CDC parser that builds dependency identity DTOs.
5
+ * @description Dependency Specifier parser that builds dependency identity DTOs.
6
6
  */
7
7
 
8
8
  import TeqFw_Di_Enum_Composition from './Enum/Composition.mjs';
@@ -11,7 +11,7 @@ import TeqFw_Di_Enum_Platform from './Enum/Platform.mjs';
11
11
  import {Factory as TeqFw_Di_Dto_DepId_Factory} from './Dto/DepId.mjs';
12
12
 
13
13
  /**
14
- * Parser for CDC identifiers into frozen dependency identity DTO.
14
+ * Parser for Dependency Specifiers into frozen dependency identity DTOs.
15
15
  */
16
16
  export default class TeqFw_Di_Parser {
17
17
  /**
@@ -26,7 +26,7 @@ export default class TeqFw_Di_Parser {
26
26
  /**
27
27
  * Detects platform prefix and strips it from the source string.
28
28
  *
29
- * @param {string} source CDC source without validation.
29
+ * @param {string} source Dependency Specifier source without validation.
30
30
  * @returns {{platform: typeof TeqFw_Di_Enum_Platform[keyof typeof TeqFw_Di_Enum_Platform], source: string}}
31
31
  */
32
32
  const detectPlatform = function (source) {
@@ -49,7 +49,7 @@ export default class TeqFw_Di_Parser {
49
49
  /**
50
50
  * Parses lifecycle and wrapper suffix from the source string.
51
51
  *
52
- * @param {string} source CDC source without platform prefix.
52
+ * @param {string} source Dependency Specifier source without platform prefix.
53
53
  * @param {typeof TeqFw_Di_Enum_Platform[keyof typeof TeqFw_Di_Enum_Platform]} platform
54
54
  * @returns {{core: string, life: typeof TeqFw_Di_Enum_Life[keyof typeof TeqFw_Di_Enum_Life] | null, lifecycleDeclared: boolean, wrappers: string[]}}
55
55
  */
@@ -89,7 +89,7 @@ export default class TeqFw_Di_Parser {
89
89
  /**
90
90
  * Splits module and export names from canonical core string.
91
91
  *
92
- * @param {string} core CDC core without lifecycle suffix.
92
+ * @param {string} core Dependency Specifier core without lifecycle suffix.
93
93
  * @returns {{moduleName: string, exportName: string|null}}
94
94
  */
95
95
  const parseModuleExport = function (core) {
@@ -139,20 +139,20 @@ export default class TeqFw_Di_Parser {
139
139
  };
140
140
 
141
141
  /**
142
- * Parses one CDC identifier and returns normalized frozen dependency DTO.
142
+ * Parses one Dependency Specifier and returns a normalized frozen dependency DTO.
143
143
  *
144
- * @param {string} cdc CDC identifier string.
144
+ * @param {string} specifier Dependency Specifier string.
145
145
  * @returns {TeqFw_Di_DepId__DTO}
146
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.');
147
+ this.parse = function (specifier) {
148
+ if (logger) logger.log(`Parser.parse: input='${specifier}'.`);
149
+ if (typeof specifier !== 'string') throw new Error('Dependency Specifier must be a string.');
150
+ if (specifier.length === 0) throw new Error('Dependency Specifier must be non-empty.');
151
+ if (!/^[\x00-\x7F]+$/.test(specifier)) throw new Error('Dependency Specifier must be ASCII.');
152
152
 
153
153
  /** @type {string} */
154
- const origin = cdc;
155
- const detected = detectPlatform(cdc);
154
+ const origin = specifier;
155
+ const detected = detectPlatform(specifier);
156
156
  const platform = detected.platform;
157
157
  const source = detected.source;
158
158
  if (source.length === 0) throw new Error('moduleName must be non-empty.');
package/types.d.ts CHANGED
@@ -1,8 +1,13 @@
1
1
  declare global {
2
+ type TeqFw_Di_Node_Registry_Namespace = import("./src/Node/Registry/Namespace.mjs").default;
2
3
  type TeqFw_Di_Config_NamespaceRegistry = import("./src/Config/NamespaceRegistry.mjs").default;
4
+ type TeqFw_Di_Node_Registry_Namespace_Entry = Readonly<{prefix: string, dirAbs: string, ext: string}>;
5
+ type TeqFw_Di_Node_Registry_Package = import("./src/Node/Registry/Package.mjs").default;
6
+ type TeqFw_Di_Node_Registry_Package_Record = Readonly<{name: string, rootAbs: string, rootReal: string, packageJson: Readonly<Record<string, unknown>>, dependencies: ReadonlyArray<string>}>;
3
7
  type TeqFw_Di_Container = import("./src/Container.mjs").default;
4
8
  type TeqFw_Di_Container_Instantiate = import("./src/Container/Instantiate.mjs").default;
5
9
  type TeqFw_Di_Container_Lifecycle = import("./src/Container/Lifecycle.mjs").default;
10
+ type TeqFw_Di_Container_Postprocess_Context = Readonly<{depId: TeqFw_Di_DepId__DTO}>;
6
11
  type TeqFw_Di_Container_GraphResolver = import("./src/Container/GraphResolver.mjs").default;
7
12
  type TeqFw_Di_Container_Executor = import("./src/Container/Executor.mjs").default;
8
13
  type TeqFw_Di_Parser = import("./src/Parser.mjs").default;
package/ai/concepts.md DELETED
@@ -1,25 +0,0 @@
1
- # concepts.md
2
-
3
- Version: 20260606
4
-
5
- ## Late Binding
6
-
7
- Dependencies are resolved at runtime rather than through direct static imports between application modules. This keeps modules independent of concrete implementations and moves dependency binding into the container.
8
-
9
- ## Runtime Linker
10
-
11
- The container acts as a runtime linker for ES modules. It interprets CDC identifiers, resolves modules, selects exports, and produces linked values for callers.
12
-
13
- ## Dependency Contracts
14
-
15
- Dependencies are declared through CDC strings and module-level `__deps__` descriptors. Together they form the contract between module code and runtime composition.
16
-
17
- The canonical `__deps__` form is hierarchical and keyed by export name.
18
-
19
- ## Namespace Mapping
20
-
21
- Logical module identifiers are translated into module-specifier bases through namespace roots. This keeps dependency addressing independent from concrete filesystem paths or URL locations.
22
-
23
- ## Immutable Linked Values
24
-
25
- Values returned by the container are frozen after linking. Consumers should treat them as stable resolved values rather than mutable construction targets.