@carlonicora/nextjs-jsonapi 1.77.3 → 1.79.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.
Files changed (146) hide show
  1. package/dist/AssistantInterface-BYgI5z1-.d.mts +12 -0
  2. package/dist/AssistantInterface-DfDcz0gJ.d.ts +12 -0
  3. package/dist/AssistantMessageInterface-DWnbd6J7.d.ts +36 -0
  4. package/dist/AssistantMessageInterface-Mla6kgPe.d.mts +36 -0
  5. package/dist/{AuthComponent-Blbs06ud.d.ts → AuthComponent-B6DIk8Vf.d.ts} +1 -1
  6. package/dist/{AuthComponent-huIaK5rm.d.mts → AuthComponent-BKI0ZbtD.d.mts} +1 -1
  7. package/dist/{BlockNoteEditor-7HAAXN3H.mjs → BlockNoteEditor-6CBDTVKV.mjs} +4 -4
  8. package/dist/{BlockNoteEditor-UB7T7V67.js → BlockNoteEditor-EH4HWI7H.js} +14 -14
  9. package/dist/{BlockNoteEditor-UB7T7V67.js.map → BlockNoteEditor-EH4HWI7H.js.map} +1 -1
  10. package/dist/RbacTypes-BTbr27Ew.d.mts +43 -0
  11. package/dist/RbacTypes-BTbr27Ew.d.ts +43 -0
  12. package/dist/{auth.interface-CQJ6A2Cj.d.ts → auth.interface-BBUgMZzs.d.ts} +1 -1
  13. package/dist/{auth.interface-Bdq7-8iV.d.mts → auth.interface-XYEREOD6.d.mts} +1 -1
  14. package/dist/billing/index.js +346 -346
  15. package/dist/billing/index.mjs +3 -3
  16. package/dist/{chunk-FKLP4NED.js → chunk-5IEWLLLD.js} +379 -18
  17. package/dist/chunk-5IEWLLLD.js.map +1 -0
  18. package/dist/{chunk-XI35ALWY.mjs → chunk-BKM5U3DE.mjs} +362 -1
  19. package/dist/chunk-BKM5U3DE.mjs.map +1 -0
  20. package/dist/{chunk-F44ET4AC.mjs → chunk-ENRSFVOS.mjs} +2657 -2264
  21. package/dist/chunk-ENRSFVOS.mjs.map +1 -0
  22. package/dist/{chunk-JOJZRGZL.mjs → chunk-MEWXQEVE.mjs} +38 -29
  23. package/dist/{chunk-JOJZRGZL.mjs.map → chunk-MEWXQEVE.mjs.map} +1 -1
  24. package/dist/{chunk-OTZEXASK.js → chunk-TWDSDTHU.js} +39 -30
  25. package/dist/chunk-TWDSDTHU.js.map +1 -0
  26. package/dist/{chunk-CV7UOUKQ.js → chunk-ZDP3MBUI.js} +1813 -1420
  27. package/dist/chunk-ZDP3MBUI.js.map +1 -0
  28. package/dist/client/index.d.mts +6 -24
  29. package/dist/client/index.d.ts +6 -24
  30. package/dist/client/index.js +4 -10
  31. package/dist/client/index.js.map +1 -1
  32. package/dist/client/index.mjs +3 -9
  33. package/dist/components/index.d.mts +51 -34
  34. package/dist/components/index.d.ts +51 -34
  35. package/dist/components/index.js +4 -4
  36. package/dist/components/index.js.map +1 -1
  37. package/dist/components/index.mjs +9 -9
  38. package/dist/{config-B3jKt9P7.d.ts → config-B5oBQVEA.d.ts} +1 -1
  39. package/dist/{config-DkHF61xA.d.mts → config-Bx_uh22h.d.mts} +1 -1
  40. package/dist/contexts/index.d.mts +65 -4
  41. package/dist/contexts/index.d.ts +65 -4
  42. package/dist/contexts/index.js +12 -4
  43. package/dist/contexts/index.js.map +1 -1
  44. package/dist/contexts/index.mjs +11 -3
  45. package/dist/core/index.d.mts +126 -11
  46. package/dist/core/index.d.ts +126 -11
  47. package/dist/core/index.js +16 -2
  48. package/dist/core/index.js.map +1 -1
  49. package/dist/core/index.mjs +15 -1
  50. package/dist/index.d.mts +118 -20
  51. package/dist/index.d.ts +118 -20
  52. package/dist/index.js +19 -3
  53. package/dist/index.js.map +1 -1
  54. package/dist/index.mjs +18 -2
  55. package/dist/{notification.interface-DG6obXUH.d.mts → notification.interface-DLZGtV7Z.d.mts} +1 -1
  56. package/dist/{notification.interface-DcSuc9CL.d.ts → notification.interface-aLEJbA_g.d.ts} +1 -1
  57. package/dist/{s3.service-DGilbikH.d.mts → s3.service-CVgLWaDc.d.mts} +2 -2
  58. package/dist/{s3.service-DjwEQJPe.d.ts → s3.service-SLlX0Zbz.d.ts} +2 -2
  59. package/dist/server/index.d.mts +3 -3
  60. package/dist/server/index.d.ts +3 -3
  61. package/dist/server/index.js +3 -3
  62. package/dist/server/index.mjs +1 -1
  63. package/dist/useDataListRetriever-BqJSFBck.d.mts +33 -0
  64. package/dist/useDataListRetriever-BqJSFBck.d.ts +33 -0
  65. package/dist/{useSocket-CmzVtg32.d.mts → useSocket-BkxHHujj.d.mts} +1 -1
  66. package/dist/{useSocket-8eUtnL7J.d.ts → useSocket-CMDjWFYm.d.ts} +1 -1
  67. package/package.json +1 -1
  68. package/src/client/index.ts +0 -4
  69. package/src/components/index.ts +2 -3
  70. package/src/contexts/index.ts +2 -0
  71. package/src/core/index.ts +4 -0
  72. package/src/core/registry/ModuleRegistry.ts +10 -0
  73. package/src/features/assistant/AssistantModule.ts +19 -0
  74. package/src/features/assistant/components/containers/AssistantContainer.tsx +56 -0
  75. package/src/features/assistant/components/containers/__tests__/AssistantContainer.spec.tsx +101 -0
  76. package/src/features/assistant/components/index.ts +1 -0
  77. package/src/features/assistant/components/parts/AssistantComposer.tsx +56 -0
  78. package/src/features/assistant/components/parts/AssistantEmptyState.tsx +47 -0
  79. package/src/features/assistant/components/parts/AssistantSidebar.tsx +64 -0
  80. package/src/features/assistant/components/parts/AssistantStatusLine.tsx +19 -0
  81. package/src/features/assistant/components/parts/AssistantThread.tsx +36 -0
  82. package/src/features/assistant/components/parts/AssistantThreadHeader.tsx +91 -0
  83. package/src/features/assistant/components/parts/__tests__/AssistantComposer.spec.tsx +32 -0
  84. package/src/features/assistant/components/parts/__tests__/AssistantEmptyState.spec.tsx +27 -0
  85. package/src/features/assistant/components/parts/__tests__/AssistantSidebar.spec.tsx +58 -0
  86. package/src/features/assistant/components/parts/__tests__/AssistantStatusLine.spec.tsx +19 -0
  87. package/src/features/assistant/components/parts/__tests__/AssistantThread.spec.tsx +39 -0
  88. package/src/features/assistant/components/parts/__tests__/AssistantThreadHeader.spec.tsx +67 -0
  89. package/src/features/assistant/contexts/AssistantContext.tsx +255 -0
  90. package/src/features/assistant/contexts/__tests__/AssistantContext.spec.tsx +375 -0
  91. package/src/features/assistant/data/Assistant.ts +37 -0
  92. package/src/features/assistant/data/AssistantInterface.ts +11 -0
  93. package/src/features/assistant/data/AssistantService.ts +79 -0
  94. package/src/features/assistant/data/index.ts +3 -0
  95. package/src/features/assistant/index.ts +2 -0
  96. package/src/features/assistant/utils/__tests__/groupThreadsByBucket.spec.ts +24 -0
  97. package/src/features/assistant/utils/__tests__/resolveReferenceableModules.spec.ts +92 -0
  98. package/src/features/assistant/utils/groupThreadsByBucket.ts +26 -0
  99. package/src/features/assistant/utils/resolveReferenceableModules.ts +14 -0
  100. package/src/features/assistant-message/AssistantMessageModule.ts +28 -0
  101. package/src/features/assistant-message/components/MessageItem.tsx +60 -0
  102. package/src/features/assistant-message/components/MessageList.tsx +38 -0
  103. package/src/features/assistant-message/components/__tests__/MessageItem.spec.tsx +108 -0
  104. package/src/features/assistant-message/components/index.ts +2 -0
  105. package/src/features/assistant-message/components/parts/ReferenceBadges.tsx +46 -0
  106. package/src/features/assistant-message/components/parts/SuggestedFollowUps.tsx +52 -0
  107. package/src/features/assistant-message/components/parts/__tests__/ReferenceBadges.spec.tsx +59 -0
  108. package/src/features/assistant-message/components/parts/__tests__/SuggestedFollowUps.spec.tsx +29 -0
  109. package/src/features/assistant-message/data/AssistantMessage.ts +95 -0
  110. package/src/features/assistant-message/data/AssistantMessageInterface.ts +21 -0
  111. package/src/features/assistant-message/data/AssistantMessageService.ts +40 -0
  112. package/src/features/assistant-message/data/__tests__/AssistantMessage.spec.ts +158 -0
  113. package/src/features/assistant-message/data/index.ts +3 -0
  114. package/src/features/assistant-message/index.ts +2 -0
  115. package/src/features/rbac/components/RbacContainer.tsx +318 -49
  116. package/src/features/rbac/components/RbacPermissionPicker.tsx +144 -121
  117. package/src/features/rbac/contexts/RbacContext.tsx +209 -0
  118. package/src/features/rbac/contexts/index.ts +1 -0
  119. package/src/features/rbac/data/RbacMatrixModel.ts +84 -0
  120. package/src/features/rbac/data/RbacService.ts +61 -33
  121. package/src/features/rbac/data/RbacTypes.ts +28 -0
  122. package/src/features/rbac/data/index.ts +1 -0
  123. package/src/features/rbac/index.ts +1 -10
  124. package/src/features/rbac/rbac.module.ts +13 -0
  125. package/src/features/user/contexts/CurrentUserContext.tsx +5 -13
  126. package/src/features/user/contexts/__tests__/CurrentUserContext.spec.tsx +141 -0
  127. package/src/index.ts +4 -0
  128. package/dist/HowToInterface-BKhnkzBp.d.ts +0 -17
  129. package/dist/HowToInterface-Cj8OuQFf.d.mts +0 -17
  130. package/dist/ModulePathsInterface-BrdqgteS.d.mts +0 -31
  131. package/dist/ModulePathsInterface-DJKs7s_s.d.ts +0 -31
  132. package/dist/chunk-CV7UOUKQ.js.map +0 -1
  133. package/dist/chunk-F44ET4AC.mjs.map +0 -1
  134. package/dist/chunk-FKLP4NED.js.map +0 -1
  135. package/dist/chunk-OTZEXASK.js.map +0 -1
  136. package/dist/chunk-XI35ALWY.mjs.map +0 -1
  137. package/dist/useRbacState-C88O-5L8.d.ts +0 -77
  138. package/dist/useRbacState-mqYiRp3J.d.mts +0 -77
  139. package/src/features/rbac/components/RbacFeatureSection.tsx +0 -66
  140. package/src/features/rbac/components/RbacModuleTable.tsx +0 -121
  141. package/src/features/rbac/components/RbacToolbar.tsx +0 -40
  142. package/src/features/rbac/hooks/useRbacState.test.ts +0 -180
  143. package/src/features/rbac/hooks/useRbacState.ts +0 -319
  144. package/src/features/rbac/utils/RbacMigrationGenerator.test.ts +0 -124
  145. package/src/features/rbac/utils/RbacMigrationGenerator.ts +0 -184
  146. /package/dist/{BlockNoteEditor-7HAAXN3H.mjs.map → BlockNoteEditor-6CBDTVKV.mjs.map} +0 -0
@@ -1,47 +1,75 @@
1
1
  import { AbstractService, EndpointCreator, HttpMethod, Modules } from "../../../core";
2
- import { FeatureInterface } from "../../feature";
3
- import { RoleInterface } from "../../role";
4
- import { PermissionMappingInterface } from "./PermissionMappingInterface";
5
- import { ModulePathsInterface } from "./ModulePathsInterface";
2
+ import type { RbacMatrixModel } from "./RbacMatrixModel";
3
+ import type { RbacMatrix } from "./RbacTypes";
6
4
 
5
+ /**
6
+ * RbacService — fetches RBAC configuration for the admin UI.
7
+ *
8
+ * Declarative-matrix methods (`fetchMatrix`, `saveMatrix`) talk to the
9
+ * dev-only endpoints added in
10
+ * `packages/nestjs-neo4jsonapi/.../rbac-dev.controller.ts`. The controller
11
+ * speaks JSON:API (singleton resource with `type: "rbac-matrix"`, `id:
12
+ * "singleton"`), so these methods go through the standard `callApi()`
13
+ * pipeline like every other service in the codebase.
14
+ *
15
+ * The backend only registers these routes when `devMode` is enabled on
16
+ * `RbacModule.register` (see `apps/api/src/features/features.modules.ts`).
17
+ * In production the routes return 404; callers should guard with a dev-mode
18
+ * check.
19
+ */
7
20
  export class RbacService extends AbstractService {
8
- static async getFeatures(): Promise<FeatureInterface[]> {
9
- const endpoint = new EndpointCreator({ endpoint: Modules.Feature }).addAdditionalParam("fetchAll", "true");
21
+ /**
22
+ * Fetch the current RBAC matrix plus each module's known BFS relationship
23
+ * paths (used by the permission picker as scope suggestions).
24
+ *
25
+ * Dev-only endpoint — see class header.
26
+ */
27
+ static async fetchMatrix(): Promise<{
28
+ matrix: RbacMatrix;
29
+ modulePaths: Record<string, readonly string[]>;
30
+ }> {
31
+ const endpoint = new EndpointCreator({ endpoint: Modules.RbacMatrix }).generate();
10
32
 
11
- return this.callApi<FeatureInterface[]>({
12
- type: Modules.Feature,
33
+ const model = await this.callApi<RbacMatrixModel>({
34
+ type: Modules.RbacMatrix,
13
35
  method: HttpMethod.GET,
14
- endpoint: endpoint.generate(),
36
+ endpoint,
15
37
  });
16
- }
17
-
18
- static async getRoles(): Promise<RoleInterface[]> {
19
- const endpoint = new EndpointCreator({ endpoint: Modules.Role }).addAdditionalParam("fetchAll", "true");
20
38
 
21
- return this.callApi<RoleInterface[]>({
22
- type: Modules.Role,
23
- method: HttpMethod.GET,
24
- endpoint: endpoint.generate(),
25
- });
39
+ return {
40
+ matrix: model.matrix ?? {},
41
+ modulePaths: model.modulePaths ?? {},
42
+ };
26
43
  }
27
44
 
28
- static async getPermissionMappings(): Promise<PermissionMappingInterface[]> {
29
- const endpoint = new EndpointCreator({ endpoint: Modules.PermissionMapping });
45
+ /**
46
+ * Persist a matrix back to the declarative `permissions.ts` file.
47
+ *
48
+ * The backend serializes the matrix to formatted TypeScript using the
49
+ * provided `roleNames` / `moduleNames` lookup tables (so the emitted file
50
+ * references `RoleId.X` / `ModuleId.X` rather than raw UUIDs) and writes
51
+ * it to `outputPath` (absolute, or relative to the repo root).
52
+ *
53
+ * Dev-only endpoint — see class header.
54
+ */
55
+ static async saveMatrix(args: {
56
+ matrix: RbacMatrix;
57
+ roleNames: Record<string, string>;
58
+ moduleNames: Record<string, string>;
59
+ outputPath: string;
60
+ }): Promise<{ bytesWritten: number; path: string }> {
61
+ const endpoint = new EndpointCreator({ endpoint: Modules.RbacMatrix }).generate();
30
62
 
31
- return this.callApi<PermissionMappingInterface[]>({
32
- type: Modules.PermissionMapping,
33
- method: HttpMethod.GET,
34
- endpoint: endpoint.generate(),
63
+ const model = await this.callApi<RbacMatrixModel>({
64
+ type: Modules.RbacMatrix,
65
+ method: HttpMethod.PUT,
66
+ endpoint,
67
+ input: args,
35
68
  });
36
- }
37
-
38
- static async getModuleRelationshipPaths(): Promise<ModulePathsInterface[]> {
39
- const endpoint = new EndpointCreator({ endpoint: Modules.ModulePaths });
40
69
 
41
- return this.callApi<ModulePathsInterface[]>({
42
- type: Modules.ModulePaths,
43
- method: HttpMethod.GET,
44
- endpoint: endpoint.generate(),
45
- });
70
+ return {
71
+ bytesWritten: model.bytesWritten ?? 0,
72
+ path: model.path ?? "",
73
+ };
46
74
  }
47
75
  }
@@ -13,3 +13,31 @@ export type PermissionsMap = {
13
13
  update?: PermissionValue;
14
14
  delete?: PermissionValue;
15
15
  };
16
+
17
+ /**
18
+ * Declarative-RBAC matrix types.
19
+ *
20
+ * Mirror of the library types defined in
21
+ * `packages/nestjs-neo4jsonapi/src/foundations/rbac/dsl/types.ts`.
22
+ * Frontend does not import from backend, so the shape is redefined here.
23
+ *
24
+ * A `PermToken` represents a single permission entry:
25
+ * - `scope: true` → unconditional (e.g. full read of the module)
26
+ * - `scope: false` → nothing (rarely used, mostly a placeholder)
27
+ * - `scope: "path"` → scoped by relationship path (e.g. "orders.account")
28
+ */
29
+ export type PermToken = { action: string; scope: boolean | string };
30
+
31
+ /**
32
+ * A per-module block of the matrix. Always has a `default` row (permissions
33
+ * granted to every role). Additional keys are role IDs → role-specific
34
+ * permission tokens that are unioned with `default` to produce the effective
35
+ * permissions for that role in that module.
36
+ */
37
+ export type RbacModuleBlock = { default: PermToken[] } & Record<string, PermToken[]>;
38
+
39
+ /**
40
+ * The full RBAC matrix as served by the dev endpoint `GET /_dev/rbac/matrix`.
41
+ * Keys are module IDs; values are module blocks.
42
+ */
43
+ export type RbacMatrix = Record<string, RbacModuleBlock>;
@@ -3,4 +3,5 @@ export * from "./PermissionMapping";
3
3
  export * from "./PermissionMappingInterface";
4
4
  export * from "./ModulePaths";
5
5
  export * from "./ModulePathsInterface";
6
+ export * from "./RbacMatrixModel";
6
7
  export * from "./RbacService";
@@ -1,19 +1,10 @@
1
1
  // Data layer
2
2
  export * from "./data";
3
3
 
4
- // Hooks
5
- export { useRbacState } from "./hooks/useRbacState";
6
-
7
- // Utils
8
- export { generateMigrationFile, downloadMigrationFile } from "./utils/RbacMigrationGenerator";
9
-
10
4
  // Components
11
5
  export { RbacContainer } from "./components/RbacContainer";
12
- export { RbacToolbar } from "./components/RbacToolbar";
13
- export { RbacFeatureSection } from "./components/RbacFeatureSection";
14
- export { RbacModuleTable } from "./components/RbacModuleTable";
15
6
  export { RbacPermissionCell } from "./components/RbacPermissionCell";
16
7
  export { RbacPermissionPicker } from "./components/RbacPermissionPicker";
17
8
 
18
9
  // Module registrations
19
- export { PermissionMappingModule, ModulePathsModule } from "./rbac.module";
10
+ export { PermissionMappingModule, ModulePathsModule, RbacMatrixModule } from "./rbac.module";
@@ -1,6 +1,7 @@
1
1
  import { ModuleFactory } from "../../permissions";
2
2
  import { PermissionMapping } from "./data/PermissionMapping";
3
3
  import { ModulePaths } from "./data/ModulePaths";
4
+ import { RbacMatrixModel } from "./data/RbacMatrixModel";
4
5
 
5
6
  export const PermissionMappingModule = (factory: ModuleFactory) =>
6
7
  factory({
@@ -17,3 +18,15 @@ export const ModulePathsModule = (factory: ModuleFactory) =>
17
18
  model: ModulePaths,
18
19
  moduleId: "f4fb3f01-a947-4c2e-89c8-354a518cdb13",
19
20
  });
21
+
22
+ /**
23
+ * Dev-only matrix module. The `name` is the URL path of the dev singleton
24
+ * endpoint (`GET|PUT _dev/rbac/matrix`), NOT a plural resource collection.
25
+ * This module is only useful when the backend is running with `devMode: true`
26
+ * on `RbacModule.register`.
27
+ */
28
+ export const RbacMatrixModule = (factory: ModuleFactory) =>
29
+ factory({
30
+ name: "_dev/rbac/matrix",
31
+ model: RbacMatrixModel,
32
+ });
@@ -1,6 +1,5 @@
1
1
  "use client";
2
2
 
3
- import { getCookie } from "cookies-next";
4
3
  import { useAtom } from "jotai";
5
4
  import { atomWithStorage } from "jotai/utils";
6
5
  import { usePathname } from "next/navigation";
@@ -46,11 +45,6 @@ export const CurrentUserProvider = ({ children }: { children: React.ReactNode })
46
45
 
47
46
  const [dehydratedUser, setDehydratedUser] = useAtom(userAtom);
48
47
 
49
- useEffect(() => {
50
- const token = getCookie("token");
51
- if (!token && dehydratedUser) setDehydratedUser(null);
52
- }, [dehydratedUser, setDehydratedUser]);
53
-
54
48
  const matchUrlToModule = (_params?: { path: string }): ModuleWithPermissions | undefined => {
55
49
  const moduleKeys = Object.getOwnPropertyNames(Modules).filter(
56
50
  (key) => key !== "prototype" && key !== "_factory" && key !== "length" && key !== "name",
@@ -148,13 +142,9 @@ export const CurrentUserProvider = ({ children }: { children: React.ReactNode })
148
142
  try {
149
143
  const fullUser = await UserService.findFullUser();
150
144
  if (fullUser) {
151
- const dehydrated = fullUser.dehydrate();
152
-
153
- setDehydratedUser(dehydrated as any);
154
- setUser(fullUser);
155
-
156
- // Update authentication cookies with fresh user data
157
- // Skip when triggered by WebSocket to prevent page reload (Server Actions modify cookies)
145
+ // Update authentication cookies with fresh user data BEFORE writing the atom,
146
+ // so downstream observers see cookies-then-user, never user-then-cookies.
147
+ // Skip when triggered by WebSocket to prevent page reload (Server Actions modify cookies).
158
148
  if (!options?.skipCookieUpdate) {
159
149
  await getTokenHandler()?.updateToken({
160
150
  userId: fullUser.id,
@@ -167,6 +157,8 @@ export const CurrentUserProvider = ({ children }: { children: React.ReactNode })
167
157
  })),
168
158
  });
169
159
  }
160
+
161
+ setDehydratedUser(fullUser.dehydrate() as any);
170
162
  }
171
163
  } catch (error) {
172
164
  console.error("Failed to refresh user data:", error);
@@ -0,0 +1,141 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { render } from "@testing-library/react";
3
+
4
+ vi.mock("cookies-next", () => ({
5
+ getCookie: vi.fn(() => undefined),
6
+ }));
7
+
8
+ vi.mock("next/navigation", () => ({
9
+ usePathname: () => "/",
10
+ }));
11
+
12
+ vi.mock("../../../../contexts/SocketContext", () => ({
13
+ useSocketContext: () => ({ socket: null, isConnected: false }),
14
+ }));
15
+
16
+ vi.mock("../../../../core", () => ({
17
+ Modules: { User: { name: "users" } },
18
+ rehydrate: (_module: unknown, data: any) => data,
19
+ }));
20
+
21
+ vi.mock("../../../../permissions", () => ({
22
+ Action: { Read: "read", Write: "write", Create: "create", Update: "update", Delete: "delete" },
23
+ checkPermissions: () => true,
24
+ }));
25
+
26
+ vi.mock("../../../../roles", () => ({
27
+ getRoleId: () => ({ Administrator: "admin" }),
28
+ }));
29
+
30
+ vi.mock("../../../auth/config", () => ({
31
+ getTokenHandler: vi.fn(() => ({
32
+ updateToken: vi.fn().mockResolvedValue(undefined),
33
+ removeToken: vi.fn().mockResolvedValue(undefined),
34
+ })),
35
+ }));
36
+
37
+ vi.mock("../../data", () => ({
38
+ UserService: {
39
+ findFullUser: vi.fn(),
40
+ },
41
+ }));
42
+
43
+ import { CurrentUserProvider, useCurrentUserContext } from "../CurrentUserContext";
44
+ import { getCookie } from "cookies-next";
45
+ import { getTokenHandler } from "../../../auth/config";
46
+ import { UserService } from "../../data";
47
+
48
+ function Consumer() {
49
+ const { currentUser } = useCurrentUserContext();
50
+ return <div data-testid="current-user-id">{currentUser ? (currentUser as { id: string }).id : "null"}</div>;
51
+ }
52
+
53
+ describe("CurrentUserProvider", () => {
54
+ beforeEach(() => {
55
+ localStorage.clear();
56
+ vi.clearAllMocks();
57
+ });
58
+
59
+ it("keeps user hydrated from localStorage when token cookie is absent", async () => {
60
+ const storedUser = {
61
+ id: "user-1",
62
+ roles: [],
63
+ company: null,
64
+ modules: [],
65
+ };
66
+ localStorage.setItem("user", JSON.stringify(storedUser));
67
+ vi.mocked(getCookie).mockReturnValue(undefined);
68
+
69
+ const { getByTestId } = render(
70
+ <CurrentUserProvider>
71
+ <Consumer />
72
+ </CurrentUserProvider>,
73
+ );
74
+
75
+ await new Promise((resolve) => setTimeout(resolve, 100));
76
+
77
+ expect(getByTestId("current-user-id").textContent).toBe("user-1");
78
+ expect(localStorage.getItem("user")).toBe(JSON.stringify(storedUser));
79
+ });
80
+
81
+ it("awaits updateToken before writing the user atom in refreshUser", async () => {
82
+ const oldUser = { id: "old", roles: [], company: null, modules: [] };
83
+ localStorage.setItem("user", JSON.stringify(oldUser));
84
+
85
+ const newDehydrated = { id: "new", roles: [], company: null, modules: [] };
86
+ const newFullUser = {
87
+ id: "new",
88
+ roles: [],
89
+ company: null,
90
+ modules: [],
91
+ dehydrate: () => newDehydrated,
92
+ };
93
+ vi.mocked(UserService.findFullUser).mockResolvedValue(newFullUser as any);
94
+
95
+ let resolveUpdateToken!: () => void;
96
+ const updateTokenPromise = new Promise<void>((resolve) => {
97
+ resolveUpdateToken = resolve;
98
+ });
99
+ const updateToken = vi.fn(async () => {
100
+ await updateTokenPromise;
101
+ });
102
+ vi.mocked(getTokenHandler).mockReturnValue({
103
+ updateToken,
104
+ removeToken: vi.fn().mockResolvedValue(undefined),
105
+ } as any);
106
+
107
+ let capturedRefreshUser: ((options?: { skipCookieUpdate?: boolean }) => Promise<void>) | undefined;
108
+ function InnerConsumer() {
109
+ const ctx = useCurrentUserContext();
110
+ capturedRefreshUser = ctx.refreshUser;
111
+ return (
112
+ <div data-testid="current-user-id">{ctx.currentUser ? (ctx.currentUser as { id: string }).id : "null"}</div>
113
+ );
114
+ }
115
+
116
+ const { getByTestId } = render(
117
+ <CurrentUserProvider>
118
+ <InnerConsumer />
119
+ </CurrentUserProvider>,
120
+ );
121
+
122
+ await new Promise((resolve) => setTimeout(resolve, 50));
123
+ expect(getByTestId("current-user-id").textContent).toBe("old");
124
+
125
+ expect(capturedRefreshUser).toBeDefined();
126
+ const refreshPromise = capturedRefreshUser!();
127
+
128
+ await Promise.resolve();
129
+ await Promise.resolve();
130
+ await new Promise((resolve) => setTimeout(resolve, 50));
131
+
132
+ expect(updateToken).toHaveBeenCalledTimes(1);
133
+ expect(getByTestId("current-user-id").textContent).toBe("old");
134
+
135
+ resolveUpdateToken();
136
+ await refreshPromise;
137
+
138
+ await new Promise((resolve) => setTimeout(resolve, 50));
139
+ expect(getByTestId("current-user-id").textContent).toBe("new");
140
+ });
141
+ });
package/src/index.ts CHANGED
@@ -46,3 +46,7 @@ export { showToast, showError, dismissToast, showCustomToast, type ToastOptions
46
46
  // RBAC feature (data + modules only; components via /components, hooks via /client)
47
47
  export * from "./features/rbac/data";
48
48
  export * from "./features/rbac/rbac.module";
49
+
50
+ // Assistant + AssistantMessage feature data
51
+ export * from "./features/assistant/data";
52
+ export * from "./features/assistant-message/data";
@@ -1,17 +0,0 @@
1
- import { C as ContentInterface, a as ContentInput } from './notification.interface-DcSuc9CL.js';
2
-
3
- type BreadcrumbItemData = {
4
- name: string;
5
- href?: string;
6
- };
7
-
8
- type HowToInput = ContentInput & {
9
- description?: any;
10
- pages?: string | undefined | null;
11
- };
12
- interface HowToInterface extends ContentInterface {
13
- get description(): any;
14
- get pages(): string | undefined;
15
- }
16
-
17
- export type { BreadcrumbItemData as B, HowToInput as H, HowToInterface as a };
@@ -1,17 +0,0 @@
1
- import { C as ContentInterface, a as ContentInput } from './notification.interface-DG6obXUH.mjs';
2
-
3
- type BreadcrumbItemData = {
4
- name: string;
5
- href?: string;
6
- };
7
-
8
- type HowToInput = ContentInput & {
9
- description?: any;
10
- pages?: string | undefined | null;
11
- };
12
- interface HowToInterface extends ContentInterface {
13
- get description(): any;
14
- get pages(): string | undefined;
15
- }
16
-
17
- export type { BreadcrumbItemData as B, HowToInput as H, HowToInterface as a };
@@ -1,31 +0,0 @@
1
- import { A as ApiDataInterface } from './ApiDataInterface-BcZeXy5X.mjs';
2
-
3
- declare const COMPANY_ADMINISTRATOR_ROLE_ID = "2e1eee00-6cba-4506-9059-ccd24e4ea5b0";
4
- type PermissionValue = boolean | string;
5
- type ActionType = "read" | "create" | "update" | "delete";
6
- declare const ACTION_TYPES: ActionType[];
7
- /** The permissions object shape used by both Module and PermissionMapping entities */
8
- type PermissionsMap = {
9
- create?: PermissionValue;
10
- read?: PermissionValue;
11
- update?: PermissionValue;
12
- delete?: PermissionValue;
13
- };
14
-
15
- interface PermissionMappingInterface extends ApiDataInterface {
16
- get roleId(): string;
17
- get moduleId(): string;
18
- get permissions(): {
19
- create?: boolean | string;
20
- read?: boolean | string;
21
- update?: boolean | string;
22
- delete?: boolean | string;
23
- };
24
- }
25
-
26
- interface ModulePathsInterface extends ApiDataInterface {
27
- get moduleId(): string;
28
- get paths(): string[];
29
- }
30
-
31
- export { type ActionType as A, COMPANY_ADMINISTRATOR_ROLE_ID as C, type ModulePathsInterface as M, type PermissionMappingInterface as P, type PermissionValue as a, type PermissionsMap as b, ACTION_TYPES as c };
@@ -1,31 +0,0 @@
1
- import { A as ApiDataInterface } from './ApiDataInterface-BcZeXy5X.js';
2
-
3
- declare const COMPANY_ADMINISTRATOR_ROLE_ID = "2e1eee00-6cba-4506-9059-ccd24e4ea5b0";
4
- type PermissionValue = boolean | string;
5
- type ActionType = "read" | "create" | "update" | "delete";
6
- declare const ACTION_TYPES: ActionType[];
7
- /** The permissions object shape used by both Module and PermissionMapping entities */
8
- type PermissionsMap = {
9
- create?: PermissionValue;
10
- read?: PermissionValue;
11
- update?: PermissionValue;
12
- delete?: PermissionValue;
13
- };
14
-
15
- interface PermissionMappingInterface extends ApiDataInterface {
16
- get roleId(): string;
17
- get moduleId(): string;
18
- get permissions(): {
19
- create?: boolean | string;
20
- read?: boolean | string;
21
- update?: boolean | string;
22
- delete?: boolean | string;
23
- };
24
- }
25
-
26
- interface ModulePathsInterface extends ApiDataInterface {
27
- get moduleId(): string;
28
- get paths(): string[];
29
- }
30
-
31
- export { type ActionType as A, COMPANY_ADMINISTRATOR_ROLE_ID as C, type ModulePathsInterface as M, type PermissionMappingInterface as P, type PermissionValue as a, type PermissionsMap as b, ACTION_TYPES as c };