nexus-shared 1.1.21 → 3.0.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/CHANGELOG.md +262 -0
- package/LICENSE +21 -0
- package/README.md +1000 -0
- package/dist/Client.Index.d.ts +55 -0
- package/dist/Client.Index.js +60 -0
- package/dist/Components/Chats/Chat.d.ts +28 -0
- package/dist/Components/Chats/Chat.js +19 -0
- package/dist/Components/Chats/ChatButton.d.ts +26 -0
- package/dist/Components/Chats/ChatButton.js +41 -0
- package/dist/Components/Chats/ChatComposer.d.ts +41 -0
- package/dist/Components/Chats/ChatComposer.js +177 -0
- package/dist/Components/Chats/ChatConversations.d.ts +35 -0
- package/dist/Components/Chats/ChatConversations.js +38 -0
- package/dist/Components/Chats/ChatPanel.d.ts +66 -0
- package/dist/Components/Chats/ChatPanel.js +94 -0
- package/dist/Components/Chats/ChatParts.d.ts +87 -0
- package/dist/Components/Chats/ChatParts.js +100 -0
- package/dist/Components/Chats/ChatThread.d.ts +22 -0
- package/dist/Components/Chats/ChatThread.js +96 -0
- package/dist/Components/Documents/Button.d.ts +29 -0
- package/dist/Components/Documents/Button.js +11 -0
- package/dist/Components/Documents/Menu.d.ts +102 -0
- package/dist/Components/Documents/Menu.js +242 -0
- package/dist/Components/Documents/SplitButton.d.ts +64 -0
- package/dist/Components/Documents/SplitButton.js +129 -0
- package/dist/Components/Documents/TabButtons.d.ts +80 -0
- package/dist/Components/Documents/TabButtons.js +38 -0
- package/dist/Components/Forms/ApiForm.d.ts +23 -0
- package/dist/Components/Forms/ApiForm.js +100 -0
- package/dist/Components/Forms/Crud.d.ts +17 -0
- package/dist/Components/Forms/Crud.js +708 -0
- package/dist/Components/Forms/CrudParts.d.ts +15 -0
- package/dist/Components/Forms/CrudParts.js +36 -0
- package/dist/Components/Forms/ExcelImport.d.ts +42 -0
- package/dist/Components/Forms/ExcelImport.js +190 -0
- package/dist/Components/Forms/Form.d.ts +52 -0
- package/dist/Components/Forms/Form.js +323 -0
- package/dist/Components/Forms/SubmitForm.d.ts +65 -0
- package/dist/Components/Forms/SubmitForm.js +198 -0
- package/dist/Components/Inputs/Calendar.d.ts +69 -0
- package/dist/Components/Inputs/Calendar.js +258 -0
- package/dist/Components/Inputs/CheckBox.d.ts +53 -0
- package/dist/Components/Inputs/CheckBox.js +87 -0
- package/dist/Components/Inputs/CheckBoxGroup.d.ts +20 -0
- package/dist/Components/Inputs/CheckBoxGroup.js +80 -0
- package/dist/Components/Inputs/ChoiceField.d.ts +122 -0
- package/dist/Components/Inputs/ChoiceField.js +169 -0
- package/dist/Components/Inputs/DatePicker.d.ts +54 -0
- package/dist/Components/Inputs/DatePicker.js +246 -0
- package/dist/Components/Inputs/DateRangePicker.d.ts +62 -0
- package/dist/Components/Inputs/DateRangePicker.js +283 -0
- package/dist/Components/Inputs/DateTimePicker.d.ts +75 -0
- package/dist/Components/Inputs/DateTimePicker.js +400 -0
- package/dist/Components/Inputs/Dropdown.d.ts +117 -0
- package/dist/Components/Inputs/Dropdown.js +839 -0
- package/dist/Components/Inputs/DropdownParts.d.ts +84 -0
- package/dist/Components/Inputs/DropdownParts.js +102 -0
- package/dist/Components/Inputs/EditorShell.d.ts +45 -0
- package/dist/Components/Inputs/EditorShell.js +214 -0
- package/dist/Components/Inputs/Field.d.ts +53 -0
- package/dist/Components/Inputs/Field.js +16 -0
- package/dist/Components/Inputs/GroupForm.d.ts +20 -0
- package/dist/Components/Inputs/GroupForm.js +172 -0
- package/dist/Components/Inputs/InputField.d.ts +59 -0
- package/dist/Components/Inputs/InputField.js +169 -0
- package/dist/Components/Inputs/InputRenderer.d.ts +37 -0
- package/dist/Components/Inputs/InputRenderer.js +99 -0
- package/dist/Components/Inputs/MarkdownEditor.d.ts +67 -0
- package/dist/Components/Inputs/MarkdownEditor.js +289 -0
- package/dist/Components/Inputs/NumberBox.d.ts +53 -0
- package/dist/Components/Inputs/NumberBox.js +228 -0
- package/dist/Components/Inputs/RadioGroup.d.ts +20 -0
- package/dist/Components/Inputs/RadioGroup.js +43 -0
- package/dist/Components/Inputs/ReadOnlyNotice.d.ts +36 -0
- package/dist/Components/Inputs/ReadOnlyNotice.js +117 -0
- package/dist/Components/Inputs/RichTextEditor.d.ts +103 -0
- package/dist/Components/Inputs/RichTextEditor.js +1170 -0
- package/dist/Components/Inputs/RichTextParts.d.ts +63 -0
- package/dist/Components/Inputs/RichTextParts.js +157 -0
- package/dist/Components/Inputs/RowsInput.d.ts +115 -0
- package/dist/Components/Inputs/RowsInput.js +308 -0
- package/dist/Components/Inputs/Slider.d.ts +87 -0
- package/dist/Components/Inputs/Slider.js +216 -0
- package/dist/Components/Inputs/TabularForm.d.ts +27 -0
- package/dist/Components/Inputs/TabularForm.js +348 -0
- package/dist/Components/Inputs/TextArea.d.ts +43 -0
- package/dist/Components/Inputs/TextArea.js +31 -0
- package/dist/Components/Inputs/TextBox.d.ts +52 -0
- package/dist/Components/Inputs/TextBox.js +58 -0
- package/dist/Components/Inputs/TextField.d.ts +31 -0
- package/dist/Components/Inputs/TextField.js +35 -0
- package/dist/Components/Inputs/TimePanel.d.ts +34 -0
- package/dist/Components/Inputs/TimePanel.js +288 -0
- package/dist/Components/Inputs/TimePicker.d.ts +54 -0
- package/dist/Components/Inputs/TimePicker.js +240 -0
- package/dist/Components/Inputs/TreeView.d.ts +160 -0
- package/dist/Components/Inputs/TreeView.js +947 -0
- package/dist/Components/Layouts/MessageBox.d.ts +71 -0
- package/dist/Components/Layouts/MessageBox.js +104 -0
- package/dist/Components/Layouts/Popup.d.ts +92 -0
- package/dist/Components/Layouts/Popup.js +376 -0
- package/dist/Components/Layouts/PopupContext.d.ts +2 -0
- package/dist/Components/Layouts/PopupContext.js +4 -0
- package/dist/Components/Layouts/PopupDock.d.ts +22 -0
- package/dist/Components/Layouts/PopupDock.js +121 -0
- package/dist/Components/Layouts/PopupHost.d.ts +56 -0
- package/dist/Components/Layouts/PopupHost.js +163 -0
- package/dist/Components/Layouts/PopupRules.d.ts +30 -0
- package/dist/Components/Layouts/PopupRules.js +63 -0
- package/dist/Components/Layouts/ThemePicker.d.ts +13 -0
- package/dist/Components/Layouts/ThemePicker.js +22 -0
- package/dist/Components/Layouts/ThemeScript.d.ts +14 -0
- package/dist/Components/Layouts/ThemeScript.js +13 -0
- package/dist/Components/Layouts/ThemeSwitcher.d.ts +13 -0
- package/dist/Components/Layouts/ThemeSwitcher.js +49 -0
- package/dist/Components/Layouts/Toaster.d.ts +76 -0
- package/dist/Components/Layouts/Toaster.js +172 -0
- package/dist/Components/Viewers/DataTable.d.ts +25 -0
- package/dist/Components/Viewers/DataTable.js +596 -0
- package/dist/Components/Viewers/DataTableColumns.d.ts +28 -0
- package/dist/Components/Viewers/DataTableColumns.js +243 -0
- package/dist/Components/Viewers/DataTableParts.d.ts +138 -0
- package/dist/Components/Viewers/DataTableParts.js +157 -0
- package/dist/Components/Viewers/MarkdownView.d.ts +26 -0
- package/dist/Components/Viewers/MarkdownView.js +79 -0
- package/dist/Components/Viewers/RichTextView.d.ts +18 -0
- package/dist/Components/Viewers/RichTextView.js +17 -0
- package/dist/Helpers/AnimationHelpers.d.ts +6 -0
- package/dist/Helpers/AnimationHelpers.js +35 -0
- package/dist/Helpers/ApiClient.d.ts +96 -0
- package/dist/Helpers/ApiClient.js +200 -0
- package/dist/Helpers/ApiFormHelpers.d.ts +49 -0
- package/dist/Helpers/ApiFormHelpers.js +120 -0
- package/dist/Helpers/ApiModules.d.ts +131 -0
- package/dist/Helpers/ApiModules.js +195 -0
- package/dist/Helpers/ApiResponses.d.ts +51 -0
- package/dist/Helpers/ApiResponses.js +250 -0
- package/dist/Helpers/ApiRoutes.d.ts +140 -0
- package/dist/Helpers/ApiRoutes.js +183 -0
- package/dist/Helpers/ApiToasts.d.ts +21 -0
- package/dist/Helpers/ApiToasts.js +22 -0
- package/dist/Helpers/ChatBackend.d.ts +134 -0
- package/dist/Helpers/ChatBackend.js +332 -0
- package/dist/Helpers/ChatHelpers.d.ts +189 -0
- package/dist/Helpers/ChatHelpers.js +486 -0
- package/dist/Helpers/ChatHooks.d.ts +81 -0
- package/dist/Helpers/ChatHooks.js +175 -0
- package/dist/Helpers/ChatStore.d.ts +132 -0
- package/dist/Helpers/ChatStore.js +394 -0
- package/dist/Helpers/ChatSummaries.d.ts +39 -0
- package/dist/Helpers/ChatSummaries.js +118 -0
- package/dist/Helpers/ClassHelpers.d.ts +3 -0
- package/dist/Helpers/ClassHelpers.js +4 -0
- package/dist/Helpers/CrudBackend.d.ts +98 -0
- package/dist/Helpers/CrudBackend.js +214 -0
- package/dist/Helpers/CrudHelpers.d.ts +111 -0
- package/dist/Helpers/CrudHelpers.js +267 -0
- package/dist/Helpers/DateHelpers.d.ts +88 -0
- package/dist/Helpers/DateHelpers.js +363 -0
- package/dist/Helpers/DatePreferences.d.ts +14 -0
- package/dist/Helpers/DatePreferences.js +94 -0
- package/dist/Helpers/DateTimeHelpers.d.ts +87 -0
- package/dist/Helpers/DateTimeHelpers.js +286 -0
- package/dist/Helpers/DragHelpers.d.ts +129 -0
- package/dist/Helpers/DragHelpers.js +281 -0
- package/dist/Helpers/DropdownHelpers.d.ts +56 -0
- package/dist/Helpers/DropdownHelpers.js +180 -0
- package/dist/Helpers/ExcelBackend.d.ts +106 -0
- package/dist/Helpers/ExcelBackend.js +290 -0
- package/dist/Helpers/ExcelHelpers.d.ts +98 -0
- package/dist/Helpers/ExcelHelpers.js +302 -0
- package/dist/Helpers/FormDrafts.d.ts +18 -0
- package/dist/Helpers/FormDrafts.js +76 -0
- package/dist/Helpers/FormStore.d.ts +233 -0
- package/dist/Helpers/FormStore.js +752 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +79 -0
- package/dist/Helpers/InputParamsHelpers.js +388 -0
- package/dist/Helpers/MarkdownEditing.d.ts +27 -0
- package/dist/Helpers/MarkdownEditing.js +250 -0
- package/dist/Helpers/MarkdownHelpers.d.ts +82 -0
- package/dist/Helpers/MarkdownHelpers.js +775 -0
- package/dist/Helpers/MessageBuilder.d.ts +74 -0
- package/dist/Helpers/MessageBuilder.js +252 -0
- package/dist/Helpers/NepaliCalendar.d.ts +25 -0
- package/dist/Helpers/NepaliCalendar.js +107 -0
- package/dist/Helpers/NumberHelpers.d.ts +71 -0
- package/dist/Helpers/NumberHelpers.js +202 -0
- package/dist/Helpers/Permissions.d.ts +62 -0
- package/dist/Helpers/Permissions.js +133 -0
- package/dist/Helpers/PopoverHelpers.d.ts +68 -0
- package/dist/Helpers/PopoverHelpers.js +195 -0
- package/dist/Helpers/RichTextHelpers.d.ts +77 -0
- package/dist/Helpers/RichTextHelpers.js +1072 -0
- package/dist/Helpers/RowsStore.d.ts +166 -0
- package/dist/Helpers/RowsStore.js +606 -0
- package/dist/Helpers/TableColumns.d.ts +47 -0
- package/dist/Helpers/TableColumns.js +282 -0
- package/dist/Helpers/TableExport.d.ts +30 -0
- package/dist/Helpers/TableExport.js +97 -0
- package/dist/Helpers/TableHelpers.d.ts +110 -0
- package/dist/Helpers/TableHelpers.js +301 -0
- package/dist/Helpers/TableStore.d.ts +142 -0
- package/dist/Helpers/TableStore.js +530 -0
- package/dist/Helpers/ThemeHelpers.d.ts +15 -0
- package/dist/Helpers/ThemeHelpers.js +16 -0
- package/dist/Helpers/TimeHelpers.d.ts +73 -0
- package/dist/Helpers/TimeHelpers.js +253 -0
- package/dist/Helpers/TreeStore.d.ts +188 -0
- package/dist/Helpers/TreeStore.js +577 -0
- package/dist/Helpers/ValidationHelpers.d.ts +124 -0
- package/dist/Helpers/ValidationHelpers.js +301 -0
- package/dist/Interfaces/ApiInterfaces.d.ts +174 -0
- package/dist/Interfaces/ApiInterfaces.js +30 -0
- package/dist/Interfaces/ChatInterfaces.d.ts +240 -0
- package/{src/sso-client.tsx → dist/Interfaces/ChatInterfaces.js} +1 -1
- package/dist/Interfaces/CrudInterfaces.d.ts +232 -0
- package/dist/Interfaces/CrudInterfaces.js +1 -0
- package/dist/Interfaces/DateInterfaces.d.ts +154 -0
- package/dist/Interfaces/DateInterfaces.js +29 -0
- package/dist/Interfaces/ExcelInterfaces.d.ts +215 -0
- package/dist/Interfaces/ExcelInterfaces.js +45 -0
- package/dist/Interfaces/FormInterfaces.d.ts +402 -0
- package/dist/Interfaces/FormInterfaces.js +1 -0
- package/dist/Interfaces/InputInterfaces.d.ts +141 -0
- package/dist/Interfaces/InputInterfaces.js +1 -0
- package/dist/Interfaces/MessageInterfaces.d.ts +111 -0
- package/dist/Interfaces/MessageInterfaces.js +5 -0
- package/dist/Interfaces/TableInterfaces.d.ts +351 -0
- package/dist/Interfaces/TableInterfaces.js +1 -0
- package/dist/Interfaces/ThemeInterfaces.d.ts +17 -0
- package/dist/Interfaces/ThemeInterfaces.js +17 -0
- package/dist/Interfaces/TypeInterfaces.d.ts +4 -0
- package/dist/Interfaces/TypeInterfaces.js +1 -0
- package/dist/Server.Index.d.ts +2 -0
- package/dist/Server.Index.js +5 -0
- package/dist/Services/ApiProxy.d.ts +32 -0
- package/dist/Services/ApiProxy.js +111 -0
- package/dist/Services/BrowserApi.d.ts +95 -0
- package/dist/Services/BrowserApi.js +129 -0
- package/dist/Services/ChatApi.d.ts +37 -0
- package/dist/Services/ChatApi.js +98 -0
- package/dist/Services/ExcelApi.d.ts +46 -0
- package/dist/Services/ExcelApi.js +196 -0
- package/dist/Services/ServerApi.d.ts +54 -0
- package/dist/Services/ServerApi.js +78 -0
- package/dist/Services/ThemeService.d.ts +17 -0
- package/dist/Services/ThemeService.js +83 -0
- package/dist/Shared.Index.d.ts +51 -0
- package/dist/Shared.Index.js +55 -0
- package/package.json +85 -33
- package/src/Styles/Nexus.Base.css +70 -0
- package/src/Styles/Nexus.Button.css +153 -0
- package/src/Styles/Nexus.Calendar.css +275 -0
- package/src/Styles/Nexus.Chat.css +1489 -0
- package/src/Styles/Nexus.Choice.css +345 -0
- package/src/Styles/Nexus.Crud.css +161 -0
- package/src/Styles/Nexus.DateTime.css +81 -0
- package/src/Styles/Nexus.Dropdown.css +774 -0
- package/src/Styles/Nexus.Editor.css +297 -0
- package/src/Styles/Nexus.Excel.css +370 -0
- package/src/Styles/Nexus.Form.css +321 -0
- package/src/Styles/Nexus.Index.css +36 -0
- package/src/Styles/Nexus.Input.css +637 -0
- package/src/Styles/Nexus.Markdown.css +265 -0
- package/src/Styles/Nexus.Menu.css +259 -0
- package/src/Styles/Nexus.Popup.css +731 -0
- package/src/Styles/Nexus.RichText.css +463 -0
- package/src/Styles/Nexus.Rows.css +596 -0
- package/src/Styles/Nexus.Slider.css +278 -0
- package/src/Styles/Nexus.Tab.Buttons.css +419 -0
- package/src/Styles/Nexus.Table.css +1296 -0
- package/src/Styles/Nexus.Theme.Picker.css +232 -0
- package/src/Styles/Nexus.Themes.css +194 -0
- package/src/Styles/Nexus.Time.css +147 -0
- package/src/Styles/Nexus.Toast.css +265 -0
- package/src/Styles/Nexus.Tokens.css +72 -0
- package/src/Styles/Nexus.Tree.css +435 -0
- package/src/api-services/authentication-service.tsx +0 -23
- package/src/api-services/preference-service.tsx +0 -5
- package/src/api-services/system-service.tsx +0 -29
- package/src/client.ts +0 -31
- package/src/components/documents/button.tsx +0 -133
- package/src/components/documents/icon-box.tsx +0 -93
- package/src/components/documents/page-title.tsx +0 -7
- package/src/components/documents/tab-button.tsx +0 -172
- package/src/components/documents/tag.tsx +0 -30
- package/src/components/index.js +0 -0
- package/src/components/inputs/checkbox-input.tsx +0 -66
- package/src/components/inputs/input-box.tsx +0 -48
- package/src/components/inputs/input-element.tsx +0 -65
- package/src/components/inputs/input-form.tsx +0 -164
- package/src/components/inputs/input.tsx +0 -181
- package/src/components/inputs/number-input.tsx +0 -108
- package/src/components/inputs/radiobox-input.tsx +0 -53
- package/src/components/inputs/textarea-input.tsx +0 -47
- package/src/components/inputs/textbox-input.tsx +0 -45
- package/src/components/layouts/global-dialogbox.tsx +0 -431
- package/src/components/layouts/global-layout.tsx +0 -64
- package/src/components/layouts/layout-helpers.tsx +0 -20
- package/src/components/layouts/panels/user-panel.tsx +0 -112
- package/src/components/layouts/utility-menu.tsx +0 -49
- package/src/components/panels/theme-panel.tsx +0 -46
- package/src/helpers/bitwise-helpers.tsx +0 -11
- package/src/helpers/browser-helpers.tsx +0 -156
- package/src/helpers/datasource-helpers.tsx +0 -99
- package/src/helpers/element-helpers.tsx +0 -123
- package/src/helpers/input-helpers.tsx +0 -74
- package/src/helpers/string-helpers.tsx +0 -28
- package/src/helpers/utility-helpers.tsx +0 -45
- package/src/helpers/validation-helpers.tsx +0 -302
- package/src/index.ts +0 -26
- package/src/interface.ts +0 -19
- package/src/interfaces/auth-token-interfaces.tsx +0 -6
- package/src/interfaces/browser-interfaces.tsx +0 -36
- package/src/interfaces/button-interfaces.tsx +0 -63
- package/src/interfaces/datasource-interfaces.tsx +0 -22
- package/src/interfaces/datatable-interfaces.tsx +0 -25
- package/src/interfaces/dialogbox-interfaces.tsx +0 -5
- package/src/interfaces/exception-interfaces.tsx +0 -99
- package/src/interfaces/http-interfaces.tsx +0 -129
- package/src/interfaces/icon-interfaces.tsx +0 -129
- package/src/interfaces/input-interfaces.tsx +0 -410
- package/src/interfaces/layout-interfaces.tsx +0 -191
- package/src/interfaces/menu-interfaces.tsx +0 -53
- package/src/interfaces/message-interfaces.tsx +0 -32
- package/src/interfaces/permission-interfaces.tsx +0 -9
- package/src/interfaces/storage-interfaces.tsx +0 -5
- package/src/interfaces/system-interfaces.tsx +0 -22
- package/src/interfaces/theme-interfaces.tsx +0 -123
- package/src/interfaces/type-interfaces.tsx +0 -28
- package/src/interfaces/user-interfaces.tsx +0 -47
- package/src/nexus-client.tsx +0 -21
- package/src/nexus.environments.tsx +0 -66
- package/src/proxy-api/proxy-backend.tsx +0 -60
- package/src/proxy-api/proxy-constants.tsx +0 -28
- package/src/proxy-api/proxy-helpers.tsx +0 -91
- package/src/proxy-api-client.tsx +0 -1
- package/src/proxy-api-server.tsx +0 -2
- package/src/services/http-client-services.tsx +0 -75
- package/src/services/http-services.tsx +0 -158
- package/src/services/loader-service.tsx +0 -185
- package/src/services/localstorage-service.tsx +0 -119
- package/src/services/message-services.tsx +0 -383
- package/src/services/theme-service.tsx +0 -163
- package/src/services/user-services.tsx +0 -10
- package/src/sso-config/auth-token-validation.ts +0 -16
- package/src/sso-config/callback-route.tsx +0 -35
- package/src/sso-config/cookie-encryption.ts +0 -51
- package/src/sso-config/cookie-helpers.tsx +0 -102
- package/src/sso-config/forgot-password-route.tsx +0 -36
- package/src/sso-config/oauth-callback-state.ts +0 -61
- package/src/sso-config/pkce-helpers.ts +0 -9
- package/src/sso-config/provider-complete-route.tsx +0 -18
- package/src/sso-config/redirect-context-actions.tsx +0 -45
- package/src/sso-config/redirect-context-helpers.tsx +0 -159
- package/src/sso-config/redirect-context-persist.tsx +0 -18
- package/src/sso-config/redirect-context-route.tsx +0 -45
- package/src/sso-config/refresh-route.tsx +0 -23
- package/src/sso-config/sign-in-route.tsx +0 -74
- package/src/sso-config/sign-out-route.tsx +0 -57
- package/src/sso-config/sign-up-route.tsx +0 -57
- package/src/sso-config/sso-backend-flow.ts +0 -38
- package/src/sso-config/sso-complete-route.tsx +0 -50
- package/src/sso-config/sso-config.ts +0 -59
- package/src/sso-config/sso-interfaces.tsx +0 -99
- package/src/sso-config/sso-response-helpers.tsx +0 -20
- package/src/sso-server.tsx +0 -21
- package/src/styles/nexus.animation.css +0 -269
- package/src/styles/nexus.core.css +0 -119
- package/src/styles/nexus.dialog.css +0 -144
- package/src/styles/nexus.grid-view.css +0 -42
- package/src/styles/nexus.icon.css +0 -51
- package/src/styles/nexus.input.css +0 -207
- package/src/styles/nexus.loader.css +0 -11
- package/src/styles/nexus.logic.css +0 -47
- package/src/styles/nexus.utility.css +0 -347
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ApiModuleValue } from "../Interfaces/ApiInterfaces.ts";
|
|
2
|
+
export interface ApiProxyOptions {
|
|
3
|
+
/** The user's token for the backend, such as from the session cookie. Without one, the request's own Authorization header goes on. */
|
|
4
|
+
token?: (request: Request) => string | null | undefined | Promise<string | null | undefined>;
|
|
5
|
+
/** Headers to add, such as a tenant from the session. */
|
|
6
|
+
headers?: (request: Request) => Record<string, string> | undefined | Promise<Record<string, string> | undefined>;
|
|
7
|
+
/** Serves only these modules, of those the app's `ApiModules` sends through the proxy. Default: all of those. */
|
|
8
|
+
allow?: readonly ApiModuleValue[];
|
|
9
|
+
/** Milliseconds to wait for the backend to start answering. Default 60,000. */
|
|
10
|
+
timeout?: number;
|
|
11
|
+
/** The `fetch` to use. Default: the global one. */
|
|
12
|
+
fetch?: typeof fetch;
|
|
13
|
+
}
|
|
14
|
+
/** What a Next.js route handler for `[module]/[...path]` receives. */
|
|
15
|
+
export interface ApiProxyContext {
|
|
16
|
+
params: Promise<{
|
|
17
|
+
module: string;
|
|
18
|
+
path?: string[];
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Makes the route handlers of the proxy. Only the modules' configured roots can be reached: a path cannot climb out of
|
|
23
|
+
* its module (`..` is refused), and the query goes on unchanged.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createApiProxy(options?: ApiProxyOptions): {
|
|
26
|
+
GET: (request: Request, context: ApiProxyContext) => Promise<Response>;
|
|
27
|
+
HEAD: (request: Request, context: ApiProxyContext) => Promise<Response>;
|
|
28
|
+
POST: (request: Request, context: ApiProxyContext) => Promise<Response>;
|
|
29
|
+
PUT: (request: Request, context: ApiProxyContext) => Promise<Response>;
|
|
30
|
+
PATCH: (request: Request, context: ApiProxyContext) => Promise<Response>;
|
|
31
|
+
DELETE: (request: Request, context: ApiProxyContext) => Promise<Response>;
|
|
32
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { DEFAULT_API_TIMEOUT, isFrameworkError } from "../Helpers/ApiClient.js";
|
|
2
|
+
import { getApiModules } from "../Helpers/ApiModules.js";
|
|
3
|
+
import { joinUrl } from "../Helpers/ApiRoutes.js";
|
|
4
|
+
import { buildMessage } from "../Helpers/MessageBuilder.js";
|
|
5
|
+
import { serverModuleRoot } from "./ServerApi.js";
|
|
6
|
+
const METHODS = new Set(["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"]);
|
|
7
|
+
/** Request headers that belong to this hop, or to the app (its cookies), not to the backend. */
|
|
8
|
+
const REQUEST_HEADERS_TO_SKIP = new Set(["host", "connection", "content-length", "transfer-encoding", "keep-alive", "upgrade", "cookie", "te", "trailer", "proxy-authorization", "proxy-connection", "accept-encoding"]);
|
|
9
|
+
/**
|
|
10
|
+
* Response headers that belong to the backend's hop. `content-encoding` and `content-length` go too: `fetch` has already
|
|
11
|
+
* decompressed the body, so passing them on would make the browser decode it twice.
|
|
12
|
+
*/
|
|
13
|
+
const RESPONSE_HEADERS_TO_SKIP = new Set(["connection", "transfer-encoding", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailer", "upgrade", "content-encoding", "content-length"]);
|
|
14
|
+
/** An answer the client reads like the backend's own. */
|
|
15
|
+
function failure(status, message, messageCode) {
|
|
16
|
+
return Response.json({ isSuccess: false, code: status, message, messageCode }, { status });
|
|
17
|
+
}
|
|
18
|
+
/** A backend redirect to its own host, pointed back through the proxy. */
|
|
19
|
+
function rewriteLocation(location, root, module, proxyPath) {
|
|
20
|
+
const target = new URL(location, root);
|
|
21
|
+
if (target.host !== root.host)
|
|
22
|
+
return location;
|
|
23
|
+
const base = root.pathname.replace(/\/+$/, "");
|
|
24
|
+
const relative = target.pathname.startsWith(`${base}/`) ? target.pathname.slice(base.length + 1) : target.pathname.replace(/^\/+/, "");
|
|
25
|
+
return `${joinUrl(joinUrl(proxyPath, module), relative)}${target.search}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Makes the route handlers of the proxy. Only the modules' configured roots can be reached: a path cannot climb out of
|
|
29
|
+
* its module (`..` is refused), and the query goes on unchanged.
|
|
30
|
+
*/
|
|
31
|
+
export function createApiProxy(options = {}) {
|
|
32
|
+
async function handle(request, context) {
|
|
33
|
+
const method = request.method.toUpperCase();
|
|
34
|
+
if (!METHODS.has(method))
|
|
35
|
+
return failure(405, buildMessage("reason", { status: 405 }), "METHOD_NOT_ALLOWED");
|
|
36
|
+
const { module: key, path = [] } = await context.params;
|
|
37
|
+
// Asked on every request, so the app's modules count even when they were registered after this route was made.
|
|
38
|
+
const modules = getApiModules();
|
|
39
|
+
const info = modules.info(key);
|
|
40
|
+
if (!info || (options.allow && !options.allow.includes(info.module)))
|
|
41
|
+
return failure(404, `No API module is named "${key}".`, "MODULE_NOT_FOUND");
|
|
42
|
+
if (!modules.proxies(info.module))
|
|
43
|
+
return failure(404, `The ${info.label} API is not called through this app's proxy.`, "MODULE_NOT_PROXIED");
|
|
44
|
+
if (path.some(segment => segment === "." || segment === ".." || /[\\/]/.test(segment)))
|
|
45
|
+
return failure(400, "The API path is not valid.", "INVALID_PATH");
|
|
46
|
+
const incoming = new URL(request.url);
|
|
47
|
+
let root;
|
|
48
|
+
try {
|
|
49
|
+
root = new URL(await serverModuleRoot(info.module, incoming.origin));
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (isFrameworkError(error))
|
|
53
|
+
throw error;
|
|
54
|
+
return failure(500, error instanceof Error ? error.message : String(error), "MODULE_NOT_CONFIGURED");
|
|
55
|
+
}
|
|
56
|
+
const target = new URL(joinUrl(root.toString(), path.map(encodeURIComponent).join("/")));
|
|
57
|
+
target.search = incoming.search;
|
|
58
|
+
const headers = new Headers();
|
|
59
|
+
request.headers.forEach((value, name) => {
|
|
60
|
+
if (!REQUEST_HEADERS_TO_SKIP.has(name.toLowerCase()))
|
|
61
|
+
headers.set(name, value);
|
|
62
|
+
});
|
|
63
|
+
for (const [name, value] of Object.entries((await options.headers?.(request)) ?? {}))
|
|
64
|
+
headers.set(name, value);
|
|
65
|
+
const token = await options.token?.(request);
|
|
66
|
+
if (token)
|
|
67
|
+
headers.set("Authorization", /^bearer /i.test(token) ? token : `Bearer ${token}`);
|
|
68
|
+
const controller = new AbortController();
|
|
69
|
+
const timeout = options.timeout ?? DEFAULT_API_TIMEOUT;
|
|
70
|
+
let timedOut = false;
|
|
71
|
+
const timer = setTimeout(() => {
|
|
72
|
+
timedOut = true;
|
|
73
|
+
controller.abort();
|
|
74
|
+
}, timeout);
|
|
75
|
+
// The browser left: stop waiting for the backend.
|
|
76
|
+
const leave = () => controller.abort();
|
|
77
|
+
request.signal?.addEventListener("abort", leave, { once: true });
|
|
78
|
+
let answer;
|
|
79
|
+
try {
|
|
80
|
+
answer = await (options.fetch ?? globalThis.fetch)(target, {
|
|
81
|
+
method,
|
|
82
|
+
headers,
|
|
83
|
+
body: method === "GET" || method === "HEAD" ? undefined : await request.arrayBuffer(),
|
|
84
|
+
redirect: "manual",
|
|
85
|
+
cache: "no-store",
|
|
86
|
+
signal: controller.signal,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (isFrameworkError(error))
|
|
91
|
+
throw error;
|
|
92
|
+
if (timedOut)
|
|
93
|
+
return failure(504, buildMessage("reason", { timeout: true }), "BACKEND_TIMEOUT");
|
|
94
|
+
return failure(502, `The ${info.label} API cannot be reached. Try again in a moment.`, "BACKEND_UNREACHABLE");
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
// The timeout covers the wait for the answer to start; a long download then streams through.
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
request.signal?.removeEventListener("abort", leave);
|
|
100
|
+
}
|
|
101
|
+
const responseHeaders = new Headers();
|
|
102
|
+
answer.headers.forEach((value, name) => {
|
|
103
|
+
const lower = name.toLowerCase();
|
|
104
|
+
if (RESPONSE_HEADERS_TO_SKIP.has(lower))
|
|
105
|
+
return;
|
|
106
|
+
responseHeaders.append(name, lower === "location" ? rewriteLocation(value, root, info.key, modules.proxyPath) : value);
|
|
107
|
+
});
|
|
108
|
+
return new Response(method === "HEAD" ? null : answer.body, { status: answer.status, statusText: answer.statusText, headers: responseHeaders });
|
|
109
|
+
}
|
|
110
|
+
return { GET: handle, HEAD: handle, POST: handle, PUT: handle, PATCH: handle, DELETE: handle };
|
|
111
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type ConfirmOptions } from "../Components/Layouts/MessageBox.tsx";
|
|
2
|
+
import { type ApiCall, type ApiResponseReader } from "../Helpers/ApiClient.ts";
|
|
3
|
+
import { type ApiToastDefaults, type ApiToastOption } from "../Helpers/ApiToasts.ts";
|
|
4
|
+
import type { ApiCallOptions, ApiResult, ApiTarget } from "../Interfaces/ApiInterfaces.ts";
|
|
5
|
+
import type { DropdownItem, DropdownPage, DropdownPageRequest } from "../Interfaces/InputInterfaces.ts";
|
|
6
|
+
/** Options of a browser call, on top of every call's options. */
|
|
7
|
+
export interface BrowserApiOptions {
|
|
8
|
+
/** Toasts for this call. Default: errors always, successes for POST, PUT, PATCH, and DELETE. See `ApiToastOption`. */
|
|
9
|
+
toast?: ApiToastOption;
|
|
10
|
+
}
|
|
11
|
+
/** How browser calls behave. Where they go (the proxy or each module's root) is the app's `ApiModules`. */
|
|
12
|
+
export interface BrowserApiConfig {
|
|
13
|
+
/** Headers on every call, such as the tenant or branch. */
|
|
14
|
+
headers?: (call: ApiCall<BrowserApiOptions>) => Record<string, string> | undefined;
|
|
15
|
+
/** A bearer token for every call. Leave it out with the proxy, which adds the token from the user's cookie. */
|
|
16
|
+
token?: (call: ApiCall<BrowserApiOptions>) => string | null | undefined;
|
|
17
|
+
/** Cookies on calls straight to a module on another origin: `include`. Default: the browser's (same origin only). */
|
|
18
|
+
credentials?: RequestCredentials;
|
|
19
|
+
/** Milliseconds to wait for an answer. Default 60,000. */
|
|
20
|
+
timeout?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Reads every answer, for a backend that does not answer as Nexus or ASP.NET Core do. Default `readApiResponse`.
|
|
23
|
+
* A call still reads its own with `read`. Registered once, it holds for calls the components make inside
|
|
24
|
+
* themselves too (`Crud`'s lists and actions, `ApiForm`'s save), which cannot be given one.
|
|
25
|
+
*/
|
|
26
|
+
read?: ApiResponseReader<BrowserApiOptions>;
|
|
27
|
+
/** Which toasts show when a call does not say. Default: errors always, successes for changes. */
|
|
28
|
+
toast?: ApiToastDefaults;
|
|
29
|
+
/** After a 401, or an expired token (Nexus error 2003), such as to send the user to sign in. The error toast shows too. */
|
|
30
|
+
onSignedOut?: (result: ApiResult) => void;
|
|
31
|
+
/** After every answer, such as to log failures. */
|
|
32
|
+
onResult?: (result: ApiResult, call: ApiCall<BrowserApiOptions>) => void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Sets up `api` for the app. Call it once in a client module that the root layout loads; later calls change only the
|
|
36
|
+
* settings they name. Nothing is needed when the proxy is at `/api/app` and the defaults suit.
|
|
37
|
+
*/
|
|
38
|
+
export declare function configureApi(config: BrowserApiConfig): void;
|
|
39
|
+
/**
|
|
40
|
+
* The app's default response reader, when it registered one (`configureApi({ read })`). For answers that did not come
|
|
41
|
+
* from a call and so have no client to read them: what an `ApiForm`'s own send function returned.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getApiReader(): ApiResponseReader<BrowserApiOptions> | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The app's call settings, for a request `api` cannot make: a download, which has to read the file's own bytes and the
|
|
46
|
+
* headers that name it, rather than an answer read as JSON. The headers, the token, and the cookie rule an app
|
|
47
|
+
* registered once belong on such a request too (`Services/ExcelApi.ts` is the one caller).
|
|
48
|
+
*/
|
|
49
|
+
export declare function getApiCallSettings(): BrowserApiConfig;
|
|
50
|
+
/** Options of `api.confirm`: the call's, and changes to the question. */
|
|
51
|
+
export type ConfirmApiOptions = ApiCallOptions & BrowserApiOptions & {
|
|
52
|
+
question?: Partial<ConfirmOptions>;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Asks first, then sends. The question comes from the message builder ("Delete Room 204?", "This cannot be undone.",
|
|
56
|
+
* a Delete button, in red for actions that lose something). Resolves with the answer, or `null` when the user said no.
|
|
57
|
+
*/
|
|
58
|
+
declare function confirmThenSend<T = unknown>(target: ApiTarget, { question, ...options }?: ConfirmApiOptions): Promise<ApiResult<T> | null>;
|
|
59
|
+
/**
|
|
60
|
+
* Calls APIs from the browser, with toasts.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* const ROOMS = MODULES.controller(NexusModule.Reservation, "Room", "room"); // MODULES: the app's defineApiModules(…)
|
|
64
|
+
*
|
|
65
|
+
* const rooms = await api.get<Room[]>(ROOMS.listing); // error toast if it fails
|
|
66
|
+
* if (rooms.ok) setRows(rooms.result);
|
|
67
|
+
*
|
|
68
|
+
* await api.put(ROOMS.update, values, { subject: `Room ${values.number}` }); // "Room 204 saved." or why not
|
|
69
|
+
* await api.delete(ROOMS.delete, { params: { id }, toast: { loading: true } });
|
|
70
|
+
* await api.confirm(ROOMS.delete, { params: { id }, subject: "Room 204" }); // "Delete Room 204?" first; null if not
|
|
71
|
+
* await api.post("/api/demo-rooms", values, { toast: false }); // a URL, no toasts
|
|
72
|
+
*/
|
|
73
|
+
export declare const api: import("../Shared.Index.ts").ApiClient<BrowserApiOptions> & {
|
|
74
|
+
confirm: typeof confirmThenSend;
|
|
75
|
+
};
|
|
76
|
+
export interface ApiOptionsLoaderOptions<TRow, TItem> {
|
|
77
|
+
/** Turns a row the API sends into a dropdown item. Default: the row is the item. */
|
|
78
|
+
toItem?: (row: TRow) => TItem;
|
|
79
|
+
/**
|
|
80
|
+
* What is sent for a page. Default, as Nexus pagination APIs take it: `{ searchKeyword, skipCount, maxCount }`, in the
|
|
81
|
+
* body for POST and in the query for GET.
|
|
82
|
+
*/
|
|
83
|
+
request?: (page: DropdownPageRequest) => Record<string, unknown>;
|
|
84
|
+
/** Other options of the call, such as `params` or `headers`. */
|
|
85
|
+
call?: Omit<ApiCallOptions, "body" | "signal">;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A dropdown's `loadOptions` for an API that sends pages (`{ results, totalCount, filterCount }`) or a whole list.
|
|
89
|
+
* Failures show in the dropdown, with Retry, not as a toast.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* <Dropdown name="roomId" label="Room" valueKey="id" labelKey="number" loadOptions={apiOptionsLoader(ROOMS.pagination)} />
|
|
93
|
+
*/
|
|
94
|
+
export declare function apiOptionsLoader<TRow extends DropdownItem = DropdownItem, TItem extends DropdownItem = TRow>(target: ApiTarget, { toItem, request, call }?: ApiOptionsLoaderOptions<TRow, TItem>): (page: DropdownPageRequest) => Promise<DropdownPage<TItem>>;
|
|
95
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { messageBox } from "../Components/Layouts/MessageBox.js";
|
|
3
|
+
import { toast } from "../Components/Layouts/Toaster.js";
|
|
4
|
+
import { apiCallContext, createApiClient } from "../Helpers/ApiClient.js";
|
|
5
|
+
import { unwrapApiResult } from "../Helpers/ApiResponses.js";
|
|
6
|
+
import { getApiModules } from "../Helpers/ApiModules.js";
|
|
7
|
+
import { isApiEndpoint, joinUrl } from "../Helpers/ApiRoutes.js";
|
|
8
|
+
import { planApiToast, planLoadingToast } from "../Helpers/ApiToasts.js";
|
|
9
|
+
import { buildMessage } from "../Helpers/MessageBuilder.js";
|
|
10
|
+
let settings = {};
|
|
11
|
+
/**
|
|
12
|
+
* Sets up `api` for the app. Call it once in a client module that the root layout loads; later calls change only the
|
|
13
|
+
* settings they name. Nothing is needed when the proxy is at `/api/app` and the defaults suit.
|
|
14
|
+
*/
|
|
15
|
+
export function configureApi(config) {
|
|
16
|
+
settings = { ...settings, ...config };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The app's default response reader, when it registered one (`configureApi({ read })`). For answers that did not come
|
|
20
|
+
* from a call and so have no client to read them: what an `ApiForm`'s own send function returned.
|
|
21
|
+
*/
|
|
22
|
+
export function getApiReader() {
|
|
23
|
+
return settings.read;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The app's call settings, for a request `api` cannot make: a download, which has to read the file's own bytes and the
|
|
27
|
+
* headers that name it, rather than an answer read as JSON. The headers, the token, and the cookie rule an app
|
|
28
|
+
* registered once belong on such a request too (`Services/ExcelApi.ts` is the one caller).
|
|
29
|
+
*/
|
|
30
|
+
export function getApiCallSettings() {
|
|
31
|
+
return settings;
|
|
32
|
+
}
|
|
33
|
+
/** Nexus error code for an expired token. */
|
|
34
|
+
const TOKEN_EXPIRED = 2003;
|
|
35
|
+
const TOAST_DURATION = 5000;
|
|
36
|
+
function showToast(plan, id) {
|
|
37
|
+
// `icon`, `duration`, and `closable` are set, so a loading toast turned into this one loses its spinner and timer.
|
|
38
|
+
toast(plan.title, { id, tone: plan.tone, message: plan.message, icon: undefined, duration: TOAST_DURATION, closable: true });
|
|
39
|
+
}
|
|
40
|
+
const config = {
|
|
41
|
+
resolveUrl: (endpoint, path) => joinUrl(getApiModules().browserUrl(endpoint.module), path),
|
|
42
|
+
headers: call => settings.headers?.(call),
|
|
43
|
+
token: call => settings.token?.(call),
|
|
44
|
+
get timeout() {
|
|
45
|
+
return settings.timeout;
|
|
46
|
+
},
|
|
47
|
+
get credentials() {
|
|
48
|
+
return settings.credentials;
|
|
49
|
+
},
|
|
50
|
+
get read() {
|
|
51
|
+
return settings.read;
|
|
52
|
+
},
|
|
53
|
+
dedupe: true,
|
|
54
|
+
onStart(call) {
|
|
55
|
+
const loading = planLoadingToast(call.context, call.options.toast);
|
|
56
|
+
const loadingId = loading ? toast.loading(loading) : undefined;
|
|
57
|
+
return result => {
|
|
58
|
+
if (result.status === 401 || result.errorCode === TOKEN_EXPIRED)
|
|
59
|
+
settings.onSignedOut?.(result);
|
|
60
|
+
settings.onResult?.(result, call);
|
|
61
|
+
const plan = planApiToast(result, call.method, call.context, call.options.toast, settings.toast);
|
|
62
|
+
if (!plan) {
|
|
63
|
+
if (loadingId)
|
|
64
|
+
toast.dismiss(loadingId);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// The same failure twice (two lists on a page loading the same URL) shows one toast, not two.
|
|
68
|
+
showToast(plan, loadingId ?? (plan.tone === "danger" && call.url ? `nx-api:${call.method}:${call.url}` : undefined));
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
const client = createApiClient(config);
|
|
73
|
+
/** Actions that lose something, asked about in red. */
|
|
74
|
+
const DESTRUCTIVE = new Set(["delete", "trash", "reject", "cancel"]);
|
|
75
|
+
/**
|
|
76
|
+
* Asks first, then sends. The question comes from the message builder ("Delete Room 204?", "This cannot be undone.",
|
|
77
|
+
* a Delete button, in red for actions that lose something). Resolves with the answer, or `null` when the user said no.
|
|
78
|
+
*/
|
|
79
|
+
async function confirmThenSend(target, { question, ...options } = {}) {
|
|
80
|
+
const { context } = apiCallContext(target, options);
|
|
81
|
+
const yes = await messageBox.confirm({
|
|
82
|
+
title: buildMessage("confirm", context),
|
|
83
|
+
message: buildMessage("confirmDetail", context) || undefined,
|
|
84
|
+
tone: typeof context.action === "string" && DESTRUCTIVE.has(context.action) ? "danger" : "question",
|
|
85
|
+
confirmLabel: buildMessage("label", context),
|
|
86
|
+
...question,
|
|
87
|
+
});
|
|
88
|
+
return yes ? client.send(target, options) : null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Calls APIs from the browser, with toasts.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* const ROOMS = MODULES.controller(NexusModule.Reservation, "Room", "room"); // MODULES: the app's defineApiModules(…)
|
|
95
|
+
*
|
|
96
|
+
* const rooms = await api.get<Room[]>(ROOMS.listing); // error toast if it fails
|
|
97
|
+
* if (rooms.ok) setRows(rooms.result);
|
|
98
|
+
*
|
|
99
|
+
* await api.put(ROOMS.update, values, { subject: `Room ${values.number}` }); // "Room 204 saved." or why not
|
|
100
|
+
* await api.delete(ROOMS.delete, { params: { id }, toast: { loading: true } });
|
|
101
|
+
* await api.confirm(ROOMS.delete, { params: { id }, subject: "Room 204" }); // "Delete Room 204?" first; null if not
|
|
102
|
+
* await api.post("/api/demo-rooms", values, { toast: false }); // a URL, no toasts
|
|
103
|
+
*/
|
|
104
|
+
export const api = Object.assign(client, { confirm: confirmThenSend });
|
|
105
|
+
/**
|
|
106
|
+
* A dropdown's `loadOptions` for an API that sends pages (`{ results, totalCount, filterCount }`) or a whole list.
|
|
107
|
+
* Failures show in the dropdown, with Retry, not as a toast.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* <Dropdown name="roomId" label="Room" valueKey="id" labelKey="number" loadOptions={apiOptionsLoader(ROOMS.pagination)} />
|
|
111
|
+
*/
|
|
112
|
+
export function apiOptionsLoader(target, { toItem, request, call } = {}) {
|
|
113
|
+
const method = call?.method ?? (isApiEndpoint(target) ? target.method : "GET");
|
|
114
|
+
return async (page) => {
|
|
115
|
+
const sent = request ? request(page) : { searchKeyword: page.search || undefined, skipCount: page.skip, maxCount: page.pageSize };
|
|
116
|
+
const answer = unwrapApiResult(await api.send(target, {
|
|
117
|
+
...call,
|
|
118
|
+
method,
|
|
119
|
+
...(method === "GET" ? { query: { ...call?.query, ...sent } } : { body: sent }),
|
|
120
|
+
signal: page.signal,
|
|
121
|
+
toast: false,
|
|
122
|
+
}));
|
|
123
|
+
const rows = Array.isArray(answer) ? answer : (answer?.results ?? []);
|
|
124
|
+
const items = toItem ? rows.map(toItem) : rows;
|
|
125
|
+
if (Array.isArray(answer))
|
|
126
|
+
return { items, hasMore: false, total: items.length };
|
|
127
|
+
return { items, total: answer?.filterCount, hasMore: page.skip + rows.length < (answer?.filterCount ?? 0) };
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ChatBackend } from "../Helpers/ChatBackend.ts";
|
|
2
|
+
import { ChatSummaryCache } from "../Helpers/ChatSummaries.ts";
|
|
3
|
+
import type { ApiCallOptions } from "../Interfaces/ApiInterfaces.ts";
|
|
4
|
+
import type { ChatDomainValue, ChatId, ChatPlaceSource, ChatScope, ChatSource, ChatSummary } from "../Interfaces/ChatInterfaces.ts";
|
|
5
|
+
/** Options of one chat's source. */
|
|
6
|
+
export interface ApiChatSourceOptions {
|
|
7
|
+
/** Which conversation at the place, when the record holds more than one. */
|
|
8
|
+
conversationId?: ChatId;
|
|
9
|
+
/** The backend to use, when it is not the app's registered one. */
|
|
10
|
+
backend?: ChatBackend<ChatDomainValue>;
|
|
11
|
+
/** Extras on every call this source makes, such as a header. */
|
|
12
|
+
call?: Omit<ApiCallOptions, "body" | "signal" | "params">;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A conversation's data through the app's API.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const scope = { domain: EDomainTypes.Leave, parentId: leave.id, userId: me.id };
|
|
19
|
+
* const source = useMemo(() => apiChatSource(scope), [scope.domain, scope.parentId, scope.userId]);
|
|
20
|
+
*/
|
|
21
|
+
export declare function apiChatSource<TDomain extends ChatDomainValue>(scope: ChatScope<TDomain>, options?: ApiChatSourceOptions): ChatSource;
|
|
22
|
+
/**
|
|
23
|
+
* The conversations a record holds, and how to start another.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const place = useMemo(() => apiChatPlace(scope), [scope.domain, scope.parentId, scope.userId]);
|
|
27
|
+
*/
|
|
28
|
+
export declare function apiChatPlace<TDomain extends ChatDomainValue>(scope: ChatScope<TDomain>, options?: ApiChatSourceOptions): ChatPlaceSource;
|
|
29
|
+
/** Asks the app's backend for many records' counts in one call. */
|
|
30
|
+
export declare function loadChatSummaries(domain: ChatDomainValue, parentIds: readonly ChatId[], userId: ChatId): Promise<Map<string, ChatSummary>>;
|
|
31
|
+
/**
|
|
32
|
+
* The counts every trigger icon on the page reads, filled in one call per domain however many icons there are. A page
|
|
33
|
+
* that already has the counts in its rows passes them to the icon instead, and never touches this.
|
|
34
|
+
*/
|
|
35
|
+
export declare function chatSummaries(): ChatSummaryCache;
|
|
36
|
+
/** Replaces the page's count cache: a test's, or one with a different batching window. */
|
|
37
|
+
export declare function defineChatSummaries(replacement: ChatSummaryCache): ChatSummaryCache;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { getChatBackend } from "../Helpers/ChatBackend.js";
|
|
3
|
+
import { ChatSummaryCache } from "../Helpers/ChatSummaries.js";
|
|
4
|
+
import { unwrapApiResult } from "../Helpers/ApiResponses.js";
|
|
5
|
+
import { api } from "./BrowserApi.js";
|
|
6
|
+
const callOptions = (options, signal) => ({ ...options?.call, ...(signal && { signal }), toast: false });
|
|
7
|
+
/**
|
|
8
|
+
* A conversation's data through the app's API.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const scope = { domain: EDomainTypes.Leave, parentId: leave.id, userId: me.id };
|
|
12
|
+
* const source = useMemo(() => apiChatSource(scope), [scope.domain, scope.parentId, scope.userId]);
|
|
13
|
+
*/
|
|
14
|
+
export function apiChatSource(scope, options) {
|
|
15
|
+
const backend = (options?.backend ?? getChatBackend());
|
|
16
|
+
const chat = options?.conversationId;
|
|
17
|
+
const source = {
|
|
18
|
+
async history(request) {
|
|
19
|
+
const answer = await api.send(backend.endpoint("history"), { ...callOptions(options, request.signal), body: backend.historyRequest(scope, request, chat) });
|
|
20
|
+
return backend.readHistory(unwrapApiResult(answer));
|
|
21
|
+
},
|
|
22
|
+
async send(draft, signal) {
|
|
23
|
+
const answer = await api.send(backend.endpoint("send"), { ...callOptions(options, signal), body: backend.sendRequest(scope, draft, chat) });
|
|
24
|
+
return backend.readMessage(unwrapApiResult(answer), scope, draft);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
if (backend.supports("participants")) {
|
|
28
|
+
source.participants = async (query) => {
|
|
29
|
+
const answer = await api.send(backend.endpoint("participants"), { ...callOptions(options, query.signal), body: backend.participantsRequest(scope, query, chat) });
|
|
30
|
+
return backend.readParticipants(unwrapApiResult(answer));
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (backend.supports("addParticipants")) {
|
|
34
|
+
source.addParticipants = async (ids, signal) => {
|
|
35
|
+
const answer = await api.send(backend.endpoint("addParticipants"), { ...callOptions(options, signal), body: backend.addParticipantsRequest(scope, ids, chat) });
|
|
36
|
+
return backend.readParticipants(unwrapApiResult(answer)).items;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (backend.supports("read")) {
|
|
40
|
+
source.markRead = async (upTo, signal) => {
|
|
41
|
+
unwrapApiResult(await api.send(backend.endpoint("read"), { ...callOptions(options, signal), body: backend.readMarkRequest(scope, upTo, chat) }));
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (backend.supports("remove")) {
|
|
45
|
+
source.remove = async (messageId, signal) => {
|
|
46
|
+
unwrapApiResult(await api.send(backend.endpoint("remove"), { ...callOptions(options, signal), params: backend.removeParams(scope, messageId) }));
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return source;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The conversations a record holds, and how to start another.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const place = useMemo(() => apiChatPlace(scope), [scope.domain, scope.parentId, scope.userId]);
|
|
56
|
+
*/
|
|
57
|
+
export function apiChatPlace(scope, options) {
|
|
58
|
+
const backend = (options?.backend ?? getChatBackend());
|
|
59
|
+
const place = {
|
|
60
|
+
async conversations(signal) {
|
|
61
|
+
const answer = await api.send(backend.endpoint("conversations"), { ...callOptions(options, signal), body: backend.conversationsRequest(scope) });
|
|
62
|
+
return backend.readConversations(unwrapApiResult(answer));
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
if (backend.supports("createConversation")) {
|
|
66
|
+
place.create = async (request, signal) => {
|
|
67
|
+
const answer = await api.send(backend.endpoint("createConversation"), { ...callOptions(options, signal), body: backend.createConversationRequest(scope, request) });
|
|
68
|
+
return backend.readConversation(unwrapApiResult(answer));
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (backend.supports("participants")) {
|
|
72
|
+
// Without a conversation, this asks who could be put in a new one here.
|
|
73
|
+
place.participants = async (query) => {
|
|
74
|
+
const answer = await api.send(backend.endpoint("participants"), { ...callOptions(options, query.signal), body: backend.participantsRequest(scope, query) });
|
|
75
|
+
return backend.readParticipants(unwrapApiResult(answer));
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return place;
|
|
79
|
+
}
|
|
80
|
+
/** Asks the app's backend for many records' counts in one call. */
|
|
81
|
+
export async function loadChatSummaries(domain, parentIds, userId) {
|
|
82
|
+
const backend = getChatBackend();
|
|
83
|
+
const answer = await api.send(backend.endpoint("summaries"), { body: backend.summariesRequest(domain, parentIds, userId), toast: false });
|
|
84
|
+
return backend.readSummaries(unwrapApiResult(answer));
|
|
85
|
+
}
|
|
86
|
+
let cache;
|
|
87
|
+
/**
|
|
88
|
+
* The counts every trigger icon on the page reads, filled in one call per domain however many icons there are. A page
|
|
89
|
+
* that already has the counts in its rows passes them to the icon instead, and never touches this.
|
|
90
|
+
*/
|
|
91
|
+
export function chatSummaries() {
|
|
92
|
+
return (cache ??= new ChatSummaryCache({ load: loadChatSummaries }));
|
|
93
|
+
}
|
|
94
|
+
/** Replaces the page's count cache: a test's, or one with a different batching window. */
|
|
95
|
+
export function defineChatSummaries(replacement) {
|
|
96
|
+
cache = replacement;
|
|
97
|
+
return replacement;
|
|
98
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ApiCallOptions, ApiResult } from "../Interfaces/ApiInterfaces.ts";
|
|
2
|
+
import type { ExcelDownload, ExcelEndpoints, ExcelExportQuery, ExcelImportAnswer, ExcelSettings, ExcelSheetRow, ExcelVerifyAnswer } from "../Interfaces/ExcelInterfaces.ts";
|
|
3
|
+
/** Options every spreadsheet call takes. */
|
|
4
|
+
export interface ExcelCallOptions extends Pick<ApiCallOptions, "params" | "query" | "headers" | "signal" | "timeout" | "subject" | "read"> {
|
|
5
|
+
/** Shows no toast for this call. Default: a failure is told, a success is not (the panel says what happened). */
|
|
6
|
+
toast?: false;
|
|
7
|
+
}
|
|
8
|
+
/** Options of an export. Its `query` is what the person has on screen, not query-string values. */
|
|
9
|
+
export interface ExcelExportOptions extends Omit<ExcelCallOptions, "query"> {
|
|
10
|
+
/** The search, the sort and the filters the person has on screen. */
|
|
11
|
+
query?: ExcelExportQuery;
|
|
12
|
+
/**
|
|
13
|
+
* Rows the search on screen leaves, when the page knows. Over the backend's limit, the person is asked first and
|
|
14
|
+
* told what will happen; without it, nothing is asked and the answer's own headers say what was left out.
|
|
15
|
+
*/
|
|
16
|
+
rows?: number;
|
|
17
|
+
/** What one row is, for the question and the toast: "city". Default: the settings' subject. */
|
|
18
|
+
subject?: string;
|
|
19
|
+
/** Asks nothing, whatever the count. For a page that asked already. */
|
|
20
|
+
confirm?: false;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The limits and the shape of the sheet. It is the same for everybody and changes once a release, so the first answer
|
|
24
|
+
* is kept for as long as the page lives - the backend says the same with a `Cache-Control` of an hour.
|
|
25
|
+
*/
|
|
26
|
+
export declare function excelSettings(endpoints: ExcelEndpoints, options?: ExcelCallOptions): Promise<ApiResult<ExcelSettings>>;
|
|
27
|
+
/** Forgets the kept settings, for a test or an app that changed what a sheet holds while it ran. */
|
|
28
|
+
export declare function clearExcelSettings(): void;
|
|
29
|
+
/** The template to fill in, as the backend built it, under the name the answer gives it. */
|
|
30
|
+
export declare function downloadExcelTemplate(endpoints: ExcelEndpoints, options?: ExcelCallOptions): Promise<ApiResult<ExcelDownload>>;
|
|
31
|
+
/**
|
|
32
|
+
* An export of what the person has on screen: the search, the sort and the filters, sent to the model's own action.
|
|
33
|
+
* Not a file written from the rows the browser happens to hold - the backend writes every row the search leaves, up to
|
|
34
|
+
* its own limit, and says in the answer's headers what it left out.
|
|
35
|
+
*
|
|
36
|
+
* Over that limit the person is asked first, because a file that is quietly short is worse than one they chose.
|
|
37
|
+
*/
|
|
38
|
+
export declare function exportExcel(endpoints: ExcelEndpoints, options?: ExcelExportOptions): Promise<ApiResult<ExcelDownload> | null>;
|
|
39
|
+
/** The filled file read and judged, with nothing written. The rows it answers with are what an import sends back. */
|
|
40
|
+
export declare function verifyExcelFile(endpoints: ExcelEndpoints, file: File, options?: ExcelCallOptions): Promise<ApiResult<ExcelVerifyAnswer>>;
|
|
41
|
+
/**
|
|
42
|
+
* Writes the rows a verify answered with, as the person left them - the **rows**, never the file again, or every fix
|
|
43
|
+
* they made on screen would be lost. Every check runs again on the way in: what the browser sends is a row, not a
|
|
44
|
+
* decision.
|
|
45
|
+
*/
|
|
46
|
+
export declare function importExcelRows(endpoints: ExcelEndpoints, rows: readonly ExcelSheetRow[], options?: ExcelCallOptions): Promise<ApiResult<ExcelImportAnswer>>;
|