@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 +276 -127
- package/dist/index.cjs +162 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -6
- package/dist/index.d.ts +41 -6
- package/dist/index.js +162 -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,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
|
|
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 `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;
|
|
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
|
|
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`
|
|
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
|
|
249
|
-
|
|
250
|
-
| Module switch
|
|
251
|
-
| Integrations (providers) | `useGetSmsIntegrationsQuery`, `useGetSmsIntegrationQuery`, `useSaveSmsIntegrationMutation`, `useTestSmsIntegrationMutation`, `useConfirmSmsIntegrationHumanDeliveryMutation`, `useDeleteSmsIntegrationMutation`, `useSetSmsIntegrationAsDefaultMutation`, `useEnableSmsIntegrationMutation`, `useDisableSmsIntegrationMutation`
|
|
252
|
-
| Templates
|
|
253
|
-
| Campaigns
|
|
254
|
-
| Preview
|
|
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…
|
|
283
|
-
|
|
|
284
|
-
| Get a taxonomy's terms as a flat list
|
|
285
|
-
| Get only the children of one term
|
|
286
|
-
| Get a taxonomy's terms as a ready-made tree
|
|
287
|
-
| Get the taxonomy structure (e.g. Countries → Cities) | `useFindTaxonomyTreeQuery`
|
|
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
|
-
{
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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,
|
|
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
|
-
{
|
|
345
|
-
|
|
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,
|
|
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
|
|
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",
|
|
380
|
-
{
|
|
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,
|
|
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
|
|
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",
|
|
416
|
-
{ "taxonomyId": "tax_categories", "parentId": "term_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,
|
|
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
|
|
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
|
|
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
|
-
{
|
|
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",
|
|
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",
|
|
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`) —
|
|
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:
|
|
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
|
|
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
|
|
769
|
-
|
|
770
|
-
| Email
|
|
771
|
-
| Push
|
|
772
|
-
| Sms
|
|
773
|
-
| Files
|
|
774
|
-
| Payments
|
|
775
|
-
| Code
|
|
776
|
-
| Membership | `Membership`
|
|
777
|
-
| Logs
|
|
778
|
-
| Database
|
|
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) =>
|
|
795
|
-
|
|
796
|
-
|
|
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
|