@mastra/libsql 1.29.0 → 1.30.0-alpha.1
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/docs/SKILL.md +5 -11
- package/dist/docs/assets/SOURCE_MAP.json +148 -2
- package/dist/docs/references/docs-knowledge-load-your-content.md +86 -0
- package/dist/docs/references/docs-knowledge-overview.md +106 -0
- package/dist/docs/references/docs-knowledge-search.md +63 -0
- package/dist/docs/references/docs-memory-overview.md +19 -7
- package/dist/docs/references/reference-core-getMemory.md +2 -2
- package/dist/docs/references/reference-core-listMemory.md +3 -3
- package/dist/docs/references/reference-core-mastra-class.md +1 -1
- package/dist/docs/references/reference-memory-memory-class.md +1 -1
- package/dist/index.cjs +831 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +832 -45
- package/dist/index.js.map +1 -1
- package/dist/storage/db/index.d.ts +4 -4
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/index.d.ts +1 -0
- package/dist/storage/domains/observability/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +100 -6
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/retention.d.ts +3 -1
- package/dist/storage/retention.d.ts.map +1 -1
- package/package.json +4 -4
- package/dist/docs/references/docs-deployment-workers.md +0 -404
- package/dist/docs/references/docs-memory-memory-processors.md +0 -442
- package/dist/docs/references/docs-memory-semantic-recall.md +0 -485
- package/dist/docs/references/docs-memory-working-memory.md +0 -450
- package/dist/docs/references/docs-storage.md +0 -224
- package/dist/docs/references/integrations-channels-github.md +0 -156
- package/dist/docs/references/reference-rag-retrieval.md +0 -672
- package/dist/docs/references/reference-storage-composite.md +0 -433
- package/dist/docs/references/reference-storage-retention.md +0 -310
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createClient } from "@libsql/client";
|
|
2
2
|
import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
|
|
3
|
-
import { AGENTS_SCHEMA, AGENT_VERSIONS_SCHEMA, AgentsStorage, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, ExperimentsStorage, FAVORITES_SCHEMA, FactoryStorage, FavoritesStorage, HarnessStorage, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeSchemaResetRequiredError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, MCP_CLIENTS_SCHEMA, MCP_CLIENT_VERSIONS_SCHEMA, MCP_SERVERS_SCHEMA, MCP_SERVER_VERSIONS_SCHEMA, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PROMPT_BLOCKS_SCHEMA, PROMPT_BLOCK_VERSIONS_SCHEMA, PromptBlocksStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, SCORERS_SCHEMA, SCORER_DEFINITIONS_SCHEMA, SCORER_DEFINITION_VERSIONS_SCHEMA, SKILLS_SCHEMA, SKILL_BLOBS_SCHEMA, SKILL_VERSIONS_SCHEMA, SPAN_SCHEMA, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_HARNESS_SESSIONS, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MEMORY_RUN_FENCES, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, THREAD_STATE_SCHEMA, TOOL_PROVIDER_CONNECTIONS_SCHEMA, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WORKSPACES_SCHEMA, WORKSPACE_VERSIONS_SCHEMA, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getSqlType, hasErrorCode, isRunFenceConflictError, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, resolveListOrderBy, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, runRetentionBatches, safelyParseJSON, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
3
|
+
import { AGENTS_SCHEMA, AGENT_VERSIONS_SCHEMA, AgentsStorage, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, ExperimentsStorage, FAVORITES_SCHEMA, FactoryStorage, FavoritesStorage, HarnessStorage, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeSchemaResetRequiredError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, MCP_CLIENTS_SCHEMA, MCP_CLIENT_VERSIONS_SCHEMA, MCP_SERVERS_SCHEMA, MCP_SERVER_VERSIONS_SCHEMA, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PROMPT_BLOCKS_SCHEMA, PROMPT_BLOCK_VERSIONS_SCHEMA, PromptBlocksStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, SCORERS_SCHEMA, SCORER_DEFINITIONS_SCHEMA, SCORER_DEFINITION_VERSIONS_SCHEMA, SKILLS_SCHEMA, SKILL_BLOBS_SCHEMA, SKILL_VERSIONS_SCHEMA, SPAN_SCHEMA, STEP_RESULT_SUMMARY_FIELDS, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_HARNESS_SESSIONS, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MEMORY_RUN_FENCES, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_RUNS, TABLE_WORKFLOW_RUN_ENTRIES, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, THREAD_STATE_SCHEMA, TOOL_PROVIDER_CONNECTIONS_SCHEMA, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WORKFLOW_RUN_JOURNAL_TABLE_CONFIGS, WORKSPACES_SCHEMA, WORKSPACE_VERSIONS_SCHEMA, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getSqlType, hasErrorCode, isRunFenceConflictError, latestStepResults, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, materializeState, materializeWorkflowRun, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, resolveListOrderBy, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, runRetentionBatches, safelyParseJSON, stateBaseOf, summarizeStepResult, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
4
4
|
import { parseFieldKey, parseSqlIdentifier } from "@mastra/core/utils";
|
|
5
5
|
import { MastraVector, validateTopK, validateUpsertInput } from "@mastra/core/vector";
|
|
6
6
|
import { basename, dirname, isAbsolute, join, resolve } from "path";
|
|
@@ -1861,6 +1861,21 @@ function resolveClient(config) {
|
|
|
1861
1861
|
function assertPositiveLimit(limit) {
|
|
1862
1862
|
if (!Number.isSafeInteger(limit) || limit <= 0) throw new Error(`prune limit must be a positive integer; received ${limit}`);
|
|
1863
1863
|
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Parses JSON object/array text in the table's `jsonb` columns only. A `text` column, such as a
|
|
1866
|
+
* span preview of `{}`, must read back as the stored string.
|
|
1867
|
+
*/
|
|
1868
|
+
function parseJsonColumns(tableName, row) {
|
|
1869
|
+
const schema = TABLE_SCHEMAS[tableName];
|
|
1870
|
+
return Object.fromEntries(Object.entries(row).map(([k, v]) => {
|
|
1871
|
+
if (schema[k]?.type !== "jsonb" || typeof v !== "string" || !(v.startsWith("{") || v.startsWith("["))) return [k, v];
|
|
1872
|
+
try {
|
|
1873
|
+
return [k, JSON.parse(v)];
|
|
1874
|
+
} catch {
|
|
1875
|
+
return [k, v];
|
|
1876
|
+
}
|
|
1877
|
+
}));
|
|
1878
|
+
}
|
|
1864
1879
|
var LibSQLDB = class extends MastraBase {
|
|
1865
1880
|
client;
|
|
1866
1881
|
maxRetries;
|
|
@@ -2156,7 +2171,7 @@ var LibSQLDB = class extends MastraBase {
|
|
|
2156
2171
|
/**
|
|
2157
2172
|
* Selects a single record from the specified table by key(s).
|
|
2158
2173
|
* Returns the most recently created record if multiple matches exist.
|
|
2159
|
-
*
|
|
2174
|
+
* Parses `jsonb` column values back to objects/arrays.
|
|
2160
2175
|
*
|
|
2161
2176
|
* @typeParam R - The expected return type of the record
|
|
2162
2177
|
* @param args - The select arguments
|
|
@@ -2174,14 +2189,7 @@ var LibSQLDB = class extends MastraBase {
|
|
|
2174
2189
|
args: values
|
|
2175
2190
|
});
|
|
2176
2191
|
if (!result.rows || result.rows.length === 0) return null;
|
|
2177
|
-
|
|
2178
|
-
return Object.fromEntries(Object.entries(row || {}).map(([k, v]) => {
|
|
2179
|
-
try {
|
|
2180
|
-
return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
|
|
2181
|
-
} catch {
|
|
2182
|
-
return [k, v];
|
|
2183
|
-
}
|
|
2184
|
-
}));
|
|
2192
|
+
return parseJsonColumns(tableName, result.rows[0] ?? {});
|
|
2185
2193
|
}
|
|
2186
2194
|
/**
|
|
2187
2195
|
* Selects multiple records from the specified table with optional filtering, ordering, and pagination.
|
|
@@ -2206,15 +2214,7 @@ var LibSQLDB = class extends MastraBase {
|
|
|
2206
2214
|
return ((await this.client.execute({
|
|
2207
2215
|
sql: statement,
|
|
2208
2216
|
args: [...whereClause?.args ?? [], ...args ?? []]
|
|
2209
|
-
})).rows ?? []).map((row) => {
|
|
2210
|
-
return Object.fromEntries(Object.entries(row || {}).map(([k, v]) => {
|
|
2211
|
-
try {
|
|
2212
|
-
return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
|
|
2213
|
-
} catch {
|
|
2214
|
-
return [k, v];
|
|
2215
|
-
}
|
|
2216
|
-
}));
|
|
2217
|
-
});
|
|
2217
|
+
})).rows ?? []).map((row) => parseJsonColumns(tableName, row ?? {}));
|
|
2218
2218
|
}
|
|
2219
2219
|
/**
|
|
2220
2220
|
* Returns the total count of records matching the optional WHERE clause.
|
|
@@ -3362,14 +3362,14 @@ function cutoffFor(policy, anchorType, now = Date.now()) {
|
|
|
3362
3362
|
return anchorType === "epoch-ms" ? cutoffMs : new Date(cutoffMs).toISOString();
|
|
3363
3363
|
}
|
|
3364
3364
|
const runBatchedDelete = runRetentionBatches;
|
|
3365
|
-
function runPrune({ db, domain, targets, options, logger }) {
|
|
3365
|
+
function runPrune({ db, domain, targets, options, logger, deleteBatch }) {
|
|
3366
3366
|
return executeRetentionPrune({
|
|
3367
3367
|
domain,
|
|
3368
3368
|
targets,
|
|
3369
3369
|
options,
|
|
3370
3370
|
beforeTarget: (target) => ensureAnchorIndex(db, target, logger),
|
|
3371
3371
|
cutoffFor: (target, now) => cutoffFor(target.policy, target.anchorType ?? "timestamp", now),
|
|
3372
|
-
deleteBatch: (target, cutoff, limit) => db.pruneBatch({
|
|
3372
|
+
deleteBatch: (target, cutoff, limit) => deleteBatch?.(target, cutoff, limit) ?? db.pruneBatch({
|
|
3373
3373
|
tableName: target.table,
|
|
3374
3374
|
column: target.column,
|
|
3375
3375
|
cutoff,
|
|
@@ -12016,9 +12016,19 @@ var ObservabilityLibSQL = class ObservabilityLibSQL extends ObservabilityStorage
|
|
|
12016
12016
|
indexed: true
|
|
12017
12017
|
} };
|
|
12018
12018
|
#db;
|
|
12019
|
+
#persistenceLocation;
|
|
12020
|
+
get persistenceLocation() {
|
|
12021
|
+
return this.#persistenceLocation;
|
|
12022
|
+
}
|
|
12019
12023
|
constructor(config) {
|
|
12020
12024
|
super();
|
|
12021
12025
|
const client = resolveClient(config);
|
|
12026
|
+
this.#persistenceLocation = config.embeddedReplica || client.protocol === "file" ? "local" : [
|
|
12027
|
+
"http",
|
|
12028
|
+
"https",
|
|
12029
|
+
"ws",
|
|
12030
|
+
"wss"
|
|
12031
|
+
].includes(client.protocol) ? "remote" : void 0;
|
|
12022
12032
|
this.#db = new LibSQLDB({
|
|
12023
12033
|
client,
|
|
12024
12034
|
maxRetries: config.maxRetries,
|
|
@@ -15135,6 +15145,228 @@ var WorkflowDefinitionsLibSQL = class extends WorkflowDefinitionsStorage {
|
|
|
15135
15145
|
};
|
|
15136
15146
|
// src/storage/domains/workflows/index.ts
|
|
15137
15147
|
const RUN_OWNER_COLUMNS = `generation, ownerId, leaseExpiresAt, ${DB_NOW_MS} AS nowMs`;
|
|
15148
|
+
/**
|
|
15149
|
+
* The entries a storage v2 write adds: one per step, in order, then one per step patch, then the
|
|
15150
|
+
* state or state base (a checkpoint with neither starts the state over at `{}`), then the state
|
|
15151
|
+
* patch unless a full state was given.
|
|
15152
|
+
*/
|
|
15153
|
+
function journalEntries(steps, { state, stateBase, statePatch }, checkpoint, stepPatches = {}) {
|
|
15154
|
+
const entries = Object.entries(steps).map(([stepId, result]) => ({
|
|
15155
|
+
kind: "step",
|
|
15156
|
+
step_id: stepId,
|
|
15157
|
+
payload: result
|
|
15158
|
+
}));
|
|
15159
|
+
for (const [stepId, ops] of Object.entries(stepPatches)) entries.push({
|
|
15160
|
+
kind: "step-patch",
|
|
15161
|
+
step_id: stepId,
|
|
15162
|
+
payload: ops
|
|
15163
|
+
});
|
|
15164
|
+
if (state !== void 0) entries.push({
|
|
15165
|
+
kind: "state",
|
|
15166
|
+
step_id: null,
|
|
15167
|
+
payload: state
|
|
15168
|
+
});
|
|
15169
|
+
else if (stateBase !== void 0) entries.push({
|
|
15170
|
+
kind: "state-base",
|
|
15171
|
+
step_id: null,
|
|
15172
|
+
payload: stateBase
|
|
15173
|
+
});
|
|
15174
|
+
else if (checkpoint) entries.push({
|
|
15175
|
+
kind: "state",
|
|
15176
|
+
step_id: null,
|
|
15177
|
+
payload: {}
|
|
15178
|
+
});
|
|
15179
|
+
if (state === void 0 && statePatch !== void 0) entries.push({
|
|
15180
|
+
kind: "state-patch",
|
|
15181
|
+
step_id: null,
|
|
15182
|
+
payload: statePatch
|
|
15183
|
+
});
|
|
15184
|
+
return entries;
|
|
15185
|
+
}
|
|
15186
|
+
/**
|
|
15187
|
+
* Upserts a storage v2 run's header, adds `entryCount` (the number of entries the write adds) to
|
|
15188
|
+
* its `lastSeq`, for {@link insertEntries} to number them up to, and returns the new `lastSeq` and
|
|
15189
|
+
* the `runToken` (picked when the write creates the run, otherwise the stored one). A checkpoint's
|
|
15190
|
+
* state entries come last, so its `startStateSeq` is the new `lastSeq`; an append keeps the stored
|
|
15191
|
+
* one unless given `startStateSeq`.
|
|
15192
|
+
*/
|
|
15193
|
+
function upsertHeader(run, entryCount, write) {
|
|
15194
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15195
|
+
const startStateSeq = write.checkpoint ? {
|
|
15196
|
+
insert: "?",
|
|
15197
|
+
update: `${TABLE_WORKFLOW_RUNS}.lastSeq + excluded.lastSeq`,
|
|
15198
|
+
args: [entryCount]
|
|
15199
|
+
} : {
|
|
15200
|
+
insert: "COALESCE(?, 0)",
|
|
15201
|
+
update: `COALESCE(?, ${TABLE_WORKFLOW_RUNS}.startStateSeq)`,
|
|
15202
|
+
args: [write.startStateSeq ?? null]
|
|
15203
|
+
};
|
|
15204
|
+
return {
|
|
15205
|
+
sql: `INSERT INTO ${TABLE_WORKFLOW_RUNS}
|
|
15206
|
+
(workflow_name, run_id, resourceId, threadId, status, header, lastSeq, startStateSeq, stateShared, runToken, createdAt, updatedAt)
|
|
15207
|
+
VALUES (?, ?, ?, ?, ?, jsonb(?), ?, ${startStateSeq.insert}, 0, ?, ?, ?)
|
|
15208
|
+
ON CONFLICT(workflow_name, run_id) DO UPDATE SET
|
|
15209
|
+
resourceId = COALESCE(excluded.resourceId, ${TABLE_WORKFLOW_RUNS}.resourceId), threadId = excluded.threadId,
|
|
15210
|
+
status = excluded.status, header = excluded.header, lastSeq = ${TABLE_WORKFLOW_RUNS}.lastSeq + excluded.lastSeq,
|
|
15211
|
+
startStateSeq = ${startStateSeq.update}, updatedAt = excluded.updatedAt
|
|
15212
|
+
RETURNING lastSeq, runToken`,
|
|
15213
|
+
args: [
|
|
15214
|
+
run.workflowName,
|
|
15215
|
+
run.runId,
|
|
15216
|
+
run.resourceId ?? null,
|
|
15217
|
+
run.threadId ?? null,
|
|
15218
|
+
run.header.status,
|
|
15219
|
+
safeStringify(run.header),
|
|
15220
|
+
entryCount,
|
|
15221
|
+
...startStateSeq.args,
|
|
15222
|
+
crypto.randomUUID(),
|
|
15223
|
+
now,
|
|
15224
|
+
now,
|
|
15225
|
+
...write.checkpoint ? [] : startStateSeq.args
|
|
15226
|
+
]
|
|
15227
|
+
};
|
|
15228
|
+
}
|
|
15229
|
+
/**
|
|
15230
|
+
* Inserts the entries in the JSON array parameter after the run's header has been bumped by
|
|
15231
|
+
* their count, numbering them up to the header's `lastSeq`. `-> '$.payload'` keeps a JSON null
|
|
15232
|
+
* apart from an undefined payload (dropped by `JSON.stringify`), which becomes SQL NULL.
|
|
15233
|
+
* Args: entry count, entries JSON, workflow name, run id.
|
|
15234
|
+
*/
|
|
15235
|
+
const INSERT_ENTRIES = `INSERT INTO ${TABLE_WORKFLOW_RUN_ENTRIES} (workflow_name, run_id, seq, kind, step_id, payload)
|
|
15236
|
+
SELECT h.workflow_name, h.run_id, h.lastSeq - ? + j.key + 1, j.value ->> '$.kind', j.value ->> '$.step_id', jsonb(j.value -> '$.payload')
|
|
15237
|
+
FROM ${TABLE_WORKFLOW_RUNS} h, json_each(?) j
|
|
15238
|
+
WHERE h.workflow_name = ? AND h.run_id = ?`;
|
|
15239
|
+
function insertEntries(run, entries) {
|
|
15240
|
+
return {
|
|
15241
|
+
sql: INSERT_ENTRIES,
|
|
15242
|
+
args: [
|
|
15243
|
+
entries.length,
|
|
15244
|
+
safeStringify(entries),
|
|
15245
|
+
run.workflowName,
|
|
15246
|
+
run.runId
|
|
15247
|
+
]
|
|
15248
|
+
};
|
|
15249
|
+
}
|
|
15250
|
+
/**
|
|
15251
|
+
* The run a `state-base` entry's payload names: the expressions of the state base index, which
|
|
15252
|
+
* a lookup must repeat exactly to use it.
|
|
15253
|
+
*/
|
|
15254
|
+
const STATE_BASE_WORKFLOW_EXPR = `payload ->> '$.workflowName'`;
|
|
15255
|
+
const STATE_BASE_RUN_EXPR = `payload ->> '$.runId'`;
|
|
15256
|
+
/**
|
|
15257
|
+
* A JSON array of {@link EntryRow} for the storage v2 run `alias` refers to: each step's
|
|
15258
|
+
* latest entry, the step patches after it (for `latestStepResults`), and the entries
|
|
15259
|
+
* `materializeState` uses for the latest state: the latest full state or state base and every
|
|
15260
|
+
* state patch after it. Drafts are left out.
|
|
15261
|
+
*
|
|
15262
|
+
* With `summary`, successful steps other than `input` come back as their summary, built here
|
|
15263
|
+
* so their data is never sent, unless step patches follow it (the caller summarizes the patched
|
|
15264
|
+
* result instead). Like `summarizeStepResult`, it keeps a field that is JSON null
|
|
15265
|
+
* and leaves out one that is absent. It then takes one arg, `STEP_RESULT_SUMMARY_FIELDS` as
|
|
15266
|
+
* JSON, bound where this SQL sits in the statement.
|
|
15267
|
+
*/
|
|
15268
|
+
function entriesSql(alias, { summary } = {}) {
|
|
15269
|
+
let payload = "json(x.payload)";
|
|
15270
|
+
if (summary) {
|
|
15271
|
+
const summarized = `(x.kind = 'step' AND x.step_id <> 'input' AND x.payload ->> '$.status' = 'success'
|
|
15272
|
+
AND (x.patch_seq IS NULL OR x.patch_seq < x.seq))`;
|
|
15273
|
+
payload = `json(CASE WHEN ${summarized} THEN (
|
|
15274
|
+
SELECT json_group_object(f.key, json(x.payload -> f.fullkey))
|
|
15275
|
+
FROM json_each(x.payload) f WHERE f.key IN (SELECT value FROM json_each(?))
|
|
15276
|
+
) ELSE x.payload END), 'summarized', ${summarized}`;
|
|
15277
|
+
}
|
|
15278
|
+
return `(SELECT json_group_array(json_object(
|
|
15279
|
+
'seq', x.seq, 'ord', CASE WHEN x.kind = 'step' THEN x.first_seq ELSE x.seq END, 'kind', x.kind, 'step_id', x.step_id,
|
|
15280
|
+
'absent', x.payload IS NULL, 'payload', ${payload}
|
|
15281
|
+
))
|
|
15282
|
+
FROM (
|
|
15283
|
+
SELECT e.seq, e.kind, e.step_id, e.payload,
|
|
15284
|
+
ROW_NUMBER() OVER (PARTITION BY e.kind, e.step_id ORDER BY e.seq DESC) AS rn,
|
|
15285
|
+
MIN(e.seq) OVER (PARTITION BY e.kind, e.step_id) AS first_seq,
|
|
15286
|
+
MAX(CASE WHEN e.kind = 'step' THEN e.seq END) OVER (PARTITION BY e.step_id) AS step_seq,
|
|
15287
|
+
MAX(CASE WHEN e.kind = 'step-patch' THEN e.seq END) OVER (PARTITION BY e.step_id) AS patch_seq,
|
|
15288
|
+
MAX(CASE WHEN e.kind IN ('state', 'state-base') THEN e.seq END) OVER () AS base_seq
|
|
15289
|
+
FROM ${TABLE_WORKFLOW_RUN_ENTRIES} e
|
|
15290
|
+
WHERE e.workflow_name = ${alias}.workflow_name AND e.run_id = ${alias}.run_id AND e.kind <> 'state-draft'
|
|
15291
|
+
) x
|
|
15292
|
+
WHERE CASE x.kind
|
|
15293
|
+
WHEN 'step' THEN x.rn = 1
|
|
15294
|
+
WHEN 'step-patch' THEN x.seq > x.step_seq
|
|
15295
|
+
ELSE x.base_seq IS NULL OR x.seq >= x.base_seq
|
|
15296
|
+
END)`;
|
|
15297
|
+
}
|
|
15298
|
+
/**
|
|
15299
|
+
* A JSON array of {@link EntryRow} with the entries `materializeState` uses for the state of the
|
|
15300
|
+
* storage v2 run `alias` refers to as of a version. For a draft, every state entry up to it, as
|
|
15301
|
+
* the drafts it is on can build on any earlier version; otherwise the latest full state or state
|
|
15302
|
+
* base up to it and the state patches after it. Takes two args, bound where this SQL sits in the
|
|
15303
|
+
* statement: the version's `seq`, then 1 when it is a draft, else 0.
|
|
15304
|
+
*/
|
|
15305
|
+
function stateEntriesSql(alias) {
|
|
15306
|
+
return `(SELECT json_group_array(json_object(
|
|
15307
|
+
'seq', x.seq, 'ord', x.seq, 'kind', x.kind, 'step_id', x.step_id, 'absent', x.payload IS NULL, 'payload', json(x.payload)
|
|
15308
|
+
))
|
|
15309
|
+
FROM (
|
|
15310
|
+
SELECT e.seq, e.kind, e.step_id, e.payload,
|
|
15311
|
+
MAX(CASE WHEN e.kind IN ('state', 'state-base') THEN e.seq END) OVER () AS base_seq
|
|
15312
|
+
FROM ${TABLE_WORKFLOW_RUN_ENTRIES} e
|
|
15313
|
+
WHERE e.workflow_name = ${alias}.workflow_name AND e.run_id = ${alias}.run_id
|
|
15314
|
+
AND e.kind NOT IN ('step', 'step-patch') AND e.seq <= ?
|
|
15315
|
+
) x
|
|
15316
|
+
WHERE ? OR (x.kind <> 'state-draft' AND (x.base_seq IS NULL OR x.seq >= x.base_seq)))`;
|
|
15317
|
+
}
|
|
15318
|
+
/** `entriesSql`, `stateEntriesSql` or `loadWorkflowRunSteps` rows, in the order to apply them. */
|
|
15319
|
+
function entryRows(json) {
|
|
15320
|
+
return JSON.parse(json).sort((a, b) => a.ord - b.ord);
|
|
15321
|
+
}
|
|
15322
|
+
/** The entries of {@link entryRows}; an undefined payload reads back as undefined, as in v1. */
|
|
15323
|
+
function toEntries(rows) {
|
|
15324
|
+
return rows.map((row) => {
|
|
15325
|
+
const payload = row.absent ? void 0 : row.payload;
|
|
15326
|
+
switch (row.kind) {
|
|
15327
|
+
case "step": return {
|
|
15328
|
+
seq: row.seq,
|
|
15329
|
+
kind: "step",
|
|
15330
|
+
stepId: row.step_id,
|
|
15331
|
+
result: payload
|
|
15332
|
+
};
|
|
15333
|
+
case "step-patch": return {
|
|
15334
|
+
seq: row.seq,
|
|
15335
|
+
kind: "step-patch",
|
|
15336
|
+
stepId: row.step_id,
|
|
15337
|
+
ops: payload
|
|
15338
|
+
};
|
|
15339
|
+
case "state": return {
|
|
15340
|
+
seq: row.seq,
|
|
15341
|
+
kind: "state",
|
|
15342
|
+
state: payload
|
|
15343
|
+
};
|
|
15344
|
+
case "state-patch": return {
|
|
15345
|
+
seq: row.seq,
|
|
15346
|
+
kind: "state-patch",
|
|
15347
|
+
ops: payload
|
|
15348
|
+
};
|
|
15349
|
+
case "state-base": return {
|
|
15350
|
+
seq: row.seq,
|
|
15351
|
+
kind: "state-base",
|
|
15352
|
+
base: payload
|
|
15353
|
+
};
|
|
15354
|
+
case "state-draft": {
|
|
15355
|
+
const draft = payload;
|
|
15356
|
+
return "state" in draft ? {
|
|
15357
|
+
seq: row.seq,
|
|
15358
|
+
kind: "state-draft",
|
|
15359
|
+
state: draft.state
|
|
15360
|
+
} : {
|
|
15361
|
+
seq: row.seq,
|
|
15362
|
+
kind: "state-draft",
|
|
15363
|
+
on: draft.on,
|
|
15364
|
+
ops: draft.ops
|
|
15365
|
+
};
|
|
15366
|
+
}
|
|
15367
|
+
}
|
|
15368
|
+
});
|
|
15369
|
+
}
|
|
15138
15370
|
function toRunOwnershipRecord(runId, row) {
|
|
15139
15371
|
const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
|
|
15140
15372
|
return {
|
|
@@ -15225,14 +15457,36 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
15225
15457
|
tableName: TABLE_WORKFLOW_RUN_OWNERS,
|
|
15226
15458
|
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS]
|
|
15227
15459
|
});
|
|
15460
|
+
for (const [tableName, config] of Object.entries(WORKFLOW_RUN_JOURNAL_TABLE_CONFIGS)) await this.#db.createTable({
|
|
15461
|
+
tableName,
|
|
15462
|
+
schema: config.columns,
|
|
15463
|
+
compositePrimaryKey: config.compositePrimaryKey
|
|
15464
|
+
});
|
|
15465
|
+
await withClientWriteLock(this.#client, () => this.#client.batch([
|
|
15466
|
+
`CREATE INDEX IF NOT EXISTS mastra_workflow_runs_name_createdat_idx ON ${TABLE_WORKFLOW_RUNS} (workflow_name, createdAt DESC)`,
|
|
15467
|
+
`CREATE INDEX IF NOT EXISTS mastra_workflow_runs_name_status_createdat_idx ON ${TABLE_WORKFLOW_RUNS} (workflow_name, status, createdAt DESC)`,
|
|
15468
|
+
`CREATE INDEX IF NOT EXISTS mastra_workflow_runs_threadid_idx ON ${TABLE_WORKFLOW_RUNS} (threadId)`,
|
|
15469
|
+
`CREATE INDEX IF NOT EXISTS mastra_workflow_run_entries_step_idx ON ${TABLE_WORKFLOW_RUN_ENTRIES} (workflow_name, run_id, kind, step_id, seq)`,
|
|
15470
|
+
`CREATE INDEX IF NOT EXISTS mastra_workflow_run_entries_state_base_idx ON ${TABLE_WORKFLOW_RUN_ENTRIES}
|
|
15471
|
+
(${STATE_BASE_WORKFLOW_EXPR}, ${STATE_BASE_RUN_EXPR}) WHERE kind = 'state-base'`
|
|
15472
|
+
].map((sql) => ({
|
|
15473
|
+
sql,
|
|
15474
|
+
args: []
|
|
15475
|
+
})), "write"));
|
|
15228
15476
|
}
|
|
15229
15477
|
async dangerouslyClearAll() {
|
|
15230
15478
|
await this.#db.deleteData({ tableName: TABLE_WORKFLOW_SNAPSHOT });
|
|
15231
15479
|
await this.#db.deleteData({ tableName: TABLE_WORKFLOW_RUN_OWNERS });
|
|
15480
|
+
await this.#db.deleteData({ tableName: TABLE_WORKFLOW_RUNS });
|
|
15481
|
+
await this.#db.deleteData({ tableName: TABLE_WORKFLOW_RUN_ENTRIES });
|
|
15232
15482
|
}
|
|
15233
15483
|
supportsRunFencing() {
|
|
15234
15484
|
return true;
|
|
15235
15485
|
}
|
|
15486
|
+
/** @experimental */
|
|
15487
|
+
supportsRunJournal() {
|
|
15488
|
+
return true;
|
|
15489
|
+
}
|
|
15236
15490
|
#runFenceCheck(fence, runId, operation) {
|
|
15237
15491
|
const resolved = resolveRunFence(this, fence, runId);
|
|
15238
15492
|
return resolved && {
|
|
@@ -15241,6 +15495,399 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
15241
15495
|
operation
|
|
15242
15496
|
};
|
|
15243
15497
|
}
|
|
15498
|
+
/**
|
|
15499
|
+
* Runs a storage v2 write's statements behind the run's fence. With `expectedStatus` or
|
|
15500
|
+
* `stateBase`, in a write transaction that first reads the stored run: it writes nothing
|
|
15501
|
+
* (resolving to null) when the run is stored and its status doesn't match `expectedStatus`, and
|
|
15502
|
+
* throws when the state version `stateBase` names isn't stored.
|
|
15503
|
+
*/
|
|
15504
|
+
#writeRun(check, run, { expectedStatus, stateBase }, statements) {
|
|
15505
|
+
if (expectedStatus === void 0 && stateBase === void 0) return withRunFence(this.#client, check, (writer) => writer.batch(statements));
|
|
15506
|
+
return inWriteTransaction(this.#client, async (tx) => {
|
|
15507
|
+
if (check) await assertRunFence(tx, check);
|
|
15508
|
+
if (expectedStatus !== void 0) {
|
|
15509
|
+
const { rows } = await tx.execute({
|
|
15510
|
+
sql: `SELECT status FROM ${TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
|
|
15511
|
+
args: [run.workflowName, run.runId]
|
|
15512
|
+
});
|
|
15513
|
+
if (rows[0] && !matchesExpectedWorkflowStatus(rows[0].status, expectedStatus)) return null;
|
|
15514
|
+
}
|
|
15515
|
+
if (stateBase !== void 0) {
|
|
15516
|
+
const { rows } = await tx.execute({
|
|
15517
|
+
sql: `SELECT 1 FROM ${TABLE_WORKFLOW_RUN_ENTRIES} WHERE workflow_name = ? AND run_id = ?
|
|
15518
|
+
AND CASE WHEN ? THEN kind = 'state-draft' AND seq = ? ELSE kind IN ('state', 'state-base') AND seq <= ? END
|
|
15519
|
+
AND ? <= (SELECT lastSeq FROM ${TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?)
|
|
15520
|
+
LIMIT 1`,
|
|
15521
|
+
args: [
|
|
15522
|
+
stateBase.workflowName,
|
|
15523
|
+
stateBase.runId,
|
|
15524
|
+
stateBase.draft ? 1 : 0,
|
|
15525
|
+
stateBase.seq,
|
|
15526
|
+
stateBase.seq,
|
|
15527
|
+
stateBase.seq,
|
|
15528
|
+
stateBase.workflowName,
|
|
15529
|
+
stateBase.runId
|
|
15530
|
+
]
|
|
15531
|
+
});
|
|
15532
|
+
if (!rows[0]) throw new Error(`Workflow run ${run.workflowName}/${run.runId} builds on state version ${stateBase.seq} of ${stateBase.workflowName}/${stateBase.runId}, which is not stored`);
|
|
15533
|
+
}
|
|
15534
|
+
const results = [];
|
|
15535
|
+
for (const statement of statements) results.push(await tx.execute(statement));
|
|
15536
|
+
return results;
|
|
15537
|
+
});
|
|
15538
|
+
}
|
|
15539
|
+
/**
|
|
15540
|
+
* Selects v1 snapshot rows and storage v2 runs as one result, newest first. Each version
|
|
15541
|
+
* has its own WHERE clause; `null` leaves it out. Storage v2 rows carry their header and
|
|
15542
|
+
* entries, or in summary mode only the summary snapshot. `page` is appended to the
|
|
15543
|
+
* combined, ordered rows (e.g. `LIMIT 1`). Bind the v1 args, then the v2 args, then `page`'s.
|
|
15544
|
+
*/
|
|
15545
|
+
#runsSql({ v1Where, v2Where, summary, page = "" }) {
|
|
15546
|
+
const branches = [];
|
|
15547
|
+
if (v1Where !== null) {
|
|
15548
|
+
const snapshot = summary ? `json_object('status', json_extract(snapshot, '$.status'), 'timestamp', json_extract(snapshot, '$.timestamp'))` : "json(snapshot)";
|
|
15549
|
+
branches.push(`SELECT workflow_name, run_id, resourceId, ${snapshot} AS snapshot, NULL AS header, createdAt, updatedAt, 1 AS version
|
|
15550
|
+
FROM ${TABLE_WORKFLOW_SNAPSHOT} ${v1Where}`);
|
|
15551
|
+
}
|
|
15552
|
+
if (v2Where !== null) {
|
|
15553
|
+
const columns = summary ? `json_object('status', status, 'timestamp', json_extract(header, '$.timestamp')) AS snapshot, NULL AS header` : "NULL AS snapshot, json(header) AS header";
|
|
15554
|
+
branches.push(`SELECT workflow_name, run_id, resourceId, ${columns}, createdAt, updatedAt, 2 AS version
|
|
15555
|
+
FROM ${TABLE_WORKFLOW_RUNS} ${v2Where}`);
|
|
15556
|
+
}
|
|
15557
|
+
return `SELECT p.*${summary ? "" : `, CASE WHEN p.version = 2 THEN ${entriesSql("p")} END AS entries`}
|
|
15558
|
+
FROM (${branches.join(" UNION ALL ")} ORDER BY createdAt DESC ${page}) p
|
|
15559
|
+
ORDER BY p.createdAt DESC`;
|
|
15560
|
+
}
|
|
15561
|
+
/**
|
|
15562
|
+
* The `base` that `materializeState` takes for a run's entries (as of `at`, or latest): the state
|
|
15563
|
+
* version of another run it builds on (`stateBaseOf`), resolved recursively, else undefined. `{}`
|
|
15564
|
+
* when that run isn't stored. One query per run in the chain.
|
|
15565
|
+
*/
|
|
15566
|
+
async #stateBase(q, entries, at) {
|
|
15567
|
+
const ref = stateBaseOf(entries, at);
|
|
15568
|
+
if (!ref) return void 0;
|
|
15569
|
+
return (await this.#stateAt(q, ref.workflowName, ref.runId, ref))?.state ?? {};
|
|
15570
|
+
}
|
|
15571
|
+
/** A storage v2 run's state as of the version `at`, or null when the run isn't stored. */
|
|
15572
|
+
async #stateAt(q, workflowName, runId, at) {
|
|
15573
|
+
const row = (await q.execute({
|
|
15574
|
+
sql: `SELECT ${stateEntriesSql("r")} AS entries
|
|
15575
|
+
FROM ${TABLE_WORKFLOW_RUNS} r WHERE r.workflow_name = ? AND r.run_id = ?`,
|
|
15576
|
+
args: [
|
|
15577
|
+
at.seq,
|
|
15578
|
+
at.draft === true ? 1 : 0,
|
|
15579
|
+
workflowName,
|
|
15580
|
+
runId
|
|
15581
|
+
]
|
|
15582
|
+
})).rows[0];
|
|
15583
|
+
if (!row) return null;
|
|
15584
|
+
const entries = toEntries(entryRows(row.entries));
|
|
15585
|
+
return materializeState(entries, {
|
|
15586
|
+
at,
|
|
15587
|
+
base: await this.#stateBase(q, entries, at)
|
|
15588
|
+
});
|
|
15589
|
+
}
|
|
15590
|
+
/**
|
|
15591
|
+
* Before a run is deleted, stores the full state in place of every `state-base` entry built on
|
|
15592
|
+
* it, so the runs built on it keep reading the same state. Runs inside the delete's transaction.
|
|
15593
|
+
*/
|
|
15594
|
+
async #detachStateChildren(q, workflowName, runId) {
|
|
15595
|
+
const children = await q.execute({
|
|
15596
|
+
sql: `SELECT workflow_name, run_id, seq, json(payload) AS payload FROM ${TABLE_WORKFLOW_RUN_ENTRIES}
|
|
15597
|
+
WHERE kind = 'state-base' AND ${STATE_BASE_WORKFLOW_EXPR} = ? AND ${STATE_BASE_RUN_EXPR} = ?`,
|
|
15598
|
+
args: [workflowName, runId]
|
|
15599
|
+
});
|
|
15600
|
+
const states = /* @__PURE__ */ new Map();
|
|
15601
|
+
for (const child of children.rows) {
|
|
15602
|
+
const ref = JSON.parse(child.payload);
|
|
15603
|
+
const version = `${ref.seq}:${ref.draft === true}`;
|
|
15604
|
+
let state = states.get(version);
|
|
15605
|
+
if (!state) {
|
|
15606
|
+
state = (await this.#stateAt(q, workflowName, runId, ref))?.state ?? {};
|
|
15607
|
+
states.set(version, state);
|
|
15608
|
+
}
|
|
15609
|
+
await q.execute({
|
|
15610
|
+
sql: `UPDATE ${TABLE_WORKFLOW_RUN_ENTRIES} SET kind = 'state', payload = jsonb(?)
|
|
15611
|
+
WHERE workflow_name = ? AND run_id = ? AND seq = ?`,
|
|
15612
|
+
args: [
|
|
15613
|
+
safeStringify(state),
|
|
15614
|
+
child.workflow_name,
|
|
15615
|
+
child.run_id,
|
|
15616
|
+
child.seq
|
|
15617
|
+
]
|
|
15618
|
+
});
|
|
15619
|
+
}
|
|
15620
|
+
}
|
|
15621
|
+
/** Rebuilds a storage v2 run's `WorkflowRunState` from its header and `entriesSql` JSON. */
|
|
15622
|
+
async #materializeRun(q, header, rows) {
|
|
15623
|
+
const parsedHeader = typeof header === "string" ? JSON.parse(header) : header;
|
|
15624
|
+
const entries = toEntries(entryRows(rows));
|
|
15625
|
+
return materializeWorkflowRun(parsedHeader, entries, await this.#stateBase(q, entries));
|
|
15626
|
+
}
|
|
15627
|
+
/** Parses a row of `#runsSql`. */
|
|
15628
|
+
async #parseRun(row) {
|
|
15629
|
+
if (Number(row.version) !== 2) return this.parseWorkflowRun(row);
|
|
15630
|
+
const snapshot = row.entries ? await this.#materializeRun(this.#client, row.header, row.entries) : row.snapshot;
|
|
15631
|
+
return {
|
|
15632
|
+
...this.parseWorkflowRun({
|
|
15633
|
+
...row,
|
|
15634
|
+
snapshot
|
|
15635
|
+
}),
|
|
15636
|
+
storageVersion: 2
|
|
15637
|
+
};
|
|
15638
|
+
}
|
|
15639
|
+
#runError(operation, workflowName, runId, error) {
|
|
15640
|
+
return new MastraError({
|
|
15641
|
+
id: createStorageErrorId("LIBSQL", operation, "FAILED"),
|
|
15642
|
+
domain: ErrorDomain.STORAGE,
|
|
15643
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
15644
|
+
details: {
|
|
15645
|
+
workflowName,
|
|
15646
|
+
runId
|
|
15647
|
+
}
|
|
15648
|
+
}, error);
|
|
15649
|
+
}
|
|
15650
|
+
async loadWorkflowRunHeader({ workflowName, runId }) {
|
|
15651
|
+
try {
|
|
15652
|
+
const row = (await this.#client.execute({
|
|
15653
|
+
sql: `SELECT json(header) AS header, lastSeq, startStateSeq, runToken FROM ${TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
|
|
15654
|
+
args: [workflowName, runId]
|
|
15655
|
+
})).rows[0];
|
|
15656
|
+
return row ? {
|
|
15657
|
+
header: JSON.parse(row.header),
|
|
15658
|
+
lastSeq: Number(row.lastSeq),
|
|
15659
|
+
startStateSeq: Number(row.startStateSeq),
|
|
15660
|
+
runToken: row.runToken
|
|
15661
|
+
} : null;
|
|
15662
|
+
} catch (error) {
|
|
15663
|
+
throw this.#runError("LOAD_WORKFLOW_RUN_HEADER", workflowName, runId, error);
|
|
15664
|
+
}
|
|
15665
|
+
}
|
|
15666
|
+
async loadWorkflowRun({ workflowName, runId, successfulSteps }) {
|
|
15667
|
+
try {
|
|
15668
|
+
const summary = successfulSteps === "summary";
|
|
15669
|
+
const run = (await this.#client.execute({
|
|
15670
|
+
sql: `SELECT json(r.header) AS header, r.lastSeq, r.startStateSeq, r.runToken, ${entriesSql("r", { summary })} AS entries
|
|
15671
|
+
FROM ${TABLE_WORKFLOW_RUNS} r WHERE r.workflow_name = ? AND r.run_id = ?`,
|
|
15672
|
+
args: summary ? [
|
|
15673
|
+
JSON.stringify(STEP_RESULT_SUMMARY_FIELDS),
|
|
15674
|
+
workflowName,
|
|
15675
|
+
runId
|
|
15676
|
+
] : [workflowName, runId]
|
|
15677
|
+
})).rows[0];
|
|
15678
|
+
if (!run) return null;
|
|
15679
|
+
const header = JSON.parse(run.header);
|
|
15680
|
+
const rows = entryRows(run.entries);
|
|
15681
|
+
const entries = toEntries(rows);
|
|
15682
|
+
const summarizedInQuery = new Set(rows.filter((row) => row.summarized).map((row) => row.step_id));
|
|
15683
|
+
const steps = {};
|
|
15684
|
+
const summarizedStepIds = [];
|
|
15685
|
+
for (const [stepId, result] of latestStepResults(entries)) {
|
|
15686
|
+
steps[stepId] = result;
|
|
15687
|
+
if (summary && stepId !== "input" && result?.status === "success") {
|
|
15688
|
+
if (!summarizedInQuery.has(stepId)) steps[stepId] = summarizeStepResult(result);
|
|
15689
|
+
summarizedStepIds.push(stepId);
|
|
15690
|
+
}
|
|
15691
|
+
}
|
|
15692
|
+
const base = await this.#stateBase(this.#client, entries);
|
|
15693
|
+
const { state, statePatchBytes } = materializeState(entries, { base });
|
|
15694
|
+
return {
|
|
15695
|
+
header,
|
|
15696
|
+
lastSeq: Number(run.lastSeq),
|
|
15697
|
+
startStateSeq: Number(run.startStateSeq),
|
|
15698
|
+
runToken: run.runToken,
|
|
15699
|
+
state,
|
|
15700
|
+
statePatchBytes,
|
|
15701
|
+
steps,
|
|
15702
|
+
summarizedStepIds
|
|
15703
|
+
};
|
|
15704
|
+
} catch (error) {
|
|
15705
|
+
throw this.#runError("LOAD_WORKFLOW_RUN", workflowName, runId, error);
|
|
15706
|
+
}
|
|
15707
|
+
}
|
|
15708
|
+
async loadWorkflowRunSteps({ workflowName, runId, stepIds, expectedLastSeq, expectedRunToken }) {
|
|
15709
|
+
try {
|
|
15710
|
+
const run = (await this.#client.execute({
|
|
15711
|
+
sql: `SELECT r.lastSeq, r.runToken, (SELECT json_group_array(json_object(
|
|
15712
|
+
'seq', x.seq, 'ord', x.seq, 'kind', x.kind, 'step_id', x.step_id, 'absent', x.payload IS NULL, 'payload', json(x.payload)
|
|
15713
|
+
))
|
|
15714
|
+
FROM (
|
|
15715
|
+
SELECT e.seq, e.kind, e.step_id, e.payload,
|
|
15716
|
+
MAX(CASE WHEN e.kind = 'step' THEN e.seq END) OVER (PARTITION BY e.step_id) AS step_seq
|
|
15717
|
+
FROM ${TABLE_WORKFLOW_RUN_ENTRIES} e
|
|
15718
|
+
WHERE e.workflow_name = r.workflow_name AND e.run_id = r.run_id AND e.kind IN ('step', 'step-patch')
|
|
15719
|
+
AND e.step_id IN (SELECT value FROM json_each(?))
|
|
15720
|
+
) x
|
|
15721
|
+
WHERE x.seq >= x.step_seq) AS entries
|
|
15722
|
+
FROM ${TABLE_WORKFLOW_RUNS} r WHERE r.workflow_name = ? AND r.run_id = ?`,
|
|
15723
|
+
args: [
|
|
15724
|
+
JSON.stringify(stepIds),
|
|
15725
|
+
workflowName,
|
|
15726
|
+
runId
|
|
15727
|
+
]
|
|
15728
|
+
})).rows[0];
|
|
15729
|
+
if (!run) return null;
|
|
15730
|
+
if (expectedLastSeq !== void 0 && Number(run.lastSeq) !== expectedLastSeq) return null;
|
|
15731
|
+
if (expectedRunToken !== void 0 && run.runToken !== expectedRunToken) return null;
|
|
15732
|
+
return Object.fromEntries(latestStepResults(toEntries(entryRows(run.entries))));
|
|
15733
|
+
} catch (error) {
|
|
15734
|
+
throw this.#runError("LOAD_WORKFLOW_RUN_STEPS", workflowName, runId, error);
|
|
15735
|
+
}
|
|
15736
|
+
}
|
|
15737
|
+
async updateWorkflowRunHeader({ workflowName, runId, opts, fence }) {
|
|
15738
|
+
const check = this.#runFenceCheck(fence, runId, "updateWorkflowRunHeader");
|
|
15739
|
+
try {
|
|
15740
|
+
return await this.executeWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
|
|
15741
|
+
if (check) await assertRunFence(tx, check);
|
|
15742
|
+
const row = (await tx.execute({
|
|
15743
|
+
sql: `SELECT json(header) AS header, lastSeq, startStateSeq, runToken FROM ${TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
|
|
15744
|
+
args: [workflowName, runId]
|
|
15745
|
+
})).rows[0];
|
|
15746
|
+
if (!row) return void 0;
|
|
15747
|
+
const current = JSON.parse(row.header);
|
|
15748
|
+
const { expectedStatus, ...patch } = opts;
|
|
15749
|
+
if (!matchesExpectedWorkflowStatus(current.status, expectedStatus)) return void 0;
|
|
15750
|
+
const header = {
|
|
15751
|
+
...current,
|
|
15752
|
+
...patch
|
|
15753
|
+
};
|
|
15754
|
+
await tx.execute({
|
|
15755
|
+
sql: `UPDATE ${TABLE_WORKFLOW_RUNS} SET header = jsonb(?), status = ?, updatedAt = ? WHERE workflow_name = ? AND run_id = ?`,
|
|
15756
|
+
args: [
|
|
15757
|
+
safeStringify(header),
|
|
15758
|
+
header.status,
|
|
15759
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
15760
|
+
workflowName,
|
|
15761
|
+
runId
|
|
15762
|
+
]
|
|
15763
|
+
});
|
|
15764
|
+
return {
|
|
15765
|
+
header,
|
|
15766
|
+
lastSeq: Number(row.lastSeq),
|
|
15767
|
+
startStateSeq: Number(row.startStateSeq),
|
|
15768
|
+
runToken: row.runToken
|
|
15769
|
+
};
|
|
15770
|
+
}), "updateWorkflowRunHeader");
|
|
15771
|
+
} catch (error) {
|
|
15772
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
15773
|
+
throw this.#runError("UPDATE_WORKFLOW_RUN_HEADER", workflowName, runId, error);
|
|
15774
|
+
}
|
|
15775
|
+
}
|
|
15776
|
+
/**
|
|
15777
|
+
* One batch behind the write lock (see `#writeRun` for `expectedStatus` and `stateBase`): marking
|
|
15778
|
+
* the run a state base builds on as shared, the header upsert, then the deletion of the run's
|
|
15779
|
+
* entries (only its steps when another run builds on its state, see `stateShared`), then the new ones.
|
|
15780
|
+
*/
|
|
15781
|
+
async writeWorkflowRunCheckpoint({ fence, expectedStatus, steps, state, stateBase, statePatch, ...run }) {
|
|
15782
|
+
const entries = journalEntries(steps, {
|
|
15783
|
+
state,
|
|
15784
|
+
stateBase,
|
|
15785
|
+
statePatch
|
|
15786
|
+
}, true);
|
|
15787
|
+
const check = this.#runFenceCheck(fence, run.runId, "writeWorkflowRunCheckpoint");
|
|
15788
|
+
try {
|
|
15789
|
+
const base = state === void 0 ? stateBase : void 0;
|
|
15790
|
+
const statements = [];
|
|
15791
|
+
if (base) statements.push({
|
|
15792
|
+
sql: `UPDATE ${TABLE_WORKFLOW_RUNS} SET stateShared = 1 WHERE workflow_name = ? AND run_id = ? AND NOT stateShared`,
|
|
15793
|
+
args: [base.workflowName, base.runId]
|
|
15794
|
+
});
|
|
15795
|
+
statements.push(upsertHeader(run, entries.length, { checkpoint: true }), {
|
|
15796
|
+
sql: `DELETE FROM ${TABLE_WORKFLOW_RUN_ENTRIES} WHERE workflow_name = ? AND run_id = ?
|
|
15797
|
+
AND (kind IN ('step', 'step-patch') OR NOT (SELECT stateShared FROM ${TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?))`,
|
|
15798
|
+
args: [
|
|
15799
|
+
run.workflowName,
|
|
15800
|
+
run.runId,
|
|
15801
|
+
run.workflowName,
|
|
15802
|
+
run.runId
|
|
15803
|
+
]
|
|
15804
|
+
}, insertEntries(run, entries));
|
|
15805
|
+
const results = await this.executeWithRetry(() => this.#writeRun(check, run, {
|
|
15806
|
+
expectedStatus,
|
|
15807
|
+
stateBase: base
|
|
15808
|
+
}, statements), "writeWorkflowRunCheckpoint");
|
|
15809
|
+
if (!results) return null;
|
|
15810
|
+
const header = results[statements.length - 3].rows[0];
|
|
15811
|
+
return {
|
|
15812
|
+
lastSeq: Number(header.lastSeq),
|
|
15813
|
+
runToken: header.runToken
|
|
15814
|
+
};
|
|
15815
|
+
} catch (error) {
|
|
15816
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
15817
|
+
throw this.#runError("WRITE_WORKFLOW_RUN_CHECKPOINT", run.workflowName, run.runId, error);
|
|
15818
|
+
}
|
|
15819
|
+
}
|
|
15820
|
+
/**
|
|
15821
|
+
* One batch behind the write lock (see `#writeRun` for `expectedStatus`): the header upsert adds
|
|
15822
|
+
* the entry count to `lastSeq`, and the insert numbers the entries up to it, so writes to one run
|
|
15823
|
+
* never interleave.
|
|
15824
|
+
*/
|
|
15825
|
+
async appendWorkflowRunEntries({ fence, steps, stepPatches, state, statePatch, startStateSeq, expectedStatus, ...run }) {
|
|
15826
|
+
const entries = journalEntries(steps, {
|
|
15827
|
+
state,
|
|
15828
|
+
statePatch
|
|
15829
|
+
}, false, stepPatches);
|
|
15830
|
+
const check = this.#runFenceCheck(fence, run.runId, "appendWorkflowRunEntries");
|
|
15831
|
+
try {
|
|
15832
|
+
const results = await this.executeWithRetry(() => this.#writeRun(check, run, { expectedStatus }, [upsertHeader(run, entries.length, {
|
|
15833
|
+
checkpoint: false,
|
|
15834
|
+
startStateSeq
|
|
15835
|
+
}), insertEntries(run, entries)]), "appendWorkflowRunEntries");
|
|
15836
|
+
if (!results) return null;
|
|
15837
|
+
const header = results[0].rows[0];
|
|
15838
|
+
return {
|
|
15839
|
+
lastSeq: Number(header.lastSeq),
|
|
15840
|
+
runToken: header.runToken
|
|
15841
|
+
};
|
|
15842
|
+
} catch (error) {
|
|
15843
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
15844
|
+
throw this.#runError("APPEND_WORKFLOW_RUN_ENTRIES", run.workflowName, run.runId, error);
|
|
15845
|
+
}
|
|
15846
|
+
}
|
|
15847
|
+
async loadWorkflowRunState({ workflowName, runId, seq }) {
|
|
15848
|
+
try {
|
|
15849
|
+
return (await this.#stateAt(this.#client, workflowName, runId, { seq }))?.state ?? null;
|
|
15850
|
+
} catch (error) {
|
|
15851
|
+
throw this.#runError("LOAD_WORKFLOW_RUN_STATE", workflowName, runId, error);
|
|
15852
|
+
}
|
|
15853
|
+
}
|
|
15854
|
+
/**
|
|
15855
|
+
* One batch behind the write lock (or the run's fence): the header's `lastSeq` bump, then the
|
|
15856
|
+
* draft numbered at it.
|
|
15857
|
+
*/
|
|
15858
|
+
async appendWorkflowRunStateDraft(args) {
|
|
15859
|
+
const { workflowName, runId } = args;
|
|
15860
|
+
const payload = "state" in args ? { state: args.state } : {
|
|
15861
|
+
on: args.on.draft ? {
|
|
15862
|
+
seq: args.on.seq,
|
|
15863
|
+
draft: true
|
|
15864
|
+
} : { seq: args.on.seq },
|
|
15865
|
+
ops: args.ops
|
|
15866
|
+
};
|
|
15867
|
+
let row;
|
|
15868
|
+
try {
|
|
15869
|
+
const [header] = await this.executeWithRetry(() => withRunFence(this.#client, this.#runFenceCheck(void 0, runId, "appendWorkflowRunStateDraft"), (writer) => writer.batch([{
|
|
15870
|
+
sql: `UPDATE ${TABLE_WORKFLOW_RUNS} SET lastSeq = lastSeq + 1, stateShared = 1
|
|
15871
|
+
WHERE workflow_name = ? AND run_id = ? RETURNING lastSeq`,
|
|
15872
|
+
args: [workflowName, runId]
|
|
15873
|
+
}, {
|
|
15874
|
+
sql: `INSERT INTO ${TABLE_WORKFLOW_RUN_ENTRIES} (workflow_name, run_id, seq, kind, step_id, payload)
|
|
15875
|
+
SELECT workflow_name, run_id, lastSeq, 'state-draft', NULL, jsonb(?)
|
|
15876
|
+
FROM ${TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
|
|
15877
|
+
args: [
|
|
15878
|
+
safeStringify(payload),
|
|
15879
|
+
workflowName,
|
|
15880
|
+
runId
|
|
15881
|
+
]
|
|
15882
|
+
}])), "appendWorkflowRunStateDraft");
|
|
15883
|
+
row = header.rows[0];
|
|
15884
|
+
} catch (error) {
|
|
15885
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
15886
|
+
throw this.#runError("APPEND_WORKFLOW_RUN_STATE_DRAFT", workflowName, runId, error);
|
|
15887
|
+
}
|
|
15888
|
+
if (!row) throw new Error(`Storage v2 run ${workflowName}/${runId} is not stored`);
|
|
15889
|
+
return { seq: Number(row.lastSeq) };
|
|
15890
|
+
}
|
|
15244
15891
|
async #readRunOwner(q, runId) {
|
|
15245
15892
|
const row = (await q.execute({
|
|
15246
15893
|
sql: `SELECT ${RUN_OWNER_COLUMNS} FROM ${TABLE_WORKFLOW_RUN_OWNERS} WHERE runId = ?`,
|
|
@@ -15341,21 +15988,56 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
15341
15988
|
throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
|
|
15342
15989
|
}
|
|
15343
15990
|
}
|
|
15344
|
-
/**
|
|
15991
|
+
/**
|
|
15992
|
+
* Delete workflow runs and run ownership rows older than their policy's `maxAge`, batched.
|
|
15993
|
+
* The `workflowSnapshot` policy covers both storage versions: storage v2 runs are pruned by
|
|
15994
|
+
* their header's last write, together with their entries.
|
|
15995
|
+
*/
|
|
15345
15996
|
async prune(policies, options) {
|
|
15346
15997
|
const targets = resolveTargets({
|
|
15347
15998
|
policies,
|
|
15348
15999
|
descriptor: WorkflowsLibSQL.retentionTables,
|
|
15349
16000
|
order: ["workflowSnapshot", "runOwnership"]
|
|
15350
16001
|
});
|
|
16002
|
+
const snapshotTarget = targets.findIndex((target) => target.table === TABLE_WORKFLOW_SNAPSHOT);
|
|
16003
|
+
if (snapshotTarget !== -1) targets.splice(snapshotTarget + 1, 0, {
|
|
16004
|
+
...targets[snapshotTarget],
|
|
16005
|
+
table: TABLE_WORKFLOW_RUNS
|
|
16006
|
+
});
|
|
15351
16007
|
return runPrune({
|
|
15352
16008
|
db: this.#db,
|
|
15353
16009
|
domain: "workflows",
|
|
15354
16010
|
targets,
|
|
15355
16011
|
options,
|
|
15356
|
-
logger: this.logger
|
|
16012
|
+
logger: this.logger,
|
|
16013
|
+
deleteBatch: (target, cutoff, limit) => target.table === TABLE_WORKFLOW_RUNS ? this.#pruneRunsBatch(cutoff, limit) : void 0
|
|
15357
16014
|
});
|
|
15358
16015
|
}
|
|
16016
|
+
/**
|
|
16017
|
+
* Deletes up to `limit` aged storage v2 runs in one write transaction: detaches the runs built on
|
|
16018
|
+
* their state, then deletes their entries and headers. Every statement selects the same runs, and
|
|
16019
|
+
* the transaction holds the write lock, so they agree.
|
|
16020
|
+
*/
|
|
16021
|
+
async #pruneRunsBatch(cutoff, limit) {
|
|
16022
|
+
const doomed = `SELECT workflow_name, run_id FROM ${TABLE_WORKFLOW_RUNS}
|
|
16023
|
+
WHERE updatedAt < ? ORDER BY updatedAt, workflow_name, run_id LIMIT ?`;
|
|
16024
|
+
return this.executeWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
|
|
16025
|
+
const shared = await tx.execute({
|
|
16026
|
+
sql: `SELECT d.workflow_name, d.run_id FROM (${doomed}) d
|
|
16027
|
+
JOIN ${TABLE_WORKFLOW_RUNS} r USING (workflow_name, run_id) WHERE r.stateShared`,
|
|
16028
|
+
args: [cutoff, limit]
|
|
16029
|
+
});
|
|
16030
|
+
for (const run of shared.rows) await this.#detachStateChildren(tx, run.workflow_name, run.run_id);
|
|
16031
|
+
await tx.execute({
|
|
16032
|
+
sql: `DELETE FROM ${TABLE_WORKFLOW_RUN_ENTRIES} WHERE (workflow_name, run_id) IN (${doomed})`,
|
|
16033
|
+
args: [cutoff, limit]
|
|
16034
|
+
});
|
|
16035
|
+
return (await tx.execute({
|
|
16036
|
+
sql: `DELETE FROM ${TABLE_WORKFLOW_RUNS} WHERE (workflow_name, run_id) IN (${doomed})`,
|
|
16037
|
+
args: [cutoff, limit]
|
|
16038
|
+
})).rowsAffected;
|
|
16039
|
+
}), `prune ${TABLE_WORKFLOW_RUNS}`);
|
|
16040
|
+
}
|
|
15359
16041
|
async setupPragmaSettings() {
|
|
15360
16042
|
try {
|
|
15361
16043
|
await this.#client.execute("PRAGMA busy_timeout = 10000;");
|
|
@@ -15387,6 +16069,10 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
15387
16069
|
args: [workflowName, runId]
|
|
15388
16070
|
});
|
|
15389
16071
|
if (!existingSnapshotResult.rows?.[0]) {
|
|
16072
|
+
if ((await tx.execute({
|
|
16073
|
+
sql: `SELECT 1 FROM ${TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
|
|
16074
|
+
args: [workflowName, runId]
|
|
16075
|
+
})).rows.length > 0) throw new Error(`updateWorkflowResults does not support storage v2 runs (run ${runId})`);
|
|
15390
16076
|
await tx.rollback();
|
|
15391
16077
|
return {};
|
|
15392
16078
|
}
|
|
@@ -15421,12 +16107,51 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
15421
16107
|
}
|
|
15422
16108
|
}), "updateWorkflowResults");
|
|
15423
16109
|
}
|
|
15424
|
-
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
|
|
16110
|
+
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence, storageVersion }) {
|
|
15425
16111
|
const check = this.#runFenceCheck(explicitFence, runId, "updateWorkflowState");
|
|
15426
16112
|
return this.executeWithRetry(() => withClientWriteLock(this.#client, async () => {
|
|
15427
16113
|
const tx = await this.#client.transaction("write");
|
|
15428
16114
|
try {
|
|
15429
16115
|
if (check) await assertRunFence(tx, check);
|
|
16116
|
+
if (storageVersion !== 1) {
|
|
16117
|
+
const row = (await tx.execute({
|
|
16118
|
+
sql: `SELECT json(header) AS header FROM ${TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
|
|
16119
|
+
args: [workflowName, runId]
|
|
16120
|
+
})).rows[0];
|
|
16121
|
+
if (row) {
|
|
16122
|
+
const current = JSON.parse(row.header);
|
|
16123
|
+
const { expectedStatus, ...patch } = opts;
|
|
16124
|
+
if (!matchesExpectedWorkflowStatus(current.status, expectedStatus)) {
|
|
16125
|
+
await tx.rollback();
|
|
16126
|
+
return;
|
|
16127
|
+
}
|
|
16128
|
+
const header = {
|
|
16129
|
+
...current,
|
|
16130
|
+
...patch
|
|
16131
|
+
};
|
|
16132
|
+
await tx.execute({
|
|
16133
|
+
sql: `UPDATE ${TABLE_WORKFLOW_RUNS} SET header = jsonb(?), status = ?, updatedAt = ? WHERE workflow_name = ? AND run_id = ?`,
|
|
16134
|
+
args: [
|
|
16135
|
+
safeStringify(header),
|
|
16136
|
+
header.status,
|
|
16137
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
16138
|
+
workflowName,
|
|
16139
|
+
runId
|
|
16140
|
+
]
|
|
16141
|
+
});
|
|
16142
|
+
const entries = await tx.execute({
|
|
16143
|
+
sql: `SELECT ${entriesSql("r")} AS entries FROM ${TABLE_WORKFLOW_RUNS} r WHERE r.workflow_name = ? AND r.run_id = ?`,
|
|
16144
|
+
args: [workflowName, runId]
|
|
16145
|
+
});
|
|
16146
|
+
const snapshot = await this.#materializeRun(tx, header, entries.rows[0].entries);
|
|
16147
|
+
await tx.commit();
|
|
16148
|
+
return snapshot;
|
|
16149
|
+
}
|
|
16150
|
+
if (storageVersion === 2) {
|
|
16151
|
+
await tx.rollback();
|
|
16152
|
+
return;
|
|
16153
|
+
}
|
|
16154
|
+
}
|
|
15430
16155
|
const existingSnapshotResult = await tx.execute({
|
|
15431
16156
|
sql: `SELECT json(snapshot) as snapshot FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
|
|
15432
16157
|
args: [workflowName, runId]
|
|
@@ -15496,15 +16221,32 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
15496
16221
|
}
|
|
15497
16222
|
async loadWorkflowRunStatus({ workflowName, runId }) {
|
|
15498
16223
|
return (await this.#client.execute({
|
|
15499
|
-
sql: `SELECT
|
|
15500
|
-
|
|
16224
|
+
sql: `SELECT COALESCE(
|
|
16225
|
+
(SELECT status FROM ${TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?),
|
|
16226
|
+
(SELECT json_extract(snapshot, '$.status') FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?)
|
|
16227
|
+
) AS status`,
|
|
16228
|
+
args: [
|
|
16229
|
+
workflowName,
|
|
16230
|
+
runId,
|
|
16231
|
+
workflowName,
|
|
16232
|
+
runId
|
|
16233
|
+
]
|
|
15501
16234
|
})).rows?.[0]?.status ?? null;
|
|
15502
16235
|
}
|
|
15503
|
-
async loadWorkflowSnapshot({ workflowName, runId }) {
|
|
16236
|
+
async loadWorkflowSnapshot({ workflowName, runId, storageVersion }) {
|
|
15504
16237
|
this.logger.debug("Loading workflow snapshot", {
|
|
15505
16238
|
workflowName,
|
|
15506
16239
|
runId
|
|
15507
16240
|
});
|
|
16241
|
+
if (storageVersion !== 1) {
|
|
16242
|
+
const row = (await this.#client.execute({
|
|
16243
|
+
sql: `SELECT json(r.header) AS header, ${entriesSql("r")} AS entries
|
|
16244
|
+
FROM ${TABLE_WORKFLOW_RUNS} r WHERE r.workflow_name = ? AND r.run_id = ?`,
|
|
16245
|
+
args: [workflowName, runId]
|
|
16246
|
+
})).rows[0];
|
|
16247
|
+
if (row) return this.#materializeRun(this.#client, row.header, row.entries);
|
|
16248
|
+
if (storageVersion === 2) return null;
|
|
16249
|
+
}
|
|
15508
16250
|
const d = await this.#db.select({
|
|
15509
16251
|
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
15510
16252
|
keys: {
|
|
@@ -15514,7 +16256,7 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
15514
16256
|
});
|
|
15515
16257
|
return d ? d.snapshot : null;
|
|
15516
16258
|
}
|
|
15517
|
-
async getWorkflowRunById({ runId, workflowName }) {
|
|
16259
|
+
async getWorkflowRunById({ runId, workflowName, storageVersion }) {
|
|
15518
16260
|
const conditions = [];
|
|
15519
16261
|
const args = [];
|
|
15520
16262
|
if (runId) {
|
|
@@ -15528,11 +16270,15 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
15528
16270
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
15529
16271
|
try {
|
|
15530
16272
|
const result = await this.#client.execute({
|
|
15531
|
-
sql:
|
|
15532
|
-
|
|
16273
|
+
sql: this.#runsSql({
|
|
16274
|
+
v1Where: storageVersion === 2 ? null : whereClause,
|
|
16275
|
+
v2Where: storageVersion === 1 ? null : whereClause,
|
|
16276
|
+
page: "LIMIT 1"
|
|
16277
|
+
}),
|
|
16278
|
+
args: [...storageVersion === 2 ? [] : args, ...storageVersion === 1 ? [] : args]
|
|
15533
16279
|
});
|
|
15534
16280
|
if (!result.rows?.[0]) return null;
|
|
15535
|
-
return this
|
|
16281
|
+
return await this.#parseRun(result.rows[0]);
|
|
15536
16282
|
} catch (error) {
|
|
15537
16283
|
throw new MastraError({
|
|
15538
16284
|
id: createStorageErrorId("LIBSQL", "GET_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
@@ -15541,14 +16287,33 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
15541
16287
|
}, error);
|
|
15542
16288
|
}
|
|
15543
16289
|
}
|
|
15544
|
-
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
16290
|
+
async deleteWorkflowRunById({ runId, workflowName, storageVersion, fence }) {
|
|
15545
16291
|
const check = this.#runFenceCheck(fence, runId, "deleteWorkflowRunById");
|
|
15546
16292
|
return this.executeWithRetry(async () => {
|
|
15547
16293
|
try {
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
|
|
15551
|
-
|
|
16294
|
+
if (storageVersion === 1) {
|
|
16295
|
+
await withRunFence(this.#client, check, (writer) => writer.execute({
|
|
16296
|
+
sql: `DELETE FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
|
|
16297
|
+
args: [workflowName, runId]
|
|
16298
|
+
}));
|
|
16299
|
+
return;
|
|
16300
|
+
}
|
|
16301
|
+
await inWriteTransaction(this.#client, async (tx) => {
|
|
16302
|
+
if (check) await assertRunFence(tx, check);
|
|
16303
|
+
const run = await tx.execute({
|
|
16304
|
+
sql: `SELECT stateShared FROM ${TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
|
|
16305
|
+
args: [workflowName, runId]
|
|
16306
|
+
});
|
|
16307
|
+
if (Number(run.rows[0]?.stateShared)) await this.#detachStateChildren(tx, workflowName, runId);
|
|
16308
|
+
for (const table of [
|
|
16309
|
+
TABLE_WORKFLOW_RUNS,
|
|
16310
|
+
TABLE_WORKFLOW_RUN_ENTRIES,
|
|
16311
|
+
...storageVersion === 2 ? [] : [TABLE_WORKFLOW_SNAPSHOT]
|
|
16312
|
+
]) await tx.execute({
|
|
16313
|
+
sql: `DELETE FROM ${table} WHERE workflow_name = ? AND run_id = ?`,
|
|
16314
|
+
args: [workflowName, runId]
|
|
16315
|
+
});
|
|
16316
|
+
});
|
|
15552
16317
|
} catch (error) {
|
|
15553
16318
|
if (isRunFenceConflictError(error)) throw error;
|
|
15554
16319
|
throw new MastraError({
|
|
@@ -15567,52 +16332,74 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
15567
16332
|
try {
|
|
15568
16333
|
const conditions = [];
|
|
15569
16334
|
const args = [];
|
|
16335
|
+
const v2Conditions = [];
|
|
16336
|
+
const v2Args = [];
|
|
15570
16337
|
if (workflowName) {
|
|
15571
16338
|
conditions.push("workflow_name = ?");
|
|
15572
16339
|
args.push(workflowName);
|
|
16340
|
+
v2Conditions.push("workflow_name = ?");
|
|
16341
|
+
v2Args.push(workflowName);
|
|
15573
16342
|
}
|
|
15574
16343
|
if (status) {
|
|
15575
16344
|
conditions.push("json_extract(snapshot, '$.status') = ?");
|
|
15576
16345
|
args.push(status);
|
|
16346
|
+
v2Conditions.push("status = ?");
|
|
16347
|
+
v2Args.push(status);
|
|
15577
16348
|
}
|
|
15578
16349
|
if (fromDate) {
|
|
15579
16350
|
conditions.push("createdAt >= ?");
|
|
15580
16351
|
args.push(fromDate.toISOString());
|
|
16352
|
+
v2Conditions.push("createdAt >= ?");
|
|
16353
|
+
v2Args.push(fromDate.toISOString());
|
|
15581
16354
|
}
|
|
15582
16355
|
if (toDate) {
|
|
15583
16356
|
conditions.push("createdAt <= ?");
|
|
15584
16357
|
args.push(toDate.toISOString());
|
|
16358
|
+
v2Conditions.push("createdAt <= ?");
|
|
16359
|
+
v2Args.push(toDate.toISOString());
|
|
15585
16360
|
}
|
|
15586
16361
|
if (resourceId) {
|
|
15587
16362
|
if (await this.#db.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId")) {
|
|
15588
16363
|
conditions.push("resourceId = ?");
|
|
15589
16364
|
args.push(resourceId);
|
|
15590
16365
|
} else this.logger.warn(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
|
|
16366
|
+
v2Conditions.push("resourceId = ?");
|
|
16367
|
+
v2Args.push(resourceId);
|
|
15591
16368
|
}
|
|
15592
16369
|
if (threadId) {
|
|
15593
16370
|
conditions.push(`(EXISTS (SELECT 1 FROM json_each(snapshot, '$.context') AS je WHERE json_extract(je.value, '$.status') = 'suspended' AND json_extract(je.value, '$.suspendPayload.__streamState.messageList.memoryInfo.threadId') = ?) OR json_extract(snapshot, '$.context.input.messageListState.memoryInfo.threadId') = ?)`);
|
|
15594
16371
|
args.push(threadId, threadId);
|
|
16372
|
+
v2Conditions.push("threadId = ?");
|
|
16373
|
+
v2Args.push(threadId);
|
|
15595
16374
|
}
|
|
15596
16375
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
16376
|
+
const v2WhereClause = v2Conditions.length > 0 ? `WHERE ${v2Conditions.join(" AND ")}` : "";
|
|
15597
16377
|
let total = 0;
|
|
15598
16378
|
const usePagination = typeof perPage === "number" && typeof page === "number";
|
|
15599
16379
|
if (usePagination) {
|
|
15600
16380
|
const countResult = await this.#client.execute({
|
|
15601
|
-
sql: `SELECT COUNT(*)
|
|
15602
|
-
|
|
16381
|
+
sql: `SELECT (SELECT COUNT(*) FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause})
|
|
16382
|
+
+ (SELECT COUNT(*) FROM ${TABLE_WORKFLOW_RUNS} ${v2WhereClause}) AS count`,
|
|
16383
|
+
args: [...args, ...v2Args]
|
|
15603
16384
|
});
|
|
15604
16385
|
total = Number(countResult.rows?.[0]?.count ?? 0);
|
|
15605
16386
|
}
|
|
15606
16387
|
const normalizedPerPage = usePagination ? normalizePerPage(perPage, Number.MAX_SAFE_INTEGER) : 0;
|
|
15607
16388
|
const offset = usePagination ? page * normalizedPerPage : 0;
|
|
15608
|
-
const
|
|
15609
|
-
sql:
|
|
15610
|
-
|
|
16389
|
+
const result = await this.#client.execute({
|
|
16390
|
+
sql: this.#runsSql({
|
|
16391
|
+
v1Where: whereClause,
|
|
16392
|
+
v2Where: v2WhereClause,
|
|
16393
|
+
summary,
|
|
16394
|
+
page: usePagination ? "LIMIT ? OFFSET ?" : ""
|
|
16395
|
+
}),
|
|
16396
|
+
args: [
|
|
15611
16397
|
...args,
|
|
15612
|
-
|
|
15613
|
-
offset
|
|
15614
|
-
]
|
|
15615
|
-
})
|
|
16398
|
+
...v2Args,
|
|
16399
|
+
...usePagination ? [normalizedPerPage, offset] : []
|
|
16400
|
+
]
|
|
16401
|
+
});
|
|
16402
|
+
const runs = await Promise.all((result.rows || []).map((row) => this.#parseRun(row)));
|
|
15616
16403
|
return {
|
|
15617
16404
|
runs,
|
|
15618
16405
|
total: total || runs.length
|