@sincpro/mobile 0.4.3 → 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/ReceiptExporter.adapter.js +10 -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 +1 -13
- package/dist/entrypoints/ui/common_provider.js +4 -133
- package/dist/entrypoints/ui/domain_switcher.d.ts +2 -2
- 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/feature.d.ts +26 -0
- package/dist/framework/feature.js +36 -0
- package/dist/framework/kernel.d.ts +1 -1
- package/dist/framework/kernel.js +17 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- 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/interceptor/intercept.d.ts +7 -2
- package/dist/infrastructure/interceptor/intercept.js +5 -2
- package/dist/infrastructure/telemetry/tracing/tracing_hooks.d.ts +9 -6
- package/dist/infrastructure/telemetry/tracing/tracing_hooks.js +61 -35
- 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/{components/molecules → activity}/ProcessToastProvider.d.ts +1 -1
- 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/atoms/DebugBanner.d.ts +1 -1
- package/dist/ui/components/molecules/BluetoothDeviceSelectorModal.d.ts +1 -1
- package/dist/ui/components/molecules/BluetoothDeviceSelectorModal.js +8 -2
- package/dist/ui/components/molecules/DeadLetterErrorBlock.d.ts +1 -1
- package/dist/ui/components/molecules/EventTimelineItem.d.ts +3 -3
- package/dist/ui/components/molecules/EventTimelineItem.js +1 -1
- package/dist/ui/components/molecules/GeoPermissionCard.d.ts +1 -1
- package/dist/ui/components/molecules/TimeZoneSelector.d.ts +1 -1
- package/dist/ui/components/molecules/TimeZoneSelectorModal.d.ts +1 -1
- package/dist/ui/components/molecules/TimeZoneSelectorModal.js +5 -3
- package/dist/ui/components/molecules/index.d.ts +1 -2
- package/dist/ui/components/molecules/index.js +1 -2
- package/dist/ui/components/organisms/BluetoothPrinterSelector.d.ts +1 -1
- package/dist/ui/components/organisms/BluetoothPrinterSelector.js +3 -0
- package/dist/ui/components/organisms/DatabaseInfoRow.d.ts +1 -1
- package/dist/ui/components/organisms/DeadLetterQueueRow.d.ts +5 -4
- package/dist/ui/components/organisms/EventRow.d.ts +4 -4
- package/dist/ui/components/organisms/EventRow.js +1 -1
- package/dist/ui/components/organisms/InjectableWebView.d.ts +2 -2
- package/dist/ui/components/organisms/InjectableWebView.js +6 -3
- package/dist/ui/components/organisms/Receipt.d.ts +2 -1
- package/dist/ui/components/organisms/TableInfoInfoRow.d.ts +1 -1
- package/dist/ui/screens/database/database.context.d.ts +2 -2
- package/dist/ui/screens/database/database.context.js +9 -18
- package/dist/ui/screens/database/database.json_detail.d.ts +1 -1
- package/dist/ui/screens/database/database.list.d.ts +1 -1
- package/dist/ui/screens/database/database.table_rows.d.ts +1 -1
- package/dist/ui/screens/dead_letter_queue/dead_letter_queue.context.d.ts +7 -7
- package/dist/ui/screens/dead_letter_queue/dead_letter_queue.list.d.ts +1 -1
- package/dist/ui/screens/events/events.context.d.ts +6 -6
- package/dist/ui/screens/events/events.context.js +20 -25
- package/dist/ui/screens/events/events.list.d.ts +1 -1
- package/package.json +51 -40
- 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.js +0 -0
|
@@ -3,6 +3,8 @@ export declare const DB_NAME = "distribution.db";
|
|
|
3
3
|
export declare function getDatabase(): Promise<SQLiteDatabase>;
|
|
4
4
|
export declare function getDatabaseSync(): SQLiteDatabase;
|
|
5
5
|
export declare const DBCursor: {
|
|
6
|
+
inTransaction(): boolean;
|
|
7
|
+
transaction<T>(work: () => Promise<T>): Promise<T>;
|
|
6
8
|
mutateDatabase(query: string, ...params: SQLiteVariadicBindParams): Promise<SQLiteRunResult>;
|
|
7
9
|
getFirstAsync<T>(query: string, ...params: SQLiteVariadicBindParams): Promise<T | null>;
|
|
8
10
|
getAllAsync<T>(query: string, ...params: SQLiteVariadicBindParams): Promise<T[]>;
|
|
@@ -3,6 +3,7 @@ import { Mutex } from "async-mutex";
|
|
|
3
3
|
import { openDatabaseAsync, openDatabaseSync, } from "expo-sqlite";
|
|
4
4
|
export const DB_NAME = "distribution.db";
|
|
5
5
|
let dbInstance = null;
|
|
6
|
+
let currentTx = null;
|
|
6
7
|
const dbLock = new Mutex();
|
|
7
8
|
// Telemetry/queue tables are excluded from query logging: logging their
|
|
8
9
|
// mutations would feed the log pipeline its own writes — and a failing
|
|
@@ -45,12 +46,33 @@ export function getDatabaseSync() {
|
|
|
45
46
|
return dbInstance;
|
|
46
47
|
}
|
|
47
48
|
async function withDB(fn) {
|
|
49
|
+
if (currentTx)
|
|
50
|
+
return fn(currentTx);
|
|
48
51
|
return dbLock.runExclusive(async () => fn(await getDatabase()));
|
|
49
52
|
}
|
|
50
53
|
function withDBSync(fn) {
|
|
51
54
|
return fn(getDatabaseSync());
|
|
52
55
|
}
|
|
53
56
|
export const DBCursor = {
|
|
57
|
+
inTransaction() {
|
|
58
|
+
return currentTx !== null;
|
|
59
|
+
},
|
|
60
|
+
async transaction(work) {
|
|
61
|
+
return dbLock.runExclusive(async () => {
|
|
62
|
+
const db = await getDatabase();
|
|
63
|
+
let result;
|
|
64
|
+
await db.withTransactionAsync(async () => {
|
|
65
|
+
currentTx = db;
|
|
66
|
+
try {
|
|
67
|
+
result = await work();
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
currentTx = null;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return result;
|
|
74
|
+
});
|
|
75
|
+
},
|
|
54
76
|
async mutateDatabase(query, ...params) {
|
|
55
77
|
const safeQuery = `${query.trim()};`;
|
|
56
78
|
return withDB(async (db) => {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function assertSqlIdentifier(name: string): void;
|
|
2
|
+
export declare function addColumnStatement(table: string, column: string, definition: string): string;
|
|
3
|
+
export declare function tableColumns(table: string): Promise<string[]>;
|
|
4
|
+
export declare function tableColumnsSync(table: string): string[];
|
|
5
|
+
export declare function addColumnIfMissing(table: string, column: string, definition: string): Promise<void>;
|
|
6
|
+
export declare function addColumnIfMissingSync(table: string, column: string, definition: string): void;
|
|
7
|
+
export declare function appliedMigrationNames(): Promise<Set<string>>;
|
|
8
|
+
export declare function markMigrationApplied(name: string): Promise<void>;
|
|
9
|
+
export declare function forgetMigrations(names: readonly string[]): Promise<void>;
|
|
@@ -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
|
+
}
|
|
@@ -41,6 +41,10 @@ export declare function interceptClass<T extends new (...args: never[]) => objec
|
|
|
41
41
|
* function's `.name` (or `"anonymous"` if unnamed).
|
|
42
42
|
*/
|
|
43
43
|
export declare function interceptFunction<T extends (...args: never[]) => unknown>(fn: T, hooks: InterceptorHooks): T;
|
|
44
|
+
export type InstanceInterceptOptions = {
|
|
45
|
+
/** Method names for which the proxy returns the raw function. */
|
|
46
|
+
skip?: (methodName: string) => boolean;
|
|
47
|
+
};
|
|
44
48
|
/**
|
|
45
49
|
* Wraps a single live instance via Proxy without touching the class prototype.
|
|
46
50
|
* Use for singletons exported directly (e.g. `dbCursor`, `distributionWorkflows`).
|
|
@@ -52,6 +56,7 @@ export declare function interceptFunction<T extends (...args: never[]) => unknow
|
|
|
52
56
|
*
|
|
53
57
|
* Hooks contract: `before` fires before the call; `after` fires on success with
|
|
54
58
|
* the resolved value; `onError` fires on throw or rejection — the error is always
|
|
55
|
-
* re-thrown after `onError` completes.
|
|
59
|
+
* re-thrown after `onError` completes. `skip` leaves a method unwrapped — a `_`
|
|
60
|
+
* helper must not open a span.
|
|
56
61
|
*/
|
|
57
|
-
export declare function interceptInstance<T extends object>(instance: T, hooks: InterceptorHooks): T;
|
|
62
|
+
export declare function interceptInstance<T extends object>(instance: T, hooks: InterceptorHooks, options?: InstanceInterceptOptions): T;
|
|
@@ -96,9 +96,10 @@ export function interceptFunction(fn, hooks) {
|
|
|
96
96
|
*
|
|
97
97
|
* Hooks contract: `before` fires before the call; `after` fires on success with
|
|
98
98
|
* the resolved value; `onError` fires on throw or rejection — the error is always
|
|
99
|
-
* re-thrown after `onError` completes.
|
|
99
|
+
* re-thrown after `onError` completes. `skip` leaves a method unwrapped — a `_`
|
|
100
|
+
* helper must not open a span.
|
|
100
101
|
*/
|
|
101
|
-
export function interceptInstance(instance, hooks) {
|
|
102
|
+
export function interceptInstance(instance, hooks, options) {
|
|
102
103
|
const className = instance.constructor?.name ?? "Object";
|
|
103
104
|
return new Proxy(instance, {
|
|
104
105
|
get(target, prop, receiver) {
|
|
@@ -106,6 +107,8 @@ export function interceptInstance(instance, hooks) {
|
|
|
106
107
|
if (typeof value !== "function" || typeof prop === "symbol") {
|
|
107
108
|
return value;
|
|
108
109
|
}
|
|
110
|
+
if (options?.skip?.(prop))
|
|
111
|
+
return value;
|
|
109
112
|
return wrap(value, className, prop, hooks).bind(target);
|
|
110
113
|
},
|
|
111
114
|
});
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import type { InterceptorHooks } from "
|
|
1
|
+
import type { InterceptorHooks } from "../../../infrastructure/interceptor/intercept";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* value inside our Context, pushed in `before` and popped in `after`/`onError`.
|
|
3
|
+
* A span per intercepted call. The active span lives in the framework context,
|
|
4
|
+
* so a nested call — an application service running a feature — is a child of
|
|
5
|
+
* the same trace.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
7
|
+
* Tracing is optional. No provider, a missing SDK, or an exporter that throws:
|
|
8
|
+
* the hooks swallow it and the use case runs. `before` only records a push it
|
|
9
|
+
* actually made, so `after` never pops a context it did not push.
|
|
10
|
+
*
|
|
11
|
+
* @param tracerName - instrumentation scope, e.g. "sincpro.mobile.checkout"
|
|
9
12
|
*/
|
|
10
13
|
export declare function tracingHooks(tracerName: string): InterceptorHooks;
|
|
@@ -1,59 +1,85 @@
|
|
|
1
1
|
import { ROOT_CONTEXT as OTEL_ROOT, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
2
|
-
import { _getContextManager } from "
|
|
3
|
-
import {
|
|
2
|
+
import { _getContextManager } from "../../../infrastructure/context_manager";
|
|
3
|
+
import { EVENT_CHAIN_KEY, openChain } from "../../../infrastructure/event_chain";
|
|
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.
|
|
6
7
|
// Guards `after`/`onError` against popping a context they never pushed.
|
|
7
8
|
const _pushedMap = new WeakSet();
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* value inside our Context, pushed in `before` and popped in `after`/`onError`.
|
|
10
|
+
* A span per intercepted call. The active span lives in the framework context,
|
|
11
|
+
* so a nested call — an application service running a feature — is a child of
|
|
12
|
+
* the same trace.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* Tracing is optional. No provider, a missing SDK, or an exporter that throws:
|
|
15
|
+
* the hooks swallow it and the use case runs. `before` only records a push it
|
|
16
|
+
* actually made, so `after` never pops a context it did not push.
|
|
17
|
+
*
|
|
18
|
+
* @param tracerName - instrumentation scope, e.g. "sincpro.mobile.checkout"
|
|
15
19
|
*/
|
|
16
20
|
export function tracingHooks(tracerName) {
|
|
17
21
|
return {
|
|
18
22
|
before(call) {
|
|
19
23
|
const manager = _getContextManager();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
let ctx = manager.active();
|
|
25
|
+
ctx = ctx.set(EVENT_CHAIN_KEY, openChain(ctx.get(EVENT_CHAIN_KEY)));
|
|
26
|
+
try {
|
|
27
|
+
const otelCtx = ctx.get(OTEL_CTX_KEY) ?? OTEL_ROOT;
|
|
28
|
+
const spanName = call.className
|
|
29
|
+
? `${call.className}.${call.methodName}`
|
|
30
|
+
: call.methodName;
|
|
31
|
+
const span = trace.getTracer(tracerName).startSpan(spanName, undefined, otelCtx);
|
|
32
|
+
_spanMap.set(call, span);
|
|
33
|
+
ctx = ctx.set(OTEL_CTX_KEY, trace.setSpan(otelCtx, span));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
_spanMap.delete(call);
|
|
37
|
+
}
|
|
38
|
+
manager.push(ctx);
|
|
34
39
|
_pushedMap.add(call);
|
|
35
40
|
},
|
|
36
41
|
after(call) {
|
|
37
42
|
const span = _spanMap.get(call);
|
|
38
|
-
if (!span)
|
|
39
|
-
return;
|
|
40
43
|
_spanMap.delete(call);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
try {
|
|
45
|
+
span?.setStatus({ code: SpanStatusCode.OK });
|
|
46
|
+
span?.end();
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// The use case already finished. A failing exporter does not change that.
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
pop(call);
|
|
53
|
+
}
|
|
45
54
|
},
|
|
46
55
|
onError(call, error) {
|
|
47
56
|
const span = _spanMap.get(call);
|
|
48
|
-
if (!span)
|
|
49
|
-
return;
|
|
50
57
|
_spanMap.delete(call);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
try {
|
|
59
|
+
if (error instanceof Error)
|
|
60
|
+
span?.recordException(error);
|
|
61
|
+
span?.setStatus({
|
|
62
|
+
code: SpanStatusCode.ERROR,
|
|
63
|
+
message: error instanceof Error ? error.message : undefined,
|
|
64
|
+
});
|
|
65
|
+
span?.end();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Same as `after`: the business error is the one that propagates.
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
pop(call);
|
|
72
|
+
}
|
|
57
73
|
},
|
|
58
74
|
};
|
|
59
75
|
}
|
|
76
|
+
function pop(call) {
|
|
77
|
+
if (!_pushedMap.delete(call))
|
|
78
|
+
return;
|
|
79
|
+
try {
|
|
80
|
+
_getContextManager().pop();
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// The stack was already cleared. Nothing of this call is left on it.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -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.
|