@norbix.ai/react-redux 1.3.0 → 1.5.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.
package/README.md CHANGED
@@ -23,11 +23,11 @@
23
23
 
24
24
  `@norbix.ai/react-redux` is a thin, opinionated layer that wires the Norbix TypeScript SDK into a Redux Toolkit + RTK Query app. You get React hooks for the most-used Norbix endpoints with full caching, request dedup, and tag-based invalidation built in. Under the hood every hook calls the same typed `Norbix` SDK — so DTOs, auth, errors, and base URLs all stay consistent with the rest of your stack.
25
25
 
26
- | You get | Provided by |
27
- |---|---|
28
- | `useGetUsersQuery`, `useFindCollectionQuery`, `useInsertOneMutation`, ... | this package |
29
- | Cache, dedup, polling, refetch on focus, optimistic updates | RTK Query (`@reduxjs/toolkit/query`) |
30
- | Tree-shakeable typed methods, JWT auth, error mapping | `@norbix.ai/ts` SDK |
26
+ | You get | Provided by |
27
+ | ------------------------------------------------------------------------- | ------------------------------------ |
28
+ | `useGetUsersQuery`, `useFindCollectionQuery`, `useInsertOneMutation`, ... | this package |
29
+ | Cache, dedup, polling, refetch on focus, optimistic updates | RTK Query (`@reduxjs/toolkit/query`) |
30
+ | Tree-shakeable typed methods, JWT auth, error mapping | `@norbix.ai/ts` SDK |
31
31
 
32
32
  ## Install
33
33
 
@@ -104,11 +104,10 @@ export function UsersList() {
104
104
 
105
105
  return (
106
106
  <ul>
107
- {data?.list?.items.map((u) => <li key={u.id}>{u.email}</li>)}
108
- <button
109
- disabled={inviting}
110
- onClick={() => invite({ email: 'maya@team.io' })}
111
- >
107
+ {data?.list?.items.map((u) => (
108
+ <li key={u.id}>{u.email}</li>
109
+ ))}
110
+ <button disabled={inviting} onClick={() => invite({ email: 'maya@team.io' })}>
112
111
  Invite
113
112
  </button>
114
113
  </ul>
@@ -120,74 +119,166 @@ After `invite()` resolves, the `User/LIST` tag is invalidated and `useGetUsersQu
120
119
 
121
120
  ## What ships in the box
122
121
 
123
- A *curated* set of hooks for the most common endpoints. Every hook is end-to-end typed from the SDK's own DTOs.
122
+ A _curated_ set of hooks for the most common endpoints. Every hook is end-to-end typed from the SDK's own DTOs.
124
123
 
125
124
  ### API surface — `norbix.api.*`
126
125
 
127
- | Hook | Wraps | Cache tags |
128
- |---|---|---|
129
- | `useLoginMutation` | `client.login(...)` | invalidates `User/LIST`, `AccountProfile/CURRENT` |
130
- | `useLogoutMutation` | `client.logout()` | invalidates `User`, `AccountProfile`, `Collection`, `Schema` |
131
- | `useAuthenticateMutation` | `api.auth.authenticate` | — |
132
- | `useGetUsersQuery` | `api.membership.getUsers` | provides `User/LIST` |
133
- | `useGetUserQuery` | `api.membership.getUser` | provides `User/<id>` |
134
- | `useInviteUserMutation` | `api.membership.inviteUser` | invalidates `User/LIST` |
135
- | `useUpdateUserMutation` | `api.membership.updateUser` | invalidates `User/<id>` + `User/LIST` |
136
- | `useBlockUserMutation` | `api.membership.blockUser` | invalidates `User/LIST` |
137
- | `useUnblockUserMutation` | `api.membership.unblockUser` | invalidates `User/LIST` |
138
- | `useDeleteUserMutation` | `api.membership.deleteUser` | invalidates `User/LIST` |
139
- | `useFindCollectionQuery` | `api.database.find` | provides `Collection/<name>` |
140
- | `useFindOneQuery` | `api.database.findOne` | provides `Collection/<name>` |
141
- | `useCountCollectionQuery` | `api.database.count` | provides `Collection/<name>` |
142
- | `useInsertOneMutation` | `api.database.insertOne` | invalidates `Collection/<name>` |
143
- | `useUpdateOneMutation` | `api.database.updateOne` | invalidates `Collection/<name>` |
144
- | `useReplaceOneMutation` | `api.database.replaceOne` | invalidates `Collection/<name>` |
145
- | `useDeleteOneMutation` | `api.database.deleteOne` | invalidates `Collection/<name>` |
146
- | `useFindTermsQuery` | `api.database.findTerms` | provides `DatabaseTaxonomyTerms/<taxonomyName>` |
147
- | `useFindTermsChildrenQuery` | `api.database.findTermsChildren` | provides `DatabaseTaxonomyTerms/<taxonomyName>` |
148
- | `useFindTermTreeQuery` | `api.database.findTermTree` | provides `DatabaseTaxonomyTerms/<taxonomyName>` |
149
- | `useFindTaxonomyTreeQuery` | `api.database.findTaxonomyTree` | provides `DatabaseTaxonomyTerms/ANY` |
150
- | `useGetApiKeysQuery` | `api.apikeys.getApiKeys` | provides `ApiKey/LIST` |
151
- | `useRegenerateApiKeysMutation` | `api.apikeys.regenerateApiKeys` | invalidates `ApiKey/LIST` |
152
- | `useGetPublicFileQuery` | `api.files.getPublicFile` | provides `Files/PUBLIC:<publicId>` |
153
- | `useTestFilesIntegrationApiMutation` | `api.files.testFilesIntegration` | invalidates `Files` + `FilesIntegrations` |
126
+ | Hook | Wraps | Cache tags |
127
+ | ------------------------------------ | -------------------------------- | ------------------------------------------------------------ |
128
+ | `useLoginMutation` | `client.login(...)` | invalidates `User/LIST`, `AccountProfile/CURRENT` |
129
+ | `useLogoutMutation` | `client.logout()` | invalidates `User`, `AccountProfile`, `Collection`, `Schema` |
130
+ | `useAuthenticateMutation` | `api.auth.authenticate` | — |
131
+ | `useGetUsersQuery` | `api.membership.getUsers` | provides `User/LIST` |
132
+ | `useGetUserQuery` | `api.membership.getUser` | provides `User/<id>` |
133
+ | `useInviteUserMutation` | `api.membership.inviteUser` | invalidates `User/LIST` |
134
+ | `useUpdateUserMutation` | `api.membership.updateUser` | invalidates `User/<id>` + `User/LIST` |
135
+ | `useBlockUserMutation` | `api.membership.blockUser` | invalidates `User/LIST` |
136
+ | `useUnblockUserMutation` | `api.membership.unblockUser` | invalidates `User/LIST` |
137
+ | `useDeleteUserMutation` | `api.membership.deleteUser` | invalidates `User/LIST` |
138
+ | `useFindCollectionQuery` | `api.database.find` | provides `Collection/<name>` |
139
+ | `useFindOneQuery` | `api.database.findOne` | provides `Collection/<name>` |
140
+ | `useCountCollectionQuery` | `api.database.count` | provides `Collection/<name>` |
141
+ | `useInsertOneMutation` | `api.database.insertOne` | invalidates `Collection/<name>` |
142
+ | `useUpdateOneMutation` | `api.database.updateOne` | invalidates `Collection/<name>` |
143
+ | `useReplaceOneMutation` | `api.database.replaceOne` | invalidates `Collection/<name>` |
144
+ | `useDeleteOneMutation` | `api.database.deleteOne` | invalidates `Collection/<name>` |
145
+ | `useFindTermsQuery` | `api.database.findTerms` | provides `DatabaseTaxonomyTerms/<taxonomyName>` |
146
+ | `useFindTermsChildrenQuery` | `api.database.findTermsChildren` | provides `DatabaseTaxonomyTerms/<taxonomyName>` |
147
+ | `useFindTermTreeQuery` | `api.database.findTermTree` | provides `DatabaseTaxonomyTerms/<taxonomyName>` |
148
+ | `useFindTaxonomyTreeQuery` | `api.database.findTaxonomyTree` | provides `DatabaseTaxonomyTerms/ANY` |
149
+ | `useGetApiKeysQuery` | `api.apikeys.getApiKeys` | provides `ApiKey/LIST` |
150
+ | `useRegenerateApiKeysMutation` | `api.apikeys.regenerateApiKeys` | invalidates `ApiKey/LIST` |
151
+ | `useGetPublicFileQuery` | `api.files.getPublicFile` | provides `Files/PUBLIC:<publicId>` |
152
+ | `useTestFilesIntegrationApiMutation` | `api.files.testFilesIntegration` | invalidates `Files` + `FilesIntegrations` |
154
153
 
155
154
  ### Hub surface — `norbix.hub.*`
156
155
 
157
- | Hook | Wraps | Cache tags |
158
- |---|---|---|
159
- | `useGetAccountProfileQuery` | `hub.account.getAccountProfile` | provides `AccountProfile/CURRENT` |
160
- | `useGetAccountStatusQuery` | `hub.account.getAccountStatus` | provides `AccountProfile/STATUS` |
161
- | `useUpdateAccountProfileMutation` | `hub.account.updateAccountProfile` | invalidates `AccountProfile/CURRENT` |
162
- | `useGetProjectsQuery` | `hub.account.getProjects` | provides `AccountProfile/PROJECTS` |
163
- | `useGetProjectQuery` | `hub.account.getProject` | provides `AccountProfile/PROJECT/<id>` |
164
- | `useCreateProjectMutation` | `hub.account.createProject` | invalidates `AccountProfile/PROJECTS` |
165
- | `useDeleteProjectMutation` | `hub.account.deleteProject` | invalidates `AccountProfile/PROJECTS` |
166
- | `useListRegionsQuery` | `hub.regions.list` | provides `Regions` |
167
- | `useUpdateProjectRegionsMutation` | `hub.regions.updateProjectRegions` | invalidates `Regions` + `Projects` |
168
- | `useGetDatabaseSchemasQuery` | `hub.database.getDatabaseSchemas` | provides `Schema/LIST` |
169
- | `useGetDatabaseSchemaQuery` | `hub.database.getDatabaseSchema` | provides `Schema/<id>` |
170
- | `useSaveDatabaseSchemaMutation` | `hub.database.saveDatabaseSchema` | invalidates `Schema/LIST` |
171
- | `useRenameDatabaseSchemaMutation` | `hub.database.renameDatabaseSchema` | invalidates `Schema/LIST` + `Schema/<id>` |
172
- | `usePublishDatabaseSchemaMutation` | `hub.database.publishDatabaseSchema` | invalidates `Schema` + `Collection` |
173
- | `useDeleteDatabaseSchemaMutation` | `hub.database.deleteDatabaseSchema` | invalidates `Schema/LIST` + `Collection` |
174
- | `useGetEmailTemplatesQuery` | `hub.notifications.getEmailTemplates` | provides `Notification/EMAIL_TEMPLATE_LIST` |
175
- | `useGetEmailTemplateQuery` | `hub.notifications.getEmailTemplate` | provides `Notification/EMAIL_TEMPLATE/<id>` |
176
- | `useCreateEmailTemplateMutation` | `hub.notifications.createEmailTemplate` | invalidates `Notification/EMAIL_TEMPLATE_LIST` |
177
- | `useUpdateEmailTemplateMutation` | `hub.notifications.updateEmailTemplate` | invalidates list + per-id |
178
- | `useDeleteEmailTemplateMutation` | `hub.notifications.deleteEmailTemplate` | invalidates list |
179
- | `useTestFilesIntegrationMutation` | `hub.files.testFilesIntegration` | — |
180
- | `useMakeFilePublicMutation` | `hub.files.makeFilePublic` | invalidates `Files` |
181
- | `useMakeFilePrivateMutation` | `hub.files.makeFilePrivate` | invalidates `Files` |
182
- | `useMakeFolderPublicMutation` | `hub.files.makeFolderPublic` | invalidates `Files` |
183
- | `useMakeFolderPrivateMutation` | `hub.files.makeFolderPrivate` | invalidates `Files` |
156
+ | Hook | Wraps | Cache tags |
157
+ | ---------------------------------- | --------------------------------------- | ---------------------------------------------- |
158
+ | `useGetAccountProfileQuery` | `hub.account.getAccountProfile` | provides `Account` |
159
+ | `useGetAccountStatusQuery` | `hub.account.getAccountStatus` | provides `Account` |
160
+ | `useUpdateAccountProfileMutation` | `hub.account.updateAccountProfile` | invalidates `Account` |
161
+ | `useGetProjectsQuery` | `hub.account.getProjects` | provides `Projects` |
162
+ | `useGetProjectQuery` | `hub.account.getProject` | provides `Projects` |
163
+ | `useCreateProjectMutation` | `hub.account.createProject` | invalidates `Projects` |
164
+ | `useDeleteProjectMutation` | `hub.account.deleteProject` | invalidates `Projects` |
165
+ | `useListRegionsQuery` | `hub.regions.list` | provides `Regions` |
166
+ | `useUpdateProjectRegionsMutation` | `hub.regions.updateProjectRegions` | invalidates `Regions` + `Projects` |
167
+ | `useGetDatabaseSchemasQuery` | `hub.database.getDatabaseSchemas` | provides `Schema/LIST` |
168
+ | `useGetDatabaseSchemaQuery` | `hub.database.getDatabaseSchema` | provides `Schema/<id>` |
169
+ | `useSaveDatabaseSchemaMutation` | `hub.database.saveDatabaseSchema` | invalidates `Schema/LIST` |
170
+ | `useRenameDatabaseSchemaMutation` | `hub.database.renameDatabaseSchema` | invalidates `Schema/LIST` + `Schema/<id>` |
171
+ | `usePublishDatabaseSchemaMutation` | `hub.database.publishDatabaseSchema` | invalidates `Schema` + `Collection` |
172
+ | `useDeleteDatabaseSchemaMutation` | `hub.database.deleteDatabaseSchema` | invalidates `Schema/LIST` + `Collection` |
173
+ | `useGetEmailTemplatesQuery` | `hub.notifications.getEmailTemplates` | provides `Notification/EMAIL_TEMPLATE_LIST` |
174
+ | `useGetEmailTemplateQuery` | `hub.notifications.getEmailTemplate` | provides `Notification/EMAIL_TEMPLATE/<id>` |
175
+ | `useCreateEmailTemplateMutation` | `hub.notifications.createEmailTemplate` | invalidates `Notification/EMAIL_TEMPLATE_LIST` |
176
+ | `useUpdateEmailTemplateMutation` | `hub.notifications.updateEmailTemplate` | invalidates list + per-id |
177
+ | `useDeleteEmailTemplateMutation` | `hub.notifications.deleteEmailTemplate` | invalidates list |
178
+ | `useTestFilesIntegrationMutation` | `hub.files.testFilesIntegration` | — |
179
+ | `useMakeFilePublicMutation` | `hub.files.makeFilePublic` | invalidates `Files` |
180
+ | `useMakeFilePrivateMutation` | `hub.files.makeFilePrivate` | invalidates `Files` |
181
+ | `useMakeFolderPublicMutation` | `hub.files.makeFolderPublic` | invalidates `Files` |
182
+ | `useMakeFolderPrivateMutation` | `hub.files.makeFolderPrivate` | invalidates `Files` |
184
183
 
185
184
  > **Need a hook we don't ship?** Two options. (1) Drop down to the SDK with `useNorbix()` for a one-off call. (2) Add a new file under `src/hooks/api/` or `src/hooks/hub/`, follow the pattern of the others, and open a PR.
186
185
 
186
+ ### End-user AI chat — `norbix.api.ai`
187
+
188
+ For a signed-in project user. `useStartEndUserChatTurnMutation` answers at once with a `turnId`; the answer streams on the user's SSE channel `ai-chat:{projectId}:{authId}` — open it with `norbix.aiChat({ authId })` from `@norbix.ai/ts` (a foreign channel is refused with 403 `AiChatChannelRefused`, no retry). Needs `@norbix.ai/ts` >= 4.3.0.
189
+
190
+ | Hook | SDK method | Cache |
191
+ | ---------------------------------------- | ------------------------------------ | -------------------- |
192
+ | `useGetEndUserChatAvailabilityQuery` | `api.ai.getEndUserChatAvailability` | provides `AiChat` |
193
+ | `useListEndUserChatSessionsQuery` | `api.ai.listEndUserChatSessions` | provides `AiChat` |
194
+ | `useCreateEndUserChatSessionMutation` | `api.ai.createEndUserChatSession` | invalidates `AiChat` |
195
+ | `useGetEndUserChatSessionQuery` | `api.ai.getEndUserChatSession` | provides `AiChat` |
196
+ | `useRenameEndUserChatSessionMutation` | `api.ai.renameEndUserChatSession` | invalidates `AiChat` |
197
+ | `useDeleteEndUserChatSessionMutation` | `api.ai.deleteEndUserChatSession` | invalidates `AiChat` |
198
+ | `usePinEndUserChatSessionMutation` | `api.ai.pinEndUserChatSession` | invalidates `AiChat` |
199
+ | `useArchiveEndUserChatSessionMutation` | `api.ai.archiveEndUserChatSession` | invalidates `AiChat` |
200
+ | `useGetEndUserChatEntriesQuery` | `api.ai.getEndUserChatEntries` | provides `AiChat` |
201
+ | `useSetEndUserChatEntryFeedbackMutation` | `api.ai.setEndUserChatEntryFeedback` | invalidates `AiChat` |
202
+ | `useListEndUserChatAttachmentsQuery` | `api.ai.listEndUserChatAttachments` | provides `AiChat` |
203
+ | `useUploadEndUserChatAttachmentMutation` | `api.ai.uploadEndUserChatAttachment` | invalidates `AiChat` |
204
+ | `useDeleteEndUserChatAttachmentMutation` | `api.ai.deleteEndUserChatAttachment` | invalidates `AiChat` |
205
+ | `useListEndUserChatMemoryQuery` | `api.ai.listEndUserChatMemory` | provides `AiChat` |
206
+ | `useForgetEndUserChatMemoryMutation` | `api.ai.forgetEndUserChatMemory` | invalidates `AiChat` |
207
+ | `useStartEndUserChatTurnMutation` | `api.ai.startEndUserChatTurn` | invalidates `AiChat` |
208
+
209
+ ### Project AI settings, assistants, usage, Admin Portal — `norbix.hub.account` / embeddings — `norbix.hub.ai`
210
+
211
+ | Hook | SDK method | Cache |
212
+ | --------------------------------------- | -------------------------------------- | ---------------------- |
213
+ | `useGetProjectAiSettingsQuery` | `hub.account.getProjectAiSettings` | provides `Projects` |
214
+ | `useUpdateProjectAiSettingsMutation` | `hub.account.updateProjectAiSettings` | invalidates `Projects` |
215
+ | `useCreateProjectAiAssistantMutation` | `hub.account.createProjectAiAssistant` | invalidates `Projects` |
216
+ | `useUpdateProjectAiAssistantMutation` | `hub.account.updateProjectAiAssistant` | invalidates `Projects` |
217
+ | `useDeleteProjectAiAssistantMutation` | `hub.account.deleteProjectAiAssistant` | invalidates `Projects` |
218
+ | `useGetProjectAiUsageQuery` | `hub.account.getProjectAiUsage` | provides `Projects` |
219
+ | `useSetAdminPortalEnabledMutation` | `hub.account.setAdminPortalEnabled` | invalidates `Projects` |
220
+ | `useGetEmbeddingIntegrationsQuery` | `hub.ai.getEmbeddingIntegrations` | provides `Ai` |
221
+ | `useSaveEmbeddingIntegrationMutation` | `hub.ai.saveEmbeddingIntegration` | invalidates `Ai` |
222
+ | `useGetEmbeddingIntegrationQuery` | `hub.ai.getEmbeddingIntegration` | provides `Ai` |
223
+ | `useDeleteEmbeddingIntegrationMutation` | `hub.ai.deleteEmbeddingIntegration` | invalidates `Ai` |
224
+ | `useTestEmbeddingIntegrationMutation` | `hub.ai.testEmbeddingIntegration` | invalidates `Ai` |
225
+ | `useSetLlmIntegrationAsDefaultMutation` | `hub.ai.setLlmIntegrationAsDefault` | invalidates `Ai` |
226
+
227
+ ### Project settings, CORS, languages, admin portal, legal, AI service users — `norbix.hub.account`
228
+
229
+ Every call takes the `projectId` (or the `norbix-project-id` header your client sets) plus the field it changes. Queries provide `Projects`; mutations invalidate `Projects`, so an open `useGetProjectQuery` refetches after a change. AI settings, assistants and usage are in the table above; LLM / MCP integrations are `hub.ai` (`useGetLlmIntegrationsQuery`, `useSaveLlmIntegrationMutation`, `useSetLlmIntegrationAsDefaultMutation`, …). Regions: `useUpdateProjectRegionsMutation` is served by `hub.regions` (it is spread after `hub.account`).
230
+
231
+ | Hook | SDK method | Cache |
232
+ | ----------------------------------------- | ------------------------------------------ | ---------------------- |
233
+ | `useGetProjectsQuery` | `hub.account.getProjects` | provides `Projects` |
234
+ | `useGetProjectQuery` | `hub.account.getProject` | provides `Projects` |
235
+ | `useGetProjectTokensQuery` | `hub.account.getProjectTokens` | provides `Projects` |
236
+ | `useCreateProjectMutation` | `hub.account.createProject` | invalidates `Projects` |
237
+ | `useEnableProjectMutation` | `hub.account.enableProject` | invalidates `Projects` |
238
+ | `useDisableProjectMutation` | `hub.account.disableProject` | invalidates `Projects` |
239
+ | `useDeleteProjectMutation` | `hub.account.deleteProject` | invalidates `Projects` |
240
+ | `useUpdateProjectNameMutation` | `hub.account.updateProjectName` | invalidates `Projects` |
241
+ | `useUpdateProjectDescriptionMutation` | `hub.account.updateProjectDescription` | invalidates `Projects` |
242
+ | `useUpdateProjectUrlMutation` | `hub.account.updateProjectUrl` | invalidates `Projects` |
243
+ | `useUpdateProjectLogoMutation` | `hub.account.updateProjectLogo` | invalidates `Projects` |
244
+ | `useUpdateProjectIconMutation` | `hub.account.updateProjectIcon` | invalidates `Projects` |
245
+ | `useUpdateProjectMainColorMutation` | `hub.account.updateProjectMainColor` | invalidates `Projects` |
246
+ | `useUpdateProjectAccentColorMutation` | `hub.account.updateProjectAccentColor` | invalidates `Projects` |
247
+ | `useUpdateProjectAllowedOriginsMutation` | `hub.account.updateProjectAllowedOrigins` | invalidates `Projects` |
248
+ | `useUpdateProjectLanguagesMutation` | `hub.account.updateProjectLanguages` | invalidates `Projects` |
249
+ | `useUpdateProjectDefaultLanguageMutation` | `hub.account.updateProjectDefaultLanguage` | invalidates `Projects` |
250
+ | `useUpdateProjectAdminUrlMutation` | `hub.account.updateProjectAdminUrl` | invalidates `Projects` |
251
+ | `useUpdateProjectLegalDocumentsMutation` | `hub.account.updateProjectLegalDocuments` | invalidates `Projects` |
252
+ | `useUpdateProjectExposeLegalMutation` | `hub.account.updateProjectExposeLegal` | invalidates `Projects` |
253
+ | `useGetAdminPortalStructureQuery` | `hub.account.getAdminPortalStructure` | provides `Projects` |
254
+ | `useAssignAdminPortalServiceUserMutation` | `hub.account.assignAdminPortalServiceUser` | invalidates `Projects` |
255
+ | `useCreateAiServiceUserMutation` | `hub.account.createAiServiceUser` | invalidates `Projects` |
256
+ | `useListAiServiceUsersQuery` | `hub.account.listAiServiceUsers` | provides `Projects` |
257
+ | `useDeleteAiServiceUserMutation` | `hub.account.deleteAiServiceUser` | invalidates `Projects` |
258
+ | `useRotateAiServiceUserKeyMutation` | `hub.account.rotateAiServiceUserKey` | invalidates `Projects` |
259
+ | `useRevokeAiServiceUserKeyMutation` | `hub.account.revokeAiServiceUserKey` | invalidates `Projects` |
260
+
261
+ `useCreateAiServiceUserMutation` and `useRotateAiServiceUserKeyMutation` return the new key **once** — show it, then drop it from state. The developer MCP endpoint (`hub.account.mcp`, `/account/mcp`) is a JSON-RPC stream for MCP clients and has no hook; call it with `useNorbix()` if you need it.
262
+
263
+ ```tsx
264
+ const { data: project } = useGetProjectQuery({ projectId });
265
+ const [setOrigins] = useUpdateProjectAllowedOriginsMutation();
266
+ await setOrigins({ projectId, origins: ['https://app.example.com'] }).unwrap();
267
+ ```
268
+
269
+ ### Public project config and legal — `norbix.api.public` (no sign-in)
270
+
271
+ | Hook | SDK method | Cache |
272
+ | -------------------------------- | ----------------------------------- | ------------------------------ |
273
+ | `useGetPublicProjectConfigQuery` | `api.public.getPublicProjectConfig` | provides `Config/PUBLIC` |
274
+ | `useGetPublicProjectLegalQuery` | `api.public.getPublicProjectLegal` | provides `Config/LEGAL:<kind>` |
275
+
276
+ The legal document is served only when the project exposes it (`useUpdateProjectExposeLegalMutation`).
277
+
187
278
  ## Public file links
188
279
 
189
280
  A file, or a whole folder prefix, can be made readable by anyone holding its
190
- link. Publishing is a Hub action and needs the signed-in client; *reading* the
281
+ link. Publishing is a Hub action and needs the signed-in client; _reading_ the
191
282
  link needs nothing at all.
192
283
 
193
284
  ```tsx
@@ -221,10 +312,10 @@ small file, read it, list the folder, delete the file — and answer one item
221
312
  per step (`UploadFile`, `GetFile`, `GetAllFiles`, `DeleteFile`) with `result`
222
313
  `OK`, `FAILED` or `NOT_TESTED`:
223
314
 
224
- | Hook | Wraps | Route | Use it from |
225
- |---|---|---|---|
315
+ | Hook | Wraps | Route | Use it from |
316
+ | ------------------------------------ | -------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------ |
226
317
  | `useTestFilesIntegrationApiMutation` | `api.files.testFilesIntegration` | `POST /{version}/files/{filesIntegrationId}/test` | your app, with an API key or a user session (needs `files:create`) |
227
- | `useTestFilesIntegrationMutation` | `hub.files.testFilesIntegration` | `POST /{version}/files/integrations/test` | a dashboard, signed in to the Hub |
318
+ | `useTestFilesIntegrationMutation` | `hub.files.testFilesIntegration` | `POST /{version}/files/integrations/test` | a dashboard, signed in to the Hub |
228
319
 
229
320
  ```tsx
230
321
  const [testIntegration, { data, isLoading }] = useTestFilesIntegrationApiMutation();
@@ -245,13 +336,13 @@ Every SMS Hub endpoint (35) has a hook, 1:1 with `norbix.hub.notifications`.
245
336
  Queries provide a tag, mutations invalidate it, so a list re-reads after a
246
337
  write without any manual refetch:
247
338
 
248
- | Area | Hooks | Cache tag |
249
- |---|---|---|
250
- | Module switch | `useEnableSmsMutation`, `useDisableSmsMutation`, `useGetSmsDisableDependenciesQuery` (what a switch-off would affect — read it before disabling), `useGetSmsSettingsQuery` | `Sms`, `SmsSettings` |
251
- | Integrations (providers) | `useGetSmsIntegrationsQuery`, `useGetSmsIntegrationQuery`, `useSaveSmsIntegrationMutation`, `useTestSmsIntegrationMutation`, `useConfirmSmsIntegrationHumanDeliveryMutation`, `useDeleteSmsIntegrationMutation`, `useSetSmsIntegrationAsDefaultMutation`, `useEnableSmsIntegrationMutation`, `useDisableSmsIntegrationMutation` | `SmsIntegrations` |
252
- | Templates | `useGetSmsTemplatesQuery`, `useGetSmsTemplateQuery`, `useCreateSmsTemplateMutation`, `useUpdateSmsTemplateMutation`, `useDeleteSmsTemplateMutation`, `useArchiveSmsTemplateMutation`, `useUnArchiveSmsTemplateMutation`, `useCloneSmsTemplateMutation`, `useGetSmsMessageContentTokensQuery`, `useRenderSmsMutation` | `SmsTemplates`, `Sms` |
253
- | Campaigns | `useGetSmsCampaignsQuery`, `useCreateSmsCampaignMutation`, `useGetSmsCampaignQuery`, `useDeleteSmsCampaignMutation`, `useStopSmsCampaignMutation`, `useGetSmsCampaignStatisticsQuery`, `useGetSmsCampaignBatchesQuery`, `useGetSmsCampaignBatchNotificationsQuery`, `useGetSmsCampaignBatchNotificationQuery`, `useGetSmsCampaignMessagesQuery`, `useGetSmsCampaignMessageQuery` | `SmsCampaigns` |
254
- | Preview | `usePreviewSmsNotificationQuery` (opens with the signed link `hash`, no sign-in — see above) | `Sms` |
339
+ | Area | Hooks | Cache tag |
340
+ | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
341
+ | Module switch | `useEnableSmsMutation`, `useDisableSmsMutation`, `useGetSmsDisableDependenciesQuery` (what a switch-off would affect — read it before disabling), `useGetSmsSettingsQuery` | `Sms`, `SmsSettings` |
342
+ | Integrations (providers) | `useGetSmsIntegrationsQuery`, `useGetSmsIntegrationQuery`, `useSaveSmsIntegrationMutation`, `useTestSmsIntegrationMutation`, `useConfirmSmsIntegrationHumanDeliveryMutation`, `useDeleteSmsIntegrationMutation`, `useSetSmsIntegrationAsDefaultMutation`, `useEnableSmsIntegrationMutation`, `useDisableSmsIntegrationMutation` | `SmsIntegrations` |
343
+ | Templates | `useGetSmsTemplatesQuery`, `useGetSmsTemplateQuery`, `useCreateSmsTemplateMutation`, `useUpdateSmsTemplateMutation`, `useDeleteSmsTemplateMutation`, `useArchiveSmsTemplateMutation`, `useUnArchiveSmsTemplateMutation`, `useCloneSmsTemplateMutation`, `useGetSmsMessageContentTokensQuery`, `useRenderSmsMutation` | `SmsTemplates`, `Sms` |
344
+ | Campaigns | `useGetSmsCampaignsQuery`, `useCreateSmsCampaignMutation`, `useGetSmsCampaignQuery`, `useDeleteSmsCampaignMutation`, `useStopSmsCampaignMutation`, `useGetSmsCampaignStatisticsQuery`, `useGetSmsCampaignBatchesQuery`, `useGetSmsCampaignBatchNotificationsQuery`, `useGetSmsCampaignBatchNotificationQuery`, `useGetSmsCampaignMessagesQuery`, `useGetSmsCampaignMessageQuery` | `SmsCampaigns` |
345
+ | Preview | `usePreviewSmsNotificationQuery` (opens with the signed link `hash`, no sign-in — see above) | `Sms` |
255
346
 
256
347
  ```tsx
257
348
  const [stop, { isLoading }] = useStopSmsCampaignMutation();
@@ -279,12 +370,12 @@ section) — the shipped ones above are the typed variants.
279
370
 
280
371
  A **taxonomy** is a named tree of **terms** (labels). A term can have one parent (a clean hierarchy) or several parents (the same item under many categories). There is one read hook per scenario — pick the one that matches what you want:
281
372
 
282
- | I want to… | Hook | `data` holds |
283
- | --- | --- | --- |
284
- | Get a taxonomy's terms as a flat list | `useFindTermsQuery` | a paginated `list` of terms |
285
- | Get only the children of one term | `useFindTermsChildrenQuery` | a `list` of child terms (direct + multi-parent) |
286
- | Get a taxonomy's terms as a ready-made tree | `useFindTermTreeQuery` | a `tree` of nested term nodes |
287
- | Get the taxonomy structure (e.g. Countries → Cities) | `useFindTaxonomyTreeQuery` | a `tree` of taxonomy nodes |
373
+ | I want to… | Hook | `data` holds |
374
+ | ---------------------------------------------------- | --------------------------- | ----------------------------------------------- |
375
+ | Get a taxonomy's terms as a flat list | `useFindTermsQuery` | a paginated `list` of terms |
376
+ | Get only the children of one term | `useFindTermsChildrenQuery` | a `list` of child terms (direct + multi-parent) |
377
+ | Get a taxonomy's terms as a ready-made tree | `useFindTermTreeQuery` | a `tree` of nested term nodes |
378
+ | Get the taxonomy structure (e.g. Countries → Cities) | `useFindTaxonomyTreeQuery` | a `tree` of taxonomy nodes |
288
379
 
289
380
  All four share the same cache tag, so a write to a taxonomy refreshes every term view automatically. The examples below all use one example `services` taxonomy shaped like this:
290
381
 
@@ -310,13 +401,46 @@ const { data, isLoading } = useFindTermsQuery({ taxonomyName: 'services' });
310
401
  {
311
402
  "list": {
312
403
  "items": [
313
- { "id": "term_indoors", "taxonomyName": "services", "parentId": null, "order": 1, "name": "Indoors" },
314
- { "id": "term_air_con", "taxonomyName": "services", "parentId": "term_indoors", "order": 1, "name": "Air conditioning" },
315
- { "id": "term_wall", "taxonomyName": "services", "parentId": "term_air_con", "order": 1, "name": "Wall-mounted" },
316
- { "id": "term_outdoors", "taxonomyName": "services", "parentId": null, "order": 2, "name": "Outdoors" },
317
- { "id": "term_solar", "taxonomyName": "services", "parentId": "term_outdoors","order": 1, "name": "Solar panels" }
404
+ {
405
+ "id": "term_indoors",
406
+ "taxonomyName": "services",
407
+ "parentId": null,
408
+ "order": 1,
409
+ "name": "Indoors"
410
+ },
411
+ {
412
+ "id": "term_air_con",
413
+ "taxonomyName": "services",
414
+ "parentId": "term_indoors",
415
+ "order": 1,
416
+ "name": "Air conditioning"
417
+ },
418
+ {
419
+ "id": "term_wall",
420
+ "taxonomyName": "services",
421
+ "parentId": "term_air_con",
422
+ "order": 1,
423
+ "name": "Wall-mounted"
424
+ },
425
+ {
426
+ "id": "term_outdoors",
427
+ "taxonomyName": "services",
428
+ "parentId": null,
429
+ "order": 2,
430
+ "name": "Outdoors"
431
+ },
432
+ {
433
+ "id": "term_solar",
434
+ "taxonomyName": "services",
435
+ "parentId": "term_outdoors",
436
+ "order": 1,
437
+ "name": "Solar panels"
438
+ }
318
439
  ],
319
- "hasMore": false, "hasPrevious": false, "startingAfter": null, "endingBefore": null
440
+ "hasMore": false,
441
+ "hasPrevious": false,
442
+ "startingAfter": null,
443
+ "endingBefore": null
320
444
  },
321
445
  "responseStatus": { "isSuccess": true }
322
446
  }
@@ -341,10 +465,25 @@ const { data } = useFindTermsQuery({
341
465
  {
342
466
  "list": {
343
467
  "items": [
344
- { "id": "term_indoors", "taxonomyName": "services", "parentId": null, "order": 1, "name": "Indoors" },
345
- { "id": "term_outdoors", "taxonomyName": "services", "parentId": null, "order": 2, "name": "Outdoors" }
468
+ {
469
+ "id": "term_indoors",
470
+ "taxonomyName": "services",
471
+ "parentId": null,
472
+ "order": 1,
473
+ "name": "Indoors"
474
+ },
475
+ {
476
+ "id": "term_outdoors",
477
+ "taxonomyName": "services",
478
+ "parentId": null,
479
+ "order": 2,
480
+ "name": "Outdoors"
481
+ }
346
482
  ],
347
- "hasMore": false, "hasPrevious": false, "startingAfter": null, "endingBefore": null
483
+ "hasMore": false,
484
+ "hasPrevious": false,
485
+ "startingAfter": null,
486
+ "endingBefore": null
348
487
  },
349
488
  "responseStatus": { "isSuccess": true }
350
489
  }
@@ -356,7 +495,7 @@ const { data } = useFindTermsQuery({
356
495
 
357
496
  ### Get a term's children
358
497
 
359
- **Goal:** the user expanded *Indoors* — load what is directly under it.
498
+ **Goal:** the user expanded _Indoors_ — load what is directly under it.
360
499
 
361
500
  ```tsx
362
501
  const { data } = useFindTermsChildrenQuery({
@@ -376,12 +515,17 @@ const { data } = useFindTermsChildrenQuery({
376
515
  "order": 1,
377
516
  "name": "Air conditioning",
378
517
  "multiParents": [
379
- { "taxonomyId": "tax_service_types", "parentId": "term_indoors", "name": "Indoors" },
380
- { "taxonomyId": "tax_service_types", "parentId": "term_energy_efficient", "name": "Energy efficient" }
518
+ { "taxonomyId": "tax_service_types", "parentId": "term_indoors", "name": "Indoors" },
519
+ {
520
+ "taxonomyId": "tax_service_types",
521
+ "parentId": "term_energy_efficient",
522
+ "name": "Energy efficient"
523
+ }
381
524
  ]
382
525
  }
383
526
  ],
384
- "hasMore": false, "hasPrevious": false
527
+ "hasMore": false,
528
+ "hasPrevious": false
385
529
  },
386
530
  "responseStatus": { "isSuccess": true }
387
531
  }
@@ -393,7 +537,7 @@ This returns **both** direct children (their `parentId` is `term_indoors`) **and
393
537
 
394
538
  ### Multi-parent: one product in several categories
395
539
 
396
- **Goal:** in a `products` taxonomy, a *Relaxing massage oil* belongs to *For couples*, *Gift ideas*, **and** *Body care*. Listing the children of **any** of those categories returns it.
540
+ **Goal:** in a `products` taxonomy, a _Relaxing massage oil_ belongs to _For couples_, _Gift ideas_, **and** _Body care_. Listing the children of **any** of those categories returns it.
397
541
 
398
542
  ```tsx
399
543
  const { data } = useFindTermsChildrenQuery({
@@ -412,12 +556,13 @@ const { data } = useFindTermsChildrenQuery({
412
556
  "name": "Relaxing massage oil",
413
557
  "multiParents": [
414
558
  { "taxonomyId": "tax_categories", "parentId": "term_for_couples", "name": "For couples" },
415
- { "taxonomyId": "tax_categories", "parentId": "term_gift_ideas", "name": "Gift ideas" },
416
- { "taxonomyId": "tax_categories", "parentId": "term_body_care", "name": "Body care" }
559
+ { "taxonomyId": "tax_categories", "parentId": "term_gift_ideas", "name": "Gift ideas" },
560
+ { "taxonomyId": "tax_categories", "parentId": "term_body_care", "name": "Body care" }
417
561
  ]
418
562
  }
419
563
  ],
420
- "hasMore": false, "hasPrevious": false
564
+ "hasMore": false,
565
+ "hasPrevious": false
421
566
  },
422
567
  "responseStatus": { "isSuccess": true }
423
568
  }
@@ -447,9 +592,7 @@ const { data } = useFindTermTreeQuery({ taxonomyName: 'services' });
447
592
  "id": "term_air_con",
448
593
  "name": "Air conditioning",
449
594
  "order": 1,
450
- "children": [
451
- { "id": "term_wall", "name": "Wall-mounted", "order": 1, "children": null }
452
- ]
595
+ "children": [{ "id": "term_wall", "name": "Wall-mounted", "order": 1, "children": null }]
453
596
  }
454
597
  ]
455
598
  },
@@ -457,9 +600,7 @@ const { data } = useFindTermTreeQuery({ taxonomyName: 'services' });
457
600
  "id": "term_outdoors",
458
601
  "name": "Outdoors",
459
602
  "order": 2,
460
- "children": [
461
- { "id": "term_solar", "name": "Solar panels", "order": 1, "children": null }
462
- ]
603
+ "children": [{ "id": "term_solar", "name": "Solar panels", "order": 1, "children": null }]
463
604
  }
464
605
  ],
465
606
  "responseStatus": { "isSuccess": true }
@@ -472,7 +613,7 @@ Roots are in `tree`; each node carries its own `children`; a leaf has `children:
472
613
 
473
614
  ### Get only a sub-tree, capped by depth
474
615
 
475
- **Goal:** start from *Indoors* and go at most 2 levels deep.
616
+ **Goal:** start from _Indoors_ and go at most 2 levels deep.
476
617
 
477
618
  ```tsx
478
619
  const { data } = useFindTermTreeQuery({
@@ -498,7 +639,7 @@ const { data } = useFindTermTreeQuery({
498
639
  }
499
640
  ```
500
641
 
501
- With `depth: 2` you get *Indoors* (level 1) and *Air conditioning* (level 2); *Wall-mounted* (level 3) is cut off, so *Air conditioning* shows `children: null`.
642
+ With `depth: 2` you get _Indoors_ (level 1) and _Air conditioning_ (level 2); _Wall-mounted_ (level 3) is cut off, so _Air conditioning_ shows `children: null`.
502
643
 
503
644
  ---
504
645
 
@@ -519,7 +660,14 @@ const { data } = useFindTaxonomyTreeQuery({});
519
660
  "taxonomySlug": "countries",
520
661
  "parentId": null,
521
662
  "children": [
522
- { "viewId": "txn_cities", "taxonomyName": "Cities", "taxonomySlug": "cities", "parentId": "txn_countries", "children": null, "terms": null }
663
+ {
664
+ "viewId": "txn_cities",
665
+ "taxonomyName": "Cities",
666
+ "taxonomySlug": "cities",
667
+ "parentId": "txn_countries",
668
+ "children": null,
669
+ "terms": null
670
+ }
523
671
  ],
524
672
  "terms": null
525
673
  }
@@ -550,7 +698,7 @@ const { data } = useFindTaxonomyTreeQuery({ includeTerms: true });
550
698
  "parentId": null,
551
699
  "terms": [
552
700
  { "id": "term_lt", "name": "Lithuania", "order": 1, "children": null },
553
- { "id": "term_lv", "name": "Latvia", "order": 2, "children": null }
701
+ { "id": "term_lv", "name": "Latvia", "order": 2, "children": null }
554
702
  ],
555
703
  "children": [
556
704
  {
@@ -560,7 +708,7 @@ const { data } = useFindTaxonomyTreeQuery({ includeTerms: true });
560
708
  "parentId": "txn_countries",
561
709
  "terms": [
562
710
  { "id": "term_vilnius", "name": "Vilnius", "order": 1, "children": null },
563
- { "id": "term_kaunas", "name": "Kaunas", "order": 2, "children": null }
711
+ { "id": "term_kaunas", "name": "Kaunas", "order": 2, "children": null }
564
712
  ],
565
713
  "children": null
566
714
  }
@@ -571,7 +719,7 @@ const { data } = useFindTaxonomyTreeQuery({ includeTerms: true });
571
719
  }
572
720
  ```
573
721
 
574
- Now each taxonomy node's `terms` holds that taxonomy's full term tree (same shape as `useFindTermTreeQuery`) — *Countries* carries its countries, *Cities* carries its cities.
722
+ Now each taxonomy node's `terms` holds that taxonomy's full term tree (same shape as `useFindTermTreeQuery`) — _Countries_ carries its countries, _Cities_ carries its cities.
575
723
 
576
724
  > All four hooks also accept the usual RTK Query options (e.g. `{ skip: !ready }`), and an optional `databaseIntegrationId` in the argument to target a non-default database.
577
725
 
@@ -627,7 +775,7 @@ For RTK Query to follow tenant changes, recreate the API slice or pass a tenant-
627
775
 
628
776
  ### Multi-region projects
629
777
 
630
- A Norbix project spans one **primary region** plus any number of **additional regions**. Two concerns, two homes: *which region your requests target* is configured on the wrapped `norbix` client (this package inherits it — every hook just calls the client you passed to `createNorbixApi`), while *which regions a project spans* is managed through the two hooks below.
778
+ A Norbix project spans one **primary region** plus any number of **additional regions**. Two concerns, two homes: _which region your requests target_ is configured on the wrapped `norbix` client (this package inherits it — every hook just calls the client you passed to `createNorbixApi`), while _which regions a project spans_ is managed through the two hooks below.
631
779
 
632
780
  **Configure the target region on the wrapped client.**
633
781
 
@@ -689,7 +837,7 @@ function RegionSettings({ projectId }: { projectId: string }) {
689
837
  ```
690
838
 
691
839
  - `useListRegionsQuery` wraps `norbix.hub.regions.list` and **provides the `Regions` tag**. As with every query hook, a lazy variant — `useLazyListRegionsQuery` — is generated alongside it.
692
- - `useUpdateProjectRegionsMutation` wraps `norbix.hub.regions.updateProjectRegions({ projectId, primaryRegion?, additionalRegions? })` and **invalidates `Regions` and `Projects`** — changing the regions a project spans updates the project DTO (`primaryRegion` / `additionalRegions`), so cached region lists *and* project queries refetch automatically once the mutation resolves.
840
+ - `useUpdateProjectRegionsMutation` wraps `norbix.hub.regions.updateProjectRegions({ projectId, primaryRegion?, additionalRegions? })` and **invalidates `Regions` and `Projects`** — changing the regions a project spans updates the project DTO (`primaryRegion` / `additionalRegions`), so cached region lists _and_ project queries refetch automatically once the mutation resolves.
693
841
 
694
842
  > `hub.account` carries `getAccountRegions` / `updateProjectRegions` aliases for the same wire endpoints (`useGetAccountRegionsQuery` tags `Account`). The `hub.regions` hooks above are the canonical pair — they are the ones wired to the `Regions` tag.
695
843
 
@@ -765,17 +913,17 @@ norbixApi.injectEndpoints({
765
913
 
766
914
  This produces all hooks automatically — `useGetEmailIntegrationsQuery`, `useSaveEmailIntegrationMutation`, `useTestEmailIntegrationMutation`, `useConfirmEmailIntegrationHumanDeliveryMutation`, and the same for push/sms/files/payments/etc.
767
915
 
768
- | Module | Prefix | Namespace | Tag | Notes |
769
- |---|---|---|---|---|
770
- | Email | `Email` | `n.hub.notifications` | `EmailIntegrations` | has `test` + `confirmHumanDelivery` |
771
- | Push | `Push` | `n.hub.notifications` | `PushIntegrations` | has `test` + `confirmHumanDelivery` |
772
- | Sms | `Sms` | `n.hub.notifications` | `SmsIntegrations` | has `test` + `confirmHumanDelivery` |
773
- | Files | `Files` | `n.hub.files` | `FilesIntegrations` | has `test` |
774
- | Payments | `Payments` | `n.hub.payments` | `PaymentIntegrations` | has `test` + `confirmHumanDelivery` |
775
- | Code | `Code` | `n.hub.code` | `CodeIntegrations` | has `test` |
776
- | Membership | `Membership` | `n.hub.membership` | `MembershipIntegrations` | no `test` |
777
- | Logs | `Logging` *(uses `Logging`, not `Logs`)* | `n.hub.logs` | `LogsIntegrations` | no `test` |
778
- | Database | `Database` | `n.hub.database` | `DatabaseIntegrations` | already wired in package |
916
+ | Module | Prefix | Namespace | Tag | Notes |
917
+ | ---------- | ---------------------------------------- | --------------------- | ------------------------ | ----------------------------------- |
918
+ | Email | `Email` | `n.hub.notifications` | `EmailIntegrations` | has `test` + `confirmHumanDelivery` |
919
+ | Push | `Push` | `n.hub.notifications` | `PushIntegrations` | has `test` + `confirmHumanDelivery` |
920
+ | Sms | `Sms` | `n.hub.notifications` | `SmsIntegrations` | has `test` + `confirmHumanDelivery` |
921
+ | Files | `Files` | `n.hub.files` | `FilesIntegrations` | has `test` |
922
+ | Payments | `Payments` | `n.hub.payments` | `PaymentIntegrations` | has `test` + `confirmHumanDelivery` |
923
+ | Code | `Code` | `n.hub.code` | `CodeIntegrations` | has `test` |
924
+ | Membership | `Membership` | `n.hub.membership` | `MembershipIntegrations` | no `test` |
925
+ | Logs | `Logging` _(uses `Logging`, not `Logs`)_ | `n.hub.logs` | `LogsIntegrations` | no `test` |
926
+ | Database | `Database` | `n.hub.database` | `DatabaseIntegrations` | already wired in package |
779
927
 
780
928
  **Trade-off.** The helper is terse but loses static request/response typing — generated hooks return `any` data, because endpoint keys are computed at runtime. If you need full types on a specific integration surface (autocompletion in your IDE), define those endpoints manually instead, following the canonical shape used by `hub.database` integrations in `src/hooks/hub/database.ts`.
781
929
 
@@ -791,9 +939,10 @@ import type { useNorbix } from '@norbix.ai/react-redux';
791
939
  export const myCampaignsService = norbixApi.injectEndpoints({
792
940
  endpoints: (builder) => ({
793
941
  getEmailCampaigns: builder.query({
794
- query: (args) => (norbix) => norbix.hub.notifications
795
- ? norbix.hub.notifications.getEmailTemplates(args) // example only
796
- : Promise.resolve({}),
942
+ query: (args) => (norbix) =>
943
+ norbix.hub.notifications
944
+ ? norbix.hub.notifications.getEmailTemplates(args) // example only
945
+ : Promise.resolve({}),
797
946
  providesTags: [{ type: 'EmailCampaigns', id: 'LIST' }],
798
947
  }),
799
948
  // ...more app-specific endpoints