@communecter/cocolight-api-client 1.0.141 → 1.0.142

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@communecter/cocolight-api-client",
3
- "version": "1.0.141",
3
+ "version": "1.0.142",
4
4
  "description": "Client Axios simplifié pour l'API cocolight",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,196 @@
1
+ import { batch, reactive } from "../utils/reactive.js";
2
+
3
+ import type {
4
+ NotificationAuthor,
5
+ NotificationItemData
6
+ } from "./serverDataType/Notification.js";
7
+
8
+ type User = import("./User.js").User; // backref ; User porte __entityTag donc non proxifié par reactive()
9
+
10
+ /** Taille de page côté serveur (ActivityStream::getNotificationsByStep indexStep=15). */
11
+ const DEFAULT_PAGE_SIZE = 15;
12
+
13
+ /** isUnread/isUnseen de CE destinataire (le serveur unset la clé quand lu/vu -> absence = faux). */
14
+ function stateFor(data: NotificationItemData, userId: string | null, key: "isUnread" | "isUnseen"): boolean {
15
+ if (!userId) return false;
16
+ return data?.notify?.id?.[userId]?.[key] === true;
17
+ }
18
+
19
+ /**
20
+ * Item de notification fin. N'étend PAS BaseEntity : il garde la donnée brute, un backref
21
+ * User (pour router les appels via callIsMe + endpointApi) et un backref manager (pour le recount).
22
+ * Les drapeaux lu/vu sont réactifs (objet reactive({...}) — surface scalaire fiable).
23
+ */
24
+ export class Notification {
25
+ private readonly _data: NotificationItemData;
26
+ private readonly _owner: User;
27
+ private _manager: Notifications | null;
28
+ private readonly _state: { isUnread: boolean; isUnseen: boolean };
29
+
30
+ constructor(data: NotificationItemData, owner: User, manager: Notifications | null = null) {
31
+ this._data = data;
32
+ this._owner = owner;
33
+ this._manager = manager;
34
+ const uid = owner.userId;
35
+ this._state = reactive({
36
+ isUnread: stateFor(data, uid, "isUnread"),
37
+ isUnseen: stateFor(data, uid, "isUnseen")
38
+ });
39
+ }
40
+
41
+ static fromData(data: NotificationItemData, owner: User, manager: Notifications | null = null): Notification {
42
+ return new Notification(data, owner, manager);
43
+ }
44
+
45
+ /** Lie l'item à son manager (appelé par le manager au wrap). */
46
+ _attach(manager: Notifications): this { this._manager = manager; return this; }
47
+
48
+ get id(): string { return this._data.id; } // injecté par ApiClient._transformData (clé de la map = hex)
49
+ get isUnread(): boolean { return this._state.isUnread; } // lecture réactive
50
+ get isUnseen(): boolean { return this._state.isUnseen; }
51
+ get data(): NotificationItemData { return this._data; }
52
+ get author(): NotificationAuthor { return this._data.author; }
53
+ get createdAt(): Date {
54
+ // ApiClient._transformData rend `created` en Date. La string ISO survient après un
55
+ // round-trip toJSON()->JSON.parse->restore(). On fait confiance à la normalisation amont.
56
+ const c = this._data.created;
57
+ return c instanceof Date ? c : new Date(c as string | number);
58
+ }
59
+
60
+ // setters locaux (utilisés par l'optimiste, sans appel réseau)
61
+ _setReadLocal(): void { this._state.isUnread = false; }
62
+ _setSeenLocal(): void { this._state.isUnseen = false; }
63
+ _restoreLocal(unread: boolean, unseen: boolean): void { this._state.isUnread = unread; this._state.isUnseen = unseen; }
64
+
65
+ /** Marque CET item comme lu : optimiste + rollback + recount manager. */
66
+ async markRead(): Promise<void> {
67
+ if (!this._state.isUnread) return;
68
+ const pu = this._state.isUnread, ps = this._state.isUnseen;
69
+ this._setReadLocal();
70
+ this._manager?._recount();
71
+ try {
72
+ await this._owner.markNotificationAsRead(this.id);
73
+ } catch (err) {
74
+ this._restoreLocal(pu, ps);
75
+ this._manager?._recount();
76
+ throw err;
77
+ }
78
+ }
79
+
80
+ /** Pas d'endpoint "seen" par item : flip local uniquement. */
81
+ markSeen(): void { this._setSeenLocal(); this._manager?._recount(); }
82
+
83
+ toJSON(): NotificationItemData { return this._data; }
84
+ }
85
+
86
+ /**
87
+ * Gestionnaire de notifications de l'utilisateur connecté. Accroché à User via `me.notifications`.
88
+ * N'étend PAS BaseEntity. Items dans un tableau SIMPLE (jamais proxifié) ; seuls les scalaires
89
+ * vivent dans un reactive({...}) (loadedUnreadCount, unseenTotal, cursor, hasMore, version).
90
+ * Aucun effect/computed créé ici (pas d'API de dispose -> pas de fuite).
91
+ */
92
+ export class Notifications {
93
+ private readonly _owner: User;
94
+ private readonly _pageSize: number;
95
+ private _items: Notification[] = [];
96
+ private readonly _state: {
97
+ loadedUnreadCount: number; // non-lus PARMI les items chargés (dérivé)
98
+ unseenTotal: number; // total NON-VUS côté serveur (countNotif) — badge cloche
99
+ cursor: number; // offset indexMin (nb d'items déjà chargés)
100
+ hasMore: boolean;
101
+ version: number; // bump à chaque mutation de liste -> réactivité de `items`
102
+ };
103
+
104
+ constructor(owner: User, pageSize: number = DEFAULT_PAGE_SIZE) {
105
+ this._owner = owner;
106
+ this._pageSize = pageSize;
107
+ this._state = reactive({ loadedUnreadCount: 0, unseenTotal: 0, cursor: 0, hasMore: true, version: 0 });
108
+ }
109
+
110
+ // surface réactive consommateur
111
+ get items(): Notification[] { void this._state.version; return this._items; }
112
+ get loadedUnreadCount(): number { return this._state.loadedUnreadCount; }
113
+ get unseenTotal(): number { return this._state.unseenTotal; } // badge "non vus"
114
+ get hasMore(): boolean { return this._state.hasMore; }
115
+
116
+ _recount(): void { this._state.loadedUnreadCount = this._items.filter((n) => n.isUnread).length; }
117
+ private _wrap(raw: NotificationItemData[]): Notification[] { return raw.map((d) => Notification.fromData(d, this._owner, this)); }
118
+ private _commit(items: Notification[]): void { this._items = items; this._state.version++; this._recount(); }
119
+
120
+ /** Première page (reset le curseur). Tri serveur : updated desc, 15/page. */
121
+ async list({ indexMin = 0 }: { indexMin?: number } = {}): Promise<Notification[]> {
122
+ const next = this._wrap(await this._owner.fetchNotifications({ indexMin }));
123
+ this._commit(next);
124
+ this._state.cursor = next.length;
125
+ this._state.hasMore = next.length >= this._pageSize;
126
+ return next;
127
+ }
128
+
129
+ /** Page suivante (curseur indexMin = nb d'items déjà chargés). Concatène par réassignation. */
130
+ async loadMore(): Promise<Notification[]> {
131
+ if (!this._state.hasMore) return [];
132
+ const page = this._wrap(await this._owner.fetchNotifications({ indexMin: this._state.cursor }));
133
+ this._commit([...this._items, ...page]);
134
+ this._state.cursor += page.length;
135
+ this._state.hasMore = page.length >= this._pageSize;
136
+ return page;
137
+ }
138
+
139
+ /** Recharge depuis le début. */
140
+ async refresh(): Promise<Notification[]> { this._state.cursor = 0; this._state.hasMore = true; return this.list(); }
141
+
142
+ /**
143
+ * Badge : total des NON-VUS via GET_NOTIFICATIONS_COUNT (refreshTimestamp = maintenant ->
144
+ * liste vide + countNotif). Met à jour unseenTotal et le retourne.
145
+ */
146
+ async count(): Promise<number> {
147
+ this._state.unseenTotal = await this._owner.fetchNotificationsCount();
148
+ return this._state.unseenTotal;
149
+ }
150
+
151
+ /**
152
+ * PUR : convertit des `NotificationItemData[]` plats (ex. cache React Query après
153
+ * hydratation SSR) en `Notification[]` liées à l'utilisateur, SANS modifier l'état du
154
+ * manager. À utiliser dans un `select` React Query (cache plat -> instances vivantes).
155
+ */
156
+ toItems(data: NotificationItemData[]): Notification[] {
157
+ return this._wrap(data);
158
+ }
159
+
160
+ async markAllRead(): Promise<void> { await this._bulk("read"); }
161
+ async markAllSeen(): Promise<void> { await this._bulk("seen"); }
162
+
163
+ private async _bulk(action: "seen" | "read"): Promise<void> {
164
+ const snap = this._items.map((n) => ({ n, u: n.isUnread, s: n.isUnseen }));
165
+ batch(() => { for (const { n } of snap) { if (action === "read") n._setReadLocal(); else n._setSeenLocal(); } });
166
+ if (action === "seen") this._state.unseenTotal = 0;
167
+ this._recount();
168
+ try {
169
+ await this._owner.markAllNotifications(action);
170
+ } catch (err) {
171
+ batch(() => { for (const { n, u, s } of snap) n._restoreLocal(u, s); });
172
+ this._recount();
173
+ throw err;
174
+ }
175
+ }
176
+
177
+ /** Supprime toutes les notifications de l'utilisateur + vide l'état local. */
178
+ async clear(): Promise<void> {
179
+ await this._owner.removeAllNotifications();
180
+ this._commit([]);
181
+ this._state.cursor = 0;
182
+ this._state.hasMore = false;
183
+ this._state.unseenTotal = 0;
184
+ }
185
+
186
+ // sérialisation (donnée brute ; le backref owner + l'état réactif sont re-construits au restore)
187
+ toJSON(): NotificationItemData[] { return this._items.map((n) => n.toJSON()); }
188
+ restore(rawItems: NotificationItemData[]): this {
189
+ this._commit(this._wrap(rawItems));
190
+ this._state.cursor = rawItems.length;
191
+ return this;
192
+ }
193
+ static restore(rawItems: NotificationItemData[], owner: User): Notifications {
194
+ return new Notifications(owner).restore(rawItems);
195
+ }
196
+ }
package/src/api/User.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ApiError } from "../error.js";
2
2
  import { BaseEntity } from "./BaseEntity.js";
3
+ import { Notifications } from "./Notifications.js";
3
4
  import { UserMixin } from "../mixin/UserMixin.js";
4
5
  import { createSocialTransform } from "../types/transforms.js";
5
6
 
@@ -17,9 +18,14 @@ import type {
17
18
  GetOrganizationsNoAdminData,
18
19
  GetOrganizationsAdminData,
19
20
  GetFriendsAdminData,
20
- DemoteAdminData
21
+ DemoteAdminData,
22
+ GetNotificationsData,
23
+ GetNotificationsCountData,
24
+ NotificationUpdateData,
25
+ MarkNotificationAsReadData
21
26
  } from "./EndpointApi.types.js";
22
27
  import type { Organization } from "./Organization.js";
28
+ import type { NotificationItemData } from "./serverDataType/Notification.js";
23
29
  import type { EntityTypes } from "@/types/entities.js";
24
30
 
25
31
  type ApiClient = import("../ApiClient.js").default;
@@ -1717,6 +1723,71 @@ export class User extends BaseEntity<UserItemNormalized> {
1717
1723
  return retour;
1718
1724
  }
1719
1725
 
1726
+ // ===========================================================================
1727
+ // Notifications (sous-ressource de l'utilisateur connecté)
1728
+ // ===========================================================================
1729
+
1730
+ /** Gestionnaire de notifications, créé paresseusement et mis en cache. */
1731
+ private _notifications?: Notifications;
1732
+
1733
+ /**
1734
+ * Point d'entrée `me.notifications` (composition, PAS une entité BaseEntity).
1735
+ * Sûr : endpointApi/apiClient sont initialisés dans le constructeur avant tout accès getter.
1736
+ */
1737
+ get notifications(): Notifications {
1738
+ return (this._notifications ??= new Notifications(this));
1739
+ }
1740
+
1741
+ /**
1742
+ * Récupère les notifications (mode liste). STATELESS + plat (`NotificationItemData[]`),
1743
+ * idéal comme `queryFn` React Query / prefetch SSR — ne touche PAS le manager réactif
1744
+ * (`me.notifications`). `ApiClient._transformData` garantit `notif` = tableau (map -> array
1745
+ * + `id` injecté), donc on fait confiance à la normalisation amont.
1746
+ * pathParams.id est OBLIGATOIRE : le défaut "@userId" ne matche pas le pattern de l'endpoint.
1747
+ */
1748
+ async fetchNotifications({ indexMin }: { indexMin?: number } = {}): Promise<NotificationItemData[]> {
1749
+ const res = (await this.callIsMe(() => {
1750
+ const data: GetNotificationsData = {
1751
+ pathParams: { type: "citoyens", id: this.userId! },
1752
+ ...(indexMin != null ? { indexMin } : {})
1753
+ };
1754
+ return this.endpointApi.getNotifications(data);
1755
+ })) as { notif?: NotificationItemData[] };
1756
+ return Array.isArray(res?.notif) ? res.notif : [];
1757
+ }
1758
+
1759
+ /**
1760
+ * Compte les notifications NON VUES (badge). STATELESS. refreshTimestamp = maintenant ->
1761
+ * le serveur renvoie `countNotif` (total non-vus) + une liste vide.
1762
+ */
1763
+ async fetchNotificationsCount(): Promise<number> {
1764
+ const res = (await this.callIsMe(() => {
1765
+ const data: GetNotificationsCountData = {
1766
+ pathParams: { type: "citoyens", id: this.userId! },
1767
+ refreshTimestamp: Math.floor(Date.now() / 1000)
1768
+ };
1769
+ return this.endpointApi.getNotificationsCount(data);
1770
+ })) as { countNotif?: number };
1771
+ return typeof res?.countNotif === "number" ? res.countNotif : 0;
1772
+ }
1773
+
1774
+ /** Marque une notification comme lue (MARK_NOTIFICATION_AS_READ). */
1775
+ async markNotificationAsRead(id: string): Promise<unknown> {
1776
+ const data: MarkNotificationAsReadData = { id };
1777
+ return this.callIsMe(() => this.endpointApi.markNotificationAsRead(data));
1778
+ }
1779
+
1780
+ /** Marque toutes les notifications comme vues/lues (NOTIFICATION_UPDATE). */
1781
+ async markAllNotifications(action: "seen" | "read"): Promise<unknown> {
1782
+ const data: NotificationUpdateData = { action, all: true };
1783
+ return this.callIsMe(() => this.endpointApi.notificationUpdate(data));
1784
+ }
1785
+
1786
+ /** Supprime toutes les notifications de l'utilisateur (REMOVE_ALL_NOTIFICATIONS). */
1787
+ async removeAllNotifications(): Promise<unknown> {
1788
+ return this.callIsMe(() => this.endpointApi.removeAllNotifications());
1789
+ }
1790
+
1720
1791
  }
1721
1792
 
1722
1793
  // Incorporation des mixins dans User
@@ -0,0 +1,81 @@
1
+ import type { DateValue, IdObject } from "./common.js";
2
+ import type EJSONType from "../../EJSONType.js";
3
+
4
+ // L'_id peut arriver sous 3 formes selon le chemin d'appel :
5
+ // - instance ObjectID (transform + _fromJSONValue actif — cas observé sur User.getNotifications)
6
+ // - { $id } (forme normalisée non-revivée)
7
+ // - string hex (24)
8
+ type ObjectIDInstance = InstanceType<typeof EJSONType["ObjectID"]>;
9
+ export type NotificationId = ObjectIDInstance | IdObject | string;
10
+
11
+ /** Auteur APRÈS aplatissement par _transformData (la map oid->objet devient un objet). */
12
+ export interface NotificationAuthor {
13
+ id?: string;
14
+ type?: string;
15
+ name: string;
16
+ profilThumbImageUrl?: string; // complété en URL absolue par la normalisation image
17
+ [k: string]: unknown;
18
+ }
19
+
20
+ export interface NotificationTarget {
21
+ type: string;
22
+ id: string;
23
+ parent?: { id: string; type: string };
24
+ }
25
+
26
+ /** État lu/vu, par destinataire. Un champ ABSENT (clé unset côté serveur) = état "faux". */
27
+ export interface NotificationReadState {
28
+ isUnread?: boolean;
29
+ isUnseen?: boolean;
30
+ }
31
+
32
+ export interface NotificationNotify {
33
+ objectType?: string;
34
+ /** keyé par oid de destinataire -> état lu/vu de CE destinataire. */
35
+ id: Record<string, NotificationReadState>;
36
+ displayName?: string;
37
+ icon?: string;
38
+ url?: string;
39
+ label?: string;
40
+ labelArray?: Record<string, string[]>;
41
+ labelAuthorObject?: string;
42
+ repeat?: boolean;
43
+ [k: string]: unknown;
44
+ }
45
+
46
+ /** Un élément du tableau `notif` (forme transformée). created/updated : Date instance OU {sec,usec}. */
47
+ export interface NotificationItemData {
48
+ id: string; // injecté depuis la clé de la map notif (= oid hex)
49
+ _id: NotificationId;
50
+ type: string;
51
+ verb: string;
52
+ author: NotificationAuthor;
53
+ created: Date | DateValue;
54
+ updated: Date | DateValue;
55
+ target: NotificationTarget;
56
+ notify: NotificationNotify;
57
+ timestamp?: number; // ajouté serveur (= updated.sec)
58
+ timeAgo?: string; // présent en brut, retiré au transform
59
+ [k: string]: unknown;
60
+ }
61
+
62
+ /**
63
+ * Réponse de GET_NOTIFICATIONS (mode liste, body indexMin) APRÈS transform :
64
+ * `notif` est un tableau (la map oid->item est convertie). Vide => [].
65
+ */
66
+ export interface GetNotificationsResult {
67
+ notif: NotificationItemData[];
68
+ [k: string]: unknown;
69
+ }
70
+
71
+ /**
72
+ * Réponse de GET_NOTIFICATIONS_COUNT (mode badge, body refreshTimestamp non-nul) :
73
+ * `notif` = notifications mises à jour APRÈS le timestamp (souvent [] si timestamp = maintenant),
74
+ * `countNotif` = nombre TOTAL de notifications NON VUES (isUnseen) — indépendant du timestamp.
75
+ * countNotif n'est présent QUE si refreshTimestamp est fourni et non-nul (sinon mode liste).
76
+ */
77
+ export interface GetNotificationsCountResult {
78
+ notif: NotificationItemData[];
79
+ countNotif?: number;
80
+ [k: string]: unknown;
81
+ }
package/src/index.ts CHANGED
@@ -41,6 +41,9 @@ const cocolightApiClient = {
41
41
 
42
42
  export default cocolightApiClient;
43
43
 
44
+ // Helpers réactifs en exports nommés (permet `import { effect, subscribeTo } from "..."`)
45
+ export { batch, computed, effect, getSignals, isReactive, isSignal, reactive, subscribeTo, watch } from "./utils/reactive.js";
46
+
44
47
  // Export des types pour faciliter l'utilisation
45
48
 
46
49
  // Classes principales
@@ -101,6 +104,10 @@ export type * from "./api/serverDataType/Action.js";
101
104
  export type * from "./api/serverDataType/common.js";
102
105
  export type * from "./api/serverDataType/Country.js";
103
106
  export type * from "./api/serverDataType/Zone.js";
107
+ export type * from "./api/serverDataType/Notification.js";
108
+
109
+ // Classes notifications (type-only, comme les entités) — instances obtenues via me.notifications
110
+ export type { Notification, Notifications } from "./api/Notifications.js";
104
111
 
105
112
  // Types utilitaires BaseEntity
106
113
  export type {
@@ -0,0 +1,79 @@
1
+ import type { NotificationAuthor, NotificationItemData } from "./serverDataType/Notification.js";
2
+ type User = import("./User.js").User;
3
+ /**
4
+ * Item de notification fin. N'étend PAS BaseEntity : il garde la donnée brute, un backref
5
+ * User (pour router les appels via callIsMe + endpointApi) et un backref manager (pour le recount).
6
+ * Les drapeaux lu/vu sont réactifs (objet reactive({...}) — surface scalaire fiable).
7
+ */
8
+ export declare class Notification {
9
+ private readonly _data;
10
+ private readonly _owner;
11
+ private _manager;
12
+ private readonly _state;
13
+ constructor(data: NotificationItemData, owner: User, manager?: Notifications | null);
14
+ static fromData(data: NotificationItemData, owner: User, manager?: Notifications | null): Notification;
15
+ /** Lie l'item à son manager (appelé par le manager au wrap). */
16
+ _attach(manager: Notifications): this;
17
+ get id(): string;
18
+ get isUnread(): boolean;
19
+ get isUnseen(): boolean;
20
+ get data(): NotificationItemData;
21
+ get author(): NotificationAuthor;
22
+ get createdAt(): Date;
23
+ _setReadLocal(): void;
24
+ _setSeenLocal(): void;
25
+ _restoreLocal(unread: boolean, unseen: boolean): void;
26
+ /** Marque CET item comme lu : optimiste + rollback + recount manager. */
27
+ markRead(): Promise<void>;
28
+ /** Pas d'endpoint "seen" par item : flip local uniquement. */
29
+ markSeen(): void;
30
+ toJSON(): NotificationItemData;
31
+ }
32
+ /**
33
+ * Gestionnaire de notifications de l'utilisateur connecté. Accroché à User via `me.notifications`.
34
+ * N'étend PAS BaseEntity. Items dans un tableau SIMPLE (jamais proxifié) ; seuls les scalaires
35
+ * vivent dans un reactive({...}) (loadedUnreadCount, unseenTotal, cursor, hasMore, version).
36
+ * Aucun effect/computed créé ici (pas d'API de dispose -> pas de fuite).
37
+ */
38
+ export declare class Notifications {
39
+ private readonly _owner;
40
+ private readonly _pageSize;
41
+ private _items;
42
+ private readonly _state;
43
+ constructor(owner: User, pageSize?: number);
44
+ get items(): Notification[];
45
+ get loadedUnreadCount(): number;
46
+ get unseenTotal(): number;
47
+ get hasMore(): boolean;
48
+ _recount(): void;
49
+ private _wrap;
50
+ private _commit;
51
+ /** Première page (reset le curseur). Tri serveur : updated desc, 15/page. */
52
+ list({ indexMin }?: {
53
+ indexMin?: number;
54
+ }): Promise<Notification[]>;
55
+ /** Page suivante (curseur indexMin = nb d'items déjà chargés). Concatène par réassignation. */
56
+ loadMore(): Promise<Notification[]>;
57
+ /** Recharge depuis le début. */
58
+ refresh(): Promise<Notification[]>;
59
+ /**
60
+ * Badge : total des NON-VUS via GET_NOTIFICATIONS_COUNT (refreshTimestamp = maintenant ->
61
+ * liste vide + countNotif). Met à jour unseenTotal et le retourne.
62
+ */
63
+ count(): Promise<number>;
64
+ /**
65
+ * PUR : convertit des `NotificationItemData[]` plats (ex. cache React Query après
66
+ * hydratation SSR) en `Notification[]` liées à l'utilisateur, SANS modifier l'état du
67
+ * manager. À utiliser dans un `select` React Query (cache plat -> instances vivantes).
68
+ */
69
+ toItems(data: NotificationItemData[]): Notification[];
70
+ markAllRead(): Promise<void>;
71
+ markAllSeen(): Promise<void>;
72
+ private _bulk;
73
+ /** Supprime toutes les notifications de l'utilisateur + vide l'état local. */
74
+ clear(): Promise<void>;
75
+ toJSON(): NotificationItemData[];
76
+ restore(rawItems: NotificationItemData[]): this;
77
+ static restore(rawItems: NotificationItemData[], owner: User): Notifications;
78
+ }
79
+ export {};
@@ -1,8 +1,10 @@
1
1
  import { BaseEntity } from "./BaseEntity.js";
2
+ import { Notifications } from "./Notifications.js";
2
3
  import type { Badge } from "./Badge.js";
3
4
  import type { PaginatorPage, PaginatorState } from "./BaseEntity.js";
4
5
  import type { ChangePasswordData, DeleteAccountData, GetSubscriptionsAdminData, GetSubscriptionsData, GetOrganizationsNoAdminData, GetOrganizationsAdminData, GetFriendsAdminData } from "./EndpointApi.types.js";
5
6
  import type { Organization } from "./Organization.js";
7
+ import type { NotificationItemData } from "./serverDataType/Notification.js";
6
8
  import type { EntityTypes } from "@/types/entities.js";
7
9
  type ApiClient = import("../ApiClient.js").default;
8
10
  type UserItemNormalized = import("./serverDataType/User.js").UserItemNormalized;
@@ -786,5 +788,33 @@ export declare class User extends BaseEntity<UserItemNormalized> {
786
788
  * ```
787
789
  */
788
790
  demoteFromAdmin(): Promise<unknown>;
791
+ /** Gestionnaire de notifications, créé paresseusement et mis en cache. */
792
+ private _notifications?;
793
+ /**
794
+ * Point d'entrée `me.notifications` (composition, PAS une entité BaseEntity).
795
+ * Sûr : endpointApi/apiClient sont initialisés dans le constructeur avant tout accès getter.
796
+ */
797
+ get notifications(): Notifications;
798
+ /**
799
+ * Récupère les notifications (mode liste). STATELESS + plat (`NotificationItemData[]`),
800
+ * idéal comme `queryFn` React Query / prefetch SSR — ne touche PAS le manager réactif
801
+ * (`me.notifications`). `ApiClient._transformData` garantit `notif` = tableau (map -> array
802
+ * + `id` injecté), donc on fait confiance à la normalisation amont.
803
+ * pathParams.id est OBLIGATOIRE : le défaut "@userId" ne matche pas le pattern de l'endpoint.
804
+ */
805
+ fetchNotifications({ indexMin }?: {
806
+ indexMin?: number;
807
+ }): Promise<NotificationItemData[]>;
808
+ /**
809
+ * Compte les notifications NON VUES (badge). STATELESS. refreshTimestamp = maintenant ->
810
+ * le serveur renvoie `countNotif` (total non-vus) + une liste vide.
811
+ */
812
+ fetchNotificationsCount(): Promise<number>;
813
+ /** Marque une notification comme lue (MARK_NOTIFICATION_AS_READ). */
814
+ markNotificationAsRead(id: string): Promise<unknown>;
815
+ /** Marque toutes les notifications comme vues/lues (NOTIFICATION_UPDATE). */
816
+ markAllNotifications(action: "seen" | "read"): Promise<unknown>;
817
+ /** Supprime toutes les notifications de l'utilisateur (REMOVE_ALL_NOTIFICATIONS). */
818
+ removeAllNotifications(): Promise<unknown>;
789
819
  }
790
820
  export {};
@@ -0,0 +1,73 @@
1
+ import type { DateValue, IdObject } from "./common.js";
2
+ import type EJSONType from "../../EJSONType.js";
3
+ type ObjectIDInstance = InstanceType<typeof EJSONType["ObjectID"]>;
4
+ export type NotificationId = ObjectIDInstance | IdObject | string;
5
+ /** Auteur APRÈS aplatissement par _transformData (la map oid->objet devient un objet). */
6
+ export interface NotificationAuthor {
7
+ id?: string;
8
+ type?: string;
9
+ name: string;
10
+ profilThumbImageUrl?: string;
11
+ [k: string]: unknown;
12
+ }
13
+ export interface NotificationTarget {
14
+ type: string;
15
+ id: string;
16
+ parent?: {
17
+ id: string;
18
+ type: string;
19
+ };
20
+ }
21
+ /** État lu/vu, par destinataire. Un champ ABSENT (clé unset côté serveur) = état "faux". */
22
+ export interface NotificationReadState {
23
+ isUnread?: boolean;
24
+ isUnseen?: boolean;
25
+ }
26
+ export interface NotificationNotify {
27
+ objectType?: string;
28
+ /** keyé par oid de destinataire -> état lu/vu de CE destinataire. */
29
+ id: Record<string, NotificationReadState>;
30
+ displayName?: string;
31
+ icon?: string;
32
+ url?: string;
33
+ label?: string;
34
+ labelArray?: Record<string, string[]>;
35
+ labelAuthorObject?: string;
36
+ repeat?: boolean;
37
+ [k: string]: unknown;
38
+ }
39
+ /** Un élément du tableau `notif` (forme transformée). created/updated : Date instance OU {sec,usec}. */
40
+ export interface NotificationItemData {
41
+ id: string;
42
+ _id: NotificationId;
43
+ type: string;
44
+ verb: string;
45
+ author: NotificationAuthor;
46
+ created: Date | DateValue;
47
+ updated: Date | DateValue;
48
+ target: NotificationTarget;
49
+ notify: NotificationNotify;
50
+ timestamp?: number;
51
+ timeAgo?: string;
52
+ [k: string]: unknown;
53
+ }
54
+ /**
55
+ * Réponse de GET_NOTIFICATIONS (mode liste, body indexMin) APRÈS transform :
56
+ * `notif` est un tableau (la map oid->item est convertie). Vide => [].
57
+ */
58
+ export interface GetNotificationsResult {
59
+ notif: NotificationItemData[];
60
+ [k: string]: unknown;
61
+ }
62
+ /**
63
+ * Réponse de GET_NOTIFICATIONS_COUNT (mode badge, body refreshTimestamp non-nul) :
64
+ * `notif` = notifications mises à jour APRÈS le timestamp (souvent [] si timestamp = maintenant),
65
+ * `countNotif` = nombre TOTAL de notifications NON VUES (isUnseen) — indépendant du timestamp.
66
+ * countNotif n'est présent QUE si refreshTimestamp est fourni et non-nul (sinon mode liste).
67
+ */
68
+ export interface GetNotificationsCountResult {
69
+ notif: NotificationItemData[];
70
+ countNotif?: number;
71
+ [k: string]: unknown;
72
+ }
73
+ export {};
package/types/index.d.ts CHANGED
@@ -37,6 +37,7 @@ declare const cocolightApiClient: {
37
37
  OfflineClientManager: typeof OfflineClientManager;
38
38
  };
39
39
  export default cocolightApiClient;
40
+ export { batch, computed, effect, getSignals, isReactive, isSignal, reactive, subscribeTo, watch } from "./utils/reactive.js";
40
41
  export type { default as Api } from "./Api.js";
41
42
  export type { default as ApiClient } from "./ApiClient.js";
42
43
  export type { default as EndpointApi } from "./api/EndpointApi.js";
@@ -78,6 +79,8 @@ export type * from "./api/serverDataType/Action.js";
78
79
  export type * from "./api/serverDataType/common.js";
79
80
  export type * from "./api/serverDataType/Country.js";
80
81
  export type * from "./api/serverDataType/Zone.js";
82
+ export type * from "./api/serverDataType/Notification.js";
83
+ export type { Notification, Notifications } from "./api/Notifications.js";
81
84
  export type { PaginatorPage, PaginatorState, FundingEnvelopeResult, FundingEnvelopeProjectItem, SearchCostumVariant, SetTypeValue, CostumContextFields, WithCostumContext, EntityType, CoformThematicValue, CoformFilterByPathResult, } from "./api/BaseEntity.js";
82
85
  export { UPDATE_PATH_MAGIC } from "./api/BaseEntity.js";
83
86
  export type { AllStepsData, PendingUploadValue, PendingUpload, ProcessUploadsOptions, AnswerGetOptions, UploadAnswerFileOptions, UploadAnswerFileResult, GetFilesOptions, AnswerFileItem, } from "./api/Answer.js";