@samebase/alchemy-convex 0.2.0 → 0.4.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/NOTICE +24 -0
- package/README.md +135 -164
- package/lib/index.d.ts +175 -185
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +800 -502
- package/lib/index.js.map +1 -1
- package/package.json +4 -3
- package/src/Credentials.ts +85 -27
- package/src/Deployment.ts +748 -0
- package/src/ManagementApi.ts +84 -64
- package/src/Project.ts +133 -16
- package/src/Providers.ts +6 -34
- package/src/Push.ts +646 -0
- package/src/index.ts +22 -35
- package/src/DefaultEnvironmentVariable.ts +0 -263
- package/src/Deploy.ts +0 -193
- package/src/DeployKey.ts +0 -332
- package/src/EnvironmentVariable.ts +0 -262
- package/src/PreviewDeployKey.ts +0 -153
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@samebase/alchemy-convex",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Convex
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "The Convex plane for Alchemy v2: projects, deployments, environment variables, and the push of the functions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"alchemy",
|
|
7
7
|
"cloudflare",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"lib",
|
|
23
23
|
"src",
|
|
24
24
|
"README.md",
|
|
25
|
-
"LICENSE"
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"NOTICE"
|
|
26
27
|
],
|
|
27
28
|
"type": "module",
|
|
28
29
|
"sideEffects": false,
|
package/src/Credentials.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
// Convex
|
|
1
|
+
// Convex credentials for the provider, by scope.
|
|
2
2
|
//
|
|
3
|
-
// The
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// - The access token works on the Management API: a team access token, an
|
|
4
|
+
// OAuth token, or the Convex CLI login. Convex.Project needs it. A
|
|
5
|
+
// Convex.Deployment needs it only when the deploy key does not fit.
|
|
6
|
+
// - The deploy key is optional. A deploy key of one deployment
|
|
7
|
+
// (`prod:<name>|...`, `dev:<name>|...`) lets Convex.Deployment set
|
|
8
|
+
// variables and push to that deployment with no access token. A preview
|
|
9
|
+
// deploy key of a project (`preview:<team>:<project>|...`) lets it create,
|
|
10
|
+
// push to, and delete preview deployments of that project with no access
|
|
11
|
+
// token.
|
|
12
|
+
//
|
|
13
|
+
// Credentials are a layer, never props: they do not go into Alchemy state.
|
|
14
|
+
// Both values are Effects, so that building the providers layer never touches
|
|
15
|
+
// the environment or the filesystem. A value is read on the first call that
|
|
16
|
+
// needs it, which keeps `alchemy plan` on an unrelated stack free of Convex
|
|
17
|
+
// credential errors.
|
|
7
18
|
import { readFile } from "node:fs/promises";
|
|
8
19
|
import { homedir } from "node:os";
|
|
9
20
|
import { join } from "node:path";
|
|
@@ -11,7 +22,9 @@ import * as Config from "effect/Config";
|
|
|
11
22
|
import * as Context from "effect/Context";
|
|
12
23
|
import * as Effect from "effect/Effect";
|
|
13
24
|
import * as Layer from "effect/Layer";
|
|
25
|
+
import * as Option from "effect/Option";
|
|
14
26
|
import * as Redacted from "effect/Redacted";
|
|
27
|
+
import * as Result from "effect/Result";
|
|
15
28
|
import * as Schema from "effect/Schema";
|
|
16
29
|
|
|
17
30
|
export interface ResolvedCredentials {
|
|
@@ -21,18 +34,34 @@ export interface ResolvedCredentials {
|
|
|
21
34
|
readonly source: "env" | "explicit" | "convex-cli-login";
|
|
22
35
|
}
|
|
23
36
|
|
|
24
|
-
export
|
|
37
|
+
export interface CredentialsService {
|
|
38
|
+
/** The Management API token. Dies when there is none. */
|
|
39
|
+
readonly accessToken: Effect.Effect<ResolvedCredentials>;
|
|
40
|
+
/** True when there is a Management API token. */
|
|
41
|
+
readonly hasAccessToken: Effect.Effect<boolean>;
|
|
42
|
+
/** A deploy key or a preview deploy key, or undefined. */
|
|
43
|
+
readonly deployKey: Effect.Effect<Redacted.Redacted<string> | undefined>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class Credentials extends Context.Service<Credentials, CredentialsService>()(
|
|
25
47
|
"@samebase/alchemy-convex/Credentials",
|
|
26
48
|
) {}
|
|
27
49
|
|
|
28
|
-
/** Environment variable read by {@link fromConfig}. */
|
|
50
|
+
/** Environment variable for the access token, read by {@link fromConfig}. */
|
|
29
51
|
export const ACCESS_TOKEN_ENV = "CONVEX_ACCESS_TOKEN";
|
|
30
52
|
|
|
53
|
+
/** Environment variable for the deploy key, read by {@link fromConfig}. The Convex CLI reads the same name. */
|
|
54
|
+
export const DEPLOY_KEY_ENV = "CONVEX_DEPLOY_KEY";
|
|
55
|
+
|
|
31
56
|
/** The Convex CLI stores its device login here after `npx convex login`. */
|
|
32
57
|
export const convexCliConfigPath = () => join(homedir(), ".convex", "config.json");
|
|
33
58
|
|
|
34
59
|
const CliConfig = Schema.Struct({ accessToken: Schema.String });
|
|
35
60
|
|
|
61
|
+
export class CredentialsError extends Schema.TaggedError<CredentialsError>()("CredentialsError", {
|
|
62
|
+
reason: Schema.String,
|
|
63
|
+
}) {}
|
|
64
|
+
|
|
36
65
|
/**
|
|
37
66
|
* Token the Convex CLI saved on this machine. It works on the Management API
|
|
38
67
|
* for the teams the logged-in user belongs to. Fails when nobody is logged in.
|
|
@@ -42,7 +71,10 @@ export const readConvexCliLogin: Effect.Effect<ResolvedCredentials, CredentialsE
|
|
|
42
71
|
const path = convexCliConfigPath();
|
|
43
72
|
const text = yield* Effect.tryPromise({
|
|
44
73
|
try: () => readFile(path, "utf8"),
|
|
45
|
-
catch: () =>
|
|
74
|
+
catch: () =>
|
|
75
|
+
new CredentialsError({
|
|
76
|
+
reason: `This step needs the Convex Management API. Set ${ACCESS_TOKEN_ENV} to a team access token, or run \`npx convex login\`. There is no Convex CLI login at ${path}.`,
|
|
77
|
+
}),
|
|
46
78
|
});
|
|
47
79
|
const parsed = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(CliConfig))(text).pipe(
|
|
48
80
|
Effect.mapError(() => new CredentialsError({ reason: `${path} has no accessToken` })),
|
|
@@ -51,28 +83,54 @@ export const readConvexCliLogin: Effect.Effect<ResolvedCredentials, CredentialsE
|
|
|
51
83
|
},
|
|
52
84
|
);
|
|
53
85
|
|
|
54
|
-
export class CredentialsError extends Schema.TaggedError<CredentialsError>()("CredentialsError", {
|
|
55
|
-
reason: Schema.String,
|
|
56
|
-
}) {}
|
|
57
|
-
|
|
58
86
|
/**
|
|
59
|
-
* Default resolution
|
|
60
|
-
*
|
|
61
|
-
* machine
|
|
62
|
-
* nothing.
|
|
87
|
+
* Default resolution through the stack ConfigProvider (process env, `.env`,
|
|
88
|
+
* stack secrets): `CONVEX_ACCESS_TOKEN`, then the Convex CLI login on this
|
|
89
|
+
* machine, for the access token; `CONVEX_DEPLOY_KEY` for the deploy key. A
|
|
90
|
+
* developer who ran `npx convex login` needs nothing. CI sets only the
|
|
91
|
+
* deploy key when the stack pushes to deployments that exist, and the access
|
|
92
|
+
* token when the stack also creates projects or deployments.
|
|
63
93
|
*/
|
|
64
94
|
export const fromConfig = (): Layer.Layer<Credentials> =>
|
|
65
|
-
Layer.
|
|
95
|
+
Layer.effect(
|
|
66
96
|
Credentials,
|
|
67
|
-
Effect.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
97
|
+
Effect.gen(function* () {
|
|
98
|
+
const resolved = yield* Effect.cached(
|
|
99
|
+
Config.Redacted(ACCESS_TOKEN_ENV).pipe(
|
|
100
|
+
Effect.map((token): ResolvedCredentials => ({ accessToken: token, source: "env" })),
|
|
101
|
+
Effect.catch(() => readConvexCliLogin),
|
|
102
|
+
Effect.result,
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
const deployKey = yield* Effect.cached(
|
|
106
|
+
Config.option(Config.Redacted(DEPLOY_KEY_ENV)).pipe(
|
|
107
|
+
Effect.map(Option.getOrUndefined),
|
|
108
|
+
Effect.orDie,
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
return {
|
|
112
|
+
accessToken: resolved.pipe(Effect.flatMap(Effect.fromResult), Effect.orDie),
|
|
113
|
+
hasAccessToken: resolved.pipe(Effect.map(Result.isSuccess)),
|
|
114
|
+
deployKey,
|
|
115
|
+
};
|
|
116
|
+
}),
|
|
74
117
|
);
|
|
75
118
|
|
|
76
|
-
/** Explicit
|
|
77
|
-
export const
|
|
78
|
-
|
|
119
|
+
/** Explicit values, for tests or callers that already hold them. */
|
|
120
|
+
export const fromValues = (values: {
|
|
121
|
+
readonly accessToken?: Redacted.Redacted<string>;
|
|
122
|
+
readonly deployKey?: Redacted.Redacted<string>;
|
|
123
|
+
}): Layer.Layer<Credentials> =>
|
|
124
|
+
Layer.succeed(Credentials, {
|
|
125
|
+
accessToken:
|
|
126
|
+
values.accessToken === undefined
|
|
127
|
+
? Effect.die(
|
|
128
|
+
new CredentialsError({
|
|
129
|
+
reason:
|
|
130
|
+
"This step needs the Convex Management API, and fromValues got no accessToken.",
|
|
131
|
+
}),
|
|
132
|
+
)
|
|
133
|
+
: Effect.succeed({ accessToken: values.accessToken, source: "explicit" } as const),
|
|
134
|
+
hasAccessToken: Effect.succeed(values.accessToken !== undefined),
|
|
135
|
+
deployKey: Effect.succeed(values.deployKey),
|
|
136
|
+
});
|