@asteby/metacore-runtime-react 29.0.1 → 29.0.2
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/CHANGELOG.md +13 -0
- package/dist/manifest-hotswap-subscriber.d.ts +3 -1
- package/dist/manifest-hotswap-subscriber.d.ts.map +1 -1
- package/dist/manifest-hotswap-subscriber.js +19 -3
- package/package.json +1 -1
- package/src/__tests__/manifest-hotswap-subscriber.test.ts +30 -0
- package/src/manifest-hotswap-subscriber.ts +22 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 29.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c0c8cb2: fix(hotswap): al recibir `ADDON_MANIFEST_CHANGED`, si la invalidación scopeada
|
|
8
|
+
no elimina ninguna entrada (el caso real: la metadata se cachea por nombre de
|
|
9
|
+
TABLA — `purchase_order_items`, `suppliers` — que no lleva el prefijo del
|
|
10
|
+
addon), se limpia TODO el cache de metadata como fallback. Antes, actualizar un
|
|
11
|
+
addon dejaba el label/ref viejo en el store persistido y el usuario tenía que
|
|
12
|
+
borrar datos del sitio a mano para ver la metadata fresca. Un cambio de manifest
|
|
13
|
+
es infrecuente (install/upgrade), así que el refetch perezoso en el próximo
|
|
14
|
+
montaje es barato y correcto.
|
|
15
|
+
|
|
3
16
|
## 29.0.1
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -45,7 +45,9 @@ export interface WireHotSwapInvalidationOptions {
|
|
|
45
45
|
* Useful for hosts that want to log/observe hot-swaps or trigger a
|
|
46
46
|
* `window.location.reload()` when the running addon's bundle hash
|
|
47
47
|
* changes (see the module-level comment above for the trade-off).
|
|
48
|
-
* `removed` is the number of cache entries flushed for this addon
|
|
48
|
+
* `removed` is the number of cache entries flushed for this addon, or
|
|
49
|
+
* `-1` when the scoped matcher found nothing and the whole metadata
|
|
50
|
+
* cache was cleared as a fallback (see `wireHotSwapInvalidation`).
|
|
49
51
|
*/
|
|
50
52
|
onSwap?: (msg: AddonManifestChangedMessage, removed: number) => void;
|
|
51
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-hotswap-subscriber.d.ts","sourceRoot":"","sources":["../src/manifest-hotswap-subscriber.ts"],"names":[],"mappings":"AA6CA,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAEzE;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAG,wBAAiC,CAAA;AAE5E;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IACxC,IAAI,EAAE,OAAO,2BAA2B,CAAA;IACxC,OAAO,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,QAAQ,EAAE,MAAM,CAAA;QAChB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,SAAS,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;CACJ;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAqB;IAClC,SAAS,EAAE,CACP,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,OAAO,EAAE,2BAA2B,KAAK,IAAI,KACtD,MAAM,IAAI,CAAA;CAClB;AAED,MAAM,WAAW,8BAA8B;IAC3C;;;;;OAKG;IACH,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB
|
|
1
|
+
{"version":3,"file":"manifest-hotswap-subscriber.d.ts","sourceRoot":"","sources":["../src/manifest-hotswap-subscriber.ts"],"names":[],"mappings":"AA6CA,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAEzE;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAG,wBAAiC,CAAA;AAE5E;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IACxC,IAAI,EAAE,OAAO,2BAA2B,CAAA;IACxC,OAAO,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,QAAQ,EAAE,MAAM,CAAA;QAChB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,SAAS,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;CACJ;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAqB;IAClC,SAAS,EAAE,CACP,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,OAAO,EAAE,2BAA2B,KAAK,IAAI,KACtD,MAAM,IAAI,CAAA;CAClB;AAED,MAAM,WAAW,8BAA8B;IAC3C;;;;;OAKG;IACH,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,2BAA2B,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACvE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,qBAAqB,GAAG,SAAS,GAAG,IAAI,EAChD,OAAO,GAAE,8BAAmC,GAC7C,MAAM,IAAI,CA+BZ;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CACxC,MAAM,EAAE,qBAAqB,GAAG,SAAS,GAAG,IAAI,EAChD,OAAO,GAAE,8BAAmC,GAC7C,IAAI,CAON"}
|
|
@@ -72,9 +72,25 @@ export function wireHotSwapInvalidation(client, options = {}) {
|
|
|
72
72
|
const addonKey = message?.payload?.addonKey;
|
|
73
73
|
if (!addonKey)
|
|
74
74
|
return;
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
const store = useMetadataCache.getState();
|
|
76
|
+
let removed = store.invalidateAddon(addonKey, matcher);
|
|
77
|
+
// Matcher-miss fallback. `invalidateAddon`'s default matcher only
|
|
78
|
+
// recognises cache keys that carry the addon prefix
|
|
79
|
+
// (`purchases`, `purchases.Model`, `purchases:Model`). But most
|
|
80
|
+
// hosts cache metadata under the model's TABLE name
|
|
81
|
+
// (`purchase_order_items`, `suppliers`) — which has no `purchases.`
|
|
82
|
+
// prefix — so a scoped invalidation removes 0 entries and the
|
|
83
|
+
// stale label/ref survives (the "why did I have to clear site
|
|
84
|
+
// data" bug). A manifest change is rare (addon install/upgrade),
|
|
85
|
+
// so when scoping matched nothing we drop the WHOLE metadata cache:
|
|
86
|
+
// correctness over a micro-optimisation. Entries refetch lazily on
|
|
87
|
+
// the next DynamicTable/relation mount, picking up the new
|
|
88
|
+
// fields/refs/labels. Hosts that DO namespace their keys keep the
|
|
89
|
+
// cheap scoped path (removed > 0).
|
|
90
|
+
if (removed === 0) {
|
|
91
|
+
store.clearAll();
|
|
92
|
+
removed = -1; // sentinel: "cleared all" (distinct from 0 = no-op)
|
|
93
|
+
}
|
|
78
94
|
onSwap?.(message, removed);
|
|
79
95
|
});
|
|
80
96
|
return unsubscribe;
|
package/package.json
CHANGED
|
@@ -118,6 +118,36 @@ describe('manifest-hotswap-subscriber', () => {
|
|
|
118
118
|
expect(onSwap).toHaveBeenCalledWith(message, 2)
|
|
119
119
|
})
|
|
120
120
|
|
|
121
|
+
it('clears the WHOLE cache when the scoped matcher finds nothing (table-named keys)', () => {
|
|
122
|
+
// Real-world case: metadata is cached under TABLE names, which carry no
|
|
123
|
+
// addon prefix. `invalidateAddon('purchases')` matches none of them, so
|
|
124
|
+
// without the fallback the stale label/ref would survive an upgrade.
|
|
125
|
+
const cache = useMetadataCache.getState()
|
|
126
|
+
cache.setMetadata('purchase_order_items', fakeMeta('items-old'))
|
|
127
|
+
cache.setMetadata('suppliers', fakeMeta('suppliers-old'))
|
|
128
|
+
cache.setModalMetadata('purchase_orders', fakeMeta('po-old'))
|
|
129
|
+
|
|
130
|
+
const onSwap = vi.fn()
|
|
131
|
+
const client = makeFakeClient()
|
|
132
|
+
wireHotSwapInvalidation(client, { onSwap })
|
|
133
|
+
|
|
134
|
+
const message: AddonManifestChangedMessage = {
|
|
135
|
+
type: 'ADDON_MANIFEST_CHANGED',
|
|
136
|
+
payload: { addonKey: 'purchases', newHash: 'sha256:new' },
|
|
137
|
+
}
|
|
138
|
+
client.emit(message)
|
|
139
|
+
|
|
140
|
+
const after = useMetadataCache.getState()
|
|
141
|
+
// Everything dropped — next mount refetches fresh metadata.
|
|
142
|
+
expect(after.cache).toEqual({})
|
|
143
|
+
expect(after.modalCache).toEqual({})
|
|
144
|
+
expect(after.prefetched).toBe(false)
|
|
145
|
+
// metadataVersion reset so prefetchAll re-seeds from the server.
|
|
146
|
+
expect(after.metadataVersion).toBe('')
|
|
147
|
+
// Sentinel -1 tells the host "cleared all" (vs 0 = genuine no-op).
|
|
148
|
+
expect(onSwap).toHaveBeenCalledWith(message, -1)
|
|
149
|
+
})
|
|
150
|
+
|
|
121
151
|
it('honours a custom matcher when the host uses a different key convention', () => {
|
|
122
152
|
const cache = useMetadataCache.getState()
|
|
123
153
|
cache.setMetadata('addons/pos/cart', fakeMeta('cart'))
|
|
@@ -97,7 +97,9 @@ export interface WireHotSwapInvalidationOptions {
|
|
|
97
97
|
* Useful for hosts that want to log/observe hot-swaps or trigger a
|
|
98
98
|
* `window.location.reload()` when the running addon's bundle hash
|
|
99
99
|
* changes (see the module-level comment above for the trade-off).
|
|
100
|
-
* `removed` is the number of cache entries flushed for this addon
|
|
100
|
+
* `removed` is the number of cache entries flushed for this addon, or
|
|
101
|
+
* `-1` when the scoped matcher found nothing and the whole metadata
|
|
102
|
+
* cache was cleared as a fallback (see `wireHotSwapInvalidation`).
|
|
101
103
|
*/
|
|
102
104
|
onSwap?: (msg: AddonManifestChangedMessage, removed: number) => void
|
|
103
105
|
}
|
|
@@ -128,9 +130,25 @@ export function wireHotSwapInvalidation(
|
|
|
128
130
|
(message) => {
|
|
129
131
|
const addonKey = message?.payload?.addonKey
|
|
130
132
|
if (!addonKey) return
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
const store = useMetadataCache.getState()
|
|
134
|
+
let removed = store.invalidateAddon(addonKey, matcher)
|
|
135
|
+
// Matcher-miss fallback. `invalidateAddon`'s default matcher only
|
|
136
|
+
// recognises cache keys that carry the addon prefix
|
|
137
|
+
// (`purchases`, `purchases.Model`, `purchases:Model`). But most
|
|
138
|
+
// hosts cache metadata under the model's TABLE name
|
|
139
|
+
// (`purchase_order_items`, `suppliers`) — which has no `purchases.`
|
|
140
|
+
// prefix — so a scoped invalidation removes 0 entries and the
|
|
141
|
+
// stale label/ref survives (the "why did I have to clear site
|
|
142
|
+
// data" bug). A manifest change is rare (addon install/upgrade),
|
|
143
|
+
// so when scoping matched nothing we drop the WHOLE metadata cache:
|
|
144
|
+
// correctness over a micro-optimisation. Entries refetch lazily on
|
|
145
|
+
// the next DynamicTable/relation mount, picking up the new
|
|
146
|
+
// fields/refs/labels. Hosts that DO namespace their keys keep the
|
|
147
|
+
// cheap scoped path (removed > 0).
|
|
148
|
+
if (removed === 0) {
|
|
149
|
+
store.clearAll()
|
|
150
|
+
removed = -1 // sentinel: "cleared all" (distinct from 0 = no-op)
|
|
151
|
+
}
|
|
134
152
|
onSwap?.(message, removed)
|
|
135
153
|
},
|
|
136
154
|
)
|