@eifi1/ui-kit 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -12
- package/dist/admin/admin-area-labels.d.ts +9 -0
- package/dist/admin/admin-area-labels.js +5 -1
- package/dist/admin/admin-area-labels.js.map +1 -1
- package/dist/admin/admin-overview.d.ts +16 -1
- package/dist/admin/admin-overview.js +12 -1
- package/dist/admin/admin-overview.js.map +1 -1
- package/dist/admin/mail-test-card.d.ts +3 -2
- package/dist/admin/mail-test-card.js +3 -1
- package/dist/admin/mail-test-card.js.map +1 -1
- package/dist/components/appearance-labels.d.ts +10 -0
- package/dist/components/appearance-labels.js +6 -1
- package/dist/components/appearance-labels.js.map +1 -1
- package/dist/components/data-table-column-settings.js +2 -2
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-head.js +2 -2
- package/dist/components/data-table-head.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +18 -2
- package/dist/components/data-table-row-actions.js +86 -2
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-shared.js +9 -2
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table.d.ts +43 -2
- package/dist/components/data-table.js +9 -4
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-format-setting.d.ts +29 -0
- package/dist/components/date-format-setting.js +36 -0
- package/dist/components/date-format-setting.js.map +1 -0
- package/dist/components/swipeable-row.d.ts +8 -0
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/tabs.d.ts +3 -2
- package/dist/components/tabs.js +1 -1
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/translation-review-editor.js +1 -0
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review-labels.d.ts +5 -0
- package/dist/components/translation-review-labels.js +2 -0
- package/dist/components/translation-review-labels.js.map +1 -1
- package/dist/components/translation-review.d.ts +18 -8
- package/dist/components/translation-review.js +64 -7
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/write-lock-labels.js +2 -1
- package/dist/components/write-lock-labels.js.map +1 -1
- package/dist/components/write-lock.d.ts +33 -3
- package/dist/components/write-lock.js +9 -0
- package/dist/components/write-lock.js.map +1 -1
- package/dist/data-table.d.ts +1 -1
- package/dist/data-table.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +4 -2
- package/dist/feedback/feedback-row-detail.js +17 -26
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-swipe.d.ts +18 -0
- package/dist/feedback/feedback-swipe.js +10 -1
- package/dist/feedback/feedback-swipe.js.map +1 -1
- package/dist/i18n/german.js +41 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +12 -1
- package/dist/i18n/kit-labels.js +8 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +40 -6
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/es.js +41 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +41 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +41 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +41 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +41 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +3 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/date-format.d.ts +81 -0
- package/dist/lib/date-format.js +56 -0
- package/dist/lib/date-format.js.map +1 -0
- package/dist/lib/format.d.ts +3 -0
- package/dist/lib/format.js +5 -4
- package/dist/lib/format.js.map +1 -1
- package/dist/notifications/device-zone-report.d.ts +27 -0
- package/dist/notifications/device-zone-report.js +24 -0
- package/dist/notifications/device-zone-report.js.map +1 -0
- package/dist/notifications/feedback-notification-types-labels.d.ts +7 -1
- package/dist/notifications/feedback-notification-types-labels.js +4 -1
- package/dist/notifications/feedback-notification-types-labels.js.map +1 -1
- package/dist/notifications/feedback-notification-types.d.ts +11 -5
- package/dist/notifications/feedback-notification-types.js +2 -1
- package/dist/notifications/feedback-notification-types.js.map +1 -1
- package/dist/notifications/notification-bell.d.ts +33 -0
- package/dist/notifications/notification-bell.js +74 -0
- package/dist/notifications/notification-bell.js.map +1 -0
- package/dist/notifications/notification-inbox.d.ts +77 -0
- package/dist/notifications/notification-inbox.js +206 -0
- package/dist/notifications/notification-inbox.js.map +1 -0
- package/dist/notifications/notification-types-setting.d.ts +6 -4
- package/dist/notifications/notification-types-setting.js +103 -45
- package/dist/notifications/notification-types-setting.js.map +1 -1
- package/dist/notifications/notification-types.d.ts +19 -7
- package/dist/notifications/notifications-labels.d.ts +41 -7
- package/dist/notifications/notifications-labels.js +18 -1
- package/dist/notifications/notifications-labels.js.map +1 -1
- package/dist/notifications/push-devices-setting.d.ts +20 -3
- package/dist/notifications/push-devices-setting.js +93 -33
- package/dist/notifications/push-devices-setting.js.map +1 -1
- package/dist/notifications/push-worker.d.ts +108 -0
- package/dist/notifications/push-worker.js +62 -0
- package/dist/notifications/push-worker.js.map +1 -0
- package/dist/notifications/quiet-hours-setting.d.ts +29 -17
- package/dist/notifications/quiet-hours-setting.js +9 -41
- package/dist/notifications/quiet-hours-setting.js.map +1 -1
- package/dist/notifications/time-zone-select.d.ts +5 -2
- package/dist/notifications/time-zone-select.js.map +1 -1
- package/dist/notifications/use-push-device.d.ts +4 -17
- package/dist/notifications/use-push-device.js +7 -15
- package/dist/notifications/use-push-device.js.map +1 -1
- package/dist/offline/local-first.d.ts +33 -0
- package/dist/offline/local-first.js +39 -0
- package/dist/offline/local-first.js.map +1 -0
- package/dist/offline/query-persistence.d.ts +26 -1
- package/dist/offline/query-persistence.js +27 -23
- package/dist/offline/query-persistence.js.map +1 -1
- package/dist/offline/service-worker-updates.d.ts +46 -0
- package/dist/offline/service-worker-updates.js +39 -0
- package/dist/offline/service-worker-updates.js.map +1 -0
- package/dist/offline.d.ts +5 -2
- package/dist/offline.js +11 -2
- package/dist/offline.js.map +1 -1
- package/dist/push-worker.d.ts +2 -0
- package/dist/push-worker.js +14 -0
- package/dist/push-worker.js.map +1 -0
- package/dist/shell/account-menu-text-size.d.ts +33 -0
- package/dist/shell/account-menu-text-size.js +51 -0
- package/dist/shell/account-menu-text-size.js.map +1 -0
- package/dist/shell/top-bar-actions.d.ts +6 -2
- package/dist/shell/top-bar-actions.js +1 -0
- package/dist/shell/top-bar-actions.js.map +1 -1
- package/dist/shell.d.ts +2 -0
- package/dist/shell.js +2 -0
- package/dist/shell.js.map +1 -1
- package/dist/sync/db.d.ts +76 -0
- package/dist/sync/db.js +24 -0
- package/dist/sync/db.js.map +1 -0
- package/dist/sync/engine.d.ts +124 -0
- package/dist/sync/engine.js +474 -0
- package/dist/sync/engine.js.map +1 -0
- package/dist/sync/hooks.d.ts +30 -0
- package/dist/sync/hooks.js +67 -0
- package/dist/sync/hooks.js.map +1 -0
- package/dist/sync/testing.d.ts +28 -0
- package/dist/sync/testing.js +41 -0
- package/dist/sync/testing.js.map +1 -0
- package/dist/sync/wire.d.ts +50 -0
- package/dist/sync/wire.js +2 -0
- package/dist/sync/wire.js.map +1 -0
- package/dist/sync.d.ts +7 -0
- package/dist/sync.js +22 -0
- package/dist/sync.js.map +1 -0
- package/dist/tour/tour-labels.js +1 -0
- package/dist/tour/tour-labels.js.map +1 -1
- package/dist/tour/tour.d.ts +30 -0
- package/dist/tour/tour.js +50 -6
- package/dist/tour/tour.js.map +1 -1
- package/package.json +19 -1
- package/src/admin/admin-area-labels.ts +13 -0
- package/src/admin/admin-overview.tsx +37 -2
- package/src/admin/mail-test-card.tsx +7 -3
- package/src/components/appearance-labels.ts +16 -0
- package/src/components/data-table-column-settings.tsx +2 -2
- package/src/components/data-table-head.tsx +2 -2
- package/src/components/data-table-row-actions.tsx +118 -2
- package/src/components/data-table-shared.tsx +21 -2
- package/src/components/data-table.tsx +51 -6
- package/src/components/date-format-setting.tsx +64 -0
- package/src/components/swipeable-row.tsx +8 -0
- package/src/components/tabs.tsx +4 -3
- package/src/components/translation-review-editor.tsx +2 -0
- package/src/components/translation-review-labels.ts +7 -0
- package/src/components/translation-review.tsx +102 -14
- package/src/components/write-lock-labels.ts +1 -0
- package/src/components/write-lock.tsx +36 -3
- package/src/data-table.ts +1 -0
- package/src/feedback/feedback-row-detail.tsx +18 -23
- package/src/feedback/feedback-swipe.tsx +28 -1
- package/src/i18n/german.ts +44 -4
- package/src/i18n/kit-labels.tsx +18 -1
- package/src/i18n/locales/en.ts +1 -0
- package/src/i18n/locales/es.ts +44 -4
- package/src/i18n/locales/fr.ts +44 -4
- package/src/i18n/locales/hu.ts +44 -4
- package/src/i18n/locales/it.ts +44 -4
- package/src/i18n/locales/zh.ts +44 -2
- package/src/i18n/review.ts +3 -1
- package/src/index.ts +42 -0
- package/src/lib/date-format.ts +151 -0
- package/src/lib/format.ts +10 -4
- package/src/notifications/device-zone-report.ts +54 -0
- package/src/notifications/feedback-notification-types-labels.ts +10 -1
- package/src/notifications/feedback-notification-types.ts +12 -5
- package/src/notifications/notification-bell.tsx +116 -0
- package/src/notifications/notification-inbox.tsx +304 -0
- package/src/notifications/notification-types-setting.tsx +151 -55
- package/src/notifications/notification-types.ts +20 -8
- package/src/notifications/notifications-labels.ts +69 -11
- package/src/notifications/push-devices-setting.tsx +123 -34
- package/src/notifications/push-worker.ts +160 -0
- package/src/notifications/quiet-hours-setting.tsx +41 -58
- package/src/notifications/time-zone-select.tsx +5 -2
- package/src/notifications/use-push-device.ts +20 -33
- package/src/offline/local-first.ts +76 -0
- package/src/offline/query-persistence.ts +57 -24
- package/src/offline/service-worker-updates.ts +81 -0
- package/src/offline.ts +7 -1
- package/src/push-worker.ts +25 -0
- package/src/shell/account-menu-text-size.tsx +77 -0
- package/src/shell/top-bar-actions.tsx +6 -1
- package/src/shell.ts +3 -0
- package/src/sync/db.ts +103 -0
- package/src/sync/engine.ts +699 -0
- package/src/sync/hooks.ts +93 -0
- package/src/sync/testing.ts +73 -0
- package/src/sync/wire.ts +61 -0
- package/src/sync.ts +29 -0
- package/src/tour/tour-labels.ts +1 -0
- package/src/tour/tour.tsx +100 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/top-bar-actions.tsx"],"sourcesContent":["import { Fragment } from \"react\";\nimport type { ReactNode } from \"react\";\n\n/**\n * The top bar's items by name (docs/top-bar-harmonization.md §2.1, §6.6). Each is the\n * app's own node — a kit part or its own — and any may be left out.\n */\nexport interface TopBarActionsProps {\n /** kastlan's `CompanySwitcher`: the context, first. */\n companySwitcher?: ReactNode;\n /** `GlobalSearch` (⌘K). */\n search?: ReactNode;\n /** `ThemeToggle`. */\n theme?: ReactNode;\n /** `FeedbackMenu`. */\n feedback?: ReactNode;\n /** `TourMenu`. */\n tours?: ReactNode;\n /** kastlan's assistant, beside the tours while it is on. */\n assistant?: ReactNode;\n /** `SyncStatusIndicator`. */\n sync?: ReactNode;\n /** `DevSwitchers` (role, then plan). */\n dev?: ReactNode;\n /** kastlan's platform button. */\n platform?: ReactNode;\n /** The account menu. When it is set the signed-out slots are not drawn. */\n account?: ReactNode;\n /** Signed out (§6.6): the bar's `LanguageMenu`, since there is no account menu to hold\n * the language. Drawn only without `account`. */\n language?: ReactNode;\n /** Signed out (§6.6): the \"Sign in\" button, where the account menu would be. Drawn only\n * without `account`. */\n signIn?: ReactNode;\n}\n\n/** §2.1's order, then the signed-out pair in the account menu's place. */\nexport const TOP_BAR_ACTION_ORDER = [\n \"companySwitcher\",\n \"search\",\n \"theme\",\n \"feedback\",\n \"tours\",\n \"assistant\",\n \"sync\",\n \"dev\",\n \"platform\",\n \"account\",\n \"language\",\n \"signIn\",\n] as const satisfies readonly (keyof TopBarActionsProps)[];\n\nconst present = (node: ReactNode) => node !== undefined && node !== null && node !== false && node !== \"\";\n\n/**\n * The top bar's actions in the one order every app shares (§2.1): company switcher ·\n * Search · Theme · Feedback · Tours (· Assistant) · Sync · dev role · dev plan · platform ·\n * account menu — and, signed out, Language · Sign in where the account menu goes (§6.6).\n * The app fills the slots it has; an empty slot is skipped, so it leaves no gap.\n *\n * It draws a fragment, no box of its own: hand it to `TopBar`'s `actions`, whose row\n * spaces the items.\n *\n * ```tsx\n * <TopBar brand={…} actions={<TopBarActions search={…} theme={…} tours={…} account={…} />} />\n * ```\n */\nexport function TopBarActions(props: TopBarActionsProps) {\n const signedOut = !present(props.account);\n return (\n <>\n {TOP_BAR_ACTION_ORDER.map((slot) => {\n if ((slot === \"language\" || slot === \"signIn\") && !signedOut) return null;\n const node = props[slot];\n return present(node) ? <Fragment key={slot}>{node}</Fragment> : null;\n })}\n </>\n );\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/shell/top-bar-actions.tsx"],"sourcesContent":["import { Fragment } from \"react\";\nimport type { ReactNode } from \"react\";\n\n/**\n * The top bar's items by name (docs/top-bar-harmonization.md §2.1, §6.6). Each is the\n * app's own node — a kit part or its own — and any may be left out.\n */\nexport interface TopBarActionsProps {\n /** kastlan's `CompanySwitcher`: the context, first. */\n companySwitcher?: ReactNode;\n /** `GlobalSearch` (⌘K). */\n search?: ReactNode;\n /** `ThemeToggle`. */\n theme?: ReactNode;\n /** `FeedbackMenu`. */\n feedback?: ReactNode;\n /** `TourMenu`. */\n tours?: ReactNode;\n /** kastlan's assistant, beside the tours while it is on. */\n assistant?: ReactNode;\n /** `SyncStatusIndicator`. */\n sync?: ReactNode;\n /** `DevSwitchers` (role, then plan). */\n dev?: ReactNode;\n /** kastlan's platform button. */\n platform?: ReactNode;\n /** 0.46 (docs/notifications-harmonization.md §10.1): `NotificationBell`, right before the\n * account menu. Optional per app: an app shows the bell or an unread dot on the avatar,\n * never both (keksdose keeps its dot and leaves this empty). */\n notifications?: ReactNode;\n /** The account menu. When it is set the signed-out slots are not drawn. */\n account?: ReactNode;\n /** Signed out (§6.6): the bar's `LanguageMenu`, since there is no account menu to hold\n * the language. Drawn only without `account`. */\n language?: ReactNode;\n /** Signed out (§6.6): the \"Sign in\" button, where the account menu would be. Drawn only\n * without `account`. */\n signIn?: ReactNode;\n}\n\n/** §2.1's order, then the signed-out pair in the account menu's place. */\nexport const TOP_BAR_ACTION_ORDER = [\n \"companySwitcher\",\n \"search\",\n \"theme\",\n \"feedback\",\n \"tours\",\n \"assistant\",\n \"sync\",\n \"dev\",\n \"platform\",\n \"notifications\",\n \"account\",\n \"language\",\n \"signIn\",\n] as const satisfies readonly (keyof TopBarActionsProps)[];\n\nconst present = (node: ReactNode) => node !== undefined && node !== null && node !== false && node !== \"\";\n\n/**\n * The top bar's actions in the one order every app shares (§2.1): company switcher ·\n * Search · Theme · Feedback · Tours (· Assistant) · Sync · dev role · dev plan · platform ·\n * notifications (the bell, 0.46) · account menu — and, signed out, Language · Sign in where the account menu goes (§6.6).\n * The app fills the slots it has; an empty slot is skipped, so it leaves no gap.\n *\n * It draws a fragment, no box of its own: hand it to `TopBar`'s `actions`, whose row\n * spaces the items.\n *\n * ```tsx\n * <TopBar brand={…} actions={<TopBarActions search={…} theme={…} tours={…} account={…} />} />\n * ```\n */\nexport function TopBarActions(props: TopBarActionsProps) {\n const signedOut = !present(props.account);\n return (\n <>\n {TOP_BAR_ACTION_ORDER.map((slot) => {\n if ((slot === \"language\" || slot === \"signIn\") && !signedOut) return null;\n const node = props[slot];\n return present(node) ? <Fragment key={slot}>{node}</Fragment> : null;\n })}\n </>\n );\n}\n"],"mappings":";AA2EI,mBAI2B,WAJ3B;AA3EJ,SAAS,YAAAA,iBAAgB;AAyClB,MAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,UAAU,CAAC,SAAoB,SAAS,UAAa,SAAS,QAAQ,SAAS,SAAS,SAAS;AAehG,SAAS,cAAc,OAA2B;AACvD,QAAM,YAAY,CAAC,QAAQ,MAAM,OAAO;AACxC,SACE,gCACG,+BAAqB,IAAI,CAAC,SAAS;AAClC,SAAK,SAAS,cAAc,SAAS,aAAa,CAAC,UAAW,QAAO;AACrE,UAAM,OAAO,MAAM,IAAI;AACvB,WAAO,QAAQ,IAAI,IAAI,oBAACA,WAAA,EAAqB,kBAAP,IAAY,IAAc;AAAA,EAClE,CAAC,GACH;AAEJ;","names":["Fragment"]}
|
package/dist/shell.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export { TopBarActions, TOP_BAR_ACTION_ORDER } from "./shell/top-bar-actions.js"
|
|
|
35
35
|
export type { TopBarActionsProps } from "./shell/top-bar-actions.js";
|
|
36
36
|
export { AccountMenuLanguage } from "./shell/account-menu-language.js";
|
|
37
37
|
export type { AccountMenuLanguageProps, AccountMenuLanguageOption } from "./shell/account-menu-language.js";
|
|
38
|
+
export { AccountMenuTextSize } from "./shell/account-menu-text-size.js";
|
|
39
|
+
export type { AccountMenuTextSizeProps } from "./shell/account-menu-text-size.js";
|
|
38
40
|
export { TourMenu } from "./shell/tour-menu.js";
|
|
39
41
|
export type { TourMenuProps, TourMenuTour } from "./shell/tour-menu.js";
|
|
40
42
|
export { SyncStatusIndicator, syncState } from "./shell/sync-status-indicator.js";
|
package/dist/shell.js
CHANGED
|
@@ -41,12 +41,14 @@ import {
|
|
|
41
41
|
import { PublicHeader } from "./landing/public-header.js";
|
|
42
42
|
import { TopBarActions, TOP_BAR_ACTION_ORDER } from "./shell/top-bar-actions.js";
|
|
43
43
|
import { AccountMenuLanguage } from "./shell/account-menu-language.js";
|
|
44
|
+
import { AccountMenuTextSize } from "./shell/account-menu-text-size.js";
|
|
44
45
|
import { TourMenu } from "./shell/tour-menu.js";
|
|
45
46
|
import { SyncStatusIndicator, syncState } from "./shell/sync-status-indicator.js";
|
|
46
47
|
import { DevSwitchers, DEV_ORIGINAL_ROLE_KEY } from "./shell/dev-switchers.js";
|
|
47
48
|
import { DEFAULT_TOP_BAR_PARTS_LABELS, useTopBarPartsLabels } from "./shell/top-bar-parts-labels.js";
|
|
48
49
|
export {
|
|
49
50
|
AccountMenuLanguage,
|
|
51
|
+
AccountMenuTextSize,
|
|
50
52
|
AdminMenuRow,
|
|
51
53
|
AdminWaitingBadge,
|
|
52
54
|
DEFAULT_LAST_VISITED_EXCLUDES,
|
package/dist/shell.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shell.ts"],"sourcesContent":["// `@eifi1/ui-kit/shell` — the composable app chrome.\n//\n// A re-slicing of the main barrel, not a new API. This entry point requires\n// `react-router`: AppShell renders NavLink and TopBarActionMenu renders Link.\nexport * from \"./shell/topbar-controls\";\nexport * from \"./shell/top-bar\";\nexport * from \"./shell/app-shell\";\nexport * from \"./shell/option-switcher-menu\";\nexport * from \"./shell/role-switcher\";\nexport * from \"./shell/topbar-action-menu\";\nexport * from \"./shell/top-bar-brand\";\nexport * from \"./shell/auth-layout\";\n// 0.40.0: the list page's fill (docs/page-layout-harmonization.md §1.3).\nexport { FillHeight, FILL_HEIGHT_CLASS } from \"./shell/fill-height\";\nexport type { FillHeightProps } from \"./shell/fill-height\";\n// 0.42.0: the account menu's \"Administration\" row, as an entry and as a HoverMenu row\n// (docs/admin-harmonization.md §2.1, §6.6).\nexport { adminMenuEntry, AdminMenuRow } from \"./shell/admin-menu\";\nexport type { AdminMenuEntryOptions, AdminMenuRowProps } from \"./shell/admin-menu\";\n// 0.43.0 (kastlan): the entry with the provider's words.\nexport { useAdminMenuEntry } from \"./shell/admin-menu\";\nexport type { UseAdminMenuEntryOptions } from \"./shell/admin-menu\";\n// 0.45.0 (docs/admin-harmonization.md §9.3.4): the row's waiting pill on its own, for\n// IconButton's `badge` (kastlan's platform button); its name is `adminArea.waitingLabel`.\nexport { AdminWaitingBadge } from \"./shell/admin-menu\";\nexport type { AdminWaitingBadgeProps } from \"./shell/admin-menu\";\n// 0.31.0: the settings and admin pages' shell (docs/settings-harmonization.md §3, §5).\nexport { SettingsLayout, SETTINGS_SEARCH_THRESHOLD } from \"./settings/settings-layout\";\nexport type { SettingsLayoutProps, SettingsLayoutWidth, SettingsDocumentTitleParts } from \"./settings/settings-layout\";\nexport {\n useSettingsRoute,\n resolveSettingsLocation,\n settingsFromListState,\n SETTINGS_FROM_LIST_STATE,\n} from \"./settings/use-settings-route\";\nexport type {\n UseSettingsRouteOptions,\n SettingsRoute,\n SettingsLayoutMode,\n SettingsLocation,\n SettingsLocationRules,\n SettingsLocationResult,\n} from \"./settings/use-settings-route\";\nexport { useSettingsFocus, SETTINGS_FOCUS_RING, SETTINGS_FOCUS_MS } from \"./settings/use-settings-focus\";\nexport { SettingsSection } from \"./settings/settings-section\";\nexport type { SettingsSectionProps } from \"./settings/settings-section\";\nexport { useSettingsLayout } from \"./settings/settings-context\";\nexport type { SettingsLayoutContextValue } from \"./settings/settings-context\";\nexport { SettingsHeadingLevel, useSettingsHeadingLevel } from \"./settings/settings-heading\";\nexport type { SettingsHeadingLevelProps, SettingsHeadingTag } from \"./settings/settings-heading\";\nexport {\n settingsSearchEntries,\n visibleSettingsGroups,\n visibleSettingsEntries,\n settingsHref,\n} from \"./settings/settings-catalogue\";\nexport type { SettingsGroup, SettingsEntry, SettingsSearchEntriesOptions } from \"./settings/settings-catalogue\";\n// 0.31.0: the public pages' header and the resume (docs/landing-demo-harmonization.md §3, §4.1).\nexport {\n RootEntry,\n RedirectIfAuthed,\n useLastVisitedPage,\n readLastVisitedPage,\n clearLastVisitedPage,\n safeNextPath,\n NEXT_PARAM,\n DEFAULT_LAST_VISITED_EXCLUDES,\n} from \"./landing/routing\";\nexport type {\n RootEntryProps,\n RedirectIfAuthedProps,\n RoutingSession,\n LastVisitedPageOptions,\n PathPattern,\n} from \"./landing/routing\";\nexport { PublicHeader } from \"./landing/public-header\";\nexport type { PublicHeaderProps, PublicHeaderBrand } from \"./landing/public-header\";\n// 0.42.0: the top bar's shared parts (docs/top-bar-harmonization.md §3.1) — the slots in\n// §2.1's order, the account menu's language sub-list, the tours menu, the sync status, the\n// local-dev switchers, and their words.\nexport { TopBarActions, TOP_BAR_ACTION_ORDER } from \"./shell/top-bar-actions\";\nexport type { TopBarActionsProps } from \"./shell/top-bar-actions\";\nexport { AccountMenuLanguage } from \"./shell/account-menu-language\";\nexport type { AccountMenuLanguageProps, AccountMenuLanguageOption } from \"./shell/account-menu-language\";\nexport { TourMenu } from \"./shell/tour-menu\";\nexport type { TourMenuProps, TourMenuTour } from \"./shell/tour-menu\";\nexport { SyncStatusIndicator, syncState } from \"./shell/sync-status-indicator\";\nexport type { SyncStatus, SyncState, SyncStatusScope, SyncStatusIndicatorProps } from \"./shell/sync-status-indicator\";\nexport { DevSwitchers, DEV_ORIGINAL_ROLE_KEY } from \"./shell/dev-switchers\";\nexport type { DevSwitchersProps } from \"./shell/dev-switchers\";\nexport { DEFAULT_TOP_BAR_PARTS_LABELS, useTopBarPartsLabels } from \"./shell/top-bar-parts-labels\";\nexport type {\n TopBarPartsLabels,\n TourMenuLabels,\n SyncStatusLabels,\n DevSwitchersLabels,\n} from \"./shell/top-bar-parts-labels\";\n"],"mappings":";AAIA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAEd,SAAS,YAAY,yBAAyB;AAI9C,SAAS,gBAAgB,oBAAoB;AAG7C,SAAS,yBAAyB;AAIlC,SAAS,yBAAyB;AAGlC,SAAS,gBAAgB,iCAAiC;AAE1D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AASP,SAAS,kBAAkB,qBAAqB,yBAAyB;AACzE,SAAS,uBAAuB;AAEhC,SAAS,yBAAyB;AAElC,SAAS,sBAAsB,+BAA+B;AAE9D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAQP,SAAS,oBAAoB;AAK7B,SAAS,eAAe,4BAA4B;AAEpD,SAAS,2BAA2B;AAEpC,SAAS,gBAAgB;AAEzB,SAAS,qBAAqB,iBAAiB;AAE/C,SAAS,cAAc,6BAA6B;AAEpD,SAAS,8BAA8B,4BAA4B;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/shell.ts"],"sourcesContent":["// `@eifi1/ui-kit/shell` — the composable app chrome.\n//\n// A re-slicing of the main barrel, not a new API. This entry point requires\n// `react-router`: AppShell renders NavLink and TopBarActionMenu renders Link.\nexport * from \"./shell/topbar-controls\";\nexport * from \"./shell/top-bar\";\nexport * from \"./shell/app-shell\";\nexport * from \"./shell/option-switcher-menu\";\nexport * from \"./shell/role-switcher\";\nexport * from \"./shell/topbar-action-menu\";\nexport * from \"./shell/top-bar-brand\";\nexport * from \"./shell/auth-layout\";\n// 0.40.0: the list page's fill (docs/page-layout-harmonization.md §1.3).\nexport { FillHeight, FILL_HEIGHT_CLASS } from \"./shell/fill-height\";\nexport type { FillHeightProps } from \"./shell/fill-height\";\n// 0.42.0: the account menu's \"Administration\" row, as an entry and as a HoverMenu row\n// (docs/admin-harmonization.md §2.1, §6.6).\nexport { adminMenuEntry, AdminMenuRow } from \"./shell/admin-menu\";\nexport type { AdminMenuEntryOptions, AdminMenuRowProps } from \"./shell/admin-menu\";\n// 0.43.0 (kastlan): the entry with the provider's words.\nexport { useAdminMenuEntry } from \"./shell/admin-menu\";\nexport type { UseAdminMenuEntryOptions } from \"./shell/admin-menu\";\n// 0.45.0 (docs/admin-harmonization.md §9.3.4): the row's waiting pill on its own, for\n// IconButton's `badge` (kastlan's platform button); its name is `adminArea.waitingLabel`.\nexport { AdminWaitingBadge } from \"./shell/admin-menu\";\nexport type { AdminWaitingBadgeProps } from \"./shell/admin-menu\";\n// 0.31.0: the settings and admin pages' shell (docs/settings-harmonization.md §3, §5).\nexport { SettingsLayout, SETTINGS_SEARCH_THRESHOLD } from \"./settings/settings-layout\";\nexport type { SettingsLayoutProps, SettingsLayoutWidth, SettingsDocumentTitleParts } from \"./settings/settings-layout\";\nexport {\n useSettingsRoute,\n resolveSettingsLocation,\n settingsFromListState,\n SETTINGS_FROM_LIST_STATE,\n} from \"./settings/use-settings-route\";\nexport type {\n UseSettingsRouteOptions,\n SettingsRoute,\n SettingsLayoutMode,\n SettingsLocation,\n SettingsLocationRules,\n SettingsLocationResult,\n} from \"./settings/use-settings-route\";\nexport { useSettingsFocus, SETTINGS_FOCUS_RING, SETTINGS_FOCUS_MS } from \"./settings/use-settings-focus\";\nexport { SettingsSection } from \"./settings/settings-section\";\nexport type { SettingsSectionProps } from \"./settings/settings-section\";\nexport { useSettingsLayout } from \"./settings/settings-context\";\nexport type { SettingsLayoutContextValue } from \"./settings/settings-context\";\nexport { SettingsHeadingLevel, useSettingsHeadingLevel } from \"./settings/settings-heading\";\nexport type { SettingsHeadingLevelProps, SettingsHeadingTag } from \"./settings/settings-heading\";\nexport {\n settingsSearchEntries,\n visibleSettingsGroups,\n visibleSettingsEntries,\n settingsHref,\n} from \"./settings/settings-catalogue\";\nexport type { SettingsGroup, SettingsEntry, SettingsSearchEntriesOptions } from \"./settings/settings-catalogue\";\n// 0.31.0: the public pages' header and the resume (docs/landing-demo-harmonization.md §3, §4.1).\nexport {\n RootEntry,\n RedirectIfAuthed,\n useLastVisitedPage,\n readLastVisitedPage,\n clearLastVisitedPage,\n safeNextPath,\n NEXT_PARAM,\n DEFAULT_LAST_VISITED_EXCLUDES,\n} from \"./landing/routing\";\nexport type {\n RootEntryProps,\n RedirectIfAuthedProps,\n RoutingSession,\n LastVisitedPageOptions,\n PathPattern,\n} from \"./landing/routing\";\nexport { PublicHeader } from \"./landing/public-header\";\nexport type { PublicHeaderProps, PublicHeaderBrand } from \"./landing/public-header\";\n// 0.42.0: the top bar's shared parts (docs/top-bar-harmonization.md §3.1) — the slots in\n// §2.1's order, the account menu's language sub-list, the tours menu, the sync status, the\n// local-dev switchers, and their words.\nexport { TopBarActions, TOP_BAR_ACTION_ORDER } from \"./shell/top-bar-actions\";\nexport type { TopBarActionsProps } from \"./shell/top-bar-actions\";\nexport { AccountMenuLanguage } from \"./shell/account-menu-language\";\nexport type { AccountMenuLanguageProps, AccountMenuLanguageOption } from \"./shell/account-menu-language\";\n// 0.46.0 (keksdose live #426): A− / A+ for the text size, beside the language.\nexport { AccountMenuTextSize } from \"./shell/account-menu-text-size\";\nexport type { AccountMenuTextSizeProps } from \"./shell/account-menu-text-size\";\nexport { TourMenu } from \"./shell/tour-menu\";\nexport type { TourMenuProps, TourMenuTour } from \"./shell/tour-menu\";\nexport { SyncStatusIndicator, syncState } from \"./shell/sync-status-indicator\";\nexport type { SyncStatus, SyncState, SyncStatusScope, SyncStatusIndicatorProps } from \"./shell/sync-status-indicator\";\nexport { DevSwitchers, DEV_ORIGINAL_ROLE_KEY } from \"./shell/dev-switchers\";\nexport type { DevSwitchersProps } from \"./shell/dev-switchers\";\nexport { DEFAULT_TOP_BAR_PARTS_LABELS, useTopBarPartsLabels } from \"./shell/top-bar-parts-labels\";\nexport type {\n TopBarPartsLabels,\n TourMenuLabels,\n SyncStatusLabels,\n DevSwitchersLabels,\n} from \"./shell/top-bar-parts-labels\";\n"],"mappings":";AAIA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAEd,SAAS,YAAY,yBAAyB;AAI9C,SAAS,gBAAgB,oBAAoB;AAG7C,SAAS,yBAAyB;AAIlC,SAAS,yBAAyB;AAGlC,SAAS,gBAAgB,iCAAiC;AAE1D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AASP,SAAS,kBAAkB,qBAAqB,yBAAyB;AACzE,SAAS,uBAAuB;AAEhC,SAAS,yBAAyB;AAElC,SAAS,sBAAsB,+BAA+B;AAE9D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAQP,SAAS,oBAAoB;AAK7B,SAAS,eAAe,4BAA4B;AAEpD,SAAS,2BAA2B;AAGpC,SAAS,2BAA2B;AAEpC,SAAS,gBAAgB;AAEzB,SAAS,qBAAqB,iBAAiB;AAE/C,SAAS,cAAc,6BAA6B;AAEpD,SAAS,8BAA8B,4BAA4B;","names":[]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import Dexie, { type Table, type Transaction } from "dexie";
|
|
2
|
+
/** A row as `/sync` carries it: the entity (a table name), its sync id, the server's id
|
|
3
|
+
* once it has one, and its fields. */
|
|
4
|
+
export interface SyncedRow {
|
|
5
|
+
entity: string;
|
|
6
|
+
id: string;
|
|
7
|
+
pk: number | null;
|
|
8
|
+
data: Record<string, unknown>;
|
|
9
|
+
/** The server's stamp of the row, as the pull carried it BESIDE `data` (kastlan's wire);
|
|
10
|
+
* the next edit here is stamped after it (`stampAfter`, D2). */
|
|
11
|
+
updated_at?: string | null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Where a change made here stands. `pending`: queued. `sending`: in the request now out (a
|
|
15
|
+
* new edit starts a change of its own). `waiting`: refused with a hold code (a lapsed plan,
|
|
16
|
+
* `holdCodes`), sent again every round. `blocked`: refused with any other code, kept, sent
|
|
17
|
+
* again only on `retryBlocked()`. `rejected`: the server's rules refused it (`reason` says
|
|
18
|
+
* why); never sent again.
|
|
19
|
+
*/
|
|
20
|
+
export type OutboxStatus = "pending" | "sending" | "waiting" | "blocked" | "rejected";
|
|
21
|
+
/** A change made on this device, for `/sync`, in the scope its caller named (D11). */
|
|
22
|
+
export interface OutboxChange {
|
|
23
|
+
seq?: number;
|
|
24
|
+
scope: string;
|
|
25
|
+
entity: string;
|
|
26
|
+
id: string;
|
|
27
|
+
op: "upsert" | "delete";
|
|
28
|
+
data: Record<string, unknown>;
|
|
29
|
+
/** The edit's time on this device (D2), stamped with `stampAfter`. */
|
|
30
|
+
updated_at: string;
|
|
31
|
+
status: OutboxStatus;
|
|
32
|
+
/** The server's words: a rejection's `detail`, a refusal's `detail`. */
|
|
33
|
+
reason?: string;
|
|
34
|
+
/** The server's code: a rejection's or a refusal's `code`. */
|
|
35
|
+
code?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface SyncMetaEntry {
|
|
38
|
+
key: string;
|
|
39
|
+
value: unknown;
|
|
40
|
+
}
|
|
41
|
+
/** The engine's database: its three tables, and the app's own under the names it gave. */
|
|
42
|
+
export type SyncDb = Dexie & {
|
|
43
|
+
rows: Table<SyncedRow, [string, string]>;
|
|
44
|
+
outbox: Table<OutboxChange, number>;
|
|
45
|
+
meta: Table<SyncMetaEntry, string>;
|
|
46
|
+
};
|
|
47
|
+
/** A schema step of an existing database (kastlan's v2: `scope` on the outbox). */
|
|
48
|
+
export interface SyncDbUpgrade {
|
|
49
|
+
version: number;
|
|
50
|
+
/** Stores this version adds or changes, beside the engine's and `extraStores`. */
|
|
51
|
+
stores?: Record<string, string | null>;
|
|
52
|
+
upgrade?(tx: Transaction): Promise<void> | void;
|
|
53
|
+
}
|
|
54
|
+
export interface CreateSyncDbOptions {
|
|
55
|
+
/** The app's own tables, Dexie schema strings: kastlan `{ photos: "id, roomId, status" }`. */
|
|
56
|
+
extraStores?: Record<string, string>;
|
|
57
|
+
/** Later versions of a database that exists on devices already, oldest first. */
|
|
58
|
+
upgrades?: readonly SyncDbUpgrade[];
|
|
59
|
+
}
|
|
60
|
+
/** The engine's own tables. `scope` is indexed: a round takes one scope's queue (D11). */
|
|
61
|
+
export declare const SYNC_DB_STORES: {
|
|
62
|
+
readonly rows: "[entity+id], entity";
|
|
63
|
+
readonly outbox: "++seq, [entity+id], status, scope";
|
|
64
|
+
readonly meta: "key";
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Open (lazily, on first use) the sync database `name`: the engine's tables plus the app's
|
|
68
|
+
* `extraStores`, at version 1 or the last of `upgrades`. One database per account (kastlan:
|
|
69
|
+
* per account and company, `kastlan-handover-<user>-<company>`), and one engine on it (D15).
|
|
70
|
+
*
|
|
71
|
+
* An app whose database predates the engine names its next version in `upgrades` and moves
|
|
72
|
+
* its data there — kastlan: `{ version: 2, upgrade: (tx) => tx.table("outbox").toCollection()
|
|
73
|
+
* .modify({ scope: "handover" }) }`. Dexie diffs the schema against the one on the device.
|
|
74
|
+
* Name the app's own type for its tables: `createSyncDb<HandoverDb>(name, { extraStores })`.
|
|
75
|
+
*/
|
|
76
|
+
export declare function createSyncDb<D extends SyncDb = SyncDb>(name: string, options?: CreateSyncDbOptions): D;
|
package/dist/sync/db.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import Dexie from "dexie";
|
|
3
|
+
const SYNC_DB_STORES = {
|
|
4
|
+
rows: "[entity+id], entity",
|
|
5
|
+
outbox: "++seq, [entity+id], status, scope",
|
|
6
|
+
meta: "key"
|
|
7
|
+
};
|
|
8
|
+
function createSyncDb(name, options = {}) {
|
|
9
|
+
const db = new Dexie(name);
|
|
10
|
+
let stores = { ...SYNC_DB_STORES, ...options.extraStores };
|
|
11
|
+
db.version(1).stores(stores);
|
|
12
|
+
const upgrades = [...options.upgrades ?? []].sort((a, b) => a.version - b.version);
|
|
13
|
+
for (const step of upgrades) {
|
|
14
|
+
stores = { ...stores, ...step.stores };
|
|
15
|
+
const version = db.version(step.version).stores(stores);
|
|
16
|
+
if (step.upgrade) version.upgrade(step.upgrade);
|
|
17
|
+
}
|
|
18
|
+
return db;
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
SYNC_DB_STORES,
|
|
22
|
+
createSyncDb
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/sync/db.ts"],"sourcesContent":["import Dexie, { type Table, type Transaction } from \"dexie\";\n\n/*\n * The device's copy for the sync engine (docs/local-first-harmonization.md §2.3, D10): ONE\n * Dexie database with the engine's three tables — the rows as `/sync` carries them, the\n * outbox of changes made here, and a key/value `meta` — plus the app's own (kastlan's\n * `photos`). A generic store, not keksdose's per-entity tables.\n */\n\n/** A row as `/sync` carries it: the entity (a table name), its sync id, the server's id\n * once it has one, and its fields. */\nexport interface SyncedRow {\n entity: string;\n id: string;\n pk: number | null;\n data: Record<string, unknown>;\n /** The server's stamp of the row, as the pull carried it BESIDE `data` (kastlan's wire);\n * the next edit here is stamped after it (`stampAfter`, D2). */\n updated_at?: string | null;\n}\n\n/**\n * Where a change made here stands. `pending`: queued. `sending`: in the request now out (a\n * new edit starts a change of its own). `waiting`: refused with a hold code (a lapsed plan,\n * `holdCodes`), sent again every round. `blocked`: refused with any other code, kept, sent\n * again only on `retryBlocked()`. `rejected`: the server's rules refused it (`reason` says\n * why); never sent again.\n */\nexport type OutboxStatus = \"pending\" | \"sending\" | \"waiting\" | \"blocked\" | \"rejected\";\n\n/** A change made on this device, for `/sync`, in the scope its caller named (D11). */\nexport interface OutboxChange {\n seq?: number;\n scope: string;\n entity: string;\n id: string;\n op: \"upsert\" | \"delete\";\n data: Record<string, unknown>;\n /** The edit's time on this device (D2), stamped with `stampAfter`. */\n updated_at: string;\n status: OutboxStatus;\n /** The server's words: a rejection's `detail`, a refusal's `detail`. */\n reason?: string;\n /** The server's code: a rejection's or a refusal's `code`. */\n code?: string;\n}\n\nexport interface SyncMetaEntry {\n key: string;\n value: unknown;\n}\n\n/** The engine's database: its three tables, and the app's own under the names it gave. */\nexport type SyncDb = Dexie & {\n rows: Table<SyncedRow, [string, string]>;\n outbox: Table<OutboxChange, number>;\n meta: Table<SyncMetaEntry, string>;\n};\n\n/** A schema step of an existing database (kastlan's v2: `scope` on the outbox). */\nexport interface SyncDbUpgrade {\n version: number;\n /** Stores this version adds or changes, beside the engine's and `extraStores`. */\n stores?: Record<string, string | null>;\n upgrade?(tx: Transaction): Promise<void> | void;\n}\n\nexport interface CreateSyncDbOptions {\n /** The app's own tables, Dexie schema strings: kastlan `{ photos: \"id, roomId, status\" }`. */\n extraStores?: Record<string, string>;\n /** Later versions of a database that exists on devices already, oldest first. */\n upgrades?: readonly SyncDbUpgrade[];\n}\n\n/** The engine's own tables. `scope` is indexed: a round takes one scope's queue (D11). */\nexport const SYNC_DB_STORES = {\n rows: \"[entity+id], entity\",\n outbox: \"++seq, [entity+id], status, scope\",\n meta: \"key\",\n} as const;\n\n/**\n * Open (lazily, on first use) the sync database `name`: the engine's tables plus the app's\n * `extraStores`, at version 1 or the last of `upgrades`. One database per account (kastlan:\n * per account and company, `kastlan-handover-<user>-<company>`), and one engine on it (D15).\n *\n * An app whose database predates the engine names its next version in `upgrades` and moves\n * its data there — kastlan: `{ version: 2, upgrade: (tx) => tx.table(\"outbox\").toCollection()\n * .modify({ scope: \"handover\" }) }`. Dexie diffs the schema against the one on the device.\n * Name the app's own type for its tables: `createSyncDb<HandoverDb>(name, { extraStores })`.\n */\nexport function createSyncDb<D extends SyncDb = SyncDb>(name: string, options: CreateSyncDbOptions = {}): D {\n const db = new Dexie(name) as D;\n let stores: Record<string, string | null> = { ...SYNC_DB_STORES, ...options.extraStores };\n db.version(1).stores(stores);\n const upgrades = [...(options.upgrades ?? [])].sort((a, b) => a.version - b.version);\n for (const step of upgrades) {\n stores = { ...stores, ...step.stores };\n const version = db.version(step.version).stores(stores);\n if (step.upgrade) version.upgrade(step.upgrade);\n }\n return db;\n}\n"],"mappings":";AAAA,OAAO,WAA6C;AA2E7C,MAAM,iBAAiB;AAAA,EAC5B,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AACR;AAYO,SAAS,aAAwC,MAAc,UAA+B,CAAC,GAAM;AAC1G,QAAM,KAAK,IAAI,MAAM,IAAI;AACzB,MAAI,SAAwC,EAAE,GAAG,gBAAgB,GAAG,QAAQ,YAAY;AACxF,KAAG,QAAQ,CAAC,EAAE,OAAO,MAAM;AAC3B,QAAM,WAAW,CAAC,GAAI,QAAQ,YAAY,CAAC,CAAE,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO;AACnF,aAAW,QAAQ,UAAU;AAC3B,aAAS,EAAE,GAAG,QAAQ,GAAG,KAAK,OAAO;AACrC,UAAM,UAAU,GAAG,QAAQ,KAAK,OAAO,EAAE,OAAO,MAAM;AACtD,QAAI,KAAK,QAAS,SAAQ,QAAQ,KAAK,OAAO;AAAA,EAChD;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { OutboxChange, SyncDb } from "./db.js";
|
|
2
|
+
import type { SyncRequestBody, SyncResponseBody } from "./wire.js";
|
|
3
|
+
export interface SyncCounts {
|
|
4
|
+
pending: number;
|
|
5
|
+
waiting: number;
|
|
6
|
+
blocked: number;
|
|
7
|
+
rejected: number;
|
|
8
|
+
}
|
|
9
|
+
export interface SyncStepContext {
|
|
10
|
+
signal: AbortSignal;
|
|
11
|
+
/** False once `forget()` bumped the generation: the step writes nothing more (D15). */
|
|
12
|
+
isCurrent(): boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface SyncStepResult {
|
|
15
|
+
/** The step met no connection: the round ends offline. */
|
|
16
|
+
offline?: boolean;
|
|
17
|
+
/** One more push/pull in the same round (kastlan's second pass after its photos). */
|
|
18
|
+
changed?: boolean;
|
|
19
|
+
/** What the step keeps on the device, added to the engine's counts. */
|
|
20
|
+
counts?: Partial<SyncCounts>;
|
|
21
|
+
}
|
|
22
|
+
/** Work after the push/pull, such as kastlan's photo upload. */
|
|
23
|
+
export interface SyncStep {
|
|
24
|
+
name: string;
|
|
25
|
+
run(db: SyncDb, ctx: SyncStepContext): Promise<SyncStepResult>;
|
|
26
|
+
/** Its counts read from the device between rounds (a photo taken offline shows at once). */
|
|
27
|
+
counts?(db: SyncDb): Promise<Partial<SyncCounts>>;
|
|
28
|
+
}
|
|
29
|
+
export interface SyncEngineState {
|
|
30
|
+
phase: "idle" | "syncing" | "offline" | "error";
|
|
31
|
+
online: boolean;
|
|
32
|
+
counts: SyncCounts;
|
|
33
|
+
/** When a scope's walk last completed (ISO), kept in `meta` (D9). */
|
|
34
|
+
lastSyncedAt: string | null;
|
|
35
|
+
/** When the next attempt after an error runs (epoch ms), or `null`. */
|
|
36
|
+
nextRetryAt: number | null;
|
|
37
|
+
}
|
|
38
|
+
export interface SyncRoundResult {
|
|
39
|
+
/** `paused`: `paused()` held the round. `dropped`: `forget()` ran during it, its answer unused. */
|
|
40
|
+
outcome: "synced" | "offline" | "error" | "paused" | "dropped";
|
|
41
|
+
/** Changes this round set `waiting`, `blocked` or `rejected`. */
|
|
42
|
+
waiting: number;
|
|
43
|
+
blocked: number;
|
|
44
|
+
rejected: number;
|
|
45
|
+
error?: unknown;
|
|
46
|
+
}
|
|
47
|
+
export interface SyncEngineOptions {
|
|
48
|
+
db: SyncDb;
|
|
49
|
+
/** The scopes this session's role holds, in round order (kastlan: handover, meetings, meters,
|
|
50
|
+
* maintenance, contacts). Read at the start of every round: roles are per company. */
|
|
51
|
+
scopes: () => readonly string[];
|
|
52
|
+
/** Posts one request (`POST /sync`) and answers its body; throws as the app's client does. */
|
|
53
|
+
send: (body: SyncRequestBody, signal: AbortSignal) => Promise<SyncResponseBody>;
|
|
54
|
+
/** Default {@link isOfflineError}: no answer, or the session refresh's passing 401. */
|
|
55
|
+
isOffline?: (err: unknown) => boolean;
|
|
56
|
+
/** A whole request refused for the plan → every sent change `waiting` with this code.
|
|
57
|
+
* Default: a 402 whose body names a `code` (or `detail.code`). */
|
|
58
|
+
planCode?: (err: unknown) => string | null;
|
|
59
|
+
/** `refused.code`s kept as `waiting` and sent again every round; any other code is
|
|
60
|
+
* `blocked`, sent again only on `retryBlocked()`. Default `["billing_read_only"]`. */
|
|
61
|
+
holdCodes?: readonly string[];
|
|
62
|
+
/** Ask for a paged pull and follow its cursor (D12). */
|
|
63
|
+
paged?: boolean;
|
|
64
|
+
steps?: readonly SyncStep[];
|
|
65
|
+
/** No round while this holds (kastlan: a practice tour fakes the server's answers). */
|
|
66
|
+
paused?: () => boolean;
|
|
67
|
+
/** The poll (30 s) and the debounce after a write (800 ms). */
|
|
68
|
+
pollMs?: number;
|
|
69
|
+
soonMs?: number;
|
|
70
|
+
/** The waits after consecutive errors; the last repeats (D13). */
|
|
71
|
+
backoffMs?: readonly number[];
|
|
72
|
+
/** Default `isSharedComputer`: on a computer marked shared the engine does not start (D15). */
|
|
73
|
+
sharedComputer?: () => boolean;
|
|
74
|
+
}
|
|
75
|
+
export interface SyncEngine {
|
|
76
|
+
/** Into the copy at once and into the queue, in the scope the caller names (D11). An edit
|
|
77
|
+
* merges into the row's last `pending` upsert; deleting a row that never reached the
|
|
78
|
+
* server forgets its queue; stamped with `stampAfter`. */
|
|
79
|
+
write(scope: string, entity: string, id: string, op: "upsert" | "delete", data?: Record<string, unknown>): Promise<void>;
|
|
80
|
+
start(): void;
|
|
81
|
+
/** Stops the poll and the timers, aborts the round in flight and drops its answer (the
|
|
82
|
+
* queue is laid back). A new engine's round on the same database waits for it. */
|
|
83
|
+
stop(): void;
|
|
84
|
+
/** Sync soon (debounced): after a write. */
|
|
85
|
+
requestSync(): void;
|
|
86
|
+
/** "Sync now": resets the backoff, runs a round (after the one under way). */
|
|
87
|
+
syncNow(): Promise<SyncRoundResult>;
|
|
88
|
+
/** A round that sends the `blocked` changes too. */
|
|
89
|
+
retryBlocked(): Promise<void>;
|
|
90
|
+
/** Drop the rejected changes (of one scope) and pull that scope afresh. */
|
|
91
|
+
discardRejected(scope?: string): Promise<void>;
|
|
92
|
+
/** The rejected changes with their reasons, for the app's banner. */
|
|
93
|
+
rejected(): Promise<OutboxChange[]>;
|
|
94
|
+
/** Sign-out: bump the generation (a round in flight writes nothing more), then clear the
|
|
95
|
+
* rows and `meta` — and the outbox and the app's tables unless `keepQueue` (D15). */
|
|
96
|
+
forget(opts: {
|
|
97
|
+
keepQueue: boolean;
|
|
98
|
+
}): Promise<void>;
|
|
99
|
+
getState(): SyncEngineState;
|
|
100
|
+
subscribe(listener: () => void): () => void;
|
|
101
|
+
}
|
|
102
|
+
export declare const DEFAULT_SYNC_BACKOFF_MS: readonly number[];
|
|
103
|
+
export declare const DEFAULT_HOLD_CODES: readonly string[];
|
|
104
|
+
/**
|
|
105
|
+
* The engine on one database (D15: one engine per database; a company switch builds a new
|
|
106
|
+
* engine on the new database and stops the old one). Nothing runs until `start()`
|
|
107
|
+
* (`useSyncWorker`), and `start()` does nothing on a computer marked shared.
|
|
108
|
+
*/
|
|
109
|
+
export declare function createSyncEngine(options: SyncEngineOptions): SyncEngine;
|
|
110
|
+
/**
|
|
111
|
+
* The engine kept for `options.db` (by its name), built on first use: one engine per
|
|
112
|
+
* database for the session (D15), so a company switch A→B→A finds A's engine again instead
|
|
113
|
+
* of building a second one beside a round in flight. The first call's options stand: read
|
|
114
|
+
* changing values (scopes, the client) through functions. A database object opened afresh
|
|
115
|
+
* under the same name replaces the kept engine (the old one is stopped).
|
|
116
|
+
*/
|
|
117
|
+
export declare function syncEngineFor(options: SyncEngineOptions): SyncEngine;
|
|
118
|
+
/**
|
|
119
|
+
* Let go of the engine {@link syncEngineFor} keeps for the database `name` (0.46): it is
|
|
120
|
+
* stopped, its database closed and forgotten, so a later `syncEngineFor` builds afresh. Call it
|
|
121
|
+
* on sign-out (and for a company left for good): every database visited in a tab otherwise
|
|
122
|
+
* keeps its engine and an open IndexedDB connection until a reload. Nothing kept: a no-op.
|
|
123
|
+
*/
|
|
124
|
+
export declare function releaseSyncEngine(name: string): void;
|