@sincpro/mobile 0.3.1 → 0.4.2

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 (100) hide show
  1. package/README.md +234 -2
  2. package/dist/entrypoints/cron/EBuiltinCron.d.ts +4 -0
  3. package/dist/entrypoints/cron/EBuiltinCron.js +5 -0
  4. package/dist/entrypoints/cron/checkNetworkStatus.cron.js +2 -1
  5. package/dist/entrypoints/db/migrations.d.ts +3 -1
  6. package/dist/entrypoints/db/migrations.js +47 -0
  7. package/dist/entrypoints/queue/QueueProcessor.js +1 -25
  8. package/dist/entrypoints/queue/subscriber_filter.d.ts +9 -0
  9. package/dist/entrypoints/queue/subscriber_filter.js +25 -0
  10. package/dist/entrypoints/ui/common_provider.js +2 -2
  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 +14 -0
  14. package/dist/framework/createApp.js +6 -0
  15. package/dist/framework/kernel.d.ts +2 -1
  16. package/dist/framework/kernel.js +8 -0
  17. package/dist/index.d.ts +7 -0
  18. package/dist/index.js +4 -0
  19. package/dist/infrastructure/context_manager/context.d.ts +16 -0
  20. package/dist/infrastructure/context_manager/context.js +37 -0
  21. package/dist/infrastructure/context_manager/context_api.d.ts +28 -0
  22. package/dist/infrastructure/context_manager/context_api.js +55 -0
  23. package/dist/infrastructure/context_manager/context_key.d.ts +15 -0
  24. package/dist/infrastructure/context_manager/context_key.js +3 -0
  25. package/dist/infrastructure/context_manager/context_manager_facade.d.ts +107 -0
  26. package/dist/infrastructure/context_manager/context_manager_facade.js +113 -0
  27. package/dist/infrastructure/context_manager/decorators/propagate_context.d.ts +24 -0
  28. package/dist/infrastructure/context_manager/decorators/propagate_context.js +52 -0
  29. package/dist/infrastructure/context_manager/decorators/with_context.d.ts +34 -0
  30. package/dist/infrastructure/context_manager/decorators/with_context.js +44 -0
  31. package/dist/infrastructure/context_manager/index.d.ts +11 -0
  32. package/dist/infrastructure/context_manager/index.js +12 -0
  33. package/dist/infrastructure/context_manager/managed_context.d.ts +87 -0
  34. package/dist/infrastructure/context_manager/managed_context.js +144 -0
  35. package/dist/infrastructure/context_manager/stack_context_manager.d.ts +39 -0
  36. package/dist/infrastructure/context_manager/stack_context_manager.js +56 -0
  37. package/dist/infrastructure/database/connector.js +10 -1
  38. package/dist/infrastructure/interceptor/index.d.ts +4 -0
  39. package/dist/infrastructure/interceptor/index.js +2 -0
  40. package/dist/infrastructure/interceptor/intercept.d.ts +57 -0
  41. package/dist/infrastructure/interceptor/intercept.js +112 -0
  42. package/dist/infrastructure/interceptor/logging_hooks.d.ts +22 -0
  43. package/dist/infrastructure/interceptor/logging_hooks.js +27 -0
  44. package/dist/infrastructure/logger.d.ts +11 -16
  45. package/dist/infrastructure/logger.js +61 -18
  46. package/dist/infrastructure/telemetry/buffer_registry.d.ts +15 -0
  47. package/dist/infrastructure/telemetry/buffer_registry.js +26 -0
  48. package/dist/infrastructure/telemetry/connectivity_state.d.ts +20 -0
  49. package/dist/infrastructure/telemetry/connectivity_state.js +28 -0
  50. package/dist/infrastructure/telemetry/context_keys.d.ts +7 -0
  51. package/dist/infrastructure/telemetry/context_keys.js +7 -0
  52. package/dist/infrastructure/telemetry/fetch_with_timeout.d.ts +8 -0
  53. package/dist/infrastructure/telemetry/fetch_with_timeout.js +17 -0
  54. package/dist/infrastructure/telemetry/flush_telemetry.d.ts +40 -0
  55. package/dist/infrastructure/telemetry/flush_telemetry.js +72 -0
  56. package/dist/infrastructure/telemetry/index.d.ts +17 -0
  57. package/dist/infrastructure/telemetry/index.js +17 -0
  58. package/dist/infrastructure/telemetry/init_telemetry.d.ts +21 -0
  59. package/dist/infrastructure/telemetry/init_telemetry.js +85 -0
  60. package/dist/infrastructure/telemetry/logging/flush_job.d.ts +44 -0
  61. package/dist/infrastructure/telemetry/logging/flush_job.js +69 -0
  62. package/dist/infrastructure/telemetry/logging/log_queue_repository.d.ts +76 -0
  63. package/dist/infrastructure/telemetry/logging/log_queue_repository.js +147 -0
  64. package/dist/infrastructure/telemetry/logging/loki_client.d.ts +32 -0
  65. package/dist/infrastructure/telemetry/logging/loki_client.js +118 -0
  66. package/dist/infrastructure/telemetry/logging/loki_registry.d.ts +12 -0
  67. package/dist/infrastructure/telemetry/logging/loki_registry.js +17 -0
  68. package/dist/infrastructure/telemetry/telemetry_flush_worker.d.ts +35 -0
  69. package/dist/infrastructure/telemetry/telemetry_flush_worker.js +66 -0
  70. package/dist/infrastructure/telemetry/telemetry_signal.d.ts +18 -0
  71. package/dist/infrastructure/telemetry/telemetry_signal.js +33 -0
  72. package/dist/infrastructure/telemetry/tracing/active_span.d.ts +22 -0
  73. package/dist/infrastructure/telemetry/tracing/active_span.js +40 -0
  74. package/dist/infrastructure/telemetry/tracing/otlp_client.d.ts +13 -0
  75. package/dist/infrastructure/telemetry/tracing/otlp_client.js +34 -0
  76. package/dist/infrastructure/telemetry/tracing/otlp_serializer.d.ts +67 -0
  77. package/dist/infrastructure/telemetry/tracing/otlp_serializer.js +101 -0
  78. package/dist/infrastructure/telemetry/tracing/span_flush_job.d.ts +25 -0
  79. package/dist/infrastructure/telemetry/tracing/span_flush_job.js +42 -0
  80. package/dist/infrastructure/telemetry/tracing/span_queue_repository.d.ts +82 -0
  81. package/dist/infrastructure/telemetry/tracing/span_queue_repository.js +134 -0
  82. package/dist/infrastructure/telemetry/tracing/span_sampler.d.ts +40 -0
  83. package/dist/infrastructure/telemetry/tracing/span_sampler.js +75 -0
  84. package/dist/infrastructure/telemetry/tracing/span_serializer.d.ts +48 -0
  85. package/dist/infrastructure/telemetry/tracing/span_serializer.js +36 -0
  86. package/dist/infrastructure/telemetry/tracing/sqlite_span_exporter.d.ts +26 -0
  87. package/dist/infrastructure/telemetry/tracing/sqlite_span_exporter.js +47 -0
  88. package/dist/infrastructure/telemetry/tracing/tracer.d.ts +22 -0
  89. package/dist/infrastructure/telemetry/tracing/tracer.js +45 -0
  90. package/dist/infrastructure/telemetry/tracing/tracing_hooks.d.ts +10 -0
  91. package/dist/infrastructure/telemetry/tracing/tracing_hooks.js +59 -0
  92. package/dist/infrastructure/telemetry/tracing_facade.d.ts +50 -0
  93. package/dist/infrastructure/telemetry/tracing_facade.js +139 -0
  94. package/dist/infrastructure/telemetry/types.d.ts +120 -0
  95. package/dist/infrastructure/telemetry/types.js +1 -0
  96. package/dist/infrastructure/workers/CronWorker.d.ts +10 -2
  97. package/dist/infrastructure/workers/CronWorker.js +18 -5
  98. package/dist/infrastructure/workers/index.d.ts +1 -1
  99. package/dist/ui/components/molecules/TimeZoneSelectorModal.js +7 -1
  100. package/package.json +17 -11
@@ -0,0 +1,22 @@
1
+ import type { SpanQueueRepository } from "./span_queue_repository";
2
+ export interface TracingResourceConfig {
3
+ serviceName?: string;
4
+ serviceVersion?: string;
5
+ deploymentEnvironment?: string;
6
+ /** Extra OTel resource attributes (device.*, os.*, tenant, …). */
7
+ extra?: Record<string, string>;
8
+ }
9
+ /**
10
+ * @param queue - repository used by the exporter to buffer spans
11
+ * @param persist - when false the TracerProvider is registered (decorators work)
12
+ * but spans are not written to SQLite; pass true only when an
13
+ * OTLP destination is configured and flush is active
14
+ * @param onEnqueued - called after spans are buffered, so the pipeline can
15
+ * opportunistically attempt delivery (store-first)
16
+ * @param resourceConfig - OTel resource attributes; without `serviceName` spans appear
17
+ * as `unknown_service` in Grafana/Tempo
18
+ */
19
+ export declare function initTracing(queue: SpanQueueRepository, persist?: boolean, onEnqueued?: () => void, resourceConfig?: TracingResourceConfig): void;
20
+ /** @internal — test use only */
21
+ export declare function _resetTracing(): void;
22
+ export declare function getTracer(name: string): import("@opentelemetry/api").Tracer;
@@ -0,0 +1,45 @@
1
+ import { trace } from "@opentelemetry/api";
2
+ import { resourceFromAttributes } from "@opentelemetry/resources";
3
+ import { BasicTracerProvider, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
4
+ import { SQLiteSpanExporter } from "./sqlite_span_exporter";
5
+ let _provider = null;
6
+ /**
7
+ * @param queue - repository used by the exporter to buffer spans
8
+ * @param persist - when false the TracerProvider is registered (decorators work)
9
+ * but spans are not written to SQLite; pass true only when an
10
+ * OTLP destination is configured and flush is active
11
+ * @param onEnqueued - called after spans are buffered, so the pipeline can
12
+ * opportunistically attempt delivery (store-first)
13
+ * @param resourceConfig - OTel resource attributes; without `serviceName` spans appear
14
+ * as `unknown_service` in Grafana/Tempo
15
+ */
16
+ export function initTracing(queue, persist = true, onEnqueued, resourceConfig) {
17
+ _provider?.shutdown().catch(() => { });
18
+ const rawAttrs = {};
19
+ if (resourceConfig?.serviceName)
20
+ rawAttrs["service.name"] = resourceConfig.serviceName;
21
+ if (resourceConfig?.serviceVersion)
22
+ rawAttrs["service.version"] = resourceConfig.serviceVersion;
23
+ if (resourceConfig?.deploymentEnvironment)
24
+ rawAttrs["deployment.environment"] = resourceConfig.deploymentEnvironment;
25
+ if (resourceConfig?.extra)
26
+ Object.assign(rawAttrs, resourceConfig.extra);
27
+ const resource = Object.keys(rawAttrs).length > 0 ? resourceFromAttributes(rawAttrs) : undefined;
28
+ const provider = new BasicTracerProvider({
29
+ resource,
30
+ spanProcessors: [
31
+ new SimpleSpanProcessor(new SQLiteSpanExporter(queue, persist, onEnqueued)),
32
+ ],
33
+ });
34
+ trace.setGlobalTracerProvider(provider);
35
+ _provider = provider;
36
+ }
37
+ /** @internal — test use only */
38
+ export function _resetTracing() {
39
+ _provider?.shutdown().catch(() => { });
40
+ _provider = null;
41
+ trace.disable();
42
+ }
43
+ export function getTracer(name) {
44
+ return trace.getTracer(name);
45
+ }
@@ -0,0 +1,10 @@
1
+ import type { InterceptorHooks } from "../../interceptor/intercept";
2
+ /**
3
+ * Returns InterceptorHooks that record an OTel span for every intercepted
4
+ * method call. Parent-child relationships are maintained through the framework
5
+ * StackContextManager: the OTel context (with the active span) is stored as a
6
+ * value inside our Context, pushed in `before` and popped in `after`/`onError`.
7
+ *
8
+ * @param tracerName - instrumentation scope name, e.g. "sincpro.mobile.checkout"
9
+ */
10
+ export declare function tracingHooks(tracerName: string): InterceptorHooks;
@@ -0,0 +1,59 @@
1
+ import { ROOT_CONTEXT as OTEL_ROOT, SpanStatusCode, trace } from "@opentelemetry/api";
2
+ import { _getContextManager } from "../../context_manager/index";
3
+ import { OTEL_CTX_KEY } from "../context_keys";
4
+ const _spanMap = new WeakMap();
5
+ // Tracks whether `before` successfully pushed a context entry for this call.
6
+ // Guards `after`/`onError` against popping a context they never pushed.
7
+ const _pushedMap = new WeakSet();
8
+ /**
9
+ * Returns InterceptorHooks that record an OTel span for every intercepted
10
+ * method call. Parent-child relationships are maintained through the framework
11
+ * StackContextManager: the OTel context (with the active span) is stored as a
12
+ * value inside our Context, pushed in `before` and popped in `after`/`onError`.
13
+ *
14
+ * @param tracerName - instrumentation scope name, e.g. "sincpro.mobile.checkout"
15
+ */
16
+ export function tracingHooks(tracerName) {
17
+ return {
18
+ before(call) {
19
+ const manager = _getContextManager();
20
+ const frameworkCtx = manager.active();
21
+ // Read the OTel context carried inside our framework context.
22
+ // Falls back to OTel ROOT if tracing has not been initialized yet.
23
+ const otelCtx = frameworkCtx.get(OTEL_CTX_KEY) ?? OTEL_ROOT;
24
+ const spanName = call.className
25
+ ? `${call.className}.${call.methodName}`
26
+ : call.methodName;
27
+ const span = trace.getTracer(tracerName).startSpan(spanName, undefined, otelCtx);
28
+ _spanMap.set(call, span);
29
+ // Store the updated OTel context (now containing this span) back into
30
+ // the framework context and push it onto the stack so nested calls see
31
+ // this span as their parent.
32
+ const newOtelCtx = trace.setSpan(otelCtx, span);
33
+ manager.push(frameworkCtx.set(OTEL_CTX_KEY, newOtelCtx));
34
+ _pushedMap.add(call);
35
+ },
36
+ after(call) {
37
+ const span = _spanMap.get(call);
38
+ if (!span)
39
+ return;
40
+ _spanMap.delete(call);
41
+ span.setStatus({ code: SpanStatusCode.OK });
42
+ span.end();
43
+ if (_pushedMap.delete(call))
44
+ _getContextManager().pop();
45
+ },
46
+ onError(call, error) {
47
+ const span = _spanMap.get(call);
48
+ if (!span)
49
+ return;
50
+ _spanMap.delete(call);
51
+ if (error instanceof Error)
52
+ span.recordException(error);
53
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error?.message });
54
+ span.end();
55
+ if (_pushedMap.delete(call))
56
+ _getContextManager().pop();
57
+ },
58
+ };
59
+ }
@@ -0,0 +1,50 @@
1
+ import type { Attributes, Span, SpanContext } from "@opentelemetry/api";
2
+ import { bufferStats } from "./buffer_registry";
3
+ import { initTelemetry } from "./init_telemetry";
4
+ import { getTracer as _getTracer } from "./tracing/tracer";
5
+ export declare function withSpan<T>(name: string, fn: (span: Span) => T | Promise<T>, opts?: {
6
+ attributes?: Attributes;
7
+ tracerName?: string;
8
+ }): Promise<T>;
9
+ export interface TraceOptions {
10
+ name?: string;
11
+ attributes?: Attributes;
12
+ tracerName?: string;
13
+ }
14
+ export declare function Trace(opts?: TraceOptions): (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
15
+ export declare function TraceClass(tracerName: string): <T extends new (...args: never[]) => object>(target: T) => T;
16
+ export declare const Tracing: {
17
+ /** Initializes telemetry (Loki + OTel). Call once at app startup. */
18
+ readonly init: typeof initTelemetry;
19
+ /** Wraps a block of code in a span. Handles start/end/error automatically. */
20
+ readonly withSpan: typeof withSpan;
21
+ /** Returns the active SpanContext ({ traceId, spanId }) or null if no span is active. */
22
+ readonly activeSpan: () => SpanContext | null;
23
+ /** Log suffix ready to append: " trace_id=xxx span_id=xxx" or "". */
24
+ readonly logSuffix: () => string;
25
+ /**
26
+ * Current size and drop pressure of the offline telemetry buffers.
27
+ * A rising `dropped` count means the device has been offline long enough
28
+ * to lose data. Surface it in health checks / dashboards.
29
+ */
30
+ readonly bufferStats: typeof bufferStats;
31
+ /** Direct access to the OTel tracer for advanced use cases. */
32
+ readonly getTracer: typeof _getTracer;
33
+ /**
34
+ * Method decorator — creates a span per invocation.
35
+ * @example
36
+ * class CheckoutUseCase {
37
+ * \@Tracing.Trace()
38
+ * async process(order: Order) { ... }
39
+ * }
40
+ */
41
+ readonly Trace: typeof Trace;
42
+ /**
43
+ * Class decorator — creates a span per public method and propagates context.
44
+ * Span name: `"<tracerName>.<methodName>"`.
45
+ * @example
46
+ * \@Tracing.TraceClass("checkout")
47
+ * class CheckoutUseCase { ... }
48
+ */
49
+ readonly TraceClass: typeof TraceClass;
50
+ };
@@ -0,0 +1,139 @@
1
+ import { ROOT_CONTEXT as OTEL_ROOT, SpanStatusCode, trace } from "@opentelemetry/api";
2
+ import { _getContextManager, PropagateContext, } from "../../infrastructure/context_manager/index";
3
+ import { interceptClass } from "../../infrastructure/interceptor/intercept";
4
+ import { bufferStats } from "./buffer_registry";
5
+ import { OTEL_CTX_KEY } from "./context_keys";
6
+ import { initTelemetry } from "./init_telemetry";
7
+ import { activeTraceLabel, getActiveSpanContext } from "./tracing/active_span";
8
+ import { getTracer as _getTracer } from "./tracing/tracer";
9
+ import { tracingHooks } from "./tracing/tracing_hooks";
10
+ // ---------------------------------------------------------------------------
11
+ // withSpan — manual span block with automatic lifecycle
12
+ // ---------------------------------------------------------------------------
13
+ export async function withSpan(name, fn, opts) {
14
+ const manager = _getContextManager();
15
+ const frameworkCtx = manager.active();
16
+ const otelCtx = frameworkCtx.get(OTEL_CTX_KEY) ?? OTEL_ROOT;
17
+ const tracer = trace.getTracer(opts?.tracerName ?? "sincpro.mobile");
18
+ const span = tracer.startSpan(name, { attributes: opts?.attributes }, otelCtx);
19
+ const newOtelCtx = trace.setSpan(otelCtx, span);
20
+ manager.push(frameworkCtx.set(OTEL_CTX_KEY, newOtelCtx));
21
+ try {
22
+ const result = await Promise.resolve(fn(span));
23
+ span.setStatus({ code: SpanStatusCode.OK });
24
+ return result;
25
+ }
26
+ catch (error) {
27
+ if (error instanceof Error)
28
+ span.recordException(error);
29
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error?.message });
30
+ throw error;
31
+ }
32
+ finally {
33
+ span.end();
34
+ manager.pop();
35
+ }
36
+ }
37
+ export function Trace(opts) {
38
+ return function (target, propertyKey, descriptor) {
39
+ const original = descriptor.value;
40
+ const className = target.constructor.name ?? "";
41
+ descriptor.value = function (...args) {
42
+ const spanName = opts?.name ?? (className ? `${className}.${propertyKey}` : propertyKey);
43
+ const manager = _getContextManager();
44
+ const frameworkCtx = manager.active();
45
+ const otelCtx = frameworkCtx.get(OTEL_CTX_KEY) ?? OTEL_ROOT;
46
+ const tracer = trace.getTracer(opts?.tracerName ?? "sincpro.mobile");
47
+ const span = tracer.startSpan(spanName, { attributes: opts?.attributes }, otelCtx);
48
+ manager.push(frameworkCtx.set(OTEL_CTX_KEY, trace.setSpan(otelCtx, span)));
49
+ let result;
50
+ try {
51
+ result = original.apply(this, args);
52
+ }
53
+ catch (error) {
54
+ // Sync method threw before returning — record and clean up immediately.
55
+ if (error instanceof Error)
56
+ span.recordException(error);
57
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error?.message });
58
+ span.end();
59
+ manager.pop();
60
+ throw error;
61
+ }
62
+ if (result instanceof Promise) {
63
+ return result.then((value) => {
64
+ span.setStatus({ code: SpanStatusCode.OK });
65
+ span.end();
66
+ manager.pop();
67
+ return value;
68
+ }, (error) => {
69
+ if (error instanceof Error)
70
+ span.recordException(error);
71
+ span.setStatus({
72
+ code: SpanStatusCode.ERROR,
73
+ message: error?.message,
74
+ });
75
+ span.end();
76
+ manager.pop();
77
+ throw error;
78
+ });
79
+ }
80
+ // Sync success path.
81
+ span.setStatus({ code: SpanStatusCode.OK });
82
+ span.end();
83
+ manager.pop();
84
+ return result;
85
+ };
86
+ return descriptor;
87
+ };
88
+ }
89
+ // ---------------------------------------------------------------------------
90
+ // @TraceClass — class decorator: PropagateContext + span per method
91
+ // ---------------------------------------------------------------------------
92
+ export function TraceClass(tracerName) {
93
+ return function (target) {
94
+ const traced = interceptClass(target, tracingHooks(tracerName));
95
+ return PropagateContext(traced);
96
+ };
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // Public facade
100
+ // ---------------------------------------------------------------------------
101
+ export const Tracing = {
102
+ /** Initializes telemetry (Loki + OTel). Call once at app startup. */
103
+ init: initTelemetry,
104
+ /** Wraps a block of code in a span. Handles start/end/error automatically. */
105
+ withSpan,
106
+ /** Returns the active SpanContext ({ traceId, spanId }) or null if no span is active. */
107
+ activeSpan() {
108
+ return getActiveSpanContext();
109
+ },
110
+ /** Log suffix ready to append: " trace_id=xxx span_id=xxx" or "". */
111
+ logSuffix() {
112
+ return activeTraceLabel();
113
+ },
114
+ /**
115
+ * Current size and drop pressure of the offline telemetry buffers.
116
+ * A rising `dropped` count means the device has been offline long enough
117
+ * to lose data. Surface it in health checks / dashboards.
118
+ */
119
+ bufferStats,
120
+ /** Direct access to the OTel tracer for advanced use cases. */
121
+ getTracer: _getTracer,
122
+ /**
123
+ * Method decorator — creates a span per invocation.
124
+ * @example
125
+ * class CheckoutUseCase {
126
+ * \@Tracing.Trace()
127
+ * async process(order: Order) { ... }
128
+ * }
129
+ */
130
+ Trace,
131
+ /**
132
+ * Class decorator — creates a span per public method and propagates context.
133
+ * Span name: `"<tracerName>.<methodName>"`.
134
+ * @example
135
+ * \@Tracing.TraceClass("checkout")
136
+ * class CheckoutUseCase { ... }
137
+ */
138
+ TraceClass,
139
+ };
@@ -0,0 +1,120 @@
1
+ export type LokiAuth = {
2
+ type: "basic";
3
+ username: string;
4
+ password: string;
5
+ } | {
6
+ type: "bearer";
7
+ token: string;
8
+ };
9
+ export interface LokiConfig {
10
+ /** Base URL of the Loki instance, e.g. "https://loki.myserver.com" */
11
+ endpoint: string;
12
+ /** Labels attached to every log stream (app, env, tenant, …) */
13
+ labels: Record<string, string>;
14
+ /**
15
+ * Standard auth shortcut (Basic / Bearer). For anything else use `headers`.
16
+ * Omit both for unauthenticated (internal/self-hosted) setups.
17
+ */
18
+ auth?: LokiAuth;
19
+ /**
20
+ * Arbitrary headers added to every Loki request — for gateways/proxies that
21
+ * authenticate with custom keys (e.g. `{ "api-key": "…" }`,
22
+ * `{ "sincpro-api-key": "…" }`, `{ "X-Scope-OrgID": "tenant-1" }`).
23
+ * Merged after `auth`, so an explicit `Authorization` here overrides `auth`.
24
+ */
25
+ headers?: Record<string, string>;
26
+ }
27
+ export interface OtlpConfig {
28
+ /** OTLP/HTTP collector base URL, e.g. "https://alloy.myserver.com" */
29
+ endpoint: string;
30
+ /** Optional headers (Authorization, X-Scope-OrgID, …) */
31
+ headers?: Record<string, string>;
32
+ }
33
+ export interface FlushConfig {
34
+ /**
35
+ * Background safety-net interval in minutes (≥15 → real OS background task).
36
+ * Set to 0 to disable the cron entirely and rely only on the production
37
+ * signal (and connectivity events, if enabled). Default 15.
38
+ */
39
+ backgroundIntervalMin?: number;
40
+ /**
41
+ * Subscribe to the app's InternetIsUp/Down events to drain the backlog the
42
+ * moment connectivity returns. Off by default — the framework does not
43
+ * require the app's connectivity cron. Default false.
44
+ */
45
+ onConnectivityEvents?: boolean;
46
+ /** HTTP send timeout in ms (fail fast instead of hanging). Default 4000. */
47
+ sendTimeoutMs?: number;
48
+ /** Coalescing window in ms for production-triggered flushes. Default 3000. */
49
+ debounceMs?: number;
50
+ }
51
+ export interface TelemetryConfig {
52
+ /**
53
+ * Name of this app/service as it appears in Grafana/Tempo/Jaeger.
54
+ * Becomes the OTel resource `service.name` attribute on every span.
55
+ * Without it, spans are grouped under `unknown_service`, making multi-app
56
+ * setups impossible to filter. Example: `"pos-mobile"`, `"warehouse-mobile"`.
57
+ */
58
+ serviceName?: string;
59
+ /**
60
+ * Semver of the deployed app. Becomes `service.version` on every span.
61
+ * Enables filtering by version in Grafana Tempo and alerting on regressions.
62
+ * Example: `"1.4.2"`.
63
+ */
64
+ serviceVersion?: string;
65
+ /**
66
+ * Deployment tier. Becomes `deployment.environment` on every span.
67
+ * Separates dev/staging/production traces in Grafana without label tricks.
68
+ * Example: `"production"`, `"staging"`, `"development"`.
69
+ */
70
+ deploymentEnvironment?: string;
71
+ /**
72
+ * Extra OTel resource attributes merged onto every span. Use for device,
73
+ * OS, tenant, or any dimension not covered above.
74
+ * Common keys (OTel semantic conventions):
75
+ * `"device.model.name"` — e.g. "iPhone 16 Pro"
76
+ * `"os.type"` — `Platform.OS` ("ios" | "android")
77
+ * `"os.version"` — `String(Platform.Version)`
78
+ * `"device.id"` — opaque installation ID
79
+ * Values must be strings (OTel resource attributes are always string here).
80
+ */
81
+ resourceAttributes?: Record<string, string>;
82
+ /** Omit to disable log shipping (e.g. traces-only setup). */
83
+ loki?: LokiConfig;
84
+ /** Omit to disable span flushing (e.g. logs-only setup). */
85
+ otlp?: OtlpConfig;
86
+ /** Delivery tuning — triggers, timeout, debounce. Sensible defaults if omitted. */
87
+ flush?: FlushConfig;
88
+ }
89
+ /**
90
+ * Snapshot of a bounded telemetry buffer.
91
+ *
92
+ * Telemetry is best-effort and bounded: when a device stays offline, the
93
+ * buffer fills and the oldest data is evicted. These numbers make that loss
94
+ * observable instead of silent. Business-critical data must NOT live here.
95
+ */
96
+ export interface BufferStats {
97
+ /** Rows currently buffered. */
98
+ rows: number;
99
+ /** Lower-bound estimate of buffered payload text in bytes (sum of field lengths). */
100
+ approxBytes: number;
101
+ /**
102
+ * Rows EVICTED after being buffered, since process start. This is the buffer
103
+ * back-pressure indicator — a rising value means data was buffered then lost
104
+ * because the device stayed offline past the cap.
105
+ */
106
+ dropped: number;
107
+ /**
108
+ * Items refused at INGEST by head sampling (never buffered), since process
109
+ * start. Distinct from `dropped`: a healthy sampler shedding load under
110
+ * pressure raises this without signalling buffer distress. 0 for log buffers
111
+ * (logs are not head-sampled).
112
+ */
113
+ sampled: number;
114
+ }
115
+ export interface TelemetryBufferStats {
116
+ /** Log queue stats, or null when log shipping is disabled. */
117
+ logs: BufferStats | null;
118
+ /** Span queue stats, or null when tracing is disabled. */
119
+ spans: BufferStats | null;
120
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -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";
@@ -4,7 +4,7 @@ import { Display } from "@sincpro/mobile-ui/Display";
4
4
  import { Form } from "@sincpro/mobile-ui/Form";
5
5
  import { useTheme } from "@sincpro/mobile-ui/theme";
6
6
  import { Typography } from "@sincpro/mobile-ui/Typography";
7
- import { useMemo, useState } from "react";
7
+ import { useEffect, useMemo, useState } from "react";
8
8
  import { TextInput, TouchableOpacity, View } from "react-native";
9
9
  const TIMEZONES_LOCALES = {
10
10
  bolivia: { timezone: "America/La_Paz", locale: "es-BO" },
@@ -27,6 +27,12 @@ function TimeZoneSelectorModal({ visible, onClose, onSelect, currentTimezone, })
27
27
  const theme = useTheme();
28
28
  const [query, setQuery] = useState("");
29
29
  const [selectedTimezone, setSelectedTimezone] = useState(currentTimezone || null);
30
+ useEffect(() => {
31
+ if (visible) {
32
+ setQuery("");
33
+ setSelectedTimezone(currentTimezone || null);
34
+ }
35
+ }, [visible]);
30
36
  const timezoneList = useMemo(() => {
31
37
  return Object.values(TIMEZONES_LOCALES).map((tz) => ({
32
38
  ...tz,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sincpro/mobile",
3
- "version": "0.3.1",
3
+ "version": "0.4.2",
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,10 @@
27
27
  "author": "Gutierrez, Andres <andres.gutierrez@sincpro.com.bo>",
28
28
  "license": "MIT",
29
29
  "dependencies": {
30
+ "@opentelemetry/api": "^1.9.0",
31
+ "@opentelemetry/core": "^2.8.0",
32
+ "@opentelemetry/resources": "^2.8.0",
33
+ "@opentelemetry/sdk-trace-base": "^2.8.0",
30
34
  "async-mutex": "^0.5.0",
31
35
  "big.js": "^7.0.1",
32
36
  "dayjs": "^1.11.18",
@@ -36,6 +40,9 @@
36
40
  "uuid": "^13.0.0"
37
41
  },
38
42
  "peerDependencies": {
43
+ "@react-navigation/native": ">=7",
44
+ "@react-navigation/native-stack": ">=7",
45
+ "@sincpro/mobile-ui": "^0.7.2",
39
46
  "expo-background-task": "*",
40
47
  "expo-clipboard": "*",
41
48
  "expo-crypto": "*",
@@ -55,15 +62,16 @@
55
62
  "react-native-gesture-handler": "*",
56
63
  "react-native-keyboard-controller": "*",
57
64
  "react-native-safe-area-context": "*",
65
+ "react-native-screens": "*",
58
66
  "react-native-toast-message": "*",
59
67
  "react-native-view-shot": "*",
60
- "react-native-webview": "*",
61
- "@react-navigation/native": ">=7",
62
- "@react-navigation/native-stack": ">=7",
63
- "react-native-screens": "*",
64
- "@sincpro/mobile-ui": "^0.7.1"
68
+ "react-native-webview": "*"
65
69
  },
66
70
  "devDependencies": {
71
+ "@opentelemetry/api": "^1.9.0",
72
+ "@react-navigation/native": "^7.0.0",
73
+ "@react-navigation/native-stack": "^7.0.0",
74
+ "@sincpro/mobile-ui": "^0.7.2",
67
75
  "@types/big.js": "^6.2.2",
68
76
  "@types/react": "~19.1.10",
69
77
  "@types/uuid": "^10.0.0",
@@ -94,16 +102,14 @@
94
102
  "react-native-keyboard-controller": "1.18.5",
95
103
  "react-native-reanimated": "~4.1.1",
96
104
  "react-native-safe-area-context": "~5.6.0",
105
+ "react-native-screens": "~4.25.2",
97
106
  "react-native-svg": "15.12.1",
98
107
  "react-native-toast-message": "^2.3.3",
99
108
  "react-native-view-shot": "^4.0.3",
100
109
  "react-native-webview": "13.15.0",
101
- "@react-navigation/native": "^7.0.0",
102
- "@react-navigation/native-stack": "^7.0.0",
103
- "react-native-screens": "~4.25.2",
104
110
  "tsc-alias": "^1.8.17",
105
- "typescript": "~5.9.2",
106
- "@sincpro/mobile-ui": "^0.7.1"
111
+ "tsx": "^4.22.4",
112
+ "typescript": "~5.9.2"
107
113
  },
108
114
  "exports": {
109
115
  ".": "./dist/index.js",