@sincpro/mobile 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -12
- package/dist/adapters/repositories/domain_event.repository.js +2 -1
- package/dist/adapters/repositories/domain_event_dead_letter.repository.js +2 -1
- package/dist/adapters/repositories/setting.repository.js +4 -3
- package/dist/domain/settings.d.ts +1 -1
- package/dist/domain/settings.js +4 -1
- package/dist/entrypoints/db/migrations.d.ts +1 -1
- package/dist/entrypoints/db/migrations.js +26 -18
- package/dist/infrastructure/logger.d.ts +2 -2
- package/dist/infrastructure/logger.js +51 -33
- package/dist/services/printer.service.js +4 -3
- package/dist/ui/screens/database/database.json_detail.js +2 -1
- package/dist/ui/screens/database/database.list.js +2 -1
- package/dist/ui/screens/database/database.table_rows.js +2 -1
- package/dist/ui/screens/dead_letter_queue/dead_letter_queue.list.js +2 -1
- package/dist/ui/screens/events/events.list.js +2 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
# @sincpro/mobile
|
|
2
2
|
|
|
3
|
-
Core del framework móvil Sincpro (React Native / Expo): infraestructura offline-first (DB, cola/eventos, cron), patrones DDD, adapters base, composición module-driven y el `AppShell`. Consume el design system `@sincpro/mobile-ui`.
|
|
3
|
+
Core del **framework móvil Sincpro** (React Native / Expo): infraestructura offline-first (DB, cola/eventos, cron), patrones DDD, adapters base, composición **module-driven** y el `AppShell`. Consume el design system `@sincpro/mobile-ui`.
|
|
4
|
+
|
|
5
|
+
> 🤖 **¿Sos un agente de IA?** Leé primero [`AGENTS.md`](AGENTS.md) — orientación rápida del ecosistema, patrones y trampas. Si algo se comporta raro, [`docs/GOTCHAS.md`](docs/GOTCHAS.md).
|
|
6
|
+
|
|
7
|
+
## Filosofía
|
|
8
|
+
|
|
9
|
+
El objetivo es **generar apps móviles de negocio muy rápido** componiendo piezas estables, no escribir cada app desde cero. Principios:
|
|
10
|
+
|
|
11
|
+
- **Framework, no monolito.** Un ecosistema de paquetes publicables; cada app es delgada y declara solo sus dominios.
|
|
12
|
+
- **Module-driven + inversión de dependencia.** El core no conoce los negocios. Cada dominio es un `DomainModule` que la app registra; el `orchestrator` alimenta cola, repos, migraciones, cron y temas desde los módulos registrados. Agregar/mover un dominio no toca el core.
|
|
13
|
+
- **Offline-first.** SQLite + cola de eventos persistente (con DLQ y reintentos) + sincronización; la app funciona sin red y reconcilia al volver.
|
|
14
|
+
- **Hexagonal.** El dominio define puertos (`IPrinterDriver`, `IRepository`, `IRemoteClient`); los adapters concretos viven afuera y se inyectan. El core es agnóstico de hardware y de backend.
|
|
15
|
+
- **Frontera real verificable.** Capas con dirección estricta `apps → mobile-odoo → mobile → mobile-ui`; el design system no sabe de negocio; los enums/eventos tienen un punto único de export.
|
|
16
|
+
|
|
17
|
+
## Ecosistema
|
|
18
|
+
|
|
19
|
+
| Paquete | Rol |
|
|
20
|
+
| ---------------------- | ------------------------------------------------------------------------------------------- |
|
|
21
|
+
| `@sincpro/mobile` | Core: infra, patrones DDD, `framework/` (DomainModule, createApp, orchestrator), `AppShell` |
|
|
22
|
+
| `@sincpro/mobile-ui` | Design system standalone (sin router, sin dominios) |
|
|
23
|
+
| `@sincpro/mobile-odoo` | Integración Odoo **opcional** (OdooClient, auth, server, partner) |
|
|
24
|
+
| `sincpro-mobile-<app>` | Apps de negocio (tickets, distribution) que componen lo anterior |
|
|
4
25
|
|
|
5
26
|
## Instalación
|
|
6
27
|
|
|
@@ -15,8 +36,9 @@ npx expo install @sincpro/mobile @sincpro/mobile-ui
|
|
|
15
36
|
Una app define sus dominios como **clases que extienden `DomainModule`** y las compone con `createAppShell`:
|
|
16
37
|
|
|
17
38
|
```tsx
|
|
18
|
-
import { createAppShell, createTheme
|
|
19
|
-
import
|
|
39
|
+
import { createAppShell, createTheme } from "@sincpro/mobile";
|
|
40
|
+
import { DomainModule } from "@sincpro/mobile/framework/domain_module";
|
|
41
|
+
import type { Subscriber } from "@sincpro/mobile/domain/event_sourcing";
|
|
20
42
|
|
|
21
43
|
class VentasModule extends DomainModule {
|
|
22
44
|
readonly key = "VENTAS";
|
|
@@ -55,20 +77,15 @@ export default createAppShell({
|
|
|
55
77
|
Las primitivas y capas se importan por subpath, p. ej.:
|
|
56
78
|
|
|
57
79
|
```ts
|
|
58
|
-
import { DomainModule } from "@sincpro/mobile/
|
|
59
|
-
import type { Subscriber } from "@sincpro/mobile/domain/
|
|
80
|
+
import { DomainModule } from "@sincpro/mobile/framework/domain_module";
|
|
81
|
+
import type { Subscriber } from "@sincpro/mobile/domain/event_sourcing";
|
|
60
82
|
import { setPrinterDriver } from "@sincpro/mobile/domain/print";
|
|
61
83
|
```
|
|
62
84
|
|
|
63
85
|
## Impresora (opcional)
|
|
64
86
|
|
|
65
|
-
El core es agnóstico del hardware: define el puerto `IPrinterDriver` (`@sincpro/mobile/domain/print`). La app registra un driver concreto al bootstrap
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
import { setPrinterDriver } from "@sincpro/mobile/domain/print";
|
|
69
|
-
setPrinterDriver(miDriver);
|
|
70
|
-
```
|
|
87
|
+
El core es agnóstico del hardware: define el puerto `IPrinterDriver` (`@sincpro/mobile/domain/print`). La app registra un driver concreto al bootstrap (vía `printerService.setDriver(...)`); si no registra ninguno, el driver Noop degrada con un warning.
|
|
71
88
|
|
|
72
89
|
## Desarrollo
|
|
73
90
|
|
|
74
|
-
Todo vía Makefile: `make
|
|
91
|
+
Todo vía Makefile. Dos comandos de calidad: `make format` (auto-fix: eslint --fix + prettier + typecheck) y `make verify-format` (gate de CI: corre `format` y falla si quedó algo sin formatear/commitear — cubre lint + formato + tipos). Además `make build` (tsc + tsc-alias → `dist/`) y `make publish`. Detalle del workflow y guardrails en [`AGENTS.md`](AGENTS.md).
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { DomainEvent, EEventStatus } from "../../domain/event_sourcing";
|
|
2
2
|
import { ECommonRepository } from "../../domain/repositories";
|
|
3
|
+
import { DATABASE_TABLES } from "../../entrypoints/db/migrations";
|
|
3
4
|
import { DBCursor } from "../../infrastructure/database";
|
|
4
5
|
import { loggerRepositories } from "../../infrastructure/logger";
|
|
5
6
|
class DomainEventRepositoryImpl {
|
|
6
7
|
name = ECommonRepository.DOMAIN_EVENT;
|
|
7
|
-
table =
|
|
8
|
+
table = DATABASE_TABLES.DOMAIN_EVENTS;
|
|
8
9
|
async hasPendingDuplicate(event) {
|
|
9
10
|
if (event.correlationId) {
|
|
10
11
|
const existing = await DBCursor.getFirstAsync(`SELECT uuid FROM ${this.table}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { DomainEvent } from "../../domain/event_sourcing";
|
|
2
2
|
import { ECommonRepository } from "../../domain/repositories";
|
|
3
|
+
import { DATABASE_TABLES } from "../../entrypoints/db/migrations";
|
|
3
4
|
import { DBCursor } from "../../infrastructure/database";
|
|
4
5
|
import { loggerRepositories } from "../../infrastructure/logger";
|
|
5
6
|
class DomainEventDeadLetterRepositoryImpl {
|
|
6
7
|
name = ECommonRepository.DOMAIN_EVENT_DEAD_LETTER;
|
|
7
|
-
table =
|
|
8
|
+
table = DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER;
|
|
8
9
|
async save(event, errorMessage) {
|
|
9
10
|
await DBCursor.mutateDatabase(`INSERT INTO ${this.table}
|
|
10
11
|
(uuid, name, label, data, requires_network, created_at, attempts, error_message, failed_at, aggregate_id, source_id, correlation_id, sequence)
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { ECommonRepository } from "../../domain/repositories";
|
|
2
|
+
import { DATABASE_TABLES } from "../../entrypoints/db/migrations";
|
|
2
3
|
import { DBCursor } from "../../infrastructure/database";
|
|
3
4
|
class SettingsRepositoryImpl {
|
|
4
5
|
name = ECommonRepository.SETTINGS;
|
|
5
|
-
table =
|
|
6
|
+
table = DATABASE_TABLES.SETTINGS;
|
|
6
7
|
async saveOneSetting(name, value) {
|
|
7
8
|
const type = typeof value;
|
|
8
|
-
await DBCursor.mutateDatabase(`INSERT OR REPLACE INTO ${
|
|
9
|
+
await DBCursor.mutateDatabase(`INSERT OR REPLACE INTO ${DATABASE_TABLES.SETTINGS} (name, value, type)
|
|
9
10
|
VALUES (?, ?, ?)`, name, JSON.stringify(value), type);
|
|
10
11
|
}
|
|
11
12
|
async getSettingByName(name) {
|
|
12
13
|
const row = await DBCursor.getFirstAsync(`SELECT value, type
|
|
13
|
-
FROM ${
|
|
14
|
+
FROM ${DATABASE_TABLES.SETTINGS}
|
|
14
15
|
WHERE name = ?`, name);
|
|
15
16
|
if (!row) {
|
|
16
17
|
return null;
|
package/dist/domain/settings.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { DBCursor } from "../../infrastructure/database";
|
|
2
|
+
export var DATABASE_TABLES;
|
|
3
|
+
(function (DATABASE_TABLES) {
|
|
4
|
+
DATABASE_TABLES["SETTINGS"] = "settings";
|
|
5
|
+
DATABASE_TABLES["EVENT_QUEUE"] = "event_queue";
|
|
6
|
+
DATABASE_TABLES["DEAD_LETTER_QUEUE"] = "dead_letter_queue";
|
|
7
|
+
DATABASE_TABLES["DOMAIN_EVENTS"] = "domain_events";
|
|
8
|
+
DATABASE_TABLES["DOMAIN_EVENTS_DEAD_LETTER"] = "domain_events_dead_letter";
|
|
9
|
+
})(DATABASE_TABLES || (DATABASE_TABLES = {}));
|
|
2
10
|
async function createSettingsTable() {
|
|
3
11
|
await DBCursor.execAsync(`
|
|
4
|
-
CREATE TABLE IF NOT EXISTS ${
|
|
12
|
+
CREATE TABLE IF NOT EXISTS ${DATABASE_TABLES.SETTINGS}(
|
|
5
13
|
name TEXT PRIMARY KEY NOT NULL,
|
|
6
14
|
value TEXT NOT NULL,
|
|
7
15
|
type TEXT NOT NULL,
|
|
@@ -11,7 +19,7 @@ async function createSettingsTable() {
|
|
|
11
19
|
}
|
|
12
20
|
async function createDeadLetterQueueTable() {
|
|
13
21
|
await DBCursor.execAsync(`
|
|
14
|
-
CREATE TABLE IF NOT EXISTS ${
|
|
22
|
+
CREATE TABLE IF NOT EXISTS ${DATABASE_TABLES.DEAD_LETTER_QUEUE} (
|
|
15
23
|
uuid TEXT PRIMARY KEY NOT NULL, -- UUID v7 for chronological ordering
|
|
16
24
|
name TEXT NOT NULL,
|
|
17
25
|
payload TEXT NOT NULL,
|
|
@@ -23,7 +31,7 @@ async function createDeadLetterQueueTable() {
|
|
|
23
31
|
}
|
|
24
32
|
async function createEventQueueTable() {
|
|
25
33
|
await DBCursor.execAsync(`
|
|
26
|
-
CREATE TABLE IF NOT EXISTS ${
|
|
34
|
+
CREATE TABLE IF NOT EXISTS ${DATABASE_TABLES.EVENT_QUEUE}(
|
|
27
35
|
uuid TEXT PRIMARY KEY NOT NULL,
|
|
28
36
|
name TEXT NOT NULL,
|
|
29
37
|
payload TEXT NOT NULL,
|
|
@@ -40,7 +48,7 @@ async function createEventQueueTable() {
|
|
|
40
48
|
}
|
|
41
49
|
async function createDomainEventsTable() {
|
|
42
50
|
await DBCursor.execAsync(`
|
|
43
|
-
CREATE TABLE IF NOT EXISTS ${
|
|
51
|
+
CREATE TABLE IF NOT EXISTS ${DATABASE_TABLES.DOMAIN_EVENTS} (
|
|
44
52
|
uuid TEXT PRIMARY KEY NOT NULL,
|
|
45
53
|
name TEXT NOT NULL,
|
|
46
54
|
label TEXT NOT NULL,
|
|
@@ -59,24 +67,24 @@ async function createDomainEventsTable() {
|
|
|
59
67
|
);
|
|
60
68
|
`);
|
|
61
69
|
await DBCursor.execAsync(`
|
|
62
|
-
CREATE INDEX IF NOT EXISTS idx_domain_events_status ON ${
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_domain_events_status ON ${DATABASE_TABLES.DOMAIN_EVENTS}(status);
|
|
63
71
|
`);
|
|
64
72
|
await DBCursor.execAsync(`
|
|
65
|
-
CREATE INDEX IF NOT EXISTS idx_domain_events_correlation ON ${
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_domain_events_correlation ON ${DATABASE_TABLES.DOMAIN_EVENTS}(correlation_id);
|
|
66
74
|
`);
|
|
67
75
|
await DBCursor.execAsync(`
|
|
68
|
-
CREATE INDEX IF NOT EXISTS idx_domain_events_aggregate ON ${
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_domain_events_aggregate ON ${DATABASE_TABLES.DOMAIN_EVENTS}(aggregate_id);
|
|
69
77
|
`);
|
|
70
78
|
await DBCursor.execAsync(`
|
|
71
|
-
CREATE INDEX IF NOT EXISTS idx_domain_events_name ON ${
|
|
79
|
+
CREATE INDEX IF NOT EXISTS idx_domain_events_name ON ${DATABASE_TABLES.DOMAIN_EVENTS}(name);
|
|
72
80
|
`);
|
|
73
81
|
await DBCursor.execAsync(`
|
|
74
|
-
CREATE INDEX IF NOT EXISTS idx_domain_events_created ON ${
|
|
82
|
+
CREATE INDEX IF NOT EXISTS idx_domain_events_created ON ${DATABASE_TABLES.DOMAIN_EVENTS}(created_at);
|
|
75
83
|
`);
|
|
76
84
|
}
|
|
77
85
|
async function createDomainEventsDeadLetterTable() {
|
|
78
86
|
await DBCursor.execAsync(`
|
|
79
|
-
CREATE TABLE IF NOT EXISTS ${
|
|
87
|
+
CREATE TABLE IF NOT EXISTS ${DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER} (
|
|
80
88
|
uuid TEXT PRIMARY KEY NOT NULL,
|
|
81
89
|
name TEXT NOT NULL,
|
|
82
90
|
label TEXT NOT NULL,
|
|
@@ -93,22 +101,22 @@ async function createDomainEventsDeadLetterTable() {
|
|
|
93
101
|
);
|
|
94
102
|
`);
|
|
95
103
|
await DBCursor.execAsync(`
|
|
96
|
-
CREATE INDEX IF NOT EXISTS idx_dead_letter_correlation ON ${
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_dead_letter_correlation ON ${DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER}(correlation_id);
|
|
97
105
|
`);
|
|
98
106
|
await DBCursor.execAsync(`
|
|
99
|
-
CREATE INDEX IF NOT EXISTS idx_dead_letter_name ON ${
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_dead_letter_name ON ${DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER}(name);
|
|
100
108
|
`);
|
|
101
109
|
await DBCursor.execAsync(`
|
|
102
|
-
CREATE INDEX IF NOT EXISTS idx_dead_letter_failed ON ${
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_dead_letter_failed ON ${DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER}(failed_at);
|
|
103
111
|
`);
|
|
104
112
|
}
|
|
105
113
|
const MIGRATIONS = [
|
|
106
|
-
{ name:
|
|
107
|
-
{ name:
|
|
108
|
-
{ name:
|
|
109
|
-
{ name:
|
|
114
|
+
{ name: DATABASE_TABLES.SETTINGS, migrationFn: createSettingsTable },
|
|
115
|
+
{ name: DATABASE_TABLES.EVENT_QUEUE, migrationFn: createEventQueueTable },
|
|
116
|
+
{ name: DATABASE_TABLES.DEAD_LETTER_QUEUE, migrationFn: createDeadLetterQueueTable },
|
|
117
|
+
{ name: DATABASE_TABLES.DOMAIN_EVENTS, migrationFn: createDomainEventsTable },
|
|
110
118
|
{
|
|
111
|
-
name:
|
|
119
|
+
name: DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER,
|
|
112
120
|
migrationFn: createDomainEventsDeadLetterTable,
|
|
113
121
|
},
|
|
114
122
|
];
|
|
@@ -5,13 +5,13 @@ interface ILogger {
|
|
|
5
5
|
warn(...args: LoggerArgs): void;
|
|
6
6
|
error(...args: LoggerArgs): void;
|
|
7
7
|
}
|
|
8
|
-
declare
|
|
8
|
+
declare enum ELogLevel {
|
|
9
9
|
DEBUG = "DEBUG",
|
|
10
10
|
INFO = "INFO",
|
|
11
11
|
WARN = "WARN",
|
|
12
12
|
ERROR = "ERROR"
|
|
13
13
|
}
|
|
14
|
-
declare
|
|
14
|
+
declare enum EApplicationLogger {
|
|
15
15
|
GLOBAL = "GLOBAL",
|
|
16
16
|
ODOO_CLIENT = "ODOO_CLIENT",
|
|
17
17
|
ADAPTER = "ADAPTER",
|
|
@@ -15,17 +15,34 @@ const Colors = {
|
|
|
15
15
|
BrightYellow: "\x1b[93m",
|
|
16
16
|
White: "\x1b[37m",
|
|
17
17
|
};
|
|
18
|
+
var ELogLevel;
|
|
19
|
+
(function (ELogLevel) {
|
|
20
|
+
ELogLevel["DEBUG"] = "DEBUG";
|
|
21
|
+
ELogLevel["INFO"] = "INFO";
|
|
22
|
+
ELogLevel["WARN"] = "WARN";
|
|
23
|
+
ELogLevel["ERROR"] = "ERROR";
|
|
24
|
+
})(ELogLevel || (ELogLevel = {}));
|
|
25
|
+
var EApplicationLogger;
|
|
26
|
+
(function (EApplicationLogger) {
|
|
27
|
+
EApplicationLogger["GLOBAL"] = "GLOBAL";
|
|
28
|
+
EApplicationLogger["ODOO_CLIENT"] = "ODOO_CLIENT";
|
|
29
|
+
EApplicationLogger["ADAPTER"] = "ADAPTER";
|
|
30
|
+
EApplicationLogger["REPOSITORIES"] = "REPOSITORIES";
|
|
31
|
+
EApplicationLogger["USE_CASES"] = "USE_CASES";
|
|
32
|
+
EApplicationLogger["QUEUE_PROCESSOR"] = "EVENT_BUS";
|
|
33
|
+
EApplicationLogger["CRON_JOBS"] = "CRON_JOBS";
|
|
34
|
+
})(EApplicationLogger || (EApplicationLogger = {}));
|
|
18
35
|
/**
|
|
19
36
|
* Color mapping for each logger context
|
|
20
37
|
*/
|
|
21
38
|
const LoggerColors = {
|
|
22
|
-
[
|
|
23
|
-
[
|
|
24
|
-
[
|
|
25
|
-
[
|
|
26
|
-
[
|
|
27
|
-
[
|
|
28
|
-
[
|
|
39
|
+
[EApplicationLogger.GLOBAL]: Colors.White,
|
|
40
|
+
[EApplicationLogger.ODOO_CLIENT]: Colors.Gray,
|
|
41
|
+
[EApplicationLogger.ADAPTER]: Colors.Gray,
|
|
42
|
+
[EApplicationLogger.REPOSITORIES]: Colors.Gray,
|
|
43
|
+
[EApplicationLogger.USE_CASES]: Colors.BrightGreen,
|
|
44
|
+
[EApplicationLogger.QUEUE_PROCESSOR]: Colors.BrightYellow,
|
|
45
|
+
[EApplicationLogger.CRON_JOBS]: Colors.Gray,
|
|
29
46
|
};
|
|
30
47
|
/**
|
|
31
48
|
* Production flag - manually toggle to disable all logs
|
|
@@ -35,35 +52,35 @@ export const IS_PRODUCTION = false;
|
|
|
35
52
|
/**
|
|
36
53
|
* Global log level for the application.
|
|
37
54
|
*/
|
|
38
|
-
export const LOG_LEVEL =
|
|
55
|
+
export const LOG_LEVEL = ELogLevel.INFO;
|
|
39
56
|
/**
|
|
40
57
|
* Configuration for enabling or disabling logs for specific application contexts.
|
|
41
58
|
*/
|
|
42
59
|
export const ENABLED_LOGS = {
|
|
43
|
-
[
|
|
44
|
-
[
|
|
45
|
-
[
|
|
46
|
-
[
|
|
47
|
-
[
|
|
48
|
-
[
|
|
49
|
-
[
|
|
60
|
+
[EApplicationLogger.GLOBAL]: true,
|
|
61
|
+
[EApplicationLogger.ODOO_CLIENT]: false,
|
|
62
|
+
[EApplicationLogger.ADAPTER]: false,
|
|
63
|
+
[EApplicationLogger.REPOSITORIES]: false,
|
|
64
|
+
[EApplicationLogger.USE_CASES]: true,
|
|
65
|
+
[EApplicationLogger.QUEUE_PROCESSOR]: true,
|
|
66
|
+
[EApplicationLogger.CRON_JOBS]: true,
|
|
50
67
|
};
|
|
51
68
|
class BaseLogger {
|
|
52
69
|
context;
|
|
53
70
|
contextPrefix;
|
|
54
71
|
constructor(context) {
|
|
55
72
|
this.context = context;
|
|
56
|
-
this.contextPrefix = context ===
|
|
73
|
+
this.contextPrefix = context === EApplicationLogger.GLOBAL ? "" : `[${context}]`;
|
|
57
74
|
}
|
|
58
75
|
getLogColor(level) {
|
|
59
76
|
switch (level) {
|
|
60
|
-
case
|
|
77
|
+
case ELogLevel.DEBUG:
|
|
61
78
|
return Colors.Gray;
|
|
62
|
-
case
|
|
79
|
+
case ELogLevel.INFO:
|
|
63
80
|
return Colors.Cyan;
|
|
64
|
-
case
|
|
81
|
+
case ELogLevel.WARN:
|
|
65
82
|
return Colors.Yellow;
|
|
66
|
-
case
|
|
83
|
+
case ELogLevel.ERROR:
|
|
67
84
|
return Colors.Red;
|
|
68
85
|
default:
|
|
69
86
|
return Colors.White;
|
|
@@ -80,9 +97,9 @@ class BaseLogger {
|
|
|
80
97
|
shouldLog(level) {
|
|
81
98
|
if (IS_PRODUCTION)
|
|
82
99
|
return false;
|
|
83
|
-
if (level ===
|
|
100
|
+
if (level === ELogLevel.ERROR || level === ELogLevel.WARN)
|
|
84
101
|
return true;
|
|
85
|
-
if (level ===
|
|
102
|
+
if (level === ELogLevel.DEBUG && LOG_LEVEL !== ELogLevel.DEBUG)
|
|
86
103
|
return false;
|
|
87
104
|
if (!ENABLED_LOGS[this.context])
|
|
88
105
|
return false;
|
|
@@ -100,26 +117,27 @@ class BaseLogger {
|
|
|
100
117
|
}
|
|
101
118
|
}
|
|
102
119
|
debug(...args) {
|
|
103
|
-
this.log(
|
|
120
|
+
this.log(ELogLevel.DEBUG, console.debug, args);
|
|
104
121
|
}
|
|
105
122
|
info(...args) {
|
|
106
|
-
this.log(
|
|
123
|
+
this.log(ELogLevel.INFO, console.log, args);
|
|
107
124
|
}
|
|
108
125
|
warn(...args) {
|
|
109
|
-
this.log(
|
|
126
|
+
this.log(ELogLevel.WARN, console.warn, args);
|
|
110
127
|
}
|
|
111
128
|
error(...args) {
|
|
112
|
-
this.log(
|
|
129
|
+
this.log(ELogLevel.ERROR, console.error, args);
|
|
113
130
|
}
|
|
114
131
|
}
|
|
115
132
|
export function createLogger(context) {
|
|
116
133
|
return new BaseLogger(context);
|
|
117
134
|
}
|
|
118
|
-
const logger = createLogger(
|
|
119
|
-
export const loggerOdooClient = createLogger(
|
|
120
|
-
export const loggerAdapter = createLogger(
|
|
121
|
-
export const loggerRepositories = createLogger(
|
|
122
|
-
export const loggerUseCases = createLogger(
|
|
123
|
-
export const loggerQueueProcessor = createLogger(
|
|
124
|
-
export const loggerCronJobs = createLogger(
|
|
135
|
+
const logger = createLogger(EApplicationLogger.GLOBAL);
|
|
136
|
+
export const loggerOdooClient = createLogger(EApplicationLogger.ODOO_CLIENT);
|
|
137
|
+
export const loggerAdapter = createLogger(EApplicationLogger.ADAPTER);
|
|
138
|
+
export const loggerRepositories = createLogger(EApplicationLogger.REPOSITORIES);
|
|
139
|
+
export const loggerUseCases = createLogger(EApplicationLogger.USE_CASES);
|
|
140
|
+
export const loggerQueueProcessor = createLogger(EApplicationLogger.QUEUE_PROCESSOR);
|
|
141
|
+
export const loggerCronJobs = createLogger(EApplicationLogger.CRON_JOBS);
|
|
142
|
+
export { EApplicationLogger };
|
|
125
143
|
export default logger;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ReceiptExporterAdapter } from "../adapters/ReceiptExporter.adapter";
|
|
2
2
|
import { SettingsRepository } from "../adapters/repositories/setting.repository";
|
|
3
3
|
import { getPrinterDriver, hasPrinterDriver, setPrinterDriver, } from "../domain/print";
|
|
4
|
+
import { ECommonSetting } from "../domain/settings";
|
|
4
5
|
import { loggerUseCases } from "../infrastructure/logger";
|
|
5
6
|
import { UI_NOTIFICATION_EVENT } from "../infrastructure/ui/events";
|
|
6
7
|
import { UIEventBus } from "../infrastructure/ui/UIEventBus";
|
|
@@ -72,16 +73,16 @@ class PrinterService {
|
|
|
72
73
|
if (this.cachedPrinter) {
|
|
73
74
|
return this.cachedPrinter;
|
|
74
75
|
}
|
|
75
|
-
const printer = await SettingsRepository.getSettingByName(
|
|
76
|
+
const printer = await SettingsRepository.getSettingByName(ECommonSetting.SELECTED_PRINTER);
|
|
76
77
|
this.cachedPrinter = printer;
|
|
77
78
|
return printer;
|
|
78
79
|
}
|
|
79
80
|
async saveSelectedPrinter(printer) {
|
|
80
|
-
await SettingsRepository.saveOneSetting(
|
|
81
|
+
await SettingsRepository.saveOneSetting(ECommonSetting.SELECTED_PRINTER, printer);
|
|
81
82
|
this.cachedPrinter = printer;
|
|
82
83
|
}
|
|
83
84
|
async clearSelectedPrinter() {
|
|
84
|
-
await SettingsRepository.saveOneSetting(
|
|
85
|
+
await SettingsRepository.saveOneSetting(ECommonSetting.SELECTED_PRINTER, null);
|
|
85
86
|
this.cachedPrinter = null;
|
|
86
87
|
}
|
|
87
88
|
async printTestPage() {
|
|
@@ -5,6 +5,7 @@ import { theme } from "@sincpro/mobile-ui/theme";
|
|
|
5
5
|
import { Typography } from "@sincpro/mobile-ui/Typography";
|
|
6
6
|
import { FormViewV2 } from "@sincpro/mobile-ui/views/FormViewV2";
|
|
7
7
|
import JsonPreview from "@sincpro/mobile-ui/widgets/JSONViewer";
|
|
8
|
+
import { EVariantScreenHeader } from "@sincpro/mobile-ui/widgets/ScreenHeader";
|
|
8
9
|
import * as Clipboard from "expo-clipboard";
|
|
9
10
|
import { View } from "react-native";
|
|
10
11
|
import { useDatabase } from "./database.context";
|
|
@@ -12,7 +13,7 @@ const Button = Form.Button;
|
|
|
12
13
|
const Icon = Display.Icon;
|
|
13
14
|
function JsonDetailView() {
|
|
14
15
|
const { selectedTable, selectedRowJson, goBackFromJson, currentRowIndex, filteredData, hasNextRow, hasPreviousRow, goToNextRow, goToPreviousRow, } = useDatabase();
|
|
15
|
-
return (_jsxs(FormViewV2.Root, { description: `JSON VIEWER - ${selectedTable}`, isLoading: false, item: { json: selectedRowJson }, name: "Detalles", onBack: goBackFromJson, children: [_jsx(FormViewV2.Header, { variant:
|
|
16
|
+
return (_jsxs(FormViewV2.Root, { description: `JSON VIEWER - ${selectedTable}`, isLoading: false, item: { json: selectedRowJson }, name: "Detalles", onBack: goBackFromJson, children: [_jsx(FormViewV2.Header, { variant: EVariantScreenHeader.FLAT_HEADER, children: _jsx(FormViewV2.Header.Actions, { children: _jsxs(Typography.Text, { className: "text-gray-500", variant: "bodySmall", children: [currentRowIndex + 1, " de ", filteredData.length] }) }) }), _jsx(FormViewV2.Content, { children: _jsx(JsonPreview, { selectedJson: selectedRowJson }) }), _jsx(FormViewV2.Footer, { children: _jsxs(View, { className: "flex-row items-center justify-between gap-3", children: [_jsx(Button, { className: "flex-none min-w-[50px]", disabled: !hasPreviousRow, icon: _jsx(Icon, { color: hasPreviousRow ? theme.secondary : theme.text.tertiary, name: "chevron-left", size: 24, type: "feather" }), onPress: goToPreviousRow, title: "", variant: "secondary" }), _jsx(Button, { className: "flex-1", onPress: async () => {
|
|
16
17
|
await Clipboard.setStringAsync(selectedRowJson);
|
|
17
18
|
}, title: "Copiar JSON", variant: "primary" }), _jsx(Button, { className: "flex-none min-w-[50px]", disabled: !hasNextRow, icon: _jsx(Icon, { color: hasNextRow ? theme.secondary : theme.text.tertiary, name: "chevron-right", size: 24, type: "feather" }), onPress: goToNextRow, title: "", variant: "secondary" })] }) })] }));
|
|
18
19
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
2
|
import DatabaseInfoRow from "../../../ui/components/organisms/DatabaseInfoRow";
|
|
3
3
|
import { ListViewV2 } from "@sincpro/mobile-ui/views/ListViewV2";
|
|
4
|
+
import { EVariantScreenHeader } from "@sincpro/mobile-ui/widgets/ScreenHeader";
|
|
4
5
|
import { useEffect } from "react";
|
|
5
6
|
import { useNavigate } from "react-router-native";
|
|
6
7
|
import { DatabaseProvider, EDatabaseView, useDatabase } from "./database.context";
|
|
@@ -15,7 +16,7 @@ function TablesView() {
|
|
|
15
16
|
const onBack = () => navigate(-1);
|
|
16
17
|
return (_jsxs(ListViewV2.Root, { isLoading: isLoading, items: tables, name: "Lista de tablas", onBack: onBack, onPressItem: async (table) => {
|
|
17
18
|
await selectTable(table.name);
|
|
18
|
-
}, children: [_jsx(ListViewV2.Header, { variant:
|
|
19
|
+
}, children: [_jsx(ListViewV2.Header, { variant: EVariantScreenHeader.FLAT_HEADER }), _jsx(ListViewV2.Content, { children: (table) => (_jsx(DatabaseInfoRow, { item: {
|
|
19
20
|
name: table.displayName || table.name,
|
|
20
21
|
description: "Tabla de base de datos",
|
|
21
22
|
tableName: table.name,
|
|
@@ -2,9 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
|
2
2
|
import { safeJsonStringify } from "../../../tools/utils/serializer";
|
|
3
3
|
import TableInfoRow from "../../../ui/components/organisms/TableInfoInfoRow";
|
|
4
4
|
import { ListViewV2 } from "@sincpro/mobile-ui/views/ListViewV2";
|
|
5
|
+
import { EVariantScreenHeader } from "@sincpro/mobile-ui/widgets/ScreenHeader";
|
|
5
6
|
import { useDatabase } from "./database.context";
|
|
6
7
|
function TableRowsView() {
|
|
7
8
|
const { isLoading, filteredData, selectedTable, goBackFromRows, selectRowJson, setSearchQuery, } = useDatabase();
|
|
8
|
-
return (_jsxs(ListViewV2.Root, { isLoading: isLoading, items: filteredData, name: `Datos - ${selectedTable}`, onBack: goBackFromRows, onPressItem: (item) => selectRowJson(safeJsonStringify(item), item.index), onSearch: setSearchQuery, children: [_jsx(ListViewV2.Header, { variant:
|
|
9
|
+
return (_jsxs(ListViewV2.Root, { isLoading: isLoading, items: filteredData, name: `Datos - ${selectedTable}`, onBack: goBackFromRows, onPressItem: (item) => selectRowJson(safeJsonStringify(item), item.index), onSearch: setSearchQuery, children: [_jsx(ListViewV2.Header, { variant: EVariantScreenHeader.FLAT_HEADER, children: _jsx(ListViewV2.Header.Search, {}) }), _jsx(ListViewV2.Content, { children: (item) => (_jsx(TableInfoRow, { item: { data: safeJsonStringify(item) }, onPress: () => selectRowJson(safeJsonStringify(item), item.index) })) })] }));
|
|
9
10
|
}
|
|
10
11
|
export default TableRowsView;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
2
|
import DeadLetterQueueRow from "../../../ui/components/organisms/DeadLetterQueueRow";
|
|
3
3
|
import { ListViewV2 } from "@sincpro/mobile-ui/views/ListViewV2";
|
|
4
|
+
import { EVariantScreenHeader } from "@sincpro/mobile-ui/widgets/ScreenHeader";
|
|
4
5
|
import { useEffect } from "react";
|
|
5
6
|
import { useNavigate } from "react-router-native";
|
|
6
7
|
import { DeadLetterQueueProvider, useDeadLetterQueue } from "./dead_letter_queue.context";
|
|
@@ -11,7 +12,7 @@ function DeadLetterQueueScreenComponent() {
|
|
|
11
12
|
loadEvents();
|
|
12
13
|
}, [loadEvents]);
|
|
13
14
|
const onBack = () => navigate(-1);
|
|
14
|
-
return (_jsxs(ListViewV2.Root, { description: "Eventos que no pudieron ser procesados", isLoading: isLoading, items: enrichedEvents, name: "Eventos fallidos", onBack: onBack, onRefresh: loadEvents, children: [_jsx(ListViewV2.Header, { variant:
|
|
15
|
+
return (_jsxs(ListViewV2.Root, { description: "Eventos que no pudieron ser procesados", isLoading: isLoading, items: enrichedEvents, name: "Eventos fallidos", onBack: onBack, onRefresh: loadEvents, children: [_jsx(ListViewV2.Header, { variant: EVariantScreenHeader.FLAT_HEADER }), _jsx(ListViewV2.Content, { children: (event) => (_jsx(DeadLetterQueueRow, { item: event, onRetry: async (evt) => {
|
|
15
16
|
await retryEvent(evt);
|
|
16
17
|
setTimeout(() => {
|
|
17
18
|
void loadEvents();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
2
|
import EventRow from "../../../ui/components/organisms/EventRow";
|
|
3
3
|
import { ListViewV2 } from "@sincpro/mobile-ui/views/ListViewV2";
|
|
4
|
+
import { EVariantScreenHeader } from "@sincpro/mobile-ui/widgets/ScreenHeader";
|
|
4
5
|
import { useEffect } from "react";
|
|
5
6
|
import { useNavigate } from "react-router-native";
|
|
6
7
|
import { EventsProvider, useEvents } from "./events.context";
|
|
@@ -17,7 +18,7 @@ function EventsScreenComponent() {
|
|
|
17
18
|
void loadEvents();
|
|
18
19
|
}, [loadEvents]);
|
|
19
20
|
const onBack = () => navigate(-1);
|
|
20
|
-
return (_jsxs(ListViewV2.Root, { description: "Eventos registrados", isLoading: isLoading, items: pagedEvents, name: "Eventos", onBack: onBack, onRefresh: loadEvents, children: [_jsx(ListViewV2.Header, { variant:
|
|
21
|
+
return (_jsxs(ListViewV2.Root, { description: "Eventos registrados", isLoading: isLoading, items: pagedEvents, name: "Eventos", onBack: onBack, onRefresh: loadEvents, children: [_jsx(ListViewV2.Header, { variant: EVariantScreenHeader.FLAT_HEADER, children: _jsx(ListViewV2.Header.Filters, { children: _jsx(ListViewV2.Header.Filters.Chips, { children: FILTER_OPTIONS.map((option) => (_jsx(ListViewV2.Header.Filters.Chip, { active: filter === option.value, label: option.label, onPress: () => setFilter(option.value) }, option.value))) }) }) }), _jsx(ListViewV2.Content, { children: (event) => (_jsx(EventRow, { item: event, onRetry: async (evt) => {
|
|
21
22
|
await retryEvent(evt);
|
|
22
23
|
} })) })] }));
|
|
23
24
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sincpro/mobile",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
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",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"react-native-view-shot": "*",
|
|
60
60
|
"react-native-webview": "*",
|
|
61
61
|
"react-router-native": ">=6",
|
|
62
|
-
"@sincpro/mobile-ui": "^0.2.
|
|
62
|
+
"@sincpro/mobile-ui": "^0.2.1"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@types/big.js": "^6.2.2",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"react-router-native": "^6.30.0",
|
|
100
100
|
"tsc-alias": "^1.8.17",
|
|
101
101
|
"typescript": "~5.9.2",
|
|
102
|
-
"@sincpro/mobile-ui": "^0.2.
|
|
102
|
+
"@sincpro/mobile-ui": "^0.2.1"
|
|
103
103
|
},
|
|
104
104
|
"exports": {
|
|
105
105
|
".": "./dist/index.js",
|