target-supabase-sdk 0.2.24 → 0.3.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.
Files changed (81) hide show
  1. package/dist/browser.d.ts +3 -2
  2. package/dist/browser.d.ts.map +1 -1
  3. package/dist/browser.js +3 -2
  4. package/dist/browser.js.map +1 -1
  5. package/dist/chunks/{list.api-BzbTJBsM.js → config-BNxsVKR4.js} +163 -10
  6. package/dist/chunks/config-BNxsVKR4.js.map +1 -0
  7. package/dist/chunks/{enable-C7lFooTG.js → enable-CF77OsXr.js} +20 -238
  8. package/dist/chunks/enable-CF77OsXr.js.map +1 -0
  9. package/dist/chunks/{task-queue-C_nUaM2S.js → task-queue-BL-KwAGW.js} +39 -13
  10. package/dist/chunks/task-queue-BL-KwAGW.js.map +1 -0
  11. package/dist/node/{node-runtime.base.d.ts → base-node-runtime.d.ts} +4 -1
  12. package/dist/node/base-node-runtime.d.ts.map +1 -0
  13. package/dist/node/node-liveness.d.ts +2 -0
  14. package/dist/node/node-liveness.d.ts.map +1 -1
  15. package/dist/node/node-service-id.d.ts +3 -0
  16. package/dist/node/node-service-id.d.ts.map +1 -0
  17. package/dist/node/node.api.d.ts +9 -1
  18. package/dist/node/node.api.d.ts.map +1 -1
  19. package/dist/node/node.interface.d.ts +9 -2
  20. package/dist/node/node.interface.d.ts.map +1 -1
  21. package/dist/node/service-guard/run-service-guard-tick.d.ts.map +1 -1
  22. package/dist/node/service-guard/service-guard-node.d.ts +3 -1
  23. package/dist/node/service-guard/service-guard-node.d.ts.map +1 -1
  24. package/dist/node/service-host/service-host.d.ts.map +1 -1
  25. package/dist/node/service-host/single-process-service.d.ts.map +1 -1
  26. package/dist/node.d.ts +2 -2
  27. package/dist/node.d.ts.map +1 -1
  28. package/dist/node.js +244 -198
  29. package/dist/node.js.map +1 -1
  30. package/dist/preload.js +3 -3
  31. package/dist/service/service.interface.d.ts +2 -2
  32. package/dist/service/service.interface.d.ts.map +1 -1
  33. package/dist/shared/log/core/log-batch.d.ts +4 -4
  34. package/dist/shared/log/core/log-batch.d.ts.map +1 -1
  35. package/dist/shared/log/index.d.ts +2 -1
  36. package/dist/shared/log/index.d.ts.map +1 -1
  37. package/dist/shared/log/spool/collector.d.ts +1 -1
  38. package/dist/shared/log/spool/collector.d.ts.map +1 -1
  39. package/dist/shared/log/spool/file.d.ts +2 -8
  40. package/dist/shared/log/spool/file.d.ts.map +1 -1
  41. package/dist/shared/log/spool/writer.d.ts.map +1 -1
  42. package/dist/shared/log/upload/flush.d.ts.map +1 -1
  43. package/dist/shared/log/upload/interface.d.ts +3 -11
  44. package/dist/shared/log/upload/interface.d.ts.map +1 -1
  45. package/dist/task/task-node.d.ts +3 -1
  46. package/dist/task/task-node.d.ts.map +1 -1
  47. package/dist/trigger/trigger-manager.d.ts +1 -1
  48. package/dist/trigger/trigger-node.d.ts +3 -1
  49. package/dist/trigger/trigger-node.d.ts.map +1 -1
  50. package/dist/trigger/trigger.api.d.ts +10 -10
  51. package/package.json +1 -1
  52. package/src/browser.ts +4 -3
  53. package/src/node/{node-runtime.base.ts → base-node-runtime.ts} +11 -2
  54. package/src/node/node-liveness.ts +6 -1
  55. package/src/node/node-service-id.ts +12 -0
  56. package/src/node/node.api.ts +7 -5
  57. package/src/node/node.interface.ts +10 -2
  58. package/src/node/service-guard/run-service-guard-tick.ts +12 -6
  59. package/src/node/service-guard/service-guard-node.ts +6 -1
  60. package/src/node/service-host/service-host.ts +2 -1
  61. package/src/node/service-host/single-process-service.ts +3 -0
  62. package/src/node.ts +2 -2
  63. package/src/service/service.interface.ts +2 -2
  64. package/src/shared/log/README.md +2 -3
  65. package/src/shared/log/core/log-batch.ts +12 -10
  66. package/src/shared/log/index.ts +2 -1
  67. package/src/shared/log/spool/collector.ts +46 -20
  68. package/src/shared/log/spool/file.ts +12 -25
  69. package/src/shared/log/spool/writer.ts +2 -7
  70. package/src/shared/log/upload/flush.ts +23 -38
  71. package/src/shared/log/upload/interface.ts +3 -12
  72. package/src/task/task-node.ts +6 -1
  73. package/src/trigger/trigger-manager.ts +1 -1
  74. package/src/trigger/trigger-node.ts +6 -1
  75. package/dist/chunks/enable-C7lFooTG.js.map +0 -1
  76. package/dist/chunks/list.api-BzbTJBsM.js.map +0 -1
  77. package/dist/chunks/task-queue-C_nUaM2S.js.map +0 -1
  78. package/dist/node/node-runtime.base.d.ts.map +0 -1
  79. package/dist/shared/log/upload/batch-id.d.ts +0 -13
  80. package/dist/shared/log/upload/batch-id.d.ts.map +0 -1
  81. package/src/shared/log/upload/batch-id.ts +0 -43
@@ -29,7 +29,7 @@ export interface ServiceDetails {
29
29
  runtime?: ServiceRuntime;
30
30
  }
31
31
 
32
- /** Per-node snapshot under a running Service — maintained by the service guard. */
32
+ /** Per-node snapshot for nodes owned by this Service instance (`Node.name === serviceId`). */
33
33
  export interface ServiceNodeSnapshot {
34
34
  nodeId: string;
35
35
  status: NodeStatus;
@@ -38,7 +38,7 @@ export interface ServiceNodeSnapshot {
38
38
 
39
39
  /**
40
40
  * Runtime observability for a registered Service instance.
41
- * Heartbeat and node rollups are written by the service guard, not the system registry Config.
41
+ * Heartbeat and node rollups are written by the service guard (scoped to this instance), not the system registry Config.
42
42
  */
43
43
  export interface ServiceRuntime {
44
44
  lastHeartBeat: number;
@@ -18,8 +18,7 @@ src/shared/log/
18
18
 
19
19
  upload/ # shared Supabase upload pipeline (Node)
20
20
  env.ts # LOG_PERSIST_ENABLED, SERVICE, PROCESS, runtime dir
21
- interface.ts # LogBatch meta, loader keys, lane type
22
- batch-id.ts # computeLogBatchIdempotencyKey
21
+ interface.ts # LogBatch meta, LOG_BATCH_LIST_VALUE, lane type
23
22
  flush.ts # buildLogListDraft, postLogBatch
24
23
  hook.ts # LogManager → spool writer offer bridge
25
24
  logger.ts # internal pipeline loggers (no feedback loop)
@@ -58,7 +57,7 @@ App code
58
57
  → {spoolRoot}/{serviceId}/{role}/*.tmp
59
58
 
60
59
  Guard collect-log runner (1 min tick)
61
- → read *.tmp → postLogBatch → rename *.json
60
+ → read *.tmp → postLogBatch (List value=log-batch, name=service) → rename *.json / .synced marker
62
61
  → GC old tmp/json (once per day; 7 d retention)
63
62
 
64
63
  log-service (downstream)
@@ -5,8 +5,8 @@
5
5
  */
6
6
 
7
7
  import type { List } from "../../../list/list.interface";
8
- import type { LogEntry } from "../core/log-manager";
9
- import { LOG_PERSIST_LOADER_KEY, type LogBatchMeta, type LogPersistLane } from "../upload/interface";
8
+ import { LOG_BATCH_LIST_VALUE, type LogBatchMeta, type LogPersistLane } from "../upload/interface";
9
+ import type { LogEntry } from "./log-manager";
10
10
 
11
11
  const LANES: ReadonlySet<string> = new Set<LogPersistLane>(["fast", "medium", "slow"]);
12
12
 
@@ -26,8 +26,8 @@ export interface FlatLogEntry extends LogEntry {
26
26
  process: string;
27
27
  lane: LogPersistLane;
28
28
  pid: number;
29
- /** Idempotency key of the source batch. */
30
- batchId: string;
29
+ /** Source List row id. */
30
+ listId: string;
31
31
  }
32
32
 
33
33
  function asRecord(value: unknown): Record<string, unknown> | null {
@@ -61,8 +61,7 @@ export function parseLogBatchMeta(rawMeta: unknown): LogBatchMeta | null {
61
61
  const service = asString(source.service);
62
62
  const processName = asString(source.process);
63
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) {
64
+ if (service == null || processName == null || laneRaw == null || !LANES.has(laneRaw)) {
66
65
  return null;
67
66
  }
68
67
 
@@ -75,7 +74,6 @@ export function parseLogBatchMeta(rawMeta: unknown): LogBatchMeta | null {
75
74
  from: asFiniteNumber(source.from),
76
75
  to: asFiniteNumber(source.to),
77
76
  flushedAt: asFiniteNumber(source.flushedAt) ?? 0,
78
- idempotencyKey,
79
77
  };
80
78
  }
81
79
 
@@ -93,9 +91,9 @@ export function isLogEntry(value: unknown): value is LogEntry {
93
91
  );
94
92
  }
95
93
 
96
- /** Is this List a persisted log batch (by loaderKey)? */
94
+ /** Is this List a persisted log batch (by `value`)? */
97
95
  export function isLogBatchList(list: List): boolean {
98
- return list.details?.loaderKey === LOG_PERSIST_LOADER_KEY;
96
+ return list.value === LOG_BATCH_LIST_VALUE;
99
97
  }
100
98
 
101
99
  /**
@@ -106,6 +104,10 @@ export function decodeLogBatch(list: List): DecodedLogBatch | null {
106
104
  if (!isLogBatchList(list)) {
107
105
  return null;
108
106
  }
107
+ const loaderKey = list.details?.loaderKey;
108
+ if (typeof loaderKey === "string" && loaderKey.endsWith(".bad")) {
109
+ return null;
110
+ }
109
111
  const meta = parseLogBatchMeta(list.details?.meta);
110
112
  if (meta == null) {
111
113
  return null;
@@ -142,7 +144,7 @@ export function flattenLogBatch(batch: DecodedLogBatch): FlatLogEntry[] {
142
144
  process: batch.meta.process,
143
145
  lane: batch.meta.lane,
144
146
  pid: batch.meta.pid,
145
- batchId: batch.meta.idempotencyKey,
147
+ listId: batch.listId,
146
148
  }));
147
149
  }
148
150
 
@@ -42,5 +42,6 @@ export {
42
42
  resolveLogData,
43
43
  withModule,
44
44
  } from "./core/log-scope";
45
+ export { formatLogSpoolBatchTimestamp } from "./spool/file";
45
46
  export type { LogBatchMeta, LogPersistLane } from "./upload/interface";
46
- export { LOG_BATCH_LOADER_KEY, LOG_PERSIST_LOADER_KEY } from "./upload/interface";
47
+ export { LOG_BATCH_LIST_VALUE } from "./upload/interface";
@@ -1,7 +1,6 @@
1
- import { access, constants, readdir, readFile, rename, stat, unlink } from "node:fs/promises";
1
+ import { access, constants, readdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { isLogEntry } from "../core/log-batch";
4
- import { computeLogBatchIdempotencyKey } from "../upload/batch-id";
5
4
  import { buildLogListDraft, postLogBatch } from "../upload/flush";
6
5
  import { spoolLogger } from "../upload/logger";
7
6
  import { type DEFAULT_LOG_SPOOL_CONFIG, resolveLogSpoolConfig } from "./config";
@@ -11,6 +10,7 @@ import { logSpoolProcessDir, resolveLogSpoolRoot } from "./paths";
11
10
  import { resolveAllLogSpoolProcessRoles } from "./process-roles";
12
11
 
13
12
  const UPLOAD_LANE = "slow";
13
+ const MAX_UPLOADS_PER_COLLECT_TICK = 20;
14
14
 
15
15
  /** Per serviceId — guard collect-log tick runs GC at most once per `gc.intervalMs`. */
16
16
  const lastGcAtMsByServiceId = new Map<string, number>();
@@ -61,6 +61,10 @@ function processMetaKey(serviceId: string, processRole: LogSpoolProcessRole): st
61
61
  return `${serviceId}:${processRole}`;
62
62
  }
63
63
 
64
+ function syncedMarkerPath(jsonPath: string): string {
65
+ return jsonPath.replace(/\.json$/, ".synced");
66
+ }
67
+
64
68
  async function uploadTmpBatch(input: {
65
69
  serviceId: string;
66
70
  serviceValue: string;
@@ -78,6 +82,7 @@ async function uploadTmpBatch(input: {
78
82
 
79
83
  const jsonFileName = input.fileName.replace(/\.tmp$/, ".json");
80
84
  const jsonPath = join(logSpoolProcessDir(input.spoolRoot, input.serviceId, input.processRole), jsonFileName);
85
+ const syncedPath = syncedMarkerPath(jsonPath);
81
86
 
82
87
  if (await pathExists(jsonPath)) {
83
88
  try {
@@ -92,6 +97,19 @@ async function uploadTmpBatch(input: {
92
97
  return "skipped";
93
98
  }
94
99
 
100
+ if (await pathExists(syncedPath)) {
101
+ try {
102
+ await rename(input.tmpPath, jsonPath);
103
+ } catch {
104
+ try {
105
+ await unlink(input.tmpPath);
106
+ } catch {
107
+ // best-effort
108
+ }
109
+ }
110
+ return "skipped";
111
+ }
112
+
95
113
  const batch = await readBatchTmpFile(input.tmpPath);
96
114
  if (batch == null) {
97
115
  spoolLogger.warn("collect-log skipped — tmp failed validation", {
@@ -108,19 +126,10 @@ async function uploadTmpBatch(input: {
108
126
  }
109
127
 
110
128
  const processKey = processMetaKey(input.serviceId, input.processRole);
111
- // TODO: `lane` is legacy transport metadata — scheduled for removal; fixed to "slow" for downstream decode compatibility.
112
- const idempotencyKey = computeLogBatchIdempotencyKey({
113
- service: input.serviceValue,
114
- process: processKey,
115
- lane: UPLOAD_LANE,
116
- entries: batch.entries,
117
- });
118
-
119
129
  const draft = buildLogListDraft({
120
130
  service: input.serviceValue,
121
131
  process: processKey,
122
132
  lane: UPLOAD_LANE,
123
- idempotencyKey,
124
133
  entries: batch.entries,
125
134
  });
126
135
 
@@ -136,8 +145,20 @@ async function uploadTmpBatch(input: {
136
145
  return "failed";
137
146
  }
138
147
 
148
+ try {
149
+ await writeFile(syncedPath, `${Date.now()}\n`, "utf8");
150
+ } catch (error) {
151
+ const message = error instanceof Error ? error.message : String(error);
152
+ spoolLogger.warn("collect-log synced marker write failed", { syncedPath, error: message });
153
+ }
154
+
139
155
  try {
140
156
  await rename(input.tmpPath, jsonPath);
157
+ try {
158
+ await unlink(syncedPath);
159
+ } catch {
160
+ // best-effort
161
+ }
141
162
  } catch (error) {
142
163
  const message = error instanceof Error ? error.message : String(error);
143
164
  spoolLogger.error("collect-log rename tmp→json failed after upload", {
@@ -145,14 +166,13 @@ async function uploadTmpBatch(input: {
145
166
  jsonPath,
146
167
  error: message,
147
168
  });
148
- return "failed";
169
+ return "uploaded";
149
170
  }
150
171
 
151
172
  spoolLogger.info("collect-log uploaded batch", {
152
173
  processRole: input.processRole,
153
174
  fileName: jsonFileName,
154
175
  entryCount: batch.entries.length,
155
- idempotencyKey,
156
176
  });
157
177
 
158
178
  return "uploaded";
@@ -217,22 +237,22 @@ async function gcSyncedJsonFiles(input: {
217
237
  }
218
238
 
219
239
  for (const name of names) {
220
- if (!name.endsWith(".json")) {
240
+ if (!name.endsWith(".json") && !name.endsWith(".synced")) {
221
241
  continue;
222
242
  }
223
- if (parseLogSpoolBatchFilename(name) == null) {
243
+ if (parseLogSpoolBatchFilename(name) == null && !name.endsWith(".synced")) {
224
244
  continue;
225
245
  }
226
246
 
227
- const jsonPath = join(processDir, name);
247
+ const filePath = join(processDir, name);
228
248
  try {
229
- const fileStat = await stat(jsonPath);
249
+ const fileStat = await stat(filePath);
230
250
  if (input.nowMs - fileStat.mtimeMs <= input.syncedRetentionMs) {
231
251
  continue;
232
252
  }
233
- await unlink(jsonPath);
253
+ await unlink(filePath);
234
254
  removed += 1;
235
- spoolLogger.info("collect-log gc removed synced json", {
255
+ spoolLogger.info("collect-log gc removed synced file", {
236
256
  fileName: name,
237
257
  processRole,
238
258
  });
@@ -256,7 +276,7 @@ function shouldRunGc(serviceId: string, nowMs: number, intervalMs: number): bool
256
276
 
257
277
  /**
258
278
  * Guard-only: upload pending `.tmp` batches, rename to `.json`, then periodic GC.
259
- * Upload completion is implied by `.json` presence; no sync-state file.
279
+ * Post success is tracked via `.json` or `.synced` marker (prevents duplicate DB inserts).
260
280
  */
261
281
  export async function runCollectLogTick(input: RunCollectLogTickInput): Promise<RunCollectLogTickResult> {
262
282
  const config = resolveLogSpoolConfig(input.config);
@@ -271,6 +291,8 @@ export async function runCollectLogTick(input: RunCollectLogTickInput): Promise<
271
291
  gcSyncedRemoved: 0,
272
292
  };
273
293
 
294
+ let uploadsThisTick = 0;
295
+
274
296
  for (const processRole of resolveAllLogSpoolProcessRoles()) {
275
297
  const processDir = logSpoolProcessDir(spoolRoot, input.serviceId, processRole);
276
298
  let names: string[] = [];
@@ -284,6 +306,9 @@ export async function runCollectLogTick(input: RunCollectLogTickInput): Promise<
284
306
  if (!name.endsWith(".tmp")) {
285
307
  continue;
286
308
  }
309
+ if (uploadsThisTick >= MAX_UPLOADS_PER_COLLECT_TICK) {
310
+ break;
311
+ }
287
312
  const status = await uploadTmpBatch({
288
313
  serviceId: input.serviceId,
289
314
  serviceValue: input.serviceValue,
@@ -295,6 +320,7 @@ export async function runCollectLogTick(input: RunCollectLogTickInput): Promise<
295
320
  });
296
321
  if (status === "uploaded") {
297
322
  result.uploaded += 1;
323
+ uploadsThisTick += 1;
298
324
  } else if (status === "skipped") {
299
325
  result.skipped += 1;
300
326
  } else {
@@ -1,37 +1,24 @@
1
- import type { LogEntry } from "../core/log-manager";
2
- import { computeLogBatchIdempotencyKey } from "../upload/batch-id";
1
+ import { randomBytes } from "node:crypto";
3
2
  import type { LogSpoolProcessRole } from "./interface";
4
3
 
5
4
  /** `role.timestamp.hash.tmp|json` — role is lowercase alphanumeric + hyphen. */
6
- const BATCH_FILE_PATTERN = /^([a-z][a-z0-9-]*)\.(\d{14})\.([a-f0-9]{8})\.(tmp|json)$/;
5
+ const BATCH_FILE_PATTERN = /^([a-z][a-z0-9-]*)\.(\d{14})\.([a-f0-9]{8})\.(tmp|json|synced)$/;
7
6
 
8
7
  export function formatLogSpoolBatchTimestamp(ms: number): string {
9
8
  const d = new Date(ms);
10
9
  const pad = (n: number) => String(n).padStart(2, "0");
11
10
  return (
12
- `${d.getFullYear()}` +
13
- `${pad(d.getMonth() + 1)}` +
14
- `${pad(d.getDate())}` +
15
- `${pad(d.getHours())}` +
16
- `${pad(d.getMinutes())}` +
17
- `${pad(d.getSeconds())}`
11
+ `${d.getUTCFullYear()}` +
12
+ `${pad(d.getUTCMonth() + 1)}` +
13
+ `${pad(d.getUTCDate())}` +
14
+ `${pad(d.getUTCHours())}` +
15
+ `${pad(d.getUTCMinutes())}` +
16
+ `${pad(d.getUTCSeconds())}`
18
17
  );
19
18
  }
20
19
 
21
- export function computeLogSpoolShortHash(input: {
22
- serviceValue: string;
23
- serviceId: string;
24
- processRole: LogSpoolProcessRole;
25
- entries: LogEntry[];
26
- }): string {
27
- const processKey = `${input.serviceId}:${input.processRole}`;
28
- const idempotencyKey = computeLogBatchIdempotencyKey({
29
- service: input.serviceValue,
30
- process: processKey,
31
- lane: "slow",
32
- entries: input.entries,
33
- });
34
- return idempotencyKey.slice(0, 8);
20
+ export function randomLogSpoolShortId(): string {
21
+ return randomBytes(4).toString("hex");
35
22
  }
36
23
 
37
24
  export function buildLogSpoolBatchBaseName(input: {
@@ -47,7 +34,7 @@ export function parseLogSpoolBatchFilename(filename: string): {
47
34
  processRole: LogSpoolProcessRole;
48
35
  timestampMs: number;
49
36
  shortHash: string;
50
- extension: "tmp" | "json";
37
+ extension: "tmp" | "json" | "synced";
51
38
  } | null {
52
39
  const match = BATCH_FILE_PATTERN.exec(filename);
53
40
  if (match == null) {
@@ -57,7 +44,7 @@ export function parseLogSpoolBatchFilename(filename: string): {
57
44
  const processRole = match[1] as LogSpoolProcessRole;
58
45
  const tsRaw = match[2];
59
46
  const shortHash = match[3];
60
- const extension = match[4] as "tmp" | "json";
47
+ const extension = match[4] as "tmp" | "json" | "synced";
61
48
 
62
49
  const year = Number(tsRaw.slice(0, 4));
63
50
  const month = Number(tsRaw.slice(4, 6));
@@ -5,7 +5,7 @@ import { estimateEntriesBytes, takeEntriesWithinBytes } from "../upload/flush";
5
5
  import { registerLogPersistOffer } from "../upload/hook";
6
6
  import { isLogPersistInternalTopic, spoolLogger } from "../upload/logger";
7
7
  import { DEFAULT_LOG_SPOOL_CONFIG, resolveLogSpoolConfig } from "./config";
8
- import { buildLogSpoolBatchBaseName, computeLogSpoolShortHash } from "./file";
8
+ import { buildLogSpoolBatchBaseName, randomLogSpoolShortId } from "./file";
9
9
  import type { EnableLogSpoolOptions, LogSpoolBatchFile, LogSpoolProcessRole, LogSpoolWriterStats } from "./interface";
10
10
  import { logSpoolProcessDir, resolveLogSpoolRoot } from "./paths";
11
11
 
@@ -224,12 +224,7 @@ class LogSpoolWriter {
224
224
  spoolRoot: string;
225
225
  }): Promise<{ fileName: string; tmpPath: string }> {
226
226
  const first = input.entries[0];
227
- const shortHash = computeLogSpoolShortHash({
228
- serviceValue: input.serviceValue,
229
- serviceId: input.serviceId,
230
- processRole: input.processRole,
231
- entries: input.entries,
232
- });
227
+ const shortHash = randomLogSpoolShortId();
233
228
  const baseName = buildLogSpoolBatchBaseName({
234
229
  processRole: input.processRole,
235
230
  firstEntryTimestamp: first.timestamp,
@@ -1,9 +1,8 @@
1
- import { isCreateTargetAlreadyExistsError } from "../../../core.api";
2
- import { postLogListCreate } from "../../../list/list.api";
1
+ import { postListCreate } from "../../../list/list.api";
3
2
  import { CategoryList } from "../../../list/list.interface";
4
3
  import type { LogEntry } from "../core/log-manager";
5
4
  import type { BuildLogListDraftInput, LogListDraft } from "./interface";
6
- import { LOG_PERSIST_LOADER_KEY } from "./interface";
5
+ import { LOG_BATCH_LIST_VALUE } from "./interface";
7
6
  import { persistLogger } from "./logger";
8
7
 
9
8
  export function estimateEntriesBytes(entries: LogEntry[]): number {
@@ -18,19 +17,18 @@ export function estimateEntriesBytes(entries: LogEntry[]): number {
18
17
  }
19
18
 
20
19
  export function buildLogListDraft(input: BuildLogListDraftInput): LogListDraft {
21
- const { service, process, lane, idempotencyKey, entries } = input;
22
- // TODO: `lane` is legacy transport metadata — scheduled for removal; callers still pass a fixed value for decode compatibility.
20
+ const { service, process, lane, entries } = input;
23
21
  const first = entries[0];
24
22
  const last = entries[entries.length - 1];
25
23
 
26
24
  return {
27
- name: `log-batch:${service}:${process}:${lane}`,
28
- value: idempotencyKey,
25
+ name: service,
26
+ value: LOG_BATCH_LIST_VALUE,
29
27
  category: CategoryList.LIST,
30
28
  tagList: ["log", lane, service],
31
29
  details: {
32
30
  manifestVersion: 0,
33
- loaderKey: LOG_PERSIST_LOADER_KEY,
31
+ loaderKey: LOG_BATCH_LIST_VALUE,
34
32
  meta: {
35
33
  service,
36
34
  process,
@@ -40,7 +38,6 @@ export function buildLogListDraft(input: BuildLogListDraftInput): LogListDraft {
40
38
  from: first?.timestamp ?? null,
41
39
  to: last?.timestamp ?? null,
42
40
  flushedAt: Date.now(),
43
- idempotencyKey,
44
41
  },
45
42
  preview: first?.message ?? "",
46
43
  items: entries,
@@ -67,36 +64,24 @@ async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: str
67
64
  }
68
65
 
69
66
  export async function postLogBatch(draft: LogListDraft, postTimeoutMs: number): Promise<void> {
70
- try {
71
- const result = await withTimeout(
72
- postLogListCreate({
73
- name: draft.name,
74
- value: draft.value,
75
- category: draft.category,
76
- details: draft.details,
77
- }),
78
- postTimeoutMs,
79
- "postLogListCreate",
80
- );
67
+ const result = await withTimeout(
68
+ postListCreate({
69
+ name: draft.name,
70
+ value: draft.value,
71
+ category: draft.category,
72
+ details: draft.details,
73
+ }),
74
+ postTimeoutMs,
75
+ "postListCreate",
76
+ );
81
77
 
82
- if (!result.success) {
83
- const message = result.error?.message ?? "postLogListCreate failed";
84
- persistLogger.error("post log batch failed", {
85
- idempotencyKey: draft.value,
86
- name: draft.name,
87
- error: message,
88
- });
89
- throw new Error(message);
90
- }
91
- } catch (error) {
92
- if (isCreateTargetAlreadyExistsError(error)) {
93
- persistLogger.error("post log batch idempotent duplicate — treating as success", {
94
- idempotencyKey: draft.value,
95
- name: draft.name,
96
- });
97
- return;
98
- }
99
- throw error;
78
+ if (!result.success) {
79
+ const message = result.error?.message ?? "postListCreate failed";
80
+ persistLogger.error("post log batch failed", {
81
+ name: draft.name,
82
+ error: message,
83
+ });
84
+ throw new Error(message);
100
85
  }
101
86
  }
102
87
 
@@ -2,17 +2,10 @@ import type { TargetDraft } from "../../../core.interface";
2
2
  import type { List } from "../../../list/list.interface";
3
3
  import type { LogEntry } from "../core/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";
5
+ /** Discriminator on `List.value` and `details.loaderKey` — query by `value` only, not JSON loaderKey. */
6
+ export const LOG_BATCH_LIST_VALUE = "log-batch";
11
7
 
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
-
15
- /** Legacy transport metadata — fixed `"slow"` for spool uploads; required for downstream decode today. */
8
+ /** Legacy transport metadata — fixed `"slow"` for spool uploads. */
16
9
  export type LogPersistLane = "fast" | "medium" | "slow";
17
10
 
18
11
  export interface LogBatchMeta {
@@ -24,14 +17,12 @@ export interface LogBatchMeta {
24
17
  from: number | null;
25
18
  to: number | null;
26
19
  flushedAt: number;
27
- idempotencyKey: string;
28
20
  }
29
21
 
30
22
  export interface BuildLogListDraftInput {
31
23
  service: string;
32
24
  process: string;
33
25
  lane: LogPersistLane;
34
- idempotencyKey: string;
35
26
  entries: LogEntry[];
36
27
  }
37
28
 
@@ -1,5 +1,6 @@
1
1
  import { isOptimisticLockResponse } from "../core.api";
2
- import { BaseNodeRuntime, LOG_TOPIC_NODE, type NodeLoopContext } from "../node/node-runtime.base";
2
+ import { BaseNodeRuntime, LOG_TOPIC_NODE, type NodeLoopContext } from "../node/base-node-runtime";
3
+ import { NodeKind } from "../node/node.interface";
3
4
  import { createLogger, createScope, type LoggerWithScope, withModule } from "../shared/log";
4
5
  import { getErrorMessage, toError } from "../shared/utils/error.utils";
5
6
  import { type PatchClaimTaskPayload, patchChangeTaskStatus, patchClaimTask } from "./task.api";
@@ -28,6 +29,10 @@ class TaskNode extends BaseNodeRuntime {
28
29
  super("taskNode");
29
30
  }
30
31
 
32
+ protected getNodeKind(): NodeKind {
33
+ return NodeKind.TASK;
34
+ }
35
+
31
36
  protected async onBeforeRegisterNode(logger: LoggerWithScope): Promise<void> {
32
37
  const { availableTaskList, includeRemote } = await TaskManager.registerTasks({ logger });
33
38
  this._availableTaskList = availableTaskList;
@@ -1,4 +1,4 @@
1
- import type { NodeLoopContext } from "../node/node-runtime.base";
1
+ import type { NodeLoopContext } from "../node/base-node-runtime";
2
2
  import { createLogger, createScope, withModule } from "../shared/log";
3
3
  import { getErrorMessage } from "../shared/utils/error.utils";
4
4
  import {
@@ -1,4 +1,5 @@
1
- import { BaseNodeRuntime, LOG_TOPIC_NODE, type NodeLoopContext } from "../node/node-runtime.base";
1
+ import { BaseNodeRuntime, LOG_TOPIC_NODE, type NodeLoopContext } from "../node/base-node-runtime";
2
+ import { NodeKind } from "../node/node.interface";
2
3
  import { createLogger, createScope, type LoggerWithScope, LogLevel } from "../shared/log";
3
4
  import { LOG_TOPIC_TRIGGER, TRIGGER_LOOP_INTERVAL_MS } from "./trigger.constant";
4
5
  import type { TriggerNodeOptions } from "./trigger.interface";
@@ -27,6 +28,10 @@ class TriggerNode extends BaseNodeRuntime {
27
28
  this.loopIntervalMs = loopIntervalMs;
28
29
  }
29
30
 
31
+ protected getNodeKind(): NodeKind {
32
+ return NodeKind.TRIGGER;
33
+ }
34
+
30
35
  protected getLoopIntervalMs(): number {
31
36
  return this.loopIntervalMs;
32
37
  }