@sincpro/mobile 0.4.1 → 0.4.3

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.
@@ -0,0 +1,4 @@
1
+ export declare enum EBuiltinCron {
2
+ CHECK_NETWORK = "CHECK_NETWORK",
3
+ TELEMETRY_FLUSH = "TELEMETRY_FLUSH"
4
+ }
@@ -0,0 +1,5 @@
1
+ export var EBuiltinCron;
2
+ (function (EBuiltinCron) {
3
+ EBuiltinCron["CHECK_NETWORK"] = "CHECK_NETWORK";
4
+ EBuiltinCron["TELEMETRY_FLUSH"] = "TELEMETRY_FLUSH";
5
+ })(EBuiltinCron || (EBuiltinCron = {}));
@@ -1,3 +1,4 @@
1
+ import { EBuiltinCron } from "../../entrypoints/cron/EBuiltinCron";
1
2
  import { loggerCronJobs } from "../../infrastructure/logger";
2
3
  import { CronWorker } from "../../infrastructure/workers";
3
4
  import { networkUseCases } from "../../services/network.service";
@@ -6,5 +7,5 @@ async function checkNetworkStatus() {
6
7
  await networkUseCases.getNetworkStatus();
7
8
  loggerCronJobs.info("Finished checking network status");
8
9
  }
9
- const cronCheckNetworkStatus = new CronWorker("CHECK_NETWORK", checkNetworkStatus, 2.5);
10
+ const cronCheckNetworkStatus = new CronWorker(EBuiltinCron.CHECK_NETWORK, checkNetworkStatus, 2.5);
10
11
  export default cronCheckNetworkStatus;
@@ -155,23 +155,6 @@ async function createSpansQueueTable() {
155
155
  ON ${DATABASE_TABLES.SPANS_QUEUE}(trace_id);
156
156
  `);
157
157
  }
158
- // Adds events and links columns to spans_queue for existing installs.
159
- // Fresh installs get them from createSpansQueueTable above; ALTER TABLE is a
160
- // no-op on those (caught and ignored — duplicate column error is expected).
161
- async function addSpansQueueEventsLinks() {
162
- try {
163
- await DBCursor.execAsync(`ALTER TABLE ${DATABASE_TABLES.SPANS_QUEUE} ADD COLUMN events TEXT NOT NULL DEFAULT '[]'`);
164
- }
165
- catch {
166
- // column already exists on fresh installs
167
- }
168
- try {
169
- await DBCursor.execAsync(`ALTER TABLE ${DATABASE_TABLES.SPANS_QUEUE} ADD COLUMN links TEXT NOT NULL DEFAULT '[]'`);
170
- }
171
- catch {
172
- // column already exists on fresh installs
173
- }
174
- }
175
158
  const MIGRATIONS = [
176
159
  { name: DATABASE_TABLES.SETTINGS, migrationFn: createSettingsTable },
177
160
  { name: DATABASE_TABLES.EVENT_QUEUE, migrationFn: createEventQueueTable },
@@ -183,6 +166,5 @@ const MIGRATIONS = [
183
166
  },
184
167
  { name: DATABASE_TABLES.TELEMETRY_QUEUE, migrationFn: createTelemetryQueueTable },
185
168
  { name: DATABASE_TABLES.SPANS_QUEUE, migrationFn: createSpansQueueTable },
186
- { name: "spans_queue_v2_events_links", migrationFn: addSpansQueueEventsLinks },
187
169
  ];
188
170
  export default MIGRATIONS;
@@ -25,6 +25,7 @@ interface ICommonContext {
25
25
  geoError: string | null;
26
26
  checkGeoPermission: () => Promise<void>;
27
27
  requestGeoPermission: () => Promise<boolean>;
28
+ locale: string | null;
28
29
  timezone: string | null;
29
30
  updateTimezone: (tz: TimezoneLocale) => Promise<void>;
30
31
  colorScheme: "light" | "dark" | "system";
@@ -23,6 +23,7 @@ export function CommonProvider({ children, lightTheme, darkTheme }) {
23
23
  const [hasGeoPermission, setHasGeoPermission] = useState(false);
24
24
  const [geoIsLoading, setGeoIsLoading] = useState(false);
25
25
  const [geoError, setGeoError] = useState(null);
26
+ const [locale, setLocale] = useState(null);
26
27
  const [timezone, setTimezone] = useState(null);
27
28
  const [colorScheme, setColorSchemeState] = useState("system");
28
29
  const errorTimeoutRef = useRef(null);
@@ -85,6 +86,7 @@ export function CommonProvider({ children, lightTheme, darkTheme }) {
85
86
  await SettingsRepository.saveOneSetting(TIMEZONE_SETTING_KEY, tz.timezone);
86
87
  await SettingsRepository.saveOneSetting(LOCALE_SETTING_KEY, tz.locale);
87
88
  setTimezone(tz.timezone);
89
+ setLocale(tz.locale);
88
90
  }
89
91
  catch (error) {
90
92
  logger.warn(error);
@@ -127,16 +129,20 @@ export function CommonProvider({ children, lightTheme, darkTheme }) {
127
129
  return () => sub.remove();
128
130
  }, [colorScheme, applyTheme]);
129
131
  useEffect(() => {
130
- async function loadTimezone() {
132
+ async function loadTimezoneAndLocale() {
131
133
  try {
132
- const timezoneResult = await SettingsRepository.getSettingByName(TIMEZONE_SETTING_KEY);
134
+ const [timezoneResult, localeResult] = await Promise.all([
135
+ SettingsRepository.getSettingByName(TIMEZONE_SETTING_KEY),
136
+ SettingsRepository.getSettingByName(LOCALE_SETTING_KEY),
137
+ ]);
133
138
  setTimezone(timezoneResult);
139
+ setLocale(localeResult);
134
140
  }
135
141
  catch (error) {
136
- logger.warn("Failed to load timezone", error);
142
+ logger.warn("Failed to load timezone/locale", error);
137
143
  }
138
144
  }
139
- loadTimezone();
145
+ loadTimezoneAndLocale();
140
146
  }, []);
141
147
  useEffect(() => {
142
148
  loggerUseCases.debug("[CommonProvider] Setting up UIEventBus listeners");
@@ -191,12 +197,12 @@ export function CommonProvider({ children, lightTheme, darkTheme }) {
191
197
  function handleCronStart(payload) {
192
198
  if (!payload || typeof payload !== "object" || !("task" in payload))
193
199
  return;
194
- const { task } = payload;
200
+ const { task, label } = payload;
195
201
  setCronTasks((prev) => {
196
202
  const next = new Map(prev);
197
203
  next.set(task, {
198
204
  id: task,
199
- label: task,
205
+ label: label ?? task,
200
206
  type: "cron",
201
207
  status: "running",
202
208
  startedAt: Date.now(),
@@ -255,6 +261,7 @@ export function CommonProvider({ children, lightTheme, darkTheme }) {
255
261
  geoError,
256
262
  checkGeoPermission,
257
263
  requestGeoPermission,
264
+ locale,
258
265
  timezone,
259
266
  updateTimezone,
260
267
  colorScheme,
@@ -272,6 +279,7 @@ export function CommonProvider({ children, lightTheme, darkTheme }) {
272
279
  geoError,
273
280
  checkGeoPermission,
274
281
  requestGeoPermission,
282
+ locale,
275
283
  timezone,
276
284
  updateTimezone,
277
285
  colorScheme,
@@ -1,4 +1,5 @@
1
1
  import type { TelemetryConfig } from "../infrastructure/telemetry";
2
+ import type { CronWorkerOpts } from "../infrastructure/workers";
2
3
  import type { DomainModule } from "./domain_module";
3
4
  import { Kernel } from "./kernel";
4
5
  export interface AppConfig {
@@ -8,5 +9,12 @@ export interface AppConfig {
8
9
  * after migrations complete — so the `telemetry_queue` table is guaranteed to exist.
9
10
  */
10
11
  telemetry?: TelemetryConfig;
12
+ /**
13
+ * Override display options for any cron by taskName.
14
+ * Useful for showing/hiding toasts on internal crons (CHECK_NETWORK, TELEMETRY_FLUSH, etc.).
15
+ * @example
16
+ * crons: { CHECK_NETWORK: { showToast: true, label: "Verificando conexión" } }
17
+ */
18
+ crons?: Record<string, Partial<CronWorkerOpts>>;
11
19
  }
12
20
  export declare function createApp(config?: AppConfig): Promise<Kernel>;
@@ -4,8 +4,10 @@ import { Kernel } from "./kernel";
4
4
  import { orchestrator } from "./orchestrator";
5
5
  export async function createApp(config = {}) {
6
6
  const kernel = new Kernel([baseModule, ...(config.domains ?? [])]);
7
+ if (config.crons)
8
+ kernel.applyCronConfig(config.crons);
7
9
  orchestrator.configure(kernel);
8
- await orchestrator.bootstrap(); // migrations run here — telemetry_queue is ready
10
+ await orchestrator.bootstrap();
9
11
  if (config.telemetry) {
10
12
  await initTelemetry(config.telemetry);
11
13
  }
@@ -1,6 +1,6 @@
1
1
  import type { IMigration } from "../domain/database";
2
2
  import type { Subscriber } from "../domain/event_sourcing";
3
- import type { CronWorker } from "../infrastructure/workers";
3
+ import type { CronWorker, CronWorkerOpts } from "../infrastructure/workers";
4
4
  import type { DomainModule } from "./domain_module";
5
5
  export declare class Kernel {
6
6
  private readonly modules;
@@ -12,6 +12,7 @@ export declare class Kernel {
12
12
  persistedTables(): string[];
13
13
  subscribersByKey(): Record<string, Subscriber[]>;
14
14
  cronsByKey(): Record<string, CronWorker[]>;
15
+ applyCronConfig(config: Record<string, Partial<CronWorkerOpts>>): void;
15
16
  runMigrations(): Promise<void>;
16
17
  bootstrap(): Promise<void>;
17
18
  restartDatabase(): Promise<void>;
@@ -36,6 +36,14 @@ export class Kernel {
36
36
  }
37
37
  return map;
38
38
  }
39
+ applyCronConfig(config) {
40
+ for (const crons of Object.values(this.cronsByKey())) {
41
+ for (const cron of crons) {
42
+ if (config[cron.taskName])
43
+ cron.configure(config[cron.taskName]);
44
+ }
45
+ }
46
+ }
39
47
  async runMigrations() {
40
48
  for (const { name, migrationFn } of this.migrations()) {
41
49
  logger.info(`Running migration: ${name}`);
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { EBuiltinCron } from "./entrypoints/cron/EBuiltinCron";
1
2
  export type { AppShellConfig } from "./entrypoints/ui/AppShell";
2
3
  export { createAppShell } from "./entrypoints/ui/AppShell";
3
4
  export type { TimezoneLocale } from "./entrypoints/ui/common_provider";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { EBuiltinCron } from "./entrypoints/cron/EBuiltinCron";
1
2
  export { createAppShell } from "./entrypoints/ui/AppShell";
2
3
  export { CommonProvider, useCommon } from "./entrypoints/ui/common_provider";
3
4
  export { ActiveDomainApp, DomainSwitcherProvider, useDomainSwitcher, } from "./entrypoints/ui/domain_switcher";
@@ -1,6 +1,7 @@
1
1
  import { InternetIsDownEvent, InternetIsUpEvent, } from "../../domain/connectivity/events";
2
+ import { EBuiltinCron } from "../../entrypoints/cron/EBuiltinCron";
2
3
  import { UIEventBus } from "../../infrastructure/ui/UIEventBus";
3
- import { CronWorker } from "../../infrastructure/workers/CronWorker";
4
+ import { CronWorker } from "../../infrastructure/workers";
4
5
  /** Default background-flush interval (minutes). ≥15 → real OS background task. */
5
6
  export const DEFAULT_BACKGROUND_INTERVAL_MIN = 15;
6
7
  /**
@@ -49,7 +50,7 @@ export class TelemetryFlushWorker {
49
50
  // offline backlog drains promptly when the OS runs the task.
50
51
  const intervalMin = this.opts.backgroundIntervalMin ?? DEFAULT_BACKGROUND_INTERVAL_MIN;
51
52
  if (intervalMin > 0) {
52
- this.cron = new CronWorker("TELEMETRY_FLUSH", () => this.flush.run({ resetBackoff: true }), intervalMin);
53
+ this.cron = new CronWorker(EBuiltinCron.TELEMETRY_FLUSH, () => this.flush.run({ resetBackoff: true }), intervalMin);
53
54
  this.cron.start().catch(() => { });
54
55
  }
55
56
  }
@@ -1,14 +1,22 @@
1
1
  export type CronJob = () => Promise<void>;
2
+ export interface CronWorkerOpts {
3
+ requiresAuth?: boolean;
4
+ label?: string;
5
+ showToast?: boolean;
6
+ }
2
7
  export declare class CronWorker {
3
8
  readonly taskName: string;
4
9
  private readonly job;
5
10
  private readonly intervalMin;
6
- readonly requiresAuth: boolean;
7
11
  private stopFn?;
8
12
  private isRegistering;
9
13
  private isExecuting;
10
14
  private registrationError?;
11
- constructor(taskName: string, job: CronJob, intervalMin?: number, requiresAuth?: boolean);
15
+ readonly requiresAuth: boolean;
16
+ label?: string;
17
+ private showToast;
18
+ constructor(taskName: string, job: CronJob, intervalMin?: number, opts?: CronWorkerOpts);
19
+ configure(opts: Partial<CronWorkerOpts>): void;
12
20
  /**
13
21
  * Registers a task with a specified interval and execution logic.
14
22
  * If the interval is less than 15 minutes, a repeating job is created using a defined stop function.
@@ -6,16 +6,26 @@ export class CronWorker {
6
6
  taskName;
7
7
  job;
8
8
  intervalMin;
9
- requiresAuth;
10
9
  stopFn;
11
10
  isRegistering = false;
12
11
  isExecuting = false;
13
12
  registrationError;
14
- constructor(taskName, job, intervalMin = 15, requiresAuth = false) {
13
+ requiresAuth;
14
+ label;
15
+ showToast;
16
+ constructor(taskName, job, intervalMin = 15, opts = {}) {
15
17
  this.taskName = taskName;
16
18
  this.job = job;
17
19
  this.intervalMin = intervalMin;
18
- this.requiresAuth = requiresAuth;
20
+ this.requiresAuth = opts.requiresAuth ?? false;
21
+ this.label = opts.label;
22
+ this.showToast = opts.showToast ?? false;
23
+ }
24
+ configure(opts) {
25
+ if (opts.label !== undefined)
26
+ this.label = opts.label;
27
+ if (opts.showToast !== undefined)
28
+ this.showToast = opts.showToast;
19
29
  }
20
30
  /**
21
31
  * Registers a task with a specified interval and execution logic.
@@ -106,7 +116,9 @@ export class CronWorker {
106
116
  async runWithEvents(job) {
107
117
  this.isExecuting = true;
108
118
  const startTime = Date.now();
109
- UIEventBus.emit("CRON_START", { task: this.taskName });
119
+ if (this.showToast) {
120
+ UIEventBus.emit("CRON_START", { task: this.taskName, label: this.label });
121
+ }
110
122
  try {
111
123
  await Promise.resolve(job());
112
124
  const duration = Date.now() - startTime;
@@ -119,7 +131,8 @@ export class CronWorker {
119
131
  }
120
132
  finally {
121
133
  this.isExecuting = false;
122
- UIEventBus.emit("CRON_END", { task: this.taskName });
134
+ if (this.showToast)
135
+ UIEventBus.emit("CRON_END", { task: this.taskName });
123
136
  }
124
137
  }
125
138
  /**
@@ -1,2 +1,2 @@
1
- export { CronJob, CronWorker } from "./CronWorker";
1
+ export { CronJob, CronWorker, CronWorkerOpts } from "./CronWorker";
2
2
  export { EventBus, EventName } from "./EventBus";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sincpro/mobile",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Sincpro Mobile framework core: offline-first infra (DB, queue/events, cron), DDD patterns, adapters, AppShell and module-driven composition for React Native",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -27,6 +27,7 @@
27
27
  "author": "Gutierrez, Andres <andres.gutierrez@sincpro.com.bo>",
28
28
  "license": "MIT",
29
29
  "dependencies": {
30
+ "@opentelemetry/api": "^1.9.0",
30
31
  "@opentelemetry/core": "^2.8.0",
31
32
  "@opentelemetry/resources": "^2.8.0",
32
33
  "@opentelemetry/sdk-trace-base": "^2.8.0",
@@ -39,7 +40,6 @@
39
40
  "uuid": "^13.0.0"
40
41
  },
41
42
  "peerDependencies": {
42
- "@opentelemetry/api": ">=1.3.0 <1.10.0",
43
43
  "@react-navigation/native": ">=7",
44
44
  "@react-navigation/native-stack": ">=7",
45
45
  "@sincpro/mobile-ui": "^0.7.2",