convex-effect-workflows 0.1.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/README.md +107 -0
- package/dist/client/ConvexCtx.d.ts +12 -0
- package/dist/client/ConvexCtx.d.ts.map +1 -0
- package/dist/client/ConvexCtx.js +6 -0
- package/dist/client/ConvexCtx.js.map +1 -0
- package/dist/client/ConvexLogger.d.ts +7 -0
- package/dist/client/ConvexLogger.d.ts.map +1 -0
- package/dist/client/ConvexLogger.js +39 -0
- package/dist/client/ConvexLogger.js.map +1 -0
- package/dist/client/ConvexTracer.d.ts +7 -0
- package/dist/client/ConvexTracer.d.ts.map +1 -0
- package/dist/client/ConvexTracer.js +60 -0
- package/dist/client/ConvexTracer.js.map +1 -0
- package/dist/client/ConvexWorkflowEngine.d.ts +308 -0
- package/dist/client/ConvexWorkflowEngine.d.ts.map +1 -0
- package/dist/client/ConvexWorkflowEngine.js +88 -0
- package/dist/client/ConvexWorkflowEngine.js.map +1 -0
- package/dist/client/activityWorker.d.ts +23 -0
- package/dist/client/activityWorker.d.ts.map +1 -0
- package/dist/client/activityWorker.js +41 -0
- package/dist/client/activityWorker.js.map +1 -0
- package/dist/client/boundaries.d.ts +27 -0
- package/dist/client/boundaries.d.ts.map +1 -0
- package/dist/client/boundaries.js +17 -0
- package/dist/client/boundaries.js.map +1 -0
- package/dist/client/encoded.d.ts +22 -0
- package/dist/client/encoded.d.ts.map +1 -0
- package/dist/client/encoded.js +276 -0
- package/dist/client/encoded.js.map +1 -0
- package/dist/client/index.d.ts +13 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +11 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/registry.d.ts +17 -0
- package/dist/client/registry.d.ts.map +1 -0
- package/dist/client/registry.js +21 -0
- package/dist/client/registry.js.map +1 -0
- package/dist/client/runner.d.ts +27 -0
- package/dist/client/runner.d.ts.map +1 -0
- package/dist/client/runner.js +90 -0
- package/dist/client/runner.js.map +1 -0
- package/dist/client/runtime.d.ts +10 -0
- package/dist/client/runtime.d.ts.map +1 -0
- package/dist/client/runtime.js +15 -0
- package/dist/client/runtime.js.map +1 -0
- package/dist/component/_generated/api.d.ts +148 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +921 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/activityCompletions.d.ts +27 -0
- package/dist/component/activityCompletions.d.ts.map +1 -0
- package/dist/component/activityCompletions.js +70 -0
- package/dist/component/activityCompletions.js.map +1 -0
- package/dist/component/boundaries.d.ts +20 -0
- package/dist/component/boundaries.d.ts.map +1 -0
- package/dist/component/boundaries.js +17 -0
- package/dist/component/boundaries.js.map +1 -0
- package/dist/component/cleanup.d.ts +11 -0
- package/dist/component/cleanup.d.ts.map +1 -0
- package/dist/component/cleanup.js +163 -0
- package/dist/component/cleanup.js.map +1 -0
- package/dist/component/clocks.d.ts +12 -0
- package/dist/component/clocks.d.ts.map +1 -0
- package/dist/component/clocks.js +26 -0
- package/dist/component/clocks.js.map +1 -0
- package/dist/component/config.d.ts +25 -0
- package/dist/component/config.d.ts.map +1 -0
- package/dist/component/config.js +110 -0
- package/dist/component/config.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +6 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/dashboard.d.ts +268 -0
- package/dist/component/dashboard.d.ts.map +1 -0
- package/dist/component/dashboard.js +622 -0
- package/dist/component/dashboard.js.map +1 -0
- package/dist/component/deferreds.d.ts +31 -0
- package/dist/component/deferreds.d.ts.map +1 -0
- package/dist/component/deferreds.js +138 -0
- package/dist/component/deferreds.js.map +1 -0
- package/dist/component/executions.d.ts +77 -0
- package/dist/component/executions.d.ts.map +1 -0
- package/dist/component/executions.js +186 -0
- package/dist/component/executions.js.map +1 -0
- package/dist/component/journalSteps.d.ts +261 -0
- package/dist/component/journalSteps.d.ts.map +1 -0
- package/dist/component/journalSteps.js +203 -0
- package/dist/component/journalSteps.js.map +1 -0
- package/dist/component/logs.d.ts +68 -0
- package/dist/component/logs.d.ts.map +1 -0
- package/dist/component/logs.js +123 -0
- package/dist/component/logs.js.map +1 -0
- package/dist/component/onComplete.d.ts +31 -0
- package/dist/component/onComplete.d.ts.map +1 -0
- package/dist/component/onComplete.js +146 -0
- package/dist/component/onComplete.js.map +1 -0
- package/dist/component/payloads.d.ts +26 -0
- package/dist/component/payloads.d.ts.map +1 -0
- package/dist/component/payloads.js +57 -0
- package/dist/component/payloads.js.map +1 -0
- package/dist/component/queries.d.ts +2 -0
- package/dist/component/queries.d.ts.map +1 -0
- package/dist/component/queries.js +2 -0
- package/dist/component/queries.js.map +1 -0
- package/dist/component/runner.d.ts +31 -0
- package/dist/component/runner.d.ts.map +1 -0
- package/dist/component/runner.js +87 -0
- package/dist/component/runner.js.map +1 -0
- package/dist/component/schema.d.ts +282 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +119 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/spans.d.ts +105 -0
- package/dist/component/spans.d.ts.map +1 -0
- package/dist/component/spans.js +190 -0
- package/dist/component/spans.js.map +1 -0
- package/dist/component/utils.d.ts +15 -0
- package/dist/component/utils.d.ts.map +1 -0
- package/dist/component/utils.js +53 -0
- package/dist/component/utils.js.map +1 -0
- package/dist/shared/constants.d.ts +12 -0
- package/dist/shared/constants.d.ts.map +1 -0
- package/dist/shared/constants.js +12 -0
- package/dist/shared/constants.js.map +1 -0
- package/dist/shared/validators.d.ts +69 -0
- package/dist/shared/validators.d.ts.map +1 -0
- package/dist/shared/validators.js +30 -0
- package/dist/shared/validators.js.map +1 -0
- package/package.json +74 -0
- package/src/client/ConvexCtx.ts +21 -0
- package/src/client/ConvexLogger.ts +52 -0
- package/src/client/ConvexTracer.ts +75 -0
- package/src/client/ConvexWorkflowEngine.test.ts +124 -0
- package/src/client/ConvexWorkflowEngine.ts +209 -0
- package/src/client/activityWorker.ts +62 -0
- package/src/client/boundaries.test.ts +83 -0
- package/src/client/boundaries.ts +79 -0
- package/src/client/encoded.lifecycle.test.ts +336 -0
- package/src/client/encoded.test.ts +153 -0
- package/src/client/encoded.ts +484 -0
- package/src/client/index.ts +47 -0
- package/src/client/registry.ts +35 -0
- package/src/client/runner.ts +165 -0
- package/src/client/runtime.ts +30 -0
- package/src/component/_generated/api.ts +179 -0
- package/src/component/_generated/component.ts +1216 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/activityCompletions.ts +73 -0
- package/src/component/boundaries.ts +55 -0
- package/src/component/cleanup.test.ts +219 -0
- package/src/component/cleanup.ts +218 -0
- package/src/component/clocks.ts +26 -0
- package/src/component/config.test.ts +159 -0
- package/src/component/config.ts +145 -0
- package/src/component/convex.config.ts +7 -0
- package/src/component/core.test.ts +829 -0
- package/src/component/dashboard.scaling.test.ts +268 -0
- package/src/component/dashboard.ts +743 -0
- package/src/component/deferreds.ts +162 -0
- package/src/component/executions.ts +225 -0
- package/src/component/journalSteps.ts +252 -0
- package/src/component/logs.ts +152 -0
- package/src/component/onComplete.ts +170 -0
- package/src/component/payloads.ts +83 -0
- package/src/component/queries.ts +8 -0
- package/src/component/runner.ts +122 -0
- package/src/component/schema.ts +155 -0
- package/src/component/setup.test.ts +15 -0
- package/src/component/spans.ts +241 -0
- package/src/component/utils.test.ts +32 -0
- package/src/component/utils.ts +73 -0
- package/src/shared/constants.test.ts +14 -0
- package/src/shared/constants.ts +15 -0
- package/src/shared/validators.ts +98 -0
- package/src/test.d.ts +8 -0
- package/src/test.ts +17 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { mutation, query, type MutationCtx, type QueryCtx } from "./_generated/server.js";
|
|
3
|
+
import { vSpanKind, vSpanStatus } from "../shared/validators.js";
|
|
4
|
+
import { generateSpanId, nowTs } from "./utils.js";
|
|
5
|
+
import { persistBoundedPayload } from "./payloads.js";
|
|
6
|
+
import { MAX_SPAN_ATTR_BYTES } from "../shared/constants.js";
|
|
7
|
+
|
|
8
|
+
type DbReaderCtx = Pick<QueryCtx, "db">;
|
|
9
|
+
|
|
10
|
+
async function getSpan(
|
|
11
|
+
ctx: DbReaderCtx,
|
|
12
|
+
executionId: string,
|
|
13
|
+
spanId: string,
|
|
14
|
+
) {
|
|
15
|
+
return await ctx.db
|
|
16
|
+
.query("spans")
|
|
17
|
+
.withIndex("by_execution_span", (q) =>
|
|
18
|
+
q.eq("executionId", executionId).eq("spanId", spanId),
|
|
19
|
+
)
|
|
20
|
+
.unique();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const createSpan = mutation({
|
|
24
|
+
args: {
|
|
25
|
+
executionId: v.string(),
|
|
26
|
+
traceId: v.string(),
|
|
27
|
+
spanId: v.optional(v.string()),
|
|
28
|
+
parentSpanId: v.optional(v.string()),
|
|
29
|
+
stepNumber: v.optional(v.number()),
|
|
30
|
+
name: v.string(),
|
|
31
|
+
kind: vSpanKind,
|
|
32
|
+
attempt: v.optional(v.number()),
|
|
33
|
+
input: v.optional(v.any()),
|
|
34
|
+
attributes: v.optional(v.any()),
|
|
35
|
+
},
|
|
36
|
+
returns: v.object({ spanId: v.string(), created: v.boolean() }),
|
|
37
|
+
handler: async (ctx, args) => {
|
|
38
|
+
const spanId = args.spanId ?? generateSpanId();
|
|
39
|
+
const existing = await getSpan(ctx, args.executionId, spanId);
|
|
40
|
+
if (existing) {
|
|
41
|
+
return { spanId, created: false };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const inputPayload =
|
|
45
|
+
args.input === undefined
|
|
46
|
+
? { size: 0, ref: undefined, preview: undefined }
|
|
47
|
+
: await persistBoundedPayload(ctx, "spanInput", args.input);
|
|
48
|
+
|
|
49
|
+
const attrPayload =
|
|
50
|
+
args.attributes === undefined
|
|
51
|
+
? undefined
|
|
52
|
+
: await persistBoundedPayload(
|
|
53
|
+
ctx,
|
|
54
|
+
"logData",
|
|
55
|
+
args.attributes,
|
|
56
|
+
MAX_SPAN_ATTR_BYTES,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
await ctx.db.insert("spans", {
|
|
60
|
+
executionId: args.executionId,
|
|
61
|
+
traceId: args.traceId,
|
|
62
|
+
spanId,
|
|
63
|
+
parentSpanId: args.parentSpanId,
|
|
64
|
+
stepNumber: args.stepNumber,
|
|
65
|
+
name: args.name,
|
|
66
|
+
kind: args.kind,
|
|
67
|
+
status: "started",
|
|
68
|
+
startTime: nowTs(),
|
|
69
|
+
attempt: args.attempt,
|
|
70
|
+
attributes: attrPayload?.preview,
|
|
71
|
+
inputRef: inputPayload.ref,
|
|
72
|
+
inputPreview: inputPayload.preview,
|
|
73
|
+
inputSize: inputPayload.size,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return { spanId, created: true };
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const endSpan = mutation({
|
|
81
|
+
args: {
|
|
82
|
+
executionId: v.string(),
|
|
83
|
+
spanId: v.string(),
|
|
84
|
+
output: v.optional(v.any()),
|
|
85
|
+
error: v.optional(v.string()),
|
|
86
|
+
attributes: v.optional(v.any()),
|
|
87
|
+
},
|
|
88
|
+
returns: v.object({ changed: v.boolean() }),
|
|
89
|
+
handler: async (ctx, args) => {
|
|
90
|
+
const span = await getSpan(ctx, args.executionId, args.spanId);
|
|
91
|
+
if (!span) {
|
|
92
|
+
throw new Error(`Span not found: ${args.executionId}/${args.spanId}`);
|
|
93
|
+
}
|
|
94
|
+
if (span.status === "ended") {
|
|
95
|
+
return { changed: false };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const outputPayload =
|
|
99
|
+
args.output === undefined
|
|
100
|
+
? { size: undefined, ref: undefined, preview: undefined }
|
|
101
|
+
: await persistBoundedPayload(ctx, "spanOutput", args.output);
|
|
102
|
+
|
|
103
|
+
const attrPayload =
|
|
104
|
+
args.attributes === undefined
|
|
105
|
+
? undefined
|
|
106
|
+
: await persistBoundedPayload(
|
|
107
|
+
ctx,
|
|
108
|
+
"logData",
|
|
109
|
+
args.attributes,
|
|
110
|
+
MAX_SPAN_ATTR_BYTES,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
await ctx.db.patch(span._id, {
|
|
114
|
+
status: "ended",
|
|
115
|
+
endTime: nowTs(),
|
|
116
|
+
outputRef: outputPayload.ref,
|
|
117
|
+
outputPreview: outputPayload.preview,
|
|
118
|
+
outputSize: outputPayload.size,
|
|
119
|
+
attributes: attrPayload?.preview ?? span.attributes,
|
|
120
|
+
error: args.error,
|
|
121
|
+
});
|
|
122
|
+
return { changed: true };
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
export const getExecutionSpan = query({
|
|
127
|
+
args: {
|
|
128
|
+
executionId: v.string(),
|
|
129
|
+
spanId: v.string(),
|
|
130
|
+
},
|
|
131
|
+
returns: v.any(),
|
|
132
|
+
handler: async (ctx, args) => {
|
|
133
|
+
return await getSpan(ctx, args.executionId, args.spanId);
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export const listExecutionSpans = query({
|
|
138
|
+
args: {
|
|
139
|
+
executionId: v.string(),
|
|
140
|
+
limit: v.number(),
|
|
141
|
+
afterStartTime: v.optional(v.number()),
|
|
142
|
+
kind: v.optional(vSpanKind),
|
|
143
|
+
status: v.optional(vSpanStatus),
|
|
144
|
+
},
|
|
145
|
+
returns: v.any(),
|
|
146
|
+
handler: async (ctx, args) => {
|
|
147
|
+
const limit = Math.max(1, Math.min(args.limit, 500));
|
|
148
|
+
let rows = await ctx.db
|
|
149
|
+
.query("spans")
|
|
150
|
+
.withIndex("by_execution_start", (q) => q.eq("executionId", args.executionId))
|
|
151
|
+
.take(limit + 200);
|
|
152
|
+
|
|
153
|
+
if (args.afterStartTime !== undefined) {
|
|
154
|
+
rows = rows.filter((row) => row.startTime > args.afterStartTime!);
|
|
155
|
+
}
|
|
156
|
+
if (args.kind) {
|
|
157
|
+
rows = rows.filter((row) => row.kind === args.kind);
|
|
158
|
+
}
|
|
159
|
+
if (args.status) {
|
|
160
|
+
rows = rows.filter((row) => row.status === args.status);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
rows.sort((a, b) => a.startTime - b.startTime);
|
|
164
|
+
const page = rows.slice(0, limit);
|
|
165
|
+
const nextAfterStartTime =
|
|
166
|
+
rows.length > limit ? page[page.length - 1]?.startTime : undefined;
|
|
167
|
+
|
|
168
|
+
return { page, nextAfterStartTime };
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Test helper for creating a span with full control over fields including status, startTime, and endTime.
|
|
174
|
+
*/
|
|
175
|
+
export const internalCreateSpan = mutation({
|
|
176
|
+
args: {
|
|
177
|
+
executionId: v.string(),
|
|
178
|
+
traceId: v.string(),
|
|
179
|
+
spanId: v.optional(v.string()),
|
|
180
|
+
parentSpanId: v.optional(v.string()),
|
|
181
|
+
stepNumber: v.optional(v.number()),
|
|
182
|
+
name: v.string(),
|
|
183
|
+
kind: vSpanKind,
|
|
184
|
+
status: vSpanStatus,
|
|
185
|
+
startTime: v.number(),
|
|
186
|
+
endTime: v.optional(v.number()),
|
|
187
|
+
attempt: v.optional(v.number()),
|
|
188
|
+
input: v.optional(v.any()),
|
|
189
|
+
output: v.optional(v.any()),
|
|
190
|
+
error: v.optional(v.string()),
|
|
191
|
+
attributes: v.optional(v.any()),
|
|
192
|
+
},
|
|
193
|
+
returns: v.object({ spanId: v.string() }),
|
|
194
|
+
handler: async (ctx, args) => {
|
|
195
|
+
const spanId = args.spanId ?? generateSpanId();
|
|
196
|
+
|
|
197
|
+
const inputPayload =
|
|
198
|
+
args.input === undefined
|
|
199
|
+
? { size: 0, ref: undefined, preview: undefined }
|
|
200
|
+
: await persistBoundedPayload(ctx, "spanInput", args.input);
|
|
201
|
+
|
|
202
|
+
const outputPayload =
|
|
203
|
+
args.output === undefined
|
|
204
|
+
? { size: undefined, ref: undefined, preview: undefined }
|
|
205
|
+
: await persistBoundedPayload(ctx, "spanOutput", args.output);
|
|
206
|
+
|
|
207
|
+
const attrPayload =
|
|
208
|
+
args.attributes === undefined
|
|
209
|
+
? undefined
|
|
210
|
+
: await persistBoundedPayload(
|
|
211
|
+
ctx,
|
|
212
|
+
"logData",
|
|
213
|
+
args.attributes,
|
|
214
|
+
MAX_SPAN_ATTR_BYTES,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
await ctx.db.insert("spans", {
|
|
218
|
+
executionId: args.executionId,
|
|
219
|
+
traceId: args.traceId,
|
|
220
|
+
spanId,
|
|
221
|
+
parentSpanId: args.parentSpanId,
|
|
222
|
+
stepNumber: args.stepNumber,
|
|
223
|
+
name: args.name,
|
|
224
|
+
kind: args.kind,
|
|
225
|
+
status: args.status,
|
|
226
|
+
startTime: args.startTime,
|
|
227
|
+
endTime: args.endTime,
|
|
228
|
+
attempt: args.attempt,
|
|
229
|
+
attributes: attrPayload?.preview,
|
|
230
|
+
inputRef: inputPayload.ref,
|
|
231
|
+
inputPreview: inputPayload.preview,
|
|
232
|
+
inputSize: inputPayload.size,
|
|
233
|
+
outputRef: outputPayload.ref,
|
|
234
|
+
outputPreview: outputPayload.preview,
|
|
235
|
+
outputSize: outputPayload.size,
|
|
236
|
+
error: args.error,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return { spanId };
|
|
240
|
+
},
|
|
241
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
generateSpanId,
|
|
4
|
+
generateTraceId,
|
|
5
|
+
toPreview,
|
|
6
|
+
valueSize,
|
|
7
|
+
shouldOffload,
|
|
8
|
+
} from "./utils.js";
|
|
9
|
+
|
|
10
|
+
describe("component utils", () => {
|
|
11
|
+
it("generates stable-length ids", () => {
|
|
12
|
+
expect(generateSpanId()).toHaveLength(16);
|
|
13
|
+
expect(generateTraceId()).toHaveLength(32);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("calculates value size", () => {
|
|
17
|
+
expect(valueSize({ a: 1 })).toBeGreaterThan(0);
|
|
18
|
+
expect(valueSize(undefined)).toBeGreaterThanOrEqual(0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("truncates large previews deterministically", () => {
|
|
22
|
+
const value = { data: "x".repeat(10_000) };
|
|
23
|
+
const preview = toPreview(value, 64) as any;
|
|
24
|
+
expect(preview._truncated).toBe(true);
|
|
25
|
+
expect(typeof preview.preview).toBe("string");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("identifies offload threshold", () => {
|
|
29
|
+
expect(shouldOffload("x".repeat(10), 1_000)).toBe(false);
|
|
30
|
+
expect(shouldOffload("x".repeat(5_000), 100)).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { getConvexSize, type Value } from "convex/values";
|
|
2
|
+
import {
|
|
3
|
+
MAX_INLINE_PAYLOAD_BYTES,
|
|
4
|
+
MAX_PREVIEW_STRING_BYTES,
|
|
5
|
+
} from "../shared/constants.js";
|
|
6
|
+
|
|
7
|
+
export function nowTs() {
|
|
8
|
+
return Date.now();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function randomHex(size = 16): string {
|
|
12
|
+
const bytes = new Uint8Array(size);
|
|
13
|
+
crypto.getRandomValues(bytes);
|
|
14
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function generateTraceId(): string {
|
|
18
|
+
return randomHex(16);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function generateSpanId(): string {
|
|
22
|
+
return randomHex(8);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type TruncatedPreview = {
|
|
26
|
+
_truncated: true;
|
|
27
|
+
preview: string;
|
|
28
|
+
originalSize?: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function valueSize(value: unknown): number {
|
|
32
|
+
try {
|
|
33
|
+
// Boundary note: getConvexSize validates Convex Value shape at runtime and
|
|
34
|
+
// throws for unsupported values, which we convert to size=0.
|
|
35
|
+
return getConvexSize((value ?? null) as Value);
|
|
36
|
+
} catch {
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function toPreview(
|
|
42
|
+
value: unknown,
|
|
43
|
+
maxBytes = MAX_PREVIEW_STRING_BYTES,
|
|
44
|
+
): Value | TruncatedPreview | undefined {
|
|
45
|
+
if (value === undefined) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let json: string | undefined;
|
|
50
|
+
try {
|
|
51
|
+
json = JSON.stringify(value);
|
|
52
|
+
} catch {
|
|
53
|
+
return { _truncated: true, preview: "<non-serializable>" };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (json === undefined) {
|
|
57
|
+
return { _truncated: true, preview: "<non-serializable>" };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (json.length <= maxBytes) {
|
|
61
|
+
return value as Value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
_truncated: true,
|
|
66
|
+
preview: json.slice(0, maxBytes),
|
|
67
|
+
originalSize: json.length,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function shouldOffload(value: unknown, inlineLimit = MAX_INLINE_PAYLOAD_BYTES) {
|
|
72
|
+
return valueSize(value) > inlineLimit;
|
|
73
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_EXECUTIONS_PAGE_SIZE,
|
|
4
|
+
MAX_INLINE_PAYLOAD_BYTES,
|
|
5
|
+
MAX_LOG_DATA_BYTES,
|
|
6
|
+
MAX_PAGE_SIZE,
|
|
7
|
+
} from "./constants.js";
|
|
8
|
+
|
|
9
|
+
describe("shared constants", () => {
|
|
10
|
+
it("enforces sane bounds", () => {
|
|
11
|
+
expect(MAX_INLINE_PAYLOAD_BYTES).toBeGreaterThan(MAX_LOG_DATA_BYTES);
|
|
12
|
+
expect(MAX_PAGE_SIZE).toBeGreaterThan(DEFAULT_EXECUTIONS_PAGE_SIZE);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const COMPONENT_NAME = "effectWorkflows";
|
|
2
|
+
|
|
3
|
+
export const MAX_INLINE_PAYLOAD_BYTES = 256_000;
|
|
4
|
+
export const MAX_PREVIEW_STRING_BYTES = 4_096;
|
|
5
|
+
export const MAX_LOG_DATA_BYTES = 64_000;
|
|
6
|
+
export const MAX_SPAN_ATTR_BYTES = 64_000;
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_EXECUTIONS_PAGE_SIZE = 50;
|
|
9
|
+
export const DEFAULT_STEPS_PAGE_SIZE = 100;
|
|
10
|
+
export const DEFAULT_SPANS_PAGE_SIZE = 200;
|
|
11
|
+
export const DEFAULT_LOGS_PAGE_SIZE = 200;
|
|
12
|
+
|
|
13
|
+
export const MAX_PAGE_SIZE = 500;
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_RETENTION_DAYS = 30;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { v, type Infer } from "convex/values";
|
|
2
|
+
|
|
3
|
+
export const vExecutionStatus = v.union(
|
|
4
|
+
v.literal("pending"),
|
|
5
|
+
v.literal("running"),
|
|
6
|
+
v.literal("suspended"),
|
|
7
|
+
v.literal("completed"),
|
|
8
|
+
v.literal("failed"),
|
|
9
|
+
v.literal("interrupted"),
|
|
10
|
+
);
|
|
11
|
+
export type ExecutionStatus = Infer<typeof vExecutionStatus>;
|
|
12
|
+
|
|
13
|
+
export const vStepKind = v.union(
|
|
14
|
+
v.literal("activity"),
|
|
15
|
+
v.literal("deferred"),
|
|
16
|
+
v.literal("clock"),
|
|
17
|
+
v.literal("nestedWorkflow"),
|
|
18
|
+
v.literal("system"),
|
|
19
|
+
);
|
|
20
|
+
export type StepKind = Infer<typeof vStepKind>;
|
|
21
|
+
|
|
22
|
+
export const vStepState = v.union(
|
|
23
|
+
v.literal("started"),
|
|
24
|
+
v.literal("suspended"),
|
|
25
|
+
v.literal("completed"),
|
|
26
|
+
v.literal("failed"),
|
|
27
|
+
v.literal("canceled"),
|
|
28
|
+
);
|
|
29
|
+
export type StepState = Infer<typeof vStepState>;
|
|
30
|
+
|
|
31
|
+
// valuePreview intentionally remains v.any(): previews can be raw inline values
|
|
32
|
+
// or truncated preview envelopes for arbitrary user payloads.
|
|
33
|
+
export const vResultEnvelope = v.union(
|
|
34
|
+
v.object({
|
|
35
|
+
kind: v.literal("success"),
|
|
36
|
+
valueRef: v.optional(v.id("payloads")),
|
|
37
|
+
valuePreview: v.optional(v.any()),
|
|
38
|
+
}),
|
|
39
|
+
v.object({
|
|
40
|
+
kind: v.literal("failed"),
|
|
41
|
+
error: v.string(),
|
|
42
|
+
valueRef: v.optional(v.id("payloads")),
|
|
43
|
+
valuePreview: v.optional(v.any()),
|
|
44
|
+
}),
|
|
45
|
+
v.object({ kind: v.literal("canceled") }),
|
|
46
|
+
);
|
|
47
|
+
export type ResultEnvelope = Infer<typeof vResultEnvelope>;
|
|
48
|
+
|
|
49
|
+
export const vDeterminismSignature = v.object({
|
|
50
|
+
kind: vStepKind,
|
|
51
|
+
opName: v.string(),
|
|
52
|
+
attempt: v.optional(v.number()),
|
|
53
|
+
scheduleMeta: v.optional(v.any()),
|
|
54
|
+
schemaVersion: v.optional(v.number()),
|
|
55
|
+
});
|
|
56
|
+
export type DeterminismSignature = Infer<typeof vDeterminismSignature>;
|
|
57
|
+
|
|
58
|
+
export const vSpanKind = v.union(
|
|
59
|
+
v.literal("workflow"),
|
|
60
|
+
v.literal("activity"),
|
|
61
|
+
v.literal("deferred"),
|
|
62
|
+
v.literal("clock"),
|
|
63
|
+
v.literal("system"),
|
|
64
|
+
);
|
|
65
|
+
export type SpanKind = Infer<typeof vSpanKind>;
|
|
66
|
+
|
|
67
|
+
export const vSpanStatus = v.union(v.literal("started"), v.literal("ended"));
|
|
68
|
+
export type SpanStatus = Infer<typeof vSpanStatus>;
|
|
69
|
+
|
|
70
|
+
export const vLogLevel = v.union(
|
|
71
|
+
v.literal("debug"),
|
|
72
|
+
v.literal("info"),
|
|
73
|
+
v.literal("warn"),
|
|
74
|
+
v.literal("error"),
|
|
75
|
+
);
|
|
76
|
+
export type LogLevel = Infer<typeof vLogLevel>;
|
|
77
|
+
|
|
78
|
+
export const vLogSource = v.union(
|
|
79
|
+
v.literal("workflow"),
|
|
80
|
+
v.literal("activity"),
|
|
81
|
+
v.literal("engine"),
|
|
82
|
+
v.literal("system"),
|
|
83
|
+
);
|
|
84
|
+
export type LogSource = Infer<typeof vLogSource>;
|
|
85
|
+
|
|
86
|
+
export const vSortOrder = v.union(v.literal("asc"), v.literal("desc"));
|
|
87
|
+
export type SortOrder = Infer<typeof vSortOrder>;
|
|
88
|
+
|
|
89
|
+
export const vPayloadKind = v.union(
|
|
90
|
+
v.literal("executionPayload"),
|
|
91
|
+
v.literal("stepInput"),
|
|
92
|
+
v.literal("stepOutput"),
|
|
93
|
+
v.literal("deferredExit"),
|
|
94
|
+
v.literal("spanInput"),
|
|
95
|
+
v.literal("spanOutput"),
|
|
96
|
+
v.literal("logData"),
|
|
97
|
+
);
|
|
98
|
+
export type PayloadKind = Infer<typeof vPayloadKind>;
|
package/src/test.d.ts
ADDED
package/src/test.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
import type { TestConvex } from "convex-test";
|
|
3
|
+
import type { GenericSchema, SchemaDefinition } from "convex/server";
|
|
4
|
+
import workpool from "@convex-dev/workpool/test";
|
|
5
|
+
import schema from "./component/schema.js";
|
|
6
|
+
|
|
7
|
+
const modules = import.meta.glob("./component/**/*.ts");
|
|
8
|
+
|
|
9
|
+
export function register(
|
|
10
|
+
t: TestConvex<SchemaDefinition<GenericSchema, boolean>>,
|
|
11
|
+
name: string = "effectWorkflows",
|
|
12
|
+
) {
|
|
13
|
+
t.registerComponent(name, schema, modules);
|
|
14
|
+
workpool.register(t);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default { register, schema, modules };
|