@samebase/alchemy-convex 0.27.3 → 0.29.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 CHANGED
@@ -44,8 +44,8 @@ export default Alchemy.Stack(
44
44
  const backend =
45
45
  stage === "prod"
46
46
  ? yield* Convex.Deployment("Backend", {
47
- // An existing project. A new one: { team: "my-team", name: "my-app" }.
48
- project: yield* Convex.Project("Project", { team: "my-team", slug: "my-app" }),
47
+ // An existing project of the team of the token. A new one: { name: "my-app" }.
48
+ project: yield* Convex.Project("Project", { slug: "my-app" }),
49
49
  type: "prod",
50
50
  source: "./convex",
51
51
  env: { AUTH_SECRET: Config.Redacted("AUTH_SECRET") },
@@ -81,7 +81,7 @@ relative to it:
81
81
 
82
82
  ```ts
83
83
  Effect.gen(function* () {
84
- const project = yield* Convex.Project("Project", { team: "my-team", slug: "my-app" });
84
+ const project = yield* Convex.Project("Project", { slug: "my-app" });
85
85
  const backend = yield* Convex.Deployment("Backend", {
86
86
  project,
87
87
  type: "prod",
@@ -166,34 +166,36 @@ values explicitly: `Convex.providers(Convex.fromValues({ accessToken, deployKey
166
166
 
167
167
  ## State in Convex
168
168
 
169
- `state: Convex.state({ team })` keeps the Alchemy state of every stack and stage of a Convex team in
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 and destroy its own project.
169
+ `state: Convex.state()` keeps the Alchemy state of every stack and stage of a Convex team in one
170
+ project of that team, `alchemy-state-store`, the same name as the Cloudflare state store of Alchemy,
171
+ so a repository holds one secret and a stack can create and destroy its own project.
172
172
  [docs/design/convex-state.md](docs/design/convex-state.md) records the decisions and the
173
173
  measurements.
174
174
 
175
175
  ```ts
176
176
  export default Alchemy.Stack(
177
177
  process.env["GITHUB_REPOSITORY"] ?? "my-org/my-app", // the stack name: the repository
178
- { providers: Convex.providers(), state: Convex.state({ team: "my-team" }) },
178
+ { providers: Convex.providers(), state: Convex.state() },
179
179
  Effect.gen(function* () {
180
180
  /* ... */
181
181
  }),
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 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.
185
+ Every run needs the team token in `CONVEX_ACCESS_TOKEN`, or the Convex CLI login on a laptop. The
186
+ state and the projects are in the team of that credential. A team access token and an OAuth token of
187
+ a team name their team. The Convex CLI login names no team, so the run uses the team of the
188
+ deployment that the Convex CLI selected: `CONVEX_DEPLOYMENT` in the environment or in `.env.local`,
189
+ which `npx convex dev` writes. A local or anonymous deployment is in no team, so the run stops and
190
+ asks for a cloud deployment. The first run of a team creates the project `alchemy-state-store` and
191
+ pushes the store functions to its production deployment with the Convex CLI of the app; a later
192
+ version of this package pushes a newer store when it finds an older one. A store that is newer than
193
+ the package fails the run with both versions, so the repositories of a team upgrade the package
194
+ together. Each run makes a deploy key of that deployment that can only run the store's internal
195
+ functions, and deletes it at the end. Rows are in the tables `resources` and `outputs` of the
196
+ component `alchemyState` in the dashboard, under the stack name. The store has no lock: run one
197
+ deploy of a stage at a time, such as with a CI concurrency group. The store and its readers
198
+ (Samebase) are released and upgraded together.
197
199
 
198
200
  Store version 3 supports native subscriptions. A trusted server checks repository access, then calls
199
201
  internal `reader:authorize({ repository, subject })` with a deployment credential. It gives the
@@ -264,20 +266,22 @@ call.
264
266
 
265
267
  ## Resources
266
268
 
267
- - `Convex.Project`: `{ team, name, deploymentRegion?, deploymentType? }` creates a project with its
268
- default deployment, or takes over one with that exact name with `--adopt`. A changed `name`
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`.
269
+ - `Convex.Project`: `{ name, deploymentRegion?, deploymentType? }` creates a project with its
270
+ default deployment in the team of the credential, or takes over one with that exact name with
271
+ `--adopt`. A changed `name` renames the project in place. `{ slug }` is an existing project of
272
+ that team, found by its slug and taken over with `--adopt`; it never creates or renames a project.
273
+ Outputs `projectId`, `slug`, `name`, `teamId`, `prodDeploymentName`, `prodDeploymentUrl`.
272
274
  - `Convex.Deployment`: `{ project, type: "prod" }` or `{ project, type: "dev" | "preview", name }`,
273
275
  each with optional `rootDir`, `source`, and `env`. Outputs `name` (such as `happy-animal-123`),
274
276
  `url`, `siteUrl`, `type`, `projectId`, `reference`, `previewName`, `hash`, and `envNames`.
275
277
  - `prod` is the project's default production deployment (with a deploy key: the key's production
276
278
  deployment). The resource never creates or deletes one. A production deployment with code that
277
279
  is not in state is taken over only with `--adopt`.
278
- - `dev` creates a dev deployment with `name` as its reference; `preview` creates a preview
279
- deployment with `name` as its preview name. An expired preview is created again on the next
280
- deploy.
280
+ - `dev` creates a dev deployment with `name` as its reference. With `--adopt`, `name` can also be
281
+ the Convex name of a dev deployment that exists, such as `happy-animal-123` from `.env.local`,
282
+ which `npx convex dev` made: the resource takes it over. A name that matches nothing creates a
283
+ dev deployment with that reference. `preview` creates a preview deployment with `name` as its
284
+ preview name. An expired preview is created again on the next deploy.
281
285
  - `rootDir` is the app directory, relative to the directory where the deploy runs, `"."` by
282
286
  default. It must be relative (`AbsoluteRootDir`) and have a package.json (`ConvexAppNotFound`).
283
287
  There is no search in the directories above it. When convex.json puts the functions outside
@@ -371,27 +375,29 @@ variables. Each callback remains one exact registered URI.
371
375
 
372
376
  ### The development stage
373
377
 
374
- A laptop is a stage `dev-<name>` of the app's stack, with its own cloud development deployment. Run
375
- `alchemy deploy --stage dev-<name>` with `CONVEX_ACCESS_TOKEN` or the Convex CLI login of the app's
376
- team, before `convex dev`. The stage has the same three rows as a preview:
378
+ A laptop gets its client from a stage `dev-<name>` of the app's stack, run on purpose, not at each
379
+ dev start: `npx convex dev` has made and selected the dev deployment and pushed the code, and then
380
+ `alchemy deploy --stage dev-<name>` gives that deployment its variables. The stage creates nothing
381
+ and pushes nothing. It has three rows on the deployment that `.env.local` selects, taken over by its
382
+ Convex name:
377
383
 
378
384
  ```ts
379
- // The stage is dev-<name>. `project` is the project of the deployment that the Convex CLI selects.
385
+ // `selected` is the name in CONVEX_DEPLOYMENT of .env.local, such as happy-animal-123, and
386
+ // `project` is the project of stage `prod`.
380
387
  const prod = Convex.SharedHubClient.read("HubClient", { stage: "prod" });
381
- const deployment =
388
+ const site =
382
389
  yield *
383
- Convex.Deployment("Deployment", {
390
+ Convex.Deployment("Verification", {
384
391
  project,
385
392
  type: "dev",
386
- name: stage,
387
- source: "./convex",
393
+ name: selected,
388
394
  env: { SAMEBASE_REPOSITORY_ID: prod.repository },
389
395
  }).pipe(adopt(true));
390
396
  const hub =
391
397
  yield *
392
398
  Convex.SharedHubClient("HubClient", {
393
399
  app: "scout",
394
- redirectUri: Output.interpolate`${deployment.siteUrl}/auth/hub/callback`,
400
+ redirectUri: Output.interpolate`${site.siteUrl}/auth/hub/callback`,
395
401
  deploymentType: "development",
396
402
  initialAccessToken: prod.initialAccessToken,
397
403
  });
@@ -400,7 +406,7 @@ yield *
400
406
  Convex.Deployment("Site", {
401
407
  project,
402
408
  type: "dev",
403
- name: stage,
409
+ name: selected,
404
410
  env: {
405
411
  HUB_ISSUER: variables.issuer,
406
412
  HUB_CLIENT_ID: variables.clientId,
@@ -412,11 +418,13 @@ yield *
412
418
  The imports are `alchemy/AdoptPolicy` for `adopt` and `alchemy/Output` for `Output`.
413
419
 
414
420
  1. `read` gives the initial access token and the repository id from the `HubClient` row of stage
415
- `prod`. Before the first deploy to main, they are empty.
416
- 2. "Deployment" finds the development deployment by its reference, or creates it. It sets
417
- `SAMEBASE_REPOSITORY_ID`, which the site serves as `/samebase-verification.json`, and pushes.
418
- 3. "HubClient" asks the hub for a development client on the deployment's site URL with the token.
419
- Each later run renews it. A development client expires 14 days after its last run or use.
421
+ `prod`.
422
+ 2. "Verification" sets `SAMEBASE_REPOSITORY_ID`, which the site serves as
423
+ `/samebase-verification.json`. `adopt` takes over the deployment and the variables that the dev
424
+ start set before.
425
+ 3. "HubClient" asks the hub for a development client on the deployment's site URL with the token. A
426
+ later run renews it. A sign-in renews it too, so a laptop in use never needs the stage again; a
427
+ development client expires 14 days after its last run or sign-in.
420
428
  4. "Site" writes the three variables. It removes only a name that leaves its own `env`, so
421
429
  `SAMEBASE_REPOSITORY_ID` stays.
422
430
 
@@ -455,8 +463,8 @@ repository `owner/name`. A stack without the stage of the pull request has no ca
455
463
 
456
464
  - `Convex.Project` and `Convex.Deployment` have the removal policy `retain`. `alchemy destroy`
457
465
  forgets them and Convex keeps them. Add `RemovalPolicy.destroy()` to delete with the resource.
458
- - A project is never replaced. A change of `team`, `slug`, `deploymentType`, or `deploymentRegion`
459
- stops the deploy with a typed error that names the fix.
466
+ - A project is never replaced. A change of `slug`, `deploymentType`, or `deploymentRegion`, or a
467
+ credential of another team, stops the deploy with a typed error that names the fix.
460
468
  - A deployment keeps its `project`, `type`, and `name`; a change stops the deploy with
461
469
  `DeploymentIdentityChange`. A production deployment is never deleted, also not with
462
470
  `RemovalPolicy.destroy()`, and also not a dev deployment that became one.
@@ -488,8 +496,6 @@ repository `owner/name`. A stack without the stage of the pull request has no ca
488
496
  deployment must answer there.
489
497
  - A run that stops between a deployment create and the state write leaves a deployment that state
490
498
  does not know. The next run stops with `OwnedBySomeoneElse`; run it with `--adopt`.
491
- - Team slugs for `{ team, name }` resolve through an unofficial dashboard endpoint. Pass the numeric
492
- team id to avoid it. `{ team, slug }` uses the official endpoint.
493
499
  - Not covered: deployment regions and classes, custom domains, log streams, teams and members,
494
500
  components, snapshots.
495
501
 
@@ -506,11 +512,11 @@ pnpm run test:live # real API calls, see below
506
512
 
507
513
  Unit tests run against recorded Management API payloads in `test/fixtures/management/` and a fake
508
514
  Convex CLI. Live tests run only with `ALCHEMY_CONVEX_LIVE=1`, which `pnpm run test:live` sets. They
509
- need a Convex login or `CONVEX_ACCESS_TOKEN` that can create projects in `ALCHEMY_CONVEX_LIVE_TEAM`.
510
- Each test creates a project whose name starts with `ALCHEMY_CONVEX_LIVE_PREFIX` (default
511
- `tmp-alchemy-convex-`), deletes it at the end, also after a failure, and refuses to delete a
512
- project, or a deployment of a project, without that prefix. A file argument runs one live file only,
513
- for example `pnpm run test:live test/live/plane.live.test.ts`.
515
+ need a team access token of `ALCHEMY_CONVEX_LIVE_TEAM` in `CONVEX_ACCESS_TOKEN`: the projects are in
516
+ the team of the token. Each test creates a project whose name starts with
517
+ `ALCHEMY_CONVEX_LIVE_PREFIX` (default `tmp-alchemy-convex-`), deletes it at the end, also after a
518
+ failure, and refuses to delete a project, or a deployment of a project, without that prefix. A file
519
+ argument runs one live file only, for example `pnpm run test:live test/live/plane.live.test.ts`.
514
520
 
515
521
  Releases: change `version` in `package.json` in a pull request. After the merge,
516
522
  `.github/workflows/release.yml` publishes that version from `main` with npm trusted publishing.
@@ -18,10 +18,11 @@ import { diffBindings, isResolved } from "alchemy/Diff";
18
18
  import * as Provider from "alchemy/Provider";
19
19
  import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
20
20
  import { AlchemyContext } from "alchemy/AlchemyContext";
21
+ import { readFileSync } from "node:fs";
22
+ import { parseEnv, promisify, stripVTControlCharacters } from "node:util";
21
23
  import { createDeploymentClient, createManagementClient } from "@convex-dev/platform";
22
24
  import * as Schedule from "effect/Schedule";
23
25
  import { createHash } from "node:crypto";
24
- import { promisify, stripVTControlCharacters } from "node:util";
25
26
  import { hashDirectory } from "alchemy/Command/Memo";
26
27
  import * as Clock from "effect/Clock";
27
28
  import * as ChildProcess from "effect/process/ChildProcess";
@@ -173,6 +174,28 @@ const TeamRow = Schema.Struct({
173
174
  const TeamRows = Schema.Array(TeamRow);
174
175
  /** The first page of GET /teams/{team_id}/projects: every row carries the slug of the team. */
175
176
  const ProjectSlugRows = Schema.Struct({ items: Schema.Array(Schema.Struct({ teamSlug: Schema.String })) });
177
+ /** GET /deployments/{name}: the project of the deployment. */
178
+ const DeploymentProject = Schema.Struct({ projectId: Schema.Number });
179
+ /** GET /projects/{id}: the team of the project. */
180
+ const ProjectTeam = Schema.Struct({ teamId: Schema.Number });
181
+ /**
182
+ * The cloud deployment that the Convex CLI selected: CONVEX_DEPLOYMENT in the environment or in
183
+ * `.env.local` of the working directory, as `npx convex dev` writes it (`dev:happy-animal-123`),
184
+ * without the type prefix. Undefined when neither has it, and for a local or anonymous deployment
185
+ * (`local:local-team-app`, `anonymous:anonymous-app`), which runs on this machine and is in no team.
186
+ */
187
+ const selectedDeployment = () => {
188
+ const fromEnv = process.env["CONVEX_DEPLOYMENT"];
189
+ const value = fromEnv !== void 0 && fromEnv !== "" ? fromEnv : (() => {
190
+ try {
191
+ return parseEnv(readFileSync(".env.local", "utf8"))["CONVEX_DEPLOYMENT"];
192
+ } catch {
193
+ return;
194
+ }
195
+ })();
196
+ const name = /^(?:(?:dev|prod|preview):)?([^:]+)$/.exec(value ?? "")?.[1];
197
+ return name === void 0 || name.startsWith("anonymous-") ? void 0 : name;
198
+ };
176
199
  /** GET /token_details: the team of a team token, or the project of a project token. */
177
200
  const TokenDetails = Schema.Union([Schema.Struct({
178
201
  type: Schema.Literal("teamToken"),
@@ -194,17 +217,36 @@ const makeService = (source) => {
194
217
  const tokenDetails = Effect.runSync(Effect.cached(request("get token details", (c) => c.GET("/token_details")).pipe(Effect.map((body) => Schema.decodeUnknownSync(TokenDetails)(body)), Effect.catchIf((error) => error.status === 403, () => Effect.succeed(void 0)))));
195
218
  /** The slug of the team, from the first project of its listing, or undefined for a team without one. Read once. */
196
219
  const teamSlug = (teamId) => request("list projects", (c) => c.GET("/teams/{team_id}/projects", { params: { path: { team_id: teamId } } })).pipe(Effect.map((body) => Schema.decodeUnknownSync(ProjectSlugRows)(body).items[0]?.teamSlug));
220
+ const projectTokenError = (projectId) => new ConvexApiError({
221
+ operation: "resolve team",
222
+ status: 403,
223
+ message: `the access token belongs to project ${projectId}, and a project token cannot create projects. Use a team access token.`
224
+ });
225
+ const teamId = Effect.runSync(Effect.cached(Effect.gen(function* () {
226
+ const details = yield* tokenDetails;
227
+ if (details?.type === "teamToken") return details.teamId;
228
+ if (details?.type === "projectToken") return yield* projectTokenError(details.projectId);
229
+ const selected = selectedDeployment();
230
+ if (selected === void 0) return yield* new ConvexApiError({
231
+ operation: "resolve team",
232
+ status: 0,
233
+ message: "the Convex CLI login names no team, and no cloud deployment is selected. Run `npx convex dev` once with a cloud deployment."
234
+ });
235
+ const deployment = yield* request("get deployment", (c) => c.GET("/deployments/{deployment_name}", { params: { path: { deployment_name: selected } } }));
236
+ const { projectId } = Schema.decodeUnknownSync(DeploymentProject)(deployment);
237
+ const project = yield* request("get project", (c) => c.GET("/projects/{project_id}", { params: { path: { project_id: projectId } } }));
238
+ return Schema.decodeUnknownSync(ProjectTeam)(project).teamId;
239
+ })));
197
240
  const resolveTeamId = (team) => Effect.gen(function* () {
198
241
  if (typeof team === "number") return team;
199
- if (team !== void 0 && /^\d+$/.test(team)) return Number(team);
242
+ if (/^\d+$/.test(team)) return Number(team);
200
243
  const details = yield* tokenDetails;
201
244
  if (details?.type === "teamToken") {
202
- if (team === void 0) return details.teamId;
203
245
  const slug = yield* teamSlug(details.teamId);
204
246
  if (slug === void 0) return yield* new ConvexApiError({
205
247
  operation: "resolve team",
206
248
  status: 0,
207
- message: `team "${team}" cannot be checked against the access token: team ${details.teamId} has no project yet. Set team to ${details.teamId}, or leave it out.`
249
+ message: `team "${team}" cannot be checked against the access token: team ${details.teamId} has no project yet. Set team to ${details.teamId}.`
208
250
  });
209
251
  if (slug !== team) return yield* new ConvexApiError({
210
252
  operation: "resolve team",
@@ -213,16 +255,7 @@ const makeService = (source) => {
213
255
  });
214
256
  return details.teamId;
215
257
  }
216
- if (details?.type === "projectToken") return yield* new ConvexApiError({
217
- operation: "resolve team",
218
- status: 403,
219
- message: `the access token belongs to project ${details.projectId}, and a project token cannot create projects. Use a team access token.`
220
- });
221
- if (team === void 0) return yield* new ConvexApiError({
222
- operation: "resolve team",
223
- status: 0,
224
- message: "set team to the slug or the id of the Convex team: this access token does not name a team."
225
- });
258
+ if (details?.type === "projectToken") return yield* projectTokenError(details.projectId);
226
259
  const resolved = yield* credentials;
227
260
  const match = (yield* Effect.tryPromise({
228
261
  try: async () => {
@@ -253,6 +286,7 @@ const makeService = (source) => {
253
286
  request,
254
287
  requestVoid,
255
288
  deploymentRequest,
289
+ teamId,
256
290
  resolveTeamId,
257
291
  isCredentialToken,
258
292
  isOAuthCredential,
@@ -782,7 +816,8 @@ const checkIdentity = (current, news) => {
782
816
  if (current.projectId !== news.project.projectId) return change("projectId", String(current.projectId), String(news.project.projectId));
783
817
  if (current.type !== news.type) return change("type", current.type, news.type);
784
818
  const currentName = nameOf(current);
785
- if (news.type !== "prod" && currentName !== news.name) return change("name", currentName ?? "(none)", news.name);
819
+ const named = news.type === "dev" && current.name === news.name;
820
+ if (news.type !== "prod" && currentName !== news.name && !named) return change("name", currentName ?? "(none)", news.name);
786
821
  return Effect.void;
787
822
  };
788
823
  const addressOf = (props) => ({
@@ -824,11 +859,13 @@ const DeploymentProvider = () => Provider.effect(DeploymentResource, Effect.gen(
824
859
  /** The project's default production deployment, or undefined. */
825
860
  const production = (management, projectId) => productionRow(management, projectId).pipe(Effect.map((row) => row === void 0 ? void 0 : targetOf(row, "prod", void 0)));
826
861
  /**
827
- * The one dev deployment with this reference, or the one preview
828
- * deployment with this preview name, or undefined.
862
+ * The one dev deployment with this reference or this Convex name, or the
863
+ * one preview deployment with this preview name, or undefined. The name
864
+ * form takes the dev deployment that `npx convex dev` made, such as the
865
+ * one `.env.local` selects, which has a reference the stack never chose.
829
866
  */
830
867
  const findByName = (management, projectId, type, name) => Effect.gen(function* () {
831
- const matches = (yield* listDeployments(management, projectId, type)).filter((row) => row.deploymentType === type && (type === "dev" ? row.reference === name : row.previewIdentifier === name));
868
+ const matches = (yield* listDeployments(management, projectId, type)).filter((row) => row.deploymentType === type && (type === "dev" ? row.reference === name || row.name === name : row.previewIdentifier === name));
832
869
  if (matches.length > 1) return yield* new AmbiguousDeployment({
833
870
  projectId,
834
871
  type,
@@ -843,7 +880,7 @@ const DeploymentProvider = () => Provider.effect(DeploymentResource, Effect.gen(
843
880
  const deployKeyTarget = (key, deployment, address) => Effect.gen(function* () {
844
881
  const info = Schema.decodeUnknownSync(DeploymentInfo)(yield* api.deploymentRequest("get deployment info", deployment, key, (client) => client.GET("/deployment_info")));
845
882
  const reference = info.reference ?? "";
846
- if (info.projectId !== address.projectId || info.deploymentType !== address.type || address.type === "dev" && reference !== address.name) return;
883
+ if (info.projectId !== address.projectId || info.deploymentType !== address.type || address.type === "dev" && reference !== address.name && deployment !== address.name) return;
847
884
  const urls = Schema.decodeUnknownSync(CanonicalUrls)(yield* api.deploymentRequest("get canonical urls", deployment, key, (client) => client.GET("/get_canonical_urls")));
848
885
  return {
849
886
  projectId: info.projectId,
@@ -1094,14 +1131,14 @@ const DeploymentProvider = () => Provider.effect(DeploymentResource, Effect.gen(
1094
1131
  //#endregion
1095
1132
  //#region src/Project.ts
1096
1133
  const Project = Resource("Convex.Project", { defaultRemovalPolicy: "retain" });
1097
- /** The props change the team of an existing project. Convex cannot move a project. */
1134
+ /** The credential is of another team than the project. Convex cannot move a project. */
1098
1135
  var ProjectTeamChange = class extends Schema.TaggedError()("ProjectTeamChange", {
1099
1136
  projectId: Schema.Number,
1100
1137
  teamId: Schema.Number,
1101
1138
  requestedTeamId: Schema.Number
1102
1139
  }) {
1103
1140
  get message() {
1104
- return `Convex.Project cannot move project ${this.projectId} from team ${this.teamId} to team ${this.requestedTeamId}. Set team back to ${this.teamId}. To use the other team, create a new resource with a new logical id and move the data yourself.`;
1141
+ return `Convex.Project cannot move project ${this.projectId} from team ${this.teamId} to team ${this.requestedTeamId}, the team of the credential. Use a token of team ${this.teamId}, or with the Convex CLI login, select a deployment of team ${this.teamId}. To use the other team, create a new resource with a new logical id and move the data yourself.`;
1105
1142
  }
1106
1143
  };
1107
1144
  /**
@@ -1137,11 +1174,11 @@ var CreatedProjectNotFound = class extends Schema.TaggedError()("CreatedProjectN
1137
1174
  };
1138
1175
  /** No project in the team has the slug. A `slug` resource never creates one. */
1139
1176
  var ProjectNotFound = class extends Schema.TaggedError()("ProjectNotFound", {
1140
- team: Schema.String,
1177
+ teamId: Schema.Number,
1141
1178
  slug: Schema.String
1142
1179
  }) {
1143
1180
  get message() {
1144
- return `Team ${this.team} has no project with the slug "${this.slug}", and a Convex.Project with a slug never creates one. Check the slug in the project URL of the Convex dashboard, or use name to create a project.`;
1181
+ return `Team ${this.teamId} has no project with the slug "${this.slug}", and a Convex.Project with a slug never creates one. Check the slug in the project URL of the Convex dashboard, or use name to create a project.`;
1145
1182
  }
1146
1183
  };
1147
1184
  /** The props change the slug of a `slug` resource: that is another project. */
@@ -1203,8 +1240,6 @@ const checkCreateTimeProps = (projectId, olds, news) => {
1203
1240
  }));
1204
1241
  return Effect.void;
1205
1242
  };
1206
- /** True when `team`, a slug or a numeric id, names the team of the project. */
1207
- const isTeamOf = (details, team) => typeof team === "number" || /^\d+$/.test(team) ? details.teamId === Number(team) : details.teamSlug === team;
1208
1243
  /** The row of the project's default production deployment, or undefined. */
1209
1244
  const prodDeploymentOf = (api, projectId) => api.request("list deployments", (client) => client.GET("/projects/{project_id}/list_deployments", { params: { path: { project_id: projectId } } })).pipe(Effect.map((rows) => Schema.decodeUnknownSync(DeploymentRows)(rows).find((row) => row.deploymentType === "prod" && row.isDefault)));
1210
1245
  /** GET /projects/{id}, or undefined after a 404. */
@@ -1278,9 +1313,9 @@ const ProjectProvider = () => Provider.effect(Project, Effect.gen(function* () {
1278
1313
  prodDeploymentUrl: prod?.deploymentUrl
1279
1314
  })));
1280
1315
  const getById = (projectId) => getProjectById(api, projectId);
1281
- /** The project with this slug in the team, or undefined. Official endpoint; the team can be a slug. */
1282
- const getBySlug = (team, slug) => absentAsUndefined(api.request("get project by slug", (client) => client.GET("/teams/{team_id_or_slug}/projects/{project_slug}", { params: { path: {
1283
- team_id_or_slug: String(team),
1316
+ /** The project with this slug in the team, or undefined. */
1317
+ const getBySlug = (teamId, slug) => absentAsUndefined(api.request("get project by slug", (client) => client.GET("/teams/{team_id_or_slug}/projects/{project_slug}", { params: { path: {
1318
+ team_id_or_slug: String(teamId),
1284
1319
  project_slug: slug
1285
1320
  } } }))).pipe(Effect.map((body) => body === void 0 ? void 0 : Schema.decodeUnknownSync(ProjectDetails)(body)));
1286
1321
  const findByName = (teamId, name) => findProjectByName(api, teamId, name);
@@ -1310,45 +1345,37 @@ const ProjectProvider = () => Provider.effect(Project, Effect.gen(function* () {
1310
1345
  diff: Effect.fn(function* ({ olds, news, output }) {
1311
1346
  if (!isResolved(news)) return void 0;
1312
1347
  if (output === void 0) return void 0;
1313
- if ("slug" in news) {
1348
+ const teamId = yield* api.teamId;
1349
+ if (teamId !== output.teamId) return yield* new ProjectTeamChange({
1350
+ projectId: output.projectId,
1351
+ teamId: output.teamId,
1352
+ requestedTeamId: teamId
1353
+ });
1354
+ if (news.slug !== void 0) {
1314
1355
  if (news.slug !== output.slug) return yield* new ProjectSlugChange({
1315
1356
  projectId: output.projectId,
1316
1357
  slug: output.slug,
1317
1358
  requestedSlug: news.slug
1318
1359
  });
1319
- if (olds.team !== news.team) {
1320
- const details = yield* getById(output.projectId);
1321
- if (details !== void 0 && !isTeamOf(details, news.team)) return yield* new ProjectTeamChange({
1322
- projectId: output.projectId,
1323
- teamId: output.teamId,
1324
- requestedTeamId: yield* api.resolveTeamId(news.team)
1325
- });
1326
- }
1327
1360
  return { action: "noop" };
1328
1361
  }
1329
- if (olds.team !== news.team) {
1330
- const requestedTeamId = yield* api.resolveTeamId(news.team);
1331
- if (requestedTeamId !== output.teamId) return yield* new ProjectTeamChange({
1332
- projectId: output.projectId,
1333
- teamId: output.teamId,
1334
- requestedTeamId
1335
- });
1336
- }
1337
- return "name" in olds && olds.name === news.name ? void 0 : { action: "update" };
1362
+ return olds.name === news.name ? void 0 : { action: "update" };
1338
1363
  }),
1339
1364
  read: Effect.fn(function* ({ olds, output }) {
1340
1365
  if (output !== void 0) {
1341
1366
  const details = yield* getById(output.projectId);
1342
1367
  return details === void 0 ? void 0 : yield* attributesOf(details);
1343
1368
  }
1344
- const existing = "slug" in olds ? yield* getBySlug(olds.team, olds.slug) : yield* findByName(yield* api.resolveTeamId(olds.team), olds.name);
1369
+ const teamId = yield* api.teamId;
1370
+ const existing = olds.slug !== void 0 ? yield* getBySlug(teamId, olds.slug) : yield* findByName(teamId, olds.name);
1345
1371
  return existing === void 0 ? void 0 : Unowned(yield* attributesOf(existing));
1346
1372
  }),
1347
1373
  reconcile: Effect.fn(function* ({ fqn, olds, news, output }) {
1348
- if ("slug" in news) {
1349
- const details = output === void 0 ? yield* getBySlug(news.team, news.slug) : yield* getById(output.projectId);
1374
+ const teamId = yield* api.teamId;
1375
+ if (news.slug !== void 0) {
1376
+ const details = output === void 0 ? yield* getBySlug(teamId, news.slug) : yield* getById(output.projectId);
1350
1377
  if (details === void 0) return yield* new ProjectNotFound({
1351
- team: String(news.team),
1378
+ teamId,
1352
1379
  slug: news.slug
1353
1380
  });
1354
1381
  if (details.slug !== news.slug) return yield* new ProjectSlugChange({
@@ -1356,10 +1383,10 @@ const ProjectProvider = () => Provider.effect(Project, Effect.gen(function* () {
1356
1383
  slug: details.slug,
1357
1384
  requestedSlug: news.slug
1358
1385
  });
1359
- if (!isTeamOf(details, news.team)) return yield* new ProjectTeamChange({
1386
+ if (details.teamId !== teamId) return yield* new ProjectTeamChange({
1360
1387
  projectId: details.id,
1361
1388
  teamId: details.teamId,
1362
- requestedTeamId: yield* api.resolveTeamId(news.team)
1389
+ requestedTeamId: teamId
1363
1390
  });
1364
1391
  if (output === void 0 && !(yield* shouldAdopt(fqn))) return yield* new OwnedBySomeoneElse({
1365
1392
  message: `Convex project ${details.id} ("${news.slug}") exists and is not in this stack's state. Re-run with --adopt to take it over.`,
@@ -1368,9 +1395,8 @@ const ProjectProvider = () => Provider.effect(Project, Effect.gen(function* () {
1368
1395
  });
1369
1396
  return yield* attributesOf(details);
1370
1397
  }
1371
- const teamId = yield* api.resolveTeamId(news.team);
1372
1398
  if (output !== void 0) {
1373
- if (olds !== void 0 && "name" in olds) yield* checkCreateTimeProps(output.projectId, olds, news);
1399
+ if (olds !== void 0 && olds.name !== void 0) yield* checkCreateTimeProps(output.projectId, olds, news);
1374
1400
  if (output.teamId !== teamId) return yield* new ProjectTeamChange({
1375
1401
  projectId: output.projectId,
1376
1402
  teamId: output.teamId,
@@ -1712,8 +1738,8 @@ const ensureSealed = (options, deployment, functions) => Effect.gen(function* ()
1712
1738
  * the run whose project is not the oldest deletes its own, which holds
1713
1739
  * nothing yet, and uses the oldest, so that every run ends on one store.
1714
1740
  */
1715
- const createStoreProject = (api, teamId, team) => Effect.gen(function* () {
1716
- yield* Effect.logInfo(`Convex state: creating the project ${STORE_PROJECT_NAME} in team ${team}`);
1741
+ const createStoreProject = (api, teamId) => Effect.gen(function* () {
1742
+ yield* Effect.logInfo(`Convex state: creating the project ${STORE_PROJECT_NAME} in team ${teamId}`);
1717
1743
  const created = yield* createProject(api, teamId, {
1718
1744
  name: STORE_PROJECT_NAME,
1719
1745
  deploymentType: "prod",
@@ -1732,9 +1758,8 @@ const createStoreProject = (api, teamId, team) => Effect.gen(function* () {
1732
1758
  */
1733
1759
  const makeConvexState = (options) => Effect.gen(function* () {
1734
1760
  const { api } = options;
1735
- const teamId = yield* api.resolveTeamId(options.team);
1736
- const team = options.team ?? teamId;
1737
- const project = (yield* findProjectByName(api, teamId, "alchemy-state-store")) ?? (yield* createStoreProject(api, teamId, team));
1761
+ const teamId = yield* api.teamId;
1762
+ const project = (yield* findProjectByName(api, teamId, "alchemy-state-store")) ?? (yield* createStoreProject(api, teamId));
1738
1763
  const deployment = yield* prodDeploymentOf(api, project.id);
1739
1764
  if (deployment === void 0) return yield* new ProductionDeploymentNotFound({ projectId: project.id });
1740
1765
  const keys = [];
@@ -1750,7 +1775,7 @@ const makeConvexState = (options) => Effect.gen(function* () {
1750
1775
  })))) };
1751
1776
  let version = yield* storeVersion(functions);
1752
1777
  if (version === void 0 || version < 5) {
1753
- 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.`);
1778
+ if (version === void 0 && deployment.lastDeployTime) return yield* failure(`project ${STORE_PROJECT_NAME} (${project.id}) of team ${teamId} has code that is not the state store, and Convex.state does not push over it. Rename that project in the Convex dashboard.`);
1754
1779
  version = yield* storeVersion(functions);
1755
1780
  if (version === void 0 || version < 5) {
1756
1781
  yield* pushStore(options, deployment);
@@ -1799,14 +1824,13 @@ const makeConvexState = (options) => Effect.gen(function* () {
1799
1824
  };
1800
1825
  });
1801
1826
  /**
1802
- * Alchemy state in the `alchemy-state-store` project of `team`, with the team token
1803
- * in CONVEX_ACCESS_TOKEN or the Convex CLI login. A team access token names
1804
- * its team, so `team` is optional with one; the Convex CLI login needs the
1805
- * slug or the id. The first use of the run finds or creates the project and
1806
- * pushes the store when it is missing or older. `alchemy plan` on another
1807
- * stack never touches Convex.
1827
+ * Alchemy state in the `alchemy-state-store` project of the team of the credential: the team token
1828
+ * in CONVEX_ACCESS_TOKEN, which names its team, or the Convex CLI login, with the team of the
1829
+ * deployment that the CLI selected (CONVEX_DEPLOYMENT, see ManagementApi.teamId). The first use of
1830
+ * the run finds or creates the project and pushes the store when it is missing or older.
1831
+ * `alchemy plan` on another stack never touches Convex.
1808
1832
  */
1809
- const state = (options = {}) => Layer.effect(State, Effect.gen(function* () {
1833
+ const state = () => Layer.effect(State, Effect.gen(function* () {
1810
1834
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
1811
1835
  const scope = yield* Scope.Scope;
1812
1836
  const context = yield* Effect.context();
@@ -1817,7 +1841,6 @@ const state = (options = {}) => Layer.effect(State, Effect.gen(function* () {
1817
1841
  env: process.env
1818
1842
  }).pipe(Effect.catch((error) => Effect.logWarning(`Convex state: the stage outputs of this run carry no record. ${error.message}`).pipe(Effect.as(void 0))));
1819
1843
  return yield* makeConvexState({
1820
- team: options.team,
1821
1844
  api,
1822
1845
  spawner,
1823
1846
  scope,
@@ -1989,4 +2012,4 @@ const registerDevelopmentClient = (options) => Effect.gen(function* () {
1989
2012
  //#endregion
1990
2013
  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 };
1991
2014
 
1992
- //# sourceMappingURL=HubClient-Bs5_j18U.js.map
2015
+ //# sourceMappingURL=HubClient-HeGoVOLO.js.map