@samebase/alchemy-convex 0.13.0 → 0.14.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/README.md +13 -3
- package/lib/index.d.ts +15 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +33 -6
- package/lib/index.js.map +1 -1
- package/package.json +13 -1
- package/src/State.ts +71 -18
- package/src/index.ts +1 -1
- package/src/store/authConfig.ts +17 -0
- package/src/store/component/reader.ts +1 -1
- package/src/store/component/schema.ts +1 -1
- package/src/store/component/state.ts +1 -1
- package/src/store/convex/auth.config.ts +2 -12
- package/src/store/convex/convex.config.ts +1 -1
- package/src/store/convex/reader.ts +3 -49
- package/src/store/convex/state.ts +21 -121
- package/src/store/convex/version.ts +10 -7
- package/src/store/functions.ts +214 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@samebase/alchemy-convex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "The Convex plane for Alchemy v2: projects, deployments, environment variables, and the push of the functions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"alchemy",
|
|
@@ -35,6 +35,18 @@
|
|
|
35
35
|
"./state/convex.config": {
|
|
36
36
|
"types": "./src/store/component/convex.config.ts",
|
|
37
37
|
"default": "./src/store/component/convex.config.ts"
|
|
38
|
+
},
|
|
39
|
+
"./state/_generated/component.js": {
|
|
40
|
+
"types": "./src/store/component/_generated/component.ts",
|
|
41
|
+
"default": "./src/store/component/_generated/component.ts"
|
|
42
|
+
},
|
|
43
|
+
"./state/functions": {
|
|
44
|
+
"types": "./src/store/functions.ts",
|
|
45
|
+
"default": "./src/store/functions.ts"
|
|
46
|
+
},
|
|
47
|
+
"./state/auth.config": {
|
|
48
|
+
"types": "./src/store/authConfig.ts",
|
|
49
|
+
"default": "./src/store/authConfig.ts"
|
|
38
50
|
}
|
|
39
51
|
},
|
|
40
52
|
"publishConfig": {
|
package/src/State.ts
CHANGED
|
@@ -4,18 +4,21 @@
|
|
|
4
4
|
// every stack and stage of the team in its production deployment: the
|
|
5
5
|
// tables `resources (stack, stage, fqn, value)` and `outputs (stack, stage,
|
|
6
6
|
// value)` of the alchemyState component (src/store/component), served by the
|
|
7
|
-
// functions of src/store/
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// functions of src/store/functions.ts. Two apps serve them: the store app of
|
|
8
|
+
// src/store/convex (a seed), which this provider pushes, and the base app of
|
|
9
|
+
// the team (a base), which mounts the component next to its own components.
|
|
10
|
+
// The store lives outside every resource that a stack manages, so a stack
|
|
11
|
+
// can create and destroy its own project. See docs/design/convex-state.md.
|
|
10
12
|
//
|
|
11
13
|
// Bootstrap, on the first call of the run: the team token (CONVEX_ACCESS_TOKEN,
|
|
12
14
|
// or the Convex CLI login) finds the project by name, or creates it; a deploy
|
|
13
15
|
// key of its production deployment that can run internal queries and
|
|
14
16
|
// mutations, and nothing else, is created for the run and deleted at its
|
|
15
|
-
// end; the `state:version` query says
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
17
|
+
// end; the `state:version` query says the store version and the app that
|
|
18
|
+
// serves it. Without one, or with an older seed, the installed Convex CLI
|
|
19
|
+
// pushes src/store/convex to it, with a push key that is deleted after the
|
|
20
|
+
// push. An older base fails the run: a push of the store app would erase the
|
|
21
|
+
// base app. The raw state functions are internal; authenticated readers get
|
|
19
22
|
// repository-scoped subscriptions with secret values removed. A key that stops working during a
|
|
20
23
|
// long run (it expires after two hours) is replaced once per call.
|
|
21
24
|
//
|
|
@@ -24,10 +27,10 @@
|
|
|
24
27
|
// anything, and uses the oldest: both runs end on one store.
|
|
25
28
|
//
|
|
26
29
|
// Samebase reads `stacks`, `getOutput`, `readStack`, and `reader:repository`
|
|
27
|
-
// by name.
|
|
28
|
-
//
|
|
29
|
-
// with a store of its own version. An older
|
|
30
|
-
//
|
|
30
|
+
// by name. STORE_VERSION is the wire contract: it rises only when a function
|
|
31
|
+
// that a released client calls changes or goes away, and a client works only
|
|
32
|
+
// with a store of its own version. An older seed is pushed over; an older base
|
|
33
|
+
// and a newer store fail the run.
|
|
31
34
|
//
|
|
32
35
|
// The stage output that `setOutput` stores is the stack's output plus the
|
|
33
36
|
// run record of src/RunRecord.ts under the key `samebase.run`: the
|
|
@@ -241,6 +244,39 @@ const storeVersion = (api: FunctionApi) =>
|
|
|
241
244
|
),
|
|
242
245
|
);
|
|
243
246
|
|
|
247
|
+
/**
|
|
248
|
+
* The app that serves the store. A store without `state:host` is a seed: it
|
|
249
|
+
* was pushed before 0.14.0, and only the store app of this package served
|
|
250
|
+
* those stores.
|
|
251
|
+
*/
|
|
252
|
+
const storeHost = (api: FunctionApi) =>
|
|
253
|
+
api
|
|
254
|
+
.call("state:host", {})
|
|
255
|
+
.pipe(
|
|
256
|
+
Effect.flatMap((reply) =>
|
|
257
|
+
reply.status === "error" && MISSING_FUNCTION.test(reply.errorMessage)
|
|
258
|
+
? Effect.succeed("seed" as const)
|
|
259
|
+
: valueOf(reply).pipe(
|
|
260
|
+
Effect.flatMap(decode(Schema.Literals(["seed", "base"]), "store host")),
|
|
261
|
+
),
|
|
262
|
+
),
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The base app of the team serves an older store. Convex.state() never
|
|
267
|
+
* pushes its store app over a base, because the push replaces every function
|
|
268
|
+
* of the deployment and so erases the base app.
|
|
269
|
+
*/
|
|
270
|
+
export class OutdatedBase extends Schema.TaggedError<OutdatedBase>()("OutdatedBase", {
|
|
271
|
+
team: Schema.Union([Schema.String, Schema.Number]),
|
|
272
|
+
deployment: Schema.String,
|
|
273
|
+
version: Schema.Number,
|
|
274
|
+
}) {
|
|
275
|
+
override get message() {
|
|
276
|
+
return `The base of team ${this.team} (${this.deployment}) answers store version ${this.version}, and this package needs store version ${STORE_VERSION}. Convex.state does not push over a base. Upgrade the dependency @samebase/alchemy-convex of the base app to the version of this package, and push the base app.`;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
244
280
|
/**
|
|
245
281
|
* The functions directory of the store app, `src/store/convex` of this
|
|
246
282
|
* package. This file is `src/State.ts` in a checkout and a chunk in `lib/`
|
|
@@ -330,8 +366,8 @@ const createStoreProject = (api: ManagementApiService, teamId: number, team: str
|
|
|
330
366
|
|
|
331
367
|
/**
|
|
332
368
|
* The StateService over the `samebase` project of the team: found or
|
|
333
|
-
* created, its store pushed when
|
|
334
|
-
* written with the keys of this run.
|
|
369
|
+
* created, its store app pushed when the store is missing or an older seed,
|
|
370
|
+
* and read and written with the keys of this run.
|
|
335
371
|
*/
|
|
336
372
|
export const makeConvexState = (options: StoreOptions) =>
|
|
337
373
|
Effect.gen(function* () {
|
|
@@ -379,22 +415,38 @@ export const makeConvexState = (options: StoreOptions) =>
|
|
|
379
415
|
),
|
|
380
416
|
};
|
|
381
417
|
|
|
418
|
+
// The store app is pushed only over no store or an older seed. A base
|
|
419
|
+
// serves the same functions, and a push would erase the base app.
|
|
420
|
+
const needsPush = (found: number | undefined) =>
|
|
421
|
+
found === undefined
|
|
422
|
+
? Effect.succeed(true)
|
|
423
|
+
: found < STORE_VERSION
|
|
424
|
+
? storeHost(functions).pipe(Effect.map((host) => host === "seed"))
|
|
425
|
+
: Effect.succeed(false);
|
|
382
426
|
let version = yield* storeVersion(functions);
|
|
383
|
-
if (
|
|
427
|
+
if (yield* needsPush(version)) {
|
|
384
428
|
// Code without `state:version` is another app with the name of the store.
|
|
385
429
|
if (version === undefined && deployment.lastDeployTime) {
|
|
386
430
|
return yield* failure(
|
|
387
431
|
`project ${STORE_PROJECT_NAME} (${project.id}) of team ${team} has code that is not the state store, and Convex.state does not push over it. Rename that project in the Convex dashboard.`,
|
|
388
432
|
);
|
|
389
433
|
}
|
|
390
|
-
// Another run can push a newer store in the meantime:
|
|
391
|
-
// before the push. The push itself has no lock (see
|
|
434
|
+
// Another run can push a newer store, or the base app, in the meantime:
|
|
435
|
+
// read again right before the push. The push itself has no lock (see
|
|
436
|
+
// the design note).
|
|
392
437
|
version = yield* storeVersion(functions);
|
|
393
|
-
if (
|
|
438
|
+
if (yield* needsPush(version)) {
|
|
394
439
|
yield* pushStore(options, deployment);
|
|
395
440
|
version = yield* storeVersion(functions);
|
|
396
441
|
}
|
|
397
442
|
}
|
|
443
|
+
if (
|
|
444
|
+
version !== undefined &&
|
|
445
|
+
version < STORE_VERSION &&
|
|
446
|
+
(yield* storeHost(functions)) === "base"
|
|
447
|
+
) {
|
|
448
|
+
return yield* new OutdatedBase({ team, deployment: deployment.name, version });
|
|
449
|
+
}
|
|
398
450
|
if (version !== STORE_VERSION) {
|
|
399
451
|
return yield* failure(
|
|
400
452
|
`${deployment.name} answers store version ${version ?? "none"}, and this package needs store version ${STORE_VERSION}.`,
|
|
@@ -445,7 +497,8 @@ export const makeConvexState = (options: StoreOptions) =>
|
|
|
445
497
|
* in CONVEX_ACCESS_TOKEN or the Convex CLI login. A team access token names
|
|
446
498
|
* its team, so `team` is optional with one; the Convex CLI login needs the
|
|
447
499
|
* slug or the id. The first use of the run finds or creates the project and
|
|
448
|
-
* pushes the store when
|
|
500
|
+
* pushes the store app when the store is missing or an older seed. An older
|
|
501
|
+
* base fails the run with {@link OutdatedBase}. `alchemy plan` on another
|
|
449
502
|
* stack never touches Convex.
|
|
450
503
|
*/
|
|
451
504
|
export const state = (
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// The auth config of each app that serves the store: the app verifies the
|
|
2
|
+
// read tokens that `reader:authorize` signs with the key of its own
|
|
3
|
+
// deployment. Exported as `@samebase/alchemy-convex/state/auth.config`.
|
|
4
|
+
import type { AuthConfig } from "convex/server";
|
|
5
|
+
|
|
6
|
+
/** The auth config of the store readers on the deployment at `cloudUrl`, the app's `env.CONVEX_CLOUD_URL`. */
|
|
7
|
+
export const authConfig = (cloudUrl: string): AuthConfig => ({
|
|
8
|
+
providers: [
|
|
9
|
+
{
|
|
10
|
+
type: "customJwt",
|
|
11
|
+
issuer: cloudUrl,
|
|
12
|
+
applicationID: `${cloudUrl}/alchemy-reader`,
|
|
13
|
+
jwks: `${cloudUrl}/.well-known/jwks.json`,
|
|
14
|
+
algorithm: "ES256",
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// `ctx.auth`.
|
|
4
4
|
import { v, type Infer } from "convex/values";
|
|
5
5
|
import { query } from "./_generated/server.js";
|
|
6
|
-
import { runRecord, validate } from "./runRecord.
|
|
6
|
+
import { runRecord, validate } from "./runRecord.js";
|
|
7
7
|
|
|
8
8
|
const stage = v.object({
|
|
9
9
|
name: v.string(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineSchema, defineTable } from "convex/server";
|
|
2
2
|
import { v } from "convex/values";
|
|
3
|
-
import { runRecord } from "./runRecord.
|
|
3
|
+
import { runRecord } from "./runRecord.js";
|
|
4
4
|
|
|
5
5
|
// `value` is the JSON text of Alchemy's encodeState(...): the same text that
|
|
6
6
|
// Alchemy's local state writes to a file. A Convex object would reject keys
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { v, type Infer } from "convex/values";
|
|
9
9
|
import type { MutationCtx, QueryCtx } from "./_generated/server.js";
|
|
10
10
|
import { mutation, query } from "./_generated/server.js";
|
|
11
|
-
import { runRecord } from "./runRecord.
|
|
11
|
+
import { runRecord } from "./runRecord.js";
|
|
12
12
|
|
|
13
13
|
const stage = { stack: v.string(), stage: v.string() };
|
|
14
14
|
const resource = { ...stage, fqn: v.string() };
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { authConfig } from "../authConfig.js";
|
|
2
2
|
import { env } from "./_generated/server.js";
|
|
3
3
|
|
|
4
|
-
export default
|
|
5
|
-
providers: [
|
|
6
|
-
{
|
|
7
|
-
type: "customJwt",
|
|
8
|
-
issuer: env.CONVEX_CLOUD_URL,
|
|
9
|
-
applicationID: `${env.CONVEX_CLOUD_URL}/alchemy-reader`,
|
|
10
|
-
jwks: `${env.CONVEX_CLOUD_URL}/.well-known/jwks.json`,
|
|
11
|
-
algorithm: "ES256",
|
|
12
|
-
},
|
|
13
|
-
],
|
|
14
|
-
} satisfies AuthConfig;
|
|
4
|
+
export default authConfig(env.CONVEX_CLOUD_URL);
|
|
@@ -1,51 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
// redaction are in the alchemyState component.
|
|
4
|
-
import { v } from "convex/values";
|
|
5
|
-
import { repositoryReply } from "../component/reader.ts";
|
|
1
|
+
// The authenticated, redacted reads of the store app, from src/store/functions.ts.
|
|
2
|
+
import { readerFunctions } from "../functions.js";
|
|
6
3
|
import { components } from "./_generated/api.js";
|
|
7
|
-
import { env, internalMutation, query } from "./_generated/server.js";
|
|
8
4
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
/** Only a deployment credential can exchange an approved repository for a read token. */
|
|
12
|
-
export const authorize = internalMutation({
|
|
13
|
-
args: { repository: v.string(), subject: v.string() },
|
|
14
|
-
returns: v.object({ token: v.string() }),
|
|
15
|
-
handler: async (ctx, args) => {
|
|
16
|
-
if (!repositoryName.test(args.repository) || args.subject === "") {
|
|
17
|
-
throw new Error("A repository and subject are required.");
|
|
18
|
-
}
|
|
19
|
-
return {
|
|
20
|
-
token: await ctx.auth.signJwt({
|
|
21
|
-
expiresInSeconds: 300,
|
|
22
|
-
claims: {
|
|
23
|
-
sub: args.subject,
|
|
24
|
-
aud: `${env.CONVEX_CLOUD_URL}/alchemy-reader`,
|
|
25
|
-
repository: args.repository,
|
|
26
|
-
scope: "alchemy:read",
|
|
27
|
-
},
|
|
28
|
-
}),
|
|
29
|
-
};
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
/** A native subscription reads only the repository named in its verified capability. */
|
|
34
|
-
export const repository = query({
|
|
35
|
-
args: {},
|
|
36
|
-
returns: repositoryReply,
|
|
37
|
-
handler: async (ctx) => {
|
|
38
|
-
const identity = await ctx.auth.getUserIdentity();
|
|
39
|
-
if (
|
|
40
|
-
identity === null ||
|
|
41
|
-
identity.scope !== "alchemy:read" ||
|
|
42
|
-
typeof identity.repository !== "string" ||
|
|
43
|
-
!repositoryName.test(identity.repository)
|
|
44
|
-
) {
|
|
45
|
-
throw new Error("Alchemy repository read access is required.");
|
|
46
|
-
}
|
|
47
|
-
return await ctx.runQuery(components.alchemyState.reader.repository, {
|
|
48
|
-
repository: identity.repository,
|
|
49
|
-
});
|
|
50
|
-
},
|
|
51
|
-
});
|
|
5
|
+
export const { authorize, repository } = readerFunctions(components.alchemyState);
|
|
@@ -1,123 +1,23 @@
|
|
|
1
|
-
// The state functions of the
|
|
2
|
-
//
|
|
3
|
-
// functions are internal
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
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.
|
|
11
|
-
import { v } from "convex/values";
|
|
12
|
-
import { runRecord } from "../component/runRecord.ts";
|
|
13
|
-
import { stackReply } from "../component/state.ts";
|
|
1
|
+
// The state functions of the store app of this package, a seed: Convex.state()
|
|
2
|
+
// pushes this directory to the samebase project of a team. The raw state
|
|
3
|
+
// functions are internal; reader.ts exposes authenticated, redacted reads.
|
|
4
|
+
// The functions come from src/store/functions.ts, which a base app imports
|
|
5
|
+
// too. Each export has its own name, so that Convex finds it.
|
|
6
|
+
import { stateFunctions } from "../functions.js";
|
|
14
7
|
import { components } from "./_generated/api.js";
|
|
15
|
-
import { internalMutation, internalQuery } from "./_generated/server.js";
|
|
16
|
-
import { STORE_VERSION } from "./version.ts";
|
|
17
8
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
/** Stages of a stack with at least one resource or one output, sorted. */
|
|
35
|
-
export const stages = internalQuery({
|
|
36
|
-
args: { stack: v.string() },
|
|
37
|
-
returns: v.array(v.string()),
|
|
38
|
-
handler: (ctx, args) => ctx.runQuery(state.stages, { stack: args.stack }),
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
export const list = internalQuery({
|
|
42
|
-
args: { stack: v.string(), stage: v.string() },
|
|
43
|
-
returns: v.array(v.string()),
|
|
44
|
-
handler: (ctx, args) => ctx.runQuery(state.list, { stack: args.stack, stage: args.stage }),
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
export const get = internalQuery({
|
|
48
|
-
args: { stack: v.string(), stage: v.string(), fqn: v.string() },
|
|
49
|
-
returns: v.union(v.string(), v.null()),
|
|
50
|
-
handler: (ctx, args) =>
|
|
51
|
-
ctx.runQuery(state.get, { stack: args.stack, stage: args.stage, fqn: args.fqn }),
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
/** Resources whose state has `status: "replaced"`. */
|
|
55
|
-
export const replaced = internalQuery({
|
|
56
|
-
args: { stack: v.string(), stage: v.string() },
|
|
57
|
-
returns: v.array(v.string()),
|
|
58
|
-
handler: (ctx, args) => ctx.runQuery(state.replaced, { stack: args.stack, stage: args.stage }),
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
export const set = internalMutation({
|
|
62
|
-
args: {
|
|
63
|
-
stack: v.string(),
|
|
64
|
-
stage: v.string(),
|
|
65
|
-
fqn: v.string(),
|
|
66
|
-
value: v.string(),
|
|
67
|
-
run: v.optional(runRecord),
|
|
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
|
-
}),
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
export const remove = internalMutation({
|
|
81
|
-
args: { stack: v.string(), stage: v.string(), fqn: v.string(), run: v.optional(runRecord) },
|
|
82
|
-
returns: v.null(),
|
|
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
|
-
}),
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
/** Deletes the resources and outputs of one stage, or of every stage of the stack. */
|
|
93
|
-
export const removeStack = internalMutation({
|
|
94
|
-
args: { stack: v.string(), stage: v.optional(v.string()) },
|
|
95
|
-
returns: v.null(),
|
|
96
|
-
handler: (ctx, args) =>
|
|
97
|
-
ctx.runMutation(state.removeStack, { stack: args.stack, stage: args.stage ?? null }),
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
export const getOutput = internalQuery({
|
|
101
|
-
args: { stack: v.string(), stage: v.string() },
|
|
102
|
-
returns: v.union(v.string(), v.null()),
|
|
103
|
-
handler: (ctx, args) => ctx.runQuery(state.getOutput, { stack: args.stack, stage: args.stage }),
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
export const setOutput = internalMutation({
|
|
107
|
-
args: { stack: v.string(), stage: v.string(), value: v.string(), run: v.optional(runRecord) },
|
|
108
|
-
returns: v.null(),
|
|
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
|
-
}),
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
/** Every stage of a stack with its resources, output, and current record, in one call. Since store version 2. */
|
|
119
|
-
export const readStack = internalQuery({
|
|
120
|
-
args: { stack: v.string() },
|
|
121
|
-
returns: stackReply,
|
|
122
|
-
handler: (ctx, args) => ctx.runQuery(state.readStack, { stack: args.stack }),
|
|
123
|
-
});
|
|
9
|
+
export const {
|
|
10
|
+
version,
|
|
11
|
+
host,
|
|
12
|
+
stacks,
|
|
13
|
+
stages,
|
|
14
|
+
list,
|
|
15
|
+
get,
|
|
16
|
+
replaced,
|
|
17
|
+
set,
|
|
18
|
+
remove,
|
|
19
|
+
removeStack,
|
|
20
|
+
getOutput,
|
|
21
|
+
setOutput,
|
|
22
|
+
readStack,
|
|
23
|
+
} = stateFunctions(components.alchemyState, "seed");
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
// The wire
|
|
2
|
-
// Convex.state() pushes
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
1
|
+
// The wire contract of the store: the `state:version` query answers it, and
|
|
2
|
+
// Convex.state() pushes the store app when a seed answers an older one or the
|
|
3
|
+
// deployment has no `version` yet. It fails the run when a base answers an
|
|
4
|
+
// older one, and on a newer one.
|
|
5
|
+
//
|
|
6
|
+
// Each consumer of a team needs this exact number, so a raise moves every
|
|
7
|
+
// consumer of the team at once. Raise it only when a function that a released
|
|
8
|
+
// client calls changes its arguments, its result, or its meaning, or goes
|
|
9
|
+
// away. A new function does not raise it. The store and its readers (Samebase)
|
|
10
|
+
// move together on a raise: a renamed function is a raise on both sides.
|
|
8
11
|
//
|
|
9
12
|
// 1: `version`, `stacks`, `stages`, `list`, `get`, `replaced`, `getOutput`,
|
|
10
13
|
// `set`, `remove`, `removeStack`, `setOutput` (0.6.0).
|