@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.
@@ -1,788 +1,12 @@
1
- var __defProp = Object.defineProperty;
2
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
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 === "object";
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 void 0;
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?.["NODE_ENV"] ?? "production";
807
- const isDev = env === "development";
808
- const isTest = env === "test";
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 ? "development" : "production"
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: 1e4
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: "silent",
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: "test",
106
+ env: 'test',
896
107
  guardrails: {
897
- mode: "throw",
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