@samebase/alchemy-convex 0.18.0 → 0.19.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 +96 -69
- package/lib/{HubClient-BZKweNfD.js → HubClient-C0XxgkK3.js} +42 -7
- package/lib/HubClient-C0XxgkK3.js.map +1 -0
- package/lib/cli.js +2 -2
- package/lib/cli.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/sealedSecret-Bf8AeIp_.js +86 -0
- package/lib/sealedSecret-Bf8AeIp_.js.map +1 -0
- package/lib/sealedSecret.d.ts +37 -0
- package/lib/sealedSecret.d.ts.map +1 -0
- package/lib/sealedSecret.js +1 -0
- package/package.json +20 -19
- package/src/Project.ts +1 -1
- package/src/Push.ts +1 -1
- package/src/RunRecord.ts +1 -0
- package/src/StackComment.ts +2 -1
- package/src/State.ts +54 -1
- package/src/store/component/_generated/api.ts +2 -0
- package/src/store/component/_generated/component.ts +85 -1
- package/src/store/component/keys.ts +73 -0
- package/src/store/component/reader.ts +1 -0
- package/src/store/component/runRecord.ts +1 -0
- package/src/store/component/schema.ts +7 -0
- package/src/store/component/state.ts +86 -4
- package/src/store/convex/_generated/api.d.ts +2 -0
- package/src/store/convex/reader.ts +1 -0
- package/src/store/convex/sealing.ts +102 -0
- package/src/store/convex/state.ts +248 -22
- package/src/store/convex/version.ts +3 -1
- package/src/store/sealedSecret.ts +129 -0
- package/lib/HubClient-BZKweNfD.js.map +0 -1
package/README.md
CHANGED
|
@@ -7,9 +7,11 @@ URLs to a Cloudflare Worker or a Vite site as ordinary outputs. No deploy key go
|
|
|
7
7
|
to another. The Convex CLI that your app installs keeps doing typecheck, codegen, bundling, and the
|
|
8
8
|
push.
|
|
9
9
|
|
|
10
|
-
Alchemy has no built-in Convex provider
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
Alchemy has no built-in Convex provider
|
|
11
|
+
([alchemy-run/alchemy-async#1306](https://github.com/alchemy-run/alchemy-async/issues/1306)). This
|
|
12
|
+
is a community provider, maintained by [Samebase](https://samebase.com). The resource model follows
|
|
13
|
+
the "Convex Plain" mode of
|
|
14
|
+
[rjdellecese/confect#408](https://github.com/rjdellecese/confect/issues/408).
|
|
13
15
|
[docs/design/convex-plane.md](docs/design/convex-plane.md) records the decisions.
|
|
14
16
|
|
|
15
17
|
Status: pinned to `alchemy@2.0.0-beta.84` and Effect 4. Alchemy ships breaking changes between
|
|
@@ -66,16 +68,15 @@ export default Alchemy.Stack(
|
|
|
66
68
|
The imports are `alchemy`, `@alchemy.run/cloudflare`, `alchemy/RemovalPolicy`, `effect/Config`,
|
|
67
69
|
`effect/Effect`, `effect/Layer`, and `@samebase/alchemy-convex` as `Convex`. Order follows the
|
|
68
70
|
outputs: the project exists before its deployment, and the push finishes before the site build. The
|
|
69
|
-
first `alchemy deploy --stage prod` takes over the existing project, and its production
|
|
70
|
-
|
|
71
|
-
`
|
|
72
|
-
it stays until Convex deletes it at expiry (14 days by default).
|
|
71
|
+
first `alchemy deploy --stage prod` takes over the existing project, and its production deployment
|
|
72
|
+
if it has code, only with `--adopt`. `alchemy destroy --stage pr-42` deletes the preview deployment.
|
|
73
|
+
Without `RemovalPolicy.destroy()`, it stays until Convex deletes it at expiry (14 days by default).
|
|
73
74
|
[`examples/app/alchemy.run.ts`](examples/app/alchemy.run.ts) is the same stack.
|
|
74
75
|
|
|
75
76
|
### The stack file at the repository root
|
|
76
77
|
|
|
77
|
-
`rootDir` is the app directory, relative to the directory where `alchemy deploy` runs, which is
|
|
78
|
-
|
|
78
|
+
`rootDir` is the app directory, relative to the directory where `alchemy deploy` runs, which is the
|
|
79
|
+
directory of the stack file. The default is `"."`. The push runs in `rootDir`, and `source` is
|
|
79
80
|
relative to it:
|
|
80
81
|
|
|
81
82
|
```ts
|
|
@@ -128,10 +129,9 @@ The deployment's Alchemy state row stores this entry in its native `bindings` ar
|
|
|
128
129
|
}
|
|
129
130
|
```
|
|
130
131
|
|
|
131
|
-
Readers inspect each binding's `data`. The deployment attributes identify the Convex deployment;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
is needed.
|
|
132
|
+
Readers inspect each binding's `data`. The deployment attributes identify the Convex deployment; the
|
|
133
|
+
row's stack and stage identify the Alchemy scope. The live repository reader includes the binding
|
|
134
|
+
through its existing resource JSON. No extra resource list, state table, or subscription is needed.
|
|
135
135
|
|
|
136
136
|
Use actual namespace ancestry when grouping resources. A slash in a resource's logical ID is not a
|
|
137
137
|
namespace boundary. For a nested install, join the installed names with slashes, such as
|
|
@@ -158,8 +158,8 @@ reads them through the stack's config provider (process env, `.env`, stack secre
|
|
|
158
158
|
actions), because the provider reads the project and type of its deployment first.
|
|
159
159
|
- A preview deploy key of a project (`preview:<team>:<project>|...`) creates, pushes to, and
|
|
160
160
|
deletes preview deployments of that project. It cannot change variables: with `env`, the
|
|
161
|
-
provider uses the access token, and without one it stops with `EnvironmentNeedsDeployKey`
|
|
162
|
-
|
|
161
|
+
provider uses the access token, and without one it stops with `EnvironmentNeedsDeployKey` before
|
|
162
|
+
it creates anything. Use project default variables in the Convex dashboard for previews.
|
|
163
163
|
|
|
164
164
|
A key that does not fit the deployment is not used: the provider uses the access token. To pass
|
|
165
165
|
values explicitly: `Convex.providers(Convex.fromValues({ accessToken, deployKey }))`.
|
|
@@ -168,9 +168,9 @@ values explicitly: `Convex.providers(Convex.fromValues({ accessToken, deployKey
|
|
|
168
168
|
|
|
169
169
|
`state: Convex.state({ team })` keeps the Alchemy state of every stack and stage of a Convex team in
|
|
170
170
|
one project of that team, `alchemy-state-store`, the same name as the Cloudflare state store of
|
|
171
|
-
Alchemy, so a repository holds one secret and a stack can create
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
Alchemy, so a repository holds one secret and a stack can create and destroy its own project.
|
|
172
|
+
[docs/design/convex-state.md](docs/design/convex-state.md) records the decisions and the
|
|
173
|
+
measurements.
|
|
174
174
|
|
|
175
175
|
```ts
|
|
176
176
|
export default Alchemy.Stack(
|
|
@@ -182,27 +182,52 @@ export default Alchemy.Stack(
|
|
|
182
182
|
);
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
-
Every run needs the team token in `CONVEX_ACCESS_TOKEN`, or the Convex CLI login on a laptop. A
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
185
|
+
Every run needs the team token in `CONVEX_ACCESS_TOKEN`, or the Convex CLI login on a laptop. A team
|
|
186
|
+
access token names its team, so `Convex.state()` needs no `team` with one; a `team` slug that is set
|
|
187
|
+
must be the slug of that team, checked against its projects, and a token of another team fails the
|
|
188
|
+
run. The Convex CLI login needs the slug or the id. The first run of a team creates the project
|
|
189
|
+
`alchemy-state-store` and pushes the store functions to its production deployment with the Convex
|
|
190
|
+
CLI of the app; a later version of this package pushes a newer store when it finds an older one. A
|
|
191
|
+
store that is newer than the package fails the run with both versions, so the repositories of a team
|
|
192
|
+
upgrade the package together. Each run makes a deploy key of that deployment that can only run the
|
|
193
|
+
store's internal functions, and deletes it at the end. Rows are in the tables `resources` and
|
|
194
|
+
`outputs` of the component `alchemyState` in the dashboard, under the stack name. The store has no
|
|
195
|
+
lock: run one deploy of a stage at a time, such as with a CI concurrency group. The store and its
|
|
196
|
+
readers (Samebase) are released and upgraded together.
|
|
197
|
+
|
|
198
|
+
Store version 3 supports native subscriptions. A trusted server checks repository access, then calls
|
|
199
|
+
internal `reader:authorize({ repository, subject })` with a deployment credential. It gives the
|
|
200
|
+
returned token to a separate `ConvexClient` connected to the state deployment. The client subscribes
|
|
201
|
+
to `reader:repository({})`; the verified token selects the repository. The token has a five-minute
|
|
202
|
+
lifetime and permits redacted reads only. The server checks access again on renewal. The store uses
|
|
203
|
+
its own native signing key and JWKS, so no extra signing secret is required. See
|
|
204
|
+
[the reader contract](docs/design/convex-state.md#repository-subscriptions-090).
|
|
205
|
+
|
|
206
|
+
### Secrets in the rows
|
|
207
|
+
|
|
208
|
+
Alchemy writes a `Redacted` value in clear text, and a row must keep it: the hub keeps only the hash
|
|
209
|
+
of a client secret, so the next run can get the value only from the state. The store seals each of
|
|
210
|
+
these values with AES-256-GCM before it writes the row. A table view, a snapshot, and a reader that
|
|
211
|
+
forgets to remove secrets show no value.
|
|
212
|
+
|
|
213
|
+
Each key is one variable `STATE_KEY_<keyId>` of the store deployment, and a sealed value names its
|
|
214
|
+
key. A run that finds no key makes one, writes it to the deployment, and never reads it back. A
|
|
215
|
+
transaction in the store chooses the current key, so two first runs cannot replace a key that a row
|
|
216
|
+
needs. Each run asks if every row is sealed under the current key, and finishes the work of a run
|
|
217
|
+
that stopped.
|
|
218
|
+
|
|
219
|
+
The functions that a run reads with open the values, so a holder of the team token or of a key that
|
|
220
|
+
can run internal queries still reads every secret of the team. A value that is not `Redacted`, such
|
|
221
|
+
as a plain string in `env`, stays clear text.
|
|
222
|
+
|
|
223
|
+
To rotate the key, from a directory that selects the project `alchemy-state-store`:
|
|
224
|
+
|
|
225
|
+
1. Add a key with a new id of letters and digits:
|
|
226
|
+
`npx convex env set --prod STATE_KEY_<keyId> "$(openssl rand -base64 32)"`.
|
|
227
|
+
2. Make it the current key: `npx convex run --prod state:rotateKey '{"keyId":"<keyId>"}'`.
|
|
228
|
+
3. The next run seals every value under it. To do this now: `npx convex run --prod state:reseal`.
|
|
229
|
+
4. When `npx convex run --prod state:sealing` answers `"sealed": true`, remove the old variable. A
|
|
230
|
+
key that left the deployment before that cannot open its values.
|
|
206
231
|
|
|
207
232
|
### What the output records about a run
|
|
208
233
|
|
|
@@ -241,15 +266,15 @@ call.
|
|
|
241
266
|
|
|
242
267
|
- `Convex.Project`: `{ team, name, deploymentRegion?, deploymentType? }` creates a project with its
|
|
243
268
|
default deployment, or takes over one with that exact name with `--adopt`. A changed `name`
|
|
244
|
-
renames the project in place. `{ team, slug }` is an existing project, found by its slug and
|
|
245
|
-
|
|
246
|
-
`
|
|
269
|
+
renames the project in place. `{ team, slug }` is an existing project, found by its slug and taken
|
|
270
|
+
over with `--adopt`; it never creates or renames a project. Outputs `projectId`, `slug`, `name`,
|
|
271
|
+
`teamId`, `prodDeploymentName`, `prodDeploymentUrl`.
|
|
247
272
|
- `Convex.Deployment`: `{ project, type: "prod" }` or `{ project, type: "dev" | "preview", name }`,
|
|
248
273
|
each with optional `rootDir`, `source`, and `env`. Outputs `name` (such as `happy-animal-123`),
|
|
249
274
|
`url`, `siteUrl`, `type`, `projectId`, `reference`, `previewName`, `hash`, and `envNames`.
|
|
250
|
-
- `prod` is the project's default production deployment (with a deploy key: the key's
|
|
251
|
-
|
|
252
|
-
|
|
275
|
+
- `prod` is the project's default production deployment (with a deploy key: the key's production
|
|
276
|
+
deployment). The resource never creates or deletes one. A production deployment with code that
|
|
277
|
+
is not in state is taken over only with `--adopt`.
|
|
253
278
|
- `dev` creates a dev deployment with `name` as its reference; `preview` creates a preview
|
|
254
279
|
deployment with `name` as its preview name. An expired preview is created again on the next
|
|
255
280
|
deploy.
|
|
@@ -264,24 +289,24 @@ call.
|
|
|
264
289
|
lockfile, the deployment name, and `env`. The same hash is a no-op. Otherwise the update sets
|
|
265
290
|
`env`, then pushes. A failed push sets the changed variables back, and deletes a dev or preview
|
|
266
291
|
deployment that the same run created.
|
|
267
|
-
- `env` values are strings or secrets such as `Config.Redacted("AUTH_SECRET")`. A name that
|
|
268
|
-
|
|
292
|
+
- `env` values are strings or secrets such as `Config.Redacted("AUTH_SECRET")`. A name that leaves
|
|
293
|
+
`env` is removed from the deployment.
|
|
269
294
|
- `source: { path, command?, include? }` replaces `convex deploy` with `command`, run in `rootDir`
|
|
270
295
|
with the push key in `CONVEX_DEPLOY_KEY`, such as
|
|
271
296
|
`["node_modules/.bin/convex-hosting", "deploy", "--build", "node_modules/.bin/vp build", "--dist", "dist/client"]`.
|
|
272
297
|
The command must push with `convex deploy` (convex-hosting does, through `--cmd`). `include`
|
|
273
298
|
narrows the hash to `path`, package.json, convex.json, the lockfile, and the listed globs, such
|
|
274
|
-
as the build inputs `["src/**", "public/**", "vite.config.ts"]`. A minted key for a command
|
|
275
|
-
|
|
299
|
+
as the build inputs `["src/**", "public/**", "vite.config.ts"]`. A minted key for a command has
|
|
300
|
+
every action, because the command can do more than a push.
|
|
276
301
|
|
|
277
302
|
- `Convex.HubClient`: `{ team, app, redirectUri, deploymentType, clientName, softwareVersion? }`
|
|
278
303
|
registers one deployment as a client of the identity hub of the team. The hub is the production
|
|
279
|
-
deployment of the project `identity-hub`. The resource finds it by name and runs
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
`
|
|
283
|
-
|
|
284
|
-
|
|
304
|
+
deployment of the project `identity-hub`. The resource finds it by name and runs its internal
|
|
305
|
+
function `identityBroker:registerClient` with a short-lived key, so the stack needs no other
|
|
306
|
+
credential and no hub address. The first client of an `app` creates the app. Outputs `issuer`,
|
|
307
|
+
`clientId`, `clientSecret`, `redirectUri`, `deploymentType`, and `clientName`, which the `env` of
|
|
308
|
+
the `Convex.Deployment` writes to the deployment. Another `redirectUri` is a new client, and a
|
|
309
|
+
destroy deletes the client.
|
|
285
310
|
|
|
286
311
|
A development deployment has no stack, so the package has a command for it. Run it in the directory
|
|
287
312
|
of the app, before the first push: it registers the deployment of that directory with
|
|
@@ -329,8 +354,8 @@ repository `owner/name`. A stack without the stage of the pull request has no ca
|
|
|
329
354
|
`DeploymentIdentityChange`. A production deployment is never deleted, also not with
|
|
330
355
|
`RemovalPolicy.destroy()`, and also not a dev deployment that became one.
|
|
331
356
|
- A dev or preview deployment that exists but is not in state belongs to someone else, such as a
|
|
332
|
-
preview that CI made. The resource takes it over only with `--adopt`, and never creates over it:
|
|
333
|
-
|
|
357
|
+
preview that CI made. The resource takes it over only with `--adopt`, and never creates over it: a
|
|
358
|
+
Convex create for an existing preview name deletes that preview.
|
|
334
359
|
- With the access token, each push uses a new deploy key with only `deployment:deploy`,
|
|
335
360
|
`deployment:env:view`, and `deployment:env:write`, which expires after two hours and is deleted
|
|
336
361
|
after the push, also after a failure. With an OAuth token, Convex returns the token itself as the
|
|
@@ -347,8 +372,9 @@ repository `owner/name`. A stack without the stage of the pull request has no ca
|
|
|
347
372
|
|
|
348
373
|
## Limits
|
|
349
374
|
|
|
350
|
-
- `env` values are props, so Alchemy state holds them.
|
|
351
|
-
|
|
375
|
+
- `env` values are props, so Alchemy state holds them. `Convex.state()` seals the `Redacted` values
|
|
376
|
+
(see [Secrets in the rows](#secrets-in-the-rows)). Use `Config.Redacted` for a secret, and keep
|
|
377
|
+
`.alchemy/` git-ignored.
|
|
352
378
|
- The hash does not see a change in a workspace package outside `rootDir`, or a push from outside
|
|
353
379
|
Alchemy. Change a file of the app to push again.
|
|
354
380
|
- With a deploy key, the provider reaches the deployment at `https://<name>.convex.cloud`, so the
|
|
@@ -362,21 +388,22 @@ repository `owner/name`. A stack without the stage of the pull request has no ca
|
|
|
362
388
|
|
|
363
389
|
## Development
|
|
364
390
|
|
|
391
|
+
This package is in the [Samebase repository](https://github.com/samebase/samebase), in
|
|
392
|
+
`packages/alchemy-convex`. Run these commands from this folder:
|
|
393
|
+
|
|
365
394
|
```sh
|
|
366
395
|
pnpm install
|
|
367
|
-
pnpm run check #
|
|
368
|
-
pnpm run build # lib/ with declarations, then publint and attw
|
|
396
|
+
pnpm run check # typecheck, lint, unit tests, build
|
|
369
397
|
pnpm run test:live # real API calls, see below
|
|
370
398
|
```
|
|
371
399
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
`pnpm run test:live test/live/plane.live.test.ts`.
|
|
400
|
+
Unit tests run against recorded Management API payloads in `test/fixtures/management/` and a fake
|
|
401
|
+
Convex CLI. Live tests run only with `ALCHEMY_CONVEX_LIVE=1`, which `pnpm run test:live` sets. They
|
|
402
|
+
need a Convex login or `CONVEX_ACCESS_TOKEN` that can create projects in `ALCHEMY_CONVEX_LIVE_TEAM`.
|
|
403
|
+
Each test creates a project whose name starts with `ALCHEMY_CONVEX_LIVE_PREFIX` (default
|
|
404
|
+
`tmp-alchemy-convex-`), deletes it at the end, also after a failure, and refuses to delete a
|
|
405
|
+
project, or a deployment of a project, without that prefix. A file argument runs one live file only,
|
|
406
|
+
for example `pnpm run test:live test/live/plane.live.test.ts`.
|
|
380
407
|
|
|
381
408
|
Releases: change `version` in `package.json` in a pull request. After the merge,
|
|
382
409
|
`.github/workflows/release.yml` publishes that version from `main` with npm trusted publishing.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { n as newKeyId, t as newKey } from "./sealedSecret-Bf8AeIp_.js";
|
|
1
2
|
import { createRequire } from "node:module";
|
|
2
3
|
import { Stack } from "alchemy/Stack";
|
|
3
4
|
import * as Effect from "effect/Effect";
|
|
@@ -1207,7 +1208,7 @@ const listProjectsByName = (api, teamId, name) => Effect.gen(function* () {
|
|
|
1207
1208
|
const matches = [];
|
|
1208
1209
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
1209
1210
|
let cursor;
|
|
1210
|
-
|
|
1211
|
+
for (;;) {
|
|
1211
1212
|
const body = yield* api.request("list projects", (client) => client.GET("/teams/{team_id}/projects", { params: {
|
|
1212
1213
|
path: { team_id: teamId },
|
|
1213
1214
|
query: {
|
|
@@ -1508,6 +1509,10 @@ const withRunRecord = (output, record) => isPlainObject(output) ? "samebase.run"
|
|
|
1508
1509
|
[RUN_RECORD_KEY]: record
|
|
1509
1510
|
} : void 0;
|
|
1510
1511
|
//#endregion
|
|
1512
|
+
//#region src/store/convex/sealing.ts
|
|
1513
|
+
/** The variable of the store deployment that holds the key `keyId`. */
|
|
1514
|
+
const stateKeyName = (keyId) => `STATE_KEY_${keyId}`;
|
|
1515
|
+
//#endregion
|
|
1511
1516
|
//#region src/State.ts
|
|
1512
1517
|
/** The name of the state project in each team. */
|
|
1513
1518
|
const STORE_PROJECT_NAME = "alchemy-state-store";
|
|
@@ -1517,6 +1522,8 @@ const STORE_PROJECT_NAME = "alchemy-state-store";
|
|
|
1517
1522
|
* `deployment:data:write` do not run an internal function.
|
|
1518
1523
|
*/
|
|
1519
1524
|
const RUN_KEY_ACTIONS = ["deployment:functions:runInternalQueries", "deployment:functions:runInternalMutations"];
|
|
1525
|
+
/** The actions of the key of one run on the store: its writes are actions that seal. */
|
|
1526
|
+
const STORE_KEY_ACTIONS = [...RUN_KEY_ACTIONS, "deployment:functions:runInternalActions"];
|
|
1520
1527
|
/** The body of POST /api/function, with HTTP 200 for a function error too. */
|
|
1521
1528
|
const Reply = Schema.Union([Schema.Struct({
|
|
1522
1529
|
status: Schema.Literal("success"),
|
|
@@ -1557,6 +1564,13 @@ const post = (url, headers, body) => Effect.tryPromise({
|
|
|
1557
1564
|
times: 5
|
|
1558
1565
|
}));
|
|
1559
1566
|
const decode = (schema, what) => (value) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(() => failure(`bad ${what}`)));
|
|
1567
|
+
/** The reply of `state:sealing`. */
|
|
1568
|
+
const Sealing = Schema.Struct({
|
|
1569
|
+
current: Schema.NullOr(Schema.String),
|
|
1570
|
+
sealed: Schema.Boolean
|
|
1571
|
+
});
|
|
1572
|
+
/** The reply of `state:reseal`. */
|
|
1573
|
+
const Resealed = Schema.Struct({ failed: Schema.Array(Schema.String) });
|
|
1560
1574
|
const makeFunctionApi = (url, key) => ({ call: (path, args) => post(`${url}/api/function`, { Authorization: `Convex ${Redacted.value(key)}` }, {
|
|
1561
1575
|
path,
|
|
1562
1576
|
args,
|
|
@@ -1639,7 +1653,7 @@ const storeDirectory = () => join(dirname(fileURLToPath(import.meta.url)), "..",
|
|
|
1639
1653
|
*/
|
|
1640
1654
|
const pushStore = (options, deployment) => Effect.gen(function* () {
|
|
1641
1655
|
const cli = yield* installedCli(join(options.store, ".."));
|
|
1642
|
-
yield* Effect.logInfo(`Convex state: pushing the store version
|
|
1656
|
+
yield* Effect.logInfo(`Convex state: pushing the store version 5 to ${deployment.name}`);
|
|
1643
1657
|
yield* withPushKey(options.api, deployment.name, PUSH_KEY_ACTIONS, (key) => push(options.spawner, key, {
|
|
1644
1658
|
deployment: deployment.name,
|
|
1645
1659
|
url: deployment.deploymentUrl,
|
|
@@ -1662,6 +1676,26 @@ const pushStore = (options, deployment) => Effect.gen(function* () {
|
|
|
1662
1676
|
}));
|
|
1663
1677
|
});
|
|
1664
1678
|
/**
|
|
1679
|
+
* Finishes the sealing of the store, whatever an earlier run left: a new key when the store has
|
|
1680
|
+
* none, then a reseal unless one finished under the current key. A run reads this each time, so a
|
|
1681
|
+
* run that stopped after a push cannot leave rows in clear text. This client makes a key once, and
|
|
1682
|
+
* it never reads a key back.
|
|
1683
|
+
*/
|
|
1684
|
+
const ensureSealed = (options, deployment, functions) => Effect.gen(function* () {
|
|
1685
|
+
const { current, sealed } = yield* functions.call("state:sealing", {}).pipe(Effect.flatMap(valueOf), Effect.flatMap(decode(Sealing, "state:sealing")));
|
|
1686
|
+
if (current === null) {
|
|
1687
|
+
const keyId = newKeyId();
|
|
1688
|
+
yield* withPushKey(options.api, deployment.name, PUSH_KEY_ACTIONS, (key) => writeVariables(deployment.deploymentUrl, key, [{
|
|
1689
|
+
name: stateKeyName(keyId),
|
|
1690
|
+
value: newKey()
|
|
1691
|
+
}]));
|
|
1692
|
+
yield* functions.call("state:chooseKey", { keyId }).pipe(Effect.flatMap(valueOf));
|
|
1693
|
+
}
|
|
1694
|
+
if (sealed) return;
|
|
1695
|
+
const { failed } = yield* functions.call("state:reseal", {}).pipe(Effect.flatMap(valueOf), Effect.flatMap(decode(Resealed, "state:reseal")));
|
|
1696
|
+
if (failed.length > 0) yield* Effect.logWarning(`Convex state: the store could not seal ${failed.join(", ")}. The next run tries again.`);
|
|
1697
|
+
});
|
|
1698
|
+
/**
|
|
1665
1699
|
* Creates the state project of the team. Two first runs can both create one:
|
|
1666
1700
|
* the run whose project is not the oldest deletes its own, which holds
|
|
1667
1701
|
* nothing yet, and uses the oldest, so that every run ends on one store.
|
|
@@ -1693,7 +1727,7 @@ const makeConvexState = (options) => Effect.gen(function* () {
|
|
|
1693
1727
|
if (deployment === void 0) return yield* new ProductionDeploymentNotFound({ projectId: project.id });
|
|
1694
1728
|
const keys = [];
|
|
1695
1729
|
yield* Scope.addFinalizer(options.scope, Effect.suspend(() => Effect.forEach(keys, (key) => deleteDeployKey(api, deployment.name, key), { discard: true })));
|
|
1696
|
-
const mint = createDeployKey(api, deployment.name, STORE_PROJECT_NAME,
|
|
1730
|
+
const mint = createDeployKey(api, deployment.name, STORE_PROJECT_NAME, STORE_KEY_ACTIONS).pipe(Effect.map((key) => {
|
|
1697
1731
|
keys.push(key);
|
|
1698
1732
|
return makeFunctionApi(deployment.deploymentUrl, key);
|
|
1699
1733
|
}), Effect.mapError((error) => failure(`no key for ${deployment.name}: ${error.message}`)));
|
|
@@ -1703,15 +1737,16 @@ const makeConvexState = (options) => Effect.gen(function* () {
|
|
|
1703
1737
|
return next.call(path, args);
|
|
1704
1738
|
})))) };
|
|
1705
1739
|
let version = yield* storeVersion(functions);
|
|
1706
|
-
if (version === void 0 || version <
|
|
1740
|
+
if (version === void 0 || version < 5) {
|
|
1707
1741
|
if (version === void 0 && deployment.lastDeployTime) return yield* failure(`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.`);
|
|
1708
1742
|
version = yield* storeVersion(functions);
|
|
1709
|
-
if (version === void 0 || version <
|
|
1743
|
+
if (version === void 0 || version < 5) {
|
|
1710
1744
|
yield* pushStore(options, deployment);
|
|
1711
1745
|
version = yield* storeVersion(functions);
|
|
1712
1746
|
}
|
|
1713
1747
|
}
|
|
1714
|
-
if (version !==
|
|
1748
|
+
if (version !== 5) return yield* failure(`${deployment.name} answers store version ${version ?? "none"}, and this package needs store version 5.`);
|
|
1749
|
+
yield* ensureSealed(options, deployment, functions);
|
|
1715
1750
|
const remote = remoteOf(functions, options.run);
|
|
1716
1751
|
/** The stack's output plus the record, when the run has one and the output has a place for it. */
|
|
1717
1752
|
const documentOf = (request) => Effect.gen(function* () {
|
|
@@ -1910,4 +1945,4 @@ const registerDevelopmentClient = (options) => Effect.gen(function* () {
|
|
|
1910
1945
|
//#endregion
|
|
1911
1946
|
export { PushTargetMismatch as A, DEPLOY_KEY_ENV as B, DeploymentProvider as C, ConvexAppNotFound as D, CodePushFailed as E, ManagementApi as F, fromValues as H, ManagementApiLive as I, ACCESS_TOKEN_ENV as L, installedCli as M, parseDeployOutput as N, ConvexCliNotFound as O, ConvexApiError as P, Credentials as R, DeploymentIdentityChange as S, ProductionDeploymentNotFound as T, fromConfig as V, ProjectProvider as _, registerDevelopmentClient as a, AmbiguousDeployment as b, RUN_RECORD_KEY as c, withRunRecord as d, AmbiguousProject as f, ProjectNotFound as g, ProjectDeploymentChange as h, HubClientProvider as i, VariablesOwnedBySomeoneElse as j, EnvironmentNeedsDeployKey as k, RunRecordError as l, Project as m, HubClient as n, STORE_PROJECT_NAME as o, CreatedProjectNotFound as p, HubClientFailed as r, state as s, HUB_PROJECT_NAME as t, runRecord as u, ProjectTeamChange as v, DeploymentResource as w, Deployment as x, AbsoluteRootDir as y, CredentialsError as z };
|
|
1912
1947
|
|
|
1913
|
-
//# sourceMappingURL=HubClient-
|
|
1948
|
+
//# sourceMappingURL=HubClient-C0XxgkK3.js.map
|