@lenne.tech/ng-base 18.1.3 → 22.0.1
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/CLAUDE.md +55 -0
- package/LICENSE +2 -2
- package/README.md +237 -44
- package/fesm2022/lenne.tech-ng-base-ai.mjs +771 -0
- package/fesm2022/lenne.tech-ng-base-ai.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-auth-provide.mjs +945 -0
- package/fesm2022/lenne.tech-ng-base-auth-provide.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-auth.mjs +454 -0
- package/fesm2022/lenne.tech-ng-base-auth.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-bug-report-dialog-BRzVmKJr.mjs +825 -0
- package/fesm2022/lenne.tech-ng-base-bug-bug-report-dialog-BRzVmKJr.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-bug-report-service-DWoTb_iw.mjs +351 -0
- package/fesm2022/lenne.tech-ng-base-bug-bug-report-service-DWoTb_iw.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-capture-engine-TkjMDdqj.mjs +1046 -0
- package/fesm2022/lenne.tech-ng-base-bug-capture-engine-TkjMDdqj.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-constants-Brm2KGHi.mjs +4 -0
- package/fesm2022/lenne.tech-ng-base-bug-constants-Brm2KGHi.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-messages-BsP4fRQU.mjs +179 -0
- package/fesm2022/lenne.tech-ng-base-bug-messages-BsP4fRQU.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-provide.mjs +26 -0
- package/fesm2022/lenne.tech-ng-base-bug-provide.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-report-Tzlhe1Cm.mjs +62 -0
- package/fesm2022/lenne.tech-ng-base-bug-report-Tzlhe1Cm.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-server.mjs +818 -0
- package/fesm2022/lenne.tech-ng-base-bug-server.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug.mjs +196 -0
- package/fesm2022/lenne.tech-ng-base-bug.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-files-ui.mjs +445 -0
- package/fesm2022/lenne.tech-ng-base-files-ui.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-files.mjs +474 -0
- package/fesm2022/lenne.tech-ng-base-files.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-server.mjs +408 -0
- package/fesm2022/lenne.tech-ng-base-server.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-share.mjs +76 -0
- package/fesm2022/lenne.tech-ng-base-share.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-sync.mjs +1096 -0
- package/fesm2022/lenne.tech-ng-base-sync.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-testing.mjs +171 -0
- package/fesm2022/lenne.tech-ng-base-testing.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-ui-provide.mjs +124 -0
- package/fesm2022/lenne.tech-ng-base-ui-provide.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-ui.mjs +34645 -0
- package/fesm2022/lenne.tech-ng-base-ui.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-utils.mjs +287 -0
- package/fesm2022/lenne.tech-ng-base-utils.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base.mjs +615 -910
- package/fesm2022/lenne.tech-ng-base.mjs.map +1 -1
- package/i18n/auth/de.json +17 -0
- package/i18n/auth/en.json +17 -0
- package/i18n/bug/de.json +85 -0
- package/i18n/bug/en.json +85 -0
- package/i18n/de.json +8 -0
- package/i18n/en.json +8 -0
- package/i18n/files/de.json +27 -0
- package/i18n/files/en.json +27 -0
- package/i18n/share/de.json +8 -0
- package/i18n/share/en.json +8 -0
- package/i18n/ui/de.json +38 -0
- package/i18n/ui/en.json +38 -0
- package/package.json +127 -58
- package/types/lenne.tech-ng-base-ai.d.ts +845 -0
- package/types/lenne.tech-ng-base-auth-provide.d.ts +604 -0
- package/types/lenne.tech-ng-base-auth.d.ts +320 -0
- package/types/lenne.tech-ng-base-bug-provide.d.ts +272 -0
- package/types/lenne.tech-ng-base-bug-rolldown-runtime-C25Le4-w.d.ts +1 -0
- package/types/lenne.tech-ng-base-bug-server.d.ts +316 -0
- package/types/lenne.tech-ng-base-bug.d.ts +202 -0
- package/types/lenne.tech-ng-base-files-ui.d.ts +116 -0
- package/types/lenne.tech-ng-base-files.d.ts +288 -0
- package/types/lenne.tech-ng-base-server.d.ts +262 -0
- package/types/lenne.tech-ng-base-share.d.ts +21 -0
- package/types/lenne.tech-ng-base-sync.d.ts +380 -0
- package/types/lenne.tech-ng-base-testing.d.ts +327 -0
- package/types/lenne.tech-ng-base-ui-provide.d.ts +65 -0
- package/types/lenne.tech-ng-base-ui.d.ts +7744 -0
- package/types/lenne.tech-ng-base-utils.d.ts +150 -0
- package/types/lenne.tech-ng-base.d.ts +503 -0
- package/ui/styles/components/accordion.css +3 -0
- package/ui/styles/components/alert-dialog.css +4 -0
- package/ui/styles/components/alert.css +3 -0
- package/ui/styles/components/aspect-ratio.css +3 -0
- package/ui/styles/components/attachment.css +4 -0
- package/ui/styles/components/autocomplete.css +4 -0
- package/ui/styles/components/avatar.css +3 -0
- package/ui/styles/components/badge.css +3 -0
- package/ui/styles/components/breadcrumb.css +3 -0
- package/ui/styles/components/bubble.css +3 -0
- package/ui/styles/components/button-group.css +3 -0
- package/ui/styles/components/button.css +3 -0
- package/ui/styles/components/calendar.css +5 -0
- package/ui/styles/components/card.css +3 -0
- package/ui/styles/components/checkbox.css +3 -0
- package/ui/styles/components/code-block.css +4 -0
- package/ui/styles/components/collapsible.css +3 -0
- package/ui/styles/components/combobox.css +5 -0
- package/ui/styles/components/command.css +5 -0
- package/ui/styles/components/context-menu.css +3 -0
- package/ui/styles/components/date-picker.css +7 -0
- package/ui/styles/components/dialog.css +4 -0
- package/ui/styles/components/drawer.css +3 -0
- package/ui/styles/components/dropdown-menu.css +3 -0
- package/ui/styles/components/empty.css +3 -0
- package/ui/styles/components/field.css +5 -0
- package/ui/styles/components/hover-card.css +3 -0
- package/ui/styles/components/index.css +91 -0
- package/ui/styles/components/input-group.css +6 -0
- package/ui/styles/components/input-otp.css +3 -0
- package/ui/styles/components/input.css +3 -0
- package/ui/styles/components/item.css +4 -0
- package/ui/styles/components/kbd.css +3 -0
- package/ui/styles/components/label.css +3 -0
- package/ui/styles/components/lt-breadcrumb.css +3 -0
- package/ui/styles/components/lt-bug-attachments.css +5 -0
- package/ui/styles/components/lt-bug-journey-timeline.css +5 -0
- package/ui/styles/components/lt-bug-report-dialog.css +15 -0
- package/ui/styles/components/lt-bug-report.css +4 -0
- package/ui/styles/components/lt-color-mode-button.css +3 -0
- package/ui/styles/components/lt-confirm-dialog.css +4 -0
- package/ui/styles/components/lt-data-view.css +5 -0
- package/ui/styles/components/lt-date-picker.css +3 -0
- package/ui/styles/components/lt-date-time-picker.css +5 -0
- package/ui/styles/components/lt-enter-action.css +2 -0
- package/ui/styles/components/lt-footer.css +3 -0
- package/ui/styles/components/lt-form-field.css +4 -0
- package/ui/styles/components/lt-header.css +5 -0
- package/ui/styles/components/lt-imports.css +21 -0
- package/ui/styles/components/lt-navigation-menu.css +3 -0
- package/ui/styles/components/lt-notice.css +3 -0
- package/ui/styles/components/lt-overlay-defaults.css +3 -0
- package/ui/styles/components/lt-page-card.css +3 -0
- package/ui/styles/components/lt-page-title.css +2 -0
- package/ui/styles/components/lt-password-input.css +4 -0
- package/ui/styles/components/lt-readonly-field.css +2 -0
- package/ui/styles/components/lt-select.css +4 -0
- package/ui/styles/components/lt-text-field.css +5 -0
- package/ui/styles/components/lt-toolbar-item.css +2 -0
- package/ui/styles/components/lt-toolbar.css +3 -0
- package/ui/styles/components/lt-tus-upload-component.css +5 -0
- package/ui/styles/components/marker.css +3 -0
- package/ui/styles/components/menubar.css +3 -0
- package/ui/styles/components/message-scroller.css +4 -0
- package/ui/styles/components/message.css +3 -0
- package/ui/styles/components/native-select.css +3 -0
- package/ui/styles/components/navigation-menu.css +3 -0
- package/ui/styles/components/pagination.css +4 -0
- package/ui/styles/components/popover.css +3 -0
- package/ui/styles/components/progress.css +3 -0
- package/ui/styles/components/questionnaire.css +4 -0
- package/ui/styles/components/radio-group.css +3 -0
- package/ui/styles/components/resizable.css +3 -0
- package/ui/styles/components/select.css +3 -0
- package/ui/styles/components/separator.css +3 -0
- package/ui/styles/components/sheet.css +4 -0
- package/ui/styles/components/sidebar.css +9 -0
- package/ui/styles/components/skeleton.css +3 -0
- package/ui/styles/components/slider.css +3 -0
- package/ui/styles/components/sonner.css +4 -0
- package/ui/styles/components/spinner.css +3 -0
- package/ui/styles/components/switch.css +3 -0
- package/ui/styles/components/table.css +3 -0
- package/ui/styles/components/tabs.css +4 -0
- package/ui/styles/components/textarea.css +3 -0
- package/ui/styles/components/toggle-group.css +4 -0
- package/ui/styles/components/toggle.css +3 -0
- package/ui/styles/components/tooltip.css +3 -0
- package/ui/styles/components/trace.css +3 -0
- package/ui/styles/components/typography.css +3 -0
- package/ui/styles/components/utils.css +2 -0
- package/ui/styles/core.css +137 -0
- package/ui/styles/palette.css +94 -0
- package/ui/styles/theme.css +14 -0
- package/ui/styles/transitions.css +131 -0
- package/base-cms/index.d.ts +0 -5
- package/base-cms/interfaces/base-cms-module-config.interface.d.ts +0 -16
- package/base-cms/pages/base-cms/base-cms.component.d.ts +0 -92
- package/base-cms/public-api.d.ts +0 -5
- package/base-components/components/breadcrumb/breadcrumb.component.d.ts +0 -10
- package/base-components/components/change-password/change-password.component.d.ts +0 -24
- package/base-components/components/checkbox/checkbox.component.d.ts +0 -12
- package/base-components/components/delete-account/delete-account.component.d.ts +0 -15
- package/base-components/components/fab-button/fab-button.component.d.ts +0 -16
- package/base-components/components/input/input.component.d.ts +0 -27
- package/base-components/components/loading-bar/loading-bar.component.d.ts +0 -17
- package/base-components/components/model-form/model-form-base.component.d.ts +0 -64
- package/base-components/components/model-form/model-form.component.d.ts +0 -139
- package/base-components/components/model-form-sub/model-form-sub.component.d.ts +0 -56
- package/base-components/components/model-table/model-table.component.d.ts +0 -115
- package/base-components/components/refence-input/refence-input.component.d.ts +0 -84
- package/base-components/components/select/select.component.d.ts +0 -25
- package/base-components/components/skeleton-loader/skeleton-loader.component.d.ts +0 -10
- package/base-components/components/tags/tags.component.d.ts +0 -60
- package/base-components/components/textarea/textarea.component.d.ts +0 -21
- package/base-components/components/toast/toast.component.d.ts +0 -19
- package/base-components/components/upload-image/upload-image.component.d.ts +0 -111
- package/base-components/directives/back-button.directive.d.ts +0 -9
- package/base-components/directives/context-menu.directive.d.ts +0 -22
- package/base-components/directives/sort.directive.d.ts +0 -15
- package/base-components/directives/stop-propagation.directive.d.ts +0 -10
- package/base-components/index.d.ts +0 -5
- package/base-components/pipes/ellipses.pipe.d.ts +0 -10
- package/base-components/public-api.d.ts +0 -26
- package/base-prototype/index.d.ts +0 -5
- package/base-prototype/pages/email-verification/email-verification.component.d.ts +0 -18
- package/base-prototype/pages/forgot-password/forgot-password.component.d.ts +0 -26
- package/base-prototype/pages/login/login.component.d.ts +0 -33
- package/base-prototype/pages/profile/profile.component.d.ts +0 -24
- package/base-prototype/pages/register/register.component.d.ts +0 -31
- package/base-prototype/pages/set-password/set-password.component.d.ts +0 -29
- package/base-prototype/public-api.d.ts +0 -9
- package/esm2022/base-cms/interfaces/base-cms-module-config.interface.mjs +0 -3
- package/esm2022/base-cms/lenne.tech-ng-base-base-cms.mjs +0 -5
- package/esm2022/base-cms/pages/base-cms/base-cms.component.mjs +0 -238
- package/esm2022/base-cms/public-api.mjs +0 -8
- package/esm2022/base-components/components/breadcrumb/breadcrumb.component.mjs +0 -23
- package/esm2022/base-components/components/change-password/change-password.component.mjs +0 -97
- package/esm2022/base-components/components/checkbox/checkbox.component.mjs +0 -38
- package/esm2022/base-components/components/delete-account/delete-account.component.mjs +0 -59
- package/esm2022/base-components/components/fab-button/fab-button.component.mjs +0 -24
- package/esm2022/base-components/components/input/input.component.mjs +0 -75
- package/esm2022/base-components/components/loading-bar/loading-bar.component.mjs +0 -37
- package/esm2022/base-components/components/model-form/model-form-base.component.mjs +0 -145
- package/esm2022/base-components/components/model-form/model-form.component.mjs +0 -591
- package/esm2022/base-components/components/model-form-sub/model-form-sub.component.mjs +0 -117
- package/esm2022/base-components/components/model-table/model-table.component.mjs +0 -478
- package/esm2022/base-components/components/refence-input/refence-input.component.mjs +0 -278
- package/esm2022/base-components/components/select/select.component.mjs +0 -62
- package/esm2022/base-components/components/skeleton-loader/skeleton-loader.component.mjs +0 -33
- package/esm2022/base-components/components/tags/tags.component.mjs +0 -227
- package/esm2022/base-components/components/textarea/textarea.component.mjs +0 -79
- package/esm2022/base-components/components/toast/toast.component.mjs +0 -69
- package/esm2022/base-components/components/upload-image/upload-image.component.mjs +0 -346
- package/esm2022/base-components/directives/back-button.directive.mjs +0 -25
- package/esm2022/base-components/directives/context-menu.directive.mjs +0 -112
- package/esm2022/base-components/directives/sort.directive.mjs +0 -61
- package/esm2022/base-components/directives/stop-propagation.directive.mjs +0 -24
- package/esm2022/base-components/lenne.tech-ng-base-base-components.mjs +0 -5
- package/esm2022/base-components/pipes/ellipses.pipe.mjs +0 -23
- package/esm2022/base-components/public-api.mjs +0 -30
- package/esm2022/base-prototype/lenne.tech-ng-base-base-prototype.mjs +0 -5
- package/esm2022/base-prototype/pages/email-verification/email-verification.component.mjs +0 -60
- package/esm2022/base-prototype/pages/forgot-password/forgot-password.component.mjs +0 -78
- package/esm2022/base-prototype/pages/login/login.component.mjs +0 -114
- package/esm2022/base-prototype/pages/profile/profile.component.mjs +0 -87
- package/esm2022/base-prototype/pages/register/register.component.mjs +0 -100
- package/esm2022/base-prototype/pages/set-password/set-password.component.mjs +0 -91
- package/esm2022/base-prototype/public-api.mjs +0 -11
- package/esm2022/lenne.tech-ng-base.mjs +0 -5
- package/esm2022/public-api.mjs +0 -25
- package/esm2022/shared/classes/auth.class.mjs +0 -14
- package/esm2022/shared/classes/basic-user.class.mjs +0 -49
- package/esm2022/shared/classes/graphql-enum.class.mjs +0 -10
- package/esm2022/shared/classes/graphql-meta.class.mjs +0 -345
- package/esm2022/shared/classes/graphql-type.class.mjs +0 -14
- package/esm2022/shared/classes/helper.class.mjs +0 -307
- package/esm2022/shared/classes/sort.class.mjs +0 -38
- package/esm2022/shared/classes/standard.class.mjs +0 -40
- package/esm2022/shared/decorators/debounce.decorator.mjs +0 -12
- package/esm2022/shared/enums/comparison-operator.enum.mjs +0 -16
- package/esm2022/shared/enums/graphql-request-type.enum.mjs +0 -10
- package/esm2022/shared/enums/logical-operator.enum.mjs +0 -10
- package/esm2022/shared/enums/skeleton-type.enum.mjs +0 -7
- package/esm2022/shared/enums/sort-order.enum.mjs +0 -6
- package/esm2022/shared/enums/toast-type.enum.mjs +0 -8
- package/esm2022/shared/functions/ws-client.function.mjs +0 -39
- package/esm2022/shared/interfaces/base-module-config.interface.mjs +0 -3
- package/esm2022/shared/interfaces/breadcrumb.interface.mjs +0 -2
- package/esm2022/shared/interfaces/can-component-deactivate.interface.mjs +0 -2
- package/esm2022/shared/interfaces/cms-enum-values.interface.mjs +0 -2
- package/esm2022/shared/interfaces/cms-field-config.interface.mjs +0 -2
- package/esm2022/shared/interfaces/cms-model-config.interface.mjs +0 -2
- package/esm2022/shared/interfaces/cms-table-field.interface.mjs +0 -2
- package/esm2022/shared/interfaces/combined-filter-input.interface.mjs +0 -2
- package/esm2022/shared/interfaces/compress-options.interface.mjs +0 -2
- package/esm2022/shared/interfaces/cropping-options.interface.mjs +0 -2
- package/esm2022/shared/interfaces/field-object.interface.mjs +0 -2
- package/esm2022/shared/interfaces/field.interface.mjs +0 -2
- package/esm2022/shared/interfaces/filter-input.interface.mjs +0 -2
- package/esm2022/shared/interfaces/find-args.interface.mjs +0 -2
- package/esm2022/shared/interfaces/graphql-crud-type.interface.mjs +0 -2
- package/esm2022/shared/interfaces/graphql-fields-object.interface.mjs +0 -2
- package/esm2022/shared/interfaces/graphql-options.interface.mjs +0 -2
- package/esm2022/shared/interfaces/graphql-plus-options.interfacen.mjs +0 -2
- package/esm2022/shared/interfaces/graphql-type-collection.interface.mjs +0 -2
- package/esm2022/shared/interfaces/login-config.interface.mjs +0 -2
- package/esm2022/shared/interfaces/register-config.interface.mjs +0 -2
- package/esm2022/shared/interfaces/single-filter-input.interface.mjs +0 -2
- package/esm2022/shared/interfaces/sort-input.interface.mjs +0 -2
- package/esm2022/shared/interfaces/time-components.interface.mjs +0 -2
- package/esm2022/shared/interfaces/toast.interface.mjs +0 -2
- package/esm2022/shared/lenne.tech-ng-base-shared.mjs +0 -5
- package/esm2022/shared/public-api.mjs +0 -74
- package/esm2022/shared/services/auth.service.mjs +0 -152
- package/esm2022/shared/services/breadcrumb.service.mjs +0 -69
- package/esm2022/shared/services/cms.service.mjs +0 -82
- package/esm2022/shared/services/file.service.mjs +0 -86
- package/esm2022/shared/services/forms.service.mjs +0 -61
- package/esm2022/shared/services/graphql-meta.service.mjs +0 -132
- package/esm2022/shared/services/graphql-plus.service.mjs +0 -93
- package/esm2022/shared/services/graphql.service.mjs +0 -510
- package/esm2022/shared/services/image.service.mjs +0 -102
- package/esm2022/shared/services/loader.service.mjs +0 -78
- package/esm2022/shared/services/navigation.service.mjs +0 -38
- package/esm2022/shared/services/scroll.service.mjs +0 -207
- package/esm2022/shared/services/storage.service.mjs +0 -149
- package/esm2022/shared/services/toast.service.mjs +0 -39
- package/esm2022/shared/services/user.service.mjs +0 -160
- package/esm2022/shared/services/ws.service.mjs +0 -17
- package/esm2022/shared/types/graphql-fields.type.mjs +0 -2
- package/esm2022/shared/types/storage.type.mjs +0 -2
- package/esm2022/shared/validators/email.validator.mjs +0 -7
- package/esm2022/shared/validators/match.validator.mjs +0 -22
- package/esm2022/shared/validators/secure-password.validator.mjs +0 -7
- package/esm2022/src/base.module.mjs +0 -64
- package/esm2022/src/decorators/auto-unsubscribe.decorator.mjs +0 -23
- package/esm2022/src/directives/lazy-load.directive.mjs +0 -48
- package/esm2022/src/directives/match-height.directive.mjs +0 -92
- package/esm2022/src/directives/resizable.directive.mjs +0 -92
- package/esm2022/src/factories/apollo-options.factory.mjs +0 -108
- package/esm2022/src/functions/extract-files.function.mjs +0 -87
- package/esm2022/src/guards/auth.guard.mjs +0 -91
- package/esm2022/src/guards/can-deactivate.guard.mjs +0 -16
- package/esm2022/src/guards/logout.guard.mjs +0 -34
- package/esm2022/src/pipes/date-ago.pipe.mjs +0 -64
- package/esm2022/src/pipes/safe-html.pipe.mjs +0 -22
- package/esm2022/src/services/fullscreen.service.mjs +0 -51
- package/esm2022/src/services/seo.service.mjs +0 -117
- package/esm2022/src/services/theme.service.mjs +0 -44
- package/fesm2022/lenne.tech-ng-base-base-cms.mjs +0 -251
- package/fesm2022/lenne.tech-ng-base-base-cms.mjs.map +0 -1
- package/fesm2022/lenne.tech-ng-base-base-components.mjs +0 -2917
- package/fesm2022/lenne.tech-ng-base-base-components.mjs.map +0 -1
- package/fesm2022/lenne.tech-ng-base-base-prototype.mjs +0 -505
- package/fesm2022/lenne.tech-ng-base-base-prototype.mjs.map +0 -1
- package/fesm2022/lenne.tech-ng-base-shared.mjs +0 -2863
- package/fesm2022/lenne.tech-ng-base-shared.mjs.map +0 -1
- package/index.d.ts +0 -5
- package/public-api.d.ts +0 -17
- package/shared/classes/auth.class.d.ts +0 -10
- package/shared/classes/basic-user.class.d.ts +0 -27
- package/shared/classes/graphql-enum.class.d.ts +0 -7
- package/shared/classes/graphql-meta.class.d.ts +0 -66
- package/shared/classes/graphql-type.class.d.ts +0 -13
- package/shared/classes/helper.class.d.ts +0 -99
- package/shared/classes/sort.class.d.ts +0 -12
- package/shared/classes/standard.class.d.ts +0 -32
- package/shared/decorators/debounce.decorator.d.ts +0 -1
- package/shared/enums/comparison-operator.enum.d.ts +0 -14
- package/shared/enums/graphql-request-type.enum.d.ts +0 -8
- package/shared/enums/logical-operator.enum.d.ts +0 -8
- package/shared/enums/skeleton-type.enum.d.ts +0 -5
- package/shared/enums/sort-order.enum.d.ts +0 -4
- package/shared/enums/toast-type.enum.d.ts +0 -6
- package/shared/functions/ws-client.function.d.ts +0 -5
- package/shared/index.d.ts +0 -5
- package/shared/interfaces/base-module-config.interface.d.ts +0 -61
- package/shared/interfaces/breadcrumb.interface.d.ts +0 -4
- package/shared/interfaces/can-component-deactivate.interface.d.ts +0 -7
- package/shared/interfaces/cms-enum-values.interface.d.ts +0 -4
- package/shared/interfaces/cms-field-config.interface.d.ts +0 -45
- package/shared/interfaces/cms-model-config.interface.d.ts +0 -14
- package/shared/interfaces/cms-table-field.interface.d.ts +0 -10
- package/shared/interfaces/combined-filter-input.interface.d.ts +0 -9
- package/shared/interfaces/compress-options.interface.d.ts +0 -15
- package/shared/interfaces/cropping-options.interface.d.ts +0 -12
- package/shared/interfaces/field-object.interface.d.ts +0 -6
- package/shared/interfaces/field.interface.d.ts +0 -5
- package/shared/interfaces/filter-input.interface.d.ts +0 -9
- package/shared/interfaces/find-args.interface.d.ts +0 -11
- package/shared/interfaces/graphql-crud-type.interface.d.ts +0 -7
- package/shared/interfaces/graphql-fields-object.interface.d.ts +0 -6
- package/shared/interfaces/graphql-options.interface.d.ts +0 -11
- package/shared/interfaces/graphql-plus-options.interfacen.d.ts +0 -9
- package/shared/interfaces/graphql-type-collection.interface.d.ts +0 -7
- package/shared/interfaces/login-config.interface.d.ts +0 -6
- package/shared/interfaces/register-config.interface.d.ts +0 -4
- package/shared/interfaces/single-filter-input.interface.d.ts +0 -11
- package/shared/interfaces/sort-input.interface.d.ts +0 -5
- package/shared/interfaces/time-components.interface.d.ts +0 -6
- package/shared/interfaces/toast.interface.d.ts +0 -8
- package/shared/public-api.d.ts +0 -65
- package/shared/services/auth.service.d.ts +0 -44
- package/shared/services/breadcrumb.service.d.ts +0 -32
- package/shared/services/cms.service.d.ts +0 -27
- package/shared/services/file.service.d.ts +0 -22
- package/shared/services/forms.service.d.ts +0 -14
- package/shared/services/graphql-meta.service.d.ts +0 -45
- package/shared/services/graphql-plus.service.d.ts +0 -27
- package/shared/services/graphql.service.d.ts +0 -71
- package/shared/services/image.service.d.ts +0 -27
- package/shared/services/loader.service.d.ts +0 -32
- package/shared/services/navigation.service.d.ts +0 -15
- package/shared/services/scroll.service.d.ts +0 -85
- package/shared/services/storage.service.d.ts +0 -49
- package/shared/services/toast.service.d.ts +0 -15
- package/shared/services/user.service.d.ts +0 -77
- package/shared/services/ws.service.d.ts +0 -8
- package/shared/types/graphql-fields.type.d.ts +0 -5
- package/shared/types/storage.type.d.ts +0 -4
- package/shared/validators/email.validator.d.ts +0 -1
- package/shared/validators/match.validator.d.ts +0 -4
- package/shared/validators/secure-password.validator.d.ts +0 -1
- package/src/base.module.d.ts +0 -16
- package/src/decorators/auto-unsubscribe.decorator.d.ts +0 -5
- package/src/directives/lazy-load.directive.d.ts +0 -17
- package/src/directives/match-height.directive.d.ts +0 -20
- package/src/directives/resizable.directive.d.ts +0 -26
- package/src/factories/apollo-options.factory.d.ts +0 -16
- package/src/functions/extract-files.function.d.ts +0 -15
- package/src/guards/auth.guard.d.ts +0 -27
- package/src/guards/can-deactivate.guard.d.ts +0 -10
- package/src/guards/logout.guard.d.ts +0 -16
- package/src/pipes/date-ago.pipe.d.ts +0 -34
- package/src/pipes/safe-html.pipe.d.ts +0 -13
- package/src/services/fullscreen.service.d.ts +0 -18
- package/src/services/seo.service.d.ts +0 -61
- package/src/services/theme.service.d.ts +0 -17
|
@@ -0,0 +1,1096 @@
|
|
|
1
|
+
import { DOCUMENT, isPlatformBrowser } from "@angular/common";
|
|
2
|
+
import { DestroyRef, InjectionToken, PLATFORM_ID, assertInInjectionContext, effect, inject, makeEnvironmentProviders, provideAppInitializer, signal, untracked } from "@angular/core";
|
|
3
|
+
import { LT_AUTH_SIGN_OUT_HOOKS, LtAuthStore } from "@lenne.tech/ng-base/auth/provide";
|
|
4
|
+
import { LT_FETCH, LT_RUNTIME_CONFIG, LtApiUrlService, mergeLtFeatureOptions } from "@lenne.tech/ng-base";
|
|
5
|
+
const LT_SYNC_DB_NAME = "lt-offline-sync";
|
|
6
|
+
const LT_SYNC_CHANNEL_NAME = "lt-offline-sync";
|
|
7
|
+
const STORES = [
|
|
8
|
+
"meta",
|
|
9
|
+
"outbox",
|
|
10
|
+
"records"
|
|
11
|
+
];
|
|
12
|
+
const done = (request) => new Promise((resolve, reject) => {
|
|
13
|
+
request.addEventListener("success", () => resolve(request.result));
|
|
14
|
+
request.addEventListener("error", () => reject(request.error));
|
|
15
|
+
});
|
|
16
|
+
let connection;
|
|
17
|
+
let opening;
|
|
18
|
+
function openDatabase() {
|
|
19
|
+
if (connection) return Promise.resolve(connection);
|
|
20
|
+
opening ??= (async () => {
|
|
21
|
+
const request = indexedDB.open(LT_SYNC_DB_NAME, 1);
|
|
22
|
+
request.addEventListener("upgradeneeded", () => {
|
|
23
|
+
const db = request.result;
|
|
24
|
+
db.createObjectStore("records", { keyPath: ["model", "id"] }).createIndex("model", "model");
|
|
25
|
+
const outbox = db.createObjectStore("outbox", {
|
|
26
|
+
autoIncrement: true,
|
|
27
|
+
keyPath: "seq"
|
|
28
|
+
});
|
|
29
|
+
outbox.createIndex("mutationId", "mutationId", { unique: true });
|
|
30
|
+
outbox.createIndex("model", "model");
|
|
31
|
+
db.createObjectStore("meta", { keyPath: "key" });
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
const db = await done(request);
|
|
35
|
+
db.addEventListener("versionchange", () => {
|
|
36
|
+
db.close();
|
|
37
|
+
if (connection === db) connection = void 0;
|
|
38
|
+
});
|
|
39
|
+
connection = db;
|
|
40
|
+
return db;
|
|
41
|
+
} finally {
|
|
42
|
+
opening = void 0;
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
return opening;
|
|
46
|
+
}
|
|
47
|
+
var LtSyncOwnerChangedError = class extends Error {
|
|
48
|
+
constructor() {
|
|
49
|
+
super("the local sync data changed owner");
|
|
50
|
+
this.name = "LtSyncOwnerChangedError";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
async function runLtSyncTx(model, fn, options = {}) {
|
|
54
|
+
const transaction = (await openDatabase()).transaction(STORES, "readwrite");
|
|
55
|
+
const finished = new Promise((resolve, reject) => {
|
|
56
|
+
transaction.addEventListener("complete", () => resolve());
|
|
57
|
+
transaction.addEventListener("abort", () => reject(transaction.error ?? /* @__PURE__ */ new Error("transaction aborted")));
|
|
58
|
+
transaction.addEventListener("error", () => reject(transaction.error));
|
|
59
|
+
});
|
|
60
|
+
const store = (name) => transaction.objectStore(name);
|
|
61
|
+
const strip = (stored) => {
|
|
62
|
+
if (!stored) return void 0;
|
|
63
|
+
const { model: _model, ...record } = stored;
|
|
64
|
+
return record;
|
|
65
|
+
};
|
|
66
|
+
const tx = {
|
|
67
|
+
addOutbox: (item) => {
|
|
68
|
+
const { seq: _seq, ...rest } = item;
|
|
69
|
+
return done(store("outbox").add({
|
|
70
|
+
...rest,
|
|
71
|
+
model
|
|
72
|
+
}));
|
|
73
|
+
},
|
|
74
|
+
clearRecords: async (keepIds) => {
|
|
75
|
+
const keys = await done(store("records").index("model").getAllKeys(model));
|
|
76
|
+
for (const key of keys) if (!keepIds.has(key[1])) await done(store("records").delete(key));
|
|
77
|
+
},
|
|
78
|
+
deleteMeta: (key) => done(store("meta").delete(key)),
|
|
79
|
+
deleteRecord: (id) => done(store("records").delete([model, id])),
|
|
80
|
+
getMeta: async (key) => (await done(store("meta").get(key)))?.value,
|
|
81
|
+
getRecord: async (id) => strip(await done(store("records").get([model, id]))),
|
|
82
|
+
listOutbox: async () => {
|
|
83
|
+
return (await done(store("outbox").index("model").getAll(model))).map(({ model: _model, ...item }) => item);
|
|
84
|
+
},
|
|
85
|
+
listRecords: async () => (await done(store("records").index("model").getAll(model))).map((record) => strip(record)),
|
|
86
|
+
putOutbox: (item) => done(store("outbox").put({
|
|
87
|
+
...item,
|
|
88
|
+
model
|
|
89
|
+
})),
|
|
90
|
+
putRecord: (record) => done(store("records").put({
|
|
91
|
+
...record,
|
|
92
|
+
model
|
|
93
|
+
})),
|
|
94
|
+
removeOutbox: async (mutationId) => {
|
|
95
|
+
const key = await done(store("outbox").index("mutationId").getKey(mutationId));
|
|
96
|
+
if (key !== void 0) await done(store("outbox").delete(key));
|
|
97
|
+
},
|
|
98
|
+
setMeta: (key, value) => done(store("meta").put({
|
|
99
|
+
key,
|
|
100
|
+
value
|
|
101
|
+
}))
|
|
102
|
+
};
|
|
103
|
+
let result;
|
|
104
|
+
try {
|
|
105
|
+
if (options.owner !== void 0 && await tx.getMeta("owner") !== options.owner) throw new LtSyncOwnerChangedError();
|
|
106
|
+
result = await fn(tx);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
transaction.abort();
|
|
109
|
+
await finished.catch(() => void 0);
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
await finished;
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
async function listLtSyncPendingModels() {
|
|
116
|
+
const transaction = (await openDatabase()).transaction(["outbox"], "readonly");
|
|
117
|
+
const items = await done(transaction.objectStore("outbox").getAll());
|
|
118
|
+
return [...new Set(items.map((item) => item.model))];
|
|
119
|
+
}
|
|
120
|
+
function getLtSyncOwner() {
|
|
121
|
+
return runLtSyncTx("", (tx) => tx.getMeta("owner"));
|
|
122
|
+
}
|
|
123
|
+
async function claimLtSyncOwner(userId) {
|
|
124
|
+
const transaction = (await openDatabase()).transaction(STORES, "readwrite");
|
|
125
|
+
const finished = new Promise((resolve, reject) => {
|
|
126
|
+
transaction.addEventListener("complete", () => resolve());
|
|
127
|
+
transaction.addEventListener("abort", () => reject(transaction.error ?? /* @__PURE__ */ new Error("transaction aborted")));
|
|
128
|
+
transaction.addEventListener("error", () => reject(transaction.error));
|
|
129
|
+
});
|
|
130
|
+
const meta = transaction.objectStore("meta");
|
|
131
|
+
const owner = (await done(meta.get("owner")))?.value;
|
|
132
|
+
let wiped = false;
|
|
133
|
+
if (owner !== userId) {
|
|
134
|
+
if (await done(transaction.objectStore("records").count()) + await done(transaction.objectStore("outbox").count()) + await done(meta.count()) > 0) {
|
|
135
|
+
for (const name of STORES) await done(transaction.objectStore(name).clear());
|
|
136
|
+
wiped = true;
|
|
137
|
+
}
|
|
138
|
+
await done(meta.put({
|
|
139
|
+
key: "owner",
|
|
140
|
+
value: userId
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
await finished;
|
|
144
|
+
if (wiped) postLtSyncMessage({ type: "cleared" });
|
|
145
|
+
return wiped;
|
|
146
|
+
}
|
|
147
|
+
async function deleteLtSyncDatabase() {
|
|
148
|
+
if (typeof indexedDB === "undefined") return;
|
|
149
|
+
await opening?.catch(() => void 0);
|
|
150
|
+
connection?.close();
|
|
151
|
+
connection = void 0;
|
|
152
|
+
await new Promise((resolve, reject) => {
|
|
153
|
+
const request = indexedDB.deleteDatabase(LT_SYNC_DB_NAME);
|
|
154
|
+
request.addEventListener("success", () => resolve());
|
|
155
|
+
request.addEventListener("error", () => reject(request.error));
|
|
156
|
+
request.addEventListener("blocked", () => void 0);
|
|
157
|
+
});
|
|
158
|
+
postLtSyncMessage({ type: "cleared" });
|
|
159
|
+
}
|
|
160
|
+
function postLtSyncMessage(message) {
|
|
161
|
+
if (typeof BroadcastChannel === "undefined") return;
|
|
162
|
+
const channel = new BroadcastChannel(LT_SYNC_CHANNEL_NAME);
|
|
163
|
+
try {
|
|
164
|
+
channel.postMessage(message);
|
|
165
|
+
} finally {
|
|
166
|
+
channel.close();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const PUSH_CHUNK = 100;
|
|
170
|
+
const MAX_PUSH_ROUNDS = 50;
|
|
171
|
+
var LtSyncOfflineError = class extends Error {
|
|
172
|
+
constructor(cause) {
|
|
173
|
+
super(`offline: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
174
|
+
this.name = "LtSyncOfflineError";
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
var LtSyncUnauthorizedError = class extends Error {
|
|
178
|
+
constructor() {
|
|
179
|
+
super("unauthorized");
|
|
180
|
+
this.name = "LtSyncUnauthorizedError";
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var LtSyncBackoffError = class extends Error {
|
|
184
|
+
status;
|
|
185
|
+
retryAfterMs;
|
|
186
|
+
constructor(status, retryAfterMs) {
|
|
187
|
+
super(`server answered ${status}`);
|
|
188
|
+
this.status = status;
|
|
189
|
+
this.retryAfterMs = retryAfterMs;
|
|
190
|
+
this.name = "LtSyncBackoffError";
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const hex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
194
|
+
function ltSyncObjectId() {
|
|
195
|
+
const random = /* @__PURE__ */ new Uint8Array(8);
|
|
196
|
+
globalThis.crypto.getRandomValues(random);
|
|
197
|
+
return Math.floor(Date.now() / 1e3).toString(16).padStart(8, "0") + hex(random);
|
|
198
|
+
}
|
|
199
|
+
function ltSyncUuidV7() {
|
|
200
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
201
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
202
|
+
let time = Date.now();
|
|
203
|
+
for (let index = 5; index >= 0; index--) {
|
|
204
|
+
bytes[index] = time % 256;
|
|
205
|
+
time = Math.floor(time / 256);
|
|
206
|
+
}
|
|
207
|
+
bytes[6] = 112 | bytes[6] & 15;
|
|
208
|
+
bytes[8] = 128 | bytes[8] & 63;
|
|
209
|
+
const value = hex(bytes);
|
|
210
|
+
return `${value.slice(0, 8)}-${value.slice(8, 12)}-${value.slice(12, 16)}-${value.slice(16, 20)}-${value.slice(20)}`;
|
|
211
|
+
}
|
|
212
|
+
let environment = {
|
|
213
|
+
basePath: "/sync",
|
|
214
|
+
buildApiUrl: (path) => path,
|
|
215
|
+
fetch: (input, init) => fetch(input, {
|
|
216
|
+
credentials: "include",
|
|
217
|
+
...init
|
|
218
|
+
})
|
|
219
|
+
};
|
|
220
|
+
function configureLtSync(env) {
|
|
221
|
+
environment = {
|
|
222
|
+
...environment,
|
|
223
|
+
...env
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function getLtSyncBasePath() {
|
|
227
|
+
return environment.basePath || "/sync";
|
|
228
|
+
}
|
|
229
|
+
function buildLtSyncUrl(path) {
|
|
230
|
+
return environment.buildApiUrl(`${getLtSyncBasePath()}${path}`);
|
|
231
|
+
}
|
|
232
|
+
function ltSyncFetch(input, init) {
|
|
233
|
+
return environment.fetch(input, init);
|
|
234
|
+
}
|
|
235
|
+
function retryAfterMs(value) {
|
|
236
|
+
if (!value) return void 0;
|
|
237
|
+
const seconds = Number(value);
|
|
238
|
+
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
|
|
239
|
+
const date = Date.parse(value);
|
|
240
|
+
return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
|
|
241
|
+
}
|
|
242
|
+
const engines = /* @__PURE__ */ new Map();
|
|
243
|
+
function ltSyncScope(model, tenantId) {
|
|
244
|
+
return tenantId ? `${model}#${tenantId}` : model;
|
|
245
|
+
}
|
|
246
|
+
function parseLtSyncScope(scope) {
|
|
247
|
+
const at = scope.indexOf("#");
|
|
248
|
+
return at === -1 ? { model: scope } : {
|
|
249
|
+
model: scope.slice(0, at),
|
|
250
|
+
tenantId: scope.slice(at + 1)
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function localOf(item) {
|
|
254
|
+
return {
|
|
255
|
+
data: item.deletedAt ? void 0 : item.data,
|
|
256
|
+
deletedAt: item.deletedAt ?? null,
|
|
257
|
+
id: item.id,
|
|
258
|
+
updatedAt: item.updatedAt ?? null,
|
|
259
|
+
version: item.version
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function lastPending(outbox, id) {
|
|
263
|
+
return outbox.filter((item) => item.id === id).at(-1);
|
|
264
|
+
}
|
|
265
|
+
function nextBase(record, pending) {
|
|
266
|
+
return pending ? (pending.baseVersion ?? 0) + 1 : record.version;
|
|
267
|
+
}
|
|
268
|
+
function pushItem(item) {
|
|
269
|
+
const { baseVersion, data, id, mutationId, op } = item;
|
|
270
|
+
return {
|
|
271
|
+
...baseVersion === void 0 ? {} : { baseVersion },
|
|
272
|
+
...data === void 0 ? {} : { data },
|
|
273
|
+
id,
|
|
274
|
+
mutationId,
|
|
275
|
+
op
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function emptyResult$1() {
|
|
279
|
+
return {
|
|
280
|
+
applied: 0,
|
|
281
|
+
conflicts: [],
|
|
282
|
+
forbidden: false,
|
|
283
|
+
needsLogin: false,
|
|
284
|
+
offline: false,
|
|
285
|
+
rejected: [],
|
|
286
|
+
resynced: false,
|
|
287
|
+
retried: 0
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function createLtSyncEngine(options) {
|
|
291
|
+
const { idFormat = "objectId", model, onConflict, pageLimit = 200, tenantId } = options;
|
|
292
|
+
const fetchImpl = options.fetch ?? ltSyncFetch;
|
|
293
|
+
const scope = ltSyncScope(model, tenantId);
|
|
294
|
+
const cursorKey = `cursor:${scope}`;
|
|
295
|
+
const run = (fn) => runLtSyncTx(scope, fn);
|
|
296
|
+
const runAs = (owner, fn) => runLtSyncTx(scope, fn, owner === void 0 ? {} : { owner });
|
|
297
|
+
const newId = () => idFormat === "uuidv7" ? ltSyncUuidV7() : ltSyncObjectId();
|
|
298
|
+
async function request(method, path, payload) {
|
|
299
|
+
const headers = { accept: "application/json" };
|
|
300
|
+
if (payload !== void 0) headers["content-type"] = "application/json";
|
|
301
|
+
if (tenantId) headers["x-tenant-id"] = tenantId;
|
|
302
|
+
let response;
|
|
303
|
+
try {
|
|
304
|
+
response = await fetchImpl(buildLtSyncUrl(path), {
|
|
305
|
+
body: payload === void 0 ? void 0 : JSON.stringify(payload),
|
|
306
|
+
headers,
|
|
307
|
+
method
|
|
308
|
+
});
|
|
309
|
+
} catch (error) {
|
|
310
|
+
throw new LtSyncOfflineError(error);
|
|
311
|
+
}
|
|
312
|
+
if (response.status === 401) throw new LtSyncUnauthorizedError();
|
|
313
|
+
if (response.status === 429 || response.status === 503) throw new LtSyncBackoffError(response.status, retryAfterMs(response.headers.get("retry-after")));
|
|
314
|
+
const text = await response.text();
|
|
315
|
+
let body;
|
|
316
|
+
try {
|
|
317
|
+
body = text ? JSON.parse(text) : void 0;
|
|
318
|
+
} catch {
|
|
319
|
+
body = void 0;
|
|
320
|
+
}
|
|
321
|
+
if (response.status === 410 || response.status === 403 && method === "GET") return {
|
|
322
|
+
body,
|
|
323
|
+
status: response.status
|
|
324
|
+
};
|
|
325
|
+
if (!response.ok) {
|
|
326
|
+
const error = /* @__PURE__ */ new Error(`${method} ${getLtSyncBasePath()}${path} answered ${response.status}: ${text.slice(0, 300)}`);
|
|
327
|
+
error.status = response.status;
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
body,
|
|
332
|
+
status: response.status
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
async function settle(tx, item, outcome, result, resolution) {
|
|
336
|
+
const status = outcome?.status;
|
|
337
|
+
if (status === "retry" || !status) {
|
|
338
|
+
result.retried++;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
await tx.removeOutbox(item.mutationId);
|
|
342
|
+
const record = await tx.getRecord(item.id);
|
|
343
|
+
if (status === "applied") {
|
|
344
|
+
result.applied++;
|
|
345
|
+
if (item.op === "delete") await tx.deleteRecord(item.id);
|
|
346
|
+
else if (record && (record.version ?? 0) < (outcome.version ?? 0)) await tx.putRecord({
|
|
347
|
+
...record,
|
|
348
|
+
version: outcome.version
|
|
349
|
+
});
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const later = (await tx.listOutbox()).filter((entry) => entry.id === item.id);
|
|
353
|
+
if (status === "merged" && outcome.record) {
|
|
354
|
+
result.applied++;
|
|
355
|
+
const pendingData = later.filter((entry) => entry.op === "upsert").reduce((data, entry) => ({
|
|
356
|
+
...data,
|
|
357
|
+
...entry.data
|
|
358
|
+
}), {});
|
|
359
|
+
const merged = localOf(outcome.record);
|
|
360
|
+
await tx.putRecord(merged.deletedAt ? merged : {
|
|
361
|
+
...merged,
|
|
362
|
+
data: {
|
|
363
|
+
...merged.data,
|
|
364
|
+
...pendingData
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (status === "conflict") {
|
|
370
|
+
result.conflicts.push(outcome);
|
|
371
|
+
for (const entry of later) await tx.removeOutbox(entry.mutationId);
|
|
372
|
+
if (!outcome.record || outcome.record.deletedAt) {
|
|
373
|
+
await tx.deleteRecord(item.id);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const server = localOf(outcome.record);
|
|
377
|
+
if (resolution === "server") {
|
|
378
|
+
await tx.putRecord(server);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
await tx.putRecord({
|
|
382
|
+
...server,
|
|
383
|
+
data: {
|
|
384
|
+
...server.data,
|
|
385
|
+
...resolution.data
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
await tx.addOutbox({
|
|
389
|
+
baseVersion: server.version,
|
|
390
|
+
data: resolution.data,
|
|
391
|
+
id: item.id,
|
|
392
|
+
mutationId: ltSyncUuidV7(),
|
|
393
|
+
op: "upsert"
|
|
394
|
+
});
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
for (const entry of later) await tx.removeOutbox(entry.mutationId);
|
|
398
|
+
if (status === "not_found") {
|
|
399
|
+
await tx.deleteRecord(item.id);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
result.rejected.push(outcome);
|
|
403
|
+
if (record && !record.version) await tx.deleteRecord(item.id);
|
|
404
|
+
else if (record) await tx.deleteMeta(cursorKey);
|
|
405
|
+
}
|
|
406
|
+
async function resolveConflict(item, outcome) {
|
|
407
|
+
if (outcome?.status !== "conflict" || !onConflict || !outcome.record || outcome.record.deletedAt) return "server";
|
|
408
|
+
const local = await run((tx) => tx.getRecord(item.id));
|
|
409
|
+
const answer = await onConflict({
|
|
410
|
+
change: {
|
|
411
|
+
data: item.data,
|
|
412
|
+
op: item.op
|
|
413
|
+
},
|
|
414
|
+
local,
|
|
415
|
+
server: localOf(outcome.record)
|
|
416
|
+
});
|
|
417
|
+
return answer === "server" || !answer?.data ? "server" : answer;
|
|
418
|
+
}
|
|
419
|
+
async function push(result, owner) {
|
|
420
|
+
const carried = /* @__PURE__ */ new Set();
|
|
421
|
+
for (let round = 0; round < MAX_PUSH_ROUNDS; round++) {
|
|
422
|
+
const chunk = await runAs(owner, async (tx) => {
|
|
423
|
+
const records = /* @__PURE__ */ new Set();
|
|
424
|
+
const items = [];
|
|
425
|
+
for (const item of await tx.listOutbox()) {
|
|
426
|
+
if (items.length === PUSH_CHUNK) break;
|
|
427
|
+
if (records.has(item.id)) continue;
|
|
428
|
+
records.add(item.id);
|
|
429
|
+
if (!carried.has(item.mutationId)) items.push(item);
|
|
430
|
+
}
|
|
431
|
+
for (const item of items) await tx.putOutbox({
|
|
432
|
+
...item,
|
|
433
|
+
attempted: true
|
|
434
|
+
});
|
|
435
|
+
return items;
|
|
436
|
+
});
|
|
437
|
+
if (!chunk.length) return;
|
|
438
|
+
for (const item of chunk) carried.add(item.mutationId);
|
|
439
|
+
const results = (await request("POST", `/${encodeURIComponent(model)}`, { items: chunk.map(pushItem) })).body?.results ?? [];
|
|
440
|
+
for (let index = 0; index < chunk.length; index++) {
|
|
441
|
+
const item = chunk[index];
|
|
442
|
+
const resolution = await resolveConflict(item, results[index]);
|
|
443
|
+
await runAs(owner, (tx) => settle(tx, item, results[index], result, resolution));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async function pull(result, owner) {
|
|
448
|
+
let cursor = await run((tx) => tx.getMeta(cursorKey));
|
|
449
|
+
for (let page = 0; page < 1e4; page++) {
|
|
450
|
+
const query = new URLSearchParams({ limit: String(pageLimit) });
|
|
451
|
+
if (cursor) query.set("cursor", cursor);
|
|
452
|
+
const response = await request("GET", `/${encodeURIComponent(model)}?${query}`);
|
|
453
|
+
if (response.status === 403) {
|
|
454
|
+
result.forbidden = true;
|
|
455
|
+
await runAs(owner, async (tx) => {
|
|
456
|
+
await tx.clearRecords(new Set((await tx.listOutbox()).map((item) => item.id)));
|
|
457
|
+
await tx.deleteMeta(cursorKey);
|
|
458
|
+
});
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
if (response.status === 410) {
|
|
462
|
+
if (result.resynced) throw new Error(`resync loop: ${JSON.stringify(response.body)}`);
|
|
463
|
+
result.resynced = true;
|
|
464
|
+
await runAs(owner, async (tx) => {
|
|
465
|
+
await tx.clearRecords(new Set((await tx.listOutbox()).map((item) => item.id)));
|
|
466
|
+
await tx.deleteMeta(cursorKey);
|
|
467
|
+
});
|
|
468
|
+
cursor = void 0;
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const body = response.body;
|
|
472
|
+
await runAs(owner, async (tx) => {
|
|
473
|
+
const pending = new Set((await tx.listOutbox()).map((item) => item.id));
|
|
474
|
+
for (const item of body.items ?? []) {
|
|
475
|
+
if (pending.has(item.id)) continue;
|
|
476
|
+
const local = await tx.getRecord(item.id);
|
|
477
|
+
if (local && local.version > item.version) continue;
|
|
478
|
+
if (item.deletedAt) await tx.deleteRecord(item.id);
|
|
479
|
+
else await tx.putRecord(localOf(item));
|
|
480
|
+
}
|
|
481
|
+
await tx.setMeta(cursorKey, body.cursor);
|
|
482
|
+
});
|
|
483
|
+
cursor = body.cursor;
|
|
484
|
+
if (!body.hasMore) return result.resynced;
|
|
485
|
+
}
|
|
486
|
+
throw new Error("pull did not end");
|
|
487
|
+
}
|
|
488
|
+
async function syncOnce() {
|
|
489
|
+
const result = emptyResult$1();
|
|
490
|
+
try {
|
|
491
|
+
const owner = await getLtSyncOwner();
|
|
492
|
+
await push(result, owner);
|
|
493
|
+
if (await pull(result, owner)) await push(result, owner);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
if (error instanceof LtSyncOwnerChangedError) return result;
|
|
496
|
+
if (error instanceof LtSyncUnauthorizedError) result.needsLogin = true;
|
|
497
|
+
else if (error instanceof LtSyncOfflineError) result.offline = true;
|
|
498
|
+
else if (error instanceof LtSyncBackoffError) result.retryAfterMs = error.retryAfterMs ?? 0;
|
|
499
|
+
else throw error;
|
|
500
|
+
}
|
|
501
|
+
return result;
|
|
502
|
+
}
|
|
503
|
+
async function syncLocked() {
|
|
504
|
+
const locks = typeof navigator === "undefined" ? void 0 : navigator.locks;
|
|
505
|
+
if (!locks) return syncOnce();
|
|
506
|
+
return await locks.request(`lt-offline-sync:${scope}`, syncOnce);
|
|
507
|
+
}
|
|
508
|
+
let current = null;
|
|
509
|
+
let next = null;
|
|
510
|
+
function sync() {
|
|
511
|
+
if (!current) {
|
|
512
|
+
current = syncLocked().finally(() => {
|
|
513
|
+
current = null;
|
|
514
|
+
});
|
|
515
|
+
return current;
|
|
516
|
+
}
|
|
517
|
+
next ??= current.catch(() => void 0).then(() => {
|
|
518
|
+
next = null;
|
|
519
|
+
return sync();
|
|
520
|
+
});
|
|
521
|
+
return next;
|
|
522
|
+
}
|
|
523
|
+
const engine = {
|
|
524
|
+
async create(data) {
|
|
525
|
+
const record = {
|
|
526
|
+
data,
|
|
527
|
+
deletedAt: null,
|
|
528
|
+
id: newId(),
|
|
529
|
+
updatedAt: null,
|
|
530
|
+
version: 0
|
|
531
|
+
};
|
|
532
|
+
await run(async (tx) => {
|
|
533
|
+
await tx.putRecord(record);
|
|
534
|
+
await tx.addOutbox({
|
|
535
|
+
data,
|
|
536
|
+
id: record.id,
|
|
537
|
+
mutationId: ltSyncUuidV7(),
|
|
538
|
+
op: "upsert"
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
return record;
|
|
542
|
+
},
|
|
543
|
+
get: (id) => run((tx) => tx.getRecord(id)),
|
|
544
|
+
async list() {
|
|
545
|
+
return (await run((tx) => tx.listRecords())).filter((record) => !record.deletedAt).sort((a, b) => a.id < b.id ? -1 : 1);
|
|
546
|
+
},
|
|
547
|
+
model,
|
|
548
|
+
pendingCount: () => run(async (tx) => (await tx.listOutbox()).length),
|
|
549
|
+
async pushPending() {
|
|
550
|
+
try {
|
|
551
|
+
await push(emptyResult$1(), await getLtSyncOwner());
|
|
552
|
+
} catch {}
|
|
553
|
+
},
|
|
554
|
+
scope,
|
|
555
|
+
async remove(id) {
|
|
556
|
+
await run(async (tx) => {
|
|
557
|
+
const record = await tx.getRecord(id);
|
|
558
|
+
if (!record || record.deletedAt) return;
|
|
559
|
+
const pending = lastPending(await tx.listOutbox(), id);
|
|
560
|
+
if (pending && !pending.attempted) {
|
|
561
|
+
await tx.removeOutbox(pending.mutationId);
|
|
562
|
+
if (pending.baseVersion === void 0) {
|
|
563
|
+
await tx.deleteRecord(id);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const baseVersion = pending && !pending.attempted ? pending.baseVersion : nextBase(record, pending);
|
|
568
|
+
await tx.addOutbox({
|
|
569
|
+
baseVersion,
|
|
570
|
+
id,
|
|
571
|
+
mutationId: ltSyncUuidV7(),
|
|
572
|
+
op: "delete"
|
|
573
|
+
});
|
|
574
|
+
await tx.putRecord({
|
|
575
|
+
...record,
|
|
576
|
+
data: void 0,
|
|
577
|
+
deletedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
},
|
|
581
|
+
sync,
|
|
582
|
+
async update(id, data) {
|
|
583
|
+
return run(async (tx) => {
|
|
584
|
+
const record = await tx.getRecord(id);
|
|
585
|
+
if (!record || record.deletedAt) throw new Error(`no live local record ${id}`);
|
|
586
|
+
const pending = lastPending(await tx.listOutbox(), id);
|
|
587
|
+
if (pending && pending.op === "upsert" && !pending.attempted) await tx.putOutbox({
|
|
588
|
+
...pending,
|
|
589
|
+
data: {
|
|
590
|
+
...pending.data,
|
|
591
|
+
...data
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
else await tx.addOutbox({
|
|
595
|
+
baseVersion: nextBase(record, pending),
|
|
596
|
+
data,
|
|
597
|
+
id,
|
|
598
|
+
mutationId: ltSyncUuidV7(),
|
|
599
|
+
op: "upsert"
|
|
600
|
+
});
|
|
601
|
+
const changed = {
|
|
602
|
+
...record,
|
|
603
|
+
data: {
|
|
604
|
+
...record.data,
|
|
605
|
+
...data
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
await tx.putRecord(changed);
|
|
609
|
+
return changed;
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
engines.set(scope, engine);
|
|
614
|
+
return engine;
|
|
615
|
+
}
|
|
616
|
+
async function flushLtSyncBeforeSignOut(userId, timeoutMs = 5e3) {
|
|
617
|
+
if (typeof indexedDB === "undefined" || !userId) return;
|
|
618
|
+
let timer;
|
|
619
|
+
const timeout = new Promise((resolve) => {
|
|
620
|
+
timer = setTimeout(resolve, timeoutMs);
|
|
621
|
+
});
|
|
622
|
+
const flush = (async () => {
|
|
623
|
+
if (await getLtSyncOwner().catch(() => void 0) !== userId) return;
|
|
624
|
+
const scopes = await listLtSyncPendingModels().catch(() => []);
|
|
625
|
+
await Promise.all(scopes.map((scope) => (engines.get(scope) ?? createLtSyncEngine(parseLtSyncScope(scope))).pushPending()));
|
|
626
|
+
})();
|
|
627
|
+
try {
|
|
628
|
+
await Promise.race([flush, timeout]);
|
|
629
|
+
} finally {
|
|
630
|
+
clearTimeout(timer);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function resetLtSyncEngines() {
|
|
634
|
+
engines.clear();
|
|
635
|
+
}
|
|
636
|
+
const LINE_LIMIT = 1048576;
|
|
637
|
+
const DEFAULT_RETRY_MS = 3e3;
|
|
638
|
+
const MAX_BACKOFF_MS = 6e4;
|
|
639
|
+
async function readEvents(body, onEvent, signal) {
|
|
640
|
+
const reader = body.getReader();
|
|
641
|
+
const decoder = new TextDecoder();
|
|
642
|
+
let buffer = "";
|
|
643
|
+
let event = {
|
|
644
|
+
data: "",
|
|
645
|
+
event: "message"
|
|
646
|
+
};
|
|
647
|
+
const dispatchLine = (line) => {
|
|
648
|
+
if (line === "") {
|
|
649
|
+
if (event.data || event.retry !== void 0) onEvent(event);
|
|
650
|
+
event = {
|
|
651
|
+
data: "",
|
|
652
|
+
event: "message"
|
|
653
|
+
};
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
if (line.startsWith(":")) return;
|
|
657
|
+
const colon = line.indexOf(":");
|
|
658
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
659
|
+
const value = colon === -1 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
660
|
+
if (field === "event") event.event = value;
|
|
661
|
+
else if (field === "data") event.data = event.data ? `${event.data}\n${value}` : value;
|
|
662
|
+
else if (field === "retry" && /^\d+$/.test(value)) event.retry = Number(value);
|
|
663
|
+
};
|
|
664
|
+
const onAbort = () => void reader.cancel().catch(() => void 0);
|
|
665
|
+
signal.addEventListener("abort", onAbort);
|
|
666
|
+
try {
|
|
667
|
+
for (;;) {
|
|
668
|
+
const { done, value } = await reader.read();
|
|
669
|
+
if (done) return;
|
|
670
|
+
buffer += decoder.decode(value, { stream: true });
|
|
671
|
+
let newline = buffer.search(/\r?\n|\r/);
|
|
672
|
+
while (newline !== -1) {
|
|
673
|
+
const line = buffer.slice(0, newline);
|
|
674
|
+
const skip = buffer[newline] === "\r" && buffer[newline + 1] === "\n" ? 2 : 1;
|
|
675
|
+
buffer = buffer.slice(newline + skip);
|
|
676
|
+
dispatchLine(line);
|
|
677
|
+
newline = buffer.search(/\r?\n|\r/);
|
|
678
|
+
}
|
|
679
|
+
if (buffer.length > LINE_LIMIT) throw new Error("sync hint stream: line exceeds 1 MiB");
|
|
680
|
+
}
|
|
681
|
+
} finally {
|
|
682
|
+
signal.removeEventListener("abort", onAbort);
|
|
683
|
+
reader.releaseLock();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
const hubs = /* @__PURE__ */ new Map();
|
|
687
|
+
function subscribeLtSyncHints(subscriber) {
|
|
688
|
+
const key = subscriber.tenantId ?? "";
|
|
689
|
+
let hub = hubs.get(key);
|
|
690
|
+
if (!hub) {
|
|
691
|
+
hub = {
|
|
692
|
+
models: "",
|
|
693
|
+
subscribers: /* @__PURE__ */ new Set()
|
|
694
|
+
};
|
|
695
|
+
hubs.set(key, hub);
|
|
696
|
+
}
|
|
697
|
+
const joined = hub;
|
|
698
|
+
joined.subscribers.add(subscriber);
|
|
699
|
+
connectHub(key, joined);
|
|
700
|
+
return () => {
|
|
701
|
+
if (!joined.subscribers.delete(subscriber)) return;
|
|
702
|
+
if (joined.subscribers.size === 0) {
|
|
703
|
+
joined.close?.();
|
|
704
|
+
if (hubs.get(key) === joined) hubs.delete(key);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
connectHub(key, joined);
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
function connectHub(key, hub) {
|
|
711
|
+
const models = [...new Set([...hub.subscribers].map((subscriber) => subscriber.model))].sort().join(",");
|
|
712
|
+
if (hub.close && hub.models === models) return;
|
|
713
|
+
hub.close?.();
|
|
714
|
+
hub.models = models;
|
|
715
|
+
hub.close = openLtSyncHints({
|
|
716
|
+
models: models.split(","),
|
|
717
|
+
onConnect: () => {
|
|
718
|
+
for (const subscriber of Array.from(hub.subscribers)) subscriber.onConnect();
|
|
719
|
+
},
|
|
720
|
+
onHint: (model) => {
|
|
721
|
+
for (const subscriber of Array.from(hub.subscribers)) if (subscriber.model === model) subscriber.onHint();
|
|
722
|
+
},
|
|
723
|
+
onStop: (reason) => {
|
|
724
|
+
hub.close = void 0;
|
|
725
|
+
hub.models = "";
|
|
726
|
+
for (const subscriber of Array.from(hub.subscribers)) subscriber.onStop?.(reason);
|
|
727
|
+
},
|
|
728
|
+
tenantId: key || void 0
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
function openLtSyncHints(options) {
|
|
732
|
+
const { models, onConnect, onHint, onStop, tenantId } = options;
|
|
733
|
+
const fetchImpl = options.fetch ?? ltSyncFetch;
|
|
734
|
+
let closed = false;
|
|
735
|
+
let controller;
|
|
736
|
+
let timer;
|
|
737
|
+
let wake;
|
|
738
|
+
let retryMs = DEFAULT_RETRY_MS;
|
|
739
|
+
let failures = 0;
|
|
740
|
+
const wait = (ms) => new Promise((resolve) => {
|
|
741
|
+
wake = resolve;
|
|
742
|
+
timer = setTimeout(resolve, ms);
|
|
743
|
+
});
|
|
744
|
+
const loop = async () => {
|
|
745
|
+
while (!closed) {
|
|
746
|
+
controller = new AbortController();
|
|
747
|
+
try {
|
|
748
|
+
const query = new URLSearchParams({ models: models.join(",") });
|
|
749
|
+
const headers = { accept: "text/event-stream" };
|
|
750
|
+
if (tenantId) headers["x-tenant-id"] = tenantId;
|
|
751
|
+
const response = await fetchImpl(buildLtSyncUrl(`/hints?${query}`), {
|
|
752
|
+
headers,
|
|
753
|
+
method: "GET",
|
|
754
|
+
signal: controller.signal
|
|
755
|
+
});
|
|
756
|
+
if (response.status === 404 || response.status === 401) {
|
|
757
|
+
await response.body?.cancel().catch(() => void 0);
|
|
758
|
+
if (!closed) onStop?.(response.status === 404 ? "disabled" : "unauthorized");
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (!response.ok || !response.body) throw new Error(`sync hint stream answered ${response.status}`);
|
|
762
|
+
failures = 0;
|
|
763
|
+
onConnect();
|
|
764
|
+
await readEvents(response.body, (event) => {
|
|
765
|
+
if (event.retry !== void 0) retryMs = event.retry;
|
|
766
|
+
if (event.event !== "change") return;
|
|
767
|
+
try {
|
|
768
|
+
const model = JSON.parse(event.data).model;
|
|
769
|
+
if (typeof model === "string") onHint(model);
|
|
770
|
+
} catch {}
|
|
771
|
+
}, controller.signal);
|
|
772
|
+
if (!closed) await wait(retryMs);
|
|
773
|
+
} catch {
|
|
774
|
+
if (closed) return;
|
|
775
|
+
failures++;
|
|
776
|
+
await wait(Math.min(retryMs * 2 ** Math.min(failures - 1, 5), MAX_BACKOFF_MS));
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
loop();
|
|
781
|
+
return () => {
|
|
782
|
+
closed = true;
|
|
783
|
+
controller?.abort();
|
|
784
|
+
clearTimeout(timer);
|
|
785
|
+
wake?.();
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
const BACKOFF_START_MS = 1e3;
|
|
789
|
+
const BACKOFF_MAX_MS = 6e4;
|
|
790
|
+
const LOCAL_CHANGE_DELAY_MS = 300;
|
|
791
|
+
const emptyResult = (overrides = {}) => ({
|
|
792
|
+
applied: 0,
|
|
793
|
+
conflicts: [],
|
|
794
|
+
forbidden: false,
|
|
795
|
+
needsLogin: false,
|
|
796
|
+
offline: false,
|
|
797
|
+
rejected: [],
|
|
798
|
+
resynced: false,
|
|
799
|
+
retried: 0,
|
|
800
|
+
...overrides
|
|
801
|
+
});
|
|
802
|
+
function injectLtOfflineSync(model, options = {}) {
|
|
803
|
+
assertInInjectionContext(injectLtOfflineSync);
|
|
804
|
+
const records = signal([], ...ngDevMode ? [{ debugName: "records" }] : /* istanbul ignore next */ []);
|
|
805
|
+
const pendingCount = signal(0, ...ngDevMode ? [{ debugName: "pendingCount" }] : /* istanbul ignore next */ []);
|
|
806
|
+
const offline = signal(false, ...ngDevMode ? [{ debugName: "offline" }] : /* istanbul ignore next */ []);
|
|
807
|
+
const syncing = signal(false, ...ngDevMode ? [{ debugName: "syncing" }] : /* istanbul ignore next */ []);
|
|
808
|
+
const needsLogin = signal(false, ...ngDevMode ? [{ debugName: "needsLogin" }] : /* istanbul ignore next */ []);
|
|
809
|
+
const forbidden = signal(false, ...ngDevMode ? [{ debugName: "forbidden" }] : /* istanbul ignore next */ []);
|
|
810
|
+
const conflicts = signal([], ...ngDevMode ? [{ debugName: "conflicts" }] : /* istanbul ignore next */ []);
|
|
811
|
+
const rejected = signal([], ...ngDevMode ? [{ debugName: "rejected" }] : /* istanbul ignore next */ []);
|
|
812
|
+
const error = signal(null, ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
|
|
813
|
+
const lastSyncedAt = signal(null, ...ngDevMode ? [{ debugName: "lastSyncedAt" }] : /* istanbul ignore next */ []);
|
|
814
|
+
const state = {
|
|
815
|
+
conflicts: conflicts.asReadonly(),
|
|
816
|
+
error: error.asReadonly(),
|
|
817
|
+
forbidden: forbidden.asReadonly(),
|
|
818
|
+
lastSyncedAt: lastSyncedAt.asReadonly(),
|
|
819
|
+
needsLogin: needsLogin.asReadonly(),
|
|
820
|
+
offline: offline.asReadonly(),
|
|
821
|
+
pendingCount: pendingCount.asReadonly(),
|
|
822
|
+
records: records.asReadonly(),
|
|
823
|
+
rejected: rejected.asReadonly(),
|
|
824
|
+
syncing: syncing.asReadonly()
|
|
825
|
+
};
|
|
826
|
+
if (!isPlatformBrowser(inject(PLATFORM_ID)) || typeof indexedDB === "undefined") {
|
|
827
|
+
const browserOnly = () => Promise.reject(/* @__PURE__ */ new Error("injectLtOfflineSync: offline sync runs in the browser only"));
|
|
828
|
+
return {
|
|
829
|
+
...state,
|
|
830
|
+
create: browserOnly,
|
|
831
|
+
get: async () => void 0,
|
|
832
|
+
remove: browserOnly,
|
|
833
|
+
sync: async () => emptyResult(),
|
|
834
|
+
update: browserOnly
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
const auth = inject(LtAuthStore);
|
|
838
|
+
const destroyRef = inject(DestroyRef);
|
|
839
|
+
const doc = inject(DOCUMENT);
|
|
840
|
+
const win = doc.defaultView;
|
|
841
|
+
const auto = options.auto !== false;
|
|
842
|
+
const engine = createLtSyncEngine({
|
|
843
|
+
idFormat: options.idFormat,
|
|
844
|
+
model,
|
|
845
|
+
onConflict: options.onConflict,
|
|
846
|
+
pageLimit: options.pageLimit,
|
|
847
|
+
tenantId: options.tenantId
|
|
848
|
+
});
|
|
849
|
+
const channel = typeof BroadcastChannel === "undefined" ? void 0 : new BroadcastChannel(LT_SYNC_CHANNEL_NAME);
|
|
850
|
+
let ownerId = null;
|
|
851
|
+
let claiming = null;
|
|
852
|
+
const currentUserId = () => {
|
|
853
|
+
const id = auth.user()?.id;
|
|
854
|
+
return id ? String(id) : null;
|
|
855
|
+
};
|
|
856
|
+
async function ensureOwner() {
|
|
857
|
+
const id = currentUserId();
|
|
858
|
+
if (!id) {
|
|
859
|
+
ownerId = null;
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
if (id === ownerId) return true;
|
|
863
|
+
if (claiming?.id !== id) {
|
|
864
|
+
const pending = {
|
|
865
|
+
done: claimLtSyncOwner(id),
|
|
866
|
+
id
|
|
867
|
+
};
|
|
868
|
+
claiming = pending;
|
|
869
|
+
pending.done.finally(() => {
|
|
870
|
+
if (claiming === pending) claiming = null;
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
await claiming.done;
|
|
874
|
+
if (currentUserId() !== id) return ensureOwner();
|
|
875
|
+
ownerId = id;
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
async function refresh() {
|
|
879
|
+
if (!await ensureOwner()) {
|
|
880
|
+
records.set([]);
|
|
881
|
+
pendingCount.set(0);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const [list, pending] = await Promise.all([engine.list(), engine.pendingCount()]);
|
|
885
|
+
records.set(list);
|
|
886
|
+
pendingCount.set(pending);
|
|
887
|
+
}
|
|
888
|
+
async function owned(fn) {
|
|
889
|
+
if (!await ensureOwner()) throw new Error("injectLtOfflineSync: no signed-in user owns the local data");
|
|
890
|
+
return fn();
|
|
891
|
+
}
|
|
892
|
+
let timer;
|
|
893
|
+
let backoffMs = 0;
|
|
894
|
+
let disposed = false;
|
|
895
|
+
let closeHints;
|
|
896
|
+
let hintsStopped = null;
|
|
897
|
+
function announce() {
|
|
898
|
+
const message = {
|
|
899
|
+
scope: engine.scope,
|
|
900
|
+
type: "changed"
|
|
901
|
+
};
|
|
902
|
+
if (!disposed) {
|
|
903
|
+
channel?.postMessage(message);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
if (typeof BroadcastChannel === "undefined") return;
|
|
907
|
+
const once = new BroadcastChannel(LT_SYNC_CHANNEL_NAME);
|
|
908
|
+
try {
|
|
909
|
+
once.postMessage(message);
|
|
910
|
+
} finally {
|
|
911
|
+
once.close();
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
function schedule(ms) {
|
|
915
|
+
if (disposed) return;
|
|
916
|
+
clearTimeout(timer);
|
|
917
|
+
timer = setTimeout(() => void sync().catch(() => void 0), ms);
|
|
918
|
+
}
|
|
919
|
+
function backOff(retryAfterMs = 0) {
|
|
920
|
+
backoffMs = Math.min(backoffMs ? backoffMs * 2 : BACKOFF_START_MS, BACKOFF_MAX_MS);
|
|
921
|
+
if (auto && win?.navigator.onLine !== false) schedule(Math.max(retryAfterMs, backoffMs));
|
|
922
|
+
}
|
|
923
|
+
async function sync() {
|
|
924
|
+
if (!await ensureOwner()) {
|
|
925
|
+
needsLogin.set(true);
|
|
926
|
+
return emptyResult({ needsLogin: true });
|
|
927
|
+
}
|
|
928
|
+
syncing.set(true);
|
|
929
|
+
try {
|
|
930
|
+
const result = await engine.sync();
|
|
931
|
+
error.set(null);
|
|
932
|
+
offline.set(result.offline);
|
|
933
|
+
needsLogin.set(result.needsLogin);
|
|
934
|
+
forbidden.set(result.forbidden);
|
|
935
|
+
conflicts.set(result.conflicts);
|
|
936
|
+
rejected.set(result.rejected);
|
|
937
|
+
if (result.offline || result.retryAfterMs !== void 0 || result.retried) backOff(result.retryAfterMs);
|
|
938
|
+
else if (!result.needsLogin) {
|
|
939
|
+
backoffMs = 0;
|
|
940
|
+
lastSyncedAt.set(/* @__PURE__ */ new Date());
|
|
941
|
+
if (hintsStopped === "unauthorized") {
|
|
942
|
+
hintsStopped = null;
|
|
943
|
+
startHints();
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
return result;
|
|
947
|
+
} catch (caught) {
|
|
948
|
+
error.set(caught instanceof Error ? caught : new Error(String(caught)));
|
|
949
|
+
throw caught;
|
|
950
|
+
} finally {
|
|
951
|
+
syncing.set(false);
|
|
952
|
+
await refresh();
|
|
953
|
+
announce();
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
const autoSync = () => {
|
|
957
|
+
if (auto) sync().catch(() => void 0);
|
|
958
|
+
};
|
|
959
|
+
async function afterLocalChange() {
|
|
960
|
+
announce();
|
|
961
|
+
await refresh();
|
|
962
|
+
if (auto) schedule(LOCAL_CHANGE_DELAY_MS);
|
|
963
|
+
}
|
|
964
|
+
function startHints() {
|
|
965
|
+
if (!auto || options.hints === false || closeHints || hintsStopped || disposed) return;
|
|
966
|
+
if (doc.visibilityState === "hidden" || !auth.user()?.id) return;
|
|
967
|
+
closeHints = subscribeLtSyncHints({
|
|
968
|
+
model,
|
|
969
|
+
onConnect: autoSync,
|
|
970
|
+
onHint: autoSync,
|
|
971
|
+
onStop: (reason) => {
|
|
972
|
+
stopHints();
|
|
973
|
+
hintsStopped = reason;
|
|
974
|
+
},
|
|
975
|
+
tenantId: options.tenantId
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
function stopHints() {
|
|
979
|
+
closeHints?.();
|
|
980
|
+
closeHints = void 0;
|
|
981
|
+
}
|
|
982
|
+
const onOnline = () => {
|
|
983
|
+
offline.set(false);
|
|
984
|
+
backoffMs = 0;
|
|
985
|
+
autoSync();
|
|
986
|
+
};
|
|
987
|
+
const onOffline = () => {
|
|
988
|
+
offline.set(true);
|
|
989
|
+
};
|
|
990
|
+
const onVisibility = () => {
|
|
991
|
+
if (doc.visibilityState === "hidden") {
|
|
992
|
+
stopHints();
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
startHints();
|
|
996
|
+
autoSync();
|
|
997
|
+
};
|
|
998
|
+
const onMessage = (event) => {
|
|
999
|
+
if (event.data?.type === "cleared") {
|
|
1000
|
+
ownerId = null;
|
|
1001
|
+
refresh();
|
|
1002
|
+
} else if (event.data?.type === "changed" && event.data.scope === engine.scope) refresh();
|
|
1003
|
+
};
|
|
1004
|
+
win?.addEventListener("online", onOnline);
|
|
1005
|
+
win?.addEventListener("offline", onOffline);
|
|
1006
|
+
win?.addEventListener("focus", autoSync);
|
|
1007
|
+
doc.addEventListener("visibilitychange", onVisibility);
|
|
1008
|
+
if (win?.navigator.onLine === false) offline.set(true);
|
|
1009
|
+
channel?.addEventListener("message", onMessage);
|
|
1010
|
+
let watchedUserId = auth.user()?.id ?? null;
|
|
1011
|
+
const userWatch = effect(() => {
|
|
1012
|
+
const id = auth.user()?.id ?? null;
|
|
1013
|
+
untracked(() => {
|
|
1014
|
+
if (id === watchedUserId) return;
|
|
1015
|
+
watchedUserId = id;
|
|
1016
|
+
stopHints();
|
|
1017
|
+
hintsStopped = hintsStopped === "disabled" ? "disabled" : null;
|
|
1018
|
+
refresh().then(() => {
|
|
1019
|
+
if (!id) return;
|
|
1020
|
+
startHints();
|
|
1021
|
+
autoSync();
|
|
1022
|
+
});
|
|
1023
|
+
});
|
|
1024
|
+
}, ...ngDevMode ? [{ debugName: "userWatch" }] : /* istanbul ignore next */ []);
|
|
1025
|
+
destroyRef.onDestroy(() => {
|
|
1026
|
+
disposed = true;
|
|
1027
|
+
clearTimeout(timer);
|
|
1028
|
+
stopHints();
|
|
1029
|
+
userWatch.destroy();
|
|
1030
|
+
channel?.removeEventListener("message", onMessage);
|
|
1031
|
+
channel?.close();
|
|
1032
|
+
win?.removeEventListener("online", onOnline);
|
|
1033
|
+
win?.removeEventListener("offline", onOffline);
|
|
1034
|
+
win?.removeEventListener("focus", autoSync);
|
|
1035
|
+
doc.removeEventListener("visibilitychange", onVisibility);
|
|
1036
|
+
});
|
|
1037
|
+
refresh().then(() => {
|
|
1038
|
+
startHints();
|
|
1039
|
+
autoSync();
|
|
1040
|
+
});
|
|
1041
|
+
return {
|
|
1042
|
+
...state,
|
|
1043
|
+
async create(data) {
|
|
1044
|
+
const record = await owned(() => engine.create(data));
|
|
1045
|
+
await afterLocalChange();
|
|
1046
|
+
return record;
|
|
1047
|
+
},
|
|
1048
|
+
async get(id) {
|
|
1049
|
+
return await ensureOwner() ? engine.get(id) : void 0;
|
|
1050
|
+
},
|
|
1051
|
+
async remove(id) {
|
|
1052
|
+
await owned(() => engine.remove(id));
|
|
1053
|
+
await afterLocalChange();
|
|
1054
|
+
},
|
|
1055
|
+
sync,
|
|
1056
|
+
async update(id, data) {
|
|
1057
|
+
const record = await owned(() => engine.update(id, data));
|
|
1058
|
+
await afterLocalChange();
|
|
1059
|
+
return record;
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
const LT_SYNC_INPUT_OPTIONS = new InjectionToken("LT_SYNC_INPUT_OPTIONS", { factory: () => ({}) });
|
|
1064
|
+
const LT_SYNC_OPTIONS = new InjectionToken("LT_SYNC_OPTIONS", { factory: () => mergeLtFeatureOptions({ basePath: "/sync" }, inject(LT_SYNC_INPUT_OPTIONS), inject(LT_RUNTIME_CONFIG, { optional: true })?.lt.sync) });
|
|
1065
|
+
function provideLtSync(options = {}) {
|
|
1066
|
+
return makeEnvironmentProviders([
|
|
1067
|
+
{
|
|
1068
|
+
provide: LT_SYNC_INPUT_OPTIONS,
|
|
1069
|
+
useValue: options
|
|
1070
|
+
},
|
|
1071
|
+
provideAppInitializer(() => {
|
|
1072
|
+
if (!isPlatformBrowser(inject(PLATFORM_ID))) return;
|
|
1073
|
+
const api = inject(LtApiUrlService);
|
|
1074
|
+
const fetch = inject(LT_FETCH);
|
|
1075
|
+
configureLtSync({
|
|
1076
|
+
basePath: inject(LT_SYNC_OPTIONS).basePath,
|
|
1077
|
+
buildApiUrl: (path) => api.buildUrl(path),
|
|
1078
|
+
fetch: (input, init) => fetch(input, init)
|
|
1079
|
+
});
|
|
1080
|
+
}),
|
|
1081
|
+
{
|
|
1082
|
+
multi: true,
|
|
1083
|
+
provide: LT_AUTH_SIGN_OUT_HOOKS,
|
|
1084
|
+
useFactory: () => {
|
|
1085
|
+
const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
1086
|
+
return {
|
|
1087
|
+
afterSignOut: () => isBrowser ? deleteLtSyncDatabase() : void 0,
|
|
1088
|
+
beforeSignOut: (userId) => isBrowser ? flushLtSyncBeforeSignOut(userId ?? void 0, 5e3) : void 0
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
]);
|
|
1093
|
+
}
|
|
1094
|
+
export { LT_SYNC_CHANNEL_NAME, LT_SYNC_DB_NAME, LT_SYNC_OPTIONS, LtSyncBackoffError, LtSyncOfflineError, LtSyncOwnerChangedError, LtSyncUnauthorizedError, buildLtSyncUrl, claimLtSyncOwner, configureLtSync, createLtSyncEngine, deleteLtSyncDatabase, flushLtSyncBeforeSignOut, getLtSyncBasePath, getLtSyncOwner, injectLtOfflineSync, listLtSyncPendingModels, ltSyncFetch, ltSyncObjectId, ltSyncScope, ltSyncUuidV7, openLtSyncHints, provideLtSync, resetLtSyncEngines, runLtSyncTx, subscribeLtSyncHints };
|
|
1095
|
+
|
|
1096
|
+
//# sourceMappingURL=lenne.tech-ng-base-sync.mjs.map
|