@desplega.ai/agent-swarm 1.157.0 → 1.158.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-adapter-qhqrk4q0.js → acp-adapter-xaq4n6pc.js} +1 -1
- package/dist/{actions-9rrg0zxm.js → actions-p9hz5yat.js} +15 -15
- package/dist/{app-enysy1zd.js → app-g26wdcz6.js} +4 -4
- package/dist/{artifact-td4q4dch.js → artifact-3v5t9jtg.js} +1 -1
- package/dist/{assistant-zegamy48.js → assistant-wydfqkb3.js} +16 -16
- package/dist/{boot-reembed-r6shx5qx.js → boot-reembed-g5ckg9yb.js} +6 -6
- package/dist/{boot-reembed-zfxb937k.js → boot-reembed-se8zmdcp.js} +5 -5
- package/dist/{boot-scrub-logs-k5y0se6r.js → boot-scrub-logs-wfxqan61.js} +3 -3
- package/dist/{seed-pricing-vqxa06qf.js → boot-seeds-s3q01xj3.js} +10 -10
- package/dist/{claude-adapter-5yc7hxwv.js → claude-adapter-tv2x4f36.js} +7 -7
- package/dist/{claude-managed-adapter-ea2aga6w.js → claude-managed-adapter-41b2s7fd.js} +2 -2
- package/dist/{claude-sdk-session-j4ywn3xf.js → claude-sdk-session-t442qz6f.js} +8 -8
- package/dist/{cli-e781c6f9.js → cli-0xjkj1j7.js} +1 -0
- package/dist/{cli-58rgvkt4.js → cli-1hkrch3k.js} +2 -2
- package/dist/{cli-yscm6cv8.js → cli-20cj37vt.js} +1866 -9
- package/dist/{cli-h0wdadsx.js → cli-4198qc9v.js} +7 -3
- package/dist/{cli-awwphhcs.js → cli-47m6dkd3.js} +362 -126
- package/dist/{cli-wb2zvbfg.js → cli-53sz8gkz.js} +1 -1
- package/dist/{cli-005pze6s.js → cli-5d10vd2m.js} +1 -1
- package/dist/{cli-v53rbqp3.js → cli-65hjmev5.js} +57 -17
- package/dist/{cli-scwq51gn.js → cli-68w09sjw.js} +7 -1
- package/dist/{cli-s98skd5r.js → cli-7gtvvpbh.js} +4 -4
- package/dist/{cli-y3nxwx0t.js → cli-7s3syk22.js} +18 -11
- package/dist/{cli-74h6f2bp.js → cli-8qmme6vd.js} +2 -2
- package/dist/{cli-wsed24mk.js → cli-987hg0aa.js} +2 -2
- package/dist/{cli-9n76jszt.js → cli-9httdpyq.js} +1 -1
- package/dist/{cli-f3va018d.js → cli-aky8tctm.js} +26 -93
- package/dist/{cli-sgyqyyxe.js → cli-c07r9a2m.js} +2 -1
- package/dist/{cli-c41wjx0r.js → cli-fgqxwrpv.js} +291 -12
- package/dist/{cli-01vfmq8c.js → cli-ftfvggzx.js} +607 -164
- package/dist/{cli-ytxs7mn1.js → cli-g8djp6qe.js} +17 -0
- package/dist/{cli-avkd6mqv.js → cli-h967nb6r.js} +551 -0
- package/dist/{cli-r9ee9c4w.js → cli-j55n64b6.js} +23 -22
- package/dist/{cli-9p33cz9h.js → cli-j57qkrpq.js} +86 -28
- package/dist/{cli-tjmghmm5.js → cli-q67y4wzm.js} +6 -3
- package/dist/{cli-n6zq3kbd.js → cli-rj19hc34.js} +1 -1
- package/dist/{cli-jytrw2na.js → cli-rjehvrts.js} +4 -4
- package/dist/{cli-tpv97m71.js → cli-rqz8qbw7.js} +245 -75
- package/dist/{cli-p37sg0a9.js → cli-s8eet254.js} +1 -1
- package/dist/cli-t1ze64h3.js +811 -0
- package/dist/{cli-6jjzxv3b.js → cli-ws6rqycm.js} +3 -2
- package/dist/{cli-xf5b7y1f.js → cli-wscdvcqz.js} +13 -5
- package/dist/{cli-5j7bm4tb.js → cli-x24pshtq.js} +5 -5
- package/dist/{cli-nbp6m6gp.js → cli-xberxa1r.js} +51 -26
- package/dist/{cli-vx9p3qdt.js → cli-xhaeg2nc.js} +1 -1
- package/dist/cli.js +29 -15
- package/dist/{codex-adapter-gns3s6c3.js → codex-adapter-cnm8wk11.js} +5 -5
- package/dist/{codex-session-runner-gqnxys5w.js → codex-session-runner-gfg547zj.js} +5 -5
- package/dist/{commands-hxy2y2sp.js → commands-e3bn0p75.js} +3 -3
- package/dist/{db-w8ghhsgf.js → db-03v9kzr2.js} +17 -3
- package/dist/{dispatcher-cp1rknjz.js → dispatcher-nrmg8yht.js} +14 -14
- package/dist/{dsh-adapter-1dwg97zv.js → dsh-adapter-k03nfqa6.js} +2 -2
- package/dist/{handlers-egfhr351.js → handlers-aqcedbyb.js} +17 -17
- package/dist/{hook-x4kftwzt.js → hook-7vkskkre.js} +19 -11
- package/dist/{hook-fbxjqfjz.js → hook-ybvmrmhq.js} +4 -4
- package/dist/{http-c7jfx990.js → http-2eqa8t24.js} +432 -195
- package/dist/{index-4k9hct21.js → index-1yks3qcr.js} +14 -14
- package/dist/{index-jtvpgb8f.js → index-a3vn96n8.js} +30 -27
- package/dist/{index-dc2ek6bw.js → index-c6zsv1bd.js} +5 -5
- package/dist/{index-yy07ef2s.js → index-chgdkq36.js} +14 -14
- package/dist/{index-r6bty6tb.js → index-x71bvq0p.js} +15 -15
- package/dist/{keepalive-f7ekefyc.js → keepalive-vtkf5n99.js} +5 -5
- package/dist/{lead-h053syts.js → lead-h3sp357x.js} +26 -26
- package/dist/{maintenance-a3w6zs54.js → maintenance-jzeqtk3q.js} +6 -6
- package/dist/{oauth-refresh-sweep-vra1dhkd.js → oauth-refresh-sweep-qp3dzmdp.js} +4 -4
- package/dist/{onboard-mfgzeqxs.js → onboard-tmsp0qbz.js} +278 -81
- package/dist/{opencode-adapter-z64xjzec.js → opencode-adapter-x6x2da1z.js} +3 -3
- package/dist/{otel-impl-2ez6xk57.js → otel-impl-9mz73q65.js} +416 -301
- package/dist/{pi-mono-adapter-2b7mw16r.js → pi-mono-adapter-m6dvv0dz.js} +2 -2
- package/dist/{pricing-refresh-6sw65tck.js → pricing-refresh-v1cbr9te.js} +5 -5
- package/dist/{rbac-roles-nm6924sy.js → rbac-roles-c9wqz83x.js} +4 -4
- package/dist/{render-v2-f026j6c4.js → render-v2-gryrh9h8.js} +4 -4
- package/dist/{setup-w606fr1q.js → setup-q9720bt6.js} +2 -2
- package/dist/{virtual-modules-8qheft18.js → virtual-modules-3htpgn8x.js} +1 -1
- package/dist/{work-objects-ezmggdne.js → work-objects-b76x7deb.js} +17 -17
- package/dist/{worker-1mbyv29k.js → worker-6vrxbv41.js} +26 -26
- package/openapi.json +1626 -629
- package/package.json +2 -1
- package/src/apps/catalog.generated.json +229 -0
- package/src/apps/page-validator.ts +71 -16
- package/src/apps/row-store.ts +66 -11
- package/src/apps/sync.ts +59 -17
- package/src/artifact-sdk/browser-sdk.ts +436 -2
- package/src/be/approval-sweeps.ts +109 -0
- package/src/be/boot-seeds.ts +40 -0
- package/src/be/chunking.ts +1 -1
- package/src/be/db/agents.ts +20 -0
- package/src/be/db/approvals.ts +78 -0
- package/src/be/db/tasks/read.ts +197 -39
- package/src/be/db/tasks/write.ts +41 -9
- package/src/be/db-client.ts +107 -48
- package/src/be/db.ts +219 -76
- package/src/be/events.ts +33 -1
- package/src/be/memory/graph-expansion.ts +10 -3
- package/src/be/memory/providers/sqlite-store.ts +55 -13
- package/src/be/memory/raters/retrieval.ts +5 -2
- package/src/be/memory/reranker.ts +26 -2
- package/src/be/memory/types.ts +2 -0
- package/src/be/migrations/166_approval_request_auto_cancellation.sql +9 -0
- package/src/be/migrations/171_memory_retrieval_relevance.sql +8 -0
- package/src/be/migrations/175_workflow_runs_list_index.sql +11 -0
- package/src/be/migrations/177_events_event_agent_created_index.sql +11 -0
- package/src/be/models-catalog.ts +2 -0
- package/src/be/modelsdev-cache.json +1857 -0
- package/src/be/modelsdev-cache.ts +14 -1
- package/src/be/pricing-normalize.ts +2 -0
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-pricing.ts +3 -1
- package/src/be/seed-skills/bundled-files.generated.json +1 -1
- package/src/be/steering.ts +53 -3
- package/src/be/swarm-config-guard.ts +3 -0
- package/src/be/task-citations.ts +29 -0
- package/src/cli.tsx +14 -1
- package/src/commands/onboard/compose-generator.ts +8 -3
- package/src/commands/onboard/container-engine.ts +220 -0
- package/src/commands/onboard/manifest.ts +1 -0
- package/src/commands/onboard/steps/health-check.tsx +4 -1
- package/src/commands/onboard/steps/prereq-check.tsx +50 -54
- package/src/commands/onboard/steps/start.tsx +31 -16
- package/src/commands/onboard/types.ts +7 -2
- package/src/commands/onboard.tsx +39 -4
- package/src/commands/profile-sync.ts +79 -26
- package/src/commands/runner.ts +8 -1
- package/src/extensions/catalog.generated.json +17 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/heartbeat/heartbeat.ts +263 -103
- package/src/hooks/hook.ts +21 -12
- package/src/http/agents.ts +35 -0
- package/src/http/approval-requests.ts +136 -64
- package/src/http/apps.ts +14 -3
- package/src/http/config.ts +15 -1
- package/src/http/events.ts +62 -2
- package/src/http/index.ts +6 -4
- package/src/http/memory.ts +21 -7
- package/src/http/schedules.ts +1 -1
- package/src/http/session-data.ts +9 -4
- package/src/http/sessions.ts +25 -18
- package/src/http/stats.ts +19 -7
- package/src/http/status.ts +11 -1
- package/src/http/tasks.ts +40 -8
- package/src/http/webhooks.ts +67 -46
- package/src/http/workflows.ts +15 -20
- package/src/linear/outbound.ts +37 -15
- package/src/otel-impl.ts +151 -1
- package/src/otel.ts +14 -3
- package/src/prompts/memories.ts +19 -2
- package/src/providers/claude-managed-models.ts +8 -0
- package/src/providers/modelsdev-reasoning.json +551 -0
- package/src/rbac/legacy-policy.ts +16 -0
- package/src/rbac/permissions.ts +4 -0
- package/src/scheduler/schedule-task.ts +3 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/swarm-sdk.ts +9 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +12 -7
- package/src/slack/app.ts +35 -4
- package/src/slack/connection-state.ts +17 -0
- package/src/tasks/context-key.ts +1 -0
- package/src/tasks/worker-follow-up.ts +6 -2
- package/src/tests/agent-task-activity.test.ts +74 -0
- package/src/tests/agents-list-model-display.test.ts +6 -1
- package/src/tests/approval-auto-cancellation-migration.test.ts +104 -0
- package/src/tests/approval-requests.test.ts +395 -0
- package/src/tests/approval-sweeps-config.test.ts +47 -0
- package/src/tests/approval-sweeps.test.ts +508 -0
- package/src/tests/apps-kibo-blocks.test.ts +77 -0
- package/src/tests/apps-spike.test.ts +56 -1
- package/src/tests/apps-sync-engine.test.ts +20 -9
- package/src/tests/boot-seeds.test.ts +170 -0
- package/src/tests/cancel-approval-request-tool.test.ts +115 -0
- package/src/tests/claude-managed-adapter.test.ts +6 -0
- package/src/tests/config-key-filter.test.ts +80 -0
- package/src/tests/context-window.test.ts +1 -0
- package/src/tests/db-client-busy-retry.test.ts +96 -0
- package/src/tests/db-client.test.ts +60 -1
- package/src/tests/db-task-read-contract.test.ts +10 -0
- package/src/tests/db-task-write-contract.test.ts +9 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/events-http.test.ts +103 -0
- package/src/tests/extensions-catalog.test.ts +22 -17
- package/src/tests/extensions-pre-task.test.ts +107 -0
- package/src/tests/fixtures/kibo-blocks-definition.json.txt +105 -0
- package/src/tests/follow-up-config-inheritance.test.ts +260 -0
- package/src/tests/heartbeat-orphan-resume.test.ts +84 -0
- package/src/tests/heartbeat-reboot-live.test.ts +95 -0
- package/src/tests/heartbeat-stall-cas.test.ts +80 -0
- package/src/tests/heartbeat.test.ts +99 -3
- package/src/tests/hook-mcp-config.test.ts +29 -0
- package/src/tests/http-api-integration.test.ts +16 -2
- package/src/tests/kv-storage.test.ts +62 -0
- package/src/tests/linear-outbound-sync.test.ts +120 -5
- package/src/tests/list-endpoint-slimming.test.ts +91 -0
- package/src/tests/memory-graph-organic-route.test.ts +155 -0
- package/src/tests/memory-http-recall-gating.test.ts +14 -4
- package/src/tests/memory-hybrid.test.ts +161 -1
- package/src/tests/memory-prompt-recall-crowdout.test.ts +297 -0
- package/src/tests/memory.test.ts +22 -0
- package/src/tests/onboard-container-engine.test.ts +240 -0
- package/src/tests/otel-event-loop-and-logs.test.ts +99 -0
- package/src/tests/pricing-routes.test.ts +14 -0
- package/src/tests/profile-sync-warning.test.ts +8 -4
- package/src/tests/promote-draft-task-route.test.ts +19 -0
- package/src/tests/provider-registration.test.ts +296 -42
- package/src/tests/rbac-engine.test.ts +30 -0
- package/src/tests/reasoning-effort.test.ts +8 -0
- package/src/tests/script-connections.test.ts +7 -0
- package/src/tests/sdk-allowlist.test.ts +39 -0
- package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
- package/src/tests/sessions-detail-batch.test.ts +250 -0
- package/src/tests/sessions.test.ts +118 -0
- package/src/tests/slack-config.test.ts +22 -0
- package/src/tests/sonnet-5-5-registry.test.ts +44 -0
- package/src/tests/status.test.ts +36 -1
- package/src/tests/swarm-diff.test.ts +316 -10
- package/src/tests/task-completion-idempotency.test.ts +2 -1
- package/src/tests/tasks-requested-by-filter.test.ts +3 -1
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/ui-list-slim-projections.test.ts +158 -0
- package/src/tests/webhooks-task-blocked.test.ts +127 -0
- package/src/tests/workflow-approval-recovery.test.ts +203 -0
- package/src/tests/workflow-hitl-routing.test.ts +22 -1
- package/src/tests/workflow-hitl-slack-card.test.ts +52 -0
- package/src/tests/workflow-http-v2.test.ts +99 -8
- package/src/tests/workflow-retry-validation.test.ts +56 -0
- package/src/tests/workflow-tla-races.test.ts +389 -0
- package/src/tests/workflow-validation-port-routing.test.ts +113 -1
- package/src/tools/cancel-approval-request.ts +57 -0
- package/src/tools/memory-search.ts +1 -0
- package/src/tools/request-human-input.ts +4 -2
- package/src/tools/send-task.ts +4 -0
- package/src/tools/templates.ts +19 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/workflows/list-workflow-runs.ts +1 -0
- package/src/types.ts +38 -0
- package/src/utils/context-window.ts +1 -0
- package/src/workflows/approval-cancel.ts +152 -0
- package/src/workflows/approval-notifications.ts +84 -0
- package/src/workflows/checkpoint.ts +10 -5
- package/src/workflows/definition.ts +21 -0
- package/src/workflows/engine.ts +152 -21
- package/src/workflows/executors/human-in-the-loop.ts +1 -1
- package/src/workflows/recovery.ts +100 -45
- package/src/workflows/resume.ts +49 -18
- package/src/workflows/retry-poller.ts +71 -21
- package/templates/extensions/README.md +1 -0
- package/templates/extensions/github-sender-allowlist/README.md +18 -0
- package/templates/extensions/github-sender-allowlist/hooks.ts +55 -0
- package/templates/extensions/github-sender-allowlist/manifest.json +8 -0
- package/templates/skills/apps/content.md +20 -1
- package/templates/skills/code-quality/content.md +26 -1
- package/templates/skills/pages/content.md +16 -4
- package/templates/skills/pages/files/examples/multiplayer-board.html +3 -12
- package/dist/cli-2daksqps.js +0 -378
- package/dist/rbac-roles-3gxmnb20.js +0 -37
package/src/be/db-client.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite";
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Async database seam.
|
|
@@ -153,10 +154,10 @@ export function isInTransaction(): boolean {
|
|
|
153
154
|
*
|
|
154
155
|
* Two properties keep a retrying operation from starving the rest of the
|
|
155
156
|
* process, both measured (see PR #1229's read-heavy probe):
|
|
156
|
-
* -
|
|
157
|
-
* (`attemptSpinMs`
|
|
158
|
-
* event loop for at most that
|
|
159
|
-
* in the async backoff sleeps,
|
|
157
|
+
* - every driver attempt runs under a SHORT `busy_timeout`
|
|
158
|
+
* (`attemptSpinMs`, or `finalAttemptSpinMs` for the last one), so the
|
|
159
|
+
* driver's synchronous busy wait blocks the event loop for at most that
|
|
160
|
+
* long per attempt; the real waiting happens in the async backoff sleeps,
|
|
160
161
|
* - the FIFO lock is RELEASED during those sleeps (except after COMMIT
|
|
161
162
|
* failures, where the open transaction pins the connection), so queued
|
|
162
163
|
* reads flow while a writer waits out an external lock holder.
|
|
@@ -168,17 +169,66 @@ export type BusyRetryOptions = {
|
|
|
168
169
|
backoffMs: number[];
|
|
169
170
|
/** `busy_timeout` applied for the duration of each retryable driver attempt. */
|
|
170
171
|
attemptSpinMs: number;
|
|
172
|
+
/**
|
|
173
|
+
* `busy_timeout` for the final attempt, after the backoff budget is spent.
|
|
174
|
+
* Defaults to `DEFAULT_FINAL_ATTEMPT_SPIN_MS`.
|
|
175
|
+
*/
|
|
176
|
+
finalAttemptSpinMs?: number;
|
|
171
177
|
};
|
|
172
178
|
|
|
173
|
-
|
|
179
|
+
/**
|
|
180
|
+
* The final attempt used to camp on the connection's ambient busy_timeout
|
|
181
|
+
* (5000ms), which froze every in-flight request for up to 5s whenever an
|
|
182
|
+
* external holder outlived the backoff budget. A slightly longer spin than
|
|
183
|
+
* `attemptSpinMs` still widens the last window without that freeze.
|
|
184
|
+
*/
|
|
185
|
+
const DEFAULT_FINAL_ATTEMPT_SPIN_MS = 250;
|
|
186
|
+
|
|
187
|
+
// The backoff sum (~10.9s) plus per-attempt spins must outlast a realistic
|
|
174
188
|
// external hold (litestream checkpoint stalls measured in seconds) so writes
|
|
175
189
|
// land in the holder's release gaps instead of failing at the driver cliff.
|
|
190
|
+
// It covers the patience the old 5s synchronous final camp provided, spent
|
|
191
|
+
// in async sleeps instead. The 500ms tail keeps attempts at most one spin
|
|
192
|
+
// apart from any 500ms release gap, so a lone writer cannot phase-lock with a
|
|
193
|
+
// periodic holder (measured: 6 of 6 writes under a 6s-hold/500ms-gap locker;
|
|
194
|
+
// 1s spacing lost 3 of 6).
|
|
176
195
|
const DEFAULT_BUSY_RETRY: BusyRetryOptions = {
|
|
177
|
-
maxWaitMs:
|
|
178
|
-
backoffMs: [25, 50, 100, 250, 500
|
|
196
|
+
maxWaitMs: 11_000,
|
|
197
|
+
backoffMs: [25, 50, 100, 250, ...Array<number>(21).fill(500)],
|
|
179
198
|
attemptSpinMs: 100,
|
|
199
|
+
finalAttemptSpinMs: DEFAULT_FINAL_ATTEMPT_SPIN_MS,
|
|
180
200
|
};
|
|
181
201
|
|
|
202
|
+
/**
|
|
203
|
+
* A single synchronous driver call longer than this froze the whole event
|
|
204
|
+
* loop (every route, not only its own request) and is logged.
|
|
205
|
+
*/
|
|
206
|
+
const SLOW_STATEMENT_WARN_MS = 100;
|
|
207
|
+
const SLOW_STATEMENT_SQL_MAX_CHARS = 200;
|
|
208
|
+
|
|
209
|
+
/** Params are never logged: they can carry secrets and user content. */
|
|
210
|
+
function warnIfSlowStatement(sql: string, durationMs: number): void {
|
|
211
|
+
if (durationMs < SLOW_STATEMENT_WARN_MS) return;
|
|
212
|
+
const flat = sql.replace(/\s+/g, " ").trim();
|
|
213
|
+
const shown =
|
|
214
|
+
flat.length > SLOW_STATEMENT_SQL_MAX_CHARS
|
|
215
|
+
? `${flat.slice(0, SLOW_STATEMENT_SQL_MAX_CHARS)}…`
|
|
216
|
+
: flat;
|
|
217
|
+
console.warn(
|
|
218
|
+
`[db-client] slow sync statement blocked the event loop for ${Math.round(durationMs)}ms: ${scrubSecrets(shown)}`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Run one synchronous driver call and log it when it blocked the loop too long. */
|
|
223
|
+
function timedStatement<T>(sql: string, run: () => T): T {
|
|
224
|
+
const startedAt = performance.now();
|
|
225
|
+
try {
|
|
226
|
+
return run();
|
|
227
|
+
} finally {
|
|
228
|
+
warnIfSlowStatement(sql, performance.now() - startedAt);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
182
232
|
function sleep(ms: number): Promise<void> {
|
|
183
233
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
184
234
|
}
|
|
@@ -206,15 +256,15 @@ class BunSqliteClient implements DbClient {
|
|
|
206
256
|
) {}
|
|
207
257
|
|
|
208
258
|
query<T>(sql: string, params: DbParam[] = []): Promise<T[]> {
|
|
209
|
-
return this.execute((db) => db.query<T, DbParam[]>(sql).all(...params));
|
|
259
|
+
return this.execute(sql, (db) => db.query<T, DbParam[]>(sql).all(...params));
|
|
210
260
|
}
|
|
211
261
|
|
|
212
262
|
get<T>(sql: string, params: DbParam[] = []): Promise<T | null> {
|
|
213
|
-
return this.execute((db) => db.query<T, DbParam[]>(sql).get(...params));
|
|
263
|
+
return this.execute(sql, (db) => db.query<T, DbParam[]>(sql).get(...params));
|
|
214
264
|
}
|
|
215
265
|
|
|
216
266
|
run(sql: string, params: DbParam[] = []): Promise<{ changes: number }> {
|
|
217
|
-
return this.execute((db) => {
|
|
267
|
+
return this.execute(sql, (db) => {
|
|
218
268
|
const result = db.query(sql).run(...params);
|
|
219
269
|
return { changes: result.changes };
|
|
220
270
|
});
|
|
@@ -226,6 +276,7 @@ class BunSqliteClient implements DbClient {
|
|
|
226
276
|
): Promise<{ changes: number; executionMs: number }> {
|
|
227
277
|
let executionMs = 0;
|
|
228
278
|
const result = await this.execute(
|
|
279
|
+
sql,
|
|
229
280
|
(db) => {
|
|
230
281
|
const run = db.query(sql).run(...params);
|
|
231
282
|
return { changes: run.changes };
|
|
@@ -248,12 +299,10 @@ class BunSqliteClient implements DbClient {
|
|
|
248
299
|
for (let attemptIndex = 0; ; attemptIndex++) {
|
|
249
300
|
const backoff = this.busyRetry.backoffMs[attemptIndex];
|
|
250
301
|
const canRetryAfter = backoff !== undefined && sleptMs + backoff <= this.busyRetry.maxWaitMs;
|
|
251
|
-
// The FINAL attempt
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
// synchronous spin per request, only after every polite attempt failed,
|
|
256
|
-
// acquires the lock the instant the holder releases.
|
|
302
|
+
// The FINAL attempt spins `finalAttemptSpinMs`, never the ambient
|
|
303
|
+
// busy_timeout: a 5s synchronous camp froze every in-flight request.
|
|
304
|
+
// The dense 500ms backoff tail catches a periodic holder's release gap
|
|
305
|
+
// instead (see DEFAULT_BUSY_RETRY).
|
|
257
306
|
const attempt = await this.runTransactionAttempt(fn, readOnly, !canRetryAfter);
|
|
258
307
|
if (attempt.ok) return attempt.value;
|
|
259
308
|
if (!canRetryAfter || backoff === undefined) throw attempt.busyBegin;
|
|
@@ -274,7 +323,7 @@ class BunSqliteClient implements DbClient {
|
|
|
274
323
|
private async runTransactionAttempt<T>(
|
|
275
324
|
fn: (tx: DbExecutor) => Promise<T>,
|
|
276
325
|
readOnly: boolean,
|
|
277
|
-
|
|
326
|
+
finalAttempt = false,
|
|
278
327
|
): Promise<{ ok: true; value: T } | { ok: false; busyBegin: unknown }> {
|
|
279
328
|
const release = await this.lock.acquire();
|
|
280
329
|
const ctx: TxContext = { closed: false, afterCommit: [] };
|
|
@@ -293,13 +342,7 @@ class BunSqliteClient implements DbClient {
|
|
|
293
342
|
// instead of on an arbitrary statement mid-callback where it is
|
|
294
343
|
// not. The short spin keeps the driver's synchronous busy wait off
|
|
295
344
|
// the event loop; the real waiting is the caller's async backoff.
|
|
296
|
-
|
|
297
|
-
// transaction()).
|
|
298
|
-
if (campOnAmbientTimeout) {
|
|
299
|
-
db.run("BEGIN IMMEDIATE");
|
|
300
|
-
} else {
|
|
301
|
-
this.shortSpin(db, () => db.run("BEGIN IMMEDIATE"));
|
|
302
|
-
}
|
|
345
|
+
this.shortSpin(db, () => db.run("BEGIN IMMEDIATE"), finalAttempt);
|
|
303
346
|
} catch (err) {
|
|
304
347
|
if (isSqliteBusy(err)) return { ok: false, busyBegin: err };
|
|
305
348
|
throw err;
|
|
@@ -393,19 +436,27 @@ class BunSqliteClient implements DbClient {
|
|
|
393
436
|
* a post-commit continuation (e.g. queueMicrotask) is treated as absent.
|
|
394
437
|
*/
|
|
395
438
|
private async execute<T>(
|
|
439
|
+
sql: string,
|
|
396
440
|
op: (db: Database) => T,
|
|
397
441
|
recordExecutionMs?: (ms: number) => void,
|
|
398
442
|
): Promise<T> {
|
|
399
443
|
// Wraps ONLY the synchronous driver call, so lock acquisition above and
|
|
400
444
|
// backoff sleeps below are never charged to the statement. Called once per
|
|
401
445
|
// attempt; a retrying statement reports the sum. See DbClient.runTimed.
|
|
402
|
-
|
|
403
|
-
|
|
446
|
+
// A BUSY attempt that will be retried is not logged as slow: its block is
|
|
447
|
+
// capped at `attemptSpinMs` by construction.
|
|
448
|
+
const timed = (attempt: () => T, retryable = false): T => {
|
|
404
449
|
const startedAt = performance.now();
|
|
450
|
+
let willRetry = false;
|
|
405
451
|
try {
|
|
406
452
|
return attempt();
|
|
453
|
+
} catch (err) {
|
|
454
|
+
willRetry = retryable && isSqliteBusy(err);
|
|
455
|
+
throw err;
|
|
407
456
|
} finally {
|
|
408
|
-
|
|
457
|
+
const ms = performance.now() - startedAt;
|
|
458
|
+
recordExecutionMs?.(ms);
|
|
459
|
+
if (!willRetry) warnIfSlowStatement(sql, ms);
|
|
409
460
|
}
|
|
410
461
|
};
|
|
411
462
|
const ctx = txContext.getStore();
|
|
@@ -420,13 +471,11 @@ class BunSqliteClient implements DbClient {
|
|
|
420
471
|
let backoff = 0;
|
|
421
472
|
try {
|
|
422
473
|
// A top-level single statement is atomic, so a repeat on BUSY is
|
|
423
|
-
// exact. The final attempt
|
|
424
|
-
// lone writer cannot stay phase-locked with a periodic holder (see
|
|
474
|
+
// exact. The final attempt spins `finalAttemptSpinMs` (see
|
|
425
475
|
// transaction()).
|
|
426
|
-
return timed(
|
|
427
|
-
canRetryAfter
|
|
428
|
-
|
|
429
|
-
: op(this.getDatabase()),
|
|
476
|
+
return timed(
|
|
477
|
+
() => this.shortSpin(this.getDatabase(), () => op(this.getDatabase()), !canRetryAfter),
|
|
478
|
+
canRetryAfter,
|
|
430
479
|
);
|
|
431
480
|
} catch (err) {
|
|
432
481
|
if (!isSqliteBusy(err) || !canRetryAfter || next === undefined) throw err;
|
|
@@ -442,17 +491,21 @@ class BunSqliteClient implements DbClient {
|
|
|
442
491
|
}
|
|
443
492
|
|
|
444
493
|
/**
|
|
445
|
-
* Run a
|
|
446
|
-
*
|
|
447
|
-
* `
|
|
448
|
-
* set/attempt/restore sequence cannot interleave with
|
|
494
|
+
* Run a driver attempt under a short `busy_timeout` so the synchronous
|
|
495
|
+
* in-driver busy wait blocks the event loop for at most `attemptSpinMs`
|
|
496
|
+
* (`finalAttemptSpinMs` for the final attempt). Only called while the FIFO
|
|
497
|
+
* lock is held, so the set/attempt/restore sequence cannot interleave with
|
|
498
|
+
* other client work.
|
|
449
499
|
*/
|
|
450
|
-
private shortSpin<T>(db: Database, attempt: () => T): T {
|
|
500
|
+
private shortSpin<T>(db: Database, attempt: () => T, finalAttempt = false): T {
|
|
451
501
|
if (this.restoreBusyTimeoutMs === null) {
|
|
452
502
|
const row = db.query<{ timeout: number }, []>("PRAGMA busy_timeout").get();
|
|
453
503
|
this.restoreBusyTimeoutMs = row?.timeout ?? 5000;
|
|
454
504
|
}
|
|
455
|
-
|
|
505
|
+
const spinMs = finalAttempt
|
|
506
|
+
? (this.busyRetry.finalAttemptSpinMs ?? DEFAULT_FINAL_ATTEMPT_SPIN_MS)
|
|
507
|
+
: this.busyRetry.attemptSpinMs;
|
|
508
|
+
db.exec(`PRAGMA busy_timeout = ${spinMs}`);
|
|
456
509
|
try {
|
|
457
510
|
return attempt();
|
|
458
511
|
} finally {
|
|
@@ -488,17 +541,23 @@ class BunSqliteClient implements DbClient {
|
|
|
488
541
|
};
|
|
489
542
|
return {
|
|
490
543
|
query: async <T>(sql: string, params: DbParam[] = []): Promise<T[]> =>
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
544
|
+
timedStatement(sql, () =>
|
|
545
|
+
guard()
|
|
546
|
+
.query<T, DbParam[]>(sql)
|
|
547
|
+
.all(...params),
|
|
548
|
+
),
|
|
494
549
|
get: async <T>(sql: string, params: DbParam[] = []): Promise<T | null> =>
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
550
|
+
timedStatement(sql, () =>
|
|
551
|
+
guard()
|
|
552
|
+
.query<T, DbParam[]>(sql)
|
|
553
|
+
.get(...params),
|
|
554
|
+
),
|
|
498
555
|
run: async (sql: string, params: DbParam[] = []): Promise<{ changes: number }> => {
|
|
499
|
-
const result =
|
|
500
|
-
|
|
501
|
-
|
|
556
|
+
const result = timedStatement(sql, () =>
|
|
557
|
+
guard()
|
|
558
|
+
.query(sql)
|
|
559
|
+
.run(...params),
|
|
560
|
+
);
|
|
502
561
|
return { changes: result.changes };
|
|
503
562
|
},
|
|
504
563
|
};
|