@v-ibe/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +40 -0
- package/dist/DI/__tests__/scoped-container-dependencies.test.d.ts +1 -0
- package/dist/DI/bootstrap.d.ts +18 -0
- package/dist/DI/decorators/inject.d.ts +37 -0
- package/dist/DI/decorators/inject.js +45 -0
- package/dist/DI/decorators/service.d.ts +24 -0
- package/dist/DI/decorators/service.js +13 -0
- package/dist/DI/di-container.d.ts +53 -0
- package/dist/DI/di-container.js +158 -0
- package/dist/DI/lifecycle.d.ts +37 -0
- package/dist/DI/lifecycle.js +6 -0
- package/dist/DI/scoped-container.d.ts +68 -0
- package/dist/DI/scoped-container.js +193 -0
- package/dist/DI/service-metadata.d.ts +32 -0
- package/dist/DI/service-metadata.js +31 -0
- package/dist/DI/types.d.ts +4 -0
- package/dist/behaviors/__tests__/behavior-system.test.d.ts +1 -0
- package/dist/behaviors/behavior-manager.d.ts +60 -0
- package/dist/behaviors/behavior-manager.js +131 -0
- package/dist/behaviors/behavior-registry.d.ts +68 -0
- package/dist/behaviors/behavior-registry.js +105 -0
- package/dist/behaviors/constants.d.ts +16 -0
- package/dist/behaviors/constants.js +8 -0
- package/dist/behaviors/decorators.d.ts +87 -0
- package/dist/behaviors/decorators.js +46 -0
- package/dist/behaviors/index.d.ts +4 -0
- package/dist/components/__tests__/host.test.d.ts +1 -0
- package/dist/components/app-tree.d.ts +49 -0
- package/dist/components/app-tree.js +122 -0
- package/dist/components/base-component.d.ts +85 -0
- package/dist/components/base-component.js +438 -0
- package/dist/components/decorators/component.d.ts +27 -0
- package/dist/components/decorators/component.js +47 -0
- package/dist/components/decorators/prop.d.ts +14 -0
- package/dist/components/decorators/prop.js +37 -0
- package/dist/components/types.d.ts +26 -0
- package/dist/core.d.ts +23 -0
- package/dist/core.js +8 -0
- package/dist/custom-components/__tests__/for.test.d.ts +1 -0
- package/dist/custom-components/__tests__/show.test.d.ts +1 -0
- package/dist/custom-components/for.d.ts +58 -0
- package/dist/custom-components/for.js +313 -0
- package/dist/custom-components/index.d.ts +2 -0
- package/dist/custom-components/show.d.ts +78 -0
- package/dist/custom-components/show.js +88 -0
- package/dist/data-management/cache/cache-invalidate.decorator.d.ts +35 -0
- package/dist/data-management/cache/cache-invalidate.decorator.js +21 -0
- package/dist/data-management/cache/cache-metadata.d.ts +15 -0
- package/dist/data-management/cache/cache-provider.interface.d.ts +67 -0
- package/dist/data-management/cache/cache-tags.decorator.d.ts +52 -0
- package/dist/data-management/cache/cache-tags.decorator.js +13 -0
- package/dist/data-management/cache/cache-update.decorator.d.ts +28 -0
- package/dist/data-management/cache/cache-update.decorator.js +21 -0
- package/dist/data-management/cache/cache.decorator.d.ts +28 -0
- package/dist/data-management/cache/cache.decorator.js +13 -0
- package/dist/data-management/cache/index.d.ts +11 -0
- package/dist/data-management/cache/local-storage-cache.d.ts +40 -0
- package/dist/data-management/cache/local-storage-cache.js +268 -0
- package/dist/data-management/cache/memory-cache.d.ts +37 -0
- package/dist/data-management/cache/memory-cache.js +149 -0
- package/dist/data-management/cache/session-storage-cache.d.ts +35 -0
- package/dist/data-management/cache/session-storage-cache.js +242 -0
- package/dist/data-management/cache/ttl.decorator.d.ts +31 -0
- package/dist/data-management/cache/ttl.decorator.js +34 -0
- package/dist/data-management/decorators/consume.d.ts +29 -0
- package/dist/data-management/decorators/consume.js +28 -0
- package/dist/data-management/decorators/id.d.ts +28 -0
- package/dist/data-management/decorators/id.js +19 -0
- package/dist/data-management/decorators/model.d.ts +48 -0
- package/dist/data-management/decorators/model.js +24 -0
- package/dist/data-management/decorators/prop.d.ts +43 -0
- package/dist/data-management/decorators/prop.js +32 -0
- package/dist/data-management/index.d.ts +13 -0
- package/dist/data-management/store/json-to-model.d.ts +45 -0
- package/dist/data-management/store/json-to-model.js +36 -0
- package/dist/data-management/store/store.d.ts +108 -0
- package/dist/data-management/store/store.js +207 -0
- package/dist/data-management/store/types.d.ts +53 -0
- package/dist/events-handler/decorators/emit.d.ts +29 -0
- package/dist/events-handler/decorators/emit.js +51 -0
- package/dist/events-handler/event-decorators.d.ts +1 -0
- package/dist/events-handler/event-emitter.service.d.ts +21 -0
- package/dist/events-handler/event-emitter.service.js +85 -0
- package/dist/events-handler/event-types.d.ts +12 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +121 -0
- package/dist/jsx/dynamic/__tests__/granular-array-renderer.test.d.ts +1 -0
- package/dist/jsx/dynamic/__tests__/jsx-array-rendering.test.d.ts +1 -0
- package/dist/jsx/dynamic/array-renderer.d.ts +2 -0
- package/dist/jsx/dynamic/array-renderer.js +133 -0
- package/dist/jsx/dynamic/child-renderer.d.ts +1 -0
- package/dist/jsx/dynamic/child-renderer.js +180 -0
- package/dist/jsx/dynamic/dom-utils.d.ts +5 -0
- package/dist/jsx/dynamic/dom-utils.js +22 -0
- package/dist/jsx/dynamic/granular-array-renderer.d.ts +16 -0
- package/dist/jsx/dynamic/granular-array-renderer.js +153 -0
- package/dist/jsx/dynamic/node-renderer.d.ts +2 -0
- package/dist/jsx/dynamic/props-handler.d.ts +3 -0
- package/dist/jsx/dynamic/props-handler.js +281 -0
- package/dist/jsx/dynamic/text-renderer.d.ts +2 -0
- package/dist/jsx/jsx-dev-runtime.d.ts +2 -0
- package/dist/jsx/jsx-runtime.d.ts +3 -0
- package/dist/jsx/types.d.ts +35 -0
- package/dist/jsx/types.js +4 -0
- package/dist/jsx-dev-runtime.d.ts +2 -0
- package/dist/jsx-dev-runtime.js +8 -0
- package/dist/jsx-runtime.d.ts +2 -0
- package/dist/jsx-runtime.js +11 -0
- package/dist/reactivity/__tests__/context-stack.test.d.ts +1 -0
- package/dist/reactivity/__tests__/nested-effects-untrack.test.d.ts +22 -0
- package/dist/reactivity/context-scope.d.ts +57 -0
- package/dist/reactivity/context-scope.js +35 -0
- package/dist/reactivity/decorators/__tests__/ctx-integration.test.d.ts +5 -0
- package/dist/reactivity/decorators/__tests__/ctx-loop.test.d.ts +10 -0
- package/dist/reactivity/decorators/__tests__/state-intelligent.test.d.ts +1 -0
- package/dist/reactivity/decorators/computed.d.ts +6 -0
- package/dist/reactivity/decorators/computed.js +17 -0
- package/dist/reactivity/decorators/create-event-decorator.d.ts +5 -0
- package/dist/reactivity/decorators/create-event-decorator.js +28 -0
- package/dist/reactivity/decorators/ctx.d.ts +9 -0
- package/dist/reactivity/decorators/ctx.js +91 -0
- package/dist/reactivity/decorators/effect.d.ts +9 -0
- package/dist/reactivity/decorators/effect.js +24 -0
- package/dist/reactivity/decorators/resource.d.ts +48 -0
- package/dist/reactivity/decorators/resource.js +20 -0
- package/dist/reactivity/decorators/state.d.ts +8 -0
- package/dist/reactivity/decorators/state.js +68 -0
- package/dist/reactivity/decorators/store.d.ts +6 -0
- package/dist/reactivity/decorators/store.js +25 -0
- package/dist/reactivity/phase-scheduler.d.ts +81 -0
- package/dist/reactivity/phase-scheduler.js +88 -0
- package/dist/reactivity/phase-scheduler.test.d.ts +1 -0
- package/dist/reactivity/reactive-cache.d.ts +21 -0
- package/dist/reactivity/reactive-cache.js +31 -0
- package/dist/reactivity/reactive-cache.test.d.ts +1 -0
- package/dist/reactivity/reactive-context.d.ts +152 -0
- package/dist/reactivity/reactive-context.js +184 -0
- package/dist/reactivity/signals/__tests__/composicion-automatica.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/composite/nivel-1-estructura-basica.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/composite/nivel-2-registro-subscribers.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/composite/nivel-3-notificaciones-basicas.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/composite/nivel-4-comparacion-valores.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/composite/nivel-5-tracking-automatico.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/composite/nivel-6-anti-glitch.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/composite/nivel-7-objetos-anidados.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/composite/nivel-8-observable-array-support.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/composite-shallow-tracking.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/effect.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/reactive-array/nivel-1-estructura-basica.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/reactive-array/nivel-2-metodos-mutadores.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/reactive-array/nivel-3-tracking-por-indice.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/reactive-array/nivel-4-tracking-length.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/reactive-array/nivel-5-tracking-mutation.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/reactive-array/nivel-6-metodos-no-mutadores.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/reactive-array/nivel-7-composicion-bidireccional.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/reactive-array/nivel-8-proxies.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/reactive-array/nivel-9-derived-cache-optimization.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/resource.test.d.ts +1 -0
- package/dist/reactivity/signals/__tests__/signal.test.d.ts +1 -0
- package/dist/reactivity/signals/array-strategies.d.ts +120 -0
- package/dist/reactivity/signals/array-strategies.js +261 -0
- package/dist/reactivity/signals/composite.d.ts +89 -0
- package/dist/reactivity/signals/composite.js +145 -0
- package/dist/reactivity/signals/computed.d.ts +61 -0
- package/dist/reactivity/signals/computed.js +107 -0
- package/dist/reactivity/signals/computed.test.d.ts +1 -0
- package/dist/reactivity/signals/derived.d.ts +10 -0
- package/dist/reactivity/signals/derived.js +24 -0
- package/dist/reactivity/signals/effect.d.ts +27 -0
- package/dist/reactivity/signals/effect.js +46 -0
- package/dist/reactivity/signals/event.d.ts +9 -0
- package/dist/reactivity/signals/event.js +15 -0
- package/dist/reactivity/signals/reactive-array.d.ts +133 -0
- package/dist/reactivity/signals/reactive-array.js +490 -0
- package/dist/reactivity/signals/reactive-proxy.d.ts +54 -0
- package/dist/reactivity/signals/reactive-proxy.js +299 -0
- package/dist/reactivity/signals/reactive-tracking.test.d.ts +1 -0
- package/dist/reactivity/signals/resource.d.ts +9 -0
- package/dist/reactivity/signals/resource.js +58 -0
- package/dist/reactivity/signals/signal.d.ts +39 -0
- package/dist/reactivity/signals/signal.js +56 -0
- package/dist/reactivity/signals/subscription-management.test.d.ts +1 -0
- package/dist/reactivity/types.d.ts +12 -0
- package/dist/router/__tests__/link-behavior-active-class.test.d.ts +1 -0
- package/dist/router/__tests__/loop-detector.test.d.ts +1 -0
- package/dist/router/__tests__/params-container-resolution.test.d.ts +1 -0
- package/dist/router/__tests__/router-generated-routes.test.d.ts +1 -0
- package/dist/router/__tests__/router-params-granular.test.d.ts +1 -0
- package/dist/router/__tests__/router-params-simple.test.d.ts +1 -0
- package/dist/router/__tests__/router-query-params.test.d.ts +1 -0
- package/dist/router/__tests__/router-route-candidates.test.d.ts +1 -0
- package/dist/router/__tests__/routeview-app-articles.test.d.ts +1 -0
- package/dist/router/__tests__/routeview-debug.test.d.ts +1 -0
- package/dist/router/__tests__/routeview-integration.test.d.ts +1 -0
- package/dist/router/__tests__/routeview-this.test.d.ts +1 -0
- package/dist/router/decorators/base-policy.d.ts +141 -0
- package/dist/router/decorators/base-policy.js +63 -0
- package/dist/router/decorators/index.d.ts +6 -0
- package/dist/router/decorators/params.d.ts +31 -0
- package/dist/router/decorators/params.js +97 -0
- package/dist/router/decorators/route-metadata.d.ts +11 -0
- package/dist/router/decorators/route-metadata.js +23 -0
- package/dist/router/decorators/route.d.ts +39 -0
- package/dist/router/decorators/route.js +7 -0
- package/dist/router/link.behavior.d.ts +87 -0
- package/dist/router/link.behavior.js +227 -0
- package/dist/router/policy-evaluator.d.ts +81 -0
- package/dist/router/policy-evaluator.js +209 -0
- package/dist/router/route-view.d.ts +56 -0
- package/dist/router/route-view.js +156 -0
- package/dist/router/router.d.ts +67 -0
- package/dist/router/router.js +308 -0
- package/dist/router/static-analysis/index.d.ts +37 -0
- package/dist/router/static-analysis/parser.d.ts +14 -0
- package/dist/router/static-analysis/parser.js +147 -0
- package/dist/router/static-analysis/scanner.d.ts +27 -0
- package/dist/router/static-analysis/scanner.js +91 -0
- package/dist/router/trie.d.ts +14 -0
- package/dist/router/trie.js +126 -0
- package/dist/router/trie.types.d.ts +36 -0
- package/dist/styles/base-style-sheet.d.ts +96 -0
- package/dist/styles/base-style-sheet.js +149 -0
- package/dist/styles/decorators/factories.d.ts +76 -0
- package/dist/styles/decorators/factories.js +11 -0
- package/dist/styles/decorators/keyframes.d.ts +238 -0
- package/dist/styles/decorators/keyframes.js +79 -0
- package/dist/styles/decorators/rule.d.ts +177 -0
- package/dist/styles/decorators/rule.js +72 -0
- package/dist/styles/decorators/scope.d.ts +66 -0
- package/dist/styles/decorators/scope.js +17 -0
- package/dist/styles/decorators/style.d.ts +1 -0
- package/dist/styles/decorators/style.js +20 -0
- package/dist/styles/decorators/useStyles.d.ts +5 -0
- package/dist/styles/decorators/useStyles.js +29 -0
- package/dist/styles/global-styles-registry.d.ts +72 -0
- package/dist/styles/global-styles-registry.js +155 -0
- package/dist/types.d.ts +1 -0
- package/dist/vite-plugins/__tests__/jsx-control-flow-transform.test.d.ts +1 -0
- package/dist/vite-plugins/index.d.ts +4 -0
- package/dist/vite-plugins/index.js +10 -0
- package/dist/vite-plugins/jsx-contextual.d.ts +7 -0
- package/dist/vite-plugins/jsx-contextual.js +53 -0
- package/dist/vite-plugins/jsx-control-flow-transform.d.ts +60 -0
- package/dist/vite-plugins/jsx-control-flow-transform.js +180 -0
- package/dist/vite-plugins/jsx-signals.d.ts +2 -0
- package/dist/vite-plugins/jsx-signals.js +124 -0
- package/dist/vite-plugins/router/route-generator-plugin.d.ts +63 -0
- package/dist/vite-plugins/router/route-generator-plugin.js +310 -0
- package/package.json +85 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { effect } from "../reactivity/signals/effect.js";
|
|
2
|
+
import { ReactiveArray } from "../reactivity/signals/reactive-array.js";
|
|
3
|
+
import { CompositeSignal } from "../reactivity/signals/composite.js";
|
|
4
|
+
function For(props) {
|
|
5
|
+
const container = document.createDocumentFragment();
|
|
6
|
+
const mainAnchor = document.createComment("for-list");
|
|
7
|
+
container.appendChild(mainAnchor);
|
|
8
|
+
const arrayState = {
|
|
9
|
+
keyMap: /* @__PURE__ */ new Map(),
|
|
10
|
+
nodeOrder: [],
|
|
11
|
+
reactiveEntries: /* @__PURE__ */ new Map()
|
|
12
|
+
};
|
|
13
|
+
const effectCleanups = /* @__PURE__ */ new Map();
|
|
14
|
+
const reactiveArray = unwrapReactiveArray(props.each);
|
|
15
|
+
if (reactiveArray) {
|
|
16
|
+
renderGranular(
|
|
17
|
+
mainAnchor,
|
|
18
|
+
reactiveArray,
|
|
19
|
+
props.children,
|
|
20
|
+
props.getKey,
|
|
21
|
+
props.fallback,
|
|
22
|
+
arrayState,
|
|
23
|
+
effectCleanups
|
|
24
|
+
);
|
|
25
|
+
} else {
|
|
26
|
+
renderStandard(
|
|
27
|
+
mainAnchor,
|
|
28
|
+
props.each,
|
|
29
|
+
props.children,
|
|
30
|
+
props.getKey,
|
|
31
|
+
props.fallback,
|
|
32
|
+
arrayState,
|
|
33
|
+
effectCleanups
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return container;
|
|
37
|
+
}
|
|
38
|
+
function renderGranular(mainAnchor, reactiveArray, renderFn, getKey, fallback, arrayState, effectCleanups) {
|
|
39
|
+
console.log("[For] Using GRANULAR rendering (ReactiveArray detected)");
|
|
40
|
+
effect(() => {
|
|
41
|
+
const currentLength = reactiveArray.length;
|
|
42
|
+
console.log(`[For] Structural effect - length: ${currentLength}`);
|
|
43
|
+
if (currentLength === 0 && fallback) {
|
|
44
|
+
renderFallback(mainAnchor, arrayState, effectCleanups, fallback);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (currentLength > 0 && arrayState.nodeOrder.includes("__fallback__")) {
|
|
48
|
+
removeKey(arrayState, effectCleanups, "__fallback__");
|
|
49
|
+
}
|
|
50
|
+
const newNodeOrder = [];
|
|
51
|
+
const existingKeys = new Set(arrayState.nodeOrder);
|
|
52
|
+
for (let i = 0; i < currentLength; i++) {
|
|
53
|
+
const key = `__index_${i}`;
|
|
54
|
+
newNodeOrder.push(key);
|
|
55
|
+
if (!existingKeys.has(key)) {
|
|
56
|
+
createItemEffect(
|
|
57
|
+
mainAnchor,
|
|
58
|
+
arrayState,
|
|
59
|
+
effectCleanups,
|
|
60
|
+
key,
|
|
61
|
+
i,
|
|
62
|
+
reactiveArray,
|
|
63
|
+
renderFn,
|
|
64
|
+
getKey
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const oldKey of existingKeys) {
|
|
69
|
+
if (!newNodeOrder.includes(oldKey)) {
|
|
70
|
+
removeKey(arrayState, effectCleanups, oldKey);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
reorderNodes(mainAnchor, arrayState.keyMap, newNodeOrder);
|
|
74
|
+
arrayState.nodeOrder = newNodeOrder;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function renderStandard(mainAnchor, arraySource, renderFn, getKey, fallback, arrayState, effectCleanups) {
|
|
78
|
+
console.log("[For] Using STANDARD rendering (non-ReactiveArray)");
|
|
79
|
+
effect(() => {
|
|
80
|
+
const items = resolveArray(arraySource);
|
|
81
|
+
console.log(`[For] Standard effect - ${items.length} items`);
|
|
82
|
+
if (items.length === 0 && fallback) {
|
|
83
|
+
renderFallback(mainAnchor, arrayState, effectCleanups, fallback);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (items.length > 0 && arrayState.nodeOrder.includes("__fallback__")) {
|
|
87
|
+
removeKey(arrayState, effectCleanups, "__fallback__");
|
|
88
|
+
}
|
|
89
|
+
const newNodeOrder = [];
|
|
90
|
+
const usedNodes = /* @__PURE__ */ new Set();
|
|
91
|
+
items.forEach((item, index) => {
|
|
92
|
+
const key = extractKey(item, index, getKey);
|
|
93
|
+
newNodeOrder.push(key);
|
|
94
|
+
const renderedElement = renderFn(item, index);
|
|
95
|
+
let node = reuseOrCreateNode(
|
|
96
|
+
arrayState.keyMap,
|
|
97
|
+
key,
|
|
98
|
+
renderedElement
|
|
99
|
+
);
|
|
100
|
+
usedNodes.add(node);
|
|
101
|
+
});
|
|
102
|
+
removeUnusedNodes(arrayState.keyMap, usedNodes);
|
|
103
|
+
reorderNodes(mainAnchor, arrayState.keyMap, newNodeOrder);
|
|
104
|
+
arrayState.nodeOrder = newNodeOrder;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function createItemEffect(mainAnchor, arrayState, effectCleanups, key, index, reactiveArray, renderFn, getKey) {
|
|
108
|
+
console.log(`[For] Creating item effect for index ${index}`);
|
|
109
|
+
const anchor = document.createComment(`for-item-${key}`);
|
|
110
|
+
mainAnchor.parentNode?.appendChild(anchor);
|
|
111
|
+
const entry = {
|
|
112
|
+
anchor,
|
|
113
|
+
currentNode: null,
|
|
114
|
+
effectHandle: null
|
|
115
|
+
};
|
|
116
|
+
arrayState.reactiveEntries.set(key, entry);
|
|
117
|
+
const effectHandle = effect(() => {
|
|
118
|
+
console.log(`[For] Item effect running for index ${index}`);
|
|
119
|
+
let item = reactiveArray.at(index);
|
|
120
|
+
if (item && typeof item === "object" && item.isSignal) {
|
|
121
|
+
if (item instanceof CompositeSignal) {
|
|
122
|
+
item = item.getPlainValue();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (item == null || typeof item === "boolean") {
|
|
126
|
+
if (entry.currentNode) {
|
|
127
|
+
entry.currentNode.parentNode?.removeChild(entry.currentNode);
|
|
128
|
+
entry.currentNode = null;
|
|
129
|
+
arrayState.keyMap.set(key, anchor);
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const renderedElement = renderFn(item, index);
|
|
134
|
+
const newNode = elementToNode(renderedElement);
|
|
135
|
+
if (entry.currentNode !== newNode) {
|
|
136
|
+
replaceNode(anchor, entry.currentNode, newNode);
|
|
137
|
+
entry.currentNode = newNode;
|
|
138
|
+
arrayState.keyMap.set(key, newNode);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
entry.effectHandle = effectHandle;
|
|
142
|
+
effectCleanups.set(key, effectHandle.dispose);
|
|
143
|
+
}
|
|
144
|
+
function renderFallback(mainAnchor, arrayState, effectCleanups, fallback) {
|
|
145
|
+
if (arrayState.nodeOrder.includes("__fallback__")) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
for (const key of arrayState.nodeOrder) {
|
|
149
|
+
removeKey(arrayState, effectCleanups, key);
|
|
150
|
+
}
|
|
151
|
+
const fallbackNode = elementToNode(fallback);
|
|
152
|
+
mainAnchor.parentNode?.insertBefore(fallbackNode, mainAnchor.nextSibling);
|
|
153
|
+
arrayState.keyMap.set("__fallback__", fallbackNode);
|
|
154
|
+
arrayState.nodeOrder = ["__fallback__"];
|
|
155
|
+
}
|
|
156
|
+
function removeKey(arrayState, effectCleanups, key) {
|
|
157
|
+
console.log(`[For] Removing key: ${key}`);
|
|
158
|
+
const cleanup = effectCleanups.get(key);
|
|
159
|
+
if (cleanup) {
|
|
160
|
+
cleanup();
|
|
161
|
+
effectCleanups.delete(key);
|
|
162
|
+
}
|
|
163
|
+
const entry = arrayState.reactiveEntries.get(key);
|
|
164
|
+
if (entry) {
|
|
165
|
+
if (entry.currentNode) {
|
|
166
|
+
entry.currentNode.parentNode?.removeChild(entry.currentNode);
|
|
167
|
+
}
|
|
168
|
+
if (entry.anchor && entry.anchor.parentNode) {
|
|
169
|
+
entry.anchor.parentNode.removeChild(entry.anchor);
|
|
170
|
+
}
|
|
171
|
+
arrayState.reactiveEntries.delete(key);
|
|
172
|
+
}
|
|
173
|
+
const node = arrayState.keyMap.get(key);
|
|
174
|
+
if (node && node.parentNode) {
|
|
175
|
+
node.parentNode.removeChild(node);
|
|
176
|
+
}
|
|
177
|
+
arrayState.keyMap.delete(key);
|
|
178
|
+
}
|
|
179
|
+
function reorderNodes(anchor, keyMap, newOrder) {
|
|
180
|
+
let previousNode = anchor;
|
|
181
|
+
newOrder.forEach((key) => {
|
|
182
|
+
const node = keyMap.get(key);
|
|
183
|
+
if (!node) return;
|
|
184
|
+
if (!node.parentNode) {
|
|
185
|
+
anchor.parentNode?.insertBefore(node, previousNode.nextSibling);
|
|
186
|
+
} else if (previousNode.nextSibling !== node) {
|
|
187
|
+
previousNode.parentNode?.insertBefore(node, previousNode.nextSibling);
|
|
188
|
+
}
|
|
189
|
+
previousNode = node;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function unwrapReactiveArray(source) {
|
|
193
|
+
if (source instanceof ReactiveArray) {
|
|
194
|
+
return source;
|
|
195
|
+
}
|
|
196
|
+
if (typeof source === "function") {
|
|
197
|
+
const value = source();
|
|
198
|
+
return unwrapReactiveArray(value);
|
|
199
|
+
}
|
|
200
|
+
if (source && typeof source === "object") {
|
|
201
|
+
const getReactive = source.__getReactiveArray;
|
|
202
|
+
if (getReactive instanceof ReactiveArray) {
|
|
203
|
+
return getReactive;
|
|
204
|
+
}
|
|
205
|
+
const internal = source.__reactiveArray__;
|
|
206
|
+
if (internal instanceof ReactiveArray) {
|
|
207
|
+
return internal;
|
|
208
|
+
}
|
|
209
|
+
const symbolKey = Symbol.for("reactiveArray");
|
|
210
|
+
const viaSymbol = source[symbolKey];
|
|
211
|
+
if (viaSymbol instanceof ReactiveArray) {
|
|
212
|
+
return viaSymbol;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (source && typeof source === "object" && source.isSignal) {
|
|
216
|
+
const value = source.get();
|
|
217
|
+
return unwrapReactiveArray(value);
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
function resolveArray(source) {
|
|
222
|
+
if (source && typeof source === "object" && source.isSignal) {
|
|
223
|
+
const value = source.get();
|
|
224
|
+
return Array.isArray(value) ? value : [];
|
|
225
|
+
}
|
|
226
|
+
if (typeof source === "function") {
|
|
227
|
+
const value = source();
|
|
228
|
+
return Array.isArray(value) ? value : [];
|
|
229
|
+
}
|
|
230
|
+
if (Array.isArray(source)) {
|
|
231
|
+
return source;
|
|
232
|
+
}
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
function extractKey(item, index, getKey) {
|
|
236
|
+
if (getKey) {
|
|
237
|
+
return String(getKey(item, index));
|
|
238
|
+
}
|
|
239
|
+
if (item && typeof item === "object") {
|
|
240
|
+
const obj = item;
|
|
241
|
+
if (obj.id != null) return String(obj.id);
|
|
242
|
+
if (obj.key != null) return String(obj.key);
|
|
243
|
+
if (obj._id != null) return String(obj._id);
|
|
244
|
+
if (obj.uuid != null) return String(obj.uuid);
|
|
245
|
+
}
|
|
246
|
+
return `__index_${index}`;
|
|
247
|
+
}
|
|
248
|
+
function reuseOrCreateNode(keyMap, key, element) {
|
|
249
|
+
const existingNode = keyMap.get(key);
|
|
250
|
+
const newNode = elementToNode(element);
|
|
251
|
+
if (existingNode && existingNode.nodeName === newNode.nodeName) {
|
|
252
|
+
if (existingNode.nodeType === Node.TEXT_NODE && newNode.nodeType === Node.TEXT_NODE) {
|
|
253
|
+
existingNode.textContent = newNode.textContent;
|
|
254
|
+
return existingNode;
|
|
255
|
+
}
|
|
256
|
+
existingNode.parentNode?.replaceChild(newNode, existingNode);
|
|
257
|
+
keyMap.set(key, newNode);
|
|
258
|
+
return newNode;
|
|
259
|
+
}
|
|
260
|
+
keyMap.set(key, newNode);
|
|
261
|
+
return newNode;
|
|
262
|
+
}
|
|
263
|
+
function removeUnusedNodes(keyMap, usedNodes) {
|
|
264
|
+
for (const [key, node] of keyMap.entries()) {
|
|
265
|
+
if (!usedNodes.has(node)) {
|
|
266
|
+
node.parentNode?.removeChild(node);
|
|
267
|
+
keyMap.delete(key);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function elementToNode(element) {
|
|
272
|
+
if (element == null || typeof element === "boolean") {
|
|
273
|
+
return document.createTextNode("");
|
|
274
|
+
}
|
|
275
|
+
if (typeof element === "string" || typeof element === "number") {
|
|
276
|
+
return document.createTextNode(String(element));
|
|
277
|
+
}
|
|
278
|
+
if (element instanceof Node) {
|
|
279
|
+
return element;
|
|
280
|
+
}
|
|
281
|
+
if (typeof element === "function") {
|
|
282
|
+
const result = element();
|
|
283
|
+
return elementToNode(result);
|
|
284
|
+
}
|
|
285
|
+
if (element instanceof DocumentFragment) {
|
|
286
|
+
if (element.childNodes.length === 1) {
|
|
287
|
+
const child = element.firstChild;
|
|
288
|
+
element.removeChild(child);
|
|
289
|
+
return child;
|
|
290
|
+
}
|
|
291
|
+
const wrapper = document.createElement("span");
|
|
292
|
+
wrapper.style.display = "contents";
|
|
293
|
+
while (element.firstChild) {
|
|
294
|
+
wrapper.appendChild(element.firstChild);
|
|
295
|
+
}
|
|
296
|
+
return wrapper;
|
|
297
|
+
}
|
|
298
|
+
if (element && typeof element === "object") {
|
|
299
|
+
console.warn("[For] Received unknown object type:", element);
|
|
300
|
+
return document.createTextNode(JSON.stringify(element));
|
|
301
|
+
}
|
|
302
|
+
return document.createTextNode("");
|
|
303
|
+
}
|
|
304
|
+
function replaceNode(anchor, oldNode, newNode) {
|
|
305
|
+
if (oldNode) {
|
|
306
|
+
anchor.parentNode?.replaceChild(newNode, oldNode);
|
|
307
|
+
} else {
|
|
308
|
+
anchor.parentNode?.insertBefore(newNode, anchor.nextSibling);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
export {
|
|
312
|
+
For
|
|
313
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHOW COMPONENT - Conditional Rendering with Untracked Children
|
|
3
|
+
*
|
|
4
|
+
* Este componente separa la evaluación de la condición del rendering del contenido,
|
|
5
|
+
* evitando loops infinitos cuando el hijo crea dependencias reactivas que invalidarían
|
|
6
|
+
* al padre.
|
|
7
|
+
*
|
|
8
|
+
* Características clave:
|
|
9
|
+
* - La condición `when` se evalúa CON tracking
|
|
10
|
+
* - El contenido `children` se ejecuta SIN tracking (untrack)
|
|
11
|
+
* - Previene loops infinitos en casos como RouteView anidados
|
|
12
|
+
* - Soporta fallback para el caso false
|
|
13
|
+
*
|
|
14
|
+
* Transformación JSX:
|
|
15
|
+
* <Show when={() => condition}>{() => content}</Show>
|
|
16
|
+
*
|
|
17
|
+
* Compila a:
|
|
18
|
+
* Show({ when: () => condition, children: () => content })
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Props del componente Show
|
|
22
|
+
*/
|
|
23
|
+
export interface ShowProps {
|
|
24
|
+
/**
|
|
25
|
+
* Condición reactiva a evaluar.
|
|
26
|
+
* Debe ser una función que retorne boolean.
|
|
27
|
+
* Esta función SE TRACKEA - cambios en sus dependencias re-evalúan el Show.
|
|
28
|
+
*/
|
|
29
|
+
when: () => boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Contenido a renderizar cuando la condición es true.
|
|
32
|
+
* Debe ser una función que retorne el contenido.
|
|
33
|
+
* Esta función NO SE TRACKEA - previene loops infinitos.
|
|
34
|
+
*/
|
|
35
|
+
children: () => any;
|
|
36
|
+
/**
|
|
37
|
+
* Contenido a mostrar cuando la condición es false.
|
|
38
|
+
* Opcional. También se ejecuta sin tracking.
|
|
39
|
+
*/
|
|
40
|
+
fallback?: () => any;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Show - Helper para rendering condicional sin loops
|
|
44
|
+
*
|
|
45
|
+
* @example Prevenir loop en RouteView
|
|
46
|
+
* ```tsx
|
|
47
|
+
* @Component
|
|
48
|
+
* class RouteView extends BaseComponent {
|
|
49
|
+
* @Resource(...)
|
|
50
|
+
* componentClass!: IResource<any>;
|
|
51
|
+
*
|
|
52
|
+
* view() {
|
|
53
|
+
* return (
|
|
54
|
+
* <>
|
|
55
|
+
* <Show when={() => this.componentClass.state === 'pending'}>
|
|
56
|
+
* {() => <div>Loading...</div>}
|
|
57
|
+
* </Show>
|
|
58
|
+
*
|
|
59
|
+
* <Show when={() => this.componentClass.state === 'ready'}>
|
|
60
|
+
* {() => this.jsx(this.componentClass.get(), {})}
|
|
61
|
+
* </Show>
|
|
62
|
+
* </>
|
|
63
|
+
* );
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example Con fallback
|
|
69
|
+
* ```tsx
|
|
70
|
+
* <Show
|
|
71
|
+
* when={() => this.user.isLoggedIn}
|
|
72
|
+
* fallback={() => <LoginButton />}
|
|
73
|
+
* >
|
|
74
|
+
* {() => <UserProfile user={this.user} />}
|
|
75
|
+
* </Show>
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare function Show(props: ShowProps): DocumentFragment;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { effect } from "../reactivity/signals/effect.js";
|
|
2
|
+
import { reactiveContext } from "../reactivity/reactive-context.js";
|
|
3
|
+
function Show(props) {
|
|
4
|
+
const container = document.createDocumentFragment();
|
|
5
|
+
const anchor = document.createComment("show");
|
|
6
|
+
container.appendChild(anchor);
|
|
7
|
+
let currentNode = null;
|
|
8
|
+
let currentBranch = "empty";
|
|
9
|
+
effect(() => {
|
|
10
|
+
if (!anchor.parentNode) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const condition = Boolean(props.when());
|
|
14
|
+
const shouldShowTruthy = condition;
|
|
15
|
+
const shouldShowFalsy = !shouldShowTruthy && props.fallback;
|
|
16
|
+
if (shouldShowTruthy) {
|
|
17
|
+
if (currentBranch === "truthy") {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (currentNode) {
|
|
21
|
+
anchor.parentNode?.removeChild(currentNode);
|
|
22
|
+
}
|
|
23
|
+
const content = reactiveContext.untrack(() => props.children());
|
|
24
|
+
const newNode = elementToNode(content);
|
|
25
|
+
anchor.parentNode?.insertBefore(newNode, anchor.nextSibling);
|
|
26
|
+
currentNode = newNode;
|
|
27
|
+
currentBranch = "truthy";
|
|
28
|
+
} else if (shouldShowFalsy) {
|
|
29
|
+
if (currentBranch === "falsy") {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (currentNode) {
|
|
33
|
+
anchor.parentNode?.removeChild(currentNode);
|
|
34
|
+
}
|
|
35
|
+
const content = reactiveContext.untrack(() => props.fallback());
|
|
36
|
+
const newNode = elementToNode(content);
|
|
37
|
+
anchor.parentNode?.insertBefore(newNode, anchor.nextSibling);
|
|
38
|
+
currentNode = newNode;
|
|
39
|
+
currentBranch = "falsy";
|
|
40
|
+
} else {
|
|
41
|
+
if (currentBranch === "empty") {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (currentNode) {
|
|
45
|
+
currentNode.parentNode?.removeChild(currentNode);
|
|
46
|
+
currentNode = null;
|
|
47
|
+
}
|
|
48
|
+
currentBranch = "empty";
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return container;
|
|
52
|
+
}
|
|
53
|
+
function elementToNode(element) {
|
|
54
|
+
if (element == null || typeof element === "boolean") {
|
|
55
|
+
return document.createTextNode("");
|
|
56
|
+
}
|
|
57
|
+
if (typeof element === "string" || typeof element === "number") {
|
|
58
|
+
return document.createTextNode(String(element));
|
|
59
|
+
}
|
|
60
|
+
if (element instanceof Node) {
|
|
61
|
+
return element;
|
|
62
|
+
}
|
|
63
|
+
if (typeof element === "function") {
|
|
64
|
+
const result = element();
|
|
65
|
+
return elementToNode(result);
|
|
66
|
+
}
|
|
67
|
+
if (element instanceof DocumentFragment) {
|
|
68
|
+
if (element.childNodes.length === 1) {
|
|
69
|
+
const child = element.firstChild;
|
|
70
|
+
element.removeChild(child);
|
|
71
|
+
return child;
|
|
72
|
+
}
|
|
73
|
+
const wrapper = document.createElement("span");
|
|
74
|
+
wrapper.style.display = "contents";
|
|
75
|
+
while (element.firstChild) {
|
|
76
|
+
wrapper.appendChild(element.firstChild);
|
|
77
|
+
}
|
|
78
|
+
return wrapper;
|
|
79
|
+
}
|
|
80
|
+
if (element && typeof element === "object") {
|
|
81
|
+
console.warn("[Show] Received unknown object type:", element);
|
|
82
|
+
return document.createTextNode(JSON.stringify(element));
|
|
83
|
+
}
|
|
84
|
+
return document.createTextNode("");
|
|
85
|
+
}
|
|
86
|
+
export {
|
|
87
|
+
Show
|
|
88
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CacheProvider } from './cache-provider.interface';
|
|
2
|
+
export type Constructor<T> = new (...args: any[]) => T;
|
|
3
|
+
export type TagExtractor = (result: any, args: any[]) => string[];
|
|
4
|
+
/**
|
|
5
|
+
* @CacheInvalidate Decorator
|
|
6
|
+
*
|
|
7
|
+
* Invalida (elimina) las entradas del cache que coincidan con los tags extraídos del
|
|
8
|
+
* resultado de un método. Útil para limpiar el cache después de operaciones de
|
|
9
|
+
* escritura que afectan a múltiples entradas relacionadas.
|
|
10
|
+
*
|
|
11
|
+
* @param ProviderClass - La clase del cache provider a usar
|
|
12
|
+
* @param tagExtractor - Función que extrae tags del resultado y argumentos
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* @CacheInvalidate(
|
|
17
|
+
* LocalStorageCache,
|
|
18
|
+
* (result) => ['users-list', `org:${result.organizationId}`]
|
|
19
|
+
* )
|
|
20
|
+
* async deleteUser(userId: string): Promise<void> {
|
|
21
|
+
* await this.httpClient.delete(`/users/${userId}`);
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example Combinar con @CacheUpdate
|
|
26
|
+
* ```typescript
|
|
27
|
+
* @CacheUpdate(LocalStorageCache, (result) => [`user:${result.id}`])
|
|
28
|
+
* @CacheInvalidate(LocalStorageCache, (result) => ['users-list', `org:${result.organizationId}`])
|
|
29
|
+
* async updateUser(id: string, data: any): Promise<User> {
|
|
30
|
+
* const response = await this.httpClient.put(`/users/${id}`, data);
|
|
31
|
+
* return response.data;
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function CacheInvalidate(ProviderClass: Constructor<CacheProvider>, tagExtractor: TagExtractor): (originalMethod: Function, context: ClassMethodDecoratorContext) => (this: any, ...args: any[]) => Promise<any>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
function CacheInvalidate(ProviderClass, tagExtractor) {
|
|
2
|
+
return function(originalMethod, context) {
|
|
3
|
+
return async function(...args) {
|
|
4
|
+
const result = await originalMethod.apply(this, args);
|
|
5
|
+
const provider = this.__container.get(ProviderClass);
|
|
6
|
+
if (!provider) {
|
|
7
|
+
console.warn(`[CacheInvalidate] Provider ${ProviderClass.name} not found in DI container`);
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
const tags = tagExtractor(result, args);
|
|
11
|
+
if (!tags || tags.length === 0) {
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
provider.invalidateTags(tags);
|
|
15
|
+
return result;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
CacheInvalidate
|
|
21
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Constructor } from '../../DI/types';
|
|
2
|
+
import { CacheProvider } from './cache-provider.interface';
|
|
3
|
+
import { TagExtractor } from './cache-tags.decorator';
|
|
4
|
+
/**
|
|
5
|
+
* Metadata storage para cache decorators
|
|
6
|
+
* Usamos un WeakMap con el método como key para evitar colisiones
|
|
7
|
+
*/
|
|
8
|
+
interface CacheMethodMetadata {
|
|
9
|
+
cacheProviderClass?: Constructor<CacheProvider>;
|
|
10
|
+
tagExtractor?: TagExtractor;
|
|
11
|
+
}
|
|
12
|
+
export declare function setCacheProviderClass(method: Function, providerClass: Constructor<CacheProvider>): void;
|
|
13
|
+
export declare function setTagExtractor(method: Function, extractor: TagExtractor): void;
|
|
14
|
+
export declare function getCacheMetadata(method: Function): CacheMethodMetadata;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CACHE PROVIDER INTERFACE
|
|
3
|
+
*
|
|
4
|
+
* Todos los cache providers deben implementar esta interface.
|
|
5
|
+
*/
|
|
6
|
+
export interface CacheProvider {
|
|
7
|
+
/**
|
|
8
|
+
* Obtiene un valor del cache.
|
|
9
|
+
* @param key - La clave del cache
|
|
10
|
+
* @returns El valor cacheado o null si no existe o expiró
|
|
11
|
+
*/
|
|
12
|
+
get<T>(key: string): T | null;
|
|
13
|
+
/**
|
|
14
|
+
* Almacena un valor en el cache con TTL.
|
|
15
|
+
* @param key - La clave del cache
|
|
16
|
+
* @param value - El valor a cachear
|
|
17
|
+
* @param ttl - Time-to-live en milisegundos
|
|
18
|
+
* @param tags - Tags para invalidación granular
|
|
19
|
+
*/
|
|
20
|
+
set<T>(key: string, value: T, ttl: number, tags?: string[]): void;
|
|
21
|
+
/**
|
|
22
|
+
* Elimina una entrada específica del cache.
|
|
23
|
+
* @param key - La clave a eliminar
|
|
24
|
+
*/
|
|
25
|
+
delete(key: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Elimina todas las entradas del cache.
|
|
28
|
+
*/
|
|
29
|
+
clear(): void;
|
|
30
|
+
/**
|
|
31
|
+
* Elimina entradas que coincidan con un patrón.
|
|
32
|
+
* @param pattern - RegExp para matching de keys
|
|
33
|
+
*/
|
|
34
|
+
invalidatePattern(pattern: RegExp): void;
|
|
35
|
+
/**
|
|
36
|
+
* Invalida todas las cache entries que tengan un tag específico.
|
|
37
|
+
* @param tag - El tag a invalidar
|
|
38
|
+
*/
|
|
39
|
+
invalidateTag(tag: string): void;
|
|
40
|
+
/**
|
|
41
|
+
* Obtiene todas las cache keys que tienen un tag específico.
|
|
42
|
+
* @param tag - El tag a buscar
|
|
43
|
+
* @returns Array de cache keys que tienen ese tag
|
|
44
|
+
*/
|
|
45
|
+
getKeysByTag(tag: string): string[];
|
|
46
|
+
/**
|
|
47
|
+
* Actualiza todas las cache entries que tengan cualquiera de los tags especificados.
|
|
48
|
+
* Preserva el TTL original de cada entrada.
|
|
49
|
+
* @param tags - Array de tags
|
|
50
|
+
* @param value - El nuevo valor a asignar
|
|
51
|
+
*/
|
|
52
|
+
updateByTags<T>(tags: string[], value: T): void;
|
|
53
|
+
/**
|
|
54
|
+
* Invalida (elimina) todas las cache entries que tengan cualquiera de los tags especificados.
|
|
55
|
+
* @param tags - Array de tags a invalidar
|
|
56
|
+
*/
|
|
57
|
+
invalidateTags(tags: string[]): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Estructura de una entrada en el cache.
|
|
61
|
+
*/
|
|
62
|
+
export interface CacheEntry<T> {
|
|
63
|
+
value: T;
|
|
64
|
+
timestamp: number;
|
|
65
|
+
ttl: number;
|
|
66
|
+
tags: string[];
|
|
67
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @CacheTags DECORATOR
|
|
3
|
+
*
|
|
4
|
+
* Asigna tags a las cache entries para invalidación granular.
|
|
5
|
+
* Los tags se extraen del resultado del método o de sus argumentos.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Función que extrae tags del resultado y/o argumentos del método.
|
|
9
|
+
*
|
|
10
|
+
* @param result - El valor retornado por el método
|
|
11
|
+
* @param args - Los argumentos con los que se llamó el método
|
|
12
|
+
* @returns Array de tags (strings)
|
|
13
|
+
*/
|
|
14
|
+
export type TagExtractor = (result: any, args: any[]) => string[];
|
|
15
|
+
/**
|
|
16
|
+
* @CacheTags - Asigna tags a cache entries para invalidación granular
|
|
17
|
+
*
|
|
18
|
+
* Los tags permiten invalidar selectivamente entries relacionadas sin
|
|
19
|
+
* necesitar eventos o regenerar cache keys.
|
|
20
|
+
*
|
|
21
|
+
* @param extractor - Función que extrae tags del resultado/args
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Tag basado en el resultado
|
|
25
|
+
* @CacheTags((result) => [`product:${result.id}`])
|
|
26
|
+
* async getProduct(id: string) { }
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Tag basado en argumentos
|
|
30
|
+
* @CacheTags((_, args) => [`product:${args[0]}`])
|
|
31
|
+
* async getProduct(id: string) { }
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // Múltiples tags
|
|
35
|
+
* @CacheTags((result) => [
|
|
36
|
+
* `product:${result.id}`,
|
|
37
|
+
* `category:${result.category}`,
|
|
38
|
+
* `brand:${result.brand}`
|
|
39
|
+
* ])
|
|
40
|
+
* async getProduct(id: string) { }
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // Tags de array de resultados
|
|
44
|
+
* @CacheTags((result) => result.map(p => `product:${p.id}`))
|
|
45
|
+
* async getProducts() { }
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // Invalidar por tag
|
|
49
|
+
* await productService.updateProduct(id, data);
|
|
50
|
+
* cache.invalidateTag(`product:${id}`);
|
|
51
|
+
*/
|
|
52
|
+
export declare function CacheTags(extractor: TagExtractor): (target: any, context: ClassMethodDecoratorContext) => void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function CacheTags(extractor) {
|
|
2
|
+
return function(target, context) {
|
|
3
|
+
const metadata = context.metadata;
|
|
4
|
+
const methodName = String(context.name);
|
|
5
|
+
if (!metadata.cacheTagExtractors) {
|
|
6
|
+
metadata.cacheTagExtractors = {};
|
|
7
|
+
}
|
|
8
|
+
metadata.cacheTagExtractors[methodName] = extractor;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
CacheTags
|
|
13
|
+
};
|