@zeniai/client-epic-state 5.1.60-betaDI0 → 5.1.60-betaDI1
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/lib/entity/notificationRegistry/notificationRegistryReducer.d.ts +5 -1
- package/lib/entity/notificationRegistry/notificationRegistryReducer.js +28 -4
- package/lib/entity/notificationRegistry/notificationRegistryState.d.ts +1 -0
- package/lib/esm/entity/notificationRegistry/notificationRegistryReducer.js +28 -4
- package/lib/esm/view/settingsView/epic/fetchNotificationSettingsEpic.js +16 -3
- package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.d.ts +1 -1
- package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.js +16 -3
- package/package.json +1 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { NotificationRegistryPayload } from './notificationRegistryPayload';
|
|
2
2
|
import { NotificationRegistryState } from './notificationRegistryState';
|
|
3
3
|
export declare const initialState: NotificationRegistryState;
|
|
4
|
-
export
|
|
4
|
+
export interface UpdateNotificationRegistryPayload {
|
|
5
|
+
registry: NotificationRegistryPayload | undefined;
|
|
6
|
+
registryEpochAtDispatch: number;
|
|
7
|
+
}
|
|
8
|
+
export declare const clearAllNotificationRegistry: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"notificationRegistry/clearAllNotificationRegistry">, updateNotificationRegistry: import("@reduxjs/toolkit").ActionCreatorWithPayload<UpdateNotificationRegistryPayload, "notificationRegistry/updateNotificationRegistry">;
|
|
5
9
|
declare const _default: import("redux").Reducer<NotificationRegistryState>;
|
|
6
10
|
export default _default;
|
|
@@ -4,21 +4,45 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.updateNotificationRegistry = exports.clearAllNotificationRegistry = exports.initialState = void 0;
|
|
5
5
|
const toolkit_1 = require("@reduxjs/toolkit");
|
|
6
6
|
const notificationRegistryPayload_1 = require("./notificationRegistryPayload");
|
|
7
|
-
exports.initialState = {};
|
|
7
|
+
exports.initialState = { registryEpoch: 0 };
|
|
8
8
|
const notificationRegistry = (0, toolkit_1.createSlice)({
|
|
9
9
|
name: 'notificationRegistry',
|
|
10
10
|
initialState: exports.initialState,
|
|
11
11
|
reducers: {
|
|
12
|
-
clearAllNotificationRegistry() {
|
|
13
|
-
|
|
12
|
+
clearAllNotificationRegistry(draft) {
|
|
13
|
+
// Bump `registryEpoch` (don't rewind to 0) so any settings-envelope
|
|
14
|
+
// GET that was already in flight when the tenant switched is treated
|
|
15
|
+
// as stale: its captured `registryEpochAtDispatch` is now lower than
|
|
16
|
+
// the current epoch, so `updateNotificationRegistry` skips its write.
|
|
17
|
+
// Mirrors the `saveEpoch` bump in `clearAllNotificationPreferencesView`
|
|
18
|
+
// — Cursor Bugbot 67d6918e. Returning `initialState` verbatim would
|
|
19
|
+
// rewind the counter and let a stale in-flight GET restore A's
|
|
20
|
+
// registry over B's.
|
|
21
|
+
return { registryEpoch: draft.registryEpoch + 1 };
|
|
14
22
|
},
|
|
15
23
|
updateNotificationRegistry(draft, action) {
|
|
24
|
+
// Stale-response guard (Cursor Bugbot 67d6918e): if the fetch epic
|
|
25
|
+
// captured a `registryEpoch` lower than the current one, a clear
|
|
26
|
+
// (tenant switch) has landed since this GET dispatched. The response
|
|
27
|
+
// carries the prior tenant's snapshot and must not restore it — nor,
|
|
28
|
+
// when it omits the registry, wipe the fresh one. Skip entirely.
|
|
29
|
+
if (action.payload.registryEpochAtDispatch < draft.registryEpoch) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Gate-off / absent-registry case: an envelope that omits
|
|
33
|
+
// `task_checklist_registry` CLEARS so tenant B doesn't inherit A's
|
|
34
|
+
// Task Checklist section. Guarded by the epoch check above so a stale
|
|
35
|
+
// absent-response can't wipe a fresh registry.
|
|
36
|
+
if (action.payload.registry == null) {
|
|
37
|
+
draft.registry = undefined;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
16
40
|
// Full-replace contract: the registry payload is atomic — every fetch
|
|
17
41
|
// returns the complete group/event tree. If the backend ever ships a
|
|
18
42
|
// partial registry response, this write MUST switch to a merge (mirror
|
|
19
43
|
// the preferences slice's `mergePreferences`). Do not assume the
|
|
20
44
|
// current payload is partial without also updating this reducer.
|
|
21
|
-
draft.registry = (0, notificationRegistryPayload_1.mapNotificationRegistryPayloadToRegistry)(action.payload);
|
|
45
|
+
draft.registry = (0, notificationRegistryPayload_1.mapNotificationRegistryPayloadToRegistry)(action.payload.registry);
|
|
22
46
|
},
|
|
23
47
|
},
|
|
24
48
|
});
|
|
@@ -1,20 +1,44 @@
|
|
|
1
1
|
import { createSlice } from '@reduxjs/toolkit';
|
|
2
2
|
import { mapNotificationRegistryPayloadToRegistry, } from './notificationRegistryPayload';
|
|
3
|
-
export const initialState = {};
|
|
3
|
+
export const initialState = { registryEpoch: 0 };
|
|
4
4
|
const notificationRegistry = createSlice({
|
|
5
5
|
name: 'notificationRegistry',
|
|
6
6
|
initialState,
|
|
7
7
|
reducers: {
|
|
8
|
-
clearAllNotificationRegistry() {
|
|
9
|
-
|
|
8
|
+
clearAllNotificationRegistry(draft) {
|
|
9
|
+
// Bump `registryEpoch` (don't rewind to 0) so any settings-envelope
|
|
10
|
+
// GET that was already in flight when the tenant switched is treated
|
|
11
|
+
// as stale: its captured `registryEpochAtDispatch` is now lower than
|
|
12
|
+
// the current epoch, so `updateNotificationRegistry` skips its write.
|
|
13
|
+
// Mirrors the `saveEpoch` bump in `clearAllNotificationPreferencesView`
|
|
14
|
+
// — Cursor Bugbot 67d6918e. Returning `initialState` verbatim would
|
|
15
|
+
// rewind the counter and let a stale in-flight GET restore A's
|
|
16
|
+
// registry over B's.
|
|
17
|
+
return { registryEpoch: draft.registryEpoch + 1 };
|
|
10
18
|
},
|
|
11
19
|
updateNotificationRegistry(draft, action) {
|
|
20
|
+
// Stale-response guard (Cursor Bugbot 67d6918e): if the fetch epic
|
|
21
|
+
// captured a `registryEpoch` lower than the current one, a clear
|
|
22
|
+
// (tenant switch) has landed since this GET dispatched. The response
|
|
23
|
+
// carries the prior tenant's snapshot and must not restore it — nor,
|
|
24
|
+
// when it omits the registry, wipe the fresh one. Skip entirely.
|
|
25
|
+
if (action.payload.registryEpochAtDispatch < draft.registryEpoch) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Gate-off / absent-registry case: an envelope that omits
|
|
29
|
+
// `task_checklist_registry` CLEARS so tenant B doesn't inherit A's
|
|
30
|
+
// Task Checklist section. Guarded by the epoch check above so a stale
|
|
31
|
+
// absent-response can't wipe a fresh registry.
|
|
32
|
+
if (action.payload.registry == null) {
|
|
33
|
+
draft.registry = undefined;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
12
36
|
// Full-replace contract: the registry payload is atomic — every fetch
|
|
13
37
|
// returns the complete group/event tree. If the backend ever ships a
|
|
14
38
|
// partial registry response, this write MUST switch to a merge (mirror
|
|
15
39
|
// the preferences slice's `mergePreferences`). Do not assume the
|
|
16
40
|
// current payload is partial without also updating this reducer.
|
|
17
|
-
draft.registry = mapNotificationRegistryPayloadToRegistry(action.payload);
|
|
41
|
+
draft.registry = mapNotificationRegistryPayloadToRegistry(action.payload.registry);
|
|
18
42
|
},
|
|
19
43
|
},
|
|
20
44
|
});
|
|
@@ -17,6 +17,12 @@ export const fetchNotificationSettingsEpic = (actions$, state$, zeniAPI) => acti
|
|
|
17
17
|
// meantime — the GET is carrying the pre-PUT snapshot and would
|
|
18
18
|
// otherwise clobber the fresh save. Cursor Bugbot 3632510237.
|
|
19
19
|
const saveEpochAtDispatch = state.notificationPreferencesViewState.saveEpoch;
|
|
20
|
+
// Snapshot the registry clear epoch at GET-dispatch time (same idiom as
|
|
21
|
+
// `saveEpochAtDispatch` above). The registry reducer compares it against
|
|
22
|
+
// the current `registryEpoch` and skips its write when a
|
|
23
|
+
// `clearAllNotificationRegistry` (tenant switch) has landed since — the
|
|
24
|
+
// GET is carrying the prior tenant's snapshot. Cursor Bugbot 67d6918e.
|
|
25
|
+
const registryEpochAtDispatch = state.notificationRegistryState.registryEpoch;
|
|
20
26
|
return zeniAPI
|
|
21
27
|
.getJSON(notificationPreferencesUrl(zeniAPI))
|
|
22
28
|
.pipe(mergeMap((response) => {
|
|
@@ -24,9 +30,16 @@ export const fetchNotificationSettingsEpic = (actions$, state$, zeniAPI) => acti
|
|
|
24
30
|
const updateActions = [
|
|
25
31
|
updateNotificationSettingsOnSuccess(response.data, userEmail),
|
|
26
32
|
];
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
// Always dispatch — an absent `task_checklist_registry` (gate
|
|
34
|
+
// off) must CLEAR the prior tenant's registry, not leave it in
|
|
35
|
+
// place. The reducer clears on `undefined` and full-replaces
|
|
36
|
+
// otherwise, both guarded by the registry epoch so a stale GET
|
|
37
|
+
// that lands after a tenant switch is skipped. Cursor Bugbot
|
|
38
|
+
// 67d6918e.
|
|
39
|
+
updateActions.push(updateNotificationRegistry({
|
|
40
|
+
registry: response.data.task_checklist_registry ?? undefined,
|
|
41
|
+
registryEpochAtDispatch,
|
|
42
|
+
}));
|
|
30
43
|
if (response.data.task_checklist_preferences != null) {
|
|
31
44
|
updateActions.push(fetchNotificationPreferencesSuccess({
|
|
32
45
|
preferences: response.data.task_checklist_preferences,
|
|
@@ -18,7 +18,7 @@ export declare const fetchNotificationSettingsEpic: (actions$: ActionsObservable
|
|
|
18
18
|
payload: import("../../../responsePayload").ZeniAPIStatus<Record<string, unknown>>;
|
|
19
19
|
type: "settings/updateNotificationSettingsOnFailure";
|
|
20
20
|
} | {
|
|
21
|
-
payload: import("../../../entity/notificationRegistry/
|
|
21
|
+
payload: import("../../../entity/notificationRegistry/notificationRegistryReducer").UpdateNotificationRegistryPayload;
|
|
22
22
|
type: "notificationRegistry/updateNotificationRegistry";
|
|
23
23
|
} | {
|
|
24
24
|
payload: import("../../notificationPreferencesView/notificationPreferencesViewReducer").FetchNotificationPreferencesSuccessPayload;
|
|
@@ -20,6 +20,12 @@ const fetchNotificationSettingsEpic = (actions$, state$, zeniAPI) => actions$.pi
|
|
|
20
20
|
// meantime — the GET is carrying the pre-PUT snapshot and would
|
|
21
21
|
// otherwise clobber the fresh save. Cursor Bugbot 3632510237.
|
|
22
22
|
const saveEpochAtDispatch = state.notificationPreferencesViewState.saveEpoch;
|
|
23
|
+
// Snapshot the registry clear epoch at GET-dispatch time (same idiom as
|
|
24
|
+
// `saveEpochAtDispatch` above). The registry reducer compares it against
|
|
25
|
+
// the current `registryEpoch` and skips its write when a
|
|
26
|
+
// `clearAllNotificationRegistry` (tenant switch) has landed since — the
|
|
27
|
+
// GET is carrying the prior tenant's snapshot. Cursor Bugbot 67d6918e.
|
|
28
|
+
const registryEpochAtDispatch = state.notificationRegistryState.registryEpoch;
|
|
23
29
|
return zeniAPI
|
|
24
30
|
.getJSON((0, notificationPreferencesEndpoint_1.notificationPreferencesUrl)(zeniAPI))
|
|
25
31
|
.pipe((0, operators_1.mergeMap)((response) => {
|
|
@@ -27,9 +33,16 @@ const fetchNotificationSettingsEpic = (actions$, state$, zeniAPI) => actions$.pi
|
|
|
27
33
|
const updateActions = [
|
|
28
34
|
(0, settingsViewReducer_1.updateNotificationSettingsOnSuccess)(response.data, userEmail),
|
|
29
35
|
];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
// Always dispatch — an absent `task_checklist_registry` (gate
|
|
37
|
+
// off) must CLEAR the prior tenant's registry, not leave it in
|
|
38
|
+
// place. The reducer clears on `undefined` and full-replaces
|
|
39
|
+
// otherwise, both guarded by the registry epoch so a stale GET
|
|
40
|
+
// that lands after a tenant switch is skipped. Cursor Bugbot
|
|
41
|
+
// 67d6918e.
|
|
42
|
+
updateActions.push((0, notificationRegistryReducer_1.updateNotificationRegistry)({
|
|
43
|
+
registry: response.data.task_checklist_registry ?? undefined,
|
|
44
|
+
registryEpochAtDispatch,
|
|
45
|
+
}));
|
|
33
46
|
if (response.data.task_checklist_preferences != null) {
|
|
34
47
|
updateActions.push((0, notificationPreferencesViewReducer_1.fetchNotificationPreferencesSuccess)({
|
|
35
48
|
preferences: response.data.task_checklist_preferences,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeniai/client-epic-state",
|
|
3
|
-
"version": "5.1.60-
|
|
3
|
+
"version": "5.1.60-betaDI1",
|
|
4
4
|
"description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/esm/index.js",
|