@samebase/alchemy-convex 0.25.0 → 0.25.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/package.json
CHANGED
|
@@ -12,6 +12,21 @@ export const runRecord = v.object({
|
|
|
12
12
|
recordedAt: v.string(),
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
+
type RunRecord = Infer<typeof runRecord>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* True when a write with `next` must replace `current`, the record of the stage. Records that differ
|
|
19
|
+
* only in `recordedAt`, the time of one write, name the same run. Two laptop deploys of one commit
|
|
20
|
+
* have no `runUrl`, so they are the same run too. A write without a record (`null`) clears one.
|
|
21
|
+
*/
|
|
22
|
+
export const runChanged = (current: RunRecord | undefined, next: RunRecord | null) => {
|
|
23
|
+
if (next === null || current === undefined) return next !== null || current !== undefined;
|
|
24
|
+
const other = new Map(Object.entries(next));
|
|
25
|
+
return Object.entries(current).some(
|
|
26
|
+
([field, value]) => field !== "recordedAt" && other.get(field) !== value,
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
15
30
|
/** Whether `value` passes `validator`. Only the kinds of {@link runRecord} pass; another kind fails. */
|
|
16
31
|
export const validate = <V extends GenericValidator>(
|
|
17
32
|
validator: V,
|
|
@@ -10,7 +10,7 @@ import type { MutationCtx, QueryCtx } from "./_generated/server.js";
|
|
|
10
10
|
// oxlint-disable-next-line samebase/no-raw-public-convex-function-builders -- Store entrypoints have no app wrapper; the reader and the deploy key enforce access.
|
|
11
11
|
import { mutation, query } from "./_generated/server.js";
|
|
12
12
|
import { generationOf } from "./keys.ts";
|
|
13
|
-
import { runRecord } from "./runRecord.ts";
|
|
13
|
+
import { runChanged, runRecord } from "./runRecord.ts";
|
|
14
14
|
|
|
15
15
|
const stage = { stack: v.string(), stage: v.string() };
|
|
16
16
|
const resource = { ...stage, fqn: v.string() };
|
|
@@ -43,17 +43,31 @@ const outputOf = (ctx: QueryCtx, args: { stack: string; stage: string }) =>
|
|
|
43
43
|
*/
|
|
44
44
|
const run = v.union(runRecord, v.null());
|
|
45
45
|
|
|
46
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* A resource write and its current run become visible in one transaction. The output row of the
|
|
48
|
+
* stage changes only when the run changes (see `runChanged`). Alchemy writes the resources of a
|
|
49
|
+
* deploy at the same time, and a write to the one output row of the stage made all of them conflict
|
|
50
|
+
* until Convex stopped the retries. A write of the same run only reads the row, and reads do not
|
|
51
|
+
* conflict. So `recordedAt` stays the time of the first write of the run, until `setOutput` writes
|
|
52
|
+
* it again.
|
|
53
|
+
*/
|
|
47
54
|
const writeCurrentRun = async (
|
|
48
55
|
ctx: MutationCtx,
|
|
49
56
|
args: { stack: string; stage: string; run: Infer<typeof run> },
|
|
50
57
|
) => {
|
|
51
58
|
const output = await outputOf(ctx, args);
|
|
59
|
+
if (output === null) {
|
|
60
|
+
if (args.run !== null)
|
|
61
|
+
await ctx.db.insert("outputs", {
|
|
62
|
+
stack: args.stack,
|
|
63
|
+
stage: args.stage,
|
|
64
|
+
currentRun: args.run,
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
52
68
|
// A write without a run record clears the record of the previous run.
|
|
53
|
-
if (output
|
|
69
|
+
if (runChanged(output.currentRun, args.run))
|
|
54
70
|
await ctx.db.patch("outputs", output._id, { currentRun: args.run ?? undefined });
|
|
55
|
-
else if (args.run !== null)
|
|
56
|
-
await ctx.db.insert("outputs", { stack: args.stack, stage: args.stage, currentRun: args.run });
|
|
57
71
|
};
|
|
58
72
|
|
|
59
73
|
/** The sorted union of two lists of names. */
|
|
@@ -228,10 +242,19 @@ export const setOutput = mutation({
|
|
|
228
242
|
returns: v.null(),
|
|
229
243
|
handler: async (ctx, args) => {
|
|
230
244
|
const row = await outputOf(ctx, args);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
245
|
+
// The output and the record of its run, with the time of this last write of the run.
|
|
246
|
+
if (row !== null)
|
|
247
|
+
await ctx.db.patch("outputs", row._id, {
|
|
248
|
+
value: args.value,
|
|
249
|
+
currentRun: args.run ?? undefined,
|
|
250
|
+
});
|
|
251
|
+
else
|
|
252
|
+
await ctx.db.insert("outputs", {
|
|
253
|
+
stack: args.stack,
|
|
254
|
+
stage: args.stage,
|
|
255
|
+
value: args.value,
|
|
256
|
+
...(args.run === null ? {} : { currentRun: args.run }),
|
|
257
|
+
});
|
|
235
258
|
return null;
|
|
236
259
|
},
|
|
237
260
|
});
|