target-supabase-sdk 0.2.3 → 0.2.4
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 +24 -18
- package/dist/browser.d.ts +11 -4
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +3 -3
- package/dist/chunks/{enable-log-persist-C7GJo_Nc.js → enable-log-persist-B8rg_wXG.js} +5 -6
- package/dist/chunks/enable-log-persist-B8rg_wXG.js.map +1 -0
- package/dist/chunks/{create-logger-DyH3mZ8p.js → list.api-Ab9v8yOg.js} +66 -57
- package/dist/chunks/list.api-Ab9v8yOg.js.map +1 -0
- package/dist/chunks/{word.interface-DLgRobKV.js → word.interface-B_fbVFOW.js} +1078 -97
- package/dist/chunks/word.interface-B_fbVFOW.js.map +1 -0
- package/dist/extraction/extraction.api.d.ts +117 -0
- package/dist/extraction/extraction.api.d.ts.map +1 -0
- package/dist/extraction/extraction.interface.d.ts +43 -0
- package/dist/extraction/extraction.interface.d.ts.map +1 -0
- package/dist/list/list.interface.d.ts +2 -2
- package/dist/list/list.interface.d.ts.map +1 -1
- package/dist/node/node-runtime.base.d.ts +13 -1
- package/dist/node/node-runtime.base.d.ts.map +1 -1
- package/dist/node/process/create-managed-child-processes.d.ts +12 -0
- package/dist/node/process/create-managed-child-processes.d.ts.map +1 -0
- package/dist/node/process/index.d.ts +3 -1
- package/dist/node/process/index.d.ts.map +1 -1
- package/dist/node/process/managed-child-processes.d.ts +15 -2
- package/dist/node/process/managed-child-processes.d.ts.map +1 -1
- package/dist/node/runtime-state/create-service-runtime-state-store.d.ts +23 -0
- package/dist/node/runtime-state/create-service-runtime-state-store.d.ts.map +1 -0
- package/dist/node/runtime-state/index.d.ts +4 -0
- package/dist/node/runtime-state/index.d.ts.map +1 -0
- package/dist/node/runtime-state/service-runtime-state.types.d.ts +57 -0
- package/dist/node/runtime-state/service-runtime-state.types.d.ts.map +1 -0
- package/dist/node/service-guard/index.d.ts +9 -0
- package/dist/node/service-guard/index.d.ts.map +1 -0
- package/dist/node/service-guard/register-service-guard-runner.d.ts +9 -0
- package/dist/node/service-guard/register-service-guard-runner.d.ts.map +1 -0
- package/dist/node/service-guard/run-readiness-gate.d.ts +11 -0
- package/dist/node/service-guard/run-readiness-gate.d.ts.map +1 -0
- package/dist/node/service-guard/run-service-guard-tick.d.ts +7 -0
- package/dist/node/service-guard/run-service-guard-tick.d.ts.map +1 -0
- package/dist/node/service-guard/service-guard-node.d.ts +18 -0
- package/dist/node/service-guard/service-guard-node.d.ts.map +1 -0
- package/dist/node/service-guard/service-guard.interface.d.ts +58 -0
- package/dist/node/service-guard/service-guard.interface.d.ts.map +1 -0
- package/dist/node/service-guard/worker-spawn-cooldown.d.ts +4 -0
- package/dist/node/service-guard/worker-spawn-cooldown.d.ts.map +1 -0
- package/dist/node/service-host/index.d.ts +9 -0
- package/dist/node/service-host/index.d.ts.map +1 -0
- package/dist/node/service-host/registry-slot-guard-step.d.ts +21 -0
- package/dist/node/service-host/registry-slot-guard-step.d.ts.map +1 -0
- package/dist/node/service-host/service-host.d.ts +36 -0
- package/dist/node/service-host/service-host.d.ts.map +1 -0
- package/dist/node/service-host/single-process-service.d.ts +21 -0
- package/dist/node/service-host/single-process-service.d.ts.map +1 -0
- package/dist/node.d.ts +15 -2
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +2088 -1495
- package/dist/node.js.map +1 -1
- package/dist/preload.js +3 -3
- package/dist/service/config.api.d.ts +143 -0
- package/dist/service/config.api.d.ts.map +1 -0
- package/dist/service/config.interface.d.ts +22 -0
- package/dist/service/config.interface.d.ts.map +1 -0
- package/dist/service/index.d.ts +16 -17
- package/dist/service/index.d.ts.map +1 -1
- package/dist/service/registry-lifecycle.d.ts +43 -0
- package/dist/service/registry-lifecycle.d.ts.map +1 -0
- package/dist/service/registry.service.d.ts +94 -0
- package/dist/service/registry.service.d.ts.map +1 -0
- package/dist/service/service-bootstrap.d.ts +28 -0
- package/dist/service/service-bootstrap.d.ts.map +1 -0
- package/dist/service/service.api.d.ts +160 -0
- package/dist/service/service.api.d.ts.map +1 -1
- package/dist/service/service.interface.d.ts +32 -1
- package/dist/service/service.interface.d.ts.map +1 -1
- package/dist/shared/log/index.d.ts +4 -0
- package/dist/shared/log/index.d.ts.map +1 -1
- package/dist/shared/log/log-batch.d.ts +44 -0
- package/dist/shared/log/log-batch.d.ts.map +1 -0
- package/dist/shared/log/log-persist-coordinator.d.ts +37 -0
- package/dist/shared/log/log-persist-coordinator.d.ts.map +1 -0
- package/dist/shared/log/log-persist-flush.d.ts.map +1 -1
- package/dist/shared/log/log-persist.config.d.ts +0 -1
- package/dist/shared/log/log-persist.config.d.ts.map +1 -1
- package/dist/shared/log/log-persist.interface.d.ts +8 -0
- package/dist/shared/log/log-persist.interface.d.ts.map +1 -1
- package/dist/shared/utils/config-path.utils.d.ts +21 -0
- package/dist/shared/utils/config-path.utils.d.ts.map +1 -1
- package/dist/shared/utils/get-value-at-path.d.ts +3 -0
- package/dist/shared/utils/get-value-at-path.d.ts.map +1 -0
- package/dist/task/index.d.ts +2 -0
- package/dist/task/index.d.ts.map +1 -1
- package/dist/task/task-manager.d.ts +1 -1
- package/dist/task/task-manager.d.ts.map +1 -1
- package/dist/task/task-post-retry.api.d.ts +24 -0
- package/dist/task/task-post-retry.api.d.ts.map +1 -0
- package/dist/task/task-repo-context.d.ts +7 -1
- package/dist/task/task-repo-context.d.ts.map +1 -1
- package/dist/trigger/trigger-manager.d.ts +1 -1
- package/dist/trigger/trigger-manager.d.ts.map +1 -1
- package/dist/trigger/trigger-node.d.ts +1 -0
- package/dist/trigger/trigger-node.d.ts.map +1 -1
- package/dist/trigger/trigger.constant.d.ts +1 -1
- package/dist/trigger/trigger.constant.d.ts.map +1 -1
- package/dist/trigger/trigger.interface.d.ts +10 -0
- package/dist/trigger/trigger.interface.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/browser.ts +98 -4
- package/src/extraction/extraction.api.ts +232 -0
- package/src/extraction/extraction.interface.ts +45 -0
- package/src/list/list.interface.ts +2 -2
- package/src/node/node-runtime.base.ts +37 -6
- package/src/node/process/create-managed-child-processes.ts +27 -0
- package/src/node/process/index.ts +4 -0
- package/src/node/process/managed-child-processes.ts +48 -15
- package/src/node/runtime-state/create-service-runtime-state-store.ts +125 -0
- package/src/node/runtime-state/index.ts +17 -0
- package/src/node/runtime-state/service-runtime-state.types.ts +64 -0
- package/src/node/service-guard/index.ts +17 -0
- package/src/node/service-guard/register-service-guard-runner.ts +64 -0
- package/src/node/service-guard/run-readiness-gate.ts +34 -0
- package/src/node/service-guard/run-service-guard-tick.ts +106 -0
- package/src/node/service-guard/service-guard-node.ts +52 -0
- package/src/node/service-guard/service-guard.interface.ts +64 -0
- package/src/node/service-guard/worker-spawn-cooldown.ts +13 -0
- package/src/node/service-host/index.ts +30 -0
- package/src/node/service-host/registry-slot-guard-step.ts +57 -0
- package/src/node/service-host/service-host.ts +168 -0
- package/src/node/service-host/single-process-service.ts +58 -0
- package/src/node.ts +69 -0
- package/src/service/config.api.ts +224 -0
- package/src/service/config.interface.ts +25 -0
- package/src/service/index.ts +74 -149
- package/src/service/registry-lifecycle.ts +144 -0
- package/src/service/registry.service.ts +564 -0
- package/src/service/service-bootstrap.ts +74 -0
- package/src/service/service.api.ts +13 -0
- package/src/service/service.interface.ts +36 -1
- package/src/shared/log/index.ts +13 -0
- package/src/shared/log/log-batch.ts +156 -0
- package/src/shared/log/log-persist-coordinator.ts +107 -0
- package/src/shared/log/log-persist-flush.ts +3 -3
- package/src/shared/log/log-persist.config.ts +0 -2
- package/src/shared/log/log-persist.interface.ts +10 -0
- package/src/shared/utils/config-path.utils.ts +35 -0
- package/src/shared/utils/get-value-at-path.ts +17 -0
- package/src/task/index.ts +2 -0
- package/src/task/task-manager.ts +10 -4
- package/src/task/task-post-retry.api.ts +61 -0
- package/src/task/task-repo-context.ts +8 -1
- package/src/trigger/trigger-manager.ts +4 -2
- package/src/trigger/trigger-node.ts +10 -4
- package/src/trigger/trigger.constant.ts +1 -1
- package/src/trigger/trigger.interface.ts +10 -0
- package/dist/chunks/create-logger-DyH3mZ8p.js.map +0 -1
- package/dist/chunks/enable-log-persist-C7GJo_Nc.js.map +0 -1
- package/dist/chunks/word.interface-DLgRobKV.js.map +0 -1
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { Target } from "../core.interface";
|
|
2
|
+
import type { NodeStatus } from "../node/node.interface";
|
|
2
3
|
import type { FieldDefinition, ServiceLifecycle } from "./base.interface";
|
|
3
4
|
|
|
4
5
|
export interface Service extends Target {
|
|
5
6
|
name: string;
|
|
6
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Logical service key (e.g. `watch-service`). **Not unique** — each `postService` creates
|
|
9
|
+
* a new instance row. Which instance is **available** is determined by registry slots
|
|
10
|
+
* (`getTargetSystemRegistry` / `resolveActiveRegistryServiceId`), not by `getService({ value })`.
|
|
11
|
+
*/
|
|
7
12
|
value: string;
|
|
8
13
|
category: CategoryService;
|
|
9
14
|
details: ServiceDetails;
|
|
@@ -20,6 +25,36 @@ export interface ServiceDetails {
|
|
|
20
25
|
/** 依赖的服务 Service keys */
|
|
21
26
|
dependencies: string[];
|
|
22
27
|
lifecycle: ServiceLifecycle;
|
|
28
|
+
/** Present on runtime instances; omitted on catalog-only rows until guard starts. */
|
|
29
|
+
runtime?: ServiceRuntime;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Per-node snapshot under a running Service — maintained by the service guard. */
|
|
33
|
+
export interface ServiceNodeSnapshot {
|
|
34
|
+
nodeId: string;
|
|
35
|
+
status: NodeStatus;
|
|
36
|
+
lastHeartBeat: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Runtime observability for a registered Service instance.
|
|
41
|
+
* Heartbeat and node rollups are written by the service guard, not the system registry Config.
|
|
42
|
+
*/
|
|
43
|
+
export interface ServiceRuntime {
|
|
44
|
+
lastHeartBeat: number;
|
|
45
|
+
nodes: ServiceNodeSnapshot[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export enum ServiceSlotStatus {
|
|
49
|
+
EMPTY = "EMPTY",
|
|
50
|
+
ACTIVE = "ACTIVE",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ServiceSlot {
|
|
54
|
+
serviceValue: string;
|
|
55
|
+
/** Bound {@link Service.id} when {@link ServiceSlotStatus.ACTIVE}; null when EMPTY. */
|
|
56
|
+
serviceId: string | null;
|
|
57
|
+
status: ServiceSlotStatus;
|
|
23
58
|
}
|
|
24
59
|
|
|
25
60
|
export interface Api extends Target {
|
package/src/shared/log/index.ts
CHANGED
|
@@ -9,9 +9,22 @@ export type {
|
|
|
9
9
|
} from "./create-logger";
|
|
10
10
|
// create-logger
|
|
11
11
|
export { createLogger } from "./create-logger";
|
|
12
|
+
export type { DecodedLogBatch, FlatLogEntry } from "./log-batch";
|
|
13
|
+
// log-batch (persisted LogBatch decode — read side of log-persist)
|
|
14
|
+
export {
|
|
15
|
+
decodeLogBatch,
|
|
16
|
+
decodeLogBatchList,
|
|
17
|
+
flattenLogBatch,
|
|
18
|
+
flattenLogBatches,
|
|
19
|
+
isLogBatchList,
|
|
20
|
+
isLogEntry,
|
|
21
|
+
parseLogBatchMeta,
|
|
22
|
+
} from "./log-batch";
|
|
12
23
|
export type { LogEntry, LoggerWithScope, LogOptions, LogRestParams, WithScopeOptions } from "./log-manager";
|
|
13
24
|
// log-manager
|
|
14
25
|
export { LogLevel, LogManager, logManager } from "./log-manager";
|
|
26
|
+
export type { LogBatchMeta, LogPersistLane } from "./log-persist.interface";
|
|
27
|
+
export { LOG_BATCH_LOADER_KEY, LOG_PERSIST_LOADER_KEY } from "./log-persist.interface";
|
|
15
28
|
export type {
|
|
16
29
|
CreateScopeInput,
|
|
17
30
|
LoggerResetScopePatch,
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read side of the log-persist pipeline: decode persisted `LogBatch` Lists into
|
|
3
|
+
* structured batches and flat entries. Pure and defensive — malformed rows are
|
|
4
|
+
* skipped rather than thrown. Mirrors the write side in `log-persist-flush.ts`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { List } from "../../list/list.interface";
|
|
8
|
+
import type { LogEntry } from "./log-manager";
|
|
9
|
+
import { LOG_PERSIST_LOADER_KEY, type LogBatchMeta, type LogPersistLane } from "./log-persist.interface";
|
|
10
|
+
|
|
11
|
+
const LANES: ReadonlySet<string> = new Set<LogPersistLane>(["fast", "medium", "slow"]);
|
|
12
|
+
|
|
13
|
+
/** A decoded log batch: origin metadata plus its raw entries. */
|
|
14
|
+
export interface DecodedLogBatch {
|
|
15
|
+
/** Source `List` row id. */
|
|
16
|
+
listId: string;
|
|
17
|
+
/** `List.created_at` (ISO string). */
|
|
18
|
+
createdAt: string;
|
|
19
|
+
meta: LogBatchMeta;
|
|
20
|
+
entries: LogEntry[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A single {@link LogEntry} enriched with its batch-level origin fields. */
|
|
24
|
+
export interface FlatLogEntry extends LogEntry {
|
|
25
|
+
service: string;
|
|
26
|
+
process: string;
|
|
27
|
+
lane: LogPersistLane;
|
|
28
|
+
pid: number;
|
|
29
|
+
/** Idempotency key of the source batch. */
|
|
30
|
+
batchId: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
34
|
+
return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function asFiniteNumber(value: unknown): number | null {
|
|
38
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function asString(value: unknown): string | null {
|
|
42
|
+
return typeof value === "string" ? value : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Parse `ListDetails.meta` (object or legacy JSON string) into typed batch meta, or null. */
|
|
46
|
+
export function parseLogBatchMeta(rawMeta: unknown): LogBatchMeta | null {
|
|
47
|
+
let source: Record<string, unknown> | null;
|
|
48
|
+
if (typeof rawMeta === "string") {
|
|
49
|
+
try {
|
|
50
|
+
source = asRecord(JSON.parse(rawMeta));
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
source = asRecord(rawMeta);
|
|
56
|
+
}
|
|
57
|
+
if (source == null) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const service = asString(source.service);
|
|
62
|
+
const processName = asString(source.process);
|
|
63
|
+
const laneRaw = asString(source.lane);
|
|
64
|
+
const idempotencyKey = asString(source.idempotencyKey);
|
|
65
|
+
if (service == null || processName == null || laneRaw == null || !LANES.has(laneRaw) || idempotencyKey == null) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
service,
|
|
71
|
+
process: processName,
|
|
72
|
+
lane: laneRaw as LogPersistLane,
|
|
73
|
+
pid: asFiniteNumber(source.pid) ?? 0,
|
|
74
|
+
count: asFiniteNumber(source.count) ?? 0,
|
|
75
|
+
from: asFiniteNumber(source.from),
|
|
76
|
+
to: asFiniteNumber(source.to),
|
|
77
|
+
flushedAt: asFiniteNumber(source.flushedAt) ?? 0,
|
|
78
|
+
idempotencyKey,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Structural guard for a persisted {@link LogEntry}. */
|
|
83
|
+
export function isLogEntry(value: unknown): value is LogEntry {
|
|
84
|
+
const record = asRecord(value);
|
|
85
|
+
return (
|
|
86
|
+
record != null &&
|
|
87
|
+
typeof record.timestamp === "number" &&
|
|
88
|
+
typeof record.level === "string" &&
|
|
89
|
+
typeof record.message === "string" &&
|
|
90
|
+
typeof record.topic === "string" &&
|
|
91
|
+
typeof record.module === "string" &&
|
|
92
|
+
typeof record.traceId === "string"
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Is this List a persisted log batch (by loaderKey)? */
|
|
97
|
+
export function isLogBatchList(list: List): boolean {
|
|
98
|
+
return list.details?.loaderKey === LOG_PERSIST_LOADER_KEY;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Decode a `List` row into a {@link DecodedLogBatch}. Returns null when the row
|
|
103
|
+
* is not a log batch or the meta/items are malformed.
|
|
104
|
+
*/
|
|
105
|
+
export function decodeLogBatch(list: List): DecodedLogBatch | null {
|
|
106
|
+
if (!isLogBatchList(list)) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const meta = parseLogBatchMeta(list.details?.meta);
|
|
110
|
+
if (meta == null) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const items = Array.isArray(list.details?.items) ? list.details.items : [];
|
|
115
|
+
const entries = items.filter(isLogEntry);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
listId: list.id,
|
|
119
|
+
createdAt: list.created_at,
|
|
120
|
+
meta,
|
|
121
|
+
entries,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Decode many List rows, dropping any that are not valid log batches. */
|
|
126
|
+
export function decodeLogBatchList(lists: readonly List[]): DecodedLogBatch[] {
|
|
127
|
+
const decoded: DecodedLogBatch[] = [];
|
|
128
|
+
for (const list of lists) {
|
|
129
|
+
const batch = decodeLogBatch(list);
|
|
130
|
+
if (batch != null) {
|
|
131
|
+
decoded.push(batch);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return decoded;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Flatten one batch into per-entry rows carrying the batch origin fields. */
|
|
138
|
+
export function flattenLogBatch(batch: DecodedLogBatch): FlatLogEntry[] {
|
|
139
|
+
return batch.entries.map((entry) => ({
|
|
140
|
+
...entry,
|
|
141
|
+
service: batch.meta.service,
|
|
142
|
+
process: batch.meta.process,
|
|
143
|
+
lane: batch.meta.lane,
|
|
144
|
+
pid: batch.meta.pid,
|
|
145
|
+
batchId: batch.meta.idempotencyKey,
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Flatten many batches into a single flat entry list. */
|
|
150
|
+
export function flattenLogBatches(batches: readonly DecodedLogBatch[]): FlatLogEntry[] {
|
|
151
|
+
const flat: FlatLogEntry[] = [];
|
|
152
|
+
for (const batch of batches) {
|
|
153
|
+
flat.push(...flattenLogBatch(batch));
|
|
154
|
+
}
|
|
155
|
+
return flat;
|
|
156
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
disableLogPersist,
|
|
3
|
+
ensureLogPersistFromEnv,
|
|
4
|
+
getLogPersistStats,
|
|
5
|
+
logPersistEnabledFromEnv,
|
|
6
|
+
snapshotLogPersistReady,
|
|
7
|
+
waitForLogPersistReady,
|
|
8
|
+
} from "./enable-log-persist";
|
|
9
|
+
import type { LogPersistReadySnapshot, LogPersistStats } from "./log-persist.interface";
|
|
10
|
+
|
|
11
|
+
export interface LogPersistCoordinatorOptions {
|
|
12
|
+
/** Service identity written into each registry shard (e.g. `log-service`). */
|
|
13
|
+
service: string;
|
|
14
|
+
/** Directory holding one JSON shard per process (`main.json`, `guard.json`, …). */
|
|
15
|
+
registryFilePath: string;
|
|
16
|
+
/** Process labels main waits for before accepting traffic (must match `LOG_PERSIST_PROCESS` per child). */
|
|
17
|
+
expectedProcesses: readonly string[];
|
|
18
|
+
/** Max wait for {@link LogPersistCoordinator.waitForAllProcessesReady} (default 60_000 ms). */
|
|
19
|
+
readyTimeoutMs?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Service-bound facade over env-driven log-persist registration and readiness gates. */
|
|
23
|
+
export interface LogPersistCoordinator {
|
|
24
|
+
/** Whether `LOG_PERSIST_ENABLED` is active in this process. */
|
|
25
|
+
isEnabled(): boolean;
|
|
26
|
+
|
|
27
|
+
/** Absolute path to the multi-process registry directory configured at construction. */
|
|
28
|
+
getRegistryFilePath(): string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Enable log-persist for this process and write/update its registry shard.
|
|
32
|
+
* @param process Optional label; defaults to `LOG_PERSIST_PROCESS` env.
|
|
33
|
+
*/
|
|
34
|
+
registerProcess(process?: string): Promise<boolean>;
|
|
35
|
+
|
|
36
|
+
/** Block until every {@link LogPersistCoordinatorOptions.expectedProcesses} entry is registered and fresh. */
|
|
37
|
+
waitForAllProcessesReady(): Promise<LogPersistReadySnapshot>;
|
|
38
|
+
|
|
39
|
+
/** Immediate readiness snapshot without blocking; `null` when log-persist is disabled. */
|
|
40
|
+
snapshotProcessesReady(): Promise<LogPersistReadySnapshot | null>;
|
|
41
|
+
|
|
42
|
+
/** Flush-queue stats for observability; `null` when log-persist is disabled. */
|
|
43
|
+
getPersistStats(): LogPersistStats | null;
|
|
44
|
+
|
|
45
|
+
/** Flush pending logs and tear down log-persist (call from process shutdown hooks). */
|
|
46
|
+
shutdownLogPersist(): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Bind service-specific log-persist registry settings (process list, paths, timeouts).
|
|
51
|
+
* Each L3 service exports one instance from `src/startup/log-persist.ts`.
|
|
52
|
+
*/
|
|
53
|
+
export function createLogPersistCoordinator(options: LogPersistCoordinatorOptions): LogPersistCoordinator {
|
|
54
|
+
const { service, registryFilePath, expectedProcesses, readyTimeoutMs } = options;
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
isEnabled() {
|
|
58
|
+
return logPersistEnabledFromEnv();
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
getRegistryFilePath() {
|
|
62
|
+
return registryFilePath;
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
registerProcess(process?: string) {
|
|
66
|
+
return ensureLogPersistFromEnv({
|
|
67
|
+
process,
|
|
68
|
+
service,
|
|
69
|
+
registryFilePath,
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
waitForAllProcessesReady() {
|
|
74
|
+
return waitForLogPersistReady({
|
|
75
|
+
registryFilePath,
|
|
76
|
+
service,
|
|
77
|
+
expectedProcesses: [...expectedProcesses],
|
|
78
|
+
timeoutMs: readyTimeoutMs,
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
async snapshotProcessesReady() {
|
|
83
|
+
if (!logPersistEnabledFromEnv()) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return snapshotLogPersistReady({
|
|
87
|
+
registryFilePath,
|
|
88
|
+
service,
|
|
89
|
+
expectedProcesses: [...expectedProcesses],
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
getPersistStats() {
|
|
94
|
+
if (!logPersistEnabledFromEnv()) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return getLogPersistStats();
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
async shutdownLogPersist() {
|
|
101
|
+
if (!logPersistEnabledFromEnv()) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
await disableLogPersist();
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -2,8 +2,8 @@ import { isCreateTargetAlreadyExistsError } from "../../core.api";
|
|
|
2
2
|
import { postLogListCreate } from "../../list/list.api";
|
|
3
3
|
import { CategoryList } from "../../list/list.interface";
|
|
4
4
|
import type { LogEntry } from "./log-manager";
|
|
5
|
-
import { LOG_PERSIST_LOADER_KEY } from "./log-persist.config";
|
|
6
5
|
import type { BuildLogListDraftInput, LogListDraft } from "./log-persist.interface";
|
|
6
|
+
import { LOG_PERSIST_LOADER_KEY } from "./log-persist.interface";
|
|
7
7
|
import { persistLogger } from "./log-persist-logger";
|
|
8
8
|
|
|
9
9
|
export function estimateEntriesBytes(entries: LogEntry[]): number {
|
|
@@ -30,7 +30,7 @@ export function buildLogListDraft(input: BuildLogListDraftInput): LogListDraft {
|
|
|
30
30
|
details: {
|
|
31
31
|
manifestVersion: 0,
|
|
32
32
|
loaderKey: LOG_PERSIST_LOADER_KEY,
|
|
33
|
-
meta:
|
|
33
|
+
meta: {
|
|
34
34
|
service,
|
|
35
35
|
process,
|
|
36
36
|
pid: globalThis.process.pid,
|
|
@@ -40,7 +40,7 @@ export function buildLogListDraft(input: BuildLogListDraftInput): LogListDraft {
|
|
|
40
40
|
to: last?.timestamp ?? null,
|
|
41
41
|
flushedAt: Date.now(),
|
|
42
42
|
idempotencyKey,
|
|
43
|
-
}
|
|
43
|
+
},
|
|
44
44
|
preview: first?.message ?? "",
|
|
45
45
|
items: entries,
|
|
46
46
|
},
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { envBool, envInt, envMs } from "../../node/env/env-parsers";
|
|
2
2
|
import type { LogPersistConfig } from "./log-persist.interface";
|
|
3
3
|
|
|
4
|
-
export const LOG_PERSIST_LOADER_KEY = "LogBatch.0";
|
|
5
|
-
|
|
6
4
|
export const DEFAULT_LOG_PERSIST_CONFIG: LogPersistConfig = {
|
|
7
5
|
fast: {
|
|
8
6
|
maxBufferEntries: 500,
|
|
@@ -2,6 +2,16 @@ import type { TargetDraft } from "../../core.interface";
|
|
|
2
2
|
import type { List } from "../../list/list.interface";
|
|
3
3
|
import type { LogEntry } from "./log-manager";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* `ListDetails.loaderKey` stamped on every persisted log batch (write + read side).
|
|
7
|
+
* Business type: **LogBatch** (transport flush). Pairs with log-service **LogTrace**
|
|
8
|
+
* (`LogTrace.0`) after rekeying by `traceId`.
|
|
9
|
+
*/
|
|
10
|
+
export const LOG_PERSIST_LOADER_KEY = "LogBatch.0";
|
|
11
|
+
|
|
12
|
+
/** Alias for {@link LOG_PERSIST_LOADER_KEY} — preferred when contrasting with LogTrace. */
|
|
13
|
+
export const LOG_BATCH_LOADER_KEY = LOG_PERSIST_LOADER_KEY;
|
|
14
|
+
|
|
5
15
|
/** Persistence lane — maps to List tagList and flush policy. */
|
|
6
16
|
export type LogPersistLane = "fast" | "medium" | "slow";
|
|
7
17
|
|
|
@@ -102,3 +102,38 @@ export async function importJsConfigModule(configPath: string): Promise<unknown>
|
|
|
102
102
|
const mod = await import(toFileImportHref(configPath));
|
|
103
103
|
return mod.default ?? mod;
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
export interface ConfigSchema<T> {
|
|
107
|
+
safeParse(input: unknown): { success: true; data: T } | { success: false; error: { message: string } };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface LoadCachedJsConfigOptions<T> {
|
|
111
|
+
path: string;
|
|
112
|
+
schema: ConfigSchema<T>;
|
|
113
|
+
force?: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const jsConfigCache = new Map<string, { mtimeMs: number; data: unknown }>();
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Import a JS config module, validate with a schema, cache by file mtime.
|
|
120
|
+
* For hot-reload, pass `{ force: true }` or rely on mtime change.
|
|
121
|
+
*/
|
|
122
|
+
export async function loadCachedJsConfigModule<T>(options: LoadCachedJsConfigOptions<T>): Promise<T> {
|
|
123
|
+
const { stat } = await import("node:fs/promises");
|
|
124
|
+
const { path, schema, force } = options;
|
|
125
|
+
const fileStat = await stat(path);
|
|
126
|
+
const cached = jsConfigCache.get(path);
|
|
127
|
+
if (!force && cached != null && cached.mtimeMs === fileStat.mtimeMs) {
|
|
128
|
+
return cached.data as T;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const raw = await importJsConfigModule(path);
|
|
132
|
+
const parsed = schema.safeParse(raw);
|
|
133
|
+
if (!parsed.success) {
|
|
134
|
+
throw new Error(`Invalid config ${path}: ${parsed.error.message}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
jsConfigCache.set(path, { mtimeMs: fileStat.mtimeMs, data: parsed.data });
|
|
138
|
+
return parsed.data;
|
|
139
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Read a dotted path (e.g. `data.items`) from a parsed JSON value. */
|
|
2
|
+
export function getValueAtPath(value: unknown, path: string | null | undefined): unknown {
|
|
3
|
+
if (path == null || path.trim() === "") {
|
|
4
|
+
return value;
|
|
5
|
+
}
|
|
6
|
+
let current: unknown = value;
|
|
7
|
+
for (const segment of path.split(".")) {
|
|
8
|
+
if (segment === "") {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (current == null || typeof current !== "object") {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
current = (current as Record<string, unknown>)[segment];
|
|
15
|
+
}
|
|
16
|
+
return current;
|
|
17
|
+
}
|
package/src/task/index.ts
CHANGED
|
@@ -35,6 +35,8 @@ export type {
|
|
|
35
35
|
export { TaskManager } from "./task-manager";
|
|
36
36
|
export type { PostTaskPayload } from "./task-post.api";
|
|
37
37
|
export { postTask, postTaskSchema } from "./task-post.api";
|
|
38
|
+
export type { PostTaskWithRetryOptions, PostTaskWithRetryResult } from "./task-post-retry.api";
|
|
39
|
+
export { POST_TASK_DEFAULT_MAX_ATTEMPTS, postTaskWithRetry } from "./task-post-retry.api";
|
|
38
40
|
export { postTaskWithValidation } from "./task-post-validated.api";
|
|
39
41
|
export type {
|
|
40
42
|
TaskRepoValidationFailureReason,
|
package/src/task/task-manager.ts
CHANGED
|
@@ -2,12 +2,13 @@ import { getScanRemoteRepoValues } from "../repo/repo.api";
|
|
|
2
2
|
import { TASK_REPO_USAGE } from "../repo/repo.interface";
|
|
3
3
|
import type { LoggerWithScope } from "../shared/log";
|
|
4
4
|
import type { ResultCode, Task } from "./task.interface";
|
|
5
|
-
import type { ExecutableTaskFn, TaskFn } from "./task-repo-context";
|
|
5
|
+
import type { ExecutableTaskFn, TaskExecutionContext, TaskFn } from "./task-repo-context";
|
|
6
6
|
|
|
7
7
|
export type {
|
|
8
8
|
BootstrapLocalTasksResult,
|
|
9
9
|
BootstrapLocalTasksStatus,
|
|
10
10
|
ExecutableTaskFn,
|
|
11
|
+
TaskExecutionContext,
|
|
11
12
|
TaskFn,
|
|
12
13
|
TaskLocalPackageConfig,
|
|
13
14
|
TaskRepoContext,
|
|
@@ -169,8 +170,8 @@ async function registerTasks({
|
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
/** 将 taskFn 与 params 闭包绑定,供 TaskNode 无参调用 `await taskFn()` */
|
|
172
|
-
function bindTaskFn(taskFn: TaskFn, taskParams: unknown): ExecutableTaskFn {
|
|
173
|
-
const run = async () => taskFn(taskParams);
|
|
173
|
+
function bindTaskFn(taskFn: TaskFn, taskParams: unknown, ctx: TaskExecutionContext): ExecutableTaskFn {
|
|
174
|
+
const run = async () => taskFn(taskParams, ctx);
|
|
174
175
|
return Object.assign(run, {
|
|
175
176
|
displayName: taskFn.displayName,
|
|
176
177
|
taskTypeKey: taskFn.taskTypeKey,
|
|
@@ -272,7 +273,12 @@ const prepareTask = async ({
|
|
|
272
273
|
},
|
|
273
274
|
});
|
|
274
275
|
|
|
275
|
-
const boundTaskFn = bindTaskFn(repoContext.taskFn, taskParams
|
|
276
|
+
const boundTaskFn = bindTaskFn(repoContext.taskFn, taskParams, {
|
|
277
|
+
taskId,
|
|
278
|
+
taskName,
|
|
279
|
+
taskTypeKey,
|
|
280
|
+
traceId: details.traceId ?? taskId,
|
|
281
|
+
});
|
|
276
282
|
logger.debug("任務準備完成", {
|
|
277
283
|
topic: LOG_TOPIC_TASK,
|
|
278
284
|
data: {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { LoggerWithScope } from "../shared/log";
|
|
2
|
+
import type { Task } from "./task.interface";
|
|
3
|
+
import { type PostTaskPayload, postTask } from "./task-post.api";
|
|
4
|
+
|
|
5
|
+
export const POST_TASK_DEFAULT_MAX_ATTEMPTS = 3;
|
|
6
|
+
|
|
7
|
+
export interface PostTaskWithRetryOptions {
|
|
8
|
+
logger: LoggerWithScope;
|
|
9
|
+
topic: string;
|
|
10
|
+
traceId: string;
|
|
11
|
+
itemLabel: string;
|
|
12
|
+
context?: Record<string, unknown>;
|
|
13
|
+
maxAttempts?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type PostTaskWithRetryResult = { ok: true; task: Task } | { ok: false };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Retry wrapper for {@link postTask} — for schedule runners posting tasks over flaky network.
|
|
20
|
+
* Logs warn on intermediate failures and critical when all attempts fail.
|
|
21
|
+
*/
|
|
22
|
+
export async function postTaskWithRetry(
|
|
23
|
+
payload: PostTaskPayload,
|
|
24
|
+
options: PostTaskWithRetryOptions,
|
|
25
|
+
): Promise<PostTaskWithRetryResult> {
|
|
26
|
+
const maxAttempts = options.maxAttempts ?? POST_TASK_DEFAULT_MAX_ATTEMPTS;
|
|
27
|
+
let lastError = "unknown error";
|
|
28
|
+
|
|
29
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
30
|
+
const { data: task, error } = await postTask(payload);
|
|
31
|
+
if (error == null && task != null) {
|
|
32
|
+
return { ok: true, task };
|
|
33
|
+
}
|
|
34
|
+
lastError = error?.message ?? lastError;
|
|
35
|
+
if (attempt < maxAttempts) {
|
|
36
|
+
options.logger.warn("postTask failed, retrying", {
|
|
37
|
+
topic: options.topic,
|
|
38
|
+
data: {
|
|
39
|
+
...options.context,
|
|
40
|
+
traceId: options.traceId,
|
|
41
|
+
itemLabel: options.itemLabel,
|
|
42
|
+
attempt,
|
|
43
|
+
maxAttempts,
|
|
44
|
+
error: lastError,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
options.logger.critical("postTask failed after retries — skip item this cycle", {
|
|
51
|
+
topic: options.topic,
|
|
52
|
+
data: {
|
|
53
|
+
...options.context,
|
|
54
|
+
traceId: options.traceId,
|
|
55
|
+
itemLabel: options.itemLabel,
|
|
56
|
+
maxAttempts,
|
|
57
|
+
error: lastError,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
return { ok: false };
|
|
61
|
+
}
|
|
@@ -4,10 +4,17 @@ export interface TaskRunResult {
|
|
|
4
4
|
extra: unknown;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
export interface TaskExecutionContext {
|
|
8
|
+
taskId: string;
|
|
9
|
+
taskName: string;
|
|
10
|
+
taskTypeKey: string;
|
|
11
|
+
traceId: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
export interface TaskFn {
|
|
8
15
|
displayName: string;
|
|
9
16
|
taskTypeKey: string;
|
|
10
|
-
(taskParams: unknown): Promise<TaskRunResult>;
|
|
17
|
+
(taskParams: unknown, ctx: TaskExecutionContext): Promise<TaskRunResult>;
|
|
11
18
|
}
|
|
12
19
|
|
|
13
20
|
/** Params already bound — used by node executor after prepareTask */
|
|
@@ -144,9 +144,11 @@ export const TriggerManager = {
|
|
|
144
144
|
return [...runnerRegistry.keys()];
|
|
145
145
|
},
|
|
146
146
|
|
|
147
|
-
getRunnersBelowLoopInterval(
|
|
147
|
+
getRunnersBelowLoopInterval(
|
|
148
|
+
loopIntervalMs: number = TRIGGER_LOOP_INTERVAL_MS,
|
|
149
|
+
): Array<{ key: string; intervalMs: number }> {
|
|
148
150
|
return [...runnerRegistry.values()]
|
|
149
|
-
.filter((runner) => runner.intervalMs <
|
|
151
|
+
.filter((runner) => runner.intervalMs < loopIntervalMs)
|
|
150
152
|
.map((runner) => ({ key: runner.key, intervalMs: runner.intervalMs }));
|
|
151
153
|
},
|
|
152
154
|
|
|
@@ -15,14 +15,20 @@ import { TriggerManager } from "./trigger-manager";
|
|
|
15
15
|
*/
|
|
16
16
|
class TriggerNode extends BaseNodeRuntime {
|
|
17
17
|
private readonly requireRunners: boolean;
|
|
18
|
+
private readonly loopIntervalMs: number;
|
|
18
19
|
|
|
19
20
|
constructor(options?: TriggerNodeOptions) {
|
|
20
|
-
super("triggerNode");
|
|
21
|
+
super("triggerNode", { beforeProcessExit: options?.beforeProcessExit });
|
|
21
22
|
this.requireRunners = options?.requireRunners ?? false;
|
|
23
|
+
const loopIntervalMs = options?.loopIntervalMs ?? TRIGGER_LOOP_INTERVAL_MS;
|
|
24
|
+
if (!Number.isFinite(loopIntervalMs) || loopIntervalMs < 1) {
|
|
25
|
+
throw new Error(`[TriggerNode] loopIntervalMs must be a positive number, got ${loopIntervalMs}`);
|
|
26
|
+
}
|
|
27
|
+
this.loopIntervalMs = loopIntervalMs;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
protected getLoopIntervalMs(): number {
|
|
25
|
-
return
|
|
31
|
+
return this.loopIntervalMs;
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
protected loopLoggerMinLevel(): LogLevel {
|
|
@@ -43,13 +49,13 @@ class TriggerNode extends BaseNodeRuntime {
|
|
|
43
49
|
return;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
for (const { key, intervalMs } of TriggerManager.getRunnersBelowLoopInterval()) {
|
|
52
|
+
for (const { key, intervalMs } of TriggerManager.getRunnersBelowLoopInterval(this.loopIntervalMs)) {
|
|
47
53
|
logger.warn("Runner interval 小於主循環 tick 間隔,實際觸發精度受限", {
|
|
48
54
|
topic: LOG_TOPIC_TRIGGER,
|
|
49
55
|
data: {
|
|
50
56
|
runnerKey: key,
|
|
51
57
|
intervalMs,
|
|
52
|
-
loopIntervalMs:
|
|
58
|
+
loopIntervalMs: this.loopIntervalMs,
|
|
53
59
|
},
|
|
54
60
|
});
|
|
55
61
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const LOG_TOPIC_TRIGGER = "trigger";
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/** Default main-loop interval for {@link TriggerNode} when `loopIntervalMs` is omitted (ms). */
|
|
4
4
|
export const TRIGGER_LOOP_INTERVAL_MS = 60_000;
|
|
5
5
|
|
|
6
6
|
/** Default extra retries after first failure within one runner tick. */
|
|
@@ -97,4 +97,14 @@ export interface TriggerNodeOptions {
|
|
|
97
97
|
* Default `false` (warn and idle loop).
|
|
98
98
|
*/
|
|
99
99
|
requireRunners?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Main loop tick interval (ms). Runners are evaluated each tick; effective runner
|
|
102
|
+
* precision is bounded by this value. Default {@link TRIGGER_LOOP_INTERVAL_MS} (60s).
|
|
103
|
+
*/
|
|
104
|
+
loopIntervalMs?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Called after node logout, immediately before `process.exit`.
|
|
107
|
+
* Typical use: {@link unregisterServiceAtShutdown} for single-process L3 services.
|
|
108
|
+
*/
|
|
109
|
+
beforeProcessExit?: () => void | Promise<void>;
|
|
100
110
|
}
|