@sincpro/mobile 0.2.0 → 0.4.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.
Files changed (95) hide show
  1. package/README.md +1063 -47
  2. package/dist/entrypoints/db/migrations.d.ts +3 -1
  3. package/dist/entrypoints/db/migrations.js +65 -0
  4. package/dist/entrypoints/queue/QueueProcessor.js +1 -25
  5. package/dist/entrypoints/queue/subscriber_filter.d.ts +9 -0
  6. package/dist/entrypoints/queue/subscriber_filter.js +25 -0
  7. package/dist/entrypoints/ui/AppShell.d.ts +55 -1
  8. package/dist/entrypoints/ui/AppShell.js +50 -5
  9. package/dist/entrypoints/ui/theme.d.ts +8 -7
  10. package/dist/entrypoints/ui/theme.js +8 -7
  11. package/dist/framework/base_module.d.ts +1 -0
  12. package/dist/framework/base_module.js +6 -1
  13. package/dist/framework/createApp.d.ts +6 -0
  14. package/dist/framework/createApp.js +5 -1
  15. package/dist/index.d.ts +6 -0
  16. package/dist/index.js +3 -0
  17. package/dist/infrastructure/context_manager/context.d.ts +16 -0
  18. package/dist/infrastructure/context_manager/context.js +37 -0
  19. package/dist/infrastructure/context_manager/context_api.d.ts +28 -0
  20. package/dist/infrastructure/context_manager/context_api.js +55 -0
  21. package/dist/infrastructure/context_manager/context_key.d.ts +15 -0
  22. package/dist/infrastructure/context_manager/context_key.js +3 -0
  23. package/dist/infrastructure/context_manager/context_manager_facade.d.ts +107 -0
  24. package/dist/infrastructure/context_manager/context_manager_facade.js +113 -0
  25. package/dist/infrastructure/context_manager/decorators/propagate_context.d.ts +24 -0
  26. package/dist/infrastructure/context_manager/decorators/propagate_context.js +52 -0
  27. package/dist/infrastructure/context_manager/decorators/with_context.d.ts +34 -0
  28. package/dist/infrastructure/context_manager/decorators/with_context.js +44 -0
  29. package/dist/infrastructure/context_manager/index.d.ts +11 -0
  30. package/dist/infrastructure/context_manager/index.js +12 -0
  31. package/dist/infrastructure/context_manager/managed_context.d.ts +87 -0
  32. package/dist/infrastructure/context_manager/managed_context.js +144 -0
  33. package/dist/infrastructure/context_manager/stack_context_manager.d.ts +39 -0
  34. package/dist/infrastructure/context_manager/stack_context_manager.js +56 -0
  35. package/dist/infrastructure/database/connector.js +10 -1
  36. package/dist/infrastructure/interceptor/index.d.ts +4 -0
  37. package/dist/infrastructure/interceptor/index.js +2 -0
  38. package/dist/infrastructure/interceptor/intercept.d.ts +57 -0
  39. package/dist/infrastructure/interceptor/intercept.js +112 -0
  40. package/dist/infrastructure/interceptor/logging_hooks.d.ts +22 -0
  41. package/dist/infrastructure/interceptor/logging_hooks.js +27 -0
  42. package/dist/infrastructure/logger.d.ts +11 -16
  43. package/dist/infrastructure/logger.js +61 -18
  44. package/dist/infrastructure/telemetry/buffer_registry.d.ts +15 -0
  45. package/dist/infrastructure/telemetry/buffer_registry.js +26 -0
  46. package/dist/infrastructure/telemetry/connectivity_state.d.ts +20 -0
  47. package/dist/infrastructure/telemetry/connectivity_state.js +28 -0
  48. package/dist/infrastructure/telemetry/context_keys.d.ts +7 -0
  49. package/dist/infrastructure/telemetry/context_keys.js +7 -0
  50. package/dist/infrastructure/telemetry/fetch_with_timeout.d.ts +8 -0
  51. package/dist/infrastructure/telemetry/fetch_with_timeout.js +17 -0
  52. package/dist/infrastructure/telemetry/flush_telemetry.d.ts +40 -0
  53. package/dist/infrastructure/telemetry/flush_telemetry.js +72 -0
  54. package/dist/infrastructure/telemetry/index.d.ts +17 -0
  55. package/dist/infrastructure/telemetry/index.js +17 -0
  56. package/dist/infrastructure/telemetry/init_telemetry.d.ts +21 -0
  57. package/dist/infrastructure/telemetry/init_telemetry.js +85 -0
  58. package/dist/infrastructure/telemetry/logging/flush_job.d.ts +44 -0
  59. package/dist/infrastructure/telemetry/logging/flush_job.js +69 -0
  60. package/dist/infrastructure/telemetry/logging/log_queue_repository.d.ts +76 -0
  61. package/dist/infrastructure/telemetry/logging/log_queue_repository.js +147 -0
  62. package/dist/infrastructure/telemetry/logging/loki_client.d.ts +32 -0
  63. package/dist/infrastructure/telemetry/logging/loki_client.js +118 -0
  64. package/dist/infrastructure/telemetry/logging/loki_registry.d.ts +12 -0
  65. package/dist/infrastructure/telemetry/logging/loki_registry.js +17 -0
  66. package/dist/infrastructure/telemetry/telemetry_flush_worker.d.ts +35 -0
  67. package/dist/infrastructure/telemetry/telemetry_flush_worker.js +65 -0
  68. package/dist/infrastructure/telemetry/telemetry_signal.d.ts +18 -0
  69. package/dist/infrastructure/telemetry/telemetry_signal.js +33 -0
  70. package/dist/infrastructure/telemetry/tracing/active_span.d.ts +22 -0
  71. package/dist/infrastructure/telemetry/tracing/active_span.js +40 -0
  72. package/dist/infrastructure/telemetry/tracing/otlp_client.d.ts +13 -0
  73. package/dist/infrastructure/telemetry/tracing/otlp_client.js +34 -0
  74. package/dist/infrastructure/telemetry/tracing/otlp_serializer.d.ts +67 -0
  75. package/dist/infrastructure/telemetry/tracing/otlp_serializer.js +101 -0
  76. package/dist/infrastructure/telemetry/tracing/span_flush_job.d.ts +25 -0
  77. package/dist/infrastructure/telemetry/tracing/span_flush_job.js +42 -0
  78. package/dist/infrastructure/telemetry/tracing/span_queue_repository.d.ts +82 -0
  79. package/dist/infrastructure/telemetry/tracing/span_queue_repository.js +134 -0
  80. package/dist/infrastructure/telemetry/tracing/span_sampler.d.ts +40 -0
  81. package/dist/infrastructure/telemetry/tracing/span_sampler.js +75 -0
  82. package/dist/infrastructure/telemetry/tracing/span_serializer.d.ts +48 -0
  83. package/dist/infrastructure/telemetry/tracing/span_serializer.js +36 -0
  84. package/dist/infrastructure/telemetry/tracing/sqlite_span_exporter.d.ts +26 -0
  85. package/dist/infrastructure/telemetry/tracing/sqlite_span_exporter.js +47 -0
  86. package/dist/infrastructure/telemetry/tracing/tracer.d.ts +22 -0
  87. package/dist/infrastructure/telemetry/tracing/tracer.js +45 -0
  88. package/dist/infrastructure/telemetry/tracing/tracing_hooks.d.ts +10 -0
  89. package/dist/infrastructure/telemetry/tracing/tracing_hooks.js +59 -0
  90. package/dist/infrastructure/telemetry/tracing_facade.d.ts +50 -0
  91. package/dist/infrastructure/telemetry/tracing_facade.js +139 -0
  92. package/dist/infrastructure/telemetry/types.d.ts +120 -0
  93. package/dist/infrastructure/telemetry/types.js +1 -0
  94. package/dist/ui/components/molecules/TimeZoneSelectorModal.js +7 -1
  95. package/package.json +17 -11
@@ -4,7 +4,9 @@ export declare enum DATABASE_TABLES {
4
4
  EVENT_QUEUE = "event_queue",
5
5
  DEAD_LETTER_QUEUE = "dead_letter_queue",
6
6
  DOMAIN_EVENTS = "domain_events",
7
- DOMAIN_EVENTS_DEAD_LETTER = "domain_events_dead_letter"
7
+ DOMAIN_EVENTS_DEAD_LETTER = "domain_events_dead_letter",
8
+ TELEMETRY_QUEUE = "telemetry_queue",
9
+ SPANS_QUEUE = "spans_queue"
8
10
  }
9
11
  declare const MIGRATIONS: IMigration[];
10
12
  export default MIGRATIONS;
@@ -6,6 +6,8 @@ export var DATABASE_TABLES;
6
6
  DATABASE_TABLES["DEAD_LETTER_QUEUE"] = "dead_letter_queue";
7
7
  DATABASE_TABLES["DOMAIN_EVENTS"] = "domain_events";
8
8
  DATABASE_TABLES["DOMAIN_EVENTS_DEAD_LETTER"] = "domain_events_dead_letter";
9
+ DATABASE_TABLES["TELEMETRY_QUEUE"] = "telemetry_queue";
10
+ DATABASE_TABLES["SPANS_QUEUE"] = "spans_queue";
9
11
  })(DATABASE_TABLES || (DATABASE_TABLES = {}));
10
12
  async function createSettingsTable() {
11
13
  await DBCursor.execAsync(`
@@ -110,6 +112,66 @@ async function createDomainEventsDeadLetterTable() {
110
112
  CREATE INDEX IF NOT EXISTS idx_dead_letter_failed ON ${DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER}(failed_at);
111
113
  `);
112
114
  }
115
+ async function createTelemetryQueueTable() {
116
+ await DBCursor.execAsync(`
117
+ CREATE TABLE IF NOT EXISTS ${DATABASE_TABLES.TELEMETRY_QUEUE} (
118
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
119
+ level TEXT NOT NULL,
120
+ message TEXT NOT NULL,
121
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
122
+ );
123
+ `);
124
+ await DBCursor.execAsync(`
125
+ CREATE INDEX IF NOT EXISTS idx_telemetry_queue_created
126
+ ON ${DATABASE_TABLES.TELEMETRY_QUEUE}(created_at);
127
+ `);
128
+ }
129
+ async function createSpansQueueTable() {
130
+ await DBCursor.execAsync(`
131
+ CREATE TABLE IF NOT EXISTS ${DATABASE_TABLES.SPANS_QUEUE} (
132
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
133
+ trace_id TEXT NOT NULL,
134
+ span_id TEXT NOT NULL,
135
+ parent_span_id TEXT,
136
+ name TEXT NOT NULL,
137
+ kind INTEGER NOT NULL DEFAULT 0,
138
+ start_time_unixnano TEXT NOT NULL,
139
+ end_time_unixnano TEXT NOT NULL,
140
+ attributes TEXT NOT NULL DEFAULT '{}',
141
+ status_code INTEGER NOT NULL DEFAULT 0,
142
+ status_message TEXT NOT NULL DEFAULT '',
143
+ resource_attrs TEXT NOT NULL DEFAULT '{}',
144
+ events TEXT NOT NULL DEFAULT '[]',
145
+ links TEXT NOT NULL DEFAULT '[]',
146
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
147
+ );
148
+ `);
149
+ await DBCursor.execAsync(`
150
+ CREATE INDEX IF NOT EXISTS idx_spans_queue_created
151
+ ON ${DATABASE_TABLES.SPANS_QUEUE}(created_at);
152
+ `);
153
+ await DBCursor.execAsync(`
154
+ CREATE INDEX IF NOT EXISTS idx_spans_queue_trace
155
+ ON ${DATABASE_TABLES.SPANS_QUEUE}(trace_id);
156
+ `);
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
+ }
113
175
  const MIGRATIONS = [
114
176
  { name: DATABASE_TABLES.SETTINGS, migrationFn: createSettingsTable },
115
177
  { name: DATABASE_TABLES.EVENT_QUEUE, migrationFn: createEventQueueTable },
@@ -119,5 +181,8 @@ const MIGRATIONS = [
119
181
  name: DATABASE_TABLES.DOMAIN_EVENTS_DEAD_LETTER,
120
182
  migrationFn: createDomainEventsDeadLetterTable,
121
183
  },
184
+ { name: DATABASE_TABLES.TELEMETRY_QUEUE, migrationFn: createTelemetryQueueTable },
185
+ { name: DATABASE_TABLES.SPANS_QUEUE, migrationFn: createSpansQueueTable },
186
+ { name: "spans_queue_v2_events_links", migrationFn: addSpansQueueEventsLinks },
122
187
  ];
123
188
  export default MIGRATIONS;
@@ -1,33 +1,9 @@
1
1
  import { loggerQueueProcessor } from "../../infrastructure/logger";
2
2
  import { EventBus } from "../../infrastructure/workers";
3
+ import { calculateDesiredSubscribers, statesAreEqual, } from "./subscriber_filter";
3
4
  let isRunning = false;
4
5
  let lastSyncState = null;
5
6
  let currentSubscribers = new Set();
6
- function statesAreEqual(a, b) {
7
- if (!a)
8
- return false;
9
- if (a.isAuthenticated !== b.isAuthenticated)
10
- return false;
11
- if (a.domains.size !== b.domains.size)
12
- return false;
13
- for (const domain of a.domains) {
14
- if (!b.domains.has(domain))
15
- return false;
16
- }
17
- return true;
18
- }
19
- function calculateDesiredSubscribers(domains, isAuthenticated, subscribersByKey) {
20
- const desired = new Set();
21
- for (const domain of domains) {
22
- const domainSubscribers = subscribersByKey[domain] ?? [];
23
- for (const subscriber of domainSubscribers) {
24
- if (isAuthenticated || !subscriber.requiresAuth) {
25
- desired.add(subscriber);
26
- }
27
- }
28
- }
29
- return desired;
30
- }
31
7
  export const QueueProcessor = {
32
8
  start: () => {
33
9
  if (isRunning)
@@ -0,0 +1,9 @@
1
+ export interface HasRequiresAuth {
2
+ readonly requiresAuth: boolean;
3
+ }
4
+ export interface SyncState {
5
+ domains: Set<string>;
6
+ isAuthenticated: boolean;
7
+ }
8
+ export declare function statesAreEqual(a: SyncState | null, b: SyncState): boolean;
9
+ export declare function calculateDesiredSubscribers<T extends HasRequiresAuth>(domains: Set<string>, isAuthenticated: boolean, subscribersByKey: Record<string, T[]>): Set<T>;
@@ -0,0 +1,25 @@
1
+ export function statesAreEqual(a, b) {
2
+ if (!a)
3
+ return false;
4
+ if (a.isAuthenticated !== b.isAuthenticated)
5
+ return false;
6
+ if (a.domains.size !== b.domains.size)
7
+ return false;
8
+ for (const domain of a.domains) {
9
+ if (!b.domains.has(domain))
10
+ return false;
11
+ }
12
+ return true;
13
+ }
14
+ export function calculateDesiredSubscribers(domains, isAuthenticated, subscribersByKey) {
15
+ const desired = new Set();
16
+ for (const domain of domains) {
17
+ const domainSubscribers = subscribersByKey[domain] ?? [];
18
+ for (const subscriber of domainSubscribers) {
19
+ if (isAuthenticated || !subscriber.requiresAuth) {
20
+ desired.add(subscriber);
21
+ }
22
+ }
23
+ }
24
+ return desired;
25
+ }
@@ -5,15 +5,69 @@ import { type ComponentType, type ReactNode } from "react";
5
5
  type ProviderComponent = ComponentType<{
6
6
  children: ReactNode;
7
7
  }>;
8
+ export interface BrandFontWeights {
9
+ light?: string;
10
+ regular?: string;
11
+ medium?: string;
12
+ semiBold?: string;
13
+ bold?: string;
14
+ extraBold?: string;
15
+ black?: string;
16
+ }
17
+ export interface BrandFontFamilies {
18
+ /** df — Display & headings. Brand character font. (Default: Inter) */
19
+ display?: BrandFontWeights;
20
+ /** bf — Body, labels, buttons. Interface text 13–15px. (Default: Inter) */
21
+ body?: BrandFontWeights;
22
+ /** cf — Captions, overlines, metadata. Uppercase + tracking. (Default: Fira Code) */
23
+ caption?: Pick<BrandFontWeights, "regular" | "medium">;
24
+ /** mono — Numeric data, SKU, amounts. Fixed-width. (Default: Fira Code) */
25
+ mono?: Pick<BrandFontWeights, "regular" | "medium">;
26
+ }
27
+ export interface BrandFontConfig {
28
+ /** Metro asset map: `{ "Satoshi-Bold": require("./assets/fonts/Satoshi-Bold.otf") }` */
29
+ files: Record<string, string | number>;
30
+ /** Semantic font family slots aligned with the brand manual (df / bf / cf / mono). */
31
+ families: BrandFontFamilies;
32
+ }
8
33
  export interface AppShellConfig {
34
+ /**
35
+ * Domain modules to bootstrap. Each module registers its own repositories,
36
+ * migrations, subscribers, and cron workers. The built-in `baseModule` is
37
+ * always included automatically — do not pass it here.
38
+ */
9
39
  domains: DomainModule[];
40
+ /**
41
+ * Map of domain key → screen component. Domains without a matching entry
42
+ * here are bootstrapped (events, repos, crons) but have no UI — they run
43
+ * silently in the background. Must contain at least `ui[activeDomain]`.
44
+ */
10
45
  ui: Record<string, ComponentType>;
46
+ /**
47
+ * Key of the domain whose UI is shown on launch. Must match one of the
48
+ * keys in `domains` AND have a corresponding entry in `ui`.
49
+ */
11
50
  activeDomain: string;
51
+ /**
52
+ * Additional React context providers to wrap around the app. Providers are
53
+ * applied in array order, outermost last — i.e. the last element in the
54
+ * array becomes the outermost wrapper and can observe all others.
55
+ *
56
+ * Example: `[ConfirmationProvider, ProcessToastProvider]`
57
+ */
12
58
  providers?: ProviderComponent[];
59
+ /** Light theme tokens. Defaults to the DS base theme if omitted. */
13
60
  theme?: ThemeTokens;
61
+ /** Dark theme tokens. When provided, the system color-scheme toggle activates it. */
14
62
  darkTheme?: ThemeTokens;
15
63
  branding?: BrandingConfig;
16
- /** Component rendered while fonts + domain init complete. Use `AppSplashView` from the DS or roll your own. */
64
+ /**
65
+ * Brand typeface declared as semantic slots (df / bf / cf / mono).
66
+ * The app owns all font asset files; the framework wires them into DS roles.
67
+ * Omitting a slot keeps the DS default (Inter for body, Fira Code for mono).
68
+ */
69
+ brandFont?: BrandFontConfig;
70
+ /** Component rendered while fonts + domain init complete. */
17
71
  splashComponent?: ComponentType;
18
72
  /** Minimum time (ms) the splash stays visible. Default 2000. */
19
73
  splashDuration?: number;
@@ -5,7 +5,7 @@ import { ToastHost } from "../../infrastructure/ui/ToastHost";
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";
8
- import { useAppFonts } from "@sincpro/mobile-ui/theme/typography";
8
+ import { configureFonts, useFonts } from "@sincpro/mobile-ui/theme/typography";
9
9
  import * as SplashScreen from "expo-splash-screen";
10
10
  import { vars } from "nativewind";
11
11
  import { useEffect, useState } from "react";
@@ -15,6 +15,47 @@ import { KeyboardProvider } from "react-native-keyboard-controller";
15
15
  import { initialWindowMetrics, SafeAreaProvider, SafeAreaView, } from "react-native-safe-area-context";
16
16
  import { CommonProvider } from "./common_provider";
17
17
  import { ActiveDomainApp, DomainSwitcherProvider } from "./domain_switcher";
18
+ /** Maps the semantic families object to the low-level DS FontRole record. */
19
+ function familiesToRoles(families) {
20
+ const roles = {};
21
+ const { display, body, caption, mono } = families;
22
+ if (display) {
23
+ if (display.regular)
24
+ roles.titleRegular = display.regular;
25
+ if (display.medium)
26
+ roles.titleMedium = display.medium;
27
+ if (display.bold)
28
+ roles.title = display.bold;
29
+ if (display.black)
30
+ roles.titleBlack = display.black;
31
+ }
32
+ if (body) {
33
+ if (body.light)
34
+ roles.light = body.light;
35
+ if (body.regular)
36
+ roles.regular = body.regular;
37
+ if (body.medium)
38
+ roles.medium = body.medium;
39
+ if (body.semiBold)
40
+ roles.semiBold = body.semiBold;
41
+ if (body.extraBold)
42
+ roles.extraBold = body.extraBold;
43
+ }
44
+ if (caption) {
45
+ if (caption.regular)
46
+ roles.mono = caption.regular;
47
+ if (caption.medium)
48
+ roles.monoMedium = caption.medium;
49
+ }
50
+ if (mono) {
51
+ if (mono.regular)
52
+ roles.mono = mono.regular;
53
+ if (mono.medium)
54
+ roles.monoMedium = mono.medium;
55
+ }
56
+ return roles;
57
+ }
58
+ /** Wraps children in the provider list. reduceRight → first item = innermost wrapper. */
18
59
  function withProviders(providers, children) {
19
60
  return providers.reduceRight((acc, Provider) => _jsx(Provider, { children: acc }), children);
20
61
  }
@@ -33,10 +74,14 @@ export function createAppShell(config) {
33
74
  if (config.branding) {
34
75
  setBranding(config.branding);
35
76
  }
77
+ // Wire brand font roles into the DS before first render.
78
+ if (config.brandFont?.families) {
79
+ configureFonts(familiesToRoles(config.brandFont.families));
80
+ }
36
81
  return function AppShell() {
37
82
  const [isReady, setIsReady] = useState(false);
38
83
  const [minDurationMet, setMinDurationMet] = useState(false);
39
- const fontsLoaded = useAppFonts();
84
+ const [brandFontsLoaded] = useFonts(config.brandFont?.files ?? {});
40
85
  const activeTheme = useTheme();
41
86
  const themeStyle = vars(themeToVars(activeTheme));
42
87
  const navTheme = {
@@ -61,11 +106,11 @@ export function createAppShell(config) {
61
106
  return () => clearTimeout(t);
62
107
  }, []);
63
108
  useEffect(() => {
64
- if (fontsLoaded && isReady && minDurationMet) {
109
+ if (brandFontsLoaded && isReady && minDurationMet) {
65
110
  SplashScreen.hideAsync().catch(() => { });
66
111
  }
67
- }, [fontsLoaded, isReady, minDurationMet]);
68
- if (!isReady || !fontsLoaded || !minDurationMet) {
112
+ }, [brandFontsLoaded, isReady, minDurationMet]);
113
+ if (!isReady || !brandFontsLoaded || !minDurationMet) {
69
114
  const Splash = config.splashComponent;
70
115
  return Splash ? (_jsx(Splash, {})) : (_jsx(View, { style: { flex: 1, backgroundColor: activeTheme.bg.page } }));
71
116
  }
@@ -1,15 +1,16 @@
1
1
  import { type DeepPartial, type ThemeTokens } from "@sincpro/mobile-ui/theme";
2
2
  /**
3
- * API simple y extensible para que los clientes definan su theme.
4
- * Parte de los defaults del framework y aplica overrides parciales (tipados).
3
+ * Build a fully-typed theme from a partial override object.
4
+ * Starts from the DS base theme and deep-merges your tokens on top.
5
5
  *
6
- * createApp({
7
- * theme: createTheme({ primary: "#FF6600", bg: { page: "#141414" } }),
8
- * domains: [...],
6
+ * createAppShell({
7
+ * theme: createTheme({ primary: "#FF6600", bg: { page: "#F5F5F5" } }),
8
+ * darkTheme: createTheme({ bg: { page: "#0A0A0A" }, primary: "#F5F5F5" }),
9
+ * ...
9
10
  * });
10
11
  *
11
- * El resultado se inyecta una vez al bootstrap (estático, sin runtime switching)
12
- * y propaga tanto a `className` (CSS vars) como a `style={theme.x}`.
12
+ * The result is injected once at bootstrap (static, not swapped at runtime)
13
+ * and propagates as both NativeWind CSS vars (`className`) and inline style props.
13
14
  */
14
15
  export declare function createTheme(overrides?: DeepPartial<ThemeTokens>): ThemeTokens;
15
16
  export type { DeepPartial, ThemeTokens };
@@ -1,15 +1,16 @@
1
1
  import { extendTheme } from "@sincpro/mobile-ui/theme";
2
2
  /**
3
- * API simple y extensible para que los clientes definan su theme.
4
- * Parte de los defaults del framework y aplica overrides parciales (tipados).
3
+ * Build a fully-typed theme from a partial override object.
4
+ * Starts from the DS base theme and deep-merges your tokens on top.
5
5
  *
6
- * createApp({
7
- * theme: createTheme({ primary: "#FF6600", bg: { page: "#141414" } }),
8
- * domains: [...],
6
+ * createAppShell({
7
+ * theme: createTheme({ primary: "#FF6600", bg: { page: "#F5F5F5" } }),
8
+ * darkTheme: createTheme({ bg: { page: "#0A0A0A" }, primary: "#F5F5F5" }),
9
+ * ...
9
10
  * });
10
11
  *
11
- * El resultado se inyecta una vez al bootstrap (estático, sin runtime switching)
12
- * y propaga tanto a `className` (CSS vars) como a `style={theme.x}`.
12
+ * The result is injected once at bootstrap (static, not swapped at runtime)
13
+ * and propagates as both NativeWind CSS vars (`className`) and inline style props.
13
14
  */
14
15
  export function createTheme(overrides) {
15
16
  return extendTheme(overrides);
@@ -10,5 +10,6 @@ export declare class BaseModule extends DomainModule {
10
10
  migrations(): IMigration[];
11
11
  subscribers(): Subscriber[];
12
12
  crons(): CronWorker[];
13
+ persistOnReset(): string[];
13
14
  }
14
15
  export declare const baseModule: BaseModule;
@@ -4,7 +4,7 @@ import { DomainEventDeadLetterRepository } from "../adapters/repositories/domain
4
4
  import { SettingsRepository } from "../adapters/repositories/setting.repository";
5
5
  import { ECommonRepository } from "../domain/repositories";
6
6
  import cronCheckNetworkStatus from "../entrypoints/cron/checkNetworkStatus.cron";
7
- import MIGRATIONS from "../entrypoints/db/migrations";
7
+ import MIGRATIONS, { DATABASE_TABLES } from "../entrypoints/db/migrations";
8
8
  import { ActivateDomainSubscriber } from "../entrypoints/queue/activateDomain.subscriber";
9
9
  import { NewAppSettingsSubscriber } from "../entrypoints/queue/newAppSettings.handler";
10
10
  import { PrintImageSubscriber } from "../entrypoints/queue/printImage.subscriber";
@@ -36,5 +36,10 @@ export class BaseModule extends DomainModule {
36
36
  crons() {
37
37
  return [cronCheckNetworkStatus];
38
38
  }
39
+ // Both telemetry tables survive restartDatabase() — data generated during
40
+ // logout must still be delivered; the flush cron runs independently of auth state.
41
+ persistOnReset() {
42
+ return [DATABASE_TABLES.TELEMETRY_QUEUE, DATABASE_TABLES.SPANS_QUEUE];
43
+ }
39
44
  }
40
45
  export const baseModule = new BaseModule();
@@ -1,6 +1,12 @@
1
+ import type { TelemetryConfig } from "../infrastructure/telemetry";
1
2
  import type { DomainModule } from "./domain_module";
2
3
  import { Kernel } from "./kernel";
3
4
  export interface AppConfig {
4
5
  domains?: DomainModule[];
6
+ /**
7
+ * Opt-in telemetry. When provided, the Loki client and flush cron start automatically
8
+ * after migrations complete — so the `telemetry_queue` table is guaranteed to exist.
9
+ */
10
+ telemetry?: TelemetryConfig;
5
11
  }
6
12
  export declare function createApp(config?: AppConfig): Promise<Kernel>;
@@ -1,9 +1,13 @@
1
+ import { initTelemetry } from "../infrastructure/telemetry";
1
2
  import { baseModule } from "./base_module";
2
3
  import { Kernel } from "./kernel";
3
4
  import { orchestrator } from "./orchestrator";
4
5
  export async function createApp(config = {}) {
5
6
  const kernel = new Kernel([baseModule, ...(config.domains ?? [])]);
6
7
  orchestrator.configure(kernel);
7
- await orchestrator.bootstrap();
8
+ await orchestrator.bootstrap(); // migrations run here — telemetry_queue is ready
9
+ if (config.telemetry) {
10
+ await initTelemetry(config.telemetry);
11
+ }
8
12
  return kernel;
9
13
  }
package/dist/index.d.ts CHANGED
@@ -13,3 +13,9 @@ export { DomainModule } from "./framework/domain_module";
13
13
  export { Kernel } from "./framework/kernel";
14
14
  export { orchestrator } from "./framework/orchestrator";
15
15
  export { installGlobalErrorHandler } from "./infrastructure/ui/errorHandler";
16
+ export type { InterceptorHooks, MethodCall, } from "./infrastructure/interceptor/intercept";
17
+ export { interceptClass, interceptFunction, interceptInstance, } from "./infrastructure/interceptor/intercept";
18
+ export type { Logger } from "./infrastructure/interceptor/logging_hooks";
19
+ export { loggingHooks } from "./infrastructure/interceptor/logging_hooks";
20
+ export type { LokiAuth, LokiConfig, TelemetryConfig, } from "./infrastructure/telemetry";
21
+ export { initTelemetry } from "./infrastructure/telemetry";
package/dist/index.js CHANGED
@@ -8,3 +8,6 @@ export { DomainModule } from "./framework/domain_module";
8
8
  export { Kernel } from "./framework/kernel";
9
9
  export { orchestrator } from "./framework/orchestrator";
10
10
  export { installGlobalErrorHandler } from "./infrastructure/ui/errorHandler";
11
+ export { interceptClass, interceptFunction, interceptInstance, } from "./infrastructure/interceptor/intercept";
12
+ export { loggingHooks } from "./infrastructure/interceptor/logging_hooks";
13
+ export { initTelemetry } from "./infrastructure/telemetry";
@@ -0,0 +1,16 @@
1
+ import type { ContextKey } from "./context_key";
2
+ /**
3
+ * Immutable execution-scoped key-value store.
4
+ *
5
+ * Every modification returns a new Context — the original is never mutated.
6
+ * This makes it safe to pass across async boundaries without defensive copies.
7
+ */
8
+ export declare class Context {
9
+ private readonly _map;
10
+ private constructor();
11
+ static readonly ROOT: Context;
12
+ get<T>(key: ContextKey<T>): T | undefined;
13
+ set<T>(key: ContextKey<T>, value: T): Context;
14
+ delete<T>(key: ContextKey<T>): Context;
15
+ describe(): string;
16
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Immutable execution-scoped key-value store.
3
+ *
4
+ * Every modification returns a new Context — the original is never mutated.
5
+ * This makes it safe to pass across async boundaries without defensive copies.
6
+ */
7
+ export class Context {
8
+ _map;
9
+ constructor(map) {
10
+ this._map = map;
11
+ }
12
+ static ROOT = new Context(new Map());
13
+ get(key) {
14
+ return this._map.get(key._symbol);
15
+ }
16
+ set(key, value) {
17
+ const next = new Map(this._map);
18
+ next.set(key._symbol, value);
19
+ return new Context(next);
20
+ }
21
+ delete(key) {
22
+ const next = new Map(this._map);
23
+ next.delete(key._symbol);
24
+ return new Context(next);
25
+ }
26
+ describe() {
27
+ const parts = [];
28
+ for (const [sym, val] of this._map) {
29
+ const name = sym.description ?? "unknown";
30
+ const repr = val !== null && typeof val === "object"
31
+ ? (val.constructor?.name ?? "Object")
32
+ : String(val);
33
+ parts.push(`${name}: ${repr}`);
34
+ }
35
+ return `Context { ${parts.join(", ") || "empty"} }`;
36
+ }
37
+ }
@@ -0,0 +1,28 @@
1
+ import { Context } from "./context";
2
+ import type { ContextKey } from "./context_key";
3
+ import { StackContextManager } from "./stack_context_manager";
4
+ /** Returns the context active in the current execution scope. */
5
+ export declare function getActiveContext(): Context;
6
+ /**
7
+ * Reads a value from the active context.
8
+ * Equivalent to getActiveContext().get(key).
9
+ */
10
+ export declare function getContext<T>(key: ContextKey<T>): T | undefined;
11
+ /**
12
+ * Runs fn within ctx as the active context.
13
+ *
14
+ * For async functions, ctx stays active until the returned Promise resolves or
15
+ * rejects — not just until the first await. This means nested calls at any
16
+ * point in the async chain see the correct context.
17
+ *
18
+ * For sync functions, ctx is active for the duration of the call.
19
+ */
20
+ export declare function runWithContext<T>(ctx: Context, fn: () => T): T;
21
+ /**
22
+ * Returns the underlying manager.
23
+ * Used by interceptor hooks (push/pop) and by adapters (e.g. OTel bridge).
24
+ * @internal
25
+ */
26
+ export declare function _getContextManager(): StackContextManager;
27
+ /** @internal — test use only */
28
+ export declare function _resetContextManager(): void;
@@ -0,0 +1,55 @@
1
+ import { StackContextManager } from "./stack_context_manager";
2
+ const _manager = new StackContextManager();
3
+ /** Returns the context active in the current execution scope. */
4
+ export function getActiveContext() {
5
+ return _manager.active();
6
+ }
7
+ /**
8
+ * Reads a value from the active context.
9
+ * Equivalent to getActiveContext().get(key).
10
+ */
11
+ export function getContext(key) {
12
+ return _manager.active().get(key);
13
+ }
14
+ /**
15
+ * Runs fn within ctx as the active context.
16
+ *
17
+ * For async functions, ctx stays active until the returned Promise resolves or
18
+ * rejects — not just until the first await. This means nested calls at any
19
+ * point in the async chain see the correct context.
20
+ *
21
+ * For sync functions, ctx is active for the duration of the call.
22
+ */
23
+ export function runWithContext(ctx, fn) {
24
+ _manager.push(ctx);
25
+ try {
26
+ const result = fn();
27
+ if (result instanceof Promise) {
28
+ return result.then((v) => {
29
+ _manager.pop();
30
+ return v;
31
+ }, (e) => {
32
+ _manager.pop();
33
+ throw e;
34
+ });
35
+ }
36
+ _manager.pop();
37
+ return result;
38
+ }
39
+ catch (e) {
40
+ _manager.pop();
41
+ throw e;
42
+ }
43
+ }
44
+ /**
45
+ * Returns the underlying manager.
46
+ * Used by interceptor hooks (push/pop) and by adapters (e.g. OTel bridge).
47
+ * @internal
48
+ */
49
+ export function _getContextManager() {
50
+ return _manager;
51
+ }
52
+ /** @internal — test use only */
53
+ export function _resetContextManager() {
54
+ _manager.reset();
55
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Opaque typed key for storing values in a Context.
3
+ *
4
+ * Two calls to createContextKey with the same description produce different
5
+ * keys — collision between modules is impossible even if they share names.
6
+ *
7
+ * _type_marker is a phantom field: it only exists in the TypeScript type
8
+ * system to carry T through inference. It is never assigned at runtime.
9
+ */
10
+ export type ContextKey<T> = {
11
+ readonly description: string;
12
+ readonly _symbol: symbol;
13
+ readonly _type_marker: T;
14
+ };
15
+ export declare function createContextKey<T>(description: string): ContextKey<T>;
@@ -0,0 +1,3 @@
1
+ export function createContextKey(description) {
2
+ return { description, _symbol: Symbol(description) };
3
+ }