@sincpro/mobile 0.4.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/adapters/JsonSerializer.adapter.d.ts +1 -1
- package/dist/adapters/JsonSerializer.adapter.js +2 -2
- package/dist/adapters/repositories/database_table.repository.d.ts +3 -2
- package/dist/adapters/repositories/database_table.repository.js +1 -1
- package/dist/adapters/repositories/document.repository.d.ts +81 -0
- package/dist/adapters/repositories/document.repository.js +653 -0
- package/dist/adapters/repositories/domain_event.repository.d.ts +17 -17
- package/dist/adapters/repositories/domain_event.repository.js +25 -31
- package/dist/adapters/repositories/domain_event_dead_letter.repository.d.ts +10 -10
- package/dist/adapters/repositories/domain_event_dead_letter.repository.js +14 -13
- package/dist/adapters/repositories/event_row.d.ts +13 -0
- package/dist/adapters/repositories/event_row.js +22 -0
- package/dist/adapters/repositories/sqlite_document_store.d.ts +33 -0
- package/dist/adapters/repositories/sqlite_document_store.js +201 -0
- package/dist/domain/connectivity/events.d.ts +5 -5
- package/dist/domain/connectivity/events.js +3 -3
- package/dist/domain/database/criteria.d.ts +19 -0
- package/dist/domain/database/criteria.js +36 -0
- package/dist/domain/database/criteria_clock.d.ts +8 -0
- package/dist/domain/database/criteria_clock.js +305 -0
- package/dist/domain/database/criteria_sql.d.ts +61 -0
- package/dist/domain/database/criteria_sql.js +476 -0
- package/dist/domain/database/database.d.ts +1 -0
- package/dist/domain/database/index.d.ts +3 -1
- package/dist/domain/database/index.js +3 -1
- package/dist/domain/database/migration_plan.d.ts +14 -0
- package/dist/domain/database/migration_plan.js +44 -0
- package/dist/domain/entity/change_tracking.d.ts +21 -0
- package/dist/domain/entity/change_tracking.js +71 -0
- package/dist/domain/entity/checks.d.ts +8 -0
- package/dist/domain/entity/checks.js +82 -0
- package/dist/domain/entity/entity.d.ts +39 -32
- package/dist/domain/entity/entity.js +94 -105
- package/dist/domain/entity/entity_collection.d.ts +15 -30
- package/dist/domain/entity/entity_collection.js +30 -121
- package/dist/domain/entity/index.d.ts +5 -0
- package/dist/domain/entity/index.js +5 -0
- package/dist/domain/entity/reconcile.d.ts +7 -0
- package/dist/domain/entity/reconcile.js +12 -0
- package/dist/domain/entity/relations.d.ts +27 -0
- package/dist/domain/entity/relations.js +69 -0
- package/dist/domain/entity/remote_sync.d.ts +4 -0
- package/dist/domain/entity/remote_sync.js +19 -0
- package/dist/domain/entity/values.d.ts +6 -0
- package/dist/domain/entity/values.js +13 -0
- package/dist/domain/events/catalog.d.ts +29 -0
- package/dist/domain/{events.js → events/catalog.js} +7 -11
- package/dist/domain/events/chain.d.ts +5 -0
- package/dist/domain/events/chain.js +6 -0
- package/dist/domain/{event_sourcing → events}/domain_event.d.ts +20 -22
- package/dist/domain/events/domain_event.js +227 -0
- package/dist/domain/events/index.d.ts +8 -0
- package/dist/domain/events/index.js +8 -0
- package/dist/domain/events/integration_event.d.ts +103 -0
- package/dist/domain/events/integration_event.js +72 -0
- package/dist/domain/events/label.d.ts +2 -0
- package/dist/domain/events/label.js +26 -0
- package/dist/domain/index.d.ts +0 -1
- package/dist/domain/index.js +0 -1
- package/dist/domain/print/events.d.ts +8 -4
- package/dist/domain/print/events.js +9 -4
- package/dist/domain/repositories/document_store.d.ts +33 -0
- package/dist/domain/repositories/document_store.js +5 -0
- package/dist/domain/repositories/hooks.d.ts +23 -0
- package/dist/domain/repositories/hooks.js +32 -0
- package/dist/domain/repositories/index.d.ts +4 -0
- package/dist/domain/repositories/index.js +4 -0
- package/dist/domain/{database → repositories}/repository.d.ts +9 -3
- package/dist/domain/settings.d.ts +4 -1
- package/dist/domain/settings.js +3 -0
- package/dist/domain/webview/events.d.ts +2 -2
- package/dist/domain/webview/events.js +2 -2
- package/dist/entrypoints/db/migrations.js +10 -10
- package/dist/entrypoints/queue/QueueProcessor.d.ts +1 -1
- package/dist/entrypoints/queue/activateDomain.subscriber.d.ts +2 -2
- package/dist/entrypoints/queue/activateDomain.subscriber.js +2 -2
- package/dist/entrypoints/queue/newAppSettings.handler.d.ts +2 -2
- package/dist/entrypoints/queue/newAppSettings.handler.js +1 -1
- package/dist/entrypoints/queue/printImage.subscriber.d.ts +1 -1
- package/dist/entrypoints/queue/printImage.subscriber.js +2 -2
- package/dist/entrypoints/queue/processWebViewMessage.subscriber.d.ts +1 -1
- package/dist/entrypoints/queue/processWebViewMessage.subscriber.js +1 -1
- package/dist/entrypoints/ui/AppShell.js +2 -2
- package/dist/entrypoints/ui/common_provider.d.ts +0 -12
- package/dist/entrypoints/ui/common_provider.js +2 -131
- package/dist/event_driven/index.d.ts +1 -0
- package/dist/event_driven/index.js +1 -0
- package/dist/{domain/event_sourcing/event_handler.d.ts → event_driven/subscriber.d.ts} +1 -1
- package/dist/exceptions.d.ts +21 -0
- package/dist/exceptions.js +30 -0
- package/dist/framework/base_module.d.ts +1 -1
- package/dist/framework/createApp.d.ts +3 -3
- package/dist/framework/domain_module.d.ts +1 -1
- package/dist/framework/kernel.d.ts +1 -1
- package/dist/framework/kernel.js +17 -8
- package/dist/infrastructure/database/connector.d.ts +2 -0
- package/dist/infrastructure/database/connector.js +22 -0
- package/dist/infrastructure/database/index.d.ts +1 -0
- package/dist/infrastructure/database/index.js +1 -0
- package/dist/infrastructure/database/schema.d.ts +9 -0
- package/dist/infrastructure/database/schema.js +61 -0
- package/dist/infrastructure/database/utils.d.ts +2 -0
- package/dist/infrastructure/database/utils.js +12 -4
- package/dist/infrastructure/event_chain.d.ts +6 -0
- package/dist/infrastructure/event_chain.js +25 -0
- package/dist/infrastructure/telemetry/tracing/tracing_hooks.js +8 -5
- package/dist/infrastructure/ui/UIEventBus.d.ts +3 -1
- package/dist/infrastructure/ui/UIEventBus.js +8 -1
- package/dist/infrastructure/ui/activity.d.ts +37 -0
- package/dist/infrastructure/ui/activity.js +84 -0
- package/dist/infrastructure/workers/CronWorker.d.ts +6 -4
- package/dist/infrastructure/workers/CronWorker.js +24 -18
- package/dist/infrastructure/workers/EventBus.d.ts +9 -5
- package/dist/infrastructure/workers/EventBus.js +68 -33
- package/dist/services/bluetooth.service.js +16 -14
- package/dist/services/dead_letter_queue.service.d.ts +7 -7
- package/dist/services/dead_letter_queue.service.js +0 -2
- package/dist/services/event.service.d.ts +3 -3
- package/dist/services/event.service.js +1 -1
- package/dist/services/network.service.d.ts +1 -1
- package/dist/services/network.service.js +17 -15
- package/dist/services/printer.service.d.ts +1 -1
- package/dist/services/printer.service.js +50 -44
- package/dist/tools/utils/date.js +4 -3
- package/dist/tools/utils/monetary.d.ts +6 -3
- package/dist/tools/utils/monetary.js +33 -21
- package/dist/tools/utils/serializer.d.ts +1 -1
- package/dist/tools/utils/serializer.js +32 -35
- package/dist/ui/activity/DeviceNotices.d.ts +1 -0
- package/dist/ui/activity/DeviceNotices.js +29 -0
- package/dist/ui/activity/ProcessToast.d.ts +2 -0
- package/dist/ui/activity/ProcessToast.js +71 -0
- package/dist/ui/activity/index.d.ts +4 -0
- package/dist/ui/activity/index.js +4 -0
- package/dist/ui/activity/useActivity.d.ts +9 -0
- package/dist/ui/activity/useActivity.js +48 -0
- package/dist/ui/components/molecules/EventTimelineItem.d.ts +2 -2
- package/dist/ui/components/molecules/EventTimelineItem.js +1 -1
- package/dist/ui/components/molecules/index.d.ts +1 -2
- package/dist/ui/components/molecules/index.js +1 -2
- package/dist/ui/components/organisms/DeadLetterQueueRow.d.ts +3 -3
- package/dist/ui/components/organisms/EventRow.d.ts +3 -3
- package/dist/ui/components/organisms/EventRow.js +1 -1
- package/dist/ui/screens/dead_letter_queue/dead_letter_queue.context.d.ts +5 -5
- package/dist/ui/screens/events/events.context.d.ts +4 -4
- package/package.json +13 -8
- package/dist/domain/event_sourcing/domain_event.js +0 -164
- package/dist/domain/event_sourcing/event.d.ts +0 -25
- package/dist/domain/event_sourcing/event.js +0 -25
- package/dist/domain/event_sourcing/index.d.ts +0 -2
- package/dist/domain/event_sourcing/index.js +0 -2
- package/dist/domain/events.d.ts +0 -33
- package/dist/infrastructure/ui/ToastHost.d.ts +0 -6
- package/dist/infrastructure/ui/ToastHost.js +0 -28
- package/dist/infrastructure/ui/events.d.ts +0 -1
- package/dist/infrastructure/ui/events.js +0 -1
- package/dist/ui/components/molecules/ProcessToast.d.ts +0 -8
- package/dist/ui/components/molecules/ProcessToast.js +0 -52
- /package/dist/domain/{repositories.d.ts → repositories/catalog.d.ts} +0 -0
- /package/dist/domain/{repositories.js → repositories/catalog.js} +0 -0
- /package/dist/domain/{database → repositories}/repository.js +0 -0
- /package/dist/{domain/event_sourcing/event_handler.js → event_driven/subscriber.js} +0 -0
- /package/dist/ui/{components/molecules → activity}/ProcessToastProvider.d.ts +0 -0
- /package/dist/ui/{components/molecules → activity}/ProcessToastProvider.js +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { DBCursor } from "../../infrastructure/database/connector";
|
|
2
|
+
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3
|
+
const DEFINITION = /^[A-Za-z0-9_ ()'",.{}-]+$/;
|
|
4
|
+
const LEDGER = "schema_migrations";
|
|
5
|
+
export function assertSqlIdentifier(name) {
|
|
6
|
+
if (!IDENTIFIER.test(name))
|
|
7
|
+
throw new Error(`invalid SQL identifier: ${name}`);
|
|
8
|
+
}
|
|
9
|
+
export function addColumnStatement(table, column, definition) {
|
|
10
|
+
assertSqlIdentifier(table);
|
|
11
|
+
assertSqlIdentifier(column);
|
|
12
|
+
if (!DEFINITION.test(definition))
|
|
13
|
+
throw new Error(`invalid column definition: ${definition}`);
|
|
14
|
+
return `ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`;
|
|
15
|
+
}
|
|
16
|
+
export async function tableColumns(table) {
|
|
17
|
+
assertSqlIdentifier(table);
|
|
18
|
+
const rows = await DBCursor.getAllAsync(`PRAGMA table_info(${table})`);
|
|
19
|
+
return rows.map((row) => row.name);
|
|
20
|
+
}
|
|
21
|
+
export function tableColumnsSync(table) {
|
|
22
|
+
assertSqlIdentifier(table);
|
|
23
|
+
const rows = DBCursor.getAllSync(`PRAGMA table_info(${table})`);
|
|
24
|
+
return rows.map((row) => row.name);
|
|
25
|
+
}
|
|
26
|
+
export async function addColumnIfMissing(table, column, definition) {
|
|
27
|
+
const existing = await tableColumns(table);
|
|
28
|
+
if (existing.includes(column))
|
|
29
|
+
return;
|
|
30
|
+
await DBCursor.execAsync(addColumnStatement(table, column, definition));
|
|
31
|
+
}
|
|
32
|
+
export function addColumnIfMissingSync(table, column, definition) {
|
|
33
|
+
const existing = tableColumnsSync(table);
|
|
34
|
+
if (existing.includes(column))
|
|
35
|
+
return;
|
|
36
|
+
DBCursor.mutateDatabaseSync(addColumnStatement(table, column, definition));
|
|
37
|
+
}
|
|
38
|
+
export async function appliedMigrationNames() {
|
|
39
|
+
await ensureLedger();
|
|
40
|
+
const rows = await DBCursor.getAllAsync(`SELECT name FROM ${LEDGER}`);
|
|
41
|
+
return new Set(rows.map((row) => row.name));
|
|
42
|
+
}
|
|
43
|
+
export async function markMigrationApplied(name) {
|
|
44
|
+
await ensureLedger();
|
|
45
|
+
await DBCursor.mutateDatabase(`INSERT OR IGNORE INTO ${LEDGER} (name) VALUES (?)`, name);
|
|
46
|
+
}
|
|
47
|
+
export async function forgetMigrations(names) {
|
|
48
|
+
await ensureLedger();
|
|
49
|
+
if (names.length === 0)
|
|
50
|
+
return;
|
|
51
|
+
const marks = names.map(() => "?").join(", ");
|
|
52
|
+
await DBCursor.mutateDatabase(`DELETE FROM ${LEDGER} WHERE name IN (${marks})`, ...names);
|
|
53
|
+
}
|
|
54
|
+
async function ensureLedger() {
|
|
55
|
+
await DBCursor.execAsync(`
|
|
56
|
+
CREATE TABLE IF NOT EXISTS ${LEDGER} (
|
|
57
|
+
name TEXT PRIMARY KEY NOT NULL,
|
|
58
|
+
applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
59
|
+
)
|
|
60
|
+
`);
|
|
61
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { isEntityId, momentOf, newEntityId } from "@sincpro/client-framework";
|
|
1
2
|
import { convertToArray } from "../../tools/utils/collections";
|
|
2
3
|
import * as Crypto from "expo-crypto";
|
|
3
|
-
|
|
4
|
+
export { isEntityId, momentOf };
|
|
4
5
|
/**
|
|
5
6
|
* Constructs a SQL placeholder string and corresponding values for use in a query based on the provided field and data.
|
|
6
7
|
*
|
|
@@ -28,8 +29,15 @@ export function getPlaceholders(field, data) {
|
|
|
28
29
|
}
|
|
29
30
|
return [`${field} IN (${placeholders})`, values];
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
function ensureRandom() {
|
|
33
|
+
if (typeof globalThis.crypto?.getRandomValues === "function")
|
|
34
|
+
return;
|
|
35
|
+
Object.defineProperty(globalThis, "crypto", {
|
|
36
|
+
value: { getRandomValues: Crypto.getRandomValues },
|
|
37
|
+
configurable: true,
|
|
34
38
|
});
|
|
35
39
|
}
|
|
40
|
+
export function generateUUID() {
|
|
41
|
+
ensureRandom();
|
|
42
|
+
return newEntityId();
|
|
43
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Chain } from "../domain/events/chain";
|
|
2
|
+
export declare const EVENT_CHAIN_KEY: import("./context_manager/context_key").ContextKey<Chain>;
|
|
3
|
+
export declare function openChain(active: Chain | undefined): Chain;
|
|
4
|
+
export declare function currentChain(): Chain | undefined;
|
|
5
|
+
export declare function runWithChain<T>(chain: Chain, fn: () => T): T;
|
|
6
|
+
export declare function traceparent(): string | null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { generateUUID } from "../infrastructure/database/utils";
|
|
2
|
+
import { getActiveSpanContext } from "../infrastructure/telemetry/tracing/active_span";
|
|
3
|
+
import { createContextKey } from "./context_manager/context_key";
|
|
4
|
+
import { getActiveContext, getContext, runWithContext } from "./context_manager/index";
|
|
5
|
+
export const EVENT_CHAIN_KEY = createContextKey("sincpro.event.chain");
|
|
6
|
+
export function openChain(active) {
|
|
7
|
+
const executionId = generateUUID();
|
|
8
|
+
if (!active) {
|
|
9
|
+
return { causationId: executionId, correlationId: executionId };
|
|
10
|
+
}
|
|
11
|
+
return { causationId: executionId, correlationId: active.correlationId };
|
|
12
|
+
}
|
|
13
|
+
export function currentChain() {
|
|
14
|
+
return getContext(EVENT_CHAIN_KEY);
|
|
15
|
+
}
|
|
16
|
+
export function runWithChain(chain, fn) {
|
|
17
|
+
return runWithContext(getActiveContext().set(EVENT_CHAIN_KEY, chain), fn);
|
|
18
|
+
}
|
|
19
|
+
export function traceparent() {
|
|
20
|
+
const span = getActiveSpanContext();
|
|
21
|
+
if (!span)
|
|
22
|
+
return null;
|
|
23
|
+
const flags = span.traceFlags.toString(16).padStart(2, "0");
|
|
24
|
+
return `00-${span.traceId}-${span.spanId}-${flags}`;
|
|
25
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ROOT_CONTEXT as OTEL_ROOT, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
2
2
|
import { _getContextManager } from "../../../infrastructure/context_manager";
|
|
3
|
+
import { EVENT_CHAIN_KEY, openChain } from "../../../infrastructure/event_chain";
|
|
3
4
|
import { OTEL_CTX_KEY } from "../../../infrastructure/telemetry/context_keys";
|
|
4
5
|
const _spanMap = new WeakMap();
|
|
5
6
|
// Tracks whether `before` successfully pushed a context entry for this call.
|
|
@@ -19,21 +20,23 @@ const _pushedMap = new WeakSet();
|
|
|
19
20
|
export function tracingHooks(tracerName) {
|
|
20
21
|
return {
|
|
21
22
|
before(call) {
|
|
23
|
+
const manager = _getContextManager();
|
|
24
|
+
let ctx = manager.active();
|
|
25
|
+
ctx = ctx.set(EVENT_CHAIN_KEY, openChain(ctx.get(EVENT_CHAIN_KEY)));
|
|
22
26
|
try {
|
|
23
|
-
const
|
|
24
|
-
const frameworkCtx = manager.active();
|
|
25
|
-
const otelCtx = frameworkCtx.get(OTEL_CTX_KEY) ?? OTEL_ROOT;
|
|
27
|
+
const otelCtx = ctx.get(OTEL_CTX_KEY) ?? OTEL_ROOT;
|
|
26
28
|
const spanName = call.className
|
|
27
29
|
? `${call.className}.${call.methodName}`
|
|
28
30
|
: call.methodName;
|
|
29
31
|
const span = trace.getTracer(tracerName).startSpan(spanName, undefined, otelCtx);
|
|
30
32
|
_spanMap.set(call, span);
|
|
31
|
-
|
|
32
|
-
_pushedMap.add(call);
|
|
33
|
+
ctx = ctx.set(OTEL_CTX_KEY, trace.setSpan(otelCtx, span));
|
|
33
34
|
}
|
|
34
35
|
catch {
|
|
35
36
|
_spanMap.delete(call);
|
|
36
37
|
}
|
|
38
|
+
manager.push(ctx);
|
|
39
|
+
_pushedMap.add(call);
|
|
37
40
|
},
|
|
38
41
|
after(call) {
|
|
39
42
|
const span = _spanMap.get(call);
|
|
@@ -3,7 +3,9 @@ declare class UIEventBusImpl {
|
|
|
3
3
|
private emitter;
|
|
4
4
|
private subscriptions;
|
|
5
5
|
private handlerIdCounter;
|
|
6
|
-
on<T = unknown>(event: string, handler: Handler<T
|
|
6
|
+
on<T = unknown>(event: string, handler: Handler<T>, options?: {
|
|
7
|
+
debounce?: boolean;
|
|
8
|
+
}): () => void;
|
|
7
9
|
off<T = unknown>(event: string, handler: Handler<T>): void;
|
|
8
10
|
emit<T = unknown>(event: string, payload?: T): void;
|
|
9
11
|
clear(): void;
|
|
@@ -4,7 +4,14 @@ class UIEventBusImpl {
|
|
|
4
4
|
emitter = mitt();
|
|
5
5
|
subscriptions = new Map();
|
|
6
6
|
handlerIdCounter = 0;
|
|
7
|
-
on(event, handler) {
|
|
7
|
+
on(event, handler, options) {
|
|
8
|
+
if (options?.debounce === false) {
|
|
9
|
+
const direct = handler;
|
|
10
|
+
this.emitter.on(event, direct);
|
|
11
|
+
return () => {
|
|
12
|
+
this.emitter.off(event, direct);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
8
15
|
const handlerId = `${event}_${++this.handlerIdCounter}`;
|
|
9
16
|
const wrappedHandler = (payload) => {
|
|
10
17
|
const sub = this.subscriptions.get(handlerId);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type LabelText } from "../../domain/events/label";
|
|
2
|
+
export declare const ACTIVITY_EVENT = "ui.activity";
|
|
3
|
+
export declare const ACTIVITY_SETTING_EVENT = "ui.activity.setting";
|
|
4
|
+
export type ActivityChannel = "queue" | "cron" | "device";
|
|
5
|
+
export type ActivityStatus = "running" | "succeeded" | "failed" | "idle";
|
|
6
|
+
export interface ActivityFact {
|
|
7
|
+
id: string;
|
|
8
|
+
channel: ActivityChannel;
|
|
9
|
+
name: string;
|
|
10
|
+
label: LabelText;
|
|
11
|
+
detail?: LabelText;
|
|
12
|
+
status: ActivityStatus;
|
|
13
|
+
ui: boolean;
|
|
14
|
+
variant?: "success" | "danger" | "info";
|
|
15
|
+
}
|
|
16
|
+
export interface ActivityState {
|
|
17
|
+
running: ActivityFact[];
|
|
18
|
+
failure?: ActivityFact;
|
|
19
|
+
notices: ActivityFact[];
|
|
20
|
+
}
|
|
21
|
+
export interface ActivitySettingChange {
|
|
22
|
+
channel: ActivityChannel;
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare const emptyActivity: ActivityState;
|
|
26
|
+
export declare function declaresUi(source: object): boolean;
|
|
27
|
+
export declare function publishActivity(fact: Omit<ActivityFact, "id">): ActivityFact;
|
|
28
|
+
export declare function notifyDevice(input: {
|
|
29
|
+
name: string;
|
|
30
|
+
title: LabelText;
|
|
31
|
+
message: LabelText;
|
|
32
|
+
variant: "success" | "danger" | "info";
|
|
33
|
+
}): void;
|
|
34
|
+
export declare function loadActivityEnabled(): Promise<Record<ActivityChannel, boolean>>;
|
|
35
|
+
export declare function setActivityEnabled(channel: ActivityChannel, enabled: boolean): Promise<void>;
|
|
36
|
+
export declare function activityVisible(fact: ActivityFact, enabled: Record<ActivityChannel, boolean>, all?: boolean): boolean;
|
|
37
|
+
export declare function applyActivity(state: ActivityState, fact: ActivityFact): ActivityState;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { SettingsRepository } from "../../adapters/repositories/setting.repository";
|
|
2
|
+
import { textOf } from "../../domain/events/label";
|
|
3
|
+
import { ECommonSetting } from "../../domain/settings";
|
|
4
|
+
import { UIEventBus } from "./UIEventBus";
|
|
5
|
+
export const ACTIVITY_EVENT = "ui.activity";
|
|
6
|
+
export const ACTIVITY_SETTING_EVENT = "ui.activity.setting";
|
|
7
|
+
export const emptyActivity = { running: [], notices: [] };
|
|
8
|
+
const SETTING_KEY = {
|
|
9
|
+
queue: ECommonSetting.UI_QUEUE,
|
|
10
|
+
cron: ECommonSetting.UI_CRON,
|
|
11
|
+
device: ECommonSetting.UI_DEVICE,
|
|
12
|
+
};
|
|
13
|
+
let sequence = 0;
|
|
14
|
+
export function declaresUi(source) {
|
|
15
|
+
return source.constructor.ui === true;
|
|
16
|
+
}
|
|
17
|
+
export function publishActivity(fact) {
|
|
18
|
+
const full = { ...fact, id: `${fact.channel}:${fact.name}:${++sequence}` };
|
|
19
|
+
UIEventBus.emit(ACTIVITY_EVENT, full);
|
|
20
|
+
return full;
|
|
21
|
+
}
|
|
22
|
+
export function notifyDevice(input) {
|
|
23
|
+
publishActivity({
|
|
24
|
+
channel: "device",
|
|
25
|
+
name: input.name,
|
|
26
|
+
label: input.title,
|
|
27
|
+
detail: input.message,
|
|
28
|
+
status: input.variant === "danger" ? "failed" : "succeeded",
|
|
29
|
+
variant: input.variant,
|
|
30
|
+
ui: true,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export async function loadActivityEnabled() {
|
|
34
|
+
const enabled = {
|
|
35
|
+
queue: true,
|
|
36
|
+
cron: true,
|
|
37
|
+
device: true,
|
|
38
|
+
};
|
|
39
|
+
for (const channel of Object.keys(SETTING_KEY)) {
|
|
40
|
+
try {
|
|
41
|
+
const value = await SettingsRepository.getSettingByName(SETTING_KEY[channel]);
|
|
42
|
+
if (value === false)
|
|
43
|
+
enabled[channel] = false;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return enabled;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return enabled;
|
|
50
|
+
}
|
|
51
|
+
export async function setActivityEnabled(channel, enabled) {
|
|
52
|
+
await SettingsRepository.saveOneSetting(SETTING_KEY[channel], enabled);
|
|
53
|
+
const change = { channel, enabled };
|
|
54
|
+
UIEventBus.emit(ACTIVITY_SETTING_EVENT, change);
|
|
55
|
+
}
|
|
56
|
+
export function activityVisible(fact, enabled, all = false) {
|
|
57
|
+
if (all)
|
|
58
|
+
return true;
|
|
59
|
+
if (!fact.ui)
|
|
60
|
+
return false;
|
|
61
|
+
if (!enabled[fact.channel])
|
|
62
|
+
return false;
|
|
63
|
+
if (fact.channel !== "device" && textOf(fact.label).length === 0)
|
|
64
|
+
return false;
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
export function applyActivity(state, fact) {
|
|
68
|
+
if (fact.channel === "device") {
|
|
69
|
+
return { ...state, notices: [...state.notices, fact].slice(-8) };
|
|
70
|
+
}
|
|
71
|
+
const running = state.running.filter((item) => item.channel !== fact.channel || item.name !== fact.name);
|
|
72
|
+
const sameFailure = state.failure?.channel === fact.channel && state.failure.name === fact.name;
|
|
73
|
+
if (fact.status === "running") {
|
|
74
|
+
return { ...state, running: [...running, fact], failure: undefined };
|
|
75
|
+
}
|
|
76
|
+
if (fact.status === "failed") {
|
|
77
|
+
return { ...state, running, failure: fact };
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
...state,
|
|
81
|
+
running,
|
|
82
|
+
failure: sameFailure ? undefined : state.failure,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { type LabelText } from "../../domain/events/label";
|
|
1
2
|
export type CronJob = () => Promise<void>;
|
|
2
3
|
export interface CronWorkerOpts {
|
|
3
4
|
requiresAuth?: boolean;
|
|
4
|
-
label?:
|
|
5
|
-
|
|
5
|
+
label?: LabelText;
|
|
6
|
+
ui?: boolean;
|
|
6
7
|
}
|
|
7
8
|
export declare class CronWorker {
|
|
8
9
|
readonly taskName: string;
|
|
@@ -13,8 +14,8 @@ export declare class CronWorker {
|
|
|
13
14
|
private isExecuting;
|
|
14
15
|
private registrationError?;
|
|
15
16
|
readonly requiresAuth: boolean;
|
|
16
|
-
label?:
|
|
17
|
-
private
|
|
17
|
+
label?: LabelText;
|
|
18
|
+
private ui;
|
|
18
19
|
constructor(taskName: string, job: CronJob, intervalMin?: number, opts?: CronWorkerOpts);
|
|
19
20
|
configure(opts: Partial<CronWorkerOpts>): void;
|
|
20
21
|
/**
|
|
@@ -42,6 +43,7 @@ export declare class CronWorker {
|
|
|
42
43
|
getLastError(): Error | undefined;
|
|
43
44
|
private createIntervalJob;
|
|
44
45
|
private runWithEvents;
|
|
46
|
+
private publish;
|
|
45
47
|
/**
|
|
46
48
|
* Waits for current job execution to complete (if any).
|
|
47
49
|
* Timeout after 10 seconds to prevent infinite wait.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loggerCronJobs } from "../../infrastructure/logger";
|
|
2
|
-
import {
|
|
2
|
+
import { publishActivity } from "../../infrastructure/ui/activity";
|
|
3
3
|
import * as BackgroundTask from "expo-background-task";
|
|
4
4
|
import * as TaskManager from "expo-task-manager";
|
|
5
5
|
export class CronWorker {
|
|
@@ -12,20 +12,20 @@ export class CronWorker {
|
|
|
12
12
|
registrationError;
|
|
13
13
|
requiresAuth;
|
|
14
14
|
label;
|
|
15
|
-
|
|
15
|
+
ui;
|
|
16
16
|
constructor(taskName, job, intervalMin = 15, opts = {}) {
|
|
17
17
|
this.taskName = taskName;
|
|
18
18
|
this.job = job;
|
|
19
19
|
this.intervalMin = intervalMin;
|
|
20
20
|
this.requiresAuth = opts.requiresAuth ?? false;
|
|
21
21
|
this.label = opts.label;
|
|
22
|
-
this.
|
|
22
|
+
this.ui = opts.ui ?? false;
|
|
23
23
|
}
|
|
24
24
|
configure(opts) {
|
|
25
25
|
if (opts.label !== undefined)
|
|
26
26
|
this.label = opts.label;
|
|
27
|
-
if (opts.
|
|
28
|
-
this.
|
|
27
|
+
if (opts.ui !== undefined)
|
|
28
|
+
this.ui = opts.ui;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
31
|
* Registers a task with a specified interval and execution logic.
|
|
@@ -45,14 +45,10 @@ export class CronWorker {
|
|
|
45
45
|
}
|
|
46
46
|
else {
|
|
47
47
|
TaskManager.defineTask(this.taskName, async () => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
catch (e) {
|
|
53
|
-
loggerCronJobs.warn(`${this.taskName} error:`, e);
|
|
54
|
-
return BackgroundTask.BackgroundTaskResult.Failed;
|
|
55
|
-
}
|
|
48
|
+
const ok = await this.runWithEvents(this.job);
|
|
49
|
+
return ok
|
|
50
|
+
? BackgroundTask.BackgroundTaskResult.Success
|
|
51
|
+
: BackgroundTask.BackgroundTaskResult.Failed;
|
|
56
52
|
});
|
|
57
53
|
await BackgroundTask.registerTaskAsync(this.taskName, {
|
|
58
54
|
minimumInterval: this.intervalMin,
|
|
@@ -116,25 +112,35 @@ export class CronWorker {
|
|
|
116
112
|
async runWithEvents(job) {
|
|
117
113
|
this.isExecuting = true;
|
|
118
114
|
const startTime = Date.now();
|
|
119
|
-
|
|
120
|
-
UIEventBus.emit("CRON_START", { task: this.taskName, label: this.label });
|
|
121
|
-
}
|
|
115
|
+
this.publish("running");
|
|
122
116
|
try {
|
|
123
117
|
await Promise.resolve(job());
|
|
124
118
|
const duration = Date.now() - startTime;
|
|
125
119
|
loggerCronJobs.debug(`${this.taskName} completed successfully in ${duration}ms`);
|
|
120
|
+
this.publish("succeeded");
|
|
121
|
+
return true;
|
|
126
122
|
}
|
|
127
123
|
catch (e) {
|
|
128
124
|
const duration = Date.now() - startTime;
|
|
129
125
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
130
126
|
loggerCronJobs.error(`${this.taskName} failed after ${duration}ms with error: ${errorMessage}`);
|
|
127
|
+
this.publish("failed", errorMessage);
|
|
128
|
+
return false;
|
|
131
129
|
}
|
|
132
130
|
finally {
|
|
133
131
|
this.isExecuting = false;
|
|
134
|
-
if (this.showToast)
|
|
135
|
-
UIEventBus.emit("CRON_END", { task: this.taskName });
|
|
136
132
|
}
|
|
137
133
|
}
|
|
134
|
+
publish(status, detail) {
|
|
135
|
+
publishActivity({
|
|
136
|
+
channel: "cron",
|
|
137
|
+
name: this.taskName,
|
|
138
|
+
label: this.label ?? this.taskName,
|
|
139
|
+
detail,
|
|
140
|
+
status,
|
|
141
|
+
ui: this.ui,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
138
144
|
/**
|
|
139
145
|
* Waits for current job execution to complete (if any).
|
|
140
146
|
* Timeout after 10 seconds to prevent infinite wait.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EventName, type IntegrationEvent } from "../../domain/events";
|
|
2
|
+
import { Subscriber } from "../../event_driven";
|
|
2
3
|
export declare const PROCESS_WORKER_MAX_ATTEMPTS = 1;
|
|
3
4
|
export { EventName };
|
|
4
5
|
export declare class EventBus {
|
|
@@ -8,6 +9,7 @@ export declare class EventBus {
|
|
|
8
9
|
private static readonly BACKOFF_MULTIPLIER;
|
|
9
10
|
private static subscribers;
|
|
10
11
|
private static isDraining;
|
|
12
|
+
private static connectivityReady;
|
|
11
13
|
private static intervalId;
|
|
12
14
|
private static internetConnected;
|
|
13
15
|
private static consecutiveFailures;
|
|
@@ -25,12 +27,12 @@ export declare class EventBus {
|
|
|
25
27
|
*/
|
|
26
28
|
static waitForIdle(): Promise<void>;
|
|
27
29
|
/**
|
|
28
|
-
*
|
|
30
|
+
* Logs the start and publishes a running activity when a subscriber exists.
|
|
29
31
|
* Returns false if no subscribers (caller should return early).
|
|
30
32
|
*/
|
|
31
33
|
private static preProcess;
|
|
32
34
|
/**
|
|
33
|
-
* Handles persistence, emits event
|
|
35
|
+
* Handles persistence, emits the event for screens, publishes the activity, logs result.
|
|
34
36
|
* If errors is empty -> success, otherwise -> failure + dead letter + throws.
|
|
35
37
|
* @throws Error if there are any errors
|
|
36
38
|
*/
|
|
@@ -47,13 +49,14 @@ export declare class EventBus {
|
|
|
47
49
|
/**
|
|
48
50
|
* Publishes a DomainEvent asynchronously - enqueues for later processing.
|
|
49
51
|
*/
|
|
50
|
-
static
|
|
52
|
+
static schedule(): void;
|
|
53
|
+
static publish(event: IntegrationEvent): Promise<void>;
|
|
51
54
|
/**
|
|
52
55
|
* Publishes a DomainEvent synchronously - executes all subscribers immediately.
|
|
53
56
|
* Saves event to DB for traceability. Moves to dead letter on failure.
|
|
54
57
|
* @throws Error if any subscriber fails or no subscribers registered
|
|
55
58
|
*/
|
|
56
|
-
static publishSync(event:
|
|
59
|
+
static publishSync(event: IntegrationEvent): Promise<void>;
|
|
57
60
|
/**
|
|
58
61
|
* Processes all pending DomainEvents from the domain_events table.
|
|
59
62
|
* Executes ALL subscribers registered for each event.
|
|
@@ -64,4 +67,5 @@ export declare class EventBus {
|
|
|
64
67
|
* Clears the event queue (domain_events table).
|
|
65
68
|
*/
|
|
66
69
|
static clearQueue(): Promise<void>;
|
|
70
|
+
private static publishQueue;
|
|
67
71
|
}
|