@samebase/alchemy-convex 0.6.1 → 0.8.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 +72 -13
- package/lib/index.d.ts +97 -11
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +260 -62
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Deployment.ts +97 -18
- package/src/Providers.ts +2 -2
- package/src/Push.ts +58 -48
- package/src/RunRecord.ts +208 -0
- package/src/State.ts +50 -6
- package/src/index.ts +9 -0
- package/src/store/convex/state.ts +48 -2
- package/src/store/convex/version.ts +8 -4
package/src/State.ts
CHANGED
|
@@ -23,13 +23,19 @@
|
|
|
23
23
|
// The run whose project is not the oldest deletes its own, before it writes
|
|
24
24
|
// anything, and uses the oldest: both runs end on one store.
|
|
25
25
|
//
|
|
26
|
-
// The function names are a wire contract: Samebase reads `stacks
|
|
27
|
-
// `getOutput` by name, and an older client calls an older
|
|
28
|
-
// name is never renamed or removed. STORE_VERSION rises when
|
|
29
|
-
// changes in a way that an older client cannot use,
|
|
30
|
-
// pushes an older store over a newer one.
|
|
26
|
+
// The function names are a wire contract: Samebase reads `stacks`,
|
|
27
|
+
// `getOutput`, and `readStack` by name, and an older client calls an older
|
|
28
|
+
// set of names. A name is never renamed or removed. STORE_VERSION rises when
|
|
29
|
+
// a function is added or changes in a way that an older client cannot use,
|
|
30
|
+
// and a client never pushes an older store over a newer one.
|
|
31
|
+
//
|
|
32
|
+
// The stage output that `setOutput` stores is the stack's output plus the
|
|
33
|
+
// run record of src/RunRecord.ts under the key `samebase.run`: the
|
|
34
|
+
// repository, stack file, branch, pull request, commit, and run that wrote
|
|
35
|
+
// the stage. `getOutput` gives the document back as stored.
|
|
31
36
|
import { dirname, join } from "node:path";
|
|
32
37
|
import { fileURLToPath } from "node:url";
|
|
38
|
+
import * as Clock from "effect/Clock";
|
|
33
39
|
import * as Effect from "effect/Effect";
|
|
34
40
|
import * as Layer from "effect/Layer";
|
|
35
41
|
import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
|
|
@@ -70,6 +76,7 @@ import {
|
|
|
70
76
|
PUSH_KEY_ACTIONS,
|
|
71
77
|
withPushKey,
|
|
72
78
|
} from "./Push.ts";
|
|
79
|
+
import { type RunRecord, runRecord, withRunRecord } from "./RunRecord.ts";
|
|
73
80
|
import { STORE_VERSION } from "./store/convex/version.ts";
|
|
74
81
|
|
|
75
82
|
/** The name of the state project in each team. */
|
|
@@ -241,6 +248,8 @@ export interface StoreOptions {
|
|
|
241
248
|
readonly scope: Scope.Scope;
|
|
242
249
|
/** The functions directory that an outdated store gets: {@link storeDirectory}. */
|
|
243
250
|
readonly store: string;
|
|
251
|
+
/** The record of this run, which `setOutput` adds to the stage output with the time of the write, or undefined for a run without one. */
|
|
252
|
+
readonly run: RunRecord | undefined;
|
|
244
253
|
}
|
|
245
254
|
|
|
246
255
|
/**
|
|
@@ -261,6 +270,7 @@ const pushStore = (
|
|
|
261
270
|
push(options.spawner, key, {
|
|
262
271
|
deployment: deployment.name,
|
|
263
272
|
url: deployment.deploymentUrl,
|
|
273
|
+
rootDir: join(options.store, ".."),
|
|
264
274
|
source: {
|
|
265
275
|
path: options.store,
|
|
266
276
|
command: [...cli, "deploy", "--typecheck", "disable", "--codegen", "disable"],
|
|
@@ -385,6 +395,21 @@ export const makeConvexState = (options: StoreOptions) =>
|
|
|
385
395
|
}
|
|
386
396
|
|
|
387
397
|
const remote = remoteOf(functions);
|
|
398
|
+
/** The stack's output plus the record, when the run has one and the output has a place for it. */
|
|
399
|
+
const documentOf = (request: { stack: string; stage: string; value: unknown }) =>
|
|
400
|
+
Effect.gen(function* () {
|
|
401
|
+
const { run } = options;
|
|
402
|
+
if (run === undefined) return request.value;
|
|
403
|
+
const recordedAt = new Date(yield* Clock.currentTimeMillis).toISOString();
|
|
404
|
+
const document = withRunRecord(request.value, { ...run, recordedAt });
|
|
405
|
+
if (document === undefined) {
|
|
406
|
+
yield* Effect.logWarning(
|
|
407
|
+
`Convex state: the output of ${request.stack}/${request.stage} is not a plain object, so it carries no run record.`,
|
|
408
|
+
);
|
|
409
|
+
return request.value;
|
|
410
|
+
}
|
|
411
|
+
return document;
|
|
412
|
+
});
|
|
388
413
|
const service: StateService = {
|
|
389
414
|
id: "convex",
|
|
390
415
|
getVersion: () => Effect.succeed(STATE_STORE_VERSION),
|
|
@@ -397,7 +422,13 @@ export const makeConvexState = (options: StoreOptions) =>
|
|
|
397
422
|
set: (request) => remote.set(request).pipe(Effect.as(request.value)),
|
|
398
423
|
delete: (request) => remote.delete(request).pipe(Effect.asVoid),
|
|
399
424
|
deleteStack: (request) => remote.deleteStack(request).pipe(Effect.asVoid),
|
|
400
|
-
|
|
425
|
+
// The document is the stack's output plus the record under `samebase.run`.
|
|
426
|
+
setOutput: (request) =>
|
|
427
|
+
Effect.gen(function* () {
|
|
428
|
+
const value = yield* documentOf(request);
|
|
429
|
+
yield* remote.setOutput({ stack: request.stack, stage: request.stage, value });
|
|
430
|
+
return value;
|
|
431
|
+
}),
|
|
401
432
|
};
|
|
402
433
|
return service;
|
|
403
434
|
});
|
|
@@ -425,12 +456,25 @@ export const state = (
|
|
|
425
456
|
const api = yield* ManagementApi.pipe(
|
|
426
457
|
Effect.provide(ManagementApiLive().pipe(Layer.provide(fromConfig()))),
|
|
427
458
|
);
|
|
459
|
+
// The record is derived once, before the first read. A checkout
|
|
460
|
+
// that gives none, such as a tarball without git, a checkout
|
|
461
|
+
// without origin, or a detached HEAD, still reads, plans, and
|
|
462
|
+
// destroys: its outputs are written without the record, and the
|
|
463
|
+
// log says why.
|
|
464
|
+
const run = yield* runRecord({ cwd: process.cwd(), env: process.env }).pipe(
|
|
465
|
+
Effect.catch((error) =>
|
|
466
|
+
Effect.logWarning(
|
|
467
|
+
`Convex state: the stage outputs of this run carry no record. ${error.message}`,
|
|
468
|
+
).pipe(Effect.as(undefined)),
|
|
469
|
+
),
|
|
470
|
+
);
|
|
428
471
|
return yield* makeConvexState({
|
|
429
472
|
team: options.team,
|
|
430
473
|
api,
|
|
431
474
|
spawner,
|
|
432
475
|
scope,
|
|
433
476
|
store: storeDirectory(),
|
|
477
|
+
run,
|
|
434
478
|
});
|
|
435
479
|
}).pipe(Effect.provideContext(context), Effect.orDie),
|
|
436
480
|
);
|
package/src/index.ts
CHANGED
|
@@ -8,10 +8,12 @@ export {
|
|
|
8
8
|
fromValues,
|
|
9
9
|
} from "./Credentials.ts";
|
|
10
10
|
export {
|
|
11
|
+
AbsoluteRootDir,
|
|
11
12
|
AmbiguousDeployment,
|
|
12
13
|
Deployment,
|
|
13
14
|
type DeploymentAttributes,
|
|
14
15
|
DeploymentIdentityChange,
|
|
16
|
+
type DeploymentInput,
|
|
15
17
|
type DeploymentProps,
|
|
16
18
|
ProductionDeploymentNotFound,
|
|
17
19
|
} from "./Deployment.ts";
|
|
@@ -28,6 +30,13 @@ export {
|
|
|
28
30
|
ProjectTeamChange,
|
|
29
31
|
} from "./Project.ts";
|
|
30
32
|
export { Providers, providers } from "./Providers.ts";
|
|
33
|
+
export {
|
|
34
|
+
RUN_RECORD_KEY,
|
|
35
|
+
type RunRecord,
|
|
36
|
+
RunRecordError,
|
|
37
|
+
runRecord,
|
|
38
|
+
withRunRecord,
|
|
39
|
+
} from "./RunRecord.ts";
|
|
31
40
|
export { state, STORE_PROJECT_NAME } from "./State.ts";
|
|
32
41
|
export {
|
|
33
42
|
CodePushFailed,
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
//
|
|
8
8
|
// A stage holds one row for each resource of its stack, so the reads of one
|
|
9
9
|
// stage collect all rows. The function names are a wire contract: Samebase
|
|
10
|
-
// reads `stacks` and `
|
|
11
|
-
// set of names. Add functions; never rename or remove one.
|
|
10
|
+
// reads `stacks`, `getOutput`, and `readStack` by name, and an older client
|
|
11
|
+
// calls an older set of names. Add functions; never rename or remove one.
|
|
12
12
|
import { v } from "convex/values";
|
|
13
13
|
import type { QueryCtx } from "./_generated/server.js";
|
|
14
14
|
import { internalMutation, internalQuery } from "./_generated/server.js";
|
|
@@ -217,3 +217,49 @@ export const setOutput = internalMutation({
|
|
|
217
217
|
return null;
|
|
218
218
|
},
|
|
219
219
|
});
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* One call for a reader: every stage of a stack in name order, each with its
|
|
223
|
+
* resources in fqn order and its output, as the JSON texts that the rows
|
|
224
|
+
* hold. A stage with an output and no resource is listed with an empty
|
|
225
|
+
* resource list; a stage with resources and no output with `output: null`.
|
|
226
|
+
* One function reads at most 16 MiB (Convex limit): a stack whose rows go
|
|
227
|
+
* beyond that fails here and is read with `stages`, `list`, `get`, and
|
|
228
|
+
* `getOutput` instead. Since store version 2.
|
|
229
|
+
*/
|
|
230
|
+
export const readStack = internalQuery({
|
|
231
|
+
args: { stack: v.string() },
|
|
232
|
+
returns: v.object({
|
|
233
|
+
stages: v.array(
|
|
234
|
+
v.object({
|
|
235
|
+
stage: v.string(),
|
|
236
|
+
resources: v.array(v.object({ fqn: v.string(), value: v.string() })),
|
|
237
|
+
output: v.union(v.string(), v.null()),
|
|
238
|
+
}),
|
|
239
|
+
),
|
|
240
|
+
}),
|
|
241
|
+
handler: async (ctx, args) => {
|
|
242
|
+
// Both indexes order by stack, then stage, then (for resources) fqn.
|
|
243
|
+
const resources = await ctx.db
|
|
244
|
+
.query("resources")
|
|
245
|
+
.withIndex("by_fqn", (q) => q.eq("stack", args.stack))
|
|
246
|
+
.collect();
|
|
247
|
+
const outputs = await ctx.db
|
|
248
|
+
.query("outputs")
|
|
249
|
+
.withIndex("by_stage", (q) => q.eq("stack", args.stack))
|
|
250
|
+
.collect();
|
|
251
|
+
const stages = union(
|
|
252
|
+
[...new Set(resources.map((row) => row.stage))],
|
|
253
|
+
[...new Set(outputs.map((row) => row.stage))],
|
|
254
|
+
);
|
|
255
|
+
return {
|
|
256
|
+
stages: stages.map((stage) => ({
|
|
257
|
+
stage,
|
|
258
|
+
resources: resources
|
|
259
|
+
.filter((row) => row.stage === stage)
|
|
260
|
+
.map((row) => ({ fqn: row.fqn, value: row.value })),
|
|
261
|
+
output: outputs.find((row) => row.stage === stage)?.value ?? null,
|
|
262
|
+
})),
|
|
263
|
+
};
|
|
264
|
+
},
|
|
265
|
+
});
|
|
@@ -1,6 +1,10 @@
|
|
|
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
|
-
// older one or has no `version` yet. Raise it when a function
|
|
4
|
-
// that an older client cannot use. Never rename or remove a
|
|
5
|
-
// older client, and Samebase, call the store by these names.
|
|
6
|
-
|
|
3
|
+
// older one or has no `version` yet. Raise it when a function is added or
|
|
4
|
+
// changes in a way that an older client cannot use. Never rename or remove a
|
|
5
|
+
// function: an older client, and Samebase, call the store by these names.
|
|
6
|
+
//
|
|
7
|
+
// 1: `version`, `stacks`, `stages`, `list`, `get`, `replaced`, `getOutput`,
|
|
8
|
+
// `set`, `remove`, `removeStack`, `setOutput` (0.6.0).
|
|
9
|
+
// 2: `readStack` (0.7.0).
|
|
10
|
+
export const STORE_VERSION = 2;
|