@carlonicora/nextjs-jsonapi 1.0.3 → 1.0.4

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 (261) hide show
  1. package/package.json +2 -1
  2. package/src/atoms/index.ts +1 -0
  3. package/src/atoms/recentPagesAtom.ts +10 -0
  4. package/src/client/context/JsonApiContext.ts +61 -0
  5. package/src/client/context/JsonApiProvider.tsx +27 -0
  6. package/src/client/context/index.ts +2 -0
  7. package/src/client/hooks/index.ts +3 -0
  8. package/src/client/hooks/useJsonApiGet.ts +188 -0
  9. package/src/client/hooks/useJsonApiMutation.ts +193 -0
  10. package/src/client/hooks/useRehydration.ts +47 -0
  11. package/src/client/index.ts +11 -0
  12. package/src/client/request.ts +97 -0
  13. package/src/client/token.ts +10 -0
  14. package/src/components/containers/PageContainer.tsx +15 -0
  15. package/src/components/containers/ReactMarkdownContainer.tsx +119 -0
  16. package/src/components/containers/TabsContainer.tsx +93 -0
  17. package/src/components/containers/index.ts +3 -0
  18. package/src/components/contents/AttributeElement.tsx +20 -0
  19. package/src/components/contents/index.ts +1 -0
  20. package/src/components/details/AllowedUsersDetails.tsx +23 -0
  21. package/src/components/details/index.ts +1 -0
  22. package/src/components/editors/BlockNoteDiffInlineContent.tsx +152 -0
  23. package/src/components/editors/BlockNoteEditor.tsx +404 -0
  24. package/src/components/editors/BlockNoteEditorContainer.tsx +13 -0
  25. package/src/components/editors/BlockNoteEditorFormattingToolbar.tsx +38 -0
  26. package/src/components/editors/index.ts +1 -0
  27. package/src/components/errors/ErrorDetails.tsx +41 -0
  28. package/src/components/errors/errorToast.ts +9 -0
  29. package/src/components/errors/index.ts +2 -0
  30. package/src/components/forms/CommonAssociationForm.tsx +162 -0
  31. package/src/components/forms/CommonDeleter.tsx +94 -0
  32. package/src/components/forms/CommonEditorButtons.tsx +30 -0
  33. package/src/components/forms/CommonEditorHeader.tsx +35 -0
  34. package/src/components/forms/CommonEditorTrigger.tsx +26 -0
  35. package/src/components/forms/DatePickerPopover.tsx +219 -0
  36. package/src/components/forms/DateRangeSelector.tsx +110 -0
  37. package/src/components/forms/FileUploader.tsx +324 -0
  38. package/src/components/forms/FormCheckbox.tsx +66 -0
  39. package/src/components/forms/FormContainerGeneric.tsx +39 -0
  40. package/src/components/forms/FormDate.tsx +247 -0
  41. package/src/components/forms/FormDateTime.tsx +231 -0
  42. package/src/components/forms/FormInput.tsx +110 -0
  43. package/src/components/forms/FormPassword.tsx +54 -0
  44. package/src/components/forms/FormPlaceAutocomplete.tsx +286 -0
  45. package/src/components/forms/FormSelect.tsx +72 -0
  46. package/src/components/forms/FormSlider.tsx +51 -0
  47. package/src/components/forms/FormSwitch.tsx +25 -0
  48. package/src/components/forms/FormTextarea.tsx +44 -0
  49. package/src/components/forms/MultiFileUploader.tsx +107 -0
  50. package/src/components/forms/PasswordInput.tsx +47 -0
  51. package/src/components/forms/index.ts +21 -0
  52. package/src/components/index.ts +11 -0
  53. package/src/components/navigations/Breadcrumb.tsx +83 -0
  54. package/src/components/navigations/ContentTitle.tsx +39 -0
  55. package/src/components/navigations/Header.tsx +27 -0
  56. package/src/components/navigations/ModeToggleSwitch.tsx +25 -0
  57. package/src/components/navigations/PageSection.tsx +64 -0
  58. package/src/components/navigations/RecentPagesNavigator.tsx +52 -0
  59. package/src/components/navigations/index.ts +6 -0
  60. package/src/components/pages/PageContainerContentDetails.tsx +76 -0
  61. package/src/components/pages/PageContentContainer.tsx +31 -0
  62. package/src/components/pages/index.ts +2 -0
  63. package/src/components/tables/ContentListTable.tsx +165 -0
  64. package/src/components/tables/ContentTableSearch.tsx +105 -0
  65. package/src/components/tables/cells/cell.component.tsx +18 -0
  66. package/src/components/tables/cells/cell.date.tsx +16 -0
  67. package/src/components/tables/cells/cell.id.tsx +27 -0
  68. package/src/components/tables/cells/cell.link.tsx +18 -0
  69. package/src/components/tables/cells/cell.text.tsx +12 -0
  70. package/src/components/tables/cells/cell.url.tsx +13 -0
  71. package/src/components/tables/cells/index.ts +5 -0
  72. package/src/components/tables/index.ts +3 -0
  73. package/src/contexts/SharedContext.tsx +35 -0
  74. package/src/contexts/index.ts +2 -0
  75. package/src/core/abstracts/AbstractApiData.ts +138 -0
  76. package/src/core/abstracts/AbstractService.ts +263 -0
  77. package/src/core/abstracts/index.ts +2 -0
  78. package/src/core/endpoint/EndpointCreator.ts +97 -0
  79. package/src/core/endpoint/index.ts +1 -0
  80. package/src/core/factories/JsonApiDataFactory.ts +12 -0
  81. package/src/core/factories/RehydrationFactory.ts +30 -0
  82. package/src/core/factories/index.ts +2 -0
  83. package/src/core/fields/FieldSelector.ts +15 -0
  84. package/src/core/fields/index.ts +1 -0
  85. package/src/core/index.ts +20 -0
  86. package/src/core/interfaces/ApiData.ts +8 -0
  87. package/src/core/interfaces/ApiDataInterface.ts +15 -0
  88. package/src/core/interfaces/ApiRequestDataTypeInterface.ts +14 -0
  89. package/src/core/interfaces/ApiResponseInterface.ts +17 -0
  90. package/src/core/interfaces/JsonApiHydratedDataInterface.ts +5 -0
  91. package/src/core/interfaces/index.ts +5 -0
  92. package/src/core/registry/DataClassRegistry.ts +51 -0
  93. package/src/core/registry/ModuleRegistrar.ts +43 -0
  94. package/src/core/registry/ModuleRegistry.ts +64 -0
  95. package/src/core/registry/index.ts +3 -0
  96. package/src/core/utils/index.ts +2 -0
  97. package/src/core/utils/rehydrate.ts +24 -0
  98. package/src/core/utils/translateResponse.ts +125 -0
  99. package/src/features/auth/auth.module.ts +9 -0
  100. package/src/features/auth/config.ts +57 -0
  101. package/src/features/auth/data/auth.interface.ts +31 -0
  102. package/src/features/auth/data/auth.service.ts +159 -0
  103. package/src/features/auth/data/auth.ts +54 -0
  104. package/src/features/auth/data/index.ts +3 -0
  105. package/src/features/auth/index.ts +3 -0
  106. package/src/features/company/company.module.ts +10 -0
  107. package/src/features/company/data/company.fields.ts +6 -0
  108. package/src/features/company/data/company.interface.ts +28 -0
  109. package/src/features/company/data/company.service.ts +73 -0
  110. package/src/features/company/data/company.ts +104 -0
  111. package/src/features/company/data/index.ts +4 -0
  112. package/src/features/company/index.ts +2 -0
  113. package/src/features/content/content.module.ts +20 -0
  114. package/src/features/content/data/content.fields.ts +13 -0
  115. package/src/features/content/data/content.interface.ts +23 -0
  116. package/src/features/content/data/content.service.ts +75 -0
  117. package/src/features/content/data/content.ts +85 -0
  118. package/src/features/content/data/index.ts +4 -0
  119. package/src/features/content/index.ts +2 -0
  120. package/src/features/feature/components/forms/FormFeatures.tsx +149 -0
  121. package/src/features/feature/components/index.ts +1 -0
  122. package/src/features/feature/data/feature.interface.ts +9 -0
  123. package/src/features/feature/data/feature.service.ts +19 -0
  124. package/src/features/feature/data/feature.ts +33 -0
  125. package/src/features/feature/data/index.ts +3 -0
  126. package/src/features/feature/feature.module.ts +10 -0
  127. package/src/features/feature/index.ts +3 -0
  128. package/src/features/index.ts +12 -0
  129. package/src/features/module/data/index.ts +2 -0
  130. package/src/features/module/data/module.interface.ts +12 -0
  131. package/src/features/module/data/module.ts +42 -0
  132. package/src/features/module/index.ts +2 -0
  133. package/src/features/module/module.module.ts +10 -0
  134. package/src/features/notification/data/index.ts +4 -0
  135. package/src/features/notification/data/notification.fields.ts +8 -0
  136. package/src/features/notification/data/notification.interface.ts +14 -0
  137. package/src/features/notification/data/notification.service.ts +34 -0
  138. package/src/features/notification/data/notification.ts +51 -0
  139. package/src/features/notification/index.ts +2 -0
  140. package/src/features/notification/notification.module.ts +10 -0
  141. package/src/features/push/data/index.ts +3 -0
  142. package/src/features/push/data/push.interface.ts +8 -0
  143. package/src/features/push/data/push.service.ts +17 -0
  144. package/src/features/push/data/push.ts +18 -0
  145. package/src/features/push/index.ts +2 -0
  146. package/src/features/push/push.module.ts +10 -0
  147. package/src/features/role/data/index.ts +4 -0
  148. package/src/features/role/data/role.fields.ts +8 -0
  149. package/src/features/role/data/role.interface.ts +16 -0
  150. package/src/features/role/data/role.service.ts +117 -0
  151. package/src/features/role/data/role.ts +62 -0
  152. package/src/features/role/index.ts +2 -0
  153. package/src/features/role/role.module.ts +10 -0
  154. package/src/features/s3/data/index.ts +3 -0
  155. package/src/features/s3/data/s3.interface.ts +11 -0
  156. package/src/features/s3/data/s3.service.ts +30 -0
  157. package/src/features/s3/data/s3.ts +60 -0
  158. package/src/features/s3/index.ts +2 -0
  159. package/src/features/s3/s3.module.ts +10 -0
  160. package/src/features/search/index.ts +1 -0
  161. package/src/features/search/interfaces/index.ts +1 -0
  162. package/src/features/search/interfaces/search.result.interface.ts +3 -0
  163. package/src/features/user/author.module.ts +10 -0
  164. package/src/features/user/components/index.ts +2 -0
  165. package/src/features/user/components/lists/ContributorsList.tsx +41 -0
  166. package/src/features/user/components/lists/index.ts +1 -0
  167. package/src/features/user/components/widgets/UserAvatar.tsx +86 -0
  168. package/src/features/user/components/widgets/index.ts +1 -0
  169. package/src/features/user/contexts/CurrentUserContext.tsx +156 -0
  170. package/src/features/user/contexts/index.ts +1 -0
  171. package/src/features/user/data/index.ts +4 -0
  172. package/src/features/user/data/user.fields.ts +8 -0
  173. package/src/features/user/data/user.interface.ts +41 -0
  174. package/src/features/user/data/user.service.ts +246 -0
  175. package/src/features/user/data/user.ts +162 -0
  176. package/src/features/user/index.ts +4 -0
  177. package/src/features/user/user.module.ts +21 -0
  178. package/src/hooks/TableGeneratorRegistry.ts +53 -0
  179. package/src/hooks/index.ts +33 -0
  180. package/src/hooks/types.ts +35 -0
  181. package/src/hooks/url.rewriter.ts +22 -0
  182. package/src/hooks/useCustomD3Graph.tsx +705 -0
  183. package/src/hooks/useDataListRetriever.ts +349 -0
  184. package/src/hooks/useDebounce.ts +33 -0
  185. package/src/hooks/usePageUrlGenerator.ts +50 -0
  186. package/src/hooks/useTableGenerator.ts +16 -0
  187. package/src/i18n/config.ts +73 -0
  188. package/src/i18n/index.ts +18 -0
  189. package/src/index.ts +16 -0
  190. package/src/interfaces/breadcrumb.item.data.interface.ts +4 -0
  191. package/src/interfaces/d3.link.interface.ts +7 -0
  192. package/src/interfaces/d3.node.interface.ts +12 -0
  193. package/src/interfaces/index.ts +3 -0
  194. package/src/permissions/check.ts +127 -0
  195. package/src/permissions/index.ts +2 -0
  196. package/src/permissions/types.ts +109 -0
  197. package/src/roles/config.ts +46 -0
  198. package/src/roles/index.ts +1 -0
  199. package/src/server/cache.ts +28 -0
  200. package/src/server/index.ts +3 -0
  201. package/src/server/request.ts +113 -0
  202. package/src/server/token.ts +10 -0
  203. package/src/shadcnui/custom/kanban.tsx +1001 -0
  204. package/src/shadcnui/custom/link.tsx +18 -0
  205. package/src/shadcnui/custom/multi-select.tsx +382 -0
  206. package/src/shadcnui/index.ts +49 -0
  207. package/src/shadcnui/ui/accordion.tsx +52 -0
  208. package/src/shadcnui/ui/alert-dialog.tsx +141 -0
  209. package/src/shadcnui/ui/alert.tsx +43 -0
  210. package/src/shadcnui/ui/avatar.tsx +50 -0
  211. package/src/shadcnui/ui/badge.tsx +40 -0
  212. package/src/shadcnui/ui/breadcrumb.tsx +115 -0
  213. package/src/shadcnui/ui/button.tsx +51 -0
  214. package/src/shadcnui/ui/calendar.tsx +73 -0
  215. package/src/shadcnui/ui/card.tsx +43 -0
  216. package/src/shadcnui/ui/carousel.tsx +225 -0
  217. package/src/shadcnui/ui/chart.tsx +320 -0
  218. package/src/shadcnui/ui/checkbox.tsx +29 -0
  219. package/src/shadcnui/ui/collapsible.tsx +11 -0
  220. package/src/shadcnui/ui/command.tsx +155 -0
  221. package/src/shadcnui/ui/context-menu.tsx +179 -0
  222. package/src/shadcnui/ui/dialog.tsx +96 -0
  223. package/src/shadcnui/ui/drawer.tsx +89 -0
  224. package/src/shadcnui/ui/dropdown-menu.tsx +205 -0
  225. package/src/shadcnui/ui/form.tsx +138 -0
  226. package/src/shadcnui/ui/hover-card.tsx +29 -0
  227. package/src/shadcnui/ui/input.tsx +21 -0
  228. package/src/shadcnui/ui/label.tsx +26 -0
  229. package/src/shadcnui/ui/navigation-menu.tsx +168 -0
  230. package/src/shadcnui/ui/popover.tsx +33 -0
  231. package/src/shadcnui/ui/progress.tsx +25 -0
  232. package/src/shadcnui/ui/radio-group.tsx +37 -0
  233. package/src/shadcnui/ui/resizable.tsx +47 -0
  234. package/src/shadcnui/ui/scroll-area.tsx +40 -0
  235. package/src/shadcnui/ui/select.tsx +164 -0
  236. package/src/shadcnui/ui/separator.tsx +28 -0
  237. package/src/shadcnui/ui/sheet.tsx +139 -0
  238. package/src/shadcnui/ui/sidebar.tsx +677 -0
  239. package/src/shadcnui/ui/skeleton.tsx +13 -0
  240. package/src/shadcnui/ui/slider.tsx +25 -0
  241. package/src/shadcnui/ui/sonner.tsx +25 -0
  242. package/src/shadcnui/ui/switch.tsx +31 -0
  243. package/src/shadcnui/ui/table.tsx +120 -0
  244. package/src/shadcnui/ui/tabs.tsx +55 -0
  245. package/src/shadcnui/ui/textarea.tsx +24 -0
  246. package/src/shadcnui/ui/toggle.tsx +39 -0
  247. package/src/shadcnui/ui/tooltip.tsx +61 -0
  248. package/src/unified/JsonApiRequest.ts +325 -0
  249. package/src/unified/index.ts +1 -0
  250. package/src/utils/blocknote-diff.util.ts +815 -0
  251. package/src/utils/blocknote-word-diff-renderer.util.ts +413 -0
  252. package/src/utils/cn.ts +6 -0
  253. package/src/utils/compose-refs.ts +61 -0
  254. package/src/utils/date-formatter.ts +53 -0
  255. package/src/utils/exists.ts +7 -0
  256. package/src/utils/index.ts +15 -0
  257. package/src/utils/schemas/entity.object.schema.ts +8 -0
  258. package/src/utils/schemas/index.ts +2 -0
  259. package/src/utils/schemas/user.object.schema.ts +9 -0
  260. package/src/utils/table-options.ts +67 -0
  261. package/src/utils/use-mobile.tsx +21 -0
@@ -0,0 +1,138 @@
1
+ import { ApiDataInterface } from "../interfaces/ApiDataInterface";
2
+ import { ApiRequestDataTypeInterface } from "../interfaces/ApiRequestDataTypeInterface";
3
+ import { JsonApiHydratedDataInterface } from "../interfaces/JsonApiHydratedDataInterface";
4
+ import { RehydrationFactory } from "../factories/RehydrationFactory";
5
+
6
+ export abstract class AbstractApiData implements ApiDataInterface {
7
+ protected _jsonApi?: any;
8
+ protected _included?: any[];
9
+
10
+ protected _id?: string;
11
+ protected _type?: string;
12
+ protected _createdAt?: Date;
13
+ protected _updatedAt?: Date;
14
+
15
+ protected _self?: string;
16
+
17
+ get type(): string {
18
+ if (!this._type) throw new Error("Type is not set.");
19
+ return this._type;
20
+ }
21
+
22
+ get id(): string {
23
+ if (!this._id) throw new Error("Id is not set.");
24
+ return this._id;
25
+ }
26
+
27
+ get self(): string | undefined {
28
+ return this._self;
29
+ }
30
+
31
+ get createdAt(): Date {
32
+ return this._createdAt ?? new Date();
33
+ }
34
+
35
+ get updatedAt(): Date {
36
+ return this._updatedAt ?? new Date();
37
+ }
38
+
39
+ get included(): any[] {
40
+ return this._included ?? [];
41
+ }
42
+
43
+ get jsonApi(): any {
44
+ return this._jsonApi;
45
+ }
46
+
47
+ ingestJsonApi(_data: JsonApiHydratedDataInterface): void {}
48
+
49
+ generateApiUrl(_params?: any): string {
50
+ throw new Error("Method not implemented.");
51
+ }
52
+
53
+ createJsonApi(_data?: any): any {
54
+ throw new Error("Method not implemented.");
55
+ }
56
+
57
+ protected _readIncluded<T extends ApiDataInterface>(
58
+ data: JsonApiHydratedDataInterface,
59
+ type: string,
60
+ dataType: ApiRequestDataTypeInterface,
61
+ ): T | T[] | undefined {
62
+ if (
63
+ data.included === undefined ||
64
+ data.included.length === 0 ||
65
+ data.jsonApi.relationships === undefined ||
66
+ data.jsonApi.relationships[type] === undefined ||
67
+ data.jsonApi.relationships[type].data === undefined
68
+ ) {
69
+ return undefined;
70
+ }
71
+
72
+ if (Array.isArray(data.jsonApi.relationships[type].data)) {
73
+ const response: T[] = data.jsonApi.relationships[type].data.map((jsonApiData: any) => {
74
+ const includedData = data.included.find(
75
+ (includedData: any) => includedData.id === jsonApiData.id && includedData.type === jsonApiData.type,
76
+ );
77
+
78
+ if (includedData === undefined) return undefined;
79
+
80
+ return RehydrationFactory.rehydrate(dataType, {
81
+ jsonApi: includedData,
82
+ included: data.included,
83
+ }) as T;
84
+ });
85
+
86
+ return response.filter((item: T | undefined) => item !== undefined) as T[];
87
+ }
88
+
89
+ const includedData = data.included.find(
90
+ (includedData: any) =>
91
+ includedData.id === data.jsonApi.relationships[type].data.id &&
92
+ includedData.type === data.jsonApi.relationships[type].data.type,
93
+ );
94
+
95
+ if (includedData === undefined && data.allData !== undefined) {
96
+ // Try to find in allData as a fallback
97
+ const fallbackData = data.allData.find(
98
+ (includedData: any) =>
99
+ includedData.id === data.jsonApi.relationships[type].data.id &&
100
+ includedData.type === data.jsonApi.relationships[type].data.type,
101
+ );
102
+ if (fallbackData !== undefined) {
103
+ return RehydrationFactory.rehydrate(dataType, {
104
+ jsonApi: fallbackData,
105
+ included: data.included,
106
+ }) as T;
107
+ }
108
+ }
109
+
110
+ if (includedData === undefined) return undefined;
111
+
112
+ return RehydrationFactory.rehydrate(dataType, {
113
+ jsonApi: includedData,
114
+ included: data.included,
115
+ }) as T;
116
+ }
117
+
118
+ dehydrate(): JsonApiHydratedDataInterface {
119
+ return {
120
+ jsonApi: this._jsonApi,
121
+ included: this._included ?? [],
122
+ };
123
+ }
124
+
125
+ rehydrate(data: JsonApiHydratedDataInterface): this {
126
+ this._jsonApi = data.jsonApi;
127
+ this._included = data.included;
128
+
129
+ this._type = this._jsonApi.type;
130
+ this._id = this._jsonApi.id;
131
+ this._createdAt = this._jsonApi.meta?.createdAt !== undefined ? new Date(this._jsonApi.meta.createdAt) : undefined;
132
+ this._updatedAt = this._jsonApi.meta?.updatedAt !== undefined ? new Date(this._jsonApi.meta.updatedAt) : undefined;
133
+
134
+ this._self = this._jsonApi.links?.self ?? undefined;
135
+
136
+ return this;
137
+ }
138
+ }
@@ -0,0 +1,263 @@
1
+ import { ApiRequestDataTypeInterface } from "../interfaces/ApiRequestDataTypeInterface";
2
+ import { ApiResponseInterface } from "../interfaces/ApiResponseInterface";
3
+
4
+ export enum HttpMethod {
5
+ GET = "GET",
6
+ POST = "POST",
7
+ PUT = "PUT",
8
+ PATCH = "PATCH",
9
+ DELETE = "DELETE",
10
+ }
11
+
12
+ export interface NextRef {
13
+ next?: string;
14
+ }
15
+
16
+ export interface PreviousRef {
17
+ previous?: string;
18
+ }
19
+
20
+ export interface SelfRef {
21
+ self?: string;
22
+ }
23
+
24
+ let globalErrorHandler: ((status: number, message: string) => void) | null = null;
25
+
26
+ /**
27
+ * Set a global error handler for API errors (client-side only).
28
+ * This handler will be called instead of throwing errors.
29
+ */
30
+ export function setGlobalErrorHandler(handler: (status: number, message: string) => void) {
31
+ globalErrorHandler = handler;
32
+ }
33
+
34
+ /**
35
+ * Get the current global error handler.
36
+ */
37
+ export function getGlobalErrorHandler(): ((status: number, message: string) => void) | null {
38
+ return globalErrorHandler;
39
+ }
40
+
41
+ /**
42
+ * Abstract base class for services that interact with the JSON:API.
43
+ * Extend this class to create feature-specific services.
44
+ */
45
+ export abstract class AbstractService {
46
+ /**
47
+ * Extract locale from client-side URL pathname
48
+ * URL structure: /{locale}/route-path (e.g., /it/accounts)
49
+ * Fallback chain: URL locale → navigator.language → "en"
50
+ */
51
+ private static getClientLocale(): string {
52
+ if (typeof window === "undefined") {
53
+ return "en"; // Server-side fallback
54
+ }
55
+
56
+ // Extract locale from URL pathname (first segment after leading slash)
57
+ const pathSegments = window.location.pathname.split("/").filter(Boolean);
58
+ const urlLocale = pathSegments[0];
59
+
60
+ // Validate against supported locales (currently only "en")
61
+ const supportedLocales = ["en"];
62
+ if (urlLocale && supportedLocales.includes(urlLocale)) {
63
+ return urlLocale;
64
+ }
65
+
66
+ // Fallback to navigator language
67
+ const navigatorLocale = navigator.language.split("-")[0];
68
+ if (navigatorLocale && supportedLocales.includes(navigatorLocale)) {
69
+ return navigatorLocale;
70
+ }
71
+
72
+ // Final fallback
73
+ return "en";
74
+ }
75
+
76
+ static async next<T>(params: {
77
+ type: ApiRequestDataTypeInterface;
78
+ endpoint: string;
79
+ next?: NextRef;
80
+ previous?: PreviousRef;
81
+ self?: SelfRef;
82
+ }): Promise<T> {
83
+ return await this.callApi<T>({
84
+ method: HttpMethod.GET,
85
+ type: params.type,
86
+ endpoint: params.endpoint,
87
+ next: params.next,
88
+ previous: params.previous,
89
+ self: params.self,
90
+ });
91
+ }
92
+
93
+ /**
94
+ * Fetch the previous page of results.
95
+ */
96
+ static async previous<T>(params: {
97
+ type: ApiRequestDataTypeInterface;
98
+ endpoint: string;
99
+ next?: NextRef;
100
+ previous?: PreviousRef;
101
+ self?: SelfRef;
102
+ }): Promise<T> {
103
+ return await this.callApi<T>({
104
+ method: HttpMethod.GET,
105
+ type: params.type,
106
+ endpoint: params.endpoint,
107
+ next: params.next,
108
+ previous: params.previous,
109
+ self: params.self,
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Make an API call with automatic environment detection and error handling.
115
+ */
116
+ protected static async callApi<T>(params: {
117
+ type: ApiRequestDataTypeInterface;
118
+ method: HttpMethod;
119
+ endpoint: string;
120
+ companyId?: string;
121
+ input?: any;
122
+ overridesJsonApiCreation?: boolean;
123
+ next?: NextRef;
124
+ previous?: PreviousRef;
125
+ self?: SelfRef;
126
+ responseType?: ApiRequestDataTypeInterface;
127
+ files?: { [key: string]: File | Blob } | File | Blob;
128
+ }): Promise<T> {
129
+ // Dynamic import to avoid bundling issues
130
+ const { JsonApiGet, JsonApiPost, JsonApiPut, JsonApiPatch, JsonApiDelete } =
131
+ await import("../../unified/JsonApiRequest");
132
+
133
+ let apiResponse: ApiResponseInterface;
134
+
135
+ // Get language based on environment
136
+ let language = "en";
137
+ if (typeof window === "undefined") {
138
+ const { getLocale } = await import("next-intl/server");
139
+ language = (await getLocale()) ?? "en";
140
+ } else {
141
+ // Client-side: extract locale from URL pathname
142
+ language = this.getClientLocale();
143
+ }
144
+
145
+ switch (params.method) {
146
+ case HttpMethod.GET:
147
+ apiResponse = await JsonApiGet({
148
+ classKey: params.type,
149
+ endpoint: params.endpoint,
150
+ companyId: params.companyId,
151
+ language: language,
152
+ });
153
+ break;
154
+ case HttpMethod.POST:
155
+ apiResponse = await JsonApiPost({
156
+ classKey: params.type,
157
+ endpoint: params.endpoint,
158
+ companyId: params.companyId,
159
+ body: params.input,
160
+ overridesJsonApiCreation: params.overridesJsonApiCreation,
161
+ language: language,
162
+ responseType: params.responseType,
163
+ files: params.files,
164
+ });
165
+ break;
166
+ case HttpMethod.PUT:
167
+ apiResponse = await JsonApiPut({
168
+ classKey: params.type,
169
+ endpoint: params.endpoint,
170
+ companyId: params.companyId,
171
+ body: params.input,
172
+ language: language,
173
+ responseType: params.responseType,
174
+ files: params.files,
175
+ });
176
+ break;
177
+ case HttpMethod.PATCH:
178
+ apiResponse = await JsonApiPatch({
179
+ classKey: params.type,
180
+ endpoint: params.endpoint,
181
+ companyId: params.companyId,
182
+ body: params.input,
183
+ overridesJsonApiCreation: params.overridesJsonApiCreation,
184
+ language: language,
185
+ responseType: params.responseType,
186
+ files: params.files,
187
+ });
188
+ break;
189
+ case HttpMethod.DELETE:
190
+ apiResponse = await JsonApiDelete({
191
+ classKey: params.type,
192
+ endpoint: params.endpoint,
193
+ companyId: params.companyId,
194
+ language: language,
195
+ responseType: params.responseType,
196
+ });
197
+ break;
198
+ default:
199
+ throw new Error("Method not found");
200
+ }
201
+
202
+ if (!apiResponse.ok) {
203
+ if (globalErrorHandler && typeof window !== "undefined") {
204
+ globalErrorHandler(apiResponse.response, apiResponse.error);
205
+ return undefined as any;
206
+ } else {
207
+ const error = new Error(`${apiResponse.response}:${apiResponse.error}`) as any;
208
+ error.status = apiResponse.response;
209
+ error.digest = `HTTP_${apiResponse.response}`;
210
+ throw error;
211
+ }
212
+ }
213
+
214
+ if (apiResponse.next && params.next) params.next.next = apiResponse.next;
215
+ if (apiResponse.prev && params.previous) params.previous.previous = apiResponse.prev;
216
+ if (apiResponse.self && params.self) params.self.self = apiResponse.self;
217
+
218
+ return apiResponse.data as T;
219
+ }
220
+
221
+ /**
222
+ * Get raw JSON:API response data without deserialization.
223
+ */
224
+ protected static async getRawData(params: {
225
+ type: ApiRequestDataTypeInterface;
226
+ method: HttpMethod;
227
+ endpoint: string;
228
+ companyId?: string;
229
+ }): Promise<any> {
230
+ const { JsonApiGet } = await import("../../unified/JsonApiRequest");
231
+
232
+ let language = "en";
233
+
234
+ if (typeof window === "undefined") {
235
+ const { getLocale } = await import("next-intl/server");
236
+ language = (await getLocale()) ?? "en";
237
+ } else {
238
+ // Client-side: extract locale from URL pathname
239
+ language = this.getClientLocale();
240
+ }
241
+
242
+ const apiResponse: ApiResponseInterface = await JsonApiGet({
243
+ classKey: params.type,
244
+ endpoint: params.endpoint,
245
+ companyId: params.companyId,
246
+ language: language,
247
+ });
248
+
249
+ if (!apiResponse.ok) {
250
+ if (globalErrorHandler && typeof window !== "undefined") {
251
+ globalErrorHandler(apiResponse.response, apiResponse.error);
252
+ return undefined as any;
253
+ } else {
254
+ const error = new Error(`${apiResponse.response}:${apiResponse.error}`) as any;
255
+ error.status = apiResponse.response;
256
+ error.digest = `HTTP_${apiResponse.response}`;
257
+ throw error;
258
+ }
259
+ }
260
+
261
+ return apiResponse.raw;
262
+ }
263
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./AbstractApiData";
2
+ export * from "./AbstractService";
@@ -0,0 +1,97 @@
1
+ import { FieldSelector } from "../fields/FieldSelector";
2
+ import { ApiRequestDataTypeInterface } from "../interfaces/ApiRequestDataTypeInterface";
3
+
4
+ export type EndpointQuery = {
5
+ endpoint: ApiRequestDataTypeInterface;
6
+ id?: string;
7
+ childEndpoint?: ApiRequestDataTypeInterface | string;
8
+ childId?: string;
9
+ additionalParams?: { key: string; value: string | string[] }[];
10
+ };
11
+
12
+ export class EndpointCreator {
13
+ private _endpoint: EndpointQuery;
14
+
15
+ constructor(params: {
16
+ endpoint: ApiRequestDataTypeInterface;
17
+ id?: string;
18
+ childEndpoint?: ApiRequestDataTypeInterface | string;
19
+ childId?: string;
20
+ additionalParams?: { key: string; value: string }[];
21
+ }) {
22
+ this._endpoint = {
23
+ endpoint: params.endpoint,
24
+ id: params.id,
25
+ childEndpoint: params.childEndpoint,
26
+ childId: params.childId,
27
+ additionalParams: params.additionalParams ?? [],
28
+ };
29
+ }
30
+
31
+ endpoint(value: ApiRequestDataTypeInterface): EndpointCreator {
32
+ this._endpoint.endpoint = value;
33
+ return this;
34
+ }
35
+
36
+ id(value: string): EndpointCreator {
37
+ this._endpoint.id = value;
38
+ return this;
39
+ }
40
+
41
+ childEndpoint(value: ApiRequestDataTypeInterface | string): EndpointCreator {
42
+ this._endpoint.childEndpoint = value;
43
+ return this;
44
+ }
45
+
46
+ childId(value: string): EndpointCreator {
47
+ this._endpoint.childId = value;
48
+ return this;
49
+ }
50
+
51
+ set additionalParams(value: { key: string; value: string }[]) {
52
+ this._endpoint.additionalParams = value;
53
+ }
54
+
55
+ addAdditionalParam(key: string, value: string | string[]): EndpointCreator {
56
+ if (!this._endpoint.additionalParams) this._endpoint.additionalParams = [];
57
+ this._endpoint.additionalParams.push({ key, value });
58
+ return this;
59
+ }
60
+
61
+ limitToType(selectors: string[]): this {
62
+ this.addAdditionalParam(`include`, selectors.join(","));
63
+ return this;
64
+ }
65
+
66
+ limitToFields(selectors: FieldSelector<any>[]): this {
67
+ if (selectors.length === 0) return this;
68
+
69
+ selectors.forEach((selector) => {
70
+ const fieldString = selector.fields.join(",");
71
+ this.addAdditionalParam(`fields[${selector.type}]`, fieldString);
72
+ });
73
+
74
+ return this;
75
+ }
76
+
77
+ generate(): string {
78
+ let additionalParams = "";
79
+ if (this._endpoint.additionalParams) {
80
+ additionalParams = this._endpoint.additionalParams.map((param) => `${param.key}=${param.value}`).join("&");
81
+ }
82
+
83
+ let response = `${this._endpoint.endpoint.name}`;
84
+ if (this._endpoint.id) response += `/${this._endpoint.id}`;
85
+ if (this._endpoint.childEndpoint) {
86
+ response += `/${
87
+ typeof this._endpoint.childEndpoint === "string"
88
+ ? this._endpoint.childEndpoint
89
+ : this._endpoint.childEndpoint.name
90
+ }`;
91
+ }
92
+ if (this._endpoint.childId) response += `/${this._endpoint.childId}`;
93
+ if (additionalParams) response += `?${additionalParams}`;
94
+
95
+ return response;
96
+ }
97
+ }
@@ -0,0 +1 @@
1
+ export * from "./EndpointCreator";
@@ -0,0 +1,12 @@
1
+ import { ApiDataInterface } from "../interfaces/ApiDataInterface";
2
+ import { ApiRequestDataTypeInterface } from "../interfaces/ApiRequestDataTypeInterface";
3
+ import { DataClassRegistry } from "../registry/DataClassRegistry";
4
+
5
+ export class JsonApiDataFactory {
6
+ public static create(classKey: ApiRequestDataTypeInterface, data: any): any {
7
+ const factoryClass = DataClassRegistry.get(classKey);
8
+
9
+ const instance = new factoryClass() as ApiDataInterface;
10
+ return instance.createJsonApi(data);
11
+ }
12
+ }
@@ -0,0 +1,30 @@
1
+ import { ApiDataInterface } from "../interfaces/ApiDataInterface";
2
+ import { ApiRequestDataTypeInterface } from "../interfaces/ApiRequestDataTypeInterface";
3
+ import { JsonApiHydratedDataInterface } from "../interfaces/JsonApiHydratedDataInterface";
4
+ import { DataClassRegistry } from "../registry/DataClassRegistry";
5
+
6
+ export class RehydrationFactory {
7
+ public static rehydrate<T extends ApiDataInterface>(
8
+ classKey: ApiRequestDataTypeInterface,
9
+ data: JsonApiHydratedDataInterface,
10
+ ): T {
11
+ const factoryClass = DataClassRegistry.get(classKey);
12
+
13
+ const instance = new factoryClass();
14
+ return instance.rehydrate(data) as T;
15
+ }
16
+
17
+ public static rehydrateList<T extends ApiDataInterface>(
18
+ classKey: ApiRequestDataTypeInterface,
19
+ data: JsonApiHydratedDataInterface[],
20
+ ): T[] {
21
+ const factoryClass = DataClassRegistry.get(classKey);
22
+
23
+ const response = data.map((item: JsonApiHydratedDataInterface) => {
24
+ const instance = new factoryClass();
25
+ return instance.rehydrate(item) as T;
26
+ });
27
+
28
+ return response;
29
+ }
30
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./JsonApiDataFactory";
2
+ export * from "./RehydrationFactory";
@@ -0,0 +1,15 @@
1
+ export type GetterKeys<T> = {
2
+ [K in keyof T]: T[K] extends () => any ? never : K;
3
+ }[keyof T];
4
+
5
+ export type FieldSelector<T> = {
6
+ type: string;
7
+ fields: ReadonlyArray<GetterKeys<T>>;
8
+ };
9
+
10
+ export function createJsonApiInclusion<T>(dataType: string, fields: ReadonlyArray<GetterKeys<T>>): FieldSelector<T> {
11
+ return {
12
+ type: dataType,
13
+ fields,
14
+ };
15
+ }
@@ -0,0 +1 @@
1
+ export * from "./FieldSelector";
@@ -0,0 +1,20 @@
1
+ // Interfaces
2
+ export * from "./interfaces";
3
+
4
+ // Abstracts (will be added)
5
+ export * from "./abstracts";
6
+
7
+ // Factories
8
+ export * from "./factories";
9
+
10
+ // Registry
11
+ export * from "./registry";
12
+
13
+ // Endpoint builder
14
+ export * from "./endpoint";
15
+
16
+ // Field selectors
17
+ export * from "./fields";
18
+
19
+ // Utilities
20
+ export * from "./utils";
@@ -0,0 +1,8 @@
1
+ export type ApiData = {
2
+ data: any;
3
+ ok: boolean;
4
+ status: number;
5
+ statusText: string;
6
+ token?: string;
7
+ refreshToken?: string;
8
+ };
@@ -0,0 +1,15 @@
1
+ import { JsonApiHydratedDataInterface } from "./JsonApiHydratedDataInterface";
2
+
3
+ export interface ApiDataInterface {
4
+ get included(): any[];
5
+ get type(): string;
6
+ get id(): string;
7
+ get createdAt(): Date;
8
+ get updatedAt(): Date;
9
+ get self(): string | undefined;
10
+ get jsonApi(): any;
11
+ generateApiUrl(params?: any): string;
12
+ dehydrate(): JsonApiHydratedDataInterface;
13
+ rehydrate(data: JsonApiHydratedDataInterface): this;
14
+ createJsonApi(data: any): any;
15
+ }
@@ -0,0 +1,14 @@
1
+ import { FieldSelector } from "../fields/FieldSelector";
2
+
3
+ export type ApiRequestDataTypeInterface = {
4
+ name: string;
5
+ cache?: string;
6
+ inclusions?: Record<
7
+ string,
8
+ {
9
+ types?: string[];
10
+ fields?: FieldSelector<any>[];
11
+ }
12
+ >;
13
+ model: new () => any;
14
+ };
@@ -0,0 +1,17 @@
1
+ import { ApiDataInterface } from "./ApiDataInterface";
2
+
3
+ export interface ApiResponseInterface {
4
+ ok: boolean;
5
+ response: number;
6
+ raw?: any;
7
+
8
+ self?: string;
9
+ next?: string;
10
+ prev?: string;
11
+ nextPage?: () => Promise<ApiResponseInterface>;
12
+ prevPage?: () => Promise<ApiResponseInterface>;
13
+
14
+ data: ApiDataInterface | ApiDataInterface[];
15
+
16
+ error: string;
17
+ }
@@ -0,0 +1,5 @@
1
+ export interface JsonApiHydratedDataInterface {
2
+ jsonApi: any;
3
+ included: any[];
4
+ allData?: any[];
5
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./ApiData";
2
+ export * from "./ApiDataInterface";
3
+ export * from "./ApiRequestDataTypeInterface";
4
+ export * from "./ApiResponseInterface";
5
+ export * from "./JsonApiHydratedDataInterface";