@signaltree/guardrails 4.0.16 → 4.1.1
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/dist/factories/index.js +15 -810
- package/dist/{index.js → lib/guardrails.js} +94 -262
- package/dist/lib/rules.js +62 -0
- package/dist/noop.js +13 -20
- package/package.json +30 -21
- package/src/factories/index.d.ts +20 -0
- package/src/index.d.ts +4 -0
- package/src/lib/guardrails.d.ts +3 -0
- package/src/lib/rules.d.ts +8 -0
- package/src/lib/types.d.ts +138 -0
- package/src/noop.d.ts +11 -0
- package/dist/factories/index.cjs +0 -922
- package/dist/factories/index.cjs.map +0 -1
- package/dist/factories/index.d.cts +0 -48
- package/dist/factories/index.d.ts +0 -48
- package/dist/factories/index.js.map +0 -1
- package/dist/index.cjs +0 -783
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -44
- package/dist/index.d.ts +0 -44
- package/dist/index.js.map +0 -1
- package/dist/noop.cjs +0 -31
- package/dist/noop.cjs.map +0 -1
- package/dist/noop.d.cts +0 -19
- package/dist/noop.d.ts +0 -19
- package/dist/noop.js.map +0 -1
- package/dist/types-DfZ9n1yX.d.cts +0 -255
- package/dist/types-DfZ9n1yX.d.ts +0 -255
package/dist/factories/index.js
CHANGED
|
@@ -1,788 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { withGuardrails } from '../lib/guardrails.js';
|
|
2
|
+
import { rules } from '../lib/rules.js';
|
|
3
3
|
|
|
4
|
-
// src/lib/guardrails.ts
|
|
5
|
-
function isFunction(value) {
|
|
6
|
-
return typeof value === "function";
|
|
7
|
-
}
|
|
8
|
-
__name(isFunction, "isFunction");
|
|
9
|
-
function isString(value) {
|
|
10
|
-
return typeof value === "string";
|
|
11
|
-
}
|
|
12
|
-
__name(isString, "isString");
|
|
13
|
-
function isObjectLike(value) {
|
|
14
|
-
return typeof value === "object" && value !== null;
|
|
15
|
-
}
|
|
16
|
-
__name(isObjectLike, "isObjectLike");
|
|
17
|
-
function resolveEnabledFlag(option) {
|
|
18
|
-
if (option === void 0) {
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
if (isFunction(option)) {
|
|
22
|
-
try {
|
|
23
|
-
return option();
|
|
24
|
-
} catch {
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return option;
|
|
29
|
-
}
|
|
30
|
-
__name(resolveEnabledFlag, "resolveEnabledFlag");
|
|
31
|
-
function supportsMiddleware(tree) {
|
|
32
|
-
const candidate = tree;
|
|
33
|
-
return isFunction(candidate.addTap) && isFunction(candidate.removeTap);
|
|
34
|
-
}
|
|
35
|
-
__name(supportsMiddleware, "supportsMiddleware");
|
|
36
|
-
function tryStructuredClone(value) {
|
|
37
|
-
const cloneFn = globalThis.structuredClone;
|
|
38
|
-
if (isFunction(cloneFn)) {
|
|
39
|
-
try {
|
|
40
|
-
return cloneFn(value);
|
|
41
|
-
} catch {
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return value;
|
|
45
|
-
}
|
|
46
|
-
__name(tryStructuredClone, "tryStructuredClone");
|
|
47
|
-
function isDevEnvironment() {
|
|
48
|
-
if (__DEV__ !== void 0) return __DEV__;
|
|
49
|
-
if (process?.env?.["NODE_ENV"] === "production") return false;
|
|
50
|
-
if (ngDevMode != null) return Boolean(ngDevMode);
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
__name(isDevEnvironment, "isDevEnvironment");
|
|
54
|
-
var MAX_TIMING_SAMPLES = 1e3;
|
|
55
|
-
var RECOMPUTATION_WINDOW_MS = 1e3;
|
|
56
|
-
function withGuardrails(config = {}) {
|
|
57
|
-
return (tree) => {
|
|
58
|
-
const enabled = resolveEnabledFlag(config.enabled);
|
|
59
|
-
if (!isDevEnvironment() || !enabled) {
|
|
60
|
-
return tree;
|
|
61
|
-
}
|
|
62
|
-
if (!supportsMiddleware(tree)) {
|
|
63
|
-
console.warn("[Guardrails] Tree does not expose middleware hooks; guardrails disabled.");
|
|
64
|
-
return tree;
|
|
65
|
-
}
|
|
66
|
-
const stats = createRuntimeStats();
|
|
67
|
-
const context = {
|
|
68
|
-
tree,
|
|
69
|
-
config,
|
|
70
|
-
stats,
|
|
71
|
-
issues: [],
|
|
72
|
-
hotPaths: [],
|
|
73
|
-
currentUpdate: null,
|
|
74
|
-
suppressed: false,
|
|
75
|
-
timings: [],
|
|
76
|
-
hotPathData: /* @__PURE__ */ new Map(),
|
|
77
|
-
issueMap: /* @__PURE__ */ new Map(),
|
|
78
|
-
signalUsage: /* @__PURE__ */ new Map(),
|
|
79
|
-
memoryHistory: [],
|
|
80
|
-
recomputationLog: [],
|
|
81
|
-
disposed: false
|
|
82
|
-
};
|
|
83
|
-
const middlewareId = `guardrails:${config.treeId ?? "tree"}:${Math.random().toString(36).slice(2)}`;
|
|
84
|
-
context.middlewareId = middlewareId;
|
|
85
|
-
const middleware = createGuardrailsMiddleware(context);
|
|
86
|
-
tree.addTap(middleware);
|
|
87
|
-
const stopMonitoring = startMonitoring(context);
|
|
88
|
-
const teardown = /* @__PURE__ */ __name(() => {
|
|
89
|
-
if (context.disposed) return;
|
|
90
|
-
context.disposed = true;
|
|
91
|
-
stopMonitoring();
|
|
92
|
-
try {
|
|
93
|
-
tree.removeTap(middlewareId);
|
|
94
|
-
} catch {
|
|
95
|
-
}
|
|
96
|
-
}, "teardown");
|
|
97
|
-
const originalDestroy = tree.destroy?.bind(tree);
|
|
98
|
-
tree.destroy = () => {
|
|
99
|
-
teardown();
|
|
100
|
-
if (originalDestroy) {
|
|
101
|
-
originalDestroy();
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
tree["__guardrails"] = createAPI(context, teardown);
|
|
105
|
-
return tree;
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
__name(withGuardrails, "withGuardrails");
|
|
109
|
-
function createRuntimeStats() {
|
|
110
|
-
return {
|
|
111
|
-
updateCount: 0,
|
|
112
|
-
totalUpdateTime: 0,
|
|
113
|
-
avgUpdateTime: 0,
|
|
114
|
-
p50UpdateTime: 0,
|
|
115
|
-
p95UpdateTime: 0,
|
|
116
|
-
p99UpdateTime: 0,
|
|
117
|
-
maxUpdateTime: 0,
|
|
118
|
-
recomputationCount: 0,
|
|
119
|
-
recomputationsPerSecond: 0,
|
|
120
|
-
signalCount: 0,
|
|
121
|
-
signalRetention: 0,
|
|
122
|
-
unreadSignalCount: 0,
|
|
123
|
-
memoryGrowthRate: 0,
|
|
124
|
-
hotPathCount: 0,
|
|
125
|
-
violationCount: 0
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
__name(createRuntimeStats, "createRuntimeStats");
|
|
129
|
-
function createGuardrailsMiddleware(context) {
|
|
130
|
-
return {
|
|
131
|
-
id: context.middlewareId ?? "guardrails",
|
|
132
|
-
before: /* @__PURE__ */ __name((action, payload, state) => {
|
|
133
|
-
if (context.suppressed) {
|
|
134
|
-
context.currentUpdate = null;
|
|
135
|
-
return !context.disposed;
|
|
136
|
-
}
|
|
137
|
-
const metadata = extractMetadata(payload);
|
|
138
|
-
if (shouldSuppressUpdate(context, metadata)) {
|
|
139
|
-
context.currentUpdate = null;
|
|
140
|
-
return !context.disposed;
|
|
141
|
-
}
|
|
142
|
-
const details = collectUpdateDetails(payload, state);
|
|
143
|
-
context.currentUpdate = {
|
|
144
|
-
action,
|
|
145
|
-
startTime: performance.now(),
|
|
146
|
-
metadata,
|
|
147
|
-
details
|
|
148
|
-
};
|
|
149
|
-
for (const detail of details) {
|
|
150
|
-
analyzePreUpdate(context, detail, metadata);
|
|
151
|
-
}
|
|
152
|
-
return !context.disposed;
|
|
153
|
-
}, "before"),
|
|
154
|
-
after: /* @__PURE__ */ __name((_action, _payload, _previousState, newState) => {
|
|
155
|
-
const pending = context.currentUpdate;
|
|
156
|
-
if (!pending) return;
|
|
157
|
-
const duration = Math.max(0, performance.now() - pending.startTime);
|
|
158
|
-
const timestamp = Date.now();
|
|
159
|
-
const recomputations = Math.max(0, pending.details.length - 1);
|
|
160
|
-
updateTimingStats(context, duration);
|
|
161
|
-
for (const [index, detail] of pending.details.entries()) {
|
|
162
|
-
const latest = getValueAtPath(newState, detail.segments);
|
|
163
|
-
const diffRatio = calculateDiffRatio(detail.oldValue, latest);
|
|
164
|
-
analyzePostUpdate(context, detail, duration, diffRatio, index === 0);
|
|
165
|
-
trackHotPath(context, detail.path, duration);
|
|
166
|
-
trackSignalUsage(context, detail.path, timestamp);
|
|
167
|
-
}
|
|
168
|
-
updateSignalStats(context, timestamp);
|
|
169
|
-
recordRecomputations(context, recomputations, timestamp);
|
|
170
|
-
context.currentUpdate = null;
|
|
171
|
-
}, "after")
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
__name(createGuardrailsMiddleware, "createGuardrailsMiddleware");
|
|
175
|
-
function updatePercentiles(context) {
|
|
176
|
-
if (context.timings.length === 0) return;
|
|
177
|
-
const sorted = [
|
|
178
|
-
...context.timings
|
|
179
|
-
].sort((a, b) => a - b);
|
|
180
|
-
context.stats.p50UpdateTime = sorted[Math.floor(sorted.length * 0.5)] || 0;
|
|
181
|
-
context.stats.p95UpdateTime = sorted[Math.floor(sorted.length * 0.95)] || 0;
|
|
182
|
-
context.stats.p99UpdateTime = sorted[Math.floor(sorted.length * 0.99)] || 0;
|
|
183
|
-
}
|
|
184
|
-
__name(updatePercentiles, "updatePercentiles");
|
|
185
|
-
function calculateDiffRatio(oldValue, newValue) {
|
|
186
|
-
if (!isComparableRecord(oldValue) || !isComparableRecord(newValue)) {
|
|
187
|
-
return Object.is(oldValue, newValue) ? 0 : 1;
|
|
188
|
-
}
|
|
189
|
-
if (oldValue === newValue) return 0;
|
|
190
|
-
const oldKeys = new Set(Object.keys(oldValue));
|
|
191
|
-
const newKeys = new Set(Object.keys(newValue));
|
|
192
|
-
const allKeys = /* @__PURE__ */ new Set([
|
|
193
|
-
...oldKeys,
|
|
194
|
-
...newKeys
|
|
195
|
-
]);
|
|
196
|
-
let changed = 0;
|
|
197
|
-
for (const key of allKeys) {
|
|
198
|
-
if (!oldKeys.has(key) || !newKeys.has(key) || oldValue[key] !== newValue[key]) {
|
|
199
|
-
changed++;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return allKeys.size === 0 ? 0 : changed / allKeys.size;
|
|
203
|
-
}
|
|
204
|
-
__name(calculateDiffRatio, "calculateDiffRatio");
|
|
205
|
-
function analyzePreUpdate(context, detail, metadata) {
|
|
206
|
-
if (!context.config.customRules) return;
|
|
207
|
-
for (const rule of context.config.customRules) {
|
|
208
|
-
evaluateRule(context, rule, {
|
|
209
|
-
path: detail.segments,
|
|
210
|
-
value: detail.newValue,
|
|
211
|
-
oldValue: detail.oldValue,
|
|
212
|
-
metadata,
|
|
213
|
-
tree: context.tree,
|
|
214
|
-
stats: context.stats
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
__name(analyzePreUpdate, "analyzePreUpdate");
|
|
219
|
-
function analyzePostUpdate(context, detail, duration, diffRatio, isPrimary) {
|
|
220
|
-
if (isPrimary && context.config.budgets?.maxUpdateTime && duration > context.config.budgets.maxUpdateTime) {
|
|
221
|
-
addIssue(context, {
|
|
222
|
-
type: "budget",
|
|
223
|
-
severity: "error",
|
|
224
|
-
message: `Update took ${duration.toFixed(2)}ms (budget: ${context.config.budgets.maxUpdateTime}ms)`,
|
|
225
|
-
path: detail.path,
|
|
226
|
-
count: 1
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
const minDiff = context.config.analysis?.minDiffForParentReplace ?? 0.8;
|
|
230
|
-
if (context.config.analysis?.warnParentReplace && diffRatio > minDiff) {
|
|
231
|
-
addIssue(context, {
|
|
232
|
-
type: "analysis",
|
|
233
|
-
severity: "warning",
|
|
234
|
-
message: `High diff ratio (${(diffRatio * 100).toFixed(0)}%) - consider scoped updates`,
|
|
235
|
-
path: detail.path,
|
|
236
|
-
count: 1,
|
|
237
|
-
diffRatio
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
__name(analyzePostUpdate, "analyzePostUpdate");
|
|
242
|
-
function trackHotPath(context, path, duration) {
|
|
243
|
-
if (!context.config.hotPaths?.enabled) return;
|
|
244
|
-
const pathKey = Array.isArray(path) ? path.join(".") : path;
|
|
245
|
-
const now = Date.now();
|
|
246
|
-
const windowMs = context.config.hotPaths.windowMs || 1e3;
|
|
247
|
-
let data = context.hotPathData.get(pathKey);
|
|
248
|
-
if (!data) {
|
|
249
|
-
data = {
|
|
250
|
-
count: 0,
|
|
251
|
-
lastUpdate: now,
|
|
252
|
-
durations: []
|
|
253
|
-
};
|
|
254
|
-
context.hotPathData.set(pathKey, data);
|
|
255
|
-
}
|
|
256
|
-
if (now - data.lastUpdate > windowMs) {
|
|
257
|
-
data.count = 0;
|
|
258
|
-
data.durations = [];
|
|
259
|
-
}
|
|
260
|
-
data.count++;
|
|
261
|
-
data.durations.push(duration);
|
|
262
|
-
data.lastUpdate = now;
|
|
263
|
-
const threshold = context.config.hotPaths.threshold || 10;
|
|
264
|
-
const updatesPerSecond = data.count / windowMs * 1e3;
|
|
265
|
-
if (updatesPerSecond > threshold) {
|
|
266
|
-
const sorted = [
|
|
267
|
-
...data.durations
|
|
268
|
-
].sort((a, b) => a - b);
|
|
269
|
-
const p95 = sorted[Math.floor(sorted.length * 0.95)] || 0;
|
|
270
|
-
const avg = data.durations.reduce((sum, d) => sum + d, 0) / data.durations.length;
|
|
271
|
-
updateHotPath(context, {
|
|
272
|
-
path: pathKey,
|
|
273
|
-
updatesPerSecond,
|
|
274
|
-
heatScore: Math.min(100, updatesPerSecond / threshold * 50),
|
|
275
|
-
downstreamEffects: 0,
|
|
276
|
-
avgDuration: avg,
|
|
277
|
-
p95Duration: p95
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
__name(trackHotPath, "trackHotPath");
|
|
282
|
-
function trackSignalUsage(context, path, timestamp) {
|
|
283
|
-
const key = Array.isArray(path) ? path.join(".") : path;
|
|
284
|
-
const entry = context.signalUsage.get(key) ?? {
|
|
285
|
-
updates: 0,
|
|
286
|
-
lastSeen: timestamp
|
|
287
|
-
};
|
|
288
|
-
entry.updates += 1;
|
|
289
|
-
entry.lastSeen = timestamp;
|
|
290
|
-
context.signalUsage.set(key, entry);
|
|
291
|
-
}
|
|
292
|
-
__name(trackSignalUsage, "trackSignalUsage");
|
|
293
|
-
function updateSignalStats(context, timestamp) {
|
|
294
|
-
const retentionWindow = context.config.memoryLeaks?.checkInterval ?? 5e3;
|
|
295
|
-
const historyWindow = Math.max(retentionWindow, 1e3);
|
|
296
|
-
const signalCount = context.signalUsage.size;
|
|
297
|
-
context.stats.signalCount = signalCount;
|
|
298
|
-
const staleCount = [
|
|
299
|
-
...context.signalUsage.values()
|
|
300
|
-
].filter((entry) => timestamp - entry.lastSeen > retentionWindow).length;
|
|
301
|
-
context.stats.signalRetention = staleCount;
|
|
302
|
-
context.stats.unreadSignalCount = 0;
|
|
303
|
-
context.memoryHistory.push({
|
|
304
|
-
timestamp,
|
|
305
|
-
count: signalCount
|
|
306
|
-
});
|
|
307
|
-
context.memoryHistory = context.memoryHistory.filter((entry) => timestamp - entry.timestamp <= historyWindow);
|
|
308
|
-
const baseline = context.memoryHistory[0]?.count ?? signalCount;
|
|
309
|
-
const growth = baseline === 0 ? 0 : (signalCount - baseline) / Math.max(1, baseline);
|
|
310
|
-
context.stats.memoryGrowthRate = growth;
|
|
311
|
-
}
|
|
312
|
-
__name(updateSignalStats, "updateSignalStats");
|
|
313
|
-
function recordRecomputations(context, count, timestamp) {
|
|
314
|
-
if (count > 0) {
|
|
315
|
-
context.stats.recomputationCount += count;
|
|
316
|
-
for (let i = 0; i < count; i++) {
|
|
317
|
-
context.recomputationLog.push(timestamp);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
if (context.recomputationLog.length) {
|
|
321
|
-
context.recomputationLog = context.recomputationLog.filter((value) => timestamp - value <= RECOMPUTATION_WINDOW_MS);
|
|
322
|
-
}
|
|
323
|
-
context.stats.recomputationsPerSecond = context.recomputationLog.length;
|
|
324
|
-
}
|
|
325
|
-
__name(recordRecomputations, "recordRecomputations");
|
|
326
|
-
function updateHotPath(context, hotPath) {
|
|
327
|
-
const existing = context.hotPaths.find((h) => h.path === hotPath.path);
|
|
328
|
-
if (existing) {
|
|
329
|
-
Object.assign(existing, hotPath);
|
|
330
|
-
} else {
|
|
331
|
-
context.hotPaths.push(hotPath);
|
|
332
|
-
const topN = context.config.hotPaths?.topN || 5;
|
|
333
|
-
if (context.hotPaths.length > topN) {
|
|
334
|
-
context.hotPaths.sort((a, b) => b.heatScore - a.heatScore);
|
|
335
|
-
context.hotPaths.length = topN;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
context.stats.hotPathCount = context.hotPaths.length;
|
|
339
|
-
}
|
|
340
|
-
__name(updateHotPath, "updateHotPath");
|
|
341
|
-
function evaluateRule(context, rule, ruleContext) {
|
|
342
|
-
const handleFailure = /* @__PURE__ */ __name(() => {
|
|
343
|
-
const message = typeof rule.message === "function" ? rule.message(ruleContext) : rule.message;
|
|
344
|
-
addIssue(context, {
|
|
345
|
-
type: "rule",
|
|
346
|
-
severity: rule.severity || "warning",
|
|
347
|
-
message,
|
|
348
|
-
path: ruleContext.path.join("."),
|
|
349
|
-
count: 1,
|
|
350
|
-
metadata: {
|
|
351
|
-
rule: rule.name
|
|
352
|
-
}
|
|
353
|
-
});
|
|
354
|
-
}, "handleFailure");
|
|
355
|
-
try {
|
|
356
|
-
const result = rule.test(ruleContext);
|
|
357
|
-
if (result instanceof Promise) {
|
|
358
|
-
result.then((outcome) => {
|
|
359
|
-
if (!outcome) {
|
|
360
|
-
handleFailure();
|
|
361
|
-
}
|
|
362
|
-
}).catch((error) => {
|
|
363
|
-
console.warn(`[Guardrails] Rule ${rule.name} rejected:`, error);
|
|
364
|
-
});
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
if (!result) {
|
|
368
|
-
handleFailure();
|
|
369
|
-
}
|
|
370
|
-
} catch (error) {
|
|
371
|
-
console.warn(`[Guardrails] Rule ${rule.name} threw error:`, error);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
__name(evaluateRule, "evaluateRule");
|
|
375
|
-
function addIssue(context, issue) {
|
|
376
|
-
if (context.suppressed) return;
|
|
377
|
-
if (context.config.reporting?.aggregateWarnings !== false) {
|
|
378
|
-
const key = `${issue.type}:${issue.path}:${issue.message}`;
|
|
379
|
-
const existing = context.issueMap.get(key);
|
|
380
|
-
if (existing) {
|
|
381
|
-
existing.count++;
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
context.issueMap.set(key, issue);
|
|
385
|
-
}
|
|
386
|
-
context.issues.push(issue);
|
|
387
|
-
context.stats.violationCount++;
|
|
388
|
-
if (context.config.mode === "throw") {
|
|
389
|
-
throw new Error(`[Guardrails] ${issue.message}`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
__name(addIssue, "addIssue");
|
|
393
|
-
function shouldSuppressUpdate(context, metadata) {
|
|
394
|
-
if (context.suppressed) return true;
|
|
395
|
-
if (!metadata) return false;
|
|
396
|
-
if (metadata.suppressGuardrails && context.config.suppression?.respectMetadata !== false) {
|
|
397
|
-
return true;
|
|
398
|
-
}
|
|
399
|
-
const autoSuppress = new Set(context.config.suppression?.autoSuppress ?? []);
|
|
400
|
-
return [
|
|
401
|
-
metadata.intent,
|
|
402
|
-
metadata.source
|
|
403
|
-
].some((value) => isString(value) && autoSuppress.has(value));
|
|
404
|
-
}
|
|
405
|
-
__name(shouldSuppressUpdate, "shouldSuppressUpdate");
|
|
406
|
-
function startMonitoring(context) {
|
|
407
|
-
const interval = setInterval(() => {
|
|
408
|
-
if (context.disposed) {
|
|
409
|
-
clearInterval(interval);
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
checkMemory(context);
|
|
413
|
-
maybeReport(context);
|
|
414
|
-
}, context.config.reporting?.interval || 5e3);
|
|
415
|
-
return () => clearInterval(interval);
|
|
416
|
-
}
|
|
417
|
-
__name(startMonitoring, "startMonitoring");
|
|
418
|
-
function checkMemory(context) {
|
|
419
|
-
if (!context.config.memoryLeaks?.enabled) return;
|
|
420
|
-
const now = Date.now();
|
|
421
|
-
const retentionWindow = context.config.memoryLeaks?.checkInterval ?? 5e3;
|
|
422
|
-
const retentionThreshold = context.config.memoryLeaks?.retentionThreshold ?? 100;
|
|
423
|
-
const growthThreshold = context.config.memoryLeaks?.growthRate ?? 0.2;
|
|
424
|
-
const staleCount = [
|
|
425
|
-
...context.signalUsage.values()
|
|
426
|
-
].filter((entry) => now - entry.lastSeen > retentionWindow).length;
|
|
427
|
-
context.stats.signalRetention = staleCount;
|
|
428
|
-
const exceedsRetention = context.stats.signalRetention > retentionThreshold;
|
|
429
|
-
const exceedsGrowth = context.stats.memoryGrowthRate > growthThreshold;
|
|
430
|
-
if (exceedsRetention || exceedsGrowth) {
|
|
431
|
-
addIssue(context, {
|
|
432
|
-
type: "memory",
|
|
433
|
-
severity: "warning",
|
|
434
|
-
message: `Potential memory leak detected (signals: ${context.stats.signalCount}, growth ${(context.stats.memoryGrowthRate * 100).toFixed(1)}%)`,
|
|
435
|
-
path: "root",
|
|
436
|
-
count: 1,
|
|
437
|
-
metadata: {
|
|
438
|
-
signalCount: context.stats.signalCount,
|
|
439
|
-
growth: context.stats.memoryGrowthRate
|
|
440
|
-
}
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
__name(checkMemory, "checkMemory");
|
|
445
|
-
function maybeReport(context) {
|
|
446
|
-
if (context.config.reporting?.console === false) return;
|
|
447
|
-
const report = generateReport(context);
|
|
448
|
-
if (context.config.reporting?.customReporter) {
|
|
449
|
-
context.config.reporting.customReporter(report);
|
|
450
|
-
}
|
|
451
|
-
if (context.config.reporting?.console && context.issues.length > 0) {
|
|
452
|
-
reportToConsole(report, context.config.reporting.console === "verbose");
|
|
453
|
-
}
|
|
454
|
-
context.issues = [];
|
|
455
|
-
context.issueMap.clear();
|
|
456
|
-
}
|
|
457
|
-
__name(maybeReport, "maybeReport");
|
|
458
|
-
function reportToConsole(report, verbose) {
|
|
459
|
-
console.group("[Guardrails] Performance Report");
|
|
460
|
-
logIssues(report.issues);
|
|
461
|
-
logHotPaths(report.hotPaths);
|
|
462
|
-
if (verbose) {
|
|
463
|
-
logVerboseStats(report);
|
|
464
|
-
}
|
|
465
|
-
console.groupEnd();
|
|
466
|
-
}
|
|
467
|
-
__name(reportToConsole, "reportToConsole");
|
|
468
|
-
function logIssues(issues) {
|
|
469
|
-
if (issues.length === 0) return;
|
|
470
|
-
console.warn(`${issues.length} issues detected:`);
|
|
471
|
-
for (const issue of issues) {
|
|
472
|
-
const prefix = getSeverityPrefix(issue.severity);
|
|
473
|
-
const countSuffix = issue.count > 1 ? ` (x${issue.count})` : "";
|
|
474
|
-
const message = `${prefix} [${issue.type}] ${issue.message}${countSuffix}`;
|
|
475
|
-
console.log(` ${message}`);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
__name(logIssues, "logIssues");
|
|
479
|
-
function logHotPaths(hotPaths) {
|
|
480
|
-
if (hotPaths.length === 0) return;
|
|
481
|
-
console.log(`
|
|
482
|
-
Hot Paths (${hotPaths.length}):`);
|
|
483
|
-
for (const hp of hotPaths) {
|
|
484
|
-
const entry = ` \u{1F525} ${hp.path}: ${hp.updatesPerSecond.toFixed(1)}/s (heat: ${hp.heatScore.toFixed(0)})`;
|
|
485
|
-
console.log(entry);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
__name(logHotPaths, "logHotPaths");
|
|
489
|
-
function logVerboseStats(report) {
|
|
490
|
-
console.log("\nStats:", report.stats);
|
|
491
|
-
console.log("Budgets:", report.budgets);
|
|
492
|
-
}
|
|
493
|
-
__name(logVerboseStats, "logVerboseStats");
|
|
494
|
-
function getSeverityPrefix(severity) {
|
|
495
|
-
if (severity === "error") return "\u274C";
|
|
496
|
-
if (severity === "warning") return "\u26A0\uFE0F";
|
|
497
|
-
return "\u2139\uFE0F";
|
|
498
|
-
}
|
|
499
|
-
__name(getSeverityPrefix, "getSeverityPrefix");
|
|
500
|
-
function generateReport(context) {
|
|
501
|
-
const memoryCurrent = context.stats.signalCount;
|
|
502
|
-
const memoryLimit = context.config.budgets?.maxMemory ?? 50;
|
|
503
|
-
const recomputationCurrent = context.stats.recomputationsPerSecond;
|
|
504
|
-
const recomputationLimit = context.config.budgets?.maxRecomputations ?? 100;
|
|
505
|
-
const budgets = {
|
|
506
|
-
updateTime: createBudgetItem(context.stats.avgUpdateTime, context.config.budgets?.maxUpdateTime || 16),
|
|
507
|
-
memory: createBudgetItem(memoryCurrent, memoryLimit),
|
|
508
|
-
recomputations: createBudgetItem(recomputationCurrent, recomputationLimit)
|
|
509
|
-
};
|
|
510
|
-
return {
|
|
511
|
-
timestamp: Date.now(),
|
|
512
|
-
treeId: context.config.treeId,
|
|
513
|
-
issues: Array.from(context.issueMap.values()),
|
|
514
|
-
hotPaths: context.hotPaths,
|
|
515
|
-
budgets,
|
|
516
|
-
stats: context.stats,
|
|
517
|
-
recommendations: generateRecommendations(context)
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
__name(generateReport, "generateReport");
|
|
521
|
-
function createBudgetItem(current, limit) {
|
|
522
|
-
if (limit <= 0) {
|
|
523
|
-
return {
|
|
524
|
-
current,
|
|
525
|
-
limit,
|
|
526
|
-
usage: 0,
|
|
527
|
-
status: "ok"
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
const usage = current / limit * 100;
|
|
531
|
-
const threshold = 80;
|
|
532
|
-
let status;
|
|
533
|
-
if (usage > 100) {
|
|
534
|
-
status = "exceeded";
|
|
535
|
-
} else if (usage > threshold) {
|
|
536
|
-
status = "warning";
|
|
537
|
-
} else {
|
|
538
|
-
status = "ok";
|
|
539
|
-
}
|
|
540
|
-
return {
|
|
541
|
-
current,
|
|
542
|
-
limit,
|
|
543
|
-
usage,
|
|
544
|
-
status
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
__name(createBudgetItem, "createBudgetItem");
|
|
548
|
-
function generateRecommendations(context) {
|
|
549
|
-
const recommendations = [];
|
|
550
|
-
if (context.hotPaths.length > 0) {
|
|
551
|
-
recommendations.push("Consider batching or debouncing updates to hot paths");
|
|
552
|
-
}
|
|
553
|
-
if (context.stats.avgUpdateTime > 10) {
|
|
554
|
-
recommendations.push("Average update time is high - review update logic");
|
|
555
|
-
}
|
|
556
|
-
return recommendations;
|
|
557
|
-
}
|
|
558
|
-
__name(generateRecommendations, "generateRecommendations");
|
|
559
|
-
function createAPI(context, teardown) {
|
|
560
|
-
return {
|
|
561
|
-
getReport: /* @__PURE__ */ __name(() => generateReport(context), "getReport"),
|
|
562
|
-
getStats: /* @__PURE__ */ __name(() => context.stats, "getStats"),
|
|
563
|
-
suppress: /* @__PURE__ */ __name((fn) => {
|
|
564
|
-
const was = context.suppressed;
|
|
565
|
-
context.suppressed = true;
|
|
566
|
-
try {
|
|
567
|
-
fn();
|
|
568
|
-
} finally {
|
|
569
|
-
context.suppressed = was;
|
|
570
|
-
}
|
|
571
|
-
}, "suppress"),
|
|
572
|
-
dispose: /* @__PURE__ */ __name(() => {
|
|
573
|
-
teardown();
|
|
574
|
-
const finalReport = generateReport(context);
|
|
575
|
-
if (context.config.reporting?.console !== false) {
|
|
576
|
-
console.log("[Guardrails] Final report on disposal:", finalReport);
|
|
577
|
-
}
|
|
578
|
-
if (context.config.reporting?.customReporter) {
|
|
579
|
-
context.config.reporting.customReporter(finalReport);
|
|
580
|
-
}
|
|
581
|
-
}, "dispose")
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
__name(createAPI, "createAPI");
|
|
585
|
-
function extractMetadata(payload) {
|
|
586
|
-
if (!isObjectLike(payload)) return void 0;
|
|
587
|
-
const candidate = payload["metadata"];
|
|
588
|
-
return isObjectLike(candidate) ? candidate : void 0;
|
|
589
|
-
}
|
|
590
|
-
__name(extractMetadata, "extractMetadata");
|
|
591
|
-
function collectUpdateDetails(payload, stateSnapshot) {
|
|
592
|
-
const details = [];
|
|
593
|
-
const visit = /* @__PURE__ */ __name((value, segments, currentState) => {
|
|
594
|
-
const path = segments.length ? segments.join(".") : "root";
|
|
595
|
-
const oldValue = captureValue(currentState);
|
|
596
|
-
if (isObjectLike(value)) {
|
|
597
|
-
details.push({
|
|
598
|
-
path,
|
|
599
|
-
segments: [
|
|
600
|
-
...segments
|
|
601
|
-
],
|
|
602
|
-
newValue: value,
|
|
603
|
-
oldValue
|
|
604
|
-
});
|
|
605
|
-
for (const [key, child] of Object.entries(value)) {
|
|
606
|
-
visit(child, [
|
|
607
|
-
...segments,
|
|
608
|
-
key
|
|
609
|
-
], isObjectLike(currentState) ? currentState[key] : void 0);
|
|
610
|
-
}
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
details.push({
|
|
614
|
-
path,
|
|
615
|
-
segments: [
|
|
616
|
-
...segments
|
|
617
|
-
],
|
|
618
|
-
newValue: value,
|
|
619
|
-
oldValue
|
|
620
|
-
});
|
|
621
|
-
}, "visit");
|
|
622
|
-
if (isObjectLike(payload)) {
|
|
623
|
-
visit(payload, [], stateSnapshot);
|
|
624
|
-
} else {
|
|
625
|
-
details.push({
|
|
626
|
-
path: "root",
|
|
627
|
-
segments: [],
|
|
628
|
-
newValue: payload,
|
|
629
|
-
oldValue: captureValue(stateSnapshot)
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
if (details.length === 0) {
|
|
633
|
-
details.push({
|
|
634
|
-
path: "root",
|
|
635
|
-
segments: [],
|
|
636
|
-
newValue: payload,
|
|
637
|
-
oldValue: captureValue(stateSnapshot)
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
return details;
|
|
641
|
-
}
|
|
642
|
-
__name(collectUpdateDetails, "collectUpdateDetails");
|
|
643
|
-
function getValueAtPath(source, segments) {
|
|
644
|
-
let current = source;
|
|
645
|
-
for (const segment of segments) {
|
|
646
|
-
if (!isObjectLike(current)) {
|
|
647
|
-
return void 0;
|
|
648
|
-
}
|
|
649
|
-
current = current[segment];
|
|
650
|
-
}
|
|
651
|
-
return current;
|
|
652
|
-
}
|
|
653
|
-
__name(getValueAtPath, "getValueAtPath");
|
|
654
|
-
function captureValue(value) {
|
|
655
|
-
return tryStructuredClone(value);
|
|
656
|
-
}
|
|
657
|
-
__name(captureValue, "captureValue");
|
|
658
|
-
function isPlainObject(value) {
|
|
659
|
-
if (!value || typeof value !== "object") return false;
|
|
660
|
-
const proto = Object.getPrototypeOf(value);
|
|
661
|
-
return proto === Object.prototype || proto === null;
|
|
662
|
-
}
|
|
663
|
-
__name(isPlainObject, "isPlainObject");
|
|
664
|
-
function updateTimingStats(context, duration) {
|
|
665
|
-
context.timings.push(duration);
|
|
666
|
-
if (context.timings.length > MAX_TIMING_SAMPLES) {
|
|
667
|
-
context.timings.shift();
|
|
668
|
-
}
|
|
669
|
-
context.stats.updateCount++;
|
|
670
|
-
context.stats.totalUpdateTime += duration;
|
|
671
|
-
context.stats.avgUpdateTime = context.stats.totalUpdateTime / context.stats.updateCount;
|
|
672
|
-
context.stats.maxUpdateTime = Math.max(context.stats.maxUpdateTime, duration);
|
|
673
|
-
updatePercentiles(context);
|
|
674
|
-
}
|
|
675
|
-
__name(updateTimingStats, "updateTimingStats");
|
|
676
|
-
function isComparableRecord(value) {
|
|
677
|
-
return isPlainObject(value);
|
|
678
|
-
}
|
|
679
|
-
__name(isComparableRecord, "isComparableRecord");
|
|
680
|
-
|
|
681
|
-
// src/lib/rules.ts
|
|
682
|
-
var rules = {
|
|
683
|
-
/**
|
|
684
|
-
* Prevents deep nesting beyond specified depth
|
|
685
|
-
*/
|
|
686
|
-
noDeepNesting: /* @__PURE__ */ __name((maxDepth = 5) => ({
|
|
687
|
-
name: "no-deep-nesting",
|
|
688
|
-
description: `Prevents nesting deeper than ${maxDepth} levels`,
|
|
689
|
-
test: /* @__PURE__ */ __name((ctx) => ctx.path.length <= maxDepth, "test"),
|
|
690
|
-
message: /* @__PURE__ */ __name((ctx) => `Path too deep: ${ctx.path.join(".")} (${ctx.path.length} levels, max: ${maxDepth})`, "message"),
|
|
691
|
-
severity: "warning",
|
|
692
|
-
tags: [
|
|
693
|
-
"architecture",
|
|
694
|
-
"complexity"
|
|
695
|
-
]
|
|
696
|
-
}), "noDeepNesting"),
|
|
697
|
-
/**
|
|
698
|
-
* Prevents storing functions in state (breaks serialization)
|
|
699
|
-
*/
|
|
700
|
-
noFunctionsInState: /* @__PURE__ */ __name(() => ({
|
|
701
|
-
name: "no-functions",
|
|
702
|
-
description: "Functions break serialization",
|
|
703
|
-
test: /* @__PURE__ */ __name((ctx) => typeof ctx.value !== "function", "test"),
|
|
704
|
-
message: "Functions cannot be stored in state (breaks serialization)",
|
|
705
|
-
severity: "error",
|
|
706
|
-
tags: [
|
|
707
|
-
"serialization",
|
|
708
|
-
"data"
|
|
709
|
-
]
|
|
710
|
-
}), "noFunctionsInState"),
|
|
711
|
-
/**
|
|
712
|
-
* Prevents cache from being persisted
|
|
713
|
-
*/
|
|
714
|
-
noCacheInPersistence: /* @__PURE__ */ __name(() => ({
|
|
715
|
-
name: "no-cache-persistence",
|
|
716
|
-
description: "Prevent cache from being persisted",
|
|
717
|
-
test: /* @__PURE__ */ __name((ctx) => {
|
|
718
|
-
if (ctx.metadata?.source === "serialization" && ctx.path.includes("cache")) {
|
|
719
|
-
return false;
|
|
720
|
-
}
|
|
721
|
-
return true;
|
|
722
|
-
}, "test"),
|
|
723
|
-
message: "Cache should not be persisted",
|
|
724
|
-
severity: "warning",
|
|
725
|
-
tags: [
|
|
726
|
-
"persistence",
|
|
727
|
-
"cache"
|
|
728
|
-
]
|
|
729
|
-
}), "noCacheInPersistence"),
|
|
730
|
-
/**
|
|
731
|
-
* Limits payload size
|
|
732
|
-
*/
|
|
733
|
-
maxPayloadSize: /* @__PURE__ */ __name((maxKB = 100) => ({
|
|
734
|
-
name: "max-payload-size",
|
|
735
|
-
description: `Limit payload size to ${maxKB}KB`,
|
|
736
|
-
test: /* @__PURE__ */ __name((ctx) => {
|
|
737
|
-
try {
|
|
738
|
-
const size = JSON.stringify(ctx.value).length;
|
|
739
|
-
return size < maxKB * 1024;
|
|
740
|
-
} catch {
|
|
741
|
-
return true;
|
|
742
|
-
}
|
|
743
|
-
}, "test"),
|
|
744
|
-
message: /* @__PURE__ */ __name((ctx) => {
|
|
745
|
-
const size = JSON.stringify(ctx.value).length;
|
|
746
|
-
const kb = (size / 1024).toFixed(1);
|
|
747
|
-
return `Payload size ${kb}KB exceeds limit of ${maxKB}KB`;
|
|
748
|
-
}, "message"),
|
|
749
|
-
severity: "warning",
|
|
750
|
-
tags: [
|
|
751
|
-
"performance",
|
|
752
|
-
"data"
|
|
753
|
-
]
|
|
754
|
-
}), "maxPayloadSize"),
|
|
755
|
-
/**
|
|
756
|
-
* Prevents storing sensitive data
|
|
757
|
-
*/
|
|
758
|
-
noSensitiveData: /* @__PURE__ */ __name((sensitiveKeys = [
|
|
759
|
-
"password",
|
|
760
|
-
"token",
|
|
761
|
-
"secret",
|
|
762
|
-
"apiKey"
|
|
763
|
-
]) => ({
|
|
764
|
-
name: "no-sensitive-data",
|
|
765
|
-
description: "Prevents storing sensitive data",
|
|
766
|
-
test: /* @__PURE__ */ __name((ctx) => {
|
|
767
|
-
return !ctx.path.some((segment) => sensitiveKeys.some((key) => typeof segment === "string" && segment.toLowerCase().includes(key.toLowerCase())));
|
|
768
|
-
}, "test"),
|
|
769
|
-
message: /* @__PURE__ */ __name((ctx) => `Sensitive data detected in path: ${ctx.path.join(".")}`, "message"),
|
|
770
|
-
severity: "error",
|
|
771
|
-
tags: [
|
|
772
|
-
"security",
|
|
773
|
-
"data"
|
|
774
|
-
]
|
|
775
|
-
}), "noSensitiveData")
|
|
776
|
-
};
|
|
777
|
-
|
|
778
|
-
// src/factories/index.ts
|
|
779
4
|
function isGuardrailsConfig(value) {
|
|
780
|
-
return Boolean(value) && typeof value ===
|
|
5
|
+
return Boolean(value) && typeof value === 'object';
|
|
781
6
|
}
|
|
782
|
-
__name(isGuardrailsConfig, "isGuardrailsConfig");
|
|
783
7
|
function resolveGuardrailsConfig(guardrails) {
|
|
784
8
|
if (guardrails === false) {
|
|
785
|
-
return
|
|
9
|
+
return undefined;
|
|
786
10
|
}
|
|
787
11
|
if (isGuardrailsConfig(guardrails)) {
|
|
788
12
|
return guardrails;
|
|
@@ -801,11 +25,10 @@ function resolveGuardrailsConfig(guardrails) {
|
|
|
801
25
|
}
|
|
802
26
|
};
|
|
803
27
|
}
|
|
804
|
-
__name(resolveGuardrailsConfig, "resolveGuardrailsConfig");
|
|
805
28
|
function createFeatureTree(signalTree, initial, options) {
|
|
806
|
-
const env = options.env ?? process?.env?.[
|
|
807
|
-
const isDev = env ===
|
|
808
|
-
const isTest = env ===
|
|
29
|
+
const env = options.env ?? process?.env?.['NODE_ENV'] ?? 'production';
|
|
30
|
+
const isDev = env === 'development';
|
|
31
|
+
const isTest = env === 'test';
|
|
809
32
|
const enhancers = [];
|
|
810
33
|
if (isDev || isTest) {
|
|
811
34
|
const guardrailsConfig = resolveGuardrailsConfig(options.guardrails);
|
|
@@ -822,15 +45,13 @@ function createFeatureTree(signalTree, initial, options) {
|
|
|
822
45
|
}
|
|
823
46
|
return tree;
|
|
824
47
|
}
|
|
825
|
-
__name(createFeatureTree, "createFeatureTree");
|
|
826
48
|
function createAngularFeatureTree(signalTree, initial, options) {
|
|
827
49
|
const isDev = Boolean(ngDevMode);
|
|
828
50
|
return createFeatureTree(signalTree, initial, {
|
|
829
51
|
...options,
|
|
830
|
-
env: isDev ?
|
|
52
|
+
env: isDev ? 'development' : 'production'
|
|
831
53
|
});
|
|
832
54
|
}
|
|
833
|
-
__name(createAngularFeatureTree, "createAngularFeatureTree");
|
|
834
55
|
function createAppShellTree(signalTree, initial) {
|
|
835
56
|
return createFeatureTree(signalTree, initial, {
|
|
836
57
|
guardrails: {
|
|
@@ -841,13 +62,10 @@ function createAppShellTree(signalTree, initial) {
|
|
|
841
62
|
hotPaths: {
|
|
842
63
|
threshold: 5
|
|
843
64
|
},
|
|
844
|
-
customRules: [
|
|
845
|
-
rules.noDeepNesting(3)
|
|
846
|
-
]
|
|
65
|
+
customRules: [rules.noDeepNesting(3)]
|
|
847
66
|
}
|
|
848
67
|
});
|
|
849
68
|
}
|
|
850
|
-
__name(createAppShellTree, "createAppShellTree");
|
|
851
69
|
function createPerformanceTree(signalTree, initial, name) {
|
|
852
70
|
return createFeatureTree(signalTree, initial, {
|
|
853
71
|
guardrails: {
|
|
@@ -862,53 +80,40 @@ function createPerformanceTree(signalTree, initial, name) {
|
|
|
862
80
|
enabled: false
|
|
863
81
|
},
|
|
864
82
|
reporting: {
|
|
865
|
-
interval:
|
|
83
|
+
interval: 10000
|
|
866
84
|
}
|
|
867
85
|
}
|
|
868
86
|
});
|
|
869
87
|
}
|
|
870
|
-
__name(createPerformanceTree, "createPerformanceTree");
|
|
871
88
|
function createFormTree(signalTree, initial, formName) {
|
|
872
89
|
return createFeatureTree(signalTree, initial, {
|
|
873
90
|
guardrails: {
|
|
874
|
-
customRules: [
|
|
875
|
-
rules.noDeepNesting(4),
|
|
876
|
-
rules.maxPayloadSize(50),
|
|
877
|
-
rules.noSensitiveData()
|
|
878
|
-
]
|
|
91
|
+
customRules: [rules.noDeepNesting(4), rules.maxPayloadSize(50), rules.noSensitiveData()]
|
|
879
92
|
}
|
|
880
93
|
});
|
|
881
94
|
}
|
|
882
|
-
__name(createFormTree, "createFormTree");
|
|
883
95
|
function createCacheTree(signalTree, initial) {
|
|
884
96
|
return createFeatureTree(signalTree, initial, {
|
|
885
97
|
guardrails: {
|
|
886
|
-
mode:
|
|
98
|
+
mode: 'silent',
|
|
887
99
|
memoryLeaks: {
|
|
888
100
|
enabled: false
|
|
889
101
|
}
|
|
890
102
|
}});
|
|
891
103
|
}
|
|
892
|
-
__name(createCacheTree, "createCacheTree");
|
|
893
104
|
function createTestTree(signalTree, initial, overrides) {
|
|
894
105
|
return createFeatureTree(signalTree, initial, {
|
|
895
|
-
env:
|
|
106
|
+
env: 'test',
|
|
896
107
|
guardrails: {
|
|
897
|
-
mode:
|
|
108
|
+
mode: 'throw',
|
|
898
109
|
budgets: {
|
|
899
110
|
maxUpdateTime: 5,
|
|
900
111
|
maxRecomputations: 50
|
|
901
112
|
},
|
|
902
|
-
customRules: [
|
|
903
|
-
rules.noFunctionsInState(),
|
|
904
|
-
rules.noDeepNesting(4)
|
|
905
|
-
],
|
|
113
|
+
customRules: [rules.noFunctionsInState(), rules.noDeepNesting(4)],
|
|
906
114
|
...overrides
|
|
907
115
|
}
|
|
908
116
|
});
|
|
909
117
|
}
|
|
910
|
-
__name(createTestTree, "createTestTree");
|
|
911
118
|
|
|
912
119
|
export { createAngularFeatureTree, createAppShellTree, createCacheTree, createFeatureTree, createFormTree, createPerformanceTree, createTestTree };
|
|
913
|
-
//# sourceMappingURL=index.js.map
|
|
914
|
-
//# sourceMappingURL=index.js.map
|