@samebase/alchemy-convex 0.15.0 → 0.16.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 +7 -16
- package/lib/{HubClient-C_nvjlvW.js → HubClient-BYBV7cF5.js} +22 -47
- package/lib/{HubClient-C_nvjlvW.js.map → HubClient-BYBV7cF5.js.map} +1 -1
- package/lib/cli.js +1 -1
- package/lib/index.d.ts +6 -18
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/package.json +1 -17
- package/src/HubClient.ts +17 -15
- package/src/State.ts +24 -77
- package/src/index.ts +2 -1
- 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 +12 -2
- package/src/store/convex/convex.config.ts +1 -1
- package/src/store/convex/reader.ts +49 -3
- package/src/store/convex/state.ts +121 -21
- package/src/store/convex/version.ts +6 -10
- package/src/store/package.json +1 -1
- package/src/store/authConfig.ts +0 -17
- package/src/store/functions.ts +0 -214
package/lib/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { I as ManagementApiLive, M as installedCli, V as fromConfig,
|
|
2
|
+
import { I as ManagementApiLive, M as installedCli, V as fromConfig, a as registerDevelopmentClient } from "./HubClient-BYBV7cF5.js";
|
|
3
3
|
import * as Effect from "effect/Effect";
|
|
4
4
|
import * as Layer from "effect/Layer";
|
|
5
5
|
import { parseArgs } from "node:util";
|
package/lib/index.d.ts
CHANGED
|
@@ -408,6 +408,8 @@ export declare class ProjectNotFound extends ProjectNotFound_base {
|
|
|
408
408
|
}
|
|
409
409
|
//#endregion
|
|
410
410
|
//#region src/HubClient.d.ts
|
|
411
|
+
/** The name of the project of the hub in each team. */
|
|
412
|
+
export declare const HUB_PROJECT_NAME = "samebase";
|
|
411
413
|
type HubDeploymentType = "production" | "development" | "preview";
|
|
412
414
|
interface HubClientProps {
|
|
413
415
|
/** The Convex team, a slug or an id, whose project `samebase` holds the hub. */
|
|
@@ -439,7 +441,7 @@ declare const HubClientFailed_base: Schema.Class<HubClientFailed, Schema.TaggedS
|
|
|
439
441
|
readonly team: Schema.Union<readonly [Schema.String, Schema.Number]>;
|
|
440
442
|
readonly reason: Schema.String;
|
|
441
443
|
}>, import("effect/Cause").YieldableError>;
|
|
442
|
-
/** The
|
|
444
|
+
/** The hub of the team could not do the call. The reason is the Convex message: it holds no secret. */
|
|
443
445
|
export declare class HubClientFailed extends HubClientFailed_base {
|
|
444
446
|
get message(): string;
|
|
445
447
|
}
|
|
@@ -494,27 +496,13 @@ export declare const withRunRecord: (output: unknown, record: RunRecord) => obje
|
|
|
494
496
|
//#endregion
|
|
495
497
|
//#region src/State.d.ts
|
|
496
498
|
/** The name of the state project in each team. */
|
|
497
|
-
export declare const STORE_PROJECT_NAME = "
|
|
498
|
-
declare const OutdatedBase_base: Schema.Class<OutdatedBase, Schema.TaggedStruct<"OutdatedBase", {
|
|
499
|
-
readonly team: Schema.Union<readonly [Schema.String, Schema.Number]>;
|
|
500
|
-
readonly deployment: Schema.String;
|
|
501
|
-
readonly version: Schema.Number;
|
|
502
|
-
}>, import("effect/Cause").YieldableError>;
|
|
503
|
-
/**
|
|
504
|
-
* The base app of the team serves an older store. Convex.state() never
|
|
505
|
-
* pushes its store app over a base, because the push replaces every function
|
|
506
|
-
* of the deployment and so erases the base app.
|
|
507
|
-
*/
|
|
508
|
-
export declare class OutdatedBase extends OutdatedBase_base {
|
|
509
|
-
get message(): string;
|
|
510
|
-
}
|
|
499
|
+
export declare const STORE_PROJECT_NAME = "alchemy-state-store";
|
|
511
500
|
/**
|
|
512
|
-
* Alchemy state in the `
|
|
501
|
+
* Alchemy state in the `alchemy-state-store` project of `team`, with the team token
|
|
513
502
|
* in CONVEX_ACCESS_TOKEN or the Convex CLI login. A team access token names
|
|
514
503
|
* its team, so `team` is optional with one; the Convex CLI login needs the
|
|
515
504
|
* slug or the id. The first use of the run finds or creates the project and
|
|
516
|
-
* pushes the store
|
|
517
|
-
* base fails the run with {@link OutdatedBase}. `alchemy plan` on another
|
|
505
|
+
* pushes the store when it is missing or older. `alchemy plan` on another
|
|
518
506
|
* stack never touches Convex.
|
|
519
507
|
*/
|
|
520
508
|
export declare const state: (options?: {
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/Credentials.ts","../src/ManagementApi.ts","../src/Providers.ts","../src/Push.ts","../src/Deployment.ts","../src/Component.ts","../src/Project.ts","../src/HubClient.ts","../src/RunRecord.ts","../src/State.ts"],"mappings":";;;;;;;;;;;;;;;;UA6BiB;;WAEN,aAAa,SAAS;;WAEtB;;UAGM;;WAEN,aAAa,OAAO,OAAO;;WAE3B,gBAAgB,OAAO;;WAEvB,WAAW,OAAO,OAAO,SAAS;;;qBAGhC,oBAAoB;;qBAKpB;;qBAGA;;;;qBAOA,yBAAyB;;;;;;;;;qBAiCzB,kBAAiB,MAAM,MAAM;;qBA0B7B,aAAc;WAChB,cAAc,SAAS;WACvB,YAAY,SAAS;MAC5B,MAAM,MAAM;;;KC3GJ,mBAAmB,kBAAkB;KACrC,mBAAmB,kBAAkB;;UAGhC,YAAY;WAClB,OAAO;WACP;WACA,UAAU;;;;;;;;;;;;;qBAQR,uBAAuB;UAuBnB;;WAEN,UAAU,GACjB,mBACA,MAAM,QAAQ,qBAAqB,QAAQ,YAAY,QACpD,OAAO,OAAO,GAAG;;WAEb,cACP,mBACA,MAAM,QAAQ,qBAAqB,QAAQ,0BACxC,OAAO,aAAa;;WAEhB,oBAAoB,GAC3B,mBACA,6BACA,WAAW,SAAS,kBACpB,MAAM,QAAQ,qBAAqB,QAAQ,YAAY,QACpD,OAAO,OAAO,GAAG;;;;;;;;WAQb,gBACP,sCACG,OAAO,eAAe;;;;;;;;WAQlB,oBAAoB,mBAAmB,OAAO;;;;;WAK9C,YAAY,OAAO,SAAS,qBAAqB;;;qBAM/C,sBAAsB;;;;qBCzFtB,kBAAkB;;;;;;qBAOlB,YAAa,cAAa,MAAM,MAAM,iBAA2B,MAAA,MAAA,8CAAA,mCAAA,oDAAA;;;;KC8BlE,oBAAoB,SAAS;;;;;;KAO7B;;WAIG;;;;;;;;WAQA;;;;;;;WAOA;;;;;;qBAkCF,0BAA0B;MAIxB;;;;;;;qBAMF,0BAA0B;MAOxB;;;;;;;;;qBAMF,uBAAuB;MAMrB;;;;;;;qBAQF,2BAA2B;MAIzB;;;;;;;qBAMF,oCAAoC;MAIlC;;;;;;qBAMF,kCAAkC;MAIhC;;;qBAuCF,oBACX;EACG;EAAa;;;;UC3HR;;WAEC;aAAoB;;;;;;;;WAOpB;;;;;;WAMA,SAAS;;WAET,MAAM,SAAS,eAAe;;KAG7B,mBACP;;WAEU;MAEV;;WAEU;;;;;WAKA;;;UAIL;WACC;WACA,MAAM;;WAEN;;WAEA;;WAEA;;WAEA;;WAEA;;UAGM,6BAA6B;;WAEnC;;WAEA;;YAGC,aAAa,8BAEvB,iBACA,sBACA,kBACA;;KAQU,kBAAkB,+BAA8B,KACxD,UAAU,kBACR,WAAW;;;;;qBAWJ,wBAAwB;MAGtB;;;;;;;;;;;;qBAeF,cAAU,YACV,OAAS,oBAAe,OAAA,OAAA,mBAAA;;;;;;EAYvB,MAAA,YAAM;IAAc;IAAgB;QAAgB,OAAA,OAAA;;;;;;;;;;qBAMrD,iCAAiC;MAU/B;;;;;;qBAMF,qCAAqC;MAInC;;;;;;;;;qBAMF,4BAA4B;MAS1B;;;;;UC1OE;WACN;WACA;WACA;;;;;;;;;qBAUE,gBAAiB;EAC5B,YAAY;EACZ;EACA,WAAW;MACZ,OAAA,yBAAA;;;;KCWW,mBAAmB;KAEnB;;WAGG;;WAEA;;WAEA,mBAAmB;;WAEnB;;;WAIA;;WAEA;;UAGE;WACN;WACA;WACA;WACA;WACA;WACA;;YAGC,UAAU,2BAA2B,cAAc,0BAA0B;qBAC5E,2BAAO,cAAA;;;;;;;qBAGP,0BAA0B;MAIxB;;;;;;;;;;;;qBASF,gCAAgC;MAS9B;;;;;;;;qBAOF,yBAAyB;MAKvB;;;;;;qBAMF,+BAA+B;MAI7B;;;;;;;qBAMF,wBAAwB;MAItB
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/Credentials.ts","../src/ManagementApi.ts","../src/Providers.ts","../src/Push.ts","../src/Deployment.ts","../src/Component.ts","../src/Project.ts","../src/HubClient.ts","../src/RunRecord.ts","../src/State.ts"],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as PushTargetMismatch, B as DEPLOY_KEY_ENV, C as DeploymentProvider, D as ConvexAppNotFound, E as CodePushFailed, F as ManagementApi, H as fromValues, I as ManagementApiLive, L as ACCESS_TOKEN_ENV, N as parseDeployOutput, O as ConvexCliNotFound, P as ConvexApiError, R as Credentials, S as DeploymentIdentityChange, T as ProductionDeploymentNotFound, V as fromConfig, _ as ProjectProvider,
|
|
1
|
+
import { A as PushTargetMismatch, B as DEPLOY_KEY_ENV, C as DeploymentProvider, D as ConvexAppNotFound, E as CodePushFailed, F as ManagementApi, H as fromValues, I as ManagementApiLive, L as ACCESS_TOKEN_ENV, N as parseDeployOutput, O as ConvexCliNotFound, P as ConvexApiError, R as Credentials, S as DeploymentIdentityChange, T as ProductionDeploymentNotFound, V as fromConfig, _ as ProjectProvider, b as AmbiguousDeployment, c as RUN_RECORD_KEY, d as withRunRecord, f as AmbiguousProject, g as ProjectNotFound, h as ProjectDeploymentChange, i as HubClientProvider, j as VariablesOwnedBySomeoneElse, k as EnvironmentNeedsDeployKey, l as RunRecordError, m as Project, n as HubClient, o as STORE_PROJECT_NAME, p as CreatedProjectNotFound, r as HubClientFailed, s as state, t as HUB_PROJECT_NAME, u as runRecord, v as ProjectTeamChange, w as DeploymentResource, x as Deployment, y as AbsoluteRootDir, z as CredentialsError } from "./HubClient-BYBV7cF5.js";
|
|
2
2
|
import { toPath } from "alchemy/FQN";
|
|
3
3
|
import { Stack } from "alchemy/Stack";
|
|
4
4
|
import * as Effect from "effect/Effect";
|
|
@@ -43,6 +43,6 @@ const providers = (credentials = fromConfig()) => Layer.effect(Providers, Provid
|
|
|
43
43
|
HubClient
|
|
44
44
|
])).pipe(Layer.provide(Layer.mergeAll(ProjectProvider(), DeploymentProvider(), HubClientProvider())), Layer.provide(ManagementApiLive()), Layer.provide(credentials), Layer.orDie);
|
|
45
45
|
//#endregion
|
|
46
|
-
export { ACCESS_TOKEN_ENV, AbsoluteRootDir, AmbiguousDeployment, AmbiguousProject, CodePushFailed, ConvexApiError, ConvexAppNotFound, ConvexCliNotFound, CreatedProjectNotFound, Credentials, CredentialsError, DEPLOY_KEY_ENV, Deployment, DeploymentIdentityChange, EnvironmentNeedsDeployKey, HubClient, HubClientFailed, ManagementApi,
|
|
46
|
+
export { ACCESS_TOKEN_ENV, AbsoluteRootDir, AmbiguousDeployment, AmbiguousProject, CodePushFailed, ConvexApiError, ConvexAppNotFound, ConvexCliNotFound, CreatedProjectNotFound, Credentials, CredentialsError, DEPLOY_KEY_ENV, Deployment, DeploymentIdentityChange, EnvironmentNeedsDeployKey, HUB_PROJECT_NAME, HubClient, HubClientFailed, ManagementApi, ProductionDeploymentNotFound, Project, ProjectDeploymentChange, ProjectNotFound, ProjectTeamChange, Providers, PushTargetMismatch, RUN_RECORD_KEY, RunRecordError, STORE_PROJECT_NAME, VariablesOwnedBySomeoneElse, bindComponent, fromConfig, fromValues, parseDeployOutput, providers, runRecord, state, withRunRecord };
|
|
47
47
|
|
|
48
48
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@samebase/alchemy-convex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
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",
|
|
@@ -34,22 +34,6 @@
|
|
|
34
34
|
".": {
|
|
35
35
|
"types": "./lib/index.d.ts",
|
|
36
36
|
"import": "./lib/index.js"
|
|
37
|
-
},
|
|
38
|
-
"./state/convex.config": {
|
|
39
|
-
"types": "./src/store/component/convex.config.ts",
|
|
40
|
-
"default": "./src/store/component/convex.config.ts"
|
|
41
|
-
},
|
|
42
|
-
"./state/_generated/component.js": {
|
|
43
|
-
"types": "./src/store/component/_generated/component.ts",
|
|
44
|
-
"default": "./src/store/component/_generated/component.ts"
|
|
45
|
-
},
|
|
46
|
-
"./state/functions": {
|
|
47
|
-
"types": "./src/store/functions.ts",
|
|
48
|
-
"default": "./src/store/functions.ts"
|
|
49
|
-
},
|
|
50
|
-
"./state/auth.config": {
|
|
51
|
-
"types": "./src/store/authConfig.ts",
|
|
52
|
-
"default": "./src/store/authConfig.ts"
|
|
53
37
|
}
|
|
54
38
|
},
|
|
55
39
|
"publishConfig": {
|
package/src/HubClient.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
// Convex.HubClient: one deployment of an app as a client of the identity hub
|
|
2
2
|
// of its team.
|
|
3
3
|
//
|
|
4
|
-
// The hub is
|
|
5
|
-
//
|
|
6
|
-
// the
|
|
7
|
-
// `identityBroker`. This resource finds the base by name and runs those
|
|
4
|
+
// The hub is the production deployment of the project `samebase` of the team.
|
|
5
|
+
// It has the internal functions of `@samebase/identity-broker` in its module
|
|
6
|
+
// `identityBroker`. This resource finds the project by name and runs those
|
|
8
7
|
// functions with a short-lived key, so a stack that has the Convex credential
|
|
9
8
|
// of the team needs no other credential and no hub address. The hub answers
|
|
10
9
|
// with its issuer and the client.
|
|
@@ -13,7 +12,7 @@
|
|
|
13
12
|
// of it. A client belongs to one redirect URI: another URI is a new client. A
|
|
14
13
|
// change of `app` updates the stored props and does not move the client: the
|
|
15
14
|
// app is the identity of the users of the app, and it does not change. The
|
|
16
|
-
// team does not change either: the state of a stack is in
|
|
15
|
+
// team does not change either: the state of a stack is in its team.
|
|
17
16
|
import { Resource } from "alchemy";
|
|
18
17
|
import { isResolved } from "alchemy/Diff";
|
|
19
18
|
import * as Provider from "alchemy/Provider";
|
|
@@ -24,7 +23,10 @@ import { ManagementApi, type ManagementApiService } from "./ManagementApi.ts";
|
|
|
24
23
|
import { findProjectByName, prodDeploymentOf } from "./Project.ts";
|
|
25
24
|
import type { Providers } from "./Providers.ts";
|
|
26
25
|
import { withPushKey } from "./Push.ts";
|
|
27
|
-
import { type FunctionApi, makeFunctionApi, RUN_KEY_ACTIONS
|
|
26
|
+
import { type FunctionApi, makeFunctionApi, RUN_KEY_ACTIONS } from "./State.ts";
|
|
27
|
+
|
|
28
|
+
/** The name of the project of the hub in each team. */
|
|
29
|
+
export const HUB_PROJECT_NAME = "samebase";
|
|
28
30
|
|
|
29
31
|
export type HubDeploymentType = "production" | "development" | "preview";
|
|
30
32
|
|
|
@@ -63,13 +65,13 @@ export type HubClient = Resource<
|
|
|
63
65
|
>;
|
|
64
66
|
export const HubClient = Resource<HubClient>("IdentityHub.Client");
|
|
65
67
|
|
|
66
|
-
/** The
|
|
68
|
+
/** The hub of the team could not do the call. The reason is the Convex message: it holds no secret. */
|
|
67
69
|
export class HubClientFailed extends Schema.TaggedError<HubClientFailed>()("HubClientFailed", {
|
|
68
70
|
team: Schema.Union([Schema.String, Schema.Number]),
|
|
69
71
|
reason: Schema.String,
|
|
70
72
|
}) {
|
|
71
73
|
override get message() {
|
|
72
|
-
return `Convex.HubClient: the
|
|
74
|
+
return `Convex.HubClient: the hub of team ${this.team}: ${this.reason}.`;
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
|
|
@@ -80,26 +82,26 @@ const Registered = Schema.Struct({
|
|
|
80
82
|
clientSecret: Schema.NonEmptyString,
|
|
81
83
|
});
|
|
82
84
|
|
|
83
|
-
/** Runs `use` with the function API of the
|
|
84
|
-
const
|
|
85
|
+
/** Runs `use` with the function API of the hub of `team`, with a key of this call only. */
|
|
86
|
+
const withHub = <A, E, R>(
|
|
85
87
|
api: ManagementApiService,
|
|
86
88
|
team: string | number,
|
|
87
89
|
use: (functions: FunctionApi) => Effect.Effect<A, E, R>,
|
|
88
90
|
) =>
|
|
89
91
|
Effect.gen(function* () {
|
|
90
92
|
const teamId = yield* api.resolveTeamId(team);
|
|
91
|
-
const project = yield* findProjectByName(api, teamId,
|
|
93
|
+
const project = yield* findProjectByName(api, teamId, HUB_PROJECT_NAME);
|
|
92
94
|
if (project === undefined) {
|
|
93
95
|
return yield* new HubClientFailed({
|
|
94
96
|
team,
|
|
95
|
-
reason: `no project ${
|
|
97
|
+
reason: `no project ${HUB_PROJECT_NAME}, which holds the hub`,
|
|
96
98
|
});
|
|
97
99
|
}
|
|
98
100
|
const deployment = yield* prodDeploymentOf(api, project.id);
|
|
99
101
|
if (deployment === undefined) {
|
|
100
102
|
return yield* new HubClientFailed({
|
|
101
103
|
team,
|
|
102
|
-
reason: `project ${
|
|
104
|
+
reason: `project ${HUB_PROJECT_NAME} has no production deployment`,
|
|
103
105
|
});
|
|
104
106
|
}
|
|
105
107
|
return yield* withPushKey(api, deployment.name, RUN_KEY_ACTIONS, (key) =>
|
|
@@ -128,7 +130,7 @@ const runHub = (
|
|
|
128
130
|
* client on the hub, which signs nobody in.
|
|
129
131
|
*/
|
|
130
132
|
export const registerHubClient = (api: ManagementApiService, props: HubClientProps) =>
|
|
131
|
-
|
|
133
|
+
withHub(api, props.team, (functions) =>
|
|
132
134
|
runHub(functions, props.team, "registerClient", {
|
|
133
135
|
appId: props.app,
|
|
134
136
|
redirectUri: props.redirectUri,
|
|
@@ -154,7 +156,7 @@ export const registerHubClient = (api: ManagementApiService, props: HubClientPro
|
|
|
154
156
|
);
|
|
155
157
|
|
|
156
158
|
const unregister = (api: ManagementApiService, team: string | number, clientId: string) =>
|
|
157
|
-
|
|
159
|
+
withHub(api, team, (functions) => runHub(functions, team, "deleteClient", { clientId }));
|
|
158
160
|
|
|
159
161
|
/** True when the client in state serves the redirect URI and deployment type of `props`. */
|
|
160
162
|
const registeredFor = (output: HubClientAttributes, props: HubClientProps) =>
|
package/src/State.ts
CHANGED
|
@@ -1,36 +1,33 @@
|
|
|
1
1
|
// Alchemy state in Convex: `state: Convex.state({ team })` in `Alchemy.Stack`.
|
|
2
2
|
//
|
|
3
|
-
// One project for each Convex team, `
|
|
3
|
+
// One project for each Convex team, `alchemy-state-store`, holds the state of
|
|
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
|
-
//
|
|
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.
|
|
7
|
+
// functions of src/store/convex. The store lives
|
|
8
|
+
// outside every resource that a stack manages, so a stack can create and
|
|
9
|
+
// destroy its own project. See docs/design/convex-state.md.
|
|
12
10
|
//
|
|
13
11
|
// Bootstrap, on the first call of the run: the team token (CONVEX_ACCESS_TOKEN,
|
|
14
12
|
// or the Convex CLI login) finds the project by name, or creates it; a deploy
|
|
15
13
|
// key of its production deployment that can run internal queries and
|
|
16
14
|
// mutations, and nothing else, is created for the run and deleted at its
|
|
17
|
-
// end; the `state:version` query says the
|
|
18
|
-
//
|
|
19
|
-
// pushes src/store/convex to it, with a push key that is deleted after
|
|
20
|
-
// push.
|
|
21
|
-
// base app. The raw state functions are internal; authenticated readers get
|
|
15
|
+
// end; the `state:version` query says whether the deployment has the store
|
|
16
|
+
// of this version. Without one, or with an older one, the installed Convex
|
|
17
|
+
// CLI pushes src/store/convex to it, with a push key that is deleted after
|
|
18
|
+
// the push. The raw state functions are internal; authenticated readers get
|
|
22
19
|
// repository-scoped subscriptions with secret values removed. A key that stops working during a
|
|
23
20
|
// long run (it expires after two hours) is replaced once per call.
|
|
24
21
|
//
|
|
25
|
-
// Two first runs of a team can both create a project named `
|
|
22
|
+
// Two first runs of a team can both create a project named `alchemy-state-store`.
|
|
26
23
|
// The run whose project is not the oldest deletes its own, before it writes
|
|
27
24
|
// anything, and uses the oldest: both runs end on one store.
|
|
28
25
|
//
|
|
29
26
|
// Samebase reads `stacks`, `getOutput`, `readStack`, and `reader:repository`
|
|
30
|
-
// by name.
|
|
31
|
-
//
|
|
32
|
-
// with a store of its own version. An older
|
|
33
|
-
//
|
|
27
|
+
// by name. The store and its readers are released and upgraded together:
|
|
28
|
+
// STORE_VERSION rises with each change of a function, and a client works only
|
|
29
|
+
// with a store of its own version. An older store is pushed over; a newer one
|
|
30
|
+
// fails the run.
|
|
34
31
|
//
|
|
35
32
|
// The stage output that `setOutput` stores is the stack's output plus the
|
|
36
33
|
// run record of src/RunRecord.ts under the key `samebase.run`: the
|
|
@@ -83,7 +80,7 @@ import { type RunRecord, runRecord, withRunRecord } from "./RunRecord.ts";
|
|
|
83
80
|
import { STORE_VERSION } from "./store/convex/version.ts";
|
|
84
81
|
|
|
85
82
|
/** The name of the state project in each team. */
|
|
86
|
-
export const STORE_PROJECT_NAME = "
|
|
83
|
+
export const STORE_PROJECT_NAME = "alchemy-state-store";
|
|
87
84
|
|
|
88
85
|
/**
|
|
89
86
|
* The actions of the key of one run: the internal queries and mutations of
|
|
@@ -244,39 +241,6 @@ const storeVersion = (api: FunctionApi) =>
|
|
|
244
241
|
),
|
|
245
242
|
);
|
|
246
243
|
|
|
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
|
-
|
|
280
244
|
/**
|
|
281
245
|
* The functions directory of the store app, `src/store/convex` of this
|
|
282
246
|
* package. This file is `src/State.ts` in a checkout and a chunk in `lib/`
|
|
@@ -288,7 +252,7 @@ export const storeDirectory = () =>
|
|
|
288
252
|
join(dirname(fileURLToPath(import.meta.url)), "..", "src", "store", "convex");
|
|
289
253
|
|
|
290
254
|
export interface StoreOptions {
|
|
291
|
-
/** Team slug or numeric team id of the `
|
|
255
|
+
/** Team slug or numeric team id of the `alchemy-state-store` project, or undefined with a team access token, which names its team. */
|
|
292
256
|
readonly team: string | number | undefined;
|
|
293
257
|
readonly api: ManagementApiService;
|
|
294
258
|
readonly spawner: ChildProcessSpawner.ChildProcessSpawner["Service"];
|
|
@@ -365,9 +329,9 @@ const createStoreProject = (api: ManagementApiService, teamId: number, team: str
|
|
|
365
329
|
});
|
|
366
330
|
|
|
367
331
|
/**
|
|
368
|
-
* The StateService over the `
|
|
369
|
-
* created, its store
|
|
370
|
-
*
|
|
332
|
+
* The StateService over the `alchemy-state-store` project of the team: found or
|
|
333
|
+
* created, its store pushed when it is missing or older, and read and
|
|
334
|
+
* written with the keys of this run.
|
|
371
335
|
*/
|
|
372
336
|
export const makeConvexState = (options: StoreOptions) =>
|
|
373
337
|
Effect.gen(function* () {
|
|
@@ -415,38 +379,22 @@ export const makeConvexState = (options: StoreOptions) =>
|
|
|
415
379
|
),
|
|
416
380
|
};
|
|
417
381
|
|
|
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);
|
|
426
382
|
let version = yield* storeVersion(functions);
|
|
427
|
-
if (
|
|
383
|
+
if (version === undefined || version < STORE_VERSION) {
|
|
428
384
|
// Code without `state:version` is another app with the name of the store.
|
|
429
385
|
if (version === undefined && deployment.lastDeployTime) {
|
|
430
386
|
return yield* failure(
|
|
431
387
|
`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.`,
|
|
432
388
|
);
|
|
433
389
|
}
|
|
434
|
-
// Another run can push a newer store
|
|
435
|
-
//
|
|
436
|
-
// the design note).
|
|
390
|
+
// Another run can push a newer store in the meantime: read again right
|
|
391
|
+
// before the push. The push itself has no lock (see the design note).
|
|
437
392
|
version = yield* storeVersion(functions);
|
|
438
|
-
if (
|
|
393
|
+
if (version === undefined || version < STORE_VERSION) {
|
|
439
394
|
yield* pushStore(options, deployment);
|
|
440
395
|
version = yield* storeVersion(functions);
|
|
441
396
|
}
|
|
442
397
|
}
|
|
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
|
-
}
|
|
450
398
|
if (version !== STORE_VERSION) {
|
|
451
399
|
return yield* failure(
|
|
452
400
|
`${deployment.name} answers store version ${version ?? "none"}, and this package needs store version ${STORE_VERSION}.`,
|
|
@@ -493,12 +441,11 @@ export const makeConvexState = (options: StoreOptions) =>
|
|
|
493
441
|
});
|
|
494
442
|
|
|
495
443
|
/**
|
|
496
|
-
* Alchemy state in the `
|
|
444
|
+
* Alchemy state in the `alchemy-state-store` project of `team`, with the team token
|
|
497
445
|
* in CONVEX_ACCESS_TOKEN or the Convex CLI login. A team access token names
|
|
498
446
|
* its team, so `team` is optional with one; the Convex CLI login needs the
|
|
499
447
|
* slug or the id. The first use of the run finds or creates the project and
|
|
500
|
-
* pushes the store
|
|
501
|
-
* base fails the run with {@link OutdatedBase}. `alchemy plan` on another
|
|
448
|
+
* pushes the store when it is missing or older. `alchemy plan` on another
|
|
502
449
|
* stack never touches Convex.
|
|
503
450
|
*/
|
|
504
451
|
export const state = (
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export {
|
|
|
19
19
|
ProductionDeploymentNotFound,
|
|
20
20
|
} from "./Deployment.ts";
|
|
21
21
|
export {
|
|
22
|
+
HUB_PROJECT_NAME,
|
|
22
23
|
HubClient,
|
|
23
24
|
type HubClientAttributes,
|
|
24
25
|
HubClientFailed,
|
|
@@ -45,7 +46,7 @@ export {
|
|
|
45
46
|
runRecord,
|
|
46
47
|
withRunRecord,
|
|
47
48
|
} from "./RunRecord.ts";
|
|
48
|
-
export {
|
|
49
|
+
export { state, STORE_PROJECT_NAME } from "./State.ts";
|
|
49
50
|
export {
|
|
50
51
|
CodePushFailed,
|
|
51
52
|
ConvexAppNotFound,
|
|
@@ -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.ts";
|
|
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.ts";
|
|
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.ts";
|
|
12
12
|
|
|
13
13
|
const stage = { stack: v.string(), stage: v.string() };
|
|
14
14
|
const resource = { ...stage, fqn: v.string() };
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { AuthConfig } from "convex/server";
|
|
2
2
|
import { env } from "./_generated/server.js";
|
|
3
3
|
|
|
4
|
-
export default
|
|
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;
|
|
@@ -1,5 +1,51 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// Authenticated reads stay in the app: `authorize` signs and `repository`
|
|
2
|
+
// verifies with `ctx.auth`, which a component does not have. The rows and the
|
|
3
|
+
// redaction are in the alchemyState component.
|
|
4
|
+
import { v } from "convex/values";
|
|
5
|
+
import { repositoryReply } from "../component/reader.ts";
|
|
3
6
|
import { components } from "./_generated/api.js";
|
|
7
|
+
import { env, internalMutation, query } from "./_generated/server.js";
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
const repositoryName = /^[^/]+\/[^/]+$/;
|
|
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
|
+
});
|