@dbx-tools/appkit-mastra 0.1.18 → 0.1.19
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 +78 -66
- package/dist/src/agents.js +6 -7
- package/dist/src/chart.js +7 -29
- package/dist/src/genie.d.ts +5 -6
- package/dist/src/genie.js +16 -36
- package/dist/src/history.js +3 -3
- package/dist/src/writer.d.ts +23 -0
- package/dist/src/writer.js +37 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/agents.ts +6 -7
- package/src/chart.ts +24 -40
- package/src/genie.ts +91 -111
- package/src/history.ts +3 -3
- package/src/writer.ts +44 -0
package/package.json
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"name": "@dbx-tools/appkit-mastra",
|
|
12
|
-
"version": "0.1.
|
|
12
|
+
"version": "0.1.19",
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@databricks/sdk-experimental": "^0.17",
|
|
15
|
-
"@dbx-tools/appkit-mastra-shared": "0.1.
|
|
16
|
-
"@dbx-tools/genie": "0.1.
|
|
17
|
-
"@dbx-tools/genie-shared": "0.1.
|
|
18
|
-
"@dbx-tools/shared": "0.1.
|
|
15
|
+
"@dbx-tools/appkit-mastra-shared": "0.1.19",
|
|
16
|
+
"@dbx-tools/genie": "0.1.19",
|
|
17
|
+
"@dbx-tools/genie-shared": "0.1.19",
|
|
18
|
+
"@dbx-tools/shared": "0.1.19",
|
|
19
19
|
"@mastra/ai-sdk": "^1",
|
|
20
20
|
"@mastra/core": "^1",
|
|
21
21
|
"@mastra/express": "^1",
|
package/src/agents.ts
CHANGED
|
@@ -658,13 +658,12 @@ function buildPluginsMap(
|
|
|
658
658
|
* Genie agent inherits the same model resolver / fallback
|
|
659
659
|
* ladder the calling agents use.
|
|
660
660
|
*
|
|
661
|
-
* The
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
* keep working without change.
|
|
661
|
+
* The Genie agent talks to Genie directly via `@dbx-tools/genie`
|
|
662
|
+
* (`genieEventChat`) and the workspace
|
|
663
|
+
* `statementExecution.getStatement` API. AppKit's stock `genie`
|
|
664
|
+
* plugin is honored only for its resource manifest and `spaces`
|
|
665
|
+
* config so existing `app.yaml` configs and `genie({ spaces })`
|
|
666
|
+
* wiring keep working without change.
|
|
668
667
|
*/
|
|
669
668
|
function resolveProvider(
|
|
670
669
|
config: MastraPluginConfig,
|
package/src/chart.ts
CHANGED
|
@@ -36,6 +36,7 @@ import { z } from "zod";
|
|
|
36
36
|
|
|
37
37
|
import type { MastraPluginConfig } from "./config.js";
|
|
38
38
|
import { ModelTier, modelForTier, buildModel } from "./model.js";
|
|
39
|
+
import { safeWrite } from "./writer.js";
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* Module-level logger tagged `[mastra/chart]`. Uses the shared
|
|
@@ -425,60 +426,43 @@ export function buildRenderDataTool(config: MastraPluginConfig) {
|
|
|
425
426
|
// for the table-like fallback / hover, option for the
|
|
426
427
|
// actual render. Best-effort write so a closed
|
|
427
428
|
// downstream stream can't take the tool down.
|
|
428
|
-
await safeWrite(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
429
|
+
await safeWrite(
|
|
430
|
+
log,
|
|
431
|
+
writer,
|
|
432
|
+
{
|
|
433
|
+
type: "chart",
|
|
434
|
+
chartId,
|
|
435
|
+
title,
|
|
436
|
+
...(description ? { description } : {}),
|
|
437
|
+
data,
|
|
438
|
+
option,
|
|
439
|
+
},
|
|
440
|
+
{ chartId },
|
|
441
|
+
);
|
|
436
442
|
} catch (err) {
|
|
437
443
|
log.warn("render:error", {
|
|
438
444
|
chartId,
|
|
439
445
|
elapsedMs: Date.now() - startedAt,
|
|
440
|
-
error:
|
|
446
|
+
error: commonUtils.errorMessage(err),
|
|
441
447
|
});
|
|
442
448
|
// Surface as a writer-level error so the slot can
|
|
443
449
|
// transition to "couldn't render chart" without the
|
|
444
450
|
// parent agent surfacing a stack trace.
|
|
445
|
-
await safeWrite(
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
451
|
+
await safeWrite(
|
|
452
|
+
log,
|
|
453
|
+
writer,
|
|
454
|
+
{
|
|
455
|
+
type: "error",
|
|
456
|
+
error: commonUtils.errorMessage(err),
|
|
457
|
+
},
|
|
458
|
+
{ chartId },
|
|
459
|
+
);
|
|
449
460
|
}
|
|
450
461
|
return { chartId };
|
|
451
462
|
},
|
|
452
463
|
});
|
|
453
464
|
}
|
|
454
465
|
|
|
455
|
-
/**
|
|
456
|
-
* Best-effort writer.write. Failures are logged at `warn` (a
|
|
457
|
-
* persistently-closed writer is the most likely culprit when
|
|
458
|
-
* chart events go missing client-side) but swallowed so a closed
|
|
459
|
-
* downstream stream (cancelled request, client navigated away)
|
|
460
|
-
* can't take a tool down.
|
|
461
|
-
*/
|
|
462
|
-
async function safeWrite(
|
|
463
|
-
writer: MinimalWriter | undefined,
|
|
464
|
-
chartId: string,
|
|
465
|
-
chunk: unknown,
|
|
466
|
-
): Promise<void> {
|
|
467
|
-
if (!writer) {
|
|
468
|
-
log.debug("write:no-writer", { chartId });
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
try {
|
|
472
|
-
await writer.write(chunk);
|
|
473
|
-
log.debug("write:ok", { chartId });
|
|
474
|
-
} catch (err) {
|
|
475
|
-
log.warn("write:error", {
|
|
476
|
-
chartId,
|
|
477
|
-
error: err instanceof Error ? err.message : String(err),
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
466
|
/**
|
|
483
467
|
* Expand a {@link ChartPlan} into a full Echarts `EChartsOption`
|
|
484
468
|
* JSON. Centralized here so the planner agent only fills in the
|
package/src/genie.ts
CHANGED
|
@@ -32,12 +32,11 @@
|
|
|
32
32
|
* {@link GenieSummaryItem} `string` variant, and returns the
|
|
33
33
|
* hydrated {@link GenieAgentResult}.
|
|
34
34
|
*
|
|
35
|
-
* The
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* keeps working without change.
|
|
35
|
+
* The inner agent talks to Genie directly via
|
|
36
|
+
* `@dbx-tools/genie` (`genieEventChat`) and the workspace
|
|
37
|
+
* `statementExecution.getStatement` API. AppKit's stock `genie`
|
|
38
|
+
* plugin is honored only for its `spaces` config so existing
|
|
39
|
+
* AppKit-style wiring keeps working without change.
|
|
41
40
|
*/
|
|
42
41
|
|
|
43
42
|
import { CacheManager, genie } from "@databricks/appkit";
|
|
@@ -73,6 +72,7 @@ import { runChartPlanner } from "./chart.js";
|
|
|
73
72
|
import type { MastraPluginConfig } from "./config.js";
|
|
74
73
|
import { MASTRA_USER_KEY, type User } from "./config.js";
|
|
75
74
|
import { buildModel } from "./model.js";
|
|
75
|
+
import { safeWrite } from "./writer.js";
|
|
76
76
|
|
|
77
77
|
const log = logUtils.logger("mastra/genie");
|
|
78
78
|
|
|
@@ -169,28 +169,6 @@ function extractStatementId(message: GenieMessage): string | undefined {
|
|
|
169
169
|
return undefined;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
/**
|
|
173
|
-
* Best-effort `writer.write`. The writer carries the unified flat
|
|
174
|
-
* event vocabulary directly - no translation layer - so
|
|
175
|
-
* subscribers narrow on `event.type` and read fields inline.
|
|
176
|
-
* Failures (downstream stream closed, cancelled request) are
|
|
177
|
-
* swallowed with a `warn` log so an in-flight Genie turn isn't
|
|
178
|
-
* taken down by a navigated-away client.
|
|
179
|
-
*/
|
|
180
|
-
async function safeWrite(
|
|
181
|
-
writer: MinimalWriter | undefined,
|
|
182
|
-
chunk: unknown,
|
|
183
|
-
): Promise<void> {
|
|
184
|
-
if (!writer) return;
|
|
185
|
-
try {
|
|
186
|
-
await writer.write(chunk);
|
|
187
|
-
} catch (err) {
|
|
188
|
-
log.warn("writer:error", {
|
|
189
|
-
error: err instanceof Error ? err.message : String(err),
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
172
|
/**
|
|
195
173
|
* Lowercased placeholder strings we reject at the `ask_genie`
|
|
196
174
|
* boundary so the LLM doesn't spend a Genie round-trip on a
|
|
@@ -362,7 +340,7 @@ async function readCachedConversationId(
|
|
|
362
340
|
return v ?? undefined;
|
|
363
341
|
} catch (err) {
|
|
364
342
|
log.warn("conversation-cache:read-error", {
|
|
365
|
-
error:
|
|
343
|
+
error: commonUtils.errorMessage(err),
|
|
366
344
|
});
|
|
367
345
|
return undefined;
|
|
368
346
|
}
|
|
@@ -386,7 +364,7 @@ async function saveCachedConversationId(
|
|
|
386
364
|
});
|
|
387
365
|
} catch (err) {
|
|
388
366
|
log.warn("conversation-cache:write-error", {
|
|
389
|
-
error:
|
|
367
|
+
error: commonUtils.errorMessage(err),
|
|
390
368
|
});
|
|
391
369
|
}
|
|
392
370
|
}
|
|
@@ -398,7 +376,7 @@ async function evictCachedConversationId(cacheKey: string | undefined): Promise<
|
|
|
398
376
|
await CacheManager.getInstanceSync().delete(cacheKey);
|
|
399
377
|
} catch (err) {
|
|
400
378
|
log.warn("conversation-cache:delete-error", {
|
|
401
|
-
error:
|
|
379
|
+
error: commonUtils.errorMessage(err),
|
|
402
380
|
});
|
|
403
381
|
}
|
|
404
382
|
}
|
|
@@ -523,7 +501,7 @@ function buildAskGenieTool(deps: InnerToolDeps) {
|
|
|
523
501
|
...(seedConversationId ? { conversationId: seedConversationId } : {}),
|
|
524
502
|
...(signal ? { context: signal } : {}),
|
|
525
503
|
})) {
|
|
526
|
-
await safeWrite(writer, event);
|
|
504
|
+
await safeWrite(log, writer, event);
|
|
527
505
|
// Wire events come in two flavors: the lifecycle `message`
|
|
528
506
|
// event embeds the raw `GenieMessage` (read its
|
|
529
507
|
// `conversation_id`), and the rest carry a flat
|
|
@@ -564,7 +542,7 @@ function buildAskGenieTool(deps: InnerToolDeps) {
|
|
|
564
542
|
log.warn("conversation-cache:stale, resetting", {
|
|
565
543
|
spaceId,
|
|
566
544
|
conversationId: seeded,
|
|
567
|
-
error:
|
|
545
|
+
error: commonUtils.errorMessage(err),
|
|
568
546
|
});
|
|
569
547
|
await evictCachedConversationId(cacheKey);
|
|
570
548
|
writeContextConversationId(requestContext, spaceId, undefined);
|
|
@@ -852,7 +830,7 @@ export function createGenieTool(opts: CreateGenieToolOptions) {
|
|
|
852
830
|
spaceId,
|
|
853
831
|
content: input.question,
|
|
854
832
|
};
|
|
855
|
-
await safeWrite(writer, startedEvent);
|
|
833
|
+
await safeWrite(log, writer, startedEvent);
|
|
856
834
|
|
|
857
835
|
const resultSets = new Map<string, StatementEntry>();
|
|
858
836
|
|
|
@@ -999,7 +977,7 @@ export function createGenieTool(opts: CreateGenieToolOptions) {
|
|
|
999
977
|
textItems: textItemCount,
|
|
1000
978
|
dataItems: dataItemCount,
|
|
1001
979
|
};
|
|
1002
|
-
await safeWrite(writer, summaryEvent);
|
|
980
|
+
await safeWrite(log, writer, summaryEvent);
|
|
1003
981
|
|
|
1004
982
|
// Chart every `data` item in parallel; map `text` items to
|
|
1005
983
|
// the shared `string` summary variant verbatim. Missing
|
|
@@ -1011,87 +989,89 @@ export function createGenieTool(opts: CreateGenieToolOptions) {
|
|
|
1011
989
|
// chart slot the moment its planner returns rather than
|
|
1012
990
|
// waiting for the entire batch to finish.
|
|
1013
991
|
const hydrated = await Promise.all(
|
|
1014
|
-
submission.summary.map(
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
992
|
+
submission.summary.map(
|
|
993
|
+
async (item: AgentSummaryItem): Promise<GenieSummaryItem | undefined> => {
|
|
994
|
+
if (item.type === "text") {
|
|
995
|
+
return { type: "string", text: item.text };
|
|
996
|
+
}
|
|
997
|
+
const entry = resultSets.get(item.statementId);
|
|
998
|
+
if (!entry) {
|
|
999
|
+
log.warn("data:missing-statement", {
|
|
1000
|
+
statementId: item.statementId,
|
|
1001
|
+
});
|
|
1002
|
+
return undefined;
|
|
1003
|
+
}
|
|
1004
|
+
const { data, messageId } = entry;
|
|
1005
|
+
let dataset: GenieDataset = { data };
|
|
1006
|
+
try {
|
|
1007
|
+
const planned = await runChartPlanner({
|
|
1008
|
+
config,
|
|
1009
|
+
requestContext,
|
|
1010
|
+
title: item.title ?? "Genie result",
|
|
1011
|
+
...(item.description ? { description: item.description } : {}),
|
|
1012
|
+
data: data.rows,
|
|
1013
|
+
...(signal ? { signal } : {}),
|
|
1014
|
+
});
|
|
1015
|
+
const chartId = commonUtils.shortId();
|
|
1016
|
+
// Slim chart reference for the LLM-bound result: just
|
|
1017
|
+
// `chartId` + `chartType`. The full Echarts spec goes
|
|
1018
|
+
// to the UI via the writer event AND into the
|
|
1019
|
+
// request-scoped chart inventory below; the model
|
|
1020
|
+
// only needs the id to place `[[chart:<id>]]`.
|
|
1021
|
+
dataset = {
|
|
1022
|
+
data,
|
|
1023
|
+
chart: {
|
|
1024
|
+
chartId,
|
|
1025
|
+
chartType: planned.chartType,
|
|
1026
|
+
},
|
|
1027
|
+
};
|
|
1028
|
+
const chartEvent: ChartEvent = {
|
|
1029
|
+
type: "chart",
|
|
1045
1030
|
chartId,
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1031
|
+
statementId: item.statementId,
|
|
1032
|
+
messageId,
|
|
1033
|
+
...(item.title ? { title: item.title } : {}),
|
|
1034
|
+
...(item.description ? { description: item.description } : {}),
|
|
1035
|
+
data: data.rows,
|
|
1036
|
+
option: planned.option,
|
|
1037
|
+
};
|
|
1038
|
+
await safeWrite(log, writer, chartEvent);
|
|
1039
|
+
// Stash the resolved chart on the per-request
|
|
1040
|
+
// `RequestContext` so downstream code in the same
|
|
1041
|
+
// request (output processors, follow-up tool calls,
|
|
1042
|
+
// any post-run hook) can look up the full spec by
|
|
1043
|
+
// `chartId` without re-fetching or re-planning.
|
|
1044
|
+
recordChartInContext(requestContext, chartEvent);
|
|
1045
|
+
} catch (err) {
|
|
1046
|
+
const errorMessage = commonUtils.errorMessage(err);
|
|
1047
|
+
log.warn("chart:error", {
|
|
1048
|
+
statementId: item.statementId,
|
|
1049
|
+
messageId,
|
|
1050
|
+
error: errorMessage,
|
|
1051
|
+
});
|
|
1052
|
+
// Surface the chart-planner failure as a writer event
|
|
1053
|
+
// stamped with the same `messageId` the rest of this
|
|
1054
|
+
// ask's wire events carry, so the host UI groups the
|
|
1055
|
+
// failure into the same pill bucket and can surface
|
|
1056
|
+
// a "couldn't render chart" note next to the table
|
|
1057
|
+
// fallback instead of silently dropping the chart.
|
|
1058
|
+
const errorEvent: MastraGenieErrorEvent = {
|
|
1059
|
+
type: "error",
|
|
1060
|
+
spaceId,
|
|
1061
|
+
messageId,
|
|
1062
|
+
error: `chart-planner: ${errorMessage}`,
|
|
1063
|
+
};
|
|
1064
|
+
await safeWrite(log, writer, errorEvent);
|
|
1065
|
+
}
|
|
1066
|
+
return {
|
|
1067
|
+
type: "visualize",
|
|
1052
1068
|
statementId: item.statementId,
|
|
1053
|
-
messageId,
|
|
1054
1069
|
...(item.title ? { title: item.title } : {}),
|
|
1055
1070
|
...(item.description ? { description: item.description } : {}),
|
|
1056
|
-
|
|
1057
|
-
option: planned.option,
|
|
1071
|
+
dataset,
|
|
1058
1072
|
};
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
// `RequestContext` so downstream code in the same
|
|
1062
|
-
// request (output processors, follow-up tool calls,
|
|
1063
|
-
// any post-run hook) can look up the full spec by
|
|
1064
|
-
// `chartId` without re-fetching or re-planning.
|
|
1065
|
-
recordChartInContext(requestContext, chartEvent);
|
|
1066
|
-
} catch (err) {
|
|
1067
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1068
|
-
log.warn("chart:error", {
|
|
1069
|
-
statementId: item.statementId,
|
|
1070
|
-
messageId,
|
|
1071
|
-
error: errorMessage,
|
|
1072
|
-
});
|
|
1073
|
-
// Surface the chart-planner failure as a writer event
|
|
1074
|
-
// stamped with the same `messageId` the rest of this
|
|
1075
|
-
// ask's wire events carry, so the host UI groups the
|
|
1076
|
-
// failure into the same pill bucket and can surface
|
|
1077
|
-
// a "couldn't render chart" note next to the table
|
|
1078
|
-
// fallback instead of silently dropping the chart.
|
|
1079
|
-
const errorEvent: MastraGenieErrorEvent = {
|
|
1080
|
-
type: "error",
|
|
1081
|
-
spaceId,
|
|
1082
|
-
messageId,
|
|
1083
|
-
error: `chart-planner: ${errorMessage}`,
|
|
1084
|
-
};
|
|
1085
|
-
await safeWrite(writer, errorEvent);
|
|
1086
|
-
}
|
|
1087
|
-
return {
|
|
1088
|
-
type: "visualize",
|
|
1089
|
-
statementId: item.statementId,
|
|
1090
|
-
...(item.title ? { title: item.title } : {}),
|
|
1091
|
-
...(item.description ? { description: item.description } : {}),
|
|
1092
|
-
dataset,
|
|
1093
|
-
};
|
|
1094
|
-
}),
|
|
1073
|
+
},
|
|
1074
|
+
),
|
|
1095
1075
|
);
|
|
1096
1076
|
const summary = hydrated.filter((x): x is GenieSummaryItem => x !== undefined);
|
|
1097
1077
|
|
package/src/history.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* session-cookie logic stays the single source of truth in `server.ts`.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { logUtils } from "@dbx-tools/shared";
|
|
19
|
+
import { commonUtils, logUtils } from "@dbx-tools/shared";
|
|
20
20
|
import { toAISdkV5Messages } from "@mastra/ai-sdk/ui";
|
|
21
21
|
import type { Agent } from "@mastra/core/agent";
|
|
22
22
|
import type { MastraDBMessage } from "@mastra/core/agent/message-list";
|
|
@@ -154,7 +154,7 @@ export async function clearHistory(
|
|
|
154
154
|
log.debug("clear:probe-failed", {
|
|
155
155
|
agentId: opts.agent.id,
|
|
156
156
|
threadId: opts.threadId,
|
|
157
|
-
error:
|
|
157
|
+
error: commonUtils.errorMessage(err),
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
160
|
|
|
@@ -169,7 +169,7 @@ export async function clearHistory(
|
|
|
169
169
|
log.warn("clear:delete-soft-failed", {
|
|
170
170
|
agentId: opts.agent.id,
|
|
171
171
|
threadId: opts.threadId,
|
|
172
|
-
error:
|
|
172
|
+
error: commonUtils.errorMessage(err),
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
175
|
log.info("clear:done", {
|
package/src/writer.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for publishing events through Mastra's
|
|
3
|
+
* `ctx.writer`. Centralizes the "the downstream stream may already
|
|
4
|
+
* be closed, don't take the whole tool down" pattern that the
|
|
5
|
+
* Genie agent and chart tool both need.
|
|
6
|
+
*
|
|
7
|
+
* Failures are logged at `warn` (a persistently-closed writer is
|
|
8
|
+
* the most likely culprit when events go missing client-side) but
|
|
9
|
+
* swallowed so a cancelled request or a client that navigated
|
|
10
|
+
* away can't crash a tool mid-flight.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { MinimalWriter } from "@dbx-tools/appkit-mastra-shared";
|
|
14
|
+
import { commonUtils, type logUtils } from "@dbx-tools/shared";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Best-effort `writer.write`. No-op when `writer` is undefined;
|
|
18
|
+
* caught errors are logged via `log.warn("writer:error", ...)`
|
|
19
|
+
* along with any caller-supplied `context` fields (e.g. a
|
|
20
|
+
* `chartId` or `messageId`) so the warning is greppable per
|
|
21
|
+
* resource.
|
|
22
|
+
*
|
|
23
|
+
* Returns when the write resolves or rejects; never throws.
|
|
24
|
+
*/
|
|
25
|
+
export async function safeWrite(
|
|
26
|
+
log: logUtils.Logger,
|
|
27
|
+
writer: MinimalWriter | undefined,
|
|
28
|
+
chunk: unknown,
|
|
29
|
+
context: Record<string, unknown> = {},
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
if (!writer) {
|
|
32
|
+
log.debug("writer:no-writer", context);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
await writer.write(chunk);
|
|
37
|
+
log.debug("writer:ok", context);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
log.warn("writer:error", {
|
|
40
|
+
...context,
|
|
41
|
+
error: commonUtils.errorMessage(err),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|