@samebase/alchemy-convex 0.4.0 → 0.5.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 +26 -2
- package/lib/index.d.ts +42 -30
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +187 -1
- package/lib/index.js.map +1 -1
- package/package.json +18 -15
- package/src/State.ts +316 -0
- package/src/component/_generated/api.ts +50 -0
- package/src/component/_generated/component.ts +92 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +169 -0
- package/src/component/convex.config.ts +5 -0
- package/src/component/schema.ts +19 -0
- package/src/component/state.ts +169 -0
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -12,16 +12,19 @@ This is a community provider, maintained by [Samebase](https://samebase.com). Th
|
|
|
12
12
|
follows the "Convex Plain" mode of [rjdellecese/confect#408](https://github.com/rjdellecese/confect/issues/408).
|
|
13
13
|
[docs/design/convex-plane.md](docs/design/convex-plane.md) records the decisions.
|
|
14
14
|
|
|
15
|
-
Status: pinned to `alchemy@2.0.0-beta.
|
|
15
|
+
Status: pinned to `alchemy@2.0.0-beta.83` and Effect 4. Alchemy ships breaking changes between
|
|
16
16
|
betas; upgrade this package and Alchemy together. A live test runs the full sequence with the real
|
|
17
17
|
Convex CLI: create, push, no-op, change, preview, deploy keys alone, and destroy.
|
|
18
18
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
21
21
|
```sh
|
|
22
|
-
pnpm add -D @samebase/alchemy-convex alchemy@2.0.0-beta.
|
|
22
|
+
pnpm add -D @samebase/alchemy-convex alchemy@2.0.0-beta.83 effect@^4.0.0 @effect/platform-node@^4.0.0 convex
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
Use one version for `effect` and for each `@effect/*` package. A mix fails at load time, such as
|
|
26
|
+
`@effect/platform-node-shared` 4.0.5 next to `effect` 4.0.3.
|
|
27
|
+
|
|
25
28
|
## Example
|
|
26
29
|
|
|
27
30
|
```ts
|
|
@@ -87,6 +90,27 @@ reads them through the stack's config provider (process env, `.env`, stack secre
|
|
|
87
90
|
A key that does not fit the deployment is not used: the provider uses the access token. To pass
|
|
88
91
|
values explicitly: `Convex.providers(Convex.fromValues({ accessToken, deployKey }))`.
|
|
89
92
|
|
|
93
|
+
## State in Convex
|
|
94
|
+
|
|
95
|
+
`state: Convex.state()` keeps the Alchemy state of every stage in the production deployment of the
|
|
96
|
+
project, so the stack needs no Cloudflare token.
|
|
97
|
+
[docs/design/convex-state.md](docs/design/convex-state.md) records the decisions.
|
|
98
|
+
|
|
99
|
+
1. Mount the state component in `convex/convex.config.ts` with its default name:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import alchemyState from "@samebase/alchemy-convex/convex.config";
|
|
103
|
+
app.use(alchemyState);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
2. Set `CONVEX_STATE_DEPLOY_KEY` in every run, a preview run too, to a production deploy key. A key
|
|
107
|
+
with only the actions `deployment:data:view` and `deployment:data:write` is sufficient.
|
|
108
|
+
|
|
109
|
+
The first run starts with empty state, keeps it in memory until `Convex.Deployment` has pushed the
|
|
110
|
+
component, and then writes it. A run whose push does not mount the component fails at the end.
|
|
111
|
+
Rows are in the `alchemyState` component tables `resources` and `outputs` in the dashboard. The
|
|
112
|
+
store has no lock: run one deploy of a stage at a time, such as with a CI concurrency group.
|
|
113
|
+
|
|
90
114
|
## Resources
|
|
91
115
|
|
|
92
116
|
- `Convex.Project`: `{ team, name, deploymentRegion?, deploymentType? }` creates a project with its
|
package/lib/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { Resource } from "alchemy";
|
|
|
7
7
|
import * as Provider from "alchemy/Provider";
|
|
8
8
|
import "effect/process/ChildProcessSpawner";
|
|
9
9
|
import { createDeploymentClient, createManagementClient } from "@convex-dev/platform";
|
|
10
|
+
import { State, StateStoreError } from "alchemy/State";
|
|
10
11
|
import { components } from "@convex-dev/platform/managementApi";
|
|
11
12
|
//#region src/Credentials.d.ts
|
|
12
13
|
interface ResolvedCredentials {
|
|
@@ -24,15 +25,15 @@ interface CredentialsService {
|
|
|
24
25
|
readonly deployKey: Effect.Effect<Redacted.Redacted<string> | undefined>;
|
|
25
26
|
}
|
|
26
27
|
declare const Credentials_base: Context.ServiceClass<Credentials, "@samebase/alchemy-convex/Credentials", CredentialsService>;
|
|
27
|
-
declare class Credentials extends Credentials_base {}
|
|
28
|
+
export declare class Credentials extends Credentials_base {}
|
|
28
29
|
/** Environment variable for the access token, read by {@link fromConfig}. */
|
|
29
|
-
declare const ACCESS_TOKEN_ENV = "CONVEX_ACCESS_TOKEN";
|
|
30
|
+
export declare const ACCESS_TOKEN_ENV = "CONVEX_ACCESS_TOKEN";
|
|
30
31
|
/** Environment variable for the deploy key, read by {@link fromConfig}. The Convex CLI reads the same name. */
|
|
31
|
-
declare const DEPLOY_KEY_ENV = "CONVEX_DEPLOY_KEY";
|
|
32
|
+
export declare const DEPLOY_KEY_ENV = "CONVEX_DEPLOY_KEY";
|
|
32
33
|
declare const CredentialsError_base: Schema.Class<CredentialsError, Schema.TaggedStruct<"CredentialsError", {
|
|
33
34
|
readonly reason: Schema.String;
|
|
34
35
|
}>, import("effect/Cause").YieldableError>;
|
|
35
|
-
declare class CredentialsError extends CredentialsError_base {}
|
|
36
|
+
export declare class CredentialsError extends CredentialsError_base {}
|
|
36
37
|
/**
|
|
37
38
|
* Default resolution through the stack ConfigProvider (process env, `.env`,
|
|
38
39
|
* stack secrets): `CONVEX_ACCESS_TOKEN`, then the Convex CLI login on this
|
|
@@ -41,9 +42,9 @@ declare class CredentialsError extends CredentialsError_base {}
|
|
|
41
42
|
* deploy key when the stack pushes to deployments that exist, and the access
|
|
42
43
|
* token when the stack also creates projects or deployments.
|
|
43
44
|
*/
|
|
44
|
-
declare const fromConfig: () => Layer.Layer<Credentials>;
|
|
45
|
+
export declare const fromConfig: () => Layer.Layer<Credentials>;
|
|
45
46
|
/** Explicit values, for tests or callers that already hold them. */
|
|
46
|
-
declare const fromValues: (values: {
|
|
47
|
+
export declare const fromValues: (values: {
|
|
47
48
|
readonly accessToken?: Redacted.Redacted<string>;
|
|
48
49
|
readonly deployKey?: Redacted.Redacted<string>;
|
|
49
50
|
}) => Layer.Layer<Credentials>;
|
|
@@ -68,7 +69,7 @@ declare const ConvexApiError_base: Schema.Class<ConvexApiError, Schema.TaggedStr
|
|
|
68
69
|
* `status` (404 means absent for read and delete), so the class is load
|
|
69
70
|
* bearing, not decorative.
|
|
70
71
|
*/
|
|
71
|
-
declare class ConvexApiError extends ConvexApiError_base {}
|
|
72
|
+
export declare class ConvexApiError extends ConvexApiError_base {}
|
|
72
73
|
interface ManagementApiService {
|
|
73
74
|
/** Run one management call with the resolved credentials. */
|
|
74
75
|
readonly request: <T>(operation: string, run: (client: ManagementClient) => Promise<FetchResult<T>>) => Effect.Effect<T, ConvexApiError>;
|
|
@@ -93,17 +94,17 @@ interface ManagementApiService {
|
|
|
93
94
|
readonly withToken: (token: Redacted.Redacted<string>) => ManagementApiService;
|
|
94
95
|
}
|
|
95
96
|
declare const ManagementApi_base: Context.ServiceClass<ManagementApi, "@samebase/alchemy-convex/ManagementApi", ManagementApiService>;
|
|
96
|
-
declare class ManagementApi extends ManagementApi_base {}
|
|
97
|
+
export declare class ManagementApi extends ManagementApi_base {}
|
|
97
98
|
//#endregion
|
|
98
99
|
//#region src/Providers.d.ts
|
|
99
100
|
declare const Providers_base: Provider.ProviderCollection<Providers, "Convex">;
|
|
100
|
-
declare class Providers extends Providers_base {}
|
|
101
|
+
export declare class Providers extends Providers_base {}
|
|
101
102
|
/**
|
|
102
103
|
* The Convex resource providers. Pass a credentials layer to override the
|
|
103
104
|
* default lookup (`CONVEX_ACCESS_TOKEN`, then the Convex CLI login, and
|
|
104
105
|
* `CONVEX_DEPLOY_KEY`).
|
|
105
106
|
*/
|
|
106
|
-
declare const providers: (credentials?: Layer.Layer<Credentials>) => Layer.Layer<Providers, never, import("effect/
|
|
107
|
+
export declare const providers: (credentials?: Layer.Layer<Credentials>) => Layer.Layer<Providers, never, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/process/ChildProcessSpawner").ChildProcessSpawner>;
|
|
107
108
|
//#endregion
|
|
108
109
|
//#region src/Push.d.ts
|
|
109
110
|
/** A value of the `env` prop: a string, or a secret such as `Config.Redacted("AUTH_SECRET")`. */
|
|
@@ -135,14 +136,14 @@ declare const ConvexCliNotFound_base: Schema.Class<ConvexCliNotFound, Schema.Tag
|
|
|
135
136
|
readonly cwd: Schema.String;
|
|
136
137
|
}>, import("effect/Cause").YieldableError>;
|
|
137
138
|
/** No `convex` package resolves from the app directory. */
|
|
138
|
-
declare class ConvexCliNotFound extends ConvexCliNotFound_base {
|
|
139
|
+
export declare class ConvexCliNotFound extends ConvexCliNotFound_base {
|
|
139
140
|
get message(): string;
|
|
140
141
|
}
|
|
141
142
|
declare const ConvexAppNotFound_base: Schema.Class<ConvexAppNotFound, Schema.TaggedStruct<"ConvexAppNotFound", {
|
|
142
143
|
readonly source: Schema.String;
|
|
143
144
|
}>, import("effect/Cause").YieldableError>;
|
|
144
145
|
/** No directory at or above the parent of `source` has a package.json. */
|
|
145
|
-
declare class ConvexAppNotFound extends ConvexAppNotFound_base {
|
|
146
|
+
export declare class ConvexAppNotFound extends ConvexAppNotFound_base {
|
|
146
147
|
get message(): string;
|
|
147
148
|
}
|
|
148
149
|
declare const CodePushFailed_base: Schema.Class<CodePushFailed, Schema.TaggedStruct<"CodePushFailed", {
|
|
@@ -152,7 +153,7 @@ declare const CodePushFailed_base: Schema.Class<CodePushFailed, Schema.TaggedStr
|
|
|
152
153
|
readonly outputTail: Schema.String;
|
|
153
154
|
}>, import("effect/Cause").YieldableError>;
|
|
154
155
|
/** The Convex CLI failed, or exited 0 without the deployment URL. The output tail never contains the key. */
|
|
155
|
-
declare class CodePushFailed extends CodePushFailed_base {
|
|
156
|
+
export declare class CodePushFailed extends CodePushFailed_base {
|
|
156
157
|
get message(): string;
|
|
157
158
|
}
|
|
158
159
|
declare const PushTargetMismatch_base: Schema.Class<PushTargetMismatch, Schema.TaggedStruct<"PushTargetMismatch", {
|
|
@@ -160,7 +161,7 @@ declare const PushTargetMismatch_base: Schema.Class<PushTargetMismatch, Schema.T
|
|
|
160
161
|
readonly actual: Schema.String;
|
|
161
162
|
}>, import("effect/Cause").YieldableError>;
|
|
162
163
|
/** The CLI pushed to another deployment than the one of the resource. Checked after the push. */
|
|
163
|
-
declare class PushTargetMismatch extends PushTargetMismatch_base {
|
|
164
|
+
export declare class PushTargetMismatch extends PushTargetMismatch_base {
|
|
164
165
|
get message(): string;
|
|
165
166
|
}
|
|
166
167
|
declare const VariablesOwnedBySomeoneElse_base: Schema.Class<VariablesOwnedBySomeoneElse, Schema.TaggedStruct<"VariablesOwnedBySomeoneElse", {
|
|
@@ -168,18 +169,18 @@ declare const VariablesOwnedBySomeoneElse_base: Schema.Class<VariablesOwnedBySom
|
|
|
168
169
|
readonly names: Schema.$Array<Schema.String>;
|
|
169
170
|
}>, import("effect/Cause").YieldableError>;
|
|
170
171
|
/** Variables in `env` exist on the deployment with other values, and adoption is off. */
|
|
171
|
-
declare class VariablesOwnedBySomeoneElse extends VariablesOwnedBySomeoneElse_base {
|
|
172
|
+
export declare class VariablesOwnedBySomeoneElse extends VariablesOwnedBySomeoneElse_base {
|
|
172
173
|
get message(): string;
|
|
173
174
|
}
|
|
174
175
|
declare const EnvironmentNeedsDeployKey_base: Schema.Class<EnvironmentNeedsDeployKey, Schema.TaggedStruct<"EnvironmentNeedsDeployKey", {
|
|
175
176
|
readonly deployment: Schema.String;
|
|
176
177
|
}>, import("effect/Cause").YieldableError>;
|
|
177
178
|
/** A preview deploy key cannot set variables: the deployment API takes only a deploy key of the deployment. */
|
|
178
|
-
declare class EnvironmentNeedsDeployKey extends EnvironmentNeedsDeployKey_base {
|
|
179
|
+
export declare class EnvironmentNeedsDeployKey extends EnvironmentNeedsDeployKey_base {
|
|
179
180
|
get message(): string;
|
|
180
181
|
}
|
|
181
182
|
/** Reads the deployment URL from `convex deploy` output. ANSI escape codes are removed first. */
|
|
182
|
-
declare const parseDeployOutput: (text: string) => {
|
|
183
|
+
export declare const parseDeployOutput: (text: string) => {
|
|
183
184
|
url: string;
|
|
184
185
|
deploymentName: string;
|
|
185
186
|
} | undefined;
|
|
@@ -233,8 +234,8 @@ interface DeploymentAttributes extends DeploymentTarget {
|
|
|
233
234
|
/** The names of the `env` variables that this resource set on the deployment, sorted. */
|
|
234
235
|
readonly envNames: readonly string[];
|
|
235
236
|
}
|
|
236
|
-
type Deployment = Resource<"Convex.Deployment", DeploymentProps, DeploymentAttributes, never, Providers>;
|
|
237
|
-
declare const Deployment: import("alchemy").ResourceClass<Deployment>;
|
|
237
|
+
export type Deployment = Resource<"Convex.Deployment", DeploymentProps, DeploymentAttributes, never, Providers>;
|
|
238
|
+
export declare const Deployment: import("alchemy").ResourceClass<Deployment>;
|
|
238
239
|
declare const DeploymentIdentityChange_base: Schema.Class<DeploymentIdentityChange, Schema.TaggedStruct<"DeploymentIdentityChange", {
|
|
239
240
|
/** Name of the deployment in state, such as `happy-animal-123`. */
|
|
240
241
|
readonly deployment: Schema.String;
|
|
@@ -243,14 +244,14 @@ declare const DeploymentIdentityChange_base: Schema.Class<DeploymentIdentityChan
|
|
|
243
244
|
readonly requested: Schema.String;
|
|
244
245
|
}>, import("effect/Cause").YieldableError>;
|
|
245
246
|
/** The props name another deployment than the one in state. */
|
|
246
|
-
declare class DeploymentIdentityChange extends DeploymentIdentityChange_base {
|
|
247
|
+
export declare class DeploymentIdentityChange extends DeploymentIdentityChange_base {
|
|
247
248
|
get message(): string;
|
|
248
249
|
}
|
|
249
250
|
declare const ProductionDeploymentNotFound_base: Schema.Class<ProductionDeploymentNotFound, Schema.TaggedStruct<"ProductionDeploymentNotFound", {
|
|
250
251
|
readonly projectId: Schema.Number;
|
|
251
252
|
}>, import("effect/Cause").YieldableError>;
|
|
252
253
|
/** The project has no default production deployment for a `prod` resource to take over. */
|
|
253
|
-
declare class ProductionDeploymentNotFound extends ProductionDeploymentNotFound_base {
|
|
254
|
+
export declare class ProductionDeploymentNotFound extends ProductionDeploymentNotFound_base {
|
|
254
255
|
get message(): string;
|
|
255
256
|
}
|
|
256
257
|
declare const AmbiguousDeployment_base: Schema.Class<AmbiguousDeployment, Schema.TaggedStruct<"AmbiguousDeployment", {
|
|
@@ -260,7 +261,7 @@ declare const AmbiguousDeployment_base: Schema.Class<AmbiguousDeployment, Schema
|
|
|
260
261
|
readonly deployments: Schema.$Array<Schema.String>;
|
|
261
262
|
}>, import("effect/Cause").YieldableError>;
|
|
262
263
|
/** More than one deployment of the project has the name, so the lookup cannot choose one. */
|
|
263
|
-
declare class AmbiguousDeployment extends AmbiguousDeployment_base {
|
|
264
|
+
export declare class AmbiguousDeployment extends AmbiguousDeployment_base {
|
|
264
265
|
get message(): string;
|
|
265
266
|
}
|
|
266
267
|
//#endregion
|
|
@@ -290,15 +291,15 @@ interface ProjectAttributes {
|
|
|
290
291
|
readonly prodDeploymentName: string | undefined;
|
|
291
292
|
readonly prodDeploymentUrl: string | undefined;
|
|
292
293
|
}
|
|
293
|
-
type Project = Resource<"Convex.Project", ProjectProps, ProjectAttributes, never, Providers>;
|
|
294
|
-
declare const Project: import("alchemy").ResourceClass<Project>;
|
|
294
|
+
export type Project = Resource<"Convex.Project", ProjectProps, ProjectAttributes, never, Providers>;
|
|
295
|
+
export declare const Project: import("alchemy").ResourceClass<Project>;
|
|
295
296
|
declare const ProjectTeamChange_base: Schema.Class<ProjectTeamChange, Schema.TaggedStruct<"ProjectTeamChange", {
|
|
296
297
|
readonly projectId: Schema.Number;
|
|
297
298
|
readonly teamId: Schema.Number;
|
|
298
299
|
readonly requestedTeamId: Schema.Number;
|
|
299
300
|
}>, import("effect/Cause").YieldableError>;
|
|
300
301
|
/** The props change the team of an existing project. Convex cannot move a project. */
|
|
301
|
-
declare class ProjectTeamChange extends ProjectTeamChange_base {
|
|
302
|
+
export declare class ProjectTeamChange extends ProjectTeamChange_base {
|
|
302
303
|
get message(): string;
|
|
303
304
|
}
|
|
304
305
|
declare const ProjectDeploymentChange_base: Schema.Class<ProjectDeploymentChange, Schema.TaggedStruct<"ProjectDeploymentChange", {
|
|
@@ -311,7 +312,7 @@ declare const ProjectDeploymentChange_base: Schema.Class<ProjectDeploymentChange
|
|
|
311
312
|
* The props change `deploymentType` or `deploymentRegion` of an existing
|
|
312
313
|
* project. Convex uses them only when it creates the project.
|
|
313
314
|
*/
|
|
314
|
-
declare class ProjectDeploymentChange extends ProjectDeploymentChange_base {
|
|
315
|
+
export declare class ProjectDeploymentChange extends ProjectDeploymentChange_base {
|
|
315
316
|
get message(): string;
|
|
316
317
|
}
|
|
317
318
|
declare const AmbiguousProject_base: Schema.Class<AmbiguousProject, Schema.TaggedStruct<"AmbiguousProject", {
|
|
@@ -320,14 +321,14 @@ declare const AmbiguousProject_base: Schema.Class<AmbiguousProject, Schema.Tagge
|
|
|
320
321
|
readonly projectIds: Schema.$Array<Schema.Number>;
|
|
321
322
|
}>, import("effect/Cause").YieldableError>;
|
|
322
323
|
/** More than one project in the team has the name, so the lookup cannot choose one. */
|
|
323
|
-
declare class AmbiguousProject extends AmbiguousProject_base {
|
|
324
|
+
export declare class AmbiguousProject extends AmbiguousProject_base {
|
|
324
325
|
get message(): string;
|
|
325
326
|
}
|
|
326
327
|
declare const CreatedProjectNotFound_base: Schema.Class<CreatedProjectNotFound, Schema.TaggedStruct<"CreatedProjectNotFound", {
|
|
327
328
|
readonly projectId: Schema.Number;
|
|
328
329
|
}>, import("effect/Cause").YieldableError>;
|
|
329
330
|
/** Convex answered a create call but then did not return the project. */
|
|
330
|
-
declare class CreatedProjectNotFound extends CreatedProjectNotFound_base {
|
|
331
|
+
export declare class CreatedProjectNotFound extends CreatedProjectNotFound_base {
|
|
331
332
|
get message(): string;
|
|
332
333
|
}
|
|
333
334
|
declare const ProjectNotFound_base: Schema.Class<ProjectNotFound, Schema.TaggedStruct<"ProjectNotFound", {
|
|
@@ -335,9 +336,20 @@ declare const ProjectNotFound_base: Schema.Class<ProjectNotFound, Schema.TaggedS
|
|
|
335
336
|
readonly slug: Schema.String;
|
|
336
337
|
}>, import("effect/Cause").YieldableError>;
|
|
337
338
|
/** No project in the team has the slug. A `slug` resource never creates one. */
|
|
338
|
-
declare class ProjectNotFound extends ProjectNotFound_base {
|
|
339
|
+
export declare class ProjectNotFound extends ProjectNotFound_base {
|
|
339
340
|
get message(): string;
|
|
340
341
|
}
|
|
341
342
|
//#endregion
|
|
342
|
-
|
|
343
|
+
//#region src/State.d.ts
|
|
344
|
+
/** Environment variable with the deploy key of the deployment that holds the state. */
|
|
345
|
+
export declare const STATE_DEPLOY_KEY_ENV = "CONVEX_STATE_DEPLOY_KEY";
|
|
346
|
+
/**
|
|
347
|
+
* Alchemy state in the alchemyState component of the deployment that the
|
|
348
|
+
* deploy key in CONVEX_STATE_DEPLOY_KEY names: the production deployment of
|
|
349
|
+
* the project. The key needs the actions `deployment:data:view` and
|
|
350
|
+
* `deployment:data:write`.
|
|
351
|
+
*/
|
|
352
|
+
export declare const state: () => Layer.Layer<State>;
|
|
353
|
+
//#endregion
|
|
354
|
+
export type { DeploymentAttributes, DeploymentProps, DeploymentRegion, EnvValue, ProjectAttributes, ProjectProps, Source };
|
|
343
355
|
//# sourceMappingURL=index.d.ts.map
|
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/Project.ts"],"mappings":"
|
|
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/Project.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;;WAEb,gBAAgB,0BAA0B,OAAO,eAAe;;;;;;;;WAQhE,oBAAoB,mBAAmB,OAAO;;;;;WAK9C,YAAY,OAAO,SAAS,qBAAqB;;;qBAM/C,sBAAsB;;;;qBClFtB,kBAAkB;;;;;;qBAOlB,YAAa,cAAa,MAAM,MAAM,iBAA2B,MAAA,MAAA,8CAAA,mCAAA,oDAAA;;;;KC8BlE,oBAAoB,SAAS;;;;;KAM7B;;WAIG;;;;;;;;WAQA;;;;;;;WAOA;;;;;;qBA2BF,0BAA0B;MAIxB;;;;;;qBAMF,0BAA0B;MAIxB;;;;;;;;;qBAMF,uBAAuB;MAMrB;;;;;;;qBAQF,2BAA2B;MAIzB;;;;;;;qBAMF,oCAAoC;MAIlC;;;;;;qBAMF,kCAAkC;MAIhC;;;qBAuCF,oBACX;EACG;EAAa;;;;UCzHR;;WAEC;aAAoB;;;;;;;;WAOpB,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,6BAEA;qBAEW,8BAAU,cAAA;;;;;;;;;qBAKV,iCAAiC;MAU/B;;;;;;qBAMF,qCAAqC;MAInC;;;;;;;;;qBAMF,4BAA4B;MAS1B;;;;;KChJH,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;;;;;qBC7EF;;;;;;;qBAgQA,aAAY,MAAM,MAAM"}
|
package/lib/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import { hashDirectory } from "alchemy/Command/Memo";
|
|
|
25
25
|
import * as Clock from "effect/Clock";
|
|
26
26
|
import * as ChildProcess from "effect/process/ChildProcess";
|
|
27
27
|
import * as Stream from "effect/Stream";
|
|
28
|
+
import { InMemoryService, STATE_STORE_VERSION, State, StateStoreError, encodeState, reviveState } from "alchemy/State";
|
|
28
29
|
//#region src/Credentials.ts
|
|
29
30
|
var Credentials = class extends Context.Service()("@samebase/alchemy-convex/Credentials") {};
|
|
30
31
|
/** Environment variable for the access token, read by {@link fromConfig}. */
|
|
@@ -1256,6 +1257,191 @@ var Providers = class extends Provider.ProviderCollection()("Convex") {};
|
|
|
1256
1257
|
*/
|
|
1257
1258
|
const providers = (credentials = fromConfig()) => Layer.effect(Providers, Provider.collection([Project, Deployment])).pipe(Layer.provide(Layer.mergeAll(ProjectProvider(), DeploymentProvider())), Layer.provide(ManagementApiLive()), Layer.provide(credentials), Layer.orDie);
|
|
1258
1259
|
//#endregion
|
|
1259
|
-
|
|
1260
|
+
//#region src/State.ts
|
|
1261
|
+
/** Environment variable with the deploy key of the deployment that holds the state. */
|
|
1262
|
+
const STATE_DEPLOY_KEY_ENV = "CONVEX_STATE_DEPLOY_KEY";
|
|
1263
|
+
/** The component path of alchemyState in the app: its default name. */
|
|
1264
|
+
const COMPONENT_PATH = "alchemyState";
|
|
1265
|
+
/** The body of POST /api/function and /api/query, with HTTP 200 or 560 for a function error. */
|
|
1266
|
+
const Reply = Schema.Union([Schema.Struct({
|
|
1267
|
+
status: Schema.Literal("success"),
|
|
1268
|
+
value: Schema.Unknown
|
|
1269
|
+
}), Schema.Struct({
|
|
1270
|
+
status: Schema.Literal("error"),
|
|
1271
|
+
errorMessage: Schema.String
|
|
1272
|
+
})]);
|
|
1273
|
+
/** The value of `_system/frontend/components:list`: the root has path "", a removed mount is "unmounted". */
|
|
1274
|
+
const Components = Schema.Array(Schema.Struct({
|
|
1275
|
+
path: Schema.String,
|
|
1276
|
+
state: Schema.String
|
|
1277
|
+
}));
|
|
1278
|
+
const failure = (message) => new StateStoreError({ message: `Convex state: ${message}` });
|
|
1279
|
+
/** No reply, HTTP 429, or HTTP 5xx: a new attempt can succeed. Every call of the store is idempotent. */
|
|
1280
|
+
const isTransient = (error) => error.http !== void 0 && (error.http.status === void 0 || error.http.status === 429 || error.http.status >= 500);
|
|
1281
|
+
const post = (url, headers, body) => Effect.tryPromise({
|
|
1282
|
+
try: () => fetch(url, {
|
|
1283
|
+
method: "POST",
|
|
1284
|
+
headers: {
|
|
1285
|
+
"Content-Type": "application/json",
|
|
1286
|
+
...headers
|
|
1287
|
+
},
|
|
1288
|
+
body: JSON.stringify(body)
|
|
1289
|
+
}),
|
|
1290
|
+
catch: () => new StateStoreError({
|
|
1291
|
+
message: `Convex state: POST ${url} failed`,
|
|
1292
|
+
http: {}
|
|
1293
|
+
})
|
|
1294
|
+
}).pipe(Effect.flatMap((response) => response.ok || response.status === 560 ? Effect.tryPromise({
|
|
1295
|
+
try: () => response.json(),
|
|
1296
|
+
catch: () => failure(`${url} gave no JSON`)
|
|
1297
|
+
}) : Effect.fail(new StateStoreError({
|
|
1298
|
+
message: `Convex state: POST ${url} failed with HTTP ${response.status}`,
|
|
1299
|
+
http: { status: response.status }
|
|
1300
|
+
}))), Effect.retry({
|
|
1301
|
+
while: isTransient,
|
|
1302
|
+
schedule: Schedule.exponential("100 millis").pipe(Schedule.jittered),
|
|
1303
|
+
times: 5
|
|
1304
|
+
}));
|
|
1305
|
+
const decode = (schema, what) => (value) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(() => failure(`bad ${what}`)));
|
|
1306
|
+
/**
|
|
1307
|
+
* The deployment URL of a deploy key. The Convex CLI makes the same unofficial
|
|
1308
|
+
* call for every deploy key (convex 1.46 `cli/lib/deploymentSelection.js`).
|
|
1309
|
+
*/
|
|
1310
|
+
const urlForKey = (key) => post("https://api.convex.dev/api/deployment/url_for_key", {}, { deployKey: Redacted.value(key) }).pipe(Effect.flatMap(decode(Schema.String, "deployment URL")));
|
|
1311
|
+
const makeFunctionApi = (url, key) => ({ call: (path, args, componentPath) => post(`${url}/api/${componentPath === void 0 ? "query" : "function"}`, { Authorization: `Convex ${Redacted.value(key)}` }, {
|
|
1312
|
+
...componentPath === void 0 ? {} : { componentPath },
|
|
1313
|
+
path,
|
|
1314
|
+
args,
|
|
1315
|
+
format: "json"
|
|
1316
|
+
}).pipe(Effect.flatMap(decode(Reply, `reply of ${path}`)), Effect.flatMap((reply) => reply.status === "success" ? Effect.succeed(reply.value) : Effect.fail(failure(reply.errorMessage.trim())))) });
|
|
1317
|
+
/** The state of the alchemyState mount: "active", "unmounted" (its rows are kept), or undefined. */
|
|
1318
|
+
const mountOf = (api) => api.call("_system/frontend/components:list", {}).pipe(Effect.flatMap(decode(Components, "component list")), Effect.map((components) => components.find((c) => c.path === COMPONENT_PATH)?.state));
|
|
1319
|
+
/** The component functions with Alchemy's encoding: the JSON text of encodeState. */
|
|
1320
|
+
const remoteOf = (api) => {
|
|
1321
|
+
const run = (name, args) => api.call(`state:${name}`, args, COMPONENT_PATH);
|
|
1322
|
+
const strings = (name, args) => run(name, args).pipe(Effect.flatMap(decode(Schema.Array(Schema.String), name)));
|
|
1323
|
+
const optional = (name, args) => run(name, args).pipe(Effect.flatMap(decode(Schema.NullOr(Schema.String), name)));
|
|
1324
|
+
const encode = (value) => JSON.stringify(encodeState(value));
|
|
1325
|
+
return {
|
|
1326
|
+
listStacks: () => strings("stacks", {}),
|
|
1327
|
+
listStages: (stack) => strings("stages", { stack }),
|
|
1328
|
+
list: (r) => strings("list", {
|
|
1329
|
+
stack: r.stack,
|
|
1330
|
+
stage: r.stage
|
|
1331
|
+
}),
|
|
1332
|
+
get: (r) => optional("get", {
|
|
1333
|
+
stack: r.stack,
|
|
1334
|
+
stage: r.stage,
|
|
1335
|
+
fqn: r.fqn
|
|
1336
|
+
}).pipe(Effect.map((text) => text === null ? void 0 : JSON.parse(text, reviveState))),
|
|
1337
|
+
getReplacedResources: (r) => strings("replaced", {
|
|
1338
|
+
stack: r.stack,
|
|
1339
|
+
stage: r.stage
|
|
1340
|
+
}).pipe(Effect.map((texts) => texts.map((text) => JSON.parse(text, reviveState)))),
|
|
1341
|
+
getOutput: (r) => optional("getOutput", {
|
|
1342
|
+
stack: r.stack,
|
|
1343
|
+
stage: r.stage
|
|
1344
|
+
}).pipe(Effect.map((text) => text === null ? void 0 : JSON.parse(text, reviveState))),
|
|
1345
|
+
set: (r) => run("set", {
|
|
1346
|
+
stack: r.stack,
|
|
1347
|
+
stage: r.stage,
|
|
1348
|
+
fqn: r.fqn,
|
|
1349
|
+
value: encode(r.value)
|
|
1350
|
+
}),
|
|
1351
|
+
delete: (r) => run("remove", {
|
|
1352
|
+
stack: r.stack,
|
|
1353
|
+
stage: r.stage,
|
|
1354
|
+
fqn: r.fqn
|
|
1355
|
+
}),
|
|
1356
|
+
deleteStack: (r) => run("removeStack", {
|
|
1357
|
+
stack: r.stack,
|
|
1358
|
+
stage: r.stage
|
|
1359
|
+
}),
|
|
1360
|
+
setOutput: (r) => run("setOutput", {
|
|
1361
|
+
stack: r.stack,
|
|
1362
|
+
stage: r.stage,
|
|
1363
|
+
value: encode(r.value)
|
|
1364
|
+
})
|
|
1365
|
+
};
|
|
1366
|
+
};
|
|
1367
|
+
const unmounted = failure(`the ${COMPONENT_PATH} component of the deployment is unmounted. Its rows are kept: mount @samebase/alchemy-convex/convex.config in convex/convex.config.ts again.`);
|
|
1368
|
+
/**
|
|
1369
|
+
* The StateService over `api`. Without an alchemyState component at
|
|
1370
|
+
* the start, the state of the run stays in memory until a write finds the
|
|
1371
|
+
* component active.
|
|
1372
|
+
*/
|
|
1373
|
+
const makeConvexState = (api) => Effect.gen(function* () {
|
|
1374
|
+
const remote = remoteOf(api);
|
|
1375
|
+
const mount = yield* mountOf(api);
|
|
1376
|
+
if (mount === "unmounted") return yield* unmounted;
|
|
1377
|
+
let pending = mount === void 0;
|
|
1378
|
+
const rows = {};
|
|
1379
|
+
const outputs = {};
|
|
1380
|
+
const memory = yield* InMemoryService(rows, outputs);
|
|
1381
|
+
/** Moves the memory to the component once it is active. Rows that the run did not read stop it. */
|
|
1382
|
+
const flush = Effect.gen(function* () {
|
|
1383
|
+
const now = yield* mountOf(api);
|
|
1384
|
+
if (now === void 0) return;
|
|
1385
|
+
if (now !== "active") return yield* unmounted;
|
|
1386
|
+
const stages = [.../* @__PURE__ */ new Set([...Object.keys(rows), ...Object.keys(outputs)])].flatMap((stack) => [.../* @__PURE__ */ new Set([...Object.keys(rows[stack] ?? {}), ...Object.keys(outputs[stack] ?? {})])].map((stage) => ({
|
|
1387
|
+
stack,
|
|
1388
|
+
stage
|
|
1389
|
+
})));
|
|
1390
|
+
for (const { stack, stage } of stages) {
|
|
1391
|
+
const found = yield* remote.list({
|
|
1392
|
+
stack,
|
|
1393
|
+
stage
|
|
1394
|
+
});
|
|
1395
|
+
if (found.length > 0) return yield* failure(`${stack}/${stage} has ${found.length} rows in the deployment that this run did not read, so the run wrote none. Run it again.`);
|
|
1396
|
+
}
|
|
1397
|
+
for (const { stack, stage } of stages) {
|
|
1398
|
+
for (const [fqn, value] of Object.entries(rows[stack]?.[stage] ?? {})) yield* remote.set({
|
|
1399
|
+
stack,
|
|
1400
|
+
stage,
|
|
1401
|
+
fqn,
|
|
1402
|
+
value
|
|
1403
|
+
});
|
|
1404
|
+
const byStage = outputs[stack];
|
|
1405
|
+
if (byStage !== void 0 && stage in byStage) yield* remote.setOutput({
|
|
1406
|
+
stack,
|
|
1407
|
+
stage,
|
|
1408
|
+
value: byStage[stage]
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
pending = false;
|
|
1412
|
+
});
|
|
1413
|
+
const read = (inMemory, inConvex) => Effect.suspend(() => pending ? inMemory : inConvex);
|
|
1414
|
+
const write = (inMemory, inConvex, result) => Effect.suspend(() => pending ? Effect.tap(inMemory, () => flush) : Effect.as(inConvex, result));
|
|
1415
|
+
return {
|
|
1416
|
+
id: "convex",
|
|
1417
|
+
getVersion: () => Effect.succeed(STATE_STORE_VERSION),
|
|
1418
|
+
listStacks: () => read(memory.listStacks(), remote.listStacks()),
|
|
1419
|
+
listStages: (stack) => read(memory.listStages(stack), remote.listStages(stack)),
|
|
1420
|
+
list: (request) => read(memory.list(request), remote.list(request)),
|
|
1421
|
+
get: (request) => read(memory.get(request), remote.get(request)),
|
|
1422
|
+
getReplacedResources: (request) => read(memory.getReplacedResources(request), remote.getReplacedResources(request)),
|
|
1423
|
+
getOutput: (request) => read(memory.getOutput(request), remote.getOutput(request)),
|
|
1424
|
+
set: (request) => write(memory.set(request), remote.set(request), request.value),
|
|
1425
|
+
delete: (request) => write(memory.delete(request), remote.delete(request), void 0),
|
|
1426
|
+
deleteStack: (request) => write(memory.deleteStack(request), remote.deleteStack(request), void 0),
|
|
1427
|
+
setOutput: (request) => write(memory.setOutput(request), remote.setOutput(request), request.value).pipe(Effect.tap(() => pending ? Effect.fail(failure(`the deployment of ${STATE_DEPLOY_KEY_ENV} has no ${COMPONENT_PATH} component at the end of the run, so this run saved no state. Mount @samebase/alchemy-convex/convex.config in convex/convex.config.ts.`)) : Effect.void))
|
|
1428
|
+
};
|
|
1429
|
+
});
|
|
1430
|
+
/**
|
|
1431
|
+
* Alchemy state in the alchemyState component of the deployment that the
|
|
1432
|
+
* deploy key in CONVEX_STATE_DEPLOY_KEY names: the production deployment of
|
|
1433
|
+
* the project. The key needs the actions `deployment:data:view` and
|
|
1434
|
+
* `deployment:data:write`.
|
|
1435
|
+
*/
|
|
1436
|
+
const state = () => Layer.effect(State, Effect.gen(function* () {
|
|
1437
|
+
const context = yield* Effect.context();
|
|
1438
|
+
return yield* Effect.cached(Effect.gen(function* () {
|
|
1439
|
+
const key = yield* Config.Redacted(STATE_DEPLOY_KEY_ENV);
|
|
1440
|
+
if (keyTargetOf(Redacted.value(key)).kind !== "deployment") return yield* failure(`${STATE_DEPLOY_KEY_ENV} must be the deploy key of one deployment, such as prod:<name>|..., not a preview deploy key.`);
|
|
1441
|
+
return yield* makeConvexState(makeFunctionApi(yield* urlForKey(key), key));
|
|
1442
|
+
}).pipe(Effect.provideContext(context), Effect.orDie));
|
|
1443
|
+
}));
|
|
1444
|
+
//#endregion
|
|
1445
|
+
export { ACCESS_TOKEN_ENV, AmbiguousDeployment, AmbiguousProject, CodePushFailed, ConvexApiError, ConvexAppNotFound, ConvexCliNotFound, CreatedProjectNotFound, Credentials, CredentialsError, DEPLOY_KEY_ENV, Deployment, DeploymentIdentityChange, EnvironmentNeedsDeployKey, ManagementApi, ProductionDeploymentNotFound, Project, ProjectDeploymentChange, ProjectNotFound, ProjectTeamChange, Providers, PushTargetMismatch, STATE_DEPLOY_KEY_ENV, VariablesOwnedBySomeoneElse, fromConfig, fromValues, parseDeployOutput, providers, state };
|
|
1260
1446
|
|
|
1261
1447
|
//# sourceMappingURL=index.js.map
|