@cldmv/slothlet 3.2.1 → 3.3.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/README.md +24 -10
- package/dist/lib/builders/api_builder.mjs +233 -3
- package/dist/lib/handlers/api-manager.mjs +23 -0
- package/dist/lib/handlers/context-async.mjs +4 -0
- package/dist/lib/handlers/context-live.mjs +5 -0
- package/dist/lib/handlers/hook-manager.mjs +5 -111
- package/dist/lib/handlers/permission-manager.mjs +408 -0
- package/dist/lib/handlers/unified-wrapper.mjs +90 -22
- package/dist/lib/handlers/version-manager.mjs +77 -4
- package/dist/lib/helpers/config.mjs +91 -7
- package/dist/lib/helpers/pattern-matcher.mjs +141 -0
- package/dist/lib/i18n/languages/de-de.json +21 -1
- package/dist/lib/i18n/languages/en-gb.json +21 -1
- package/dist/lib/i18n/languages/en-us.json +21 -1
- package/dist/lib/i18n/languages/es-mx.json +21 -1
- package/dist/lib/i18n/languages/fr-fr.json +21 -1
- package/dist/lib/i18n/languages/hi-in.json +21 -1
- package/dist/lib/i18n/languages/ja-jp.json +21 -1
- package/dist/lib/i18n/languages/ko-kr.json +21 -1
- package/dist/lib/i18n/languages/pt-br.json +21 -1
- package/dist/lib/i18n/languages/ru-ru.json +21 -1
- package/dist/lib/i18n/languages/zh-cn.json +21 -1
- package/dist/slothlet.mjs +11 -2
- package/package.json +4 -1
- package/types/dist/lib/builders/api_builder.d.mts.map +1 -1
- package/types/dist/lib/handlers/api-manager.d.mts.map +1 -1
- package/types/dist/lib/handlers/context-async.d.mts.map +1 -1
- package/types/dist/lib/handlers/context-live.d.mts.map +1 -1
- package/types/dist/lib/handlers/hook-manager.d.mts.map +1 -1
- package/types/dist/lib/handlers/permission-manager.d.mts +151 -0
- package/types/dist/lib/handlers/permission-manager.d.mts.map +1 -0
- package/types/dist/lib/handlers/unified-wrapper.d.mts.map +1 -1
- package/types/dist/lib/handlers/version-manager.d.mts.map +1 -1
- package/types/dist/lib/helpers/config.d.mts +16 -0
- package/types/dist/lib/helpers/config.d.mts.map +1 -1
- package/types/dist/lib/helpers/pattern-matcher.d.mts +44 -0
- package/types/dist/lib/helpers/pattern-matcher.d.mts.map +1 -0
- package/types/dist/slothlet.d.mts.map +1 -1
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
import { ComponentBase } from "@cldmv/slothlet/factories/component-base";
|
|
22
|
+
import { compilePattern } from "@cldmv/slothlet/helpers/pattern-matcher";
|
|
23
|
+
import { translate } from "@cldmv/slothlet/i18n";
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
let ruleIdCounter = 0;
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
export class PermissionManager extends ComponentBase {
|
|
30
|
+
|
|
31
|
+
static slothletProperty = "permissionManager";
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
#rules = new Map();
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
#defaultPolicy = "allow";
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
#enabled = false;
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
#audit = "default";
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
#resolvedCache = new Map();
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
#compiledCache = new Map();
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
constructor(slothlet) {
|
|
53
|
+
super(slothlet);
|
|
54
|
+
|
|
55
|
+
const permConfig = slothlet.config?.permissions;
|
|
56
|
+
if (permConfig) {
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
this.#defaultPolicy = permConfig.defaultPolicy || "allow";
|
|
60
|
+
this.#enabled = permConfig.enabled !== false;
|
|
61
|
+
this.#audit = permConfig.audit || "default";
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if (Array.isArray(permConfig.rules)) {
|
|
65
|
+
for (const rule of permConfig.rules) {
|
|
66
|
+
this.addRule(rule, null);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
this.addRule({ caller: "**", target: "slothlet.permissions.control.**", effect: "deny" }, "__builtin__");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
addRule(rule, ownerModuleID = null, ruleId = null) {
|
|
77
|
+
this.#validateRule(rule);
|
|
78
|
+
|
|
79
|
+
const id = ruleId || `perm-${++ruleIdCounter}`;
|
|
80
|
+
const entry = {
|
|
81
|
+
id,
|
|
82
|
+
caller: rule.caller,
|
|
83
|
+
target: rule.target,
|
|
84
|
+
effect: rule.effect,
|
|
85
|
+
ownerModuleID: ownerModuleID,
|
|
86
|
+
registeredAt: Date.now()
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
this.#rules.set(id, entry);
|
|
90
|
+
this.#clearCache();
|
|
91
|
+
|
|
92
|
+
this.debug("permissions", {
|
|
93
|
+
key: "DEBUG_PERMISSION_RULE_ADDED",
|
|
94
|
+
ruleId: id,
|
|
95
|
+
caller: rule.caller,
|
|
96
|
+
target: rule.target,
|
|
97
|
+
effect: rule.effect,
|
|
98
|
+
ownerModuleID
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return id;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
removeRule(ruleId, callerModuleID = null) {
|
|
106
|
+
const entry = this.#rules.get(ruleId);
|
|
107
|
+
if (!entry) return false;
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if (callerModuleID && entry.ownerModuleID && callerModuleID === entry.ownerModuleID) {
|
|
115
|
+
throw new this.SlothletError("PERMISSION_SELF_MODIFY", {
|
|
116
|
+
ruleId,
|
|
117
|
+
moduleID: callerModuleID
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.#rules.delete(ruleId);
|
|
122
|
+
this.#clearCache();
|
|
123
|
+
|
|
124
|
+
this.debug("permissions", {
|
|
125
|
+
key: "DEBUG_PERMISSION_RULE_REMOVED",
|
|
126
|
+
ruleId,
|
|
127
|
+
caller: entry.caller,
|
|
128
|
+
target: entry.target,
|
|
129
|
+
effect: entry.effect
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
checkAccess(callerPath, targetPath, callerFilePath = null, targetFilePath = null) {
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
const isControlTarget = targetPath?.startsWith("slothlet.permissions.control.");
|
|
144
|
+
if (!this.#enabled && !isControlTarget) return true;
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
if (callerFilePath && targetFilePath && callerFilePath === targetFilePath) {
|
|
148
|
+
this.#emitAuditEvent("permission:self-bypass", {
|
|
149
|
+
caller: callerPath,
|
|
150
|
+
target: targetPath,
|
|
151
|
+
filePath: callerFilePath
|
|
152
|
+
});
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
const cacheKey = `${callerPath}::${targetPath}`;
|
|
158
|
+
if (this.#resolvedCache.has(cacheKey)) {
|
|
159
|
+
const cached = this.#resolvedCache.get(cacheKey);
|
|
160
|
+
this.#emitAuditEvent(cached.event, cached.payload);
|
|
161
|
+
return cached.allowed;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
const entry = this.#evaluate(callerPath, targetPath);
|
|
166
|
+
this.#resolvedCache.set(cacheKey, entry);
|
|
167
|
+
this.#emitAuditEvent(entry.event, entry.payload);
|
|
168
|
+
return entry.allowed;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
getRulesForPath(targetPath) {
|
|
173
|
+
const matching = [];
|
|
174
|
+
for (const entry of this.#rules.values()) {
|
|
175
|
+
const targetMatcher = this.#getCompiledPattern(entry.target);
|
|
176
|
+
if (targetMatcher(targetPath)) {
|
|
177
|
+
matching.push(this.#serializeRule(entry));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return matching;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
getRulesByModule(moduleID) {
|
|
185
|
+
const matching = [];
|
|
186
|
+
for (const entry of this.#rules.values()) {
|
|
187
|
+
if (entry.ownerModuleID === moduleID) {
|
|
188
|
+
matching.push(this.#serializeRule(entry));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return matching;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
getRulesForCaller(callerPath) {
|
|
196
|
+
const matching = [];
|
|
197
|
+
for (const entry of this.#rules.values()) {
|
|
198
|
+
const callerMatcher = this.#getCompiledPattern(entry.caller);
|
|
199
|
+
if (callerMatcher(callerPath)) {
|
|
200
|
+
matching.push(this.#serializeRule(entry));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return matching;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
enable() {
|
|
208
|
+
this.#enabled = true;
|
|
209
|
+
this.#clearCache();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
disable() {
|
|
214
|
+
this.#enabled = false;
|
|
215
|
+
this.#clearCache();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
isEnabled() {
|
|
220
|
+
return this.#enabled;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
exportRules() {
|
|
228
|
+
const rules = [];
|
|
229
|
+
for (const entry of this.#rules.values()) {
|
|
230
|
+
rules.push({
|
|
231
|
+
rule: { caller: entry.caller, target: entry.target, effect: entry.effect },
|
|
232
|
+
ownerModuleID: entry.ownerModuleID
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return rules;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
importRules(registrations) {
|
|
240
|
+
if (!Array.isArray(registrations)) return;
|
|
241
|
+
for (const reg of registrations) {
|
|
242
|
+
this.addRule(reg.rule, reg.ownerModuleID);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
async shutdown() {
|
|
249
|
+
this.#rules.clear();
|
|
250
|
+
this.#resolvedCache.clear();
|
|
251
|
+
this.#compiledCache.clear();
|
|
252
|
+
this.#enabled = false;
|
|
253
|
+
this.#defaultPolicy = "allow";
|
|
254
|
+
this.#audit = "default";
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
#validateRule(rule) {
|
|
261
|
+
if (!rule || typeof rule !== "object") {
|
|
262
|
+
throw new this.SlothletError("INVALID_PERMISSION_RULE", {
|
|
263
|
+
reason: translate("PERM_RULE_NOT_OBJECT"),
|
|
264
|
+
received: typeof rule
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
if (typeof rule.caller !== "string" || !rule.caller) {
|
|
268
|
+
throw new this.SlothletError("INVALID_PERMISSION_RULE", {
|
|
269
|
+
reason: translate("PERM_RULE_CALLER_REQUIRED"),
|
|
270
|
+
received: typeof rule.caller
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
if (typeof rule.target !== "string" || !rule.target) {
|
|
274
|
+
throw new this.SlothletError("INVALID_PERMISSION_RULE", {
|
|
275
|
+
reason: translate("PERM_RULE_TARGET_REQUIRED"),
|
|
276
|
+
received: typeof rule.target
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
if (rule.effect !== "allow" && rule.effect !== "deny") {
|
|
280
|
+
throw new this.SlothletError("INVALID_PERMISSION_RULE", {
|
|
281
|
+
reason: translate("PERM_RULE_EFFECT_INVALID"),
|
|
282
|
+
received: rule.effect
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
#evaluate(callerPath, targetPath) {
|
|
289
|
+
|
|
290
|
+
const matches = [];
|
|
291
|
+
|
|
292
|
+
for (const entry of this.#rules.values()) {
|
|
293
|
+
const callerMatcher = this.#getCompiledPattern(entry.caller);
|
|
294
|
+
const targetMatcher = this.#getCompiledPattern(entry.target);
|
|
295
|
+
|
|
296
|
+
if (callerMatcher(callerPath) && targetMatcher(targetPath)) {
|
|
297
|
+
matches.push(entry);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
if (matches.length === 0) {
|
|
303
|
+
const allowed = this.#defaultPolicy === "allow";
|
|
304
|
+
return {
|
|
305
|
+
allowed,
|
|
306
|
+
event: "permission:default",
|
|
307
|
+
payload: { caller: callerPath, target: targetPath, policy: this.#defaultPolicy }
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
matches.sort((a, b) => {
|
|
313
|
+
const specA = this.#computeSpecificity(a, callerPath, targetPath);
|
|
314
|
+
const specB = this.#computeSpecificity(b, callerPath, targetPath);
|
|
315
|
+
if (specA !== specB) return specB - specA;
|
|
316
|
+
return a.registeredAt - b.registeredAt;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
const highestSpec = this.#computeSpecificity(matches[0], callerPath, targetPath);
|
|
321
|
+
const topTier = matches.filter((m) => this.#computeSpecificity(m, callerPath, targetPath) === highestSpec);
|
|
322
|
+
|
|
323
|
+
const winner = topTier[topTier.length - 1];
|
|
324
|
+
const allowed = winner.effect === "allow";
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
allowed,
|
|
328
|
+
event: allowed ? "permission:allowed" : "permission:denied",
|
|
329
|
+
payload: { caller: callerPath, target: targetPath, rule: this.#serializeRule(winner) }
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
#computeSpecificity(entry, callerPath, targetPath) {
|
|
335
|
+
return this.#patternSpecificity(entry.caller, callerPath) + this.#patternSpecificity(entry.target, targetPath);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
#patternSpecificity(pattern, _path) {
|
|
340
|
+
|
|
341
|
+
if (!pattern.includes("*") && !pattern.includes("?") && !pattern.includes("{")) {
|
|
342
|
+
return 3;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (pattern.includes("**")) {
|
|
346
|
+
return 1;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return 2;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
#getCompiledPattern(pattern) {
|
|
354
|
+
let matcher = this.#compiledCache.get(pattern);
|
|
355
|
+
if (!matcher) {
|
|
356
|
+
matcher = compilePattern(pattern);
|
|
357
|
+
this.#compiledCache.set(pattern, matcher);
|
|
358
|
+
}
|
|
359
|
+
return matcher;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
#clearCache() {
|
|
364
|
+
this.#resolvedCache.clear();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
#emitAuditEvent(event, payload) {
|
|
369
|
+
|
|
370
|
+
this.debug("permissions", {
|
|
371
|
+
key:
|
|
372
|
+
event === "permission:denied"
|
|
373
|
+
? "DEBUG_PERMISSION_DENIED"
|
|
374
|
+
: event === "permission:allowed"
|
|
375
|
+
? "DEBUG_PERMISSION_ALLOWED"
|
|
376
|
+
: event === "permission:self-bypass"
|
|
377
|
+
? "DEBUG_PERMISSION_SELF_BYPASS"
|
|
378
|
+
: "DEBUG_PERMISSION_DEFAULT",
|
|
379
|
+
...payload
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
const alwaysEmit = event === "permission:denied" || event === "permission:self-bypass";
|
|
384
|
+
if (!alwaysEmit && this.#audit !== "verbose") return;
|
|
385
|
+
|
|
386
|
+
const lifecycle = this.slothlet.handlers?.lifecycle;
|
|
387
|
+
if (lifecycle) {
|
|
388
|
+
lifecycle.emit(event, { ...payload, timestamp: Date.now() });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
#serializeRule(entry) {
|
|
394
|
+
return {
|
|
395
|
+
id: entry.id,
|
|
396
|
+
caller: entry.caller,
|
|
397
|
+
target: entry.target,
|
|
398
|
+
effect: entry.effect,
|
|
399
|
+
ownerModuleID: entry.ownerModuleID,
|
|
400
|
+
registeredAt: entry.registeredAt
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
debug(category, data) {
|
|
406
|
+
this.slothlet.debug(category, data);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
@@ -1654,6 +1654,13 @@ export class UnifiedWrapper extends ComponentBase {
|
|
|
1654
1654
|
},
|
|
1655
1655
|
|
|
1656
1656
|
async apply(___target, ___thisArg, args) {
|
|
1657
|
+
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
|
|
1661
|
+
|
|
1662
|
+
const ___capturedCallerWrapper = wrapper.slothlet.contextManager?.tryGetContext?.()?.currentWrapper ?? null;
|
|
1663
|
+
|
|
1657
1664
|
wrapper.slothlet.debug("wrapper", {
|
|
1658
1665
|
key: "DEBUG_MODE_WAITING_APPLY_ENTRY",
|
|
1659
1666
|
apiPath: wrapper.____slothletInternal.apiPath,
|
|
@@ -1834,6 +1841,27 @@ export class UnifiedWrapper extends ComponentBase {
|
|
|
1834
1841
|
|
|
1835
1842
|
|
|
1836
1843
|
|
|
1844
|
+
|
|
1845
|
+
|
|
1846
|
+
|
|
1847
|
+
|
|
1848
|
+
|
|
1849
|
+
|
|
1850
|
+
if (___capturedCallerWrapper && wrapper.slothlet.contextManager) {
|
|
1851
|
+
const ___instanceStore = wrapper.slothlet.contextManager.instances?.get?.(wrapper.instanceID);
|
|
1852
|
+
|
|
1853
|
+
|
|
1854
|
+
|
|
1855
|
+
if (___instanceStore && !___instanceStore.currentWrapper) {
|
|
1856
|
+
return wrapper.slothlet.contextManager.runInContext(
|
|
1857
|
+
wrapper.instanceID,
|
|
1858
|
+
() => Reflect.apply(current, lastObject, args),
|
|
1859
|
+
null,
|
|
1860
|
+
[],
|
|
1861
|
+
___capturedCallerWrapper
|
|
1862
|
+
);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1837
1865
|
return Reflect.apply(current, lastObject, args);
|
|
1838
1866
|
}
|
|
1839
1867
|
|
|
@@ -2540,6 +2568,35 @@ export class UnifiedWrapper extends ComponentBase {
|
|
|
2540
2568
|
}
|
|
2541
2569
|
|
|
2542
2570
|
|
|
2571
|
+
|
|
2572
|
+
|
|
2573
|
+
const permissionManager = wrapper.slothlet.handlers?.permissionManager;
|
|
2574
|
+
if (permissionManager && permissionManager.isEnabled()) {
|
|
2575
|
+
const ctx = wrapper.slothlet.contextManager?.tryGetContext?.();
|
|
2576
|
+
|
|
2577
|
+
|
|
2578
|
+
const callerWrapper = ctx?.currentWrapper;
|
|
2579
|
+
|
|
2580
|
+
|
|
2581
|
+
if (callerWrapper) {
|
|
2582
|
+
|
|
2583
|
+
|
|
2584
|
+
const callerPath = callerWrapper.____slothletInternal?.apiPath ?? "";
|
|
2585
|
+
const callerFilePath = callerWrapper.____slothletInternal?.filePath ?? null;
|
|
2586
|
+
const targetPath = wrapper.____slothletInternal.apiPath;
|
|
2587
|
+
const targetFilePath = wrapper.____slothletInternal.filePath ?? null;
|
|
2588
|
+
|
|
2589
|
+
|
|
2590
|
+
if (!permissionManager.checkAccess(callerPath, targetPath, callerFilePath, targetFilePath)) {
|
|
2591
|
+
throw new wrapper.SlothletError("PERMISSION_DENIED", {
|
|
2592
|
+
caller: callerPath,
|
|
2593
|
+
target: targetPath
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
|
|
2543
2600
|
const hookManager = wrapper.slothlet.handlers?.hookManager;
|
|
2544
2601
|
|
|
2545
2602
|
const hasHooks = hookManager && hookManager.enabled && !wrapper.____slothletInternal.apiPath.startsWith("slothlet.hook");
|
|
@@ -2584,33 +2641,43 @@ export class UnifiedWrapper extends ComponentBase {
|
|
|
2584
2641
|
const checkMaterialized = () => {
|
|
2585
2642
|
if (wrapper.____slothletInternal.state.materialized) {
|
|
2586
2643
|
const impl = wrapper.____slothletInternal.impl;
|
|
2587
|
-
|
|
2588
|
-
if (
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
if (
|
|
2595
|
-
|
|
2644
|
+
try {
|
|
2645
|
+
if (typeof impl === "function") {
|
|
2646
|
+
if (wrapper.slothlet.contextManager) {
|
|
2647
|
+
resolve(wrapper.slothlet.contextManager.runInContext(wrapper.instanceID, impl, thisArg, args, wrapper));
|
|
2648
|
+
} else {
|
|
2649
|
+
resolve(impl.apply(thisArg, args));
|
|
2650
|
+
}
|
|
2651
|
+
} else if (impl && typeof impl === "object" && typeof impl.default === "function") {
|
|
2652
|
+
if (wrapper.contextManager) {
|
|
2653
|
+
resolve(wrapper.contextManager.runInContext(wrapper.instanceID, impl.default, impl, args, wrapper));
|
|
2654
|
+
} else {
|
|
2655
|
+
resolve(impl.default.apply(impl, args));
|
|
2656
|
+
}
|
|
2596
2657
|
} else {
|
|
2597
|
-
|
|
2658
|
+
reject(
|
|
2659
|
+
new wrapper.slothlet.SlothletError(
|
|
2660
|
+
"INVALID_CONFIG_NOT_A_FUNCTION",
|
|
2661
|
+
{
|
|
2662
|
+
apiPath: wrapper.____slothletInternal.apiPath,
|
|
2663
|
+
actualType: typeof impl
|
|
2664
|
+
},
|
|
2665
|
+
null,
|
|
2666
|
+
{ validationError: true }
|
|
2667
|
+
)
|
|
2668
|
+
);
|
|
2598
2669
|
}
|
|
2599
|
-
}
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
apiPath: wrapper.____slothletInternal.apiPath,
|
|
2605
|
-
actualType: typeof impl
|
|
2606
|
-
},
|
|
2607
|
-
null,
|
|
2608
|
-
{ validationError: true }
|
|
2609
|
-
)
|
|
2610
|
-
);
|
|
2670
|
+
} catch (err) {
|
|
2671
|
+
|
|
2672
|
+
|
|
2673
|
+
|
|
2674
|
+
reject(err);
|
|
2611
2675
|
}
|
|
2612
2676
|
return;
|
|
2613
2677
|
}
|
|
2678
|
+
|
|
2679
|
+
|
|
2680
|
+
|
|
2614
2681
|
if (!wrapper.____slothletInternal.state.inFlight) {
|
|
2615
2682
|
reject(
|
|
2616
2683
|
new wrapper.slothlet.SlothletError(
|
|
@@ -2623,6 +2690,7 @@ export class UnifiedWrapper extends ComponentBase {
|
|
|
2623
2690
|
)
|
|
2624
2691
|
);
|
|
2625
2692
|
return;
|
|
2693
|
+
|
|
2626
2694
|
}
|
|
2627
2695
|
setImmediate(checkMaterialized);
|
|
2628
2696
|
};
|
|
@@ -508,6 +508,30 @@ export class VersionManager extends ComponentBase {
|
|
|
508
508
|
return manager.#walkApiPath([versionTag, ...logicalPath.split(".")]);
|
|
509
509
|
};
|
|
510
510
|
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
target[Symbol.for("nodejs.util.inspect.custom")] = function (_depth, options, inspectFn) {
|
|
517
|
+
const vw = resolveVersionedWrapper();
|
|
518
|
+
if (vw) {
|
|
519
|
+
try {
|
|
520
|
+
return typeof inspectFn === "function" ? inspectFn(vw, options) : inspect(vw, options);
|
|
521
|
+
} catch {
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
void 0;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
const entry = manager.#registry.get(logicalPath);
|
|
531
|
+
const versions = entry ? Array.from(entry.versions.keys()) : [];
|
|
532
|
+
return { __versionDispatcher: logicalPath, versions };
|
|
533
|
+
};
|
|
534
|
+
|
|
511
535
|
const handlers = {
|
|
512
536
|
|
|
513
537
|
get(t, prop) {
|
|
@@ -566,24 +590,26 @@ export class VersionManager extends ComponentBase {
|
|
|
566
590
|
|
|
567
591
|
|
|
568
592
|
|
|
569
|
-
|
|
570
|
-
|
|
593
|
+
|
|
594
|
+
if (prop === inspect.custom) {
|
|
595
|
+
return (_depth, options, inspectFn) => {
|
|
571
596
|
const vw = resolveVersionedWrapper();
|
|
572
597
|
if (vw) {
|
|
573
598
|
try {
|
|
574
|
-
return inspect(vw);
|
|
599
|
+
return typeof inspectFn === "function" ? inspectFn(vw, options) : inspect(vw, options);
|
|
575
600
|
} catch {
|
|
576
601
|
|
|
602
|
+
|
|
577
603
|
void 0;
|
|
578
604
|
}
|
|
579
605
|
}
|
|
580
606
|
|
|
607
|
+
|
|
581
608
|
const entry = manager.#registry.get(logicalPath);
|
|
582
609
|
const versions = entry ? Array.from(entry.versions.keys()) : [];
|
|
583
610
|
return { __versionDispatcher: logicalPath, versions };
|
|
584
611
|
};
|
|
585
612
|
}
|
|
586
|
-
|
|
587
613
|
|
|
588
614
|
|
|
589
615
|
if (prop === "toString") return () => `[VersionDispatcher: ${logicalPath}]`;
|
|
@@ -741,6 +767,53 @@ export class VersionManager extends ComponentBase {
|
|
|
741
767
|
}
|
|
742
768
|
|
|
743
769
|
return Reflect.defineProperty(vw, prop, descriptor);
|
|
770
|
+
},
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
set(t, prop, value) {
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
if (typeof prop === "symbol") return true;
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
if (prop === "____slothletInternal" || prop === "_impl" || prop === "__impl" || prop === "__state" || prop === "__invalid")
|
|
783
|
+
return true;
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
if (
|
|
787
|
+
prop === "__isVersionDispatcher" ||
|
|
788
|
+
prop === "__mode" ||
|
|
789
|
+
prop === "__apiPath" ||
|
|
790
|
+
prop === "__slothletPath" ||
|
|
791
|
+
prop === "__isCallable" ||
|
|
792
|
+
prop === "__materializeOnCreate" ||
|
|
793
|
+
prop === "__materialized" ||
|
|
794
|
+
prop === "__inFlight" ||
|
|
795
|
+
prop === "__displayName" ||
|
|
796
|
+
prop === "__moduleID" ||
|
|
797
|
+
prop === "_materialize" ||
|
|
798
|
+
prop === "length" ||
|
|
799
|
+
prop === "name"
|
|
800
|
+
)
|
|
801
|
+
return true;
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
if (prop === "then" || prop === "constructor" || prop === "toString" || prop === "valueOf" || prop === "toJSON") return true;
|
|
805
|
+
|
|
806
|
+
const vw = resolveVersionedWrapper();
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
if (!vw) return true;
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
return Reflect.set(vw, prop, value, vw);
|
|
744
817
|
}
|
|
745
818
|
};
|
|
746
819
|
|