@sincpro/mobile 0.4.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +3 -3
  2. package/dist/adapters/JsonSerializer.adapter.d.ts +1 -1
  3. package/dist/adapters/JsonSerializer.adapter.js +2 -2
  4. package/dist/adapters/repositories/database_table.repository.d.ts +3 -2
  5. package/dist/adapters/repositories/database_table.repository.js +1 -1
  6. package/dist/adapters/repositories/document.repository.d.ts +81 -0
  7. package/dist/adapters/repositories/document.repository.js +653 -0
  8. package/dist/adapters/repositories/domain_event.repository.d.ts +17 -17
  9. package/dist/adapters/repositories/domain_event.repository.js +25 -31
  10. package/dist/adapters/repositories/domain_event_dead_letter.repository.d.ts +10 -10
  11. package/dist/adapters/repositories/domain_event_dead_letter.repository.js +14 -13
  12. package/dist/adapters/repositories/event_row.d.ts +13 -0
  13. package/dist/adapters/repositories/event_row.js +22 -0
  14. package/dist/adapters/repositories/sqlite_document_store.d.ts +33 -0
  15. package/dist/adapters/repositories/sqlite_document_store.js +201 -0
  16. package/dist/domain/connectivity/events.d.ts +5 -5
  17. package/dist/domain/connectivity/events.js +3 -3
  18. package/dist/domain/database/criteria.d.ts +19 -0
  19. package/dist/domain/database/criteria.js +36 -0
  20. package/dist/domain/database/criteria_clock.d.ts +8 -0
  21. package/dist/domain/database/criteria_clock.js +305 -0
  22. package/dist/domain/database/criteria_sql.d.ts +61 -0
  23. package/dist/domain/database/criteria_sql.js +476 -0
  24. package/dist/domain/database/database.d.ts +1 -0
  25. package/dist/domain/database/index.d.ts +3 -1
  26. package/dist/domain/database/index.js +3 -1
  27. package/dist/domain/database/migration_plan.d.ts +14 -0
  28. package/dist/domain/database/migration_plan.js +44 -0
  29. package/dist/domain/entity/change_tracking.d.ts +21 -0
  30. package/dist/domain/entity/change_tracking.js +71 -0
  31. package/dist/domain/entity/checks.d.ts +8 -0
  32. package/dist/domain/entity/checks.js +82 -0
  33. package/dist/domain/entity/entity.d.ts +39 -32
  34. package/dist/domain/entity/entity.js +94 -105
  35. package/dist/domain/entity/entity_collection.d.ts +15 -30
  36. package/dist/domain/entity/entity_collection.js +30 -121
  37. package/dist/domain/entity/index.d.ts +5 -0
  38. package/dist/domain/entity/index.js +5 -0
  39. package/dist/domain/entity/reconcile.d.ts +7 -0
  40. package/dist/domain/entity/reconcile.js +12 -0
  41. package/dist/domain/entity/relations.d.ts +27 -0
  42. package/dist/domain/entity/relations.js +69 -0
  43. package/dist/domain/entity/remote_sync.d.ts +4 -0
  44. package/dist/domain/entity/remote_sync.js +19 -0
  45. package/dist/domain/entity/values.d.ts +6 -0
  46. package/dist/domain/entity/values.js +13 -0
  47. package/dist/domain/events/catalog.d.ts +29 -0
  48. package/dist/domain/{events.js → events/catalog.js} +7 -11
  49. package/dist/domain/events/chain.d.ts +5 -0
  50. package/dist/domain/events/chain.js +6 -0
  51. package/dist/domain/{event_sourcing → events}/domain_event.d.ts +20 -22
  52. package/dist/domain/events/domain_event.js +227 -0
  53. package/dist/domain/events/index.d.ts +8 -0
  54. package/dist/domain/events/index.js +8 -0
  55. package/dist/domain/events/integration_event.d.ts +103 -0
  56. package/dist/domain/events/integration_event.js +72 -0
  57. package/dist/domain/events/label.d.ts +2 -0
  58. package/dist/domain/events/label.js +26 -0
  59. package/dist/domain/index.d.ts +0 -1
  60. package/dist/domain/index.js +0 -1
  61. package/dist/domain/print/events.d.ts +8 -4
  62. package/dist/domain/print/events.js +9 -4
  63. package/dist/domain/repositories/document_store.d.ts +33 -0
  64. package/dist/domain/repositories/document_store.js +5 -0
  65. package/dist/domain/repositories/hooks.d.ts +23 -0
  66. package/dist/domain/repositories/hooks.js +32 -0
  67. package/dist/domain/repositories/index.d.ts +4 -0
  68. package/dist/domain/repositories/index.js +4 -0
  69. package/dist/domain/{database → repositories}/repository.d.ts +9 -3
  70. package/dist/domain/settings.d.ts +4 -1
  71. package/dist/domain/settings.js +3 -0
  72. package/dist/domain/webview/events.d.ts +2 -2
  73. package/dist/domain/webview/events.js +2 -2
  74. package/dist/entrypoints/db/migrations.js +10 -10
  75. package/dist/entrypoints/queue/QueueProcessor.d.ts +1 -1
  76. package/dist/entrypoints/queue/activateDomain.subscriber.d.ts +2 -2
  77. package/dist/entrypoints/queue/activateDomain.subscriber.js +2 -2
  78. package/dist/entrypoints/queue/newAppSettings.handler.d.ts +2 -2
  79. package/dist/entrypoints/queue/newAppSettings.handler.js +1 -1
  80. package/dist/entrypoints/queue/printImage.subscriber.d.ts +1 -1
  81. package/dist/entrypoints/queue/printImage.subscriber.js +2 -2
  82. package/dist/entrypoints/queue/processWebViewMessage.subscriber.d.ts +1 -1
  83. package/dist/entrypoints/queue/processWebViewMessage.subscriber.js +1 -1
  84. package/dist/entrypoints/ui/AppShell.js +2 -2
  85. package/dist/entrypoints/ui/common_provider.d.ts +0 -12
  86. package/dist/entrypoints/ui/common_provider.js +2 -131
  87. package/dist/event_driven/index.d.ts +1 -0
  88. package/dist/event_driven/index.js +1 -0
  89. package/dist/{domain/event_sourcing/event_handler.d.ts → event_driven/subscriber.d.ts} +1 -1
  90. package/dist/exceptions.d.ts +21 -0
  91. package/dist/exceptions.js +30 -0
  92. package/dist/framework/base_module.d.ts +1 -1
  93. package/dist/framework/createApp.d.ts +3 -3
  94. package/dist/framework/domain_module.d.ts +1 -1
  95. package/dist/framework/kernel.d.ts +1 -1
  96. package/dist/framework/kernel.js +17 -8
  97. package/dist/infrastructure/database/connector.d.ts +2 -0
  98. package/dist/infrastructure/database/connector.js +22 -0
  99. package/dist/infrastructure/database/index.d.ts +1 -0
  100. package/dist/infrastructure/database/index.js +1 -0
  101. package/dist/infrastructure/database/schema.d.ts +9 -0
  102. package/dist/infrastructure/database/schema.js +61 -0
  103. package/dist/infrastructure/database/utils.d.ts +2 -0
  104. package/dist/infrastructure/database/utils.js +12 -4
  105. package/dist/infrastructure/event_chain.d.ts +6 -0
  106. package/dist/infrastructure/event_chain.js +25 -0
  107. package/dist/infrastructure/telemetry/tracing/tracing_hooks.js +8 -5
  108. package/dist/infrastructure/ui/UIEventBus.d.ts +3 -1
  109. package/dist/infrastructure/ui/UIEventBus.js +8 -1
  110. package/dist/infrastructure/ui/activity.d.ts +37 -0
  111. package/dist/infrastructure/ui/activity.js +84 -0
  112. package/dist/infrastructure/workers/CronWorker.d.ts +6 -4
  113. package/dist/infrastructure/workers/CronWorker.js +24 -18
  114. package/dist/infrastructure/workers/EventBus.d.ts +9 -5
  115. package/dist/infrastructure/workers/EventBus.js +68 -33
  116. package/dist/services/bluetooth.service.js +16 -14
  117. package/dist/services/dead_letter_queue.service.d.ts +7 -7
  118. package/dist/services/dead_letter_queue.service.js +0 -2
  119. package/dist/services/event.service.d.ts +3 -3
  120. package/dist/services/event.service.js +1 -1
  121. package/dist/services/network.service.d.ts +1 -1
  122. package/dist/services/network.service.js +17 -15
  123. package/dist/services/printer.service.d.ts +1 -1
  124. package/dist/services/printer.service.js +50 -44
  125. package/dist/tools/utils/date.js +4 -3
  126. package/dist/tools/utils/monetary.d.ts +6 -3
  127. package/dist/tools/utils/monetary.js +33 -21
  128. package/dist/tools/utils/serializer.d.ts +1 -1
  129. package/dist/tools/utils/serializer.js +32 -35
  130. package/dist/ui/activity/DeviceNotices.d.ts +1 -0
  131. package/dist/ui/activity/DeviceNotices.js +29 -0
  132. package/dist/ui/activity/ProcessToast.d.ts +2 -0
  133. package/dist/ui/activity/ProcessToast.js +71 -0
  134. package/dist/ui/activity/index.d.ts +4 -0
  135. package/dist/ui/activity/index.js +4 -0
  136. package/dist/ui/activity/useActivity.d.ts +9 -0
  137. package/dist/ui/activity/useActivity.js +48 -0
  138. package/dist/ui/components/molecules/EventTimelineItem.d.ts +2 -2
  139. package/dist/ui/components/molecules/EventTimelineItem.js +1 -1
  140. package/dist/ui/components/molecules/index.d.ts +1 -2
  141. package/dist/ui/components/molecules/index.js +1 -2
  142. package/dist/ui/components/organisms/DeadLetterQueueRow.d.ts +3 -3
  143. package/dist/ui/components/organisms/EventRow.d.ts +3 -3
  144. package/dist/ui/components/organisms/EventRow.js +1 -1
  145. package/dist/ui/screens/dead_letter_queue/dead_letter_queue.context.d.ts +5 -5
  146. package/dist/ui/screens/events/events.context.d.ts +4 -4
  147. package/package.json +13 -8
  148. package/dist/domain/event_sourcing/domain_event.js +0 -164
  149. package/dist/domain/event_sourcing/event.d.ts +0 -25
  150. package/dist/domain/event_sourcing/event.js +0 -25
  151. package/dist/domain/event_sourcing/index.d.ts +0 -2
  152. package/dist/domain/event_sourcing/index.js +0 -2
  153. package/dist/domain/events.d.ts +0 -33
  154. package/dist/infrastructure/ui/ToastHost.d.ts +0 -6
  155. package/dist/infrastructure/ui/ToastHost.js +0 -28
  156. package/dist/infrastructure/ui/events.d.ts +0 -1
  157. package/dist/infrastructure/ui/events.js +0 -1
  158. package/dist/ui/components/molecules/ProcessToast.d.ts +0 -8
  159. package/dist/ui/components/molecules/ProcessToast.js +0 -52
  160. /package/dist/domain/{repositories.d.ts → repositories/catalog.d.ts} +0 -0
  161. /package/dist/domain/{repositories.js → repositories/catalog.js} +0 -0
  162. /package/dist/domain/{database → repositories}/repository.js +0 -0
  163. /package/dist/{domain/event_sourcing/event_handler.js → event_driven/subscriber.js} +0 -0
  164. /package/dist/ui/{components/molecules → activity}/ProcessToastProvider.d.ts +0 -0
  165. /package/dist/ui/{components/molecules → activity}/ProcessToastProvider.js +0 -0
@@ -1,5 +1,5 @@
1
- import { DomainEvent } from "../../domain/event_sourcing";
2
- export declare class WebViewMessageReceivedEvent extends DomainEvent {
1
+ import { IntegrationEvent } from "../../domain/events/integration_event";
2
+ export declare class WebViewMessageReceivedEvent extends IntegrationEvent {
3
3
  static readonly name = "common.webview.v1.message_received";
4
4
  static readonly label = "Procesando datos de la web";
5
5
  static readonly requiresNetwork = false;
@@ -1,5 +1,5 @@
1
- import { DomainEvent } from "../../domain/event_sourcing";
2
- export class WebViewMessageReceivedEvent extends DomainEvent {
1
+ import { IntegrationEvent } from "../../domain/events/integration_event";
2
+ export class WebViewMessageReceivedEvent extends IntegrationEvent {
3
3
  static name = "common.webview.v1.message_received";
4
4
  static label = "Procesando datos de la web";
5
5
  static requiresNetwork = false;
@@ -155,16 +155,16 @@ async function createSpansQueueTable() {
155
155
  ON ${DATABASE_TABLES.SPANS_QUEUE}(trace_id);
156
156
  `);
157
157
  }
158
+ function migration(name, migrationFn) {
159
+ return { name, tables: [name], migrationFn };
160
+ }
158
161
  const MIGRATIONS = [
159
- { name: DATABASE_TABLES.SETTINGS, migrationFn: createSettingsTable },
160
- { name: DATABASE_TABLES.EVENT_QUEUE, migrationFn: createEventQueueTable },
161
- { name: DATABASE_TABLES.DEAD_LETTER_QUEUE, migrationFn: createDeadLetterQueueTable },
162
- { name: DATABASE_TABLES.DOMAIN_EVENTS, migrationFn: createDomainEventsTable },
163
- {
164
- name: DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER,
165
- migrationFn: createDomainEventsDeadLetterTable,
166
- },
167
- { name: DATABASE_TABLES.TELEMETRY_QUEUE, migrationFn: createTelemetryQueueTable },
168
- { name: DATABASE_TABLES.SPANS_QUEUE, migrationFn: createSpansQueueTable },
162
+ migration(DATABASE_TABLES.SETTINGS, createSettingsTable),
163
+ migration(DATABASE_TABLES.EVENT_QUEUE, createEventQueueTable),
164
+ migration(DATABASE_TABLES.DEAD_LETTER_QUEUE, createDeadLetterQueueTable),
165
+ migration(DATABASE_TABLES.DOMAIN_EVENTS, createDomainEventsTable),
166
+ migration(DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER, createDomainEventsDeadLetterTable),
167
+ migration(DATABASE_TABLES.TELEMETRY_QUEUE, createTelemetryQueueTable),
168
+ migration(DATABASE_TABLES.SPANS_QUEUE, createSpansQueueTable),
169
169
  ];
170
170
  export default MIGRATIONS;
@@ -1,4 +1,4 @@
1
- import type { Subscriber } from "../../domain/event_sourcing";
1
+ import type { Subscriber } from "../../event_driven";
2
2
  export interface SyncResult {
3
3
  changed: boolean;
4
4
  added: number;
@@ -1,5 +1,5 @@
1
- import { DomainEvent, Subscriber } from "../../domain/event_sourcing";
2
- import { ActivateDomainEvent, DeactivateDomainEvent } from "../../domain/events";
1
+ import { ActivateDomainEvent, DeactivateDomainEvent, DomainEvent } from "../../domain/events";
2
+ import { Subscriber } from "../../event_driven";
3
3
  export declare class ActivateDomainSubscriber extends Subscriber {
4
4
  readonly requiresAuth = false;
5
5
  listen: (typeof ActivateDomainEvent | typeof DeactivateDomainEvent)[];
@@ -1,5 +1,5 @@
1
- import { Subscriber } from "../../domain/event_sourcing";
2
- import { ActivateDomainEvent, DeactivateDomainEvent } from "../../domain/events";
1
+ import { ActivateDomainEvent, DeactivateDomainEvent, } from "../../domain/events";
2
+ import { Subscriber } from "../../event_driven";
3
3
  import { orchestrator } from "../../framework/orchestrator";
4
4
  export class ActivateDomainSubscriber extends Subscriber {
5
5
  requiresAuth = false;
@@ -1,5 +1,5 @@
1
- import { DomainEvent, Subscriber } from "../../domain/event_sourcing";
2
- import { NewAppSettingsEvent } from "../../domain/events";
1
+ import { DomainEvent, NewAppSettingsEvent } from "../../domain/events";
2
+ import { Subscriber } from "../../event_driven";
3
3
  export declare class NewAppSettingsSubscriber extends Subscriber {
4
4
  listen: (typeof NewAppSettingsEvent)[];
5
5
  getEvent(event: DomainEvent): NewAppSettingsEvent;
@@ -1,6 +1,6 @@
1
1
  import { SettingsRepository } from "../../adapters/repositories/setting.repository";
2
- import { Subscriber } from "../../domain/event_sourcing";
3
2
  import { NewAppSettingsEvent } from "../../domain/events";
3
+ import { Subscriber } from "../../event_driven";
4
4
  import { loggerUseCases } from "../../infrastructure/logger";
5
5
  export class NewAppSettingsSubscriber extends Subscriber {
6
6
  listen = [NewAppSettingsEvent];
@@ -1,5 +1,5 @@
1
- import { Subscriber } from "../../domain/event_sourcing";
2
1
  import { PrintImageRequestedEvent } from "../../domain/events";
2
+ import { Subscriber } from "../../event_driven";
3
3
  export declare class PrintImageSubscriber extends Subscriber {
4
4
  readonly requiresAuth = false;
5
5
  listen: (typeof PrintImageRequestedEvent)[];
@@ -1,5 +1,5 @@
1
- import { Subscriber } from "../../domain/event_sourcing";
2
1
  import { PrintImageRequestedEvent } from "../../domain/events";
2
+ import { Subscriber } from "../../event_driven";
3
3
  import { DomainValidationError } from "../../exceptions";
4
4
  import { printerService } from "../../services/printer.service";
5
5
  export class PrintImageSubscriber extends Subscriber {
@@ -8,7 +8,7 @@ export class PrintImageSubscriber extends Subscriber {
8
8
  async process(event) {
9
9
  const printed = await printerService.printImageBase64(event.imageBase64);
10
10
  if (!printed) {
11
- throw new DomainValidationError("Print failed — printer not connected or timed out");
11
+ throw new DomainValidationError("No se pudo imprimir");
12
12
  }
13
13
  }
14
14
  }
@@ -1,5 +1,5 @@
1
- import { Subscriber } from "../../domain/event_sourcing";
2
1
  import { WebViewMessageReceivedEvent } from "../../domain/events";
2
+ import { Subscriber } from "../../event_driven";
3
3
  export declare class ProcessWebViewMessageSubscriber extends Subscriber {
4
4
  readonly requiresAuth = false;
5
5
  listen: (typeof WebViewMessageReceivedEvent)[];
@@ -1,5 +1,5 @@
1
- import { Subscriber } from "../../domain/event_sourcing";
2
1
  import { PrintImageRequestedEvent, WebViewMessageReceivedEvent, } from "../../domain/events";
2
+ import { Subscriber } from "../../event_driven";
3
3
  import { DomainValidationError } from "../../exceptions";
4
4
  import { EventBus } from "../../infrastructure/workers";
5
5
  import { webViewService } from "../../services/webview.service";
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import { DefaultTheme as NavDefaultTheme, NavigationContainer, } from "@react-navigation/native";
3
3
  import { createApp } from "../../framework/createApp";
4
- import { ToastHost } from "../../infrastructure/ui/ToastHost";
4
+ import { DeviceNotices } from "../../ui/activity";
5
5
  import { setBranding } from "@sincpro/mobile-ui";
6
6
  import { ToastProvider } from "@sincpro/mobile-ui/Feedback";
7
7
  import { setActiveTheme, themeToVars, useTheme, } from "@sincpro/mobile-ui/theme";
@@ -114,6 +114,6 @@ export function createAppShell(config) {
114
114
  const Splash = config.splashComponent;
115
115
  return Splash ? (_jsx(Splash, {})) : (_jsx(View, { style: { flex: 1, backgroundColor: activeTheme.bg.page } }));
116
116
  }
117
- return (_jsx(GestureHandlerRootView, { style: { flex: 1 }, children: _jsx(View, { className: "flex-1", style: themeStyle, children: _jsx(SafeAreaProvider, { initialMetrics: initialWindowMetrics, children: _jsx(SafeAreaView, { edges: ["top"], style: { flex: 1 }, children: _jsx(KeyboardProvider, { children: _jsx(CommonProvider, { darkTheme: config.darkTheme, lightTheme: config.theme, children: _jsxs(ToastProvider, { position: "top", children: [withProviders(providers, _jsx(DomainSwitcherProvider, { apps: apps, initialDomain: config.activeDomain, children: _jsx(NavigationContainer, { theme: navTheme, children: _jsx(ActiveDomainApp, {}) }) })), _jsx(ToastHost, {})] }) }) }) }) }) }) }));
117
+ return (_jsx(GestureHandlerRootView, { style: { flex: 1 }, children: _jsx(View, { className: "flex-1", style: themeStyle, children: _jsx(SafeAreaProvider, { initialMetrics: initialWindowMetrics, children: _jsx(SafeAreaView, { edges: ["top"], style: { flex: 1 }, children: _jsx(KeyboardProvider, { children: _jsx(CommonProvider, { darkTheme: config.darkTheme, lightTheme: config.theme, children: _jsxs(ToastProvider, { position: "top", children: [withProviders(providers, _jsx(DomainSwitcherProvider, { apps: apps, initialDomain: config.activeDomain, children: _jsx(NavigationContainer, { theme: navTheme, children: _jsx(ActiveDomainApp, {}) }) })), _jsx(DeviceNotices, {})] }) }) }) }) }) }) }));
118
118
  };
119
119
  }
@@ -4,22 +4,10 @@ export interface TimezoneLocale {
4
4
  timezone: string;
5
5
  locale: string;
6
6
  }
7
- interface Activity {
8
- id: string;
9
- label: string;
10
- type: "queue" | "cron";
11
- status: "running" | "failed";
12
- error?: string;
13
- startedAt: number;
14
- }
15
7
  interface ICommonContext {
16
8
  debugMode: boolean;
17
9
  debugModeLoaded: boolean;
18
10
  toggleDebugMode: () => Promise<void>;
19
- currentActivity?: Activity;
20
- lastError?: Activity;
21
- isProcessing: boolean;
22
- dismiss: () => void;
23
11
  hasGeoPermission: boolean;
24
12
  geoIsLoading: boolean;
25
13
  geoError: string | null;
@@ -1,33 +1,24 @@
1
1
  import { jsx as _jsx } from "nativewind/jsx-runtime";
2
2
  import { GeoAdapter } from "../../adapters/Geo.adapter";
3
3
  import { SettingsRepository } from "../../adapters/repositories/setting.repository";
4
- import { QueueEndEvent, QueueStartEvent } from "../../domain/events";
5
- import logger, { loggerUseCases } from "../../infrastructure/logger";
6
- import { UIEventBus } from "../../infrastructure/ui/UIEventBus";
4
+ import logger from "../../infrastructure/logger";
7
5
  import { DEFAULT_DARK_THEME, DEFAULT_THEME, setActiveTheme } from "@sincpro/mobile-ui/theme";
8
- import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
6
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react";
9
7
  import { Appearance, AppState } from "react-native";
10
8
  const DEBUG_MODE_SETTING_KEY = "app.debug_mode";
11
9
  const TIMEZONE_SETTING_KEY = "app.timezone";
12
10
  const LOCALE_SETTING_KEY = "app.locale";
13
11
  const COLOR_SCHEME_SETTING_KEY = "app.color_scheme";
14
- const QUEUE_MIN_VISIBLE_MS = 1200;
15
12
  const CommonContext = createContext(null);
16
13
  export function CommonProvider({ children, lightTheme, darkTheme }) {
17
14
  const [debugMode, setDebugMode] = useState(false);
18
15
  const [debugModeLoaded, setDebugModeLoaded] = useState(false);
19
- const [currentActivity, setCurrentActivity] = useState(undefined);
20
- const [cronTasks, setCronTasks] = useState(new Map());
21
- const [lastError, setLastError] = useState(undefined);
22
- const [dismissed, setDismissed] = useState(false);
23
16
  const [hasGeoPermission, setHasGeoPermission] = useState(false);
24
17
  const [geoIsLoading, setGeoIsLoading] = useState(false);
25
18
  const [geoError, setGeoError] = useState(null);
26
19
  const [locale, setLocale] = useState(null);
27
20
  const [timezone, setTimezone] = useState(null);
28
21
  const [colorScheme, setColorSchemeState] = useState("system");
29
- const errorTimeoutRef = useRef(null);
30
- const queueClearTimeoutRef = useRef(null);
31
22
  useEffect(() => {
32
23
  async function loadDebugMode() {
33
24
  const value = await SettingsRepository.getSettingByName(DEBUG_MODE_SETTING_KEY);
@@ -41,14 +32,6 @@ export function CommonProvider({ children, lightTheme, darkTheme }) {
41
32
  await SettingsRepository.saveOneSetting(DEBUG_MODE_SETTING_KEY, newValue);
42
33
  setDebugMode(newValue);
43
34
  }, [debugMode]);
44
- const dismiss = useCallback(() => {
45
- setDismissed(true);
46
- setLastError(undefined);
47
- if (errorTimeoutRef.current) {
48
- clearTimeout(errorTimeoutRef.current);
49
- errorTimeoutRef.current = null;
50
- }
51
- }, []);
52
35
  const checkGeoPermission = useCallback(async () => {
53
36
  setGeoIsLoading(true);
54
37
  setGeoError(null);
@@ -144,118 +127,10 @@ export function CommonProvider({ children, lightTheme, darkTheme }) {
144
127
  }
145
128
  loadTimezoneAndLocale();
146
129
  }, []);
147
- useEffect(() => {
148
- loggerUseCases.debug("[CommonProvider] Setting up UIEventBus listeners");
149
- function handleQueueStart(payload) {
150
- if (!payload || typeof payload !== "object")
151
- return;
152
- const { event, label } = payload;
153
- if (!label)
154
- return;
155
- setCurrentActivity({
156
- id: event,
157
- label,
158
- type: "queue",
159
- status: "running",
160
- startedAt: Date.now(),
161
- });
162
- setDismissed(false);
163
- }
164
- function handleQueueEnd(payload) {
165
- if (!payload || typeof payload !== "object")
166
- return;
167
- const { event, success = true, error } = payload;
168
- setCurrentActivity((current) => {
169
- if (current?.id !== event)
170
- return current;
171
- if (!success) {
172
- const failedActivity = { ...current, status: "failed", error };
173
- setLastError(failedActivity);
174
- if (errorTimeoutRef.current)
175
- clearTimeout(errorTimeoutRef.current);
176
- errorTimeoutRef.current = setTimeout(() => {
177
- setLastError(undefined);
178
- errorTimeoutRef.current = null;
179
- }, 10000);
180
- return undefined;
181
- }
182
- // Éxito: mantener visible un mínimo para que la cola sea perceptible
183
- // aunque el evento se procese al instante.
184
- const remaining = QUEUE_MIN_VISIBLE_MS - (Date.now() - current.startedAt);
185
- if (remaining > 0) {
186
- if (queueClearTimeoutRef.current)
187
- clearTimeout(queueClearTimeoutRef.current);
188
- queueClearTimeoutRef.current = setTimeout(() => {
189
- setCurrentActivity((c) => (c?.id === event ? undefined : c));
190
- queueClearTimeoutRef.current = null;
191
- }, remaining);
192
- return current;
193
- }
194
- return undefined;
195
- });
196
- }
197
- function handleCronStart(payload) {
198
- if (!payload || typeof payload !== "object" || !("task" in payload))
199
- return;
200
- const { task, label } = payload;
201
- setCronTasks((prev) => {
202
- const next = new Map(prev);
203
- next.set(task, {
204
- id: task,
205
- label: label ?? task,
206
- type: "cron",
207
- status: "running",
208
- startedAt: Date.now(),
209
- });
210
- return next;
211
- });
212
- }
213
- function handleCronEnd(payload) {
214
- if (!payload || typeof payload !== "object" || !("task" in payload))
215
- return;
216
- const { task } = payload;
217
- setCronTasks((prev) => {
218
- const next = new Map(prev);
219
- next.delete(task);
220
- return next;
221
- });
222
- }
223
- const unsubscribers = [
224
- UIEventBus.on(QueueStartEvent.name, handleQueueStart),
225
- UIEventBus.on(QueueEndEvent.name, handleQueueEnd),
226
- UIEventBus.on("CRON_START", handleCronStart),
227
- UIEventBus.on("CRON_END", handleCronEnd),
228
- ];
229
- return () => {
230
- loggerUseCases.debug("[CommonProvider] Cleaning up UIEventBus listeners");
231
- unsubscribers.forEach((unsub) => unsub());
232
- if (errorTimeoutRef.current)
233
- clearTimeout(errorTimeoutRef.current);
234
- if (queueClearTimeoutRef.current)
235
- clearTimeout(queueClearTimeoutRef.current);
236
- };
237
- }, []);
238
- const displayActivity = useMemo(() => {
239
- if (currentActivity)
240
- return currentActivity;
241
- const firstCron = cronTasks.values().next().value;
242
- if (firstCron)
243
- return firstCron;
244
- return undefined;
245
- }, [currentActivity, cronTasks]);
246
- const isProcessing = useMemo(() => {
247
- if (dismissed)
248
- return false;
249
- return !!(currentActivity || cronTasks.size > 0 || lastError);
250
- }, [currentActivity, cronTasks, lastError, dismissed]);
251
130
  const value = useMemo(() => ({
252
131
  debugMode,
253
132
  debugModeLoaded,
254
133
  toggleDebugMode,
255
- currentActivity: displayActivity,
256
- lastError,
257
- isProcessing,
258
- dismiss,
259
134
  hasGeoPermission,
260
135
  geoIsLoading,
261
136
  geoError,
@@ -270,10 +145,6 @@ export function CommonProvider({ children, lightTheme, darkTheme }) {
270
145
  debugMode,
271
146
  debugModeLoaded,
272
147
  toggleDebugMode,
273
- displayActivity,
274
- lastError,
275
- isProcessing,
276
- dismiss,
277
148
  hasGeoPermission,
278
149
  geoIsLoading,
279
150
  geoError,
@@ -0,0 +1 @@
1
+ export { Subscriber } from "../event_driven/subscriber";
@@ -0,0 +1 @@
1
+ export { Subscriber } from "../event_driven/subscriber";
@@ -1,4 +1,4 @@
1
- import { DomainEvent } from "../../domain/event_sourcing/domain_event";
1
+ import { DomainEvent } from "../domain/events/domain_event";
2
2
  export declare abstract class Subscriber {
3
3
  abstract listen: (typeof DomainEvent)[];
4
4
  readonly requiresAuth: boolean;
@@ -16,6 +16,27 @@ export declare class DomainBackendError extends DomainException {
16
16
  export declare class DomainAppError extends DomainException {
17
17
  constructor(message: string);
18
18
  }
19
+ export declare class StaleAggregate extends DomainException {
20
+ constructor(message: string);
21
+ }
22
+ export declare class ChecksFailed extends DomainValidationError {
23
+ readonly failures: {
24
+ rule: string;
25
+ message: string;
26
+ path: string[];
27
+ }[];
28
+ constructor(failures: {
29
+ rule: string;
30
+ message: string;
31
+ path: string[];
32
+ }[]);
33
+ }
34
+ export declare class RepeatableFailure extends DomainException {
35
+ constructor(message: string);
36
+ }
37
+ export declare class RejectedFailure extends DomainException {
38
+ constructor(message: string);
39
+ }
19
40
  export declare const ValidationError: (message: string) => never;
20
41
  export declare const UserError: (message: string) => never;
21
42
  export declare const BackendError: (message: string) => never;
@@ -34,6 +34,36 @@ export class DomainAppError extends DomainException {
34
34
  this.name = "AppError";
35
35
  }
36
36
  }
37
+ export class StaleAggregate extends DomainException {
38
+ constructor(message) {
39
+ super(message);
40
+ this.name = "StaleAggregate";
41
+ }
42
+ }
43
+ export class ChecksFailed extends DomainValidationError {
44
+ failures;
45
+ constructor(failures) {
46
+ super(failures
47
+ .map((failure) => failure.path.length > 0
48
+ ? `${failure.path.join(".")}: ${failure.message}`
49
+ : failure.message)
50
+ .join("; "));
51
+ this.failures = failures;
52
+ this.name = "ChecksFailed";
53
+ }
54
+ }
55
+ export class RepeatableFailure extends DomainException {
56
+ constructor(message) {
57
+ super(message);
58
+ this.name = "RepeatableFailure";
59
+ }
60
+ }
61
+ export class RejectedFailure extends DomainException {
62
+ constructor(message) {
63
+ super(message);
64
+ this.name = "RejectedFailure";
65
+ }
66
+ }
37
67
  export const ValidationError = (message) => {
38
68
  // setTimeout(() => {
39
69
  // throw new DomainValidationError(message);
@@ -1,5 +1,5 @@
1
1
  import type { IMigration } from "../domain/database";
2
- import type { Subscriber } from "../domain/event_sourcing";
2
+ import type { Subscriber } from "../event_driven";
3
3
  import type { CronWorker } from "../infrastructure/workers";
4
4
  import { DomainModule } from "./domain_module";
5
5
  export declare class BaseModule extends DomainModule {
@@ -10,10 +10,10 @@ export interface AppConfig {
10
10
  */
11
11
  telemetry?: TelemetryConfig;
12
12
  /**
13
- * Override display options for any cron by taskName.
14
- * Useful for showing/hiding toasts on internal crons (CHECK_NETWORK, TELEMETRY_FLUSH, etc.).
13
+ * Override options for any cron by taskName.
14
+ * `ui` marks the cron for the activity toast. Silence is the default.
15
15
  * @example
16
- * crons: { CHECK_NETWORK: { showToast: true, label: "Verificando conexión" } }
16
+ * crons: { CHECK_NETWORK: { ui: true, label: "Verificando conexión" } }
17
17
  */
18
18
  crons?: Record<string, Partial<CronWorkerOpts>>;
19
19
  }
@@ -1,5 +1,5 @@
1
1
  import type { IMigration } from "../domain/database";
2
- import type { Subscriber } from "../domain/event_sourcing";
2
+ import type { Subscriber } from "../event_driven";
3
3
  import type { CronWorker } from "../infrastructure/workers";
4
4
  export declare abstract class DomainModule {
5
5
  abstract readonly key: string;
@@ -1,5 +1,5 @@
1
1
  import type { IMigration } from "../domain/database";
2
- import type { Subscriber } from "../domain/event_sourcing";
2
+ import type { Subscriber } from "../event_driven";
3
3
  import type { CronWorker, CronWorkerOpts } from "../infrastructure/workers";
4
4
  import type { DomainModule } from "./domain_module";
5
5
  export declare class Kernel {
@@ -1,6 +1,8 @@
1
+ import { applyMigrationStep, migrationsNewerThanApp, pendingMigrations, resetPlan, } from "../domain/database/migration_plan";
1
2
  import { repos } from "../entrypoints/db/repositories";
2
3
  import { DBCursor } from "../infrastructure/database";
3
4
  import { initializeRepositoryFacade } from "../infrastructure/database/mapped";
5
+ import { appliedMigrationNames, forgetMigrations, markMigrationApplied, } from "../infrastructure/database/schema";
4
6
  import logger, { loggerRepositories } from "../infrastructure/logger";
5
7
  export class Kernel {
6
8
  modules;
@@ -45,9 +47,16 @@ export class Kernel {
45
47
  }
46
48
  }
47
49
  async runMigrations() {
48
- for (const { name, migrationFn } of this.migrations()) {
49
- logger.info(`Running migration: ${name}`);
50
- await migrationFn();
50
+ const applied = await appliedMigrationNames();
51
+ const migrations = this.migrations();
52
+ const newer = migrationsNewerThanApp(applied, migrations.map((migration) => migration.name));
53
+ if (newer.length > 0) {
54
+ throw new Error(`the database is newer than this app: ${newer.join(", ")}`);
55
+ }
56
+ for (const migration of pendingMigrations(migrations, applied)) {
57
+ logger.info(`Running migration: ${migration.name}`);
58
+ await applyMigrationStep(migration, (work) => DBCursor.transaction(work), markMigrationApplied);
59
+ applied.add(migration.name);
51
60
  }
52
61
  }
53
62
  async bootstrap() {
@@ -57,12 +66,12 @@ export class Kernel {
57
66
  }
58
67
  async restartDatabase() {
59
68
  loggerRepositories.warn("Restarting database");
60
- const persisted = this.persistedTables();
61
- const tablesToDrop = this.migrations().filter((migration) => !persisted.includes(migration.name));
62
- for (const { name } of tablesToDrop) {
63
- loggerRepositories.info(`Dropping table: ${name}`);
64
- await DBCursor.execAsync(`DROP TABLE IF EXISTS ${name}`);
69
+ const plan = resetPlan(this.migrations(), this.persistedTables());
70
+ for (const table of plan.drop) {
71
+ loggerRepositories.info(`Dropping table: ${table}`);
72
+ await DBCursor.execAsync(`DROP TABLE IF EXISTS ${table}`);
65
73
  }
74
+ await forgetMigrations(plan.forget);
66
75
  await this.runMigrations();
67
76
  }
68
77
  }
@@ -3,6 +3,8 @@ export declare const DB_NAME = "distribution.db";
3
3
  export declare function getDatabase(): Promise<SQLiteDatabase>;
4
4
  export declare function getDatabaseSync(): SQLiteDatabase;
5
5
  export declare const DBCursor: {
6
+ inTransaction(): boolean;
7
+ transaction<T>(work: () => Promise<T>): Promise<T>;
6
8
  mutateDatabase(query: string, ...params: SQLiteVariadicBindParams): Promise<SQLiteRunResult>;
7
9
  getFirstAsync<T>(query: string, ...params: SQLiteVariadicBindParams): Promise<T | null>;
8
10
  getAllAsync<T>(query: string, ...params: SQLiteVariadicBindParams): Promise<T[]>;
@@ -3,6 +3,7 @@ import { Mutex } from "async-mutex";
3
3
  import { openDatabaseAsync, openDatabaseSync, } from "expo-sqlite";
4
4
  export const DB_NAME = "distribution.db";
5
5
  let dbInstance = null;
6
+ let currentTx = null;
6
7
  const dbLock = new Mutex();
7
8
  // Telemetry/queue tables are excluded from query logging: logging their
8
9
  // mutations would feed the log pipeline its own writes — and a failing
@@ -45,12 +46,33 @@ export function getDatabaseSync() {
45
46
  return dbInstance;
46
47
  }
47
48
  async function withDB(fn) {
49
+ if (currentTx)
50
+ return fn(currentTx);
48
51
  return dbLock.runExclusive(async () => fn(await getDatabase()));
49
52
  }
50
53
  function withDBSync(fn) {
51
54
  return fn(getDatabaseSync());
52
55
  }
53
56
  export const DBCursor = {
57
+ inTransaction() {
58
+ return currentTx !== null;
59
+ },
60
+ async transaction(work) {
61
+ return dbLock.runExclusive(async () => {
62
+ const db = await getDatabase();
63
+ let result;
64
+ await db.withTransactionAsync(async () => {
65
+ currentTx = db;
66
+ try {
67
+ result = await work();
68
+ }
69
+ finally {
70
+ currentTx = null;
71
+ }
72
+ });
73
+ return result;
74
+ });
75
+ },
54
76
  async mutateDatabase(query, ...params) {
55
77
  const safeQuery = `${query.trim()};`;
56
78
  return withDB(async (db) => {
@@ -1,2 +1,3 @@
1
1
  export { DB_NAME, DBCursor } from "./connector";
2
2
  export { mapped, resolveEntity } from "./mapped";
3
+ export { addColumnIfMissing, addColumnIfMissingSync } from "./schema";
@@ -1,2 +1,3 @@
1
1
  export { DB_NAME, DBCursor } from "./connector";
2
2
  export { mapped, resolveEntity } from "./mapped";
3
+ export { addColumnIfMissing, addColumnIfMissingSync } from "./schema";
@@ -0,0 +1,9 @@
1
+ export declare function assertSqlIdentifier(name: string): void;
2
+ export declare function addColumnStatement(table: string, column: string, definition: string): string;
3
+ export declare function tableColumns(table: string): Promise<string[]>;
4
+ export declare function tableColumnsSync(table: string): string[];
5
+ export declare function addColumnIfMissing(table: string, column: string, definition: string): Promise<void>;
6
+ export declare function addColumnIfMissingSync(table: string, column: string, definition: string): void;
7
+ export declare function appliedMigrationNames(): Promise<Set<string>>;
8
+ export declare function markMigrationApplied(name: string): Promise<void>;
9
+ export declare function forgetMigrations(names: readonly string[]): Promise<void>;