@agent-native/core 0.101.7 → 0.101.10
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +22 -0
- package/corpus/core/docs/content/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ar-SA/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/de-DE/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/es-ES/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/fr-FR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/hi-IN/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ja-JP/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ko-KR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/pt-BR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/zh-CN/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/zh-TW/deployment.mdx +9 -7
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/review/ReviewCommentComposer.tsx +9 -1
- package/corpus/core/src/client/review/ReviewThreadPanel.tsx +17 -2
- package/corpus/core/src/client/settings/SettingsPanel.tsx +41 -21
- package/corpus/core/src/client/settings/useBuilderStatus.ts +28 -9
- package/corpus/core/src/client/setup-connections/BuilderConnectCard.tsx +2 -7
- package/corpus/core/src/server/auth.ts +25 -1
- package/corpus/core/src/server/builder-browser.ts +353 -1
- package/corpus/core/src/server/core-routes-plugin.ts +601 -223
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +80 -1
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +28 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +5 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +30 -3
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +196 -0
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +115 -1
- package/corpus/templates/calendar/app/lib/out-of-office.ts +45 -0
- package/corpus/templates/calendar/changelog/2026-07-14-out-of-office-blocks-now-sit-behind-meetings-and-event-stack.md +6 -0
- package/corpus/templates/content/AGENTS.md +3 -2
- package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +87 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -25
- package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +427 -18
- package/corpus/templates/content/actions/_builder-cms-write-client.ts +17 -73
- package/corpus/templates/content/actions/_builder-cms-write-settings.ts +27 -7
- package/corpus/templates/content/actions/_builder-source-execution-claim.ts +124 -0
- package/corpus/templates/content/actions/_builder-source-execution-preservation.ts +21 -0
- package/corpus/templates/content/actions/_builder-source-timings.ts +57 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +1041 -154
- package/corpus/templates/content/actions/_database-utils.ts +89 -12
- package/corpus/templates/content/actions/_preview-document-draft.ts +31 -0
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +309 -0
- package/corpus/templates/content/actions/execute-builder-source-batch.ts +120 -49
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +608 -264
- package/corpus/templates/content/actions/get-preview-document-draft.ts +26 -0
- package/corpus/templates/content/actions/materialize-builder-required-fields.ts +550 -0
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +112 -65
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +463 -170
- package/corpus/templates/content/actions/preview-builder-source-review.ts +99 -0
- package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +2 -2
- package/corpus/templates/content/actions/update-preview-document-draft.ts +153 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +27 -15
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +57 -5
- package/corpus/templates/content/app/components/editor/body-hydration.ts +27 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2353 -576
- package/corpus/templates/content/app/components/editor/database/settings.tsx +13 -10
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +358 -75
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +124 -29
- package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +2 -0
- package/corpus/templates/content/app/global.css +18 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +68 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +53 -4
- package/corpus/templates/content/app/i18n/zh-TW.ts +25 -0
- package/corpus/templates/content/app/i18n-data.ts +287 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-backed-preview-edits-are-preserved-when-article-bodi.md +6 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-cms-sources-can-now-be-enabled-for-guarded-staged-or.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-builder-connection-setup-is-now-available-directly-from-conn.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-builder-reviews-now-hide-already-applied-writes-detect-nativ.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-required-builder-publishing-fields-can-now-be-added-directly.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-you-can-now-cancel-a-prepared-builder-update-safely-before-i.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-account-connection-now-remains-available-when-branch.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-backed-articles-now-preserve-fifth-and-sixth-level-h.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-sources-now-review-and-publish-rich-article-updates-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-writes-now-stop-retries-when-reconciliation-is-needed.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-file-and-media-links-now-validate-before-saving.md +6 -0
- package/corpus/templates/content/parity/matrix.md +27 -25
- package/corpus/templates/content/parity/matrix.ts +50 -2
- package/corpus/templates/content/server/db/schema.ts +47 -0
- package/corpus/templates/content/server/plugins/auth.ts +1 -0
- package/corpus/templates/content/server/plugins/db.ts +53 -0
- package/corpus/templates/content/shared/api.ts +70 -1
- package/corpus/templates/content/shared/builder-mdx.ts +705 -15
- package/corpus/templates/content/shared/nfm.ts +3 -3
- package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +52 -7
- package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +36 -4
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
- package/corpus/templates/design/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/design/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/design/app/i18n/en-US.ts +1 -0
- package/corpus/templates/design/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/design/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/design/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/design/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/design/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/design/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/design/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +11 -0
- package/corpus/templates/design/changelog/2026-07-14-reviewers-can-now-pin-comments-from-the-canvas-toolbar-targe.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-viewer-comment-controls-now-sit-inside-the-read-only-notice-.md +6 -0
- package/corpus/templates/design/shared/review-anchor.ts +48 -0
- package/corpus/templates/plan/server/plan-mdx.ts +53 -7
- package/dist/client/review/ReviewCommentComposer.d.ts +2 -1
- package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
- package/dist/client/review/ReviewCommentComposer.js +3 -3
- package/dist/client/review/ReviewCommentComposer.js.map +1 -1
- package/dist/client/review/ReviewThreadPanel.d.ts +9 -1
- package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
- package/dist/client/review/ReviewThreadPanel.js +6 -4
- package/dist/client/review/ReviewThreadPanel.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts +3 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +11 -4
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +8 -4
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +19 -6
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/setup-connections/BuilderConnectCard.d.ts.map +1 -1
- package/dist/client/setup-connections/BuilderConnectCard.js +2 -5
- package/dist/client/setup-connections/BuilderConnectCard.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/auth.d.ts +1 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +19 -2
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-browser.d.ts +78 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +246 -1
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +41 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +260 -14
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/docs/content/deployment.mdx +9 -7
- package/docs/content/locales/ar-SA/deployment.mdx +9 -7
- package/docs/content/locales/de-DE/deployment.mdx +9 -7
- package/docs/content/locales/es-ES/deployment.mdx +9 -7
- package/docs/content/locales/fr-FR/deployment.mdx +9 -7
- package/docs/content/locales/hi-IN/deployment.mdx +9 -7
- package/docs/content/locales/ja-JP/deployment.mdx +9 -7
- package/docs/content/locales/ko-KR/deployment.mdx +9 -7
- package/docs/content/locales/pt-BR/deployment.mdx +9 -7
- package/docs/content/locales/zh-CN/deployment.mdx +9 -7
- package/docs/content/locales/zh-TW/deployment.mdx +9 -7
- package/package.json +2 -2
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
BUILDER_CMS_SAFE_WRITE_MODEL,
|
|
3
2
|
type ContentDatabaseSourceCapabilities,
|
|
4
3
|
type ContentDatabaseSourcePushMode,
|
|
5
4
|
type ContentDatabaseSourceWriteMode,
|
|
@@ -126,6 +125,32 @@ function pushModeForTier(
|
|
|
126
125
|
return "none";
|
|
127
126
|
}
|
|
128
127
|
|
|
128
|
+
function writeModeCopy(writeMode: ContentDatabaseSourceWriteMode) {
|
|
129
|
+
if (writeMode === "publish_updates") {
|
|
130
|
+
return {
|
|
131
|
+
pushModeLabel: "Publish updates",
|
|
132
|
+
pushModeDescription:
|
|
133
|
+
"Review, update, and publish existing Builder entries. New entries are never created by this mode.",
|
|
134
|
+
notes:
|
|
135
|
+
"Builder CMS source with reviewed update-in-place publication enabled for existing entries.",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (writeMode === "stage_only") {
|
|
139
|
+
return {
|
|
140
|
+
pushModeLabel: "Stage only",
|
|
141
|
+
pushModeDescription:
|
|
142
|
+
"Prepare and review local revisions without writing to Builder.",
|
|
143
|
+
notes:
|
|
144
|
+
"Builder CMS source with local review staging enabled and remote writes disabled.",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
pushModeLabel: "Read only",
|
|
149
|
+
pushModeDescription: "Refresh and review Builder content without writing.",
|
|
150
|
+
notes: "Builder CMS source with remote writes disabled.",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
129
154
|
export function builderCmsWriteSettingsFromJson(args: {
|
|
130
155
|
capabilitiesJson: string | null | undefined;
|
|
131
156
|
metadataJson: string | null | undefined;
|
|
@@ -178,11 +203,6 @@ export function buildBuilderCmsWriteModeJson(
|
|
|
178
203
|
"Live writes can only be enabled for Builder CMS sources.",
|
|
179
204
|
);
|
|
180
205
|
}
|
|
181
|
-
if (args.sourceTable !== BUILDER_CMS_SAFE_WRITE_MODEL) {
|
|
182
|
-
throw new Error(
|
|
183
|
-
`Live Builder writes are only allowed for ${BUILDER_CMS_SAFE_WRITE_MODEL}.`,
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
206
|
}
|
|
187
207
|
|
|
188
208
|
if (
|
|
@@ -199,6 +219,7 @@ export function buildBuilderCmsWriteModeJson(
|
|
|
199
219
|
};
|
|
200
220
|
const nextMetadata: Record<string, unknown> = {
|
|
201
221
|
...metadata,
|
|
222
|
+
...writeModeCopy(writeMode),
|
|
202
223
|
writeMode,
|
|
203
224
|
allowPublicationTransitions,
|
|
204
225
|
allowedWriteModes,
|
|
@@ -226,7 +247,6 @@ export function mergeBuilderCmsWriteSettingsIntoJson(args: {
|
|
|
226
247
|
});
|
|
227
248
|
if (
|
|
228
249
|
currentSettings.liveWritesEnabled !== true ||
|
|
229
|
-
args.sourceTable !== BUILDER_CMS_SAFE_WRITE_MODEL ||
|
|
230
250
|
currentSettings.writeMode === "read_only"
|
|
231
251
|
) {
|
|
232
252
|
return {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { and, eq } from "drizzle-orm";
|
|
4
|
+
|
|
5
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
6
|
+
import { builderExecutionHasResponseEvidence } from "./_builder-source-execution-preservation.js";
|
|
7
|
+
|
|
8
|
+
function claimId(sourceId: string, idempotencyKey: string) {
|
|
9
|
+
return createHash("sha256")
|
|
10
|
+
.update(`${sourceId}\u0000${idempotencyKey}`)
|
|
11
|
+
.digest("hex");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function evidenceRank(row: {
|
|
15
|
+
state: string;
|
|
16
|
+
payloadJson: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
id: string;
|
|
19
|
+
}) {
|
|
20
|
+
const stateRank =
|
|
21
|
+
row.state === "succeeded"
|
|
22
|
+
? 0
|
|
23
|
+
: row.state === "response_received"
|
|
24
|
+
? 1
|
|
25
|
+
: row.state === "reconciliation_required"
|
|
26
|
+
? 2
|
|
27
|
+
: builderExecutionHasResponseEvidence(row.payloadJson)
|
|
28
|
+
? 3
|
|
29
|
+
: row.state === "running"
|
|
30
|
+
? 4
|
|
31
|
+
: 5;
|
|
32
|
+
return { stateRank, updatedAt: row.updatedAt, id: row.id };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function canonicalExecution<
|
|
36
|
+
T extends {
|
|
37
|
+
state: string;
|
|
38
|
+
payloadJson: string;
|
|
39
|
+
updatedAt: string;
|
|
40
|
+
id: string;
|
|
41
|
+
},
|
|
42
|
+
>(rows: T[]) {
|
|
43
|
+
return [...rows].sort((left, right) => {
|
|
44
|
+
const a = evidenceRank(left);
|
|
45
|
+
const b = evidenceRank(right);
|
|
46
|
+
return (
|
|
47
|
+
a.stateRank - b.stateRank ||
|
|
48
|
+
b.updatedAt.localeCompare(a.updatedAt) ||
|
|
49
|
+
a.id.localeCompare(b.id)
|
|
50
|
+
);
|
|
51
|
+
})[0];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function claimBuilderSourceExecutionGate(args: {
|
|
55
|
+
ownerEmail: string;
|
|
56
|
+
sourceId: string;
|
|
57
|
+
idempotencyKey: string;
|
|
58
|
+
now: string;
|
|
59
|
+
}) {
|
|
60
|
+
const db = getDb();
|
|
61
|
+
const whereKey = and(
|
|
62
|
+
eq(schema.contentDatabaseSourceExecutionClaims.ownerEmail, args.ownerEmail),
|
|
63
|
+
eq(schema.contentDatabaseSourceExecutionClaims.sourceId, args.sourceId),
|
|
64
|
+
eq(
|
|
65
|
+
schema.contentDatabaseSourceExecutionClaims.idempotencyKey,
|
|
66
|
+
args.idempotencyKey,
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
const [existingClaim] = await db
|
|
70
|
+
.select()
|
|
71
|
+
.from(schema.contentDatabaseSourceExecutionClaims)
|
|
72
|
+
.where(whereKey)
|
|
73
|
+
.limit(1);
|
|
74
|
+
if (existingClaim) return existingClaim.executionId;
|
|
75
|
+
|
|
76
|
+
const candidates = await db
|
|
77
|
+
.select()
|
|
78
|
+
.from(schema.contentDatabaseSourceExecutions)
|
|
79
|
+
.where(
|
|
80
|
+
and(
|
|
81
|
+
eq(schema.contentDatabaseSourceExecutions.ownerEmail, args.ownerEmail),
|
|
82
|
+
eq(schema.contentDatabaseSourceExecutions.sourceId, args.sourceId),
|
|
83
|
+
eq(
|
|
84
|
+
schema.contentDatabaseSourceExecutions.idempotencyKey,
|
|
85
|
+
args.idempotencyKey,
|
|
86
|
+
),
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
const canonical = canonicalExecution(candidates);
|
|
90
|
+
const executionId = canonical?.id ?? crypto.randomUUID();
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await db.insert(schema.contentDatabaseSourceExecutionClaims).values({
|
|
94
|
+
id: claimId(args.sourceId, args.idempotencyKey),
|
|
95
|
+
ownerEmail: args.ownerEmail,
|
|
96
|
+
sourceId: args.sourceId,
|
|
97
|
+
idempotencyKey: args.idempotencyKey,
|
|
98
|
+
executionId,
|
|
99
|
+
createdAt: args.now,
|
|
100
|
+
});
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const [winner] = await db
|
|
103
|
+
.select()
|
|
104
|
+
.from(schema.contentDatabaseSourceExecutionClaims)
|
|
105
|
+
.where(whereKey)
|
|
106
|
+
.limit(1);
|
|
107
|
+
if (!winner) throw error;
|
|
108
|
+
return winner.executionId;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (candidates.length > 1 && canonical) {
|
|
112
|
+
await db
|
|
113
|
+
.update(schema.contentDatabaseSourceExecutions)
|
|
114
|
+
.set({
|
|
115
|
+
state: "reconciliation_required",
|
|
116
|
+
summary: "Duplicate execution gates require reconciliation.",
|
|
117
|
+
lastError:
|
|
118
|
+
"Multiple execution rows exist for this idempotency key. Evidence was preserved; do not retry.",
|
|
119
|
+
updatedAt: args.now,
|
|
120
|
+
})
|
|
121
|
+
.where(eq(schema.contentDatabaseSourceExecutions.id, canonical.id));
|
|
122
|
+
}
|
|
123
|
+
return executionId;
|
|
124
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function builderExecutionHasResponseEvidence(payloadJson: string) {
|
|
2
|
+
try {
|
|
3
|
+
const payload = JSON.parse(payloadJson) as Record<string, unknown>;
|
|
4
|
+
return Boolean(payload.response && typeof payload.response === "object");
|
|
5
|
+
} catch {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function shouldPreserveBuilderExecution(args: {
|
|
11
|
+
state: string;
|
|
12
|
+
payloadJson: string;
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
args.state === "running" ||
|
|
16
|
+
args.state === "response_received" ||
|
|
17
|
+
args.state === "reconciliation_required" ||
|
|
18
|
+
args.state === "succeeded" ||
|
|
19
|
+
builderExecutionHasResponseEvidence(args.payloadJson)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { BuilderActionTiming } from "../shared/api.js";
|
|
2
|
+
|
|
3
|
+
type Clock = () => number;
|
|
4
|
+
|
|
5
|
+
function defaultClock() {
|
|
6
|
+
return performance.now();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createBuilderSourceTiming(
|
|
10
|
+
action: string,
|
|
11
|
+
clock: Clock = defaultClock,
|
|
12
|
+
) {
|
|
13
|
+
const actionStartedAt = clock();
|
|
14
|
+
const timings: BuilderActionTiming[] = [];
|
|
15
|
+
|
|
16
|
+
function record(name: string, startedAt: number) {
|
|
17
|
+
const durationMs = Math.max(0, clock() - startedAt);
|
|
18
|
+
timings.push({ name, durationMs });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function measure<T>(name: string, operation: () => Promise<T>) {
|
|
22
|
+
const startedAt = clock();
|
|
23
|
+
try {
|
|
24
|
+
return await operation();
|
|
25
|
+
} finally {
|
|
26
|
+
record(name, startedAt);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ensure(name: string) {
|
|
31
|
+
if (!timings.some((timing) => timing.name === name)) {
|
|
32
|
+
timings.push({ name, durationMs: 0 });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function add(name: string, durationMs: number) {
|
|
37
|
+
const safeDurationMs = Math.max(0, durationMs);
|
|
38
|
+
timings.push({ name, durationMs: safeDurationMs });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function finish() {
|
|
42
|
+
if (!timings.some((timing) => timing.name === "total")) {
|
|
43
|
+
record("total", actionStartedAt);
|
|
44
|
+
}
|
|
45
|
+
return timings.map((timing) => ({ ...timing }));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function log(outcome: "succeeded" | "failed") {
|
|
49
|
+
console.info("builder_source_action_timing", {
|
|
50
|
+
action,
|
|
51
|
+
outcome,
|
|
52
|
+
timings: finish(),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { start: clock, record, measure, add, ensure, finish, log };
|
|
57
|
+
}
|