@samebase/alchemy-convex 0.10.0 → 0.12.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.
@@ -2,17 +2,21 @@
2
2
  // Convex team, pushed by Convex.state() from this directory. These raw state
3
3
  // functions are internal. reader.ts exposes authenticated, redacted reads.
4
4
  //
5
- // A stage holds one row for each resource of its stack, so the reads of one
6
- // stage collect all rows. Samebase reads `stacks`, `getOutput`, and
7
- // `readStack` by name: a renamed function is a version bump of the store and
8
- // of Samebase.
5
+ // The rows are in the alchemyState component (src/store/component). Each
6
+ // function here forwards to the component function of the same name, with the
7
+ // same results; an absent `run` or `stage` goes to the component as null.
8
+ // These names and arguments are the wire contract of STORE_VERSION. Samebase
9
+ // reads `stacks`, `getOutput`, and `readStack` by name: a renamed function is
10
+ // a version bump of the store and of Samebase.
9
11
  import { v } from "convex/values";
10
- import type { MutationCtx, QueryCtx } from "./_generated/server.js";
12
+ import { runRecord } from "../component/runRecord.ts";
13
+ import { stackReply } from "../component/state.ts";
14
+ import { components } from "./_generated/api.js";
11
15
  import { internalMutation, internalQuery } from "./_generated/server.js";
12
- import { runRecord } from "./runRecord.ts";
13
- import type { RunRecord } from "../../RunRecord.ts";
14
16
  import { STORE_VERSION } from "./version.ts";
15
17
 
18
+ const state = components.alchemyState.state;
19
+
16
20
  /** The wire version of this store, compared with the client's STORE_VERSION. */
17
21
  export const version = internalQuery({
18
22
  args: {},
@@ -20,272 +24,100 @@ export const version = internalQuery({
20
24
  handler: async () => STORE_VERSION,
21
25
  });
22
26
 
23
- const stage = { stack: v.string(), stage: v.string() };
24
- const resource = { ...stage, fqn: v.string() };
25
-
26
- const rowOf = (ctx: QueryCtx, args: { stack: string; stage: string; fqn: string }) =>
27
- ctx.db
28
- .query("resources")
29
- .withIndex("by_fqn", (q) =>
30
- q.eq("stack", args.stack).eq("stage", args.stage).eq("fqn", args.fqn),
31
- )
32
- .unique();
33
-
34
- const rowsOf = (ctx: QueryCtx, args: { stack: string; stage: string }) =>
35
- ctx.db
36
- .query("resources")
37
- .withIndex("by_fqn", (q) => q.eq("stack", args.stack).eq("stage", args.stage))
38
- .collect();
39
-
40
- const outputOf = (ctx: QueryCtx, args: { stack: string; stage: string }) =>
41
- ctx.db
42
- .query("outputs")
43
- .withIndex("by_stage", (q) => q.eq("stack", args.stack).eq("stage", args.stage))
44
- .unique();
45
-
46
- /** A resource write and its current run become visible in one transaction. */
47
- const writeCurrentRun = async (
48
- ctx: MutationCtx,
49
- args: { stack: string; stage: string; run?: RunRecord | undefined },
50
- ) => {
51
- const output = await outputOf(ctx, args);
52
- // A write without a run record clears the record of the previous run.
53
- if (output !== null) await ctx.db.patch("outputs", output._id, { currentRun: args.run });
54
- else if (args.run !== undefined)
55
- await ctx.db.insert("outputs", { stack: args.stack, stage: args.stage, currentRun: args.run });
56
- };
57
-
58
- /** The sorted union of two lists of names. */
59
- const union = (a: readonly string[], b: readonly string[]) => [...new Set([...a, ...b])].sort();
60
-
61
- /**
62
- * The distinct names of one table in index order, with one index read for
63
- * each name: `first(after)` is the first row whose name is above `after`.
64
- */
65
- const distinct = async (first: (after: string | undefined) => Promise<string | null>) => {
66
- const names: string[] = [];
67
- for (;;) {
68
- const next = await first(names.at(-1));
69
- if (next === null) return names;
70
- names.push(next);
71
- }
72
- };
73
-
74
27
  /** Stacks with at least one resource or one output, sorted. */
75
28
  export const stacks = internalQuery({
76
29
  args: {},
77
30
  returns: v.array(v.string()),
78
- handler: async (ctx) =>
79
- union(
80
- await distinct(
81
- async (after) =>
82
- (
83
- await ctx.db
84
- .query("resources")
85
- .withIndex("by_fqn", (q) => (after === undefined ? q : q.gt("stack", after)))
86
- .first()
87
- )?.stack ?? null,
88
- ),
89
- await distinct(
90
- async (after) =>
91
- (
92
- await ctx.db
93
- .query("outputs")
94
- .withIndex("by_stage", (q) => (after === undefined ? q : q.gt("stack", after)))
95
- .first()
96
- )?.stack ?? null,
97
- ),
98
- ),
31
+ handler: (ctx) => ctx.runQuery(state.stacks, {}),
99
32
  });
100
33
 
101
34
  /** Stages of a stack with at least one resource or one output, sorted. */
102
35
  export const stages = internalQuery({
103
36
  args: { stack: v.string() },
104
37
  returns: v.array(v.string()),
105
- handler: async (ctx, args) =>
106
- union(
107
- await distinct(
108
- async (after) =>
109
- (
110
- await ctx.db
111
- .query("resources")
112
- .withIndex("by_fqn", (q) =>
113
- after === undefined
114
- ? q.eq("stack", args.stack)
115
- : q.eq("stack", args.stack).gt("stage", after),
116
- )
117
- .first()
118
- )?.stage ?? null,
119
- ),
120
- await distinct(
121
- async (after) =>
122
- (
123
- await ctx.db
124
- .query("outputs")
125
- .withIndex("by_stage", (q) =>
126
- after === undefined
127
- ? q.eq("stack", args.stack)
128
- : q.eq("stack", args.stack).gt("stage", after),
129
- )
130
- .first()
131
- )?.stage ?? null,
132
- ),
133
- ),
38
+ handler: (ctx, args) => ctx.runQuery(state.stages, { stack: args.stack }),
134
39
  });
135
40
 
136
41
  export const list = internalQuery({
137
- args: stage,
42
+ args: { stack: v.string(), stage: v.string() },
138
43
  returns: v.array(v.string()),
139
- handler: async (ctx, args) => (await rowsOf(ctx, args)).map((row) => row.fqn),
44
+ handler: (ctx, args) => ctx.runQuery(state.list, { stack: args.stack, stage: args.stage }),
140
45
  });
141
46
 
142
47
  export const get = internalQuery({
143
- args: resource,
48
+ args: { stack: v.string(), stage: v.string(), fqn: v.string() },
144
49
  returns: v.union(v.string(), v.null()),
145
- handler: async (ctx, args) => (await rowOf(ctx, args))?.value ?? null,
50
+ handler: (ctx, args) =>
51
+ ctx.runQuery(state.get, { stack: args.stack, stage: args.stage, fqn: args.fqn }),
146
52
  });
147
53
 
148
54
  /** Resources whose state has `status: "replaced"`. */
149
55
  export const replaced = internalQuery({
150
- args: stage,
56
+ args: { stack: v.string(), stage: v.string() },
151
57
  returns: v.array(v.string()),
152
- handler: async (ctx, args) =>
153
- (await rowsOf(ctx, args))
154
- .map((row) => row.value)
155
- .filter((value) => {
156
- const parsed: unknown = JSON.parse(value);
157
- return (
158
- typeof parsed === "object" &&
159
- parsed !== null &&
160
- "status" in parsed &&
161
- parsed.status === "replaced"
162
- );
163
- }),
58
+ handler: (ctx, args) => ctx.runQuery(state.replaced, { stack: args.stack, stage: args.stage }),
164
59
  });
165
60
 
166
61
  export const set = internalMutation({
167
- args: { ...resource, value: v.string(), run: v.optional(runRecord) },
168
- returns: v.null(),
169
- handler: async (ctx, args) => {
170
- const row = await rowOf(ctx, args);
171
- if (row === null)
172
- await ctx.db.insert("resources", {
173
- stack: args.stack,
174
- stage: args.stage,
175
- fqn: args.fqn,
176
- value: args.value,
177
- });
178
- else await ctx.db.patch("resources", row._id, { value: args.value });
179
- await writeCurrentRun(ctx, args);
180
- return null;
62
+ args: {
63
+ stack: v.string(),
64
+ stage: v.string(),
65
+ fqn: v.string(),
66
+ value: v.string(),
67
+ run: v.optional(runRecord),
181
68
  },
69
+ returns: v.null(),
70
+ handler: (ctx, args) =>
71
+ ctx.runMutation(state.set, {
72
+ stack: args.stack,
73
+ stage: args.stage,
74
+ fqn: args.fqn,
75
+ value: args.value,
76
+ run: args.run ?? null,
77
+ }),
182
78
  });
183
79
 
184
80
  export const remove = internalMutation({
185
- args: { ...resource, run: v.optional(runRecord) },
81
+ args: { stack: v.string(), stage: v.string(), fqn: v.string(), run: v.optional(runRecord) },
186
82
  returns: v.null(),
187
- handler: async (ctx, args) => {
188
- const row = await rowOf(ctx, args);
189
- if (row !== null) await ctx.db.delete("resources", row._id);
190
- await writeCurrentRun(ctx, args);
191
- return null;
192
- },
83
+ handler: (ctx, args) =>
84
+ ctx.runMutation(state.remove, {
85
+ stack: args.stack,
86
+ stage: args.stage,
87
+ fqn: args.fqn,
88
+ run: args.run ?? null,
89
+ }),
193
90
  });
194
91
 
195
92
  /** Deletes the resources and outputs of one stage, or of every stage of the stack. */
196
93
  export const removeStack = internalMutation({
197
94
  args: { stack: v.string(), stage: v.optional(v.string()) },
198
95
  returns: v.null(),
199
- handler: async (ctx, args) => {
200
- const { stack } = args;
201
- const resources = await ctx.db
202
- .query("resources")
203
- .withIndex("by_fqn", (q) =>
204
- args.stage === undefined
205
- ? q.eq("stack", stack)
206
- : q.eq("stack", stack).eq("stage", args.stage),
207
- )
208
- .collect();
209
- const outputs = await ctx.db
210
- .query("outputs")
211
- .withIndex("by_stage", (q) =>
212
- args.stage === undefined
213
- ? q.eq("stack", stack)
214
- : q.eq("stack", stack).eq("stage", args.stage),
215
- )
216
- .collect();
217
- for (const row of resources) await ctx.db.delete("resources", row._id);
218
- for (const row of outputs) await ctx.db.delete("outputs", row._id);
219
- return null;
220
- },
96
+ handler: (ctx, args) =>
97
+ ctx.runMutation(state.removeStack, { stack: args.stack, stage: args.stage ?? null }),
221
98
  });
222
99
 
223
100
  export const getOutput = internalQuery({
224
- args: stage,
101
+ args: { stack: v.string(), stage: v.string() },
225
102
  returns: v.union(v.string(), v.null()),
226
- handler: async (ctx, args) => (await outputOf(ctx, args))?.value ?? null,
103
+ handler: (ctx, args) => ctx.runQuery(state.getOutput, { stack: args.stack, stage: args.stage }),
227
104
  });
228
105
 
229
106
  export const setOutput = internalMutation({
230
- args: { ...stage, value: v.string(), run: v.optional(runRecord) },
107
+ args: { stack: v.string(), stage: v.string(), value: v.string(), run: v.optional(runRecord) },
231
108
  returns: v.null(),
232
- handler: async (ctx, args) => {
233
- const row = await outputOf(ctx, args);
234
- if (row === null)
235
- await ctx.db.insert("outputs", { stack: args.stack, stage: args.stage, value: args.value });
236
- else await ctx.db.patch("outputs", row._id, { value: args.value });
237
- await writeCurrentRun(ctx, args);
238
- return null;
239
- },
109
+ handler: (ctx, args) =>
110
+ ctx.runMutation(state.setOutput, {
111
+ stack: args.stack,
112
+ stage: args.stage,
113
+ value: args.value,
114
+ run: args.run ?? null,
115
+ }),
240
116
  });
241
117
 
242
- /**
243
- * One call for a reader: every stage of a stack in name order, each with its
244
- * resources in fqn order and its output, as the JSON texts that the rows
245
- * hold. A stage with an output and no resource is listed with an empty
246
- * resource list; a stage with resources and no output with `output: null`.
247
- * One function reads at most 16 MiB (Convex limit): a stack whose rows go
248
- * beyond that fails here and is read with `stages`, `list`, `get`, and
249
- * `getOutput` instead. Since store version 2.
250
- */
118
+ /** Every stage of a stack with its resources, output, and current record, in one call. Since store version 2. */
251
119
  export const readStack = internalQuery({
252
120
  args: { stack: v.string() },
253
- returns: v.object({
254
- stages: v.array(
255
- v.object({
256
- stage: v.string(),
257
- resources: v.array(v.object({ fqn: v.string(), value: v.string() })),
258
- output: v.union(v.string(), v.null()),
259
- record: v.union(runRecord, v.null()),
260
- }),
261
- ),
262
- }),
263
- handler: async (ctx, args) => {
264
- // Both indexes order by stack, then stage, then (for resources) fqn.
265
- const resources = await ctx.db
266
- .query("resources")
267
- .withIndex("by_fqn", (q) => q.eq("stack", args.stack))
268
- .collect();
269
- const outputs = await ctx.db
270
- .query("outputs")
271
- .withIndex("by_stage", (q) => q.eq("stack", args.stack))
272
- .collect();
273
- const stages = union(
274
- [...new Set(resources.map((row) => row.stage))],
275
- [...new Set(outputs.map((row) => row.stage))],
276
- );
277
- return {
278
- stages: stages.map((stage) => {
279
- const output = outputs.find((row) => row.stage === stage);
280
- return {
281
- stage,
282
- resources: resources
283
- .filter((row) => row.stage === stage)
284
- .map((row) => ({ fqn: row.fqn, value: row.value })),
285
- output: output?.value ?? null,
286
- record: output?.currentRun ?? null,
287
- };
288
- }),
289
- };
290
- },
121
+ returns: stackReply,
122
+ handler: (ctx, args) => ctx.runQuery(state.readStack, { stack: args.stack }),
291
123
  });
@@ -1,7 +1,8 @@
1
1
  // The wire version of the store: the `state:version` query answers it, and
2
2
  // Convex.state() pushes this directory when the store of the team answers an
3
3
  // older one or has no `version` yet, and fails the run on a newer one. Raise
4
- // it with each change of a function. The store and its readers (Samebase) are
4
+ // it with each change of a function of this app or of the alchemyState
5
+ // component (src/store/component). The store and its readers (Samebase) are
5
6
  // released and upgraded together: a renamed function is a version bump on
6
7
  // both sides.
7
8
  //
@@ -9,4 +10,5 @@
9
10
  // `set`, `remove`, `removeStack`, `setOutput` (0.6.0).
10
11
  // 2: `readStack` (0.7.0).
11
12
  // 3: authenticated subscriptions and current stage run records (0.9.0).
12
- export const STORE_VERSION = 3;
13
+ // 4: the store is a component (same functions).
14
+ export const STORE_VERSION = 4;
File without changes
File without changes