@rhombus-std/di.core 0.0.0-alpha.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.
- package/LICENSE +21 -0
- package/README.md +367 -0
- package/dist/index.d.ts +775 -0
- package/dist/index.js +299 -0
- package/package.json +41 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
class DiError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = new.target.name;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class OpenTokenRegistrationError extends DiError {
|
|
10
|
+
token;
|
|
11
|
+
method;
|
|
12
|
+
constructor(token, method) {
|
|
13
|
+
super(method === "add" ? `Cannot register open template "${token}": every type argument of ` + `an open service token must be a hole ($N). Make every argument ` + `a hole, or close the token fully.` : `Cannot register open template "${token}" with ${method}(): open ` + `registrations are class-only. Register a class with ` + `add("${token}", MyClass), or close the token first.`);
|
|
14
|
+
this.token = token;
|
|
15
|
+
this.method = method;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/guards.ts
|
|
20
|
+
function isFactoryRef(slot) {
|
|
21
|
+
return typeof slot === "object" && slot !== null && typeof slot.type === "string";
|
|
22
|
+
}
|
|
23
|
+
function isUnionSlot(slot) {
|
|
24
|
+
return typeof slot === "object" && slot !== null && Array.isArray(slot.union);
|
|
25
|
+
}
|
|
26
|
+
function isLiteralRef(slot) {
|
|
27
|
+
return typeof slot === "object" && slot !== null && "value" in slot;
|
|
28
|
+
}
|
|
29
|
+
function isTypeArgRef(slot) {
|
|
30
|
+
return typeof slot === "object" && slot !== null && typeof slot.typeArg === "number";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/tokens.ts
|
|
34
|
+
var HOLE_PATTERN = /^\$[1-9][0-9]*$/;
|
|
35
|
+
function closeToken(base, ...args) {
|
|
36
|
+
if (!args.length) {
|
|
37
|
+
return base;
|
|
38
|
+
}
|
|
39
|
+
return `${base}<${args.join(",")}>`;
|
|
40
|
+
}
|
|
41
|
+
function parseToken(token) {
|
|
42
|
+
const open = token.indexOf("<");
|
|
43
|
+
if (open <= 0) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const base = token.slice(0, open);
|
|
47
|
+
if (base.includes(">") || base.includes('"')) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const args = [];
|
|
51
|
+
let depth = 1;
|
|
52
|
+
let inQuote = false;
|
|
53
|
+
let argStart = open + 1;
|
|
54
|
+
for (let i = open + 1;i < token.length; i++) {
|
|
55
|
+
const ch = token[i];
|
|
56
|
+
if (inQuote) {
|
|
57
|
+
if (ch === "\\") {
|
|
58
|
+
i++;
|
|
59
|
+
} else if (ch === '"') {
|
|
60
|
+
inQuote = false;
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (ch === '"') {
|
|
65
|
+
inQuote = true;
|
|
66
|
+
} else if (ch === "<") {
|
|
67
|
+
depth++;
|
|
68
|
+
} else if (ch === ">") {
|
|
69
|
+
depth--;
|
|
70
|
+
if (!depth) {
|
|
71
|
+
if (i !== token.length - 1) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const last = token.slice(argStart, i);
|
|
75
|
+
if (!last) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
args.push(last);
|
|
79
|
+
return { base, args };
|
|
80
|
+
}
|
|
81
|
+
} else if (ch === "," && depth === 1) {
|
|
82
|
+
const arg = token.slice(argStart, i);
|
|
83
|
+
if (!arg) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
args.push(arg);
|
|
87
|
+
argStart = i + 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
function isOpenToken(token) {
|
|
93
|
+
if (HOLE_PATTERN.test(token)) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
const parsed = parseToken(token);
|
|
97
|
+
if (!parsed) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return parsed.args.some(isOpenToken);
|
|
101
|
+
}
|
|
102
|
+
function substituteToken(template, args) {
|
|
103
|
+
if (HOLE_PATTERN.test(template)) {
|
|
104
|
+
return holeArg(Number(template.slice(1)), args);
|
|
105
|
+
}
|
|
106
|
+
const parsed = parseToken(template);
|
|
107
|
+
if (!parsed) {
|
|
108
|
+
return template;
|
|
109
|
+
}
|
|
110
|
+
return closeToken(parsed.base, ...parsed.args.map((a) => substituteToken(a, args)));
|
|
111
|
+
}
|
|
112
|
+
function substituteSignatures(signatures, args) {
|
|
113
|
+
return signatures.map((sig) => sig.map((slot) => substituteSlot(slot, args)));
|
|
114
|
+
}
|
|
115
|
+
function substituteSlot(slot, args) {
|
|
116
|
+
if (typeof slot === "string") {
|
|
117
|
+
return substituteToken(slot, args);
|
|
118
|
+
}
|
|
119
|
+
if (isTypeArgRef(slot)) {
|
|
120
|
+
return { value: holeArg(slot.typeArg, args) };
|
|
121
|
+
}
|
|
122
|
+
if (isFactoryRef(slot)) {
|
|
123
|
+
const type = substituteToken(slot.type, args);
|
|
124
|
+
if (slot.params) {
|
|
125
|
+
return { type, params: slot.params.map((p) => substituteToken(p, args)) };
|
|
126
|
+
}
|
|
127
|
+
return { type };
|
|
128
|
+
}
|
|
129
|
+
if (isUnionSlot(slot)) {
|
|
130
|
+
return { union: slot.union.map((m) => substituteSlot(m, args)) };
|
|
131
|
+
}
|
|
132
|
+
return slot;
|
|
133
|
+
}
|
|
134
|
+
function holeArg(n, args) {
|
|
135
|
+
const arg = args[n - 1];
|
|
136
|
+
if (arg === undefined) {
|
|
137
|
+
throw new RangeError(`Hole $${n} has no matching type argument (${args.length} supplied).`);
|
|
138
|
+
}
|
|
139
|
+
return arg;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/service-manifest.ts
|
|
143
|
+
function appendTo(map, key, value) {
|
|
144
|
+
const existing = map.get(key);
|
|
145
|
+
if (existing === undefined) {
|
|
146
|
+
map.set(key, [value]);
|
|
147
|
+
} else {
|
|
148
|
+
existing.push(value);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
class ServiceManifestClass {
|
|
153
|
+
#registrations = new Map;
|
|
154
|
+
#openRegistrations = new Map;
|
|
155
|
+
constructor() {}
|
|
156
|
+
#append(token, registration) {
|
|
157
|
+
appendTo(this.#registrations, token, registration);
|
|
158
|
+
}
|
|
159
|
+
#appendOpen(base, registration) {
|
|
160
|
+
appendTo(this.#openRegistrations, base, registration);
|
|
161
|
+
}
|
|
162
|
+
#scopedContinuation(applyScope) {
|
|
163
|
+
return {
|
|
164
|
+
as(scope) {
|
|
165
|
+
if (scope === undefined) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
applyScope(scope);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
#appendScoped(token, base) {
|
|
173
|
+
this.#append(token, base);
|
|
174
|
+
const list = this.#registrations.get(token);
|
|
175
|
+
const index = list.length - 1;
|
|
176
|
+
return this.#scopedContinuation((scope) => {
|
|
177
|
+
list[index] = { ...base, scope };
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
#appendOpenScoped(token, ctor, signatures) {
|
|
181
|
+
const parsed = parseToken(token);
|
|
182
|
+
if (parsed === undefined || !parsed.args.every((arg) => HOLE_PATTERN.test(arg))) {
|
|
183
|
+
throw new OpenTokenRegistrationError(token, "add");
|
|
184
|
+
}
|
|
185
|
+
const base = {
|
|
186
|
+
template: token,
|
|
187
|
+
base: parsed.base,
|
|
188
|
+
pattern: parsed.args,
|
|
189
|
+
ctor,
|
|
190
|
+
scope: undefined,
|
|
191
|
+
signatures
|
|
192
|
+
};
|
|
193
|
+
this.#appendOpen(parsed.base, base);
|
|
194
|
+
const list = this.#openRegistrations.get(parsed.base);
|
|
195
|
+
const index = list.length - 1;
|
|
196
|
+
return this.#scopedContinuation((scope) => {
|
|
197
|
+
list[index] = { ...base, scope };
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
add(...args) {
|
|
201
|
+
if (args.length === 1 || typeof args[0] !== "string") {
|
|
202
|
+
throw new TypeError("add<I>(ctor) / add<I>(factory) require the @rhombus-std/di.transformer plugin. " + 'Without it, register with an explicit token: add("my:token", MyClass) ' + 'or addFactory("my:token", (scope) => ...).');
|
|
203
|
+
}
|
|
204
|
+
const [token, ctor, signatures] = args;
|
|
205
|
+
if (isOpenToken(token)) {
|
|
206
|
+
return this.#appendOpenScoped(token, ctor, signatures);
|
|
207
|
+
}
|
|
208
|
+
const construct = ctor;
|
|
209
|
+
return this.#appendScoped(token, {
|
|
210
|
+
produce: (...a) => new construct(...a),
|
|
211
|
+
scope: undefined,
|
|
212
|
+
signatures,
|
|
213
|
+
name: construct.name,
|
|
214
|
+
arity: construct.length
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
addFactory(...args) {
|
|
218
|
+
if (args.length === 1 || typeof args[0] !== "string") {
|
|
219
|
+
throw new TypeError("addFactory<I>(fn) requires the @rhombus-std/di.transformer plugin. Without it, " + 'register with an explicit token: addFactory("my:token", (scope) => ...).');
|
|
220
|
+
}
|
|
221
|
+
const [token, factory, signatures] = args;
|
|
222
|
+
if (isOpenToken(token)) {
|
|
223
|
+
throw new OpenTokenRegistrationError(token, "addFactory");
|
|
224
|
+
}
|
|
225
|
+
return this.#appendScoped(token, {
|
|
226
|
+
produce: factory,
|
|
227
|
+
scope: undefined,
|
|
228
|
+
signatures,
|
|
229
|
+
name: factory.name,
|
|
230
|
+
arity: 0
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
addValue(...args) {
|
|
234
|
+
if (args.length === 1 || typeof args[0] !== "string") {
|
|
235
|
+
throw new TypeError("addValue<I>(value) requires the @rhombus-std/di.transformer plugin. Without it, " + 'register with an explicit token: addValue("my:token", value).');
|
|
236
|
+
}
|
|
237
|
+
const [token, value] = args;
|
|
238
|
+
if (isOpenToken(token)) {
|
|
239
|
+
throw new OpenTokenRegistrationError(token, "addValue");
|
|
240
|
+
}
|
|
241
|
+
this.#append(token, {
|
|
242
|
+
produce: () => value,
|
|
243
|
+
scope: undefined,
|
|
244
|
+
name: "",
|
|
245
|
+
arity: 0
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
seal() {
|
|
249
|
+
const registrations = new Map;
|
|
250
|
+
for (const [token, list] of this.#registrations) {
|
|
251
|
+
registrations.set(token, Object.freeze([...list]));
|
|
252
|
+
}
|
|
253
|
+
Object.freeze(registrations);
|
|
254
|
+
const openRegistrations = new Map;
|
|
255
|
+
for (const [base, list] of this.#openRegistrations) {
|
|
256
|
+
openRegistrations.set(base, Object.freeze([...list]));
|
|
257
|
+
}
|
|
258
|
+
Object.freeze(openRegistrations);
|
|
259
|
+
return { registrations, openRegistrations };
|
|
260
|
+
}
|
|
261
|
+
build() {
|
|
262
|
+
throw new TypeError("ServiceManifest.build() requires the @rhombus-std/di runtime. Import " + "@rhombus-std/di (which constructs the resolution engine) before " + "calling build() — di.core ships only the registration collection.");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// src/slots.ts
|
|
266
|
+
function union(...slots) {
|
|
267
|
+
return { union: slots };
|
|
268
|
+
}
|
|
269
|
+
function typeArg(n) {
|
|
270
|
+
return { typeArg: n };
|
|
271
|
+
}
|
|
272
|
+
// src/provider-token.ts
|
|
273
|
+
var RESOLVER_TOKEN = "@rhombus-std/di.core:Resolver";
|
|
274
|
+
var RESOLVE_SCOPE_TOKEN = "@rhombus-std/di.core:ResolveScope";
|
|
275
|
+
var PROVIDER_TOKENS = new Set([
|
|
276
|
+
RESOLVER_TOKEN,
|
|
277
|
+
RESOLVE_SCOPE_TOKEN
|
|
278
|
+
]);
|
|
279
|
+
function isProviderToken(token) {
|
|
280
|
+
return PROVIDER_TOKENS.has(token);
|
|
281
|
+
}
|
|
282
|
+
export {
|
|
283
|
+
union,
|
|
284
|
+
typeArg,
|
|
285
|
+
substituteToken,
|
|
286
|
+
substituteSignatures,
|
|
287
|
+
parseToken,
|
|
288
|
+
isUnionSlot,
|
|
289
|
+
isTypeArgRef,
|
|
290
|
+
isProviderToken,
|
|
291
|
+
isOpenToken,
|
|
292
|
+
isLiteralRef,
|
|
293
|
+
isFactoryRef,
|
|
294
|
+
closeToken,
|
|
295
|
+
ServiceManifestClass,
|
|
296
|
+
RESOLVER_TOKEN,
|
|
297
|
+
OpenTokenRegistrationError,
|
|
298
|
+
DiError
|
|
299
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rhombus-std/di.core",
|
|
3
|
+
"version": "0.0.0-alpha.0",
|
|
4
|
+
"description": "The ioc abstractions substrate: the dependency-signature data format, the slot/token surface + helpers, and the concrete registration builder (ServiceManifest) a library author depends on to author registrations without the di resolution engine. Zero dependencies.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dependency-injection",
|
|
7
|
+
"di",
|
|
8
|
+
"ioc",
|
|
9
|
+
"typescript"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/fnioc/std.git",
|
|
15
|
+
"directory": "libraries/di.core"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"provenance": true
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@rhombus-toolkit/func": "3.5.3"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "bun run build.ts",
|
|
39
|
+
"lint": "eslint ."
|
|
40
|
+
}
|
|
41
|
+
}
|