@norbix.ai/react-redux 1.2.0 → 1.4.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 +254 -120
- package/dist/index.cjs +140 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -6
- package/dist/index.d.ts +34 -6
- package/dist/index.js +140 -16
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
|
27
|
-
|
|
28
|
-
| `useGetUsersQuery`, `useFindCollectionQuery`, `useInsertOneMutation`, ... | this package
|
|
29
|
-
| Cache, dedup, polling, refetch on focus, optimistic updates
|
|
30
|
-
| Tree-shakeable typed methods, JWT auth, error mapping
|
|
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) =>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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,115 @@ After `invite()` resolves, the `User/LIST` tag is invalidated and `useGetUsersQu
|
|
|
120
119
|
|
|
121
120
|
## What ships in the box
|
|
122
121
|
|
|
123
|
-
A
|
|
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
|
|
128
|
-
|
|
129
|
-
| `useLoginMutation`
|
|
130
|
-
| `useLogoutMutation`
|
|
131
|
-
| `useAuthenticateMutation`
|
|
132
|
-
| `useGetUsersQuery`
|
|
133
|
-
| `useGetUserQuery`
|
|
134
|
-
| `useInviteUserMutation`
|
|
135
|
-
| `useUpdateUserMutation`
|
|
136
|
-
| `useBlockUserMutation`
|
|
137
|
-
| `useUnblockUserMutation`
|
|
138
|
-
| `useDeleteUserMutation`
|
|
139
|
-
| `useFindCollectionQuery`
|
|
140
|
-
| `useFindOneQuery`
|
|
141
|
-
| `useCountCollectionQuery`
|
|
142
|
-
| `useInsertOneMutation`
|
|
143
|
-
| `useUpdateOneMutation`
|
|
144
|
-
| `useReplaceOneMutation`
|
|
145
|
-
| `useDeleteOneMutation`
|
|
146
|
-
| `useFindTermsQuery`
|
|
147
|
-
| `useFindTermsChildrenQuery`
|
|
148
|
-
| `useFindTermTreeQuery`
|
|
149
|
-
| `useFindTaxonomyTreeQuery`
|
|
150
|
-
| `useGetApiKeysQuery`
|
|
151
|
-
| `useRegenerateApiKeysMutation`
|
|
152
|
-
| `useGetPublicFileQuery`
|
|
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
|
|
158
|
-
|
|
159
|
-
| `useGetAccountProfileQuery`
|
|
160
|
-
| `useGetAccountStatusQuery`
|
|
161
|
-
| `useUpdateAccountProfileMutation`
|
|
162
|
-
| `useGetProjectsQuery`
|
|
163
|
-
| `useGetProjectQuery`
|
|
164
|
-
| `useCreateProjectMutation`
|
|
165
|
-
| `useDeleteProjectMutation`
|
|
166
|
-
| `useListRegionsQuery`
|
|
167
|
-
| `useUpdateProjectRegionsMutation`
|
|
168
|
-
| `useGetDatabaseSchemasQuery`
|
|
169
|
-
| `useGetDatabaseSchemaQuery`
|
|
170
|
-
| `useSaveDatabaseSchemaMutation`
|
|
171
|
-
| `useRenameDatabaseSchemaMutation`
|
|
172
|
-
| `usePublishDatabaseSchemaMutation` | `hub.database.publishDatabaseSchema`
|
|
173
|
-
| `useDeleteDatabaseSchemaMutation`
|
|
174
|
-
| `useGetEmailTemplatesQuery`
|
|
175
|
-
| `useGetEmailTemplateQuery`
|
|
176
|
-
| `useCreateEmailTemplateMutation`
|
|
177
|
-
| `useUpdateEmailTemplateMutation`
|
|
178
|
-
| `useDeleteEmailTemplateMutation`
|
|
179
|
-
| `useTestFilesIntegrationMutation`
|
|
180
|
-
| `useMakeFilePublicMutation`
|
|
181
|
-
| `useMakeFilePrivateMutation`
|
|
182
|
-
| `useMakeFolderPublicMutation`
|
|
183
|
-
| `useMakeFolderPrivateMutation`
|
|
156
|
+
| Hook | Wraps | Cache tags |
|
|
157
|
+
| ---------------------------------- | --------------------------------------- | ---------------------------------------------- |
|
|
158
|
+
| `useGetAccountProfileQuery` | `hub.account.getAccountProfile` | provides `AccountProfile/CURRENT` |
|
|
159
|
+
| `useGetAccountStatusQuery` | `hub.account.getAccountStatus` | provides `AccountProfile/STATUS` |
|
|
160
|
+
| `useUpdateAccountProfileMutation` | `hub.account.updateAccountProfile` | invalidates `AccountProfile/CURRENT` |
|
|
161
|
+
| `useGetProjectsQuery` | `hub.account.getProjects` | provides `AccountProfile/PROJECTS` |
|
|
162
|
+
| `useGetProjectQuery` | `hub.account.getProject` | provides `AccountProfile/PROJECT/<id>` |
|
|
163
|
+
| `useCreateProjectMutation` | `hub.account.createProject` | invalidates `AccountProfile/PROJECTS` |
|
|
164
|
+
| `useDeleteProjectMutation` | `hub.account.deleteProject` | invalidates `AccountProfile/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
|
+
|
|
187
227
|
## Public file links
|
|
188
228
|
|
|
189
229
|
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;
|
|
230
|
+
link. Publishing is a Hub action and needs the signed-in client; _reading_ the
|
|
191
231
|
link needs nothing at all.
|
|
192
232
|
|
|
193
233
|
```tsx
|
|
@@ -221,10 +261,10 @@ small file, read it, list the folder, delete the file — and answer one item
|
|
|
221
261
|
per step (`UploadFile`, `GetFile`, `GetAllFiles`, `DeleteFile`) with `result`
|
|
222
262
|
`OK`, `FAILED` or `NOT_TESTED`:
|
|
223
263
|
|
|
224
|
-
| Hook
|
|
225
|
-
|
|
264
|
+
| Hook | Wraps | Route | Use it from |
|
|
265
|
+
| ------------------------------------ | -------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------ |
|
|
226
266
|
| `useTestFilesIntegrationApiMutation` | `api.files.testFilesIntegration` | `POST /{version}/files/{filesIntegrationId}/test` | your app, with an API key or a user session (needs `files:create`) |
|
|
227
|
-
| `useTestFilesIntegrationMutation`
|
|
267
|
+
| `useTestFilesIntegrationMutation` | `hub.files.testFilesIntegration` | `POST /{version}/files/integrations/test` | a dashboard, signed in to the Hub |
|
|
228
268
|
|
|
229
269
|
```tsx
|
|
230
270
|
const [testIntegration, { data, isLoading }] = useTestFilesIntegrationApiMutation();
|
|
@@ -239,16 +279,52 @@ the storage, and a failed delete step leaves its file behind — and
|
|
|
239
279
|
`FilesIntegrations`, because the gateway records the last test result on the
|
|
240
280
|
integration.
|
|
241
281
|
|
|
282
|
+
## SMS
|
|
283
|
+
|
|
284
|
+
Every SMS Hub endpoint (35) has a hook, 1:1 with `norbix.hub.notifications`.
|
|
285
|
+
Queries provide a tag, mutations invalidate it, so a list re-reads after a
|
|
286
|
+
write without any manual refetch:
|
|
287
|
+
|
|
288
|
+
| Area | Hooks | Cache tag |
|
|
289
|
+
|---|---|---|
|
|
290
|
+
| Module switch | `useEnableSmsMutation`, `useDisableSmsMutation`, `useGetSmsDisableDependenciesQuery` (what a switch-off would affect — read it before disabling), `useGetSmsSettingsQuery` | `Sms`, `SmsSettings` |
|
|
291
|
+
| Integrations (providers) | `useGetSmsIntegrationsQuery`, `useGetSmsIntegrationQuery`, `useSaveSmsIntegrationMutation`, `useTestSmsIntegrationMutation`, `useConfirmSmsIntegrationHumanDeliveryMutation`, `useDeleteSmsIntegrationMutation`, `useSetSmsIntegrationAsDefaultMutation`, `useEnableSmsIntegrationMutation`, `useDisableSmsIntegrationMutation` | `SmsIntegrations` |
|
|
292
|
+
| Templates | `useGetSmsTemplatesQuery`, `useGetSmsTemplateQuery`, `useCreateSmsTemplateMutation`, `useUpdateSmsTemplateMutation`, `useDeleteSmsTemplateMutation`, `useArchiveSmsTemplateMutation`, `useUnArchiveSmsTemplateMutation`, `useCloneSmsTemplateMutation`, `useGetSmsMessageContentTokensQuery`, `useRenderSmsMutation` | `SmsTemplates`, `Sms` |
|
|
293
|
+
| Campaigns | `useGetSmsCampaignsQuery`, `useCreateSmsCampaignMutation`, `useGetSmsCampaignQuery`, `useDeleteSmsCampaignMutation`, `useStopSmsCampaignMutation`, `useGetSmsCampaignStatisticsQuery`, `useGetSmsCampaignBatchesQuery`, `useGetSmsCampaignBatchNotificationsQuery`, `useGetSmsCampaignBatchNotificationQuery`, `useGetSmsCampaignMessagesQuery`, `useGetSmsCampaignMessageQuery` | `SmsCampaigns` |
|
|
294
|
+
| Preview | `usePreviewSmsNotificationQuery` (opens with the signed link `hash`, no sign-in — see above) | `Sms` |
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
const [stop, { isLoading }] = useStopSmsCampaignMutation();
|
|
298
|
+
const [render] = useRenderSmsMutation();
|
|
299
|
+
|
|
300
|
+
// Stop a running campaign — no further messages go out; this cannot be undone.
|
|
301
|
+
await stop({ id: 'cmp_1' });
|
|
302
|
+
|
|
303
|
+
// Render a template's Razor code with token values before saving it.
|
|
304
|
+
const { data } = await render({
|
|
305
|
+
code: 'Hi @Model.FirstName, your code is @Model.Code',
|
|
306
|
+
tokens: [
|
|
307
|
+
{ name: 'FirstName', value: 'Ada' },
|
|
308
|
+
{ name: 'Code', value: '4242' },
|
|
309
|
+
],
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
`useRenderSmsMutation` saves nothing on the server, so it invalidates nothing;
|
|
314
|
+
it is a mutation only so a render is never served from the cache. The
|
|
315
|
+
integration hooks can also be produced by `buildIntegrationsEndpoints` (next
|
|
316
|
+
section) — the shipped ones above are the typed variants.
|
|
317
|
+
|
|
242
318
|
## Working with terms
|
|
243
319
|
|
|
244
320
|
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:
|
|
245
321
|
|
|
246
|
-
| I want to…
|
|
247
|
-
|
|
|
248
|
-
| Get a taxonomy's terms as a flat list
|
|
249
|
-
| Get only the children of one term
|
|
250
|
-
| Get a taxonomy's terms as a ready-made tree
|
|
251
|
-
| Get the taxonomy structure (e.g. Countries → Cities) | `useFindTaxonomyTreeQuery`
|
|
322
|
+
| I want to… | Hook | `data` holds |
|
|
323
|
+
| ---------------------------------------------------- | --------------------------- | ----------------------------------------------- |
|
|
324
|
+
| Get a taxonomy's terms as a flat list | `useFindTermsQuery` | a paginated `list` of terms |
|
|
325
|
+
| Get only the children of one term | `useFindTermsChildrenQuery` | a `list` of child terms (direct + multi-parent) |
|
|
326
|
+
| Get a taxonomy's terms as a ready-made tree | `useFindTermTreeQuery` | a `tree` of nested term nodes |
|
|
327
|
+
| Get the taxonomy structure (e.g. Countries → Cities) | `useFindTaxonomyTreeQuery` | a `tree` of taxonomy nodes |
|
|
252
328
|
|
|
253
329
|
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:
|
|
254
330
|
|
|
@@ -274,13 +350,46 @@ const { data, isLoading } = useFindTermsQuery({ taxonomyName: 'services' });
|
|
|
274
350
|
{
|
|
275
351
|
"list": {
|
|
276
352
|
"items": [
|
|
277
|
-
{
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
353
|
+
{
|
|
354
|
+
"id": "term_indoors",
|
|
355
|
+
"taxonomyName": "services",
|
|
356
|
+
"parentId": null,
|
|
357
|
+
"order": 1,
|
|
358
|
+
"name": "Indoors"
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
"id": "term_air_con",
|
|
362
|
+
"taxonomyName": "services",
|
|
363
|
+
"parentId": "term_indoors",
|
|
364
|
+
"order": 1,
|
|
365
|
+
"name": "Air conditioning"
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
"id": "term_wall",
|
|
369
|
+
"taxonomyName": "services",
|
|
370
|
+
"parentId": "term_air_con",
|
|
371
|
+
"order": 1,
|
|
372
|
+
"name": "Wall-mounted"
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
"id": "term_outdoors",
|
|
376
|
+
"taxonomyName": "services",
|
|
377
|
+
"parentId": null,
|
|
378
|
+
"order": 2,
|
|
379
|
+
"name": "Outdoors"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"id": "term_solar",
|
|
383
|
+
"taxonomyName": "services",
|
|
384
|
+
"parentId": "term_outdoors",
|
|
385
|
+
"order": 1,
|
|
386
|
+
"name": "Solar panels"
|
|
387
|
+
}
|
|
282
388
|
],
|
|
283
|
-
"hasMore": false,
|
|
389
|
+
"hasMore": false,
|
|
390
|
+
"hasPrevious": false,
|
|
391
|
+
"startingAfter": null,
|
|
392
|
+
"endingBefore": null
|
|
284
393
|
},
|
|
285
394
|
"responseStatus": { "isSuccess": true }
|
|
286
395
|
}
|
|
@@ -305,10 +414,25 @@ const { data } = useFindTermsQuery({
|
|
|
305
414
|
{
|
|
306
415
|
"list": {
|
|
307
416
|
"items": [
|
|
308
|
-
{
|
|
309
|
-
|
|
417
|
+
{
|
|
418
|
+
"id": "term_indoors",
|
|
419
|
+
"taxonomyName": "services",
|
|
420
|
+
"parentId": null,
|
|
421
|
+
"order": 1,
|
|
422
|
+
"name": "Indoors"
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
"id": "term_outdoors",
|
|
426
|
+
"taxonomyName": "services",
|
|
427
|
+
"parentId": null,
|
|
428
|
+
"order": 2,
|
|
429
|
+
"name": "Outdoors"
|
|
430
|
+
}
|
|
310
431
|
],
|
|
311
|
-
"hasMore": false,
|
|
432
|
+
"hasMore": false,
|
|
433
|
+
"hasPrevious": false,
|
|
434
|
+
"startingAfter": null,
|
|
435
|
+
"endingBefore": null
|
|
312
436
|
},
|
|
313
437
|
"responseStatus": { "isSuccess": true }
|
|
314
438
|
}
|
|
@@ -320,7 +444,7 @@ const { data } = useFindTermsQuery({
|
|
|
320
444
|
|
|
321
445
|
### Get a term's children
|
|
322
446
|
|
|
323
|
-
**Goal:** the user expanded
|
|
447
|
+
**Goal:** the user expanded _Indoors_ — load what is directly under it.
|
|
324
448
|
|
|
325
449
|
```tsx
|
|
326
450
|
const { data } = useFindTermsChildrenQuery({
|
|
@@ -340,12 +464,17 @@ const { data } = useFindTermsChildrenQuery({
|
|
|
340
464
|
"order": 1,
|
|
341
465
|
"name": "Air conditioning",
|
|
342
466
|
"multiParents": [
|
|
343
|
-
{ "taxonomyId": "tax_service_types", "parentId": "term_indoors",
|
|
344
|
-
{
|
|
467
|
+
{ "taxonomyId": "tax_service_types", "parentId": "term_indoors", "name": "Indoors" },
|
|
468
|
+
{
|
|
469
|
+
"taxonomyId": "tax_service_types",
|
|
470
|
+
"parentId": "term_energy_efficient",
|
|
471
|
+
"name": "Energy efficient"
|
|
472
|
+
}
|
|
345
473
|
]
|
|
346
474
|
}
|
|
347
475
|
],
|
|
348
|
-
"hasMore": false,
|
|
476
|
+
"hasMore": false,
|
|
477
|
+
"hasPrevious": false
|
|
349
478
|
},
|
|
350
479
|
"responseStatus": { "isSuccess": true }
|
|
351
480
|
}
|
|
@@ -357,7 +486,7 @@ This returns **both** direct children (their `parentId` is `term_indoors`) **and
|
|
|
357
486
|
|
|
358
487
|
### Multi-parent: one product in several categories
|
|
359
488
|
|
|
360
|
-
**Goal:** in a `products` taxonomy, a
|
|
489
|
+
**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.
|
|
361
490
|
|
|
362
491
|
```tsx
|
|
363
492
|
const { data } = useFindTermsChildrenQuery({
|
|
@@ -376,12 +505,13 @@ const { data } = useFindTermsChildrenQuery({
|
|
|
376
505
|
"name": "Relaxing massage oil",
|
|
377
506
|
"multiParents": [
|
|
378
507
|
{ "taxonomyId": "tax_categories", "parentId": "term_for_couples", "name": "For couples" },
|
|
379
|
-
{ "taxonomyId": "tax_categories", "parentId": "term_gift_ideas",
|
|
380
|
-
{ "taxonomyId": "tax_categories", "parentId": "term_body_care",
|
|
508
|
+
{ "taxonomyId": "tax_categories", "parentId": "term_gift_ideas", "name": "Gift ideas" },
|
|
509
|
+
{ "taxonomyId": "tax_categories", "parentId": "term_body_care", "name": "Body care" }
|
|
381
510
|
]
|
|
382
511
|
}
|
|
383
512
|
],
|
|
384
|
-
"hasMore": false,
|
|
513
|
+
"hasMore": false,
|
|
514
|
+
"hasPrevious": false
|
|
385
515
|
},
|
|
386
516
|
"responseStatus": { "isSuccess": true }
|
|
387
517
|
}
|
|
@@ -411,9 +541,7 @@ const { data } = useFindTermTreeQuery({ taxonomyName: 'services' });
|
|
|
411
541
|
"id": "term_air_con",
|
|
412
542
|
"name": "Air conditioning",
|
|
413
543
|
"order": 1,
|
|
414
|
-
"children": [
|
|
415
|
-
{ "id": "term_wall", "name": "Wall-mounted", "order": 1, "children": null }
|
|
416
|
-
]
|
|
544
|
+
"children": [{ "id": "term_wall", "name": "Wall-mounted", "order": 1, "children": null }]
|
|
417
545
|
}
|
|
418
546
|
]
|
|
419
547
|
},
|
|
@@ -421,9 +549,7 @@ const { data } = useFindTermTreeQuery({ taxonomyName: 'services' });
|
|
|
421
549
|
"id": "term_outdoors",
|
|
422
550
|
"name": "Outdoors",
|
|
423
551
|
"order": 2,
|
|
424
|
-
"children": [
|
|
425
|
-
{ "id": "term_solar", "name": "Solar panels", "order": 1, "children": null }
|
|
426
|
-
]
|
|
552
|
+
"children": [{ "id": "term_solar", "name": "Solar panels", "order": 1, "children": null }]
|
|
427
553
|
}
|
|
428
554
|
],
|
|
429
555
|
"responseStatus": { "isSuccess": true }
|
|
@@ -436,7 +562,7 @@ Roots are in `tree`; each node carries its own `children`; a leaf has `children:
|
|
|
436
562
|
|
|
437
563
|
### Get only a sub-tree, capped by depth
|
|
438
564
|
|
|
439
|
-
**Goal:** start from
|
|
565
|
+
**Goal:** start from _Indoors_ and go at most 2 levels deep.
|
|
440
566
|
|
|
441
567
|
```tsx
|
|
442
568
|
const { data } = useFindTermTreeQuery({
|
|
@@ -462,7 +588,7 @@ const { data } = useFindTermTreeQuery({
|
|
|
462
588
|
}
|
|
463
589
|
```
|
|
464
590
|
|
|
465
|
-
With `depth: 2` you get
|
|
591
|
+
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`.
|
|
466
592
|
|
|
467
593
|
---
|
|
468
594
|
|
|
@@ -483,7 +609,14 @@ const { data } = useFindTaxonomyTreeQuery({});
|
|
|
483
609
|
"taxonomySlug": "countries",
|
|
484
610
|
"parentId": null,
|
|
485
611
|
"children": [
|
|
486
|
-
{
|
|
612
|
+
{
|
|
613
|
+
"viewId": "txn_cities",
|
|
614
|
+
"taxonomyName": "Cities",
|
|
615
|
+
"taxonomySlug": "cities",
|
|
616
|
+
"parentId": "txn_countries",
|
|
617
|
+
"children": null,
|
|
618
|
+
"terms": null
|
|
619
|
+
}
|
|
487
620
|
],
|
|
488
621
|
"terms": null
|
|
489
622
|
}
|
|
@@ -514,7 +647,7 @@ const { data } = useFindTaxonomyTreeQuery({ includeTerms: true });
|
|
|
514
647
|
"parentId": null,
|
|
515
648
|
"terms": [
|
|
516
649
|
{ "id": "term_lt", "name": "Lithuania", "order": 1, "children": null },
|
|
517
|
-
{ "id": "term_lv", "name": "Latvia",
|
|
650
|
+
{ "id": "term_lv", "name": "Latvia", "order": 2, "children": null }
|
|
518
651
|
],
|
|
519
652
|
"children": [
|
|
520
653
|
{
|
|
@@ -524,7 +657,7 @@ const { data } = useFindTaxonomyTreeQuery({ includeTerms: true });
|
|
|
524
657
|
"parentId": "txn_countries",
|
|
525
658
|
"terms": [
|
|
526
659
|
{ "id": "term_vilnius", "name": "Vilnius", "order": 1, "children": null },
|
|
527
|
-
{ "id": "term_kaunas",
|
|
660
|
+
{ "id": "term_kaunas", "name": "Kaunas", "order": 2, "children": null }
|
|
528
661
|
],
|
|
529
662
|
"children": null
|
|
530
663
|
}
|
|
@@ -535,7 +668,7 @@ const { data } = useFindTaxonomyTreeQuery({ includeTerms: true });
|
|
|
535
668
|
}
|
|
536
669
|
```
|
|
537
670
|
|
|
538
|
-
Now each taxonomy node's `terms` holds that taxonomy's full term tree (same shape as `useFindTermTreeQuery`) —
|
|
671
|
+
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.
|
|
539
672
|
|
|
540
673
|
> 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.
|
|
541
674
|
|
|
@@ -591,7 +724,7 @@ For RTK Query to follow tenant changes, recreate the API slice or pass a tenant-
|
|
|
591
724
|
|
|
592
725
|
### Multi-region projects
|
|
593
726
|
|
|
594
|
-
A Norbix project spans one **primary region** plus any number of **additional regions**. Two concerns, two homes:
|
|
727
|
+
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.
|
|
595
728
|
|
|
596
729
|
**Configure the target region on the wrapped client.**
|
|
597
730
|
|
|
@@ -653,7 +786,7 @@ function RegionSettings({ projectId }: { projectId: string }) {
|
|
|
653
786
|
```
|
|
654
787
|
|
|
655
788
|
- `useListRegionsQuery` wraps `norbix.hub.regions.list` and **provides the `Regions` tag**. As with every query hook, a lazy variant — `useLazyListRegionsQuery` — is generated alongside it.
|
|
656
|
-
- `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
|
|
789
|
+
- `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.
|
|
657
790
|
|
|
658
791
|
> `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.
|
|
659
792
|
|
|
@@ -729,17 +862,17 @@ norbixApi.injectEndpoints({
|
|
|
729
862
|
|
|
730
863
|
This produces all hooks automatically — `useGetEmailIntegrationsQuery`, `useSaveEmailIntegrationMutation`, `useTestEmailIntegrationMutation`, `useConfirmEmailIntegrationHumanDeliveryMutation`, and the same for push/sms/files/payments/etc.
|
|
731
864
|
|
|
732
|
-
| Module
|
|
733
|
-
|
|
734
|
-
| Email
|
|
735
|
-
| Push
|
|
736
|
-
| Sms
|
|
737
|
-
| Files
|
|
738
|
-
| Payments
|
|
739
|
-
| Code
|
|
740
|
-
| Membership | `Membership`
|
|
741
|
-
| Logs
|
|
742
|
-
| Database
|
|
865
|
+
| Module | Prefix | Namespace | Tag | Notes |
|
|
866
|
+
| ---------- | ---------------------------------------- | --------------------- | ------------------------ | ----------------------------------- |
|
|
867
|
+
| Email | `Email` | `n.hub.notifications` | `EmailIntegrations` | has `test` + `confirmHumanDelivery` |
|
|
868
|
+
| Push | `Push` | `n.hub.notifications` | `PushIntegrations` | has `test` + `confirmHumanDelivery` |
|
|
869
|
+
| Sms | `Sms` | `n.hub.notifications` | `SmsIntegrations` | has `test` + `confirmHumanDelivery` |
|
|
870
|
+
| Files | `Files` | `n.hub.files` | `FilesIntegrations` | has `test` |
|
|
871
|
+
| Payments | `Payments` | `n.hub.payments` | `PaymentIntegrations` | has `test` + `confirmHumanDelivery` |
|
|
872
|
+
| Code | `Code` | `n.hub.code` | `CodeIntegrations` | has `test` |
|
|
873
|
+
| Membership | `Membership` | `n.hub.membership` | `MembershipIntegrations` | no `test` |
|
|
874
|
+
| Logs | `Logging` _(uses `Logging`, not `Logs`)_ | `n.hub.logs` | `LogsIntegrations` | no `test` |
|
|
875
|
+
| Database | `Database` | `n.hub.database` | `DatabaseIntegrations` | already wired in package |
|
|
743
876
|
|
|
744
877
|
**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`.
|
|
745
878
|
|
|
@@ -755,9 +888,10 @@ import type { useNorbix } from '@norbix.ai/react-redux';
|
|
|
755
888
|
export const myCampaignsService = norbixApi.injectEndpoints({
|
|
756
889
|
endpoints: (builder) => ({
|
|
757
890
|
getEmailCampaigns: builder.query({
|
|
758
|
-
query: (args) => (norbix) =>
|
|
759
|
-
|
|
760
|
-
|
|
891
|
+
query: (args) => (norbix) =>
|
|
892
|
+
norbix.hub.notifications
|
|
893
|
+
? norbix.hub.notifications.getEmailTemplates(args) // example only
|
|
894
|
+
: Promise.resolve({}),
|
|
761
895
|
providesTags: [{ type: 'EmailCampaigns', id: 'LIST' }],
|
|
762
896
|
}),
|
|
763
897
|
// ...more app-specific endpoints
|