@samebase/alchemy-convex 0.1.0 → 0.2.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.
Files changed (51) hide show
  1. package/README.md +85 -17
  2. package/lib/index.d.ts +352 -9
  3. package/lib/index.d.ts.map +1 -1
  4. package/lib/index.js +962 -10
  5. package/lib/index.js.map +1 -1
  6. package/package.json +7 -4
  7. package/src/Adoption.ts +30 -0
  8. package/src/DefaultEnvironmentVariable.ts +127 -29
  9. package/src/Deploy.ts +37 -15
  10. package/src/DeployKey.ts +221 -60
  11. package/src/EnvironmentVariable.ts +174 -41
  12. package/src/ManagementApi.ts +57 -1
  13. package/src/PreviewDeployKey.ts +65 -38
  14. package/src/Project.ts +236 -52
  15. package/src/index.ts +9 -0
  16. package/lib/Credentials.d.ts +0 -39
  17. package/lib/Credentials.d.ts.map +0 -1
  18. package/lib/Credentials.js +0 -49
  19. package/lib/Credentials.js.map +0 -1
  20. package/lib/DefaultEnvironmentVariable.d.ts +0 -58
  21. package/lib/DefaultEnvironmentVariable.d.ts.map +0 -1
  22. package/lib/DefaultEnvironmentVariable.js +0 -93
  23. package/lib/DefaultEnvironmentVariable.js.map +0 -1
  24. package/lib/Deploy.d.ts +0 -62
  25. package/lib/Deploy.d.ts.map +0 -1
  26. package/lib/Deploy.js +0 -92
  27. package/lib/Deploy.js.map +0 -1
  28. package/lib/DeployKey.d.ts +0 -48
  29. package/lib/DeployKey.d.ts.map +0 -1
  30. package/lib/DeployKey.js +0 -104
  31. package/lib/DeployKey.js.map +0 -1
  32. package/lib/EnvironmentVariable.d.ts +0 -37
  33. package/lib/EnvironmentVariable.d.ts.map +0 -1
  34. package/lib/EnvironmentVariable.js +0 -65
  35. package/lib/EnvironmentVariable.js.map +0 -1
  36. package/lib/ManagementApi.d.ts +0 -50
  37. package/lib/ManagementApi.d.ts.map +0 -1
  38. package/lib/ManagementApi.js +0 -103
  39. package/lib/ManagementApi.js.map +0 -1
  40. package/lib/PreviewDeployKey.d.ts +0 -34
  41. package/lib/PreviewDeployKey.d.ts.map +0 -1
  42. package/lib/PreviewDeployKey.js +0 -78
  43. package/lib/PreviewDeployKey.js.map +0 -1
  44. package/lib/Project.d.ts +0 -29
  45. package/lib/Project.d.ts.map +0 -1
  46. package/lib/Project.js +0 -114
  47. package/lib/Project.js.map +0 -1
  48. package/lib/Providers.d.ts +0 -13
  49. package/lib/Providers.d.ts.map +0 -1
  50. package/lib/Providers.js +0 -30
  51. package/lib/Providers.js.map +0 -1
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,0BAA0B,GAI3B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,MAAM,EAEN,UAAU,EAEV,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,SAAS,GAIV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,GAGpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EACL,gBAAgB,GAGjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAEL,OAAO,GAGR,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","names":["checkIdentity"],"sources":["../src/Credentials.ts","../src/Adoption.ts","../src/ManagementApi.ts","../src/EnvironmentVariable.ts","../src/DefaultEnvironmentVariable.ts","../src/Deploy.ts","../src/DeployKey.ts","../src/PreviewDeployKey.ts","../src/Project.ts","../src/Providers.ts"],"sourcesContent":["// Convex control-plane credentials for the provider.\n//\n// The service value is an Effect so that building the providers layer never\n// touches the environment or the filesystem. The token is read on the first\n// lifecycle call, which keeps `alchemy plan` on an unrelated stack free of\n// Convex credential errors.\nimport { readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport * as Config from \"effect/Config\";\nimport * as Context from \"effect/Context\";\nimport * as Effect from \"effect/Effect\";\nimport * as Layer from \"effect/Layer\";\nimport * as Redacted from \"effect/Redacted\";\nimport * as Schema from \"effect/Schema\";\n\nexport interface ResolvedCredentials {\n /** Bearer token accepted by https://api.convex.dev/v1: a team access token, an OAuth token, or the Convex CLI login. */\n readonly accessToken: Redacted.Redacted<string>;\n /** Where the token came from, for error messages only. */\n readonly source: \"env\" | \"explicit\" | \"convex-cli-login\";\n}\n\nexport class Credentials extends Context.Service<Credentials, Effect.Effect<ResolvedCredentials>>()(\n \"@samebase/alchemy-convex/Credentials\",\n) {}\n\n/** Environment variable read by {@link fromConfig}. */\nexport const ACCESS_TOKEN_ENV = \"CONVEX_ACCESS_TOKEN\";\n\n/** The Convex CLI stores its device login here after `npx convex login`. */\nexport const convexCliConfigPath = () => join(homedir(), \".convex\", \"config.json\");\n\nconst CliConfig = Schema.Struct({ accessToken: Schema.String });\n\n/**\n * Token the Convex CLI saved on this machine. It works on the Management API\n * for the teams the logged-in user belongs to. Fails when nobody is logged in.\n */\nexport const readConvexCliLogin: Effect.Effect<ResolvedCredentials, CredentialsError> = Effect.gen(\n function* () {\n const path = convexCliConfigPath();\n const text = yield* Effect.tryPromise({\n try: () => readFile(path, \"utf8\"),\n catch: () => new CredentialsError({ reason: `no Convex CLI login at ${path}` }),\n });\n const parsed = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(CliConfig))(text).pipe(\n Effect.mapError(() => new CredentialsError({ reason: `${path} has no accessToken` })),\n );\n return { accessToken: Redacted.make(parsed.accessToken), source: \"convex-cli-login\" } as const;\n },\n);\n\nexport class CredentialsError extends Schema.TaggedError<CredentialsError>()(\"CredentialsError\", {\n reason: Schema.String,\n}) {}\n\n/**\n * Default resolution: `CONVEX_ACCESS_TOKEN` from the stack ConfigProvider\n * (process env, `.env`, stack secrets), then the Convex CLI login on this\n * machine. CI sets the variable; a developer who ran `npx convex login` needs\n * nothing.\n */\nexport const fromConfig = (): Layer.Layer<Credentials> =>\n Layer.succeed(\n Credentials,\n Effect.cached(\n Config.Redacted(ACCESS_TOKEN_ENV).pipe(\n Effect.map((accessToken): ResolvedCredentials => ({ accessToken, source: \"env\" })),\n Effect.catch(() => readConvexCliLogin),\n Effect.orDie,\n ),\n ).pipe(Effect.flatten),\n );\n\n/** Explicit token, for tests or callers that already hold a value. */\nexport const fromToken = (accessToken: Redacted.Redacted<string>): Layer.Layer<Credentials> =>\n Layer.succeed(Credentials, Effect.succeed({ accessToken, source: \"explicit\" } as const));\n","// Adoption check for reconcile.\n//\n// The engine checks ownership during plan: `read` returns `Unowned(...)` for\n// an object that is not in state, and the engine then needs `--adopt`. A\n// reconcile without state can still find an existing object, for example\n// after a replacement, after an interrupted create, or when another run\n// created it between plan and apply. Reconcile takes that object over only\n// when adoption is on. The order is the same as in the engine: the\n// resource's own `adopt(...)`, then the AdoptPolicy service, then the\n// `--adopt` flag in AlchemyContext.\n//\n// Pattern from Confect, `packages/alchemy/src/internal/Lifecycle.ts`\n// (https://github.com/rjdellecese/confect, ISC license).\nimport { AdoptPolicy } from \"alchemy/AdoptPolicy\";\nimport { AlchemyContext } from \"alchemy/AlchemyContext\";\nimport { Stack } from \"alchemy/Stack\";\nimport * as Effect from \"effect/Effect\";\nimport * as Option from \"effect/Option\";\n\n/** True when the resource `fqn` may take over an object that is not in state. */\nexport const shouldAdopt = (fqn: string): Effect.Effect<boolean> =>\n Effect.gen(function* () {\n const stack = yield* Effect.serviceOption(Stack);\n const own = Option.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;\n if (own !== undefined) return own;\n const policy = yield* Effect.serviceOption(AdoptPolicy);\n if (Option.isSome(policy)) return policy.value;\n const context = yield* Effect.serviceOption(AlchemyContext);\n return Option.isSome(context) && context.value.adopt;\n });\n","// Effect wrapper around the official Convex platform client.\n//\n// `@convex-dev/platform` is an openapi-fetch client: every call resolves to\n// `{ data, error, response }`. This service turns that into an Effect with one\n// typed error, resolves credentials lazily, and validates the fields the\n// provider relies on at runtime.\nimport { createDeploymentClient, createManagementClient } from \"@convex-dev/platform\";\nimport * as Context from \"effect/Context\";\nimport * as Effect from \"effect/Effect\";\nimport * as Layer from \"effect/Layer\";\nimport * as Redacted from \"effect/Redacted\";\nimport * as Schedule from \"effect/Schedule\";\nimport * as Schema from \"effect/Schema\";\nimport { Credentials } from \"./Credentials.ts\";\n\nexport type ManagementClient = ReturnType<typeof createManagementClient>;\nexport type DeploymentClient = ReturnType<typeof createDeploymentClient>;\n\n/** Shape of every openapi-fetch result we consume. */\nexport interface FetchResult<T> {\n readonly data?: T;\n readonly error?: unknown;\n readonly response: Response;\n}\n\n/**\n * One error for every control-plane and deployment call. Resources branch on\n * `status` (404 means absent for read and delete), so the class is load\n * bearing, not decorative.\n */\nexport class ConvexApiError extends Schema.TaggedError<ConvexApiError>()(\"ConvexApiError\", {\n operation: Schema.String,\n status: Schema.Number,\n code: Schema.optionalKey(Schema.String),\n message: Schema.String,\n}) {}\n\nconst ErrorBody = Schema.Struct({\n code: Schema.optionalKey(Schema.String),\n message: Schema.optionalKey(Schema.String),\n});\n\nconst toApiError = (operation: string, result: FetchResult<unknown>) => {\n const body = Schema.decodeUnknownSync(Schema.NullishOr(ErrorBody))(result.error ?? null);\n const code = body?.code;\n return new ConvexApiError({\n operation,\n status: result.response.status,\n ...(code === undefined ? {} : { code }),\n message: body?.message ?? `${operation} failed with HTTP ${result.response.status}`,\n });\n};\n\nexport interface ManagementApiService {\n /** Run one management call with the resolved credentials. */\n readonly request: <T>(\n operation: string,\n run: (client: ManagementClient) => Promise<FetchResult<T>>,\n ) => Effect.Effect<T, ConvexApiError>;\n /** Run one management call whose success response has no body. */\n readonly requestVoid: (\n operation: string,\n run: (client: ManagementClient) => Promise<FetchResult<unknown>>,\n ) => Effect.Effect<void, ConvexApiError>;\n /** Run one deployment-scoped call authenticated with a deploy key. */\n readonly deploymentRequest: <T>(\n operation: string,\n deploymentUrlOrName: string,\n deployKey: Redacted.Redacted<string>,\n run: (client: DeploymentClient) => Promise<FetchResult<T>>,\n ) => Effect.Effect<T, ConvexApiError>;\n /** Team id for a slug or numeric id. */\n readonly resolveTeamId: (team: string | number) => Effect.Effect<number, ConvexApiError>;\n /**\n * True when a deploy key that Convex returned carries the token of the\n * Management API credential itself. With an OAuth token, create_deploy_key\n * and create_preview_deploy_key create no new key: they return the OAuth\n * token with a new prefix, and the part after \"|\" is the same. A delete of\n * that key would revoke the credential.\n */\n readonly isCredentialToken: (secret: string) => Effect.Effect<boolean>;\n}\n\n/** The part of a Convex token after the first \"|\", or the whole value when it has none. */\nconst tokenPart = (value: string) => value.slice(value.indexOf(\"|\") + 1);\n\nexport class ManagementApi extends Context.Service<ManagementApi, ManagementApiService>()(\n \"@samebase/alchemy-convex/ManagementApi\",\n) {}\n\nconst attempt = <T>(operation: string, run: () => Promise<FetchResult<T>>) =>\n Effect.tryPromise({\n try: run,\n catch: (cause) =>\n new ConvexApiError({\n operation,\n status: 0,\n message: cause instanceof Error ? cause.message : String(cause),\n }),\n });\n\n/** Reads `data` or fails with the HTTP status and the API's own code and message. */\nexport const settle = <T>(\n operation: string,\n run: () => Promise<FetchResult<T>>,\n): Effect.Effect<T, ConvexApiError> =>\n attempt(operation, run).pipe(\n Effect.flatMap((result) =>\n result.response.ok && result.data !== undefined\n ? Effect.succeed(result.data)\n : Effect.fail(toApiError(operation, result)),\n ),\n );\n\n/** For endpoints that answer with an empty body, such as delete calls. */\nexport const settleVoid = (\n operation: string,\n run: () => Promise<FetchResult<unknown>>,\n): Effect.Effect<void, ConvexApiError> =>\n attempt(operation, run).pipe(\n Effect.flatMap((result) =>\n result.response.ok ? Effect.void : Effect.fail(toApiError(operation, result)),\n ),\n );\n\n/**\n * True when an idempotent write can succeed on a new attempt:\n *\n * - A Convex write conflict. Two parallel update_environment_variables calls\n * on one deployment gave HTTP 503 `OptimisticConcurrencyControlFailure`,\n * \"Data read or written in this mutation changed while it was being run\",\n * in a live run. The platform codes `OCC` and `WriteConflict` and the\n * message \"... changed while this mutation was being run ...\" mean the same.\n * - Any other 5xx answer. Two parallel delete_preview_deploy_key calls on one\n * project gave HTTP 500 `InternalServerError`, \"Your request couldn't be\n * completed. Try again later.\", in a live run. The next attempt succeeded.\n */\nexport const isRetryableWriteError = (error: ConvexApiError): boolean =>\n error.code === \"OptimisticConcurrencyControlFailure\" ||\n error.code === \"OCC\" ||\n error.code === \"WriteConflict\" ||\n /changed while (it|this mutation) was being run/.test(error.message) ||\n error.status >= 500;\n\n/**\n * Runs an idempotent write again after a retryable Convex error: at most\n * four more attempts, with exponential backoff from 100 ms and jitter. Other\n * errors fail at once, and so does the last retryable error. Use it only for\n * a write that has the same result when it runs twice, such as setting a\n * value or deleting by id. Never for a create that does not check first.\n */\nexport const retryIdempotentWrite = <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n): Effect.Effect<A, E, R> =>\n effect.pipe(\n Effect.retry({\n while: (error: E) => error instanceof ConvexApiError && isRetryableWriteError(error),\n times: 4,\n schedule: Schedule.exponential(\"100 millis\").pipe(Schedule.jittered),\n }),\n );\n\n/** Turns a 404 into `undefined` so read and delete stay idempotent. */\nexport const absentAsUndefined = <A, R>(\n effect: Effect.Effect<A, ConvexApiError, R>,\n): Effect.Effect<A | undefined, ConvexApiError, R> =>\n effect.pipe(\n Effect.catchIf(\n (error) => error.status === 404,\n () => Effect.succeed(undefined),\n ),\n );\n\nconst TeamRow = Schema.Struct({ id: Schema.Number, slug: Schema.String });\nconst TeamRows = Schema.Array(TeamRow);\n\nexport const ManagementApiLive = (): Layer.Layer<ManagementApi, never, Credentials> =>\n Layer.effect(\n ManagementApi,\n Effect.gen(function* () {\n const credentials = yield* Credentials;\n const client = Effect.cached(\n credentials.pipe(\n Effect.map((resolved) => createManagementClient(Redacted.value(resolved.accessToken))),\n ),\n ).pipe(Effect.flatten);\n\n const request: ManagementApiService[\"request\"] = (operation, run) =>\n client.pipe(Effect.flatMap((c) => settle(operation, () => run(c))));\n\n const requestVoid: ManagementApiService[\"requestVoid\"] = (operation, run) =>\n client.pipe(Effect.flatMap((c) => settleVoid(operation, () => run(c))));\n\n const deploymentRequest: ManagementApiService[\"deploymentRequest\"] = (\n operation,\n deploymentUrlOrName,\n deployKey,\n run,\n ) =>\n settle(operation, () =>\n run(createDeploymentClient(deploymentUrlOrName, Redacted.value(deployKey))),\n );\n\n // The Management API addresses teams by numeric id. The only slug lookup\n // it offers is per project, so a slug falls back to the dashboard's team\n // list, which is unofficial. Pass the numeric team id to skip this.\n const resolveTeamId: ManagementApiService[\"resolveTeamId\"] = (team) =>\n Effect.gen(function* () {\n if (typeof team === \"number\") return team;\n if (/^\\d+$/.test(team)) return Number(team);\n const resolved = yield* credentials;\n const rows = yield* Effect.tryPromise({\n try: async () => {\n const response = await fetch(\"https://api.convex.dev/api/dashboard/teams\", {\n headers: { Authorization: `Bearer ${Redacted.value(resolved.accessToken)}` },\n });\n if (!response.ok) {\n throw new Error(`team lookup for \"${team}\" failed with HTTP ${response.status}`);\n }\n return Schema.decodeUnknownSync(TeamRows)(await response.json());\n },\n catch: (cause) =>\n new ConvexApiError({\n operation: \"resolve team\",\n status: 0,\n message: cause instanceof Error ? cause.message : String(cause),\n }),\n });\n const match = rows.find((row) => row.slug === team);\n if (match === undefined) {\n return yield* new ConvexApiError({\n operation: \"resolve team\",\n status: 404,\n message: `no team with slug \"${team}\" for this token`,\n });\n }\n return match.id;\n });\n\n const isCredentialToken: ManagementApiService[\"isCredentialToken\"] = (secret) =>\n credentials.pipe(\n Effect.map(\n (resolved) => tokenPart(Redacted.value(resolved.accessToken)) === tokenPart(secret),\n ),\n );\n\n return { request, requestVoid, deploymentRequest, resolveTeamId, isCredentialToken };\n }),\n );\n","// Convex.EnvironmentVariable: one environment variable on one deployment.\n//\n// Environment variables live on the deployment API\n// (https://<deployment>.convex.cloud/api/v1), which authenticates with a\n// deploy key, not with the Management API token. Setting a variable and\n// removing it are the same call: update_environment_variables with\n// `value: null` removes the variable.\n//\n// A variable that exists but is not in state belongs to someone else: its\n// old value is lost when this resource writes it. Alchemy takes it over only\n// with --adopt.\n//\n// The variable's name and deployment cannot change. A change fails with\n// VariableIdentityChange: use a new resource with a new logical id. A\n// replacement could let the engine delete an old generation that names the\n// live variable, and a rename in place needs a cleanup that can fail and\n// leave a variable or a revoked key behind.\n//\n// The attributes keep the deploy key of the variable's deployment. After a\n// refused change, the props can name another deployment and its key, so\n// read and delete use the attributes, not the props.\nimport { createDeploymentClient } from \"@convex-dev/platform\";\nimport { Resource } from \"alchemy\";\nimport { isResolved } from \"alchemy/Diff\";\nimport { OwnedBySomeoneElse, Unowned } from \"alchemy/AdoptPolicy\";\nimport * as Provider from \"alchemy/Provider\";\nimport * as Effect from \"effect/Effect\";\nimport * as Redacted from \"effect/Redacted\";\nimport * as Schema from \"effect/Schema\";\nimport { shouldAdopt } from \"./Adoption.ts\";\nimport {\n absentAsUndefined,\n ManagementApi,\n type ManagementApiService,\n retryIdempotentWrite,\n settleVoid,\n} from \"./ManagementApi.ts\";\nimport type { Providers } from \"./Providers.ts\";\n\n/**\n * The variable's identity when the listing has `name`. Own keys only: a\n * valid variable name such as `toString` must not match the object prototype.\n */\nexport const findVariable = (\n list: typeof EnvironmentVariableList.Type,\n deployment: string,\n name: string,\n): { readonly deployment: string; readonly name: string } | undefined =>\n Object.hasOwn(list.environmentVariables, name) ? { deployment, name } : undefined;\n\n/**\n * One attempt to set one variable, or to remove it when `value` is null. The\n * deployment answers 200 with an empty body, so this reads no response data.\n */\nconst writeVariable = (\n deployment: string,\n deployKey: Redacted.Redacted<string>,\n name: string,\n value: string | Redacted.Redacted<string> | null,\n) =>\n settleVoid(\"update environment variables\", () =>\n createDeploymentClient(deployment, Redacted.value(deployKey)).POST(\n \"/update_environment_variables\",\n {\n body: {\n changes: [{ name, value: Redacted.isRedacted(value) ? Redacted.value(value) : value }],\n },\n },\n ),\n );\n\n/**\n * Sets one variable, or removes it when `value` is null. The call sets a\n * value, so it runs again after a write conflict or a 5xx answer, for example\n * when another resource writes a variable of the same deployment at the same\n * time. Use it only where the variable is already ours to set.\n */\nexport const updateVariable = (\n deployment: string,\n deployKey: Redacted.Redacted<string>,\n name: string,\n value: string | Redacted.Redacted<string> | null,\n) => retryIdempotentWrite(writeVariable(deployment, deployKey, name, value));\n\n/** GET https://<deployment>.convex.cloud/api/v1/list_environment_variables */\nexport const EnvironmentVariableList = Schema.Struct({\n environmentVariables: Schema.Record(Schema.String, Schema.String),\n});\n\n/** The variable's identity, or undefined when the variable or the deployment is absent. */\nconst readVariable = (\n api: ManagementApiService,\n deployment: string,\n deployKey: Redacted.Redacted<string>,\n name: string,\n) =>\n // 404: the deployment no longer exists, so neither does the variable.\n absentAsUndefined(\n api.deploymentRequest(\"list environment variables\", deployment, deployKey, (client) =>\n client.GET(\"/list_environment_variables\"),\n ),\n ).pipe(\n Effect.map((body) =>\n body === undefined\n ? undefined\n : findVariable(Schema.decodeUnknownSync(EnvironmentVariableList)(body), deployment, name),\n ),\n );\n\nexport interface EnvironmentVariableProps {\n /** Deployment name, or its URL for deployments outside US East. */\n readonly deployment: string;\n /** Deploy key for `deployment`, such as `deployKey.deployKey`. */\n readonly deployKey: Redacted.Redacted<string>;\n readonly name: string;\n readonly value: string | Redacted.Redacted<string>;\n}\n\nexport interface EnvironmentVariableAttributes {\n readonly deployment: string;\n readonly name: string;\n /** Deploy key for `deployment`, for read and delete. Undefined in state from 0.1.x. */\n readonly deployKey: Redacted.Redacted<string> | undefined;\n}\n\nexport type EnvironmentVariable = Resource<\n \"Convex.EnvironmentVariable\",\n EnvironmentVariableProps,\n EnvironmentVariableAttributes,\n never,\n Providers\n>;\nexport const EnvironmentVariable = Resource<EnvironmentVariable>(\"Convex.EnvironmentVariable\");\n\n/**\n * The props name another variable than the one in state. Used by\n * Convex.EnvironmentVariable and Convex.DefaultEnvironmentVariable.\n */\nexport class VariableIdentityChange extends Schema.TaggedError<VariableIdentityChange>()(\n \"VariableIdentityChange\",\n {\n resourceType: Schema.String,\n field: Schema.Literals([\"name\", \"deployment\", \"projectId\", \"deploymentType\"]),\n current: Schema.String,\n requested: Schema.String,\n },\n) {\n override get message() {\n return `${this.resourceType} cannot change its ${this.field} from ${this.current} to ${this.requested}. Create a new resource with a new logical id for the new name or deployment, and remove this one.`;\n }\n}\n\n/** Fails with {@link VariableIdentityChange} when the props name another variable than the one in state. */\nconst checkIdentity = (output: EnvironmentVariableAttributes, news: EnvironmentVariableProps) =>\n output.name !== news.name\n ? Effect.fail(\n new VariableIdentityChange({\n resourceType: EnvironmentVariable.Type,\n field: \"name\",\n current: output.name,\n requested: news.name,\n }),\n )\n : output.deployment !== news.deployment\n ? Effect.fail(\n new VariableIdentityChange({\n resourceType: EnvironmentVariable.Type,\n field: \"deployment\",\n current: output.deployment,\n requested: news.deployment,\n }),\n )\n : Effect.void;\n\n/**\n * The deploy key for the variable in state: from the attributes, or, for\n * state from 0.1.x, from props that name the same deployment.\n */\nconst keyOf = (\n output: EnvironmentVariableAttributes,\n olds: EnvironmentVariableProps | undefined,\n): Redacted.Redacted<string> | undefined =>\n output.deployKey ?? (olds?.deployment === output.deployment ? olds.deployKey : undefined);\n\nexport const EnvironmentVariableProvider = () =>\n Provider.effect(\n EnvironmentVariable,\n Effect.gen(function* () {\n const api = yield* ManagementApi;\n\n return {\n // Compares with the attributes: after a refused change, `olds` are\n // the refused props. Otherwise undefined: the engine updates when any\n // prop changed and compares Redacted values, such as the value and\n // the deploy key, by content.\n diff: Effect.fn(function* ({ news, output }) {\n if (!isResolved(news) || output === undefined) return undefined;\n yield* checkIdentity(output, news);\n return undefined;\n }),\n\n read: Effect.fn(function* ({ olds, output }) {\n if (output !== undefined) {\n const key = keyOf(output, olds);\n // Without a key for the deployment in state, keep the state as it is.\n if (key === undefined) return output;\n const found = yield* readVariable(api, output.deployment, key, output.name);\n return found === undefined ? undefined : output;\n }\n const found = yield* readVariable(api, olds.deployment, olds.deployKey, olds.name);\n // No state: the variable belongs to someone else until the user passes --adopt.\n return found === undefined ? undefined : Unowned({ ...found, deployKey: olds.deployKey });\n }),\n\n reconcile: Effect.fn(function* ({ fqn, news, output }) {\n // Also here: with an unresolved prop in plan, diff could not check.\n if (output !== undefined) yield* checkIdentity(output, news);\n const write = writeVariable(news.deployment, news.deployKey, news.name, news.value);\n if (output !== undefined || (yield* shouldAdopt(fqn))) {\n // In state, or adopted: the variable is ours to set.\n yield* retryIdempotentWrite(write);\n } else {\n // Not in state: overwriting a variable that exists loses its\n // value, so that needs adoption. Each attempt checks first, so a\n // retry after a write conflict never overwrites a variable that\n // another writer created in the meantime.\n yield* retryIdempotentWrite(\n Effect.gen(function* () {\n const existing = yield* readVariable(\n api,\n news.deployment,\n news.deployKey,\n news.name,\n );\n if (existing !== undefined) {\n return yield* new OwnedBySomeoneElse({\n message: `Environment variable ${news.name} already exists on deployment ${news.deployment}. Re-run with --adopt to take it over and overwrite its value, or use a different name.`,\n resourceType: EnvironmentVariable.Type,\n physicalName: news.name,\n });\n }\n yield* write;\n }),\n );\n }\n return { deployment: news.deployment, name: news.name, deployKey: news.deployKey };\n }),\n\n delete: Effect.fn(function* ({ olds, output }) {\n const key = keyOf(output, olds);\n if (key === undefined) {\n yield* Effect.logWarning(\n `Environment variable ${output.name} on deployment ${output.deployment} stays: Alchemy has no deploy key for that deployment. Remove it in the Convex dashboard.`,\n );\n return;\n }\n // Removing an absent variable succeeds; 404 means the deployment is gone.\n yield* absentAsUndefined(updateVariable(output.deployment, key, output.name, null));\n }),\n };\n }),\n );\n","// Convex.DefaultEnvironmentVariable: a project default environment variable\n// for one deployment type. Convex sets it on each new deployment of that type,\n// such as auth keys that every pull request preview deployment needs.\n//\n// Defaults live on the Management API and authenticate with the team\n// credential, not with a deploy key. Setting a default and removing it are the\n// same call: update_default_environment_variables with `value: null` removes\n// the default, and removing an absent default also answers 200.\n//\n// A default that exists but is not in state belongs to someone else: its old\n// value is lost when this resource writes it. Alchemy takes it over only with\n// --adopt.\n//\n// The default's project, name, and deployment type cannot change. A change\n// fails with VariableIdentityChange: use a new resource with a new logical\n// id. Read and delete use the identity in the attributes: after a refused\n// change, the props name the refused one.\nimport { Resource } from \"alchemy\";\nimport { OwnedBySomeoneElse, Unowned } from \"alchemy/AdoptPolicy\";\nimport { isResolved } from \"alchemy/Diff\";\nimport * as Provider from \"alchemy/Provider\";\nimport * as Effect from \"effect/Effect\";\nimport * as Redacted from \"effect/Redacted\";\nimport * as Schema from \"effect/Schema\";\nimport type { components } from \"@convex-dev/platform/managementApi\";\nimport { shouldAdopt } from \"./Adoption.ts\";\nimport { VariableIdentityChange } from \"./EnvironmentVariable.ts\";\nimport {\n absentAsUndefined,\n ManagementApi,\n type ManagementApiService,\n retryIdempotentWrite,\n} from \"./ManagementApi.ts\";\nimport type { Providers } from \"./Providers.ts\";\n\n/** Deployment types a default can target, from the generated schema. */\nexport type DeploymentType = components[\"schemas\"][\"DeploymentType\"];\n\n/**\n * Attributes when the listing has `name` for `deploymentType`. An unfiltered\n * listing merges the deployment types that share one value into one row, so\n * the match checks membership in `deploymentTypes`.\n */\nexport const findDefault = (\n list: typeof DefaultEnvironmentVariableList.Type,\n projectId: number,\n name: string,\n deploymentType: DeploymentType,\n): DefaultEnvironmentVariableAttributes | undefined =>\n list.items.some((item) => item.name === name && item.deploymentTypes.includes(deploymentType))\n ? { projectId, name, deploymentType }\n : undefined;\n\n/**\n * Lists the defaults named `name` for `deploymentType`. Convex filters on\n * both, so the listing has at most one row and needs no pagination.\n */\nexport const listDefaults = (\n api: ManagementApiService,\n projectId: number,\n name: string,\n deploymentType: DeploymentType,\n) =>\n api\n .request(\"list default environment variables\", (client) =>\n client.GET(\"/projects/{project_id}/list_default_environment_variables\", {\n params: { path: { project_id: projectId }, query: { name, deploymentType } },\n }),\n )\n .pipe(Effect.map((body) => Schema.decodeUnknownSync(DefaultEnvironmentVariableList)(body)));\n\n/** One attempt to set one default, or to remove it when `value` is null. */\nconst writeDefault = (\n api: ManagementApiService,\n projectId: number,\n name: string,\n deploymentType: DeploymentType,\n value: string | Redacted.Redacted<string> | null,\n) =>\n api.requestVoid(\"update default environment variables\", (client) =>\n client.POST(\"/projects/{project_id}/update_default_environment_variables\", {\n params: { path: { project_id: projectId } },\n body: {\n changes: [\n {\n name,\n deploymentType,\n value: Redacted.isRedacted(value) ? Redacted.value(value) : value,\n },\n ],\n },\n }),\n );\n\n/**\n * Sets one default, or removes it when `value` is null. Convex answers 200\n * with an empty body, so this reads no response data. The call sets a value,\n * so it runs again after a write conflict or a 5xx answer, for example when\n * another resource writes a default of the same project at the same time.\n */\nexport const updateDefault = (\n api: ManagementApiService,\n projectId: number,\n name: string,\n deploymentType: DeploymentType,\n value: string | Redacted.Redacted<string> | null,\n) => retryIdempotentWrite(writeDefault(api, projectId, name, deploymentType, value));\n\n/** The default's identity, or undefined when the default or the project is absent. */\nconst readDefault = (\n api: ManagementApiService,\n projectId: number,\n name: string,\n deploymentType: DeploymentType,\n) =>\n // 404 ProjectNotFound: the project no longer exists, so neither does the default.\n absentAsUndefined(listDefaults(api, projectId, name, deploymentType)).pipe(\n Effect.map((list) =>\n list === undefined ? undefined : findDefault(list, projectId, name, deploymentType),\n ),\n );\n\n/**\n * GET /projects/{project_id}/list_default_environment_variables. Only the\n * fields the provider uses: the row's `value` is the secret in plain text.\n */\nexport const DefaultEnvironmentVariableList = Schema.Struct({\n items: Schema.Array(\n Schema.Struct({ name: Schema.String, deploymentTypes: Schema.Array(Schema.String) }),\n ),\n});\n\nexport interface DefaultEnvironmentVariableProps {\n /** Numeric project id, such as `project.projectId`. */\n readonly projectId: number;\n readonly name: string;\n readonly value: string | Redacted.Redacted<string>;\n /** Deployments of this type that Convex creates from now on get the variable. */\n readonly deploymentType: DeploymentType;\n}\n\nexport interface DefaultEnvironmentVariableAttributes {\n readonly projectId: number;\n readonly name: string;\n readonly deploymentType: DeploymentType;\n}\n\nexport type DefaultEnvironmentVariable = Resource<\n \"Convex.DefaultEnvironmentVariable\",\n DefaultEnvironmentVariableProps,\n DefaultEnvironmentVariableAttributes,\n never,\n Providers\n>;\nexport const DefaultEnvironmentVariable = Resource<DefaultEnvironmentVariable>(\n \"Convex.DefaultEnvironmentVariable\",\n);\n\n/** Fails with VariableIdentityChange when the props name another default than the one in state. */\nconst checkIdentity = (\n output: DefaultEnvironmentVariableAttributes,\n news: DefaultEnvironmentVariableProps,\n) => {\n const changed =\n output.projectId !== news.projectId\n ? ({\n field: \"projectId\",\n current: `${output.projectId}`,\n requested: `${news.projectId}`,\n } as const)\n : output.name !== news.name\n ? ({ field: \"name\", current: output.name, requested: news.name } as const)\n : output.deploymentType !== news.deploymentType\n ? ({\n field: \"deploymentType\",\n current: output.deploymentType,\n requested: news.deploymentType,\n } as const)\n : undefined;\n return changed === undefined\n ? Effect.void\n : Effect.fail(\n new VariableIdentityChange({ resourceType: DefaultEnvironmentVariable.Type, ...changed }),\n );\n};\n\nexport const DefaultEnvironmentVariableProvider = () =>\n Provider.effect(\n DefaultEnvironmentVariable,\n Effect.gen(function* () {\n const api = yield* ManagementApi;\n\n return {\n // Compares with the attributes: after a refused change, `olds` are\n // the refused props. Otherwise undefined: the engine updates when any\n // prop changed and compares a Redacted value by content.\n diff: Effect.fn(function* ({ news, output }) {\n if (!isResolved(news) || output === undefined) return undefined;\n yield* checkIdentity(output, news);\n return undefined;\n }),\n\n read: Effect.fn(function* ({ olds, output }) {\n const at = output ?? olds;\n const found = yield* readDefault(api, at.projectId, at.name, at.deploymentType);\n if (found === undefined) return undefined;\n // No state: the default belongs to someone else until the user passes --adopt.\n return output ?? Unowned(found);\n }),\n\n reconcile: Effect.fn(function* ({ fqn, news, output }) {\n // Also here: with an unresolved prop in plan, diff could not check.\n if (output !== undefined) yield* checkIdentity(output, news);\n const write = writeDefault(\n api,\n news.projectId,\n news.name,\n news.deploymentType,\n news.value,\n );\n if (output !== undefined || (yield* shouldAdopt(fqn))) {\n // In state, or adopted: the default is ours to set.\n yield* retryIdempotentWrite(write);\n } else {\n // Not in state: overwriting a default that exists loses its\n // value, so that needs adoption. Each attempt checks first, so a\n // retry after a write conflict never overwrites a default that\n // another writer created in the meantime.\n yield* retryIdempotentWrite(\n Effect.gen(function* () {\n const existing = yield* readDefault(\n api,\n news.projectId,\n news.name,\n news.deploymentType,\n );\n if (existing !== undefined) {\n return yield* new OwnedBySomeoneElse({\n message: `Project ${news.projectId} already has a ${news.deploymentType} default environment variable ${news.name}. Re-run with --adopt to take it over and overwrite its value, or use a different name.`,\n resourceType: DefaultEnvironmentVariable.Type,\n physicalName: news.name,\n });\n }\n yield* write;\n }),\n );\n }\n return {\n projectId: news.projectId,\n name: news.name,\n deploymentType: news.deploymentType,\n };\n }),\n\n delete: Effect.fn(function* ({ output }) {\n // Removing an absent default succeeds; 404 ProjectNotFound means the project is gone.\n yield* absentAsUndefined(\n updateDefault(api, output.projectId, output.name, output.deploymentType, null),\n );\n }),\n };\n }),\n );\n","// Convex.Deploy: pushes the functions in a `convex/` directory with\n// `npx convex deploy` and exposes the deployment URL for a frontend build.\n//\n// The deployment itself belongs to the project, so delete leaves the last\n// pushed functions running. The URL comes from the CLI's own output because\n// the deploy key, not this resource, decides which deployment receives the push.\nimport { stripVTControlCharacters } from \"node:util\";\nimport { Resource } from \"alchemy\";\nimport { CommandExecutor, type CommandRunProps } from \"alchemy/Command\";\nimport { hashDirectory, type MemoOptions } from \"alchemy/Command/Memo\";\nimport { havePropsChanged, isResolved } from \"alchemy/Diff\";\nimport * as Provider from \"alchemy/Provider\";\nimport * as Effect from \"effect/Effect\";\nimport type * as Redacted from \"effect/Redacted\";\nimport * as Schema from \"effect/Schema\";\nimport type { Providers } from \"./Providers.ts\";\n\nexport interface DeployProps extends Pick<CommandRunProps, \"cwd\" | \"env\" | \"timeout\"> {\n /**\n * Deploy key that selects the target deployment: a production, development,\n * project, or preview deploy key. The CLI receives it as `CONVEX_DEPLOY_KEY`,\n * and Alchemy removes its value from the logged output.\n */\n readonly deployKey: Redacted.Redacted<string>;\n /**\n * Preview deployment name for a preview deploy key. The CLI reuses an\n * existing preview deployment with this name.\n */\n readonly previewName?: string;\n /**\n * Like {@link previewName}, but the CLI deletes and recreates an existing\n * preview deployment with this name, so every run gets a new URL.\n */\n readonly previewCreate?: string;\n /**\n * Function to run after the push, such as `init:seed`. The CLI runs it only\n * when the push created a new preview deployment.\n */\n readonly previewRun?: string;\n /**\n * More `convex deploy` flags, added after the preview flags. Alchemy splits\n * the command on whitespace, so an argument cannot contain whitespace: use\n * `--typecheck=disable`, not `--typecheck disable` as one argument.\n */\n readonly extraArgs?: readonly string[];\n /**\n * Files hashed to skip an unchanged push. By default every non-gitignored\n * file under `cwd` and the nearest lockfile. Use\n * `{ include: [\"convex/**\"], lockfile: true }` to ignore frontend changes,\n * or `false` to push on every deploy. A preview deploy (`previewName` or\n * `previewCreate`) pushes on every deploy: Convex can delete an expired\n * preview deployment, and the hash cannot show that.\n * @default true\n */\n readonly memo?: MemoOptions | boolean;\n}\n\nexport interface DeployAttributes {\n /** Client URL of the deployment that received the push, such as `https://happy-otter-123.convex.cloud`. */\n readonly url: string;\n /** First label of the URL host, such as `happy-otter-123`. */\n readonly deploymentName: string;\n /** Hash of the memoized input files after the push, or `undefined` when `memo` is `false`. */\n readonly hash: string | undefined;\n}\n\nexport type Deploy = Resource<\"Convex.Deploy\", DeployProps, DeployAttributes, never, Providers>;\nexport const Deploy = Resource<Deploy>(\"Convex.Deploy\");\n\n/**\n * The CLI prints this line with `logFinishedStep` after the push succeeds, to\n * stderr (convex 1.45 `cli/lib/deploy2.js` for existing deployments,\n * `cli/deploy.js` for preview deployments). The host is\n * `<deployment>.convex.cloud` or `<deployment>.<region>.convex.cloud`.\n */\nconst DEPLOYED_LINE =\n /Deployed Convex functions to (https:\\/\\/([a-z0-9]+(?:-[a-z0-9]+)*)(?:\\.[a-z0-9]+(?:-[a-z0-9]+)*)?\\.convex\\.cloud)\\/?(?=\\s|$)/m;\n\n/** Reads the deployment URL from `convex deploy` output. ANSI escape codes are removed first. */\nexport const parseDeployOutput = (\n text: string,\n): { url: string; deploymentName: string } | undefined => {\n const match = DEPLOYED_LINE.exec(stripVTControlCharacters(text));\n const url = match?.[1];\n const deploymentName = match?.[2];\n return url === undefined || deploymentName === undefined ? undefined : { url, deploymentName };\n};\n\n/** The `convex deploy` argv, starting with `npx`. */\nexport const deployArgs = (\n props: Pick<DeployProps, \"previewName\" | \"previewCreate\" | \"previewRun\" | \"extraArgs\">,\n): readonly string[] => [\n \"npx\",\n \"convex\",\n \"deploy\",\n ...(props.previewName === undefined ? [] : [\"--preview-name\", props.previewName]),\n ...(props.previewCreate === undefined ? [] : [\"--preview-create\", props.previewCreate]),\n ...(props.previewRun === undefined ? [] : [\"--preview-run\", props.previewRun]),\n ...(props.extraArgs ?? []),\n];\n\n/** Number of output lines quoted when the CLI exits 0 without the deployed line. */\nconst OUTPUT_TAIL_LINES = 20;\n\n/** A `convex deploy` argument contains whitespace, which Alchemy's command runner splits on. */\nexport class DeployArgumentError extends Schema.TaggedError<DeployArgumentError>()(\n \"DeployArgumentError\",\n { argument: Schema.String },\n) {\n override get message() {\n return `Convex.Deploy argument \"${this.argument}\" contains whitespace, and Alchemy splits the command on whitespace. Use the --flag=value form, such as --typecheck=disable, and names without spaces.`;\n }\n}\n\n/** `npx convex deploy` exited 0 but did not print the deployment URL. */\nexport class DeployOutputError extends Schema.TaggedError<DeployOutputError>()(\n \"DeployOutputError\",\n { outputTail: Schema.String },\n) {\n override get message() {\n return `npx convex deploy exited 0 but printed no \"Deployed Convex functions to <url>\" line, so Convex.Deploy has no deployment URL. Remove --dry-run from extraArgs if it is there, and read the CLI output. Last output:\\n${this.outputTail}`;\n }\n}\n\nexport const DeployProvider = () =>\n Provider.effect(\n Deploy,\n Effect.gen(function* () {\n const { run } = yield* CommandExecutor;\n\n const hashOf = (props: DeployProps) =>\n props.memo === false\n ? Effect.succeed(undefined)\n : hashDirectory({\n ...(props.cwd === undefined ? {} : { cwd: props.cwd }),\n memo: props.memo === true || props.memo === undefined ? {} : props.memo,\n });\n\n return {\n // No `stables`: the same deploy key can push to a different URL. A\n // project deploy key follows the project's default production\n // deployment, and a preview deployment can expire or be recreated\n // under the same name.\n diff: Effect.fn(function* ({ olds, news, output }) {\n if (!isResolved(news)) return undefined;\n // A preview deployment can expire. The same hash then hides a\n // missing deployment, so a preview always pushes. The CLI reuses\n // a live preview with the same name.\n const preview = news.previewName !== undefined || news.previewCreate !== undefined;\n if (\n preview ||\n output === undefined ||\n output.hash === undefined ||\n havePropsChanged(olds, news)\n ) {\n return { action: \"update\" } as const;\n }\n return { action: (yield* hashOf(news)) === output.hash ? \"noop\" : \"update\" } as const;\n }),\n\n reconcile: Effect.fn(function* ({ news, session }) {\n const args = deployArgs(news);\n const spaced = args.find((arg) => /\\s/.test(arg));\n if (spaced !== undefined) return yield* new DeployArgumentError({ argument: spaced });\n // A push is idempotent: the CLI uploads the full function set every time.\n const { stdout, stderr } = yield* run(\n {\n command: args.join(\" \"),\n ...(news.cwd === undefined ? {} : { cwd: news.cwd }),\n ...(news.timeout === undefined ? {} : { timeout: news.timeout }),\n env: { ...news.env, CONVEX_DEPLOY_KEY: news.deployKey },\n },\n session,\n );\n // `run` returns output with the deploy key already replaced.\n const output = `${stdout}\\n${stderr}`;\n const deployed = parseDeployOutput(output);\n if (deployed === undefined) {\n const outputTail = stripVTControlCharacters(output)\n .split(/\\r?\\n|\\r/)\n .filter((line) => line.trim() !== \"\")\n .slice(-OUTPUT_TAIL_LINES)\n .join(\"\\n\");\n return yield* new DeployOutputError({ outputTail });\n }\n return { ...deployed, hash: yield* hashOf(news) };\n }),\n\n // The deployment belongs to the project; its functions stay deployed.\n delete: () => Effect.void,\n };\n }),\n );\n","// Convex.DeployKey: a deploy key for one deployment, such as the key\n// `npx convex deploy` reads from CONVEX_DEPLOY_KEY.\n//\n// Convex returns the secret only once, from create_deploy_key, and the\n// response has no id. The listing shows each key with a numeric id and a\n// name: the requested name, or, when a listed key already has that name, the\n// requested name plus a suffix such as \" (870993b4-ffe6-4911-adbd-e29f7fd712f2)\".\n//\n// One resource must never revoke the key of another resource:\n// - The requested name is the `name` prop plus a hash of the resource's fqn\n// and instance id, so two resources with the same `name` request\n// different names.\n// - After the create, exactly one listed key must have the requested name.\n// Otherwise the create fails with a typed error that lists the keys. State\n// keeps that key's numeric id.\n// - Delete sends the secret as `id`, so it can revoke only the key that this\n// resource created. It never deletes by a listed name.\n// - With an OAuth token as the Management API credential, Convex returns\n// that token as the \"new\" key. The provider never stores, revokes, or\n// deletes such a key: the create fails with DeployKeyIsCredential.\n// - Delete runs only for a key with a `keyId` in state. A 0.1.x key, which\n// has none, stays in Convex with a warning.\n// A key cannot change after creation, so every property change is a replacement.\nimport { createHash } from \"node:crypto\";\nimport { Resource } from \"alchemy\";\nimport { havePropsChanged, isResolved } from \"alchemy/Diff\";\nimport * as Provider from \"alchemy/Provider\";\nimport * as Effect from \"effect/Effect\";\nimport * as Redacted from \"effect/Redacted\";\nimport * as Schema from \"effect/Schema\";\nimport type { components } from \"@convex-dev/platform/managementApi\";\nimport {\n absentAsUndefined,\n type ConvexApiError,\n ManagementApi,\n retryIdempotentWrite,\n} from \"./ManagementApi.ts\";\nimport type { Providers } from \"./Providers.ts\";\n\n/** Actions a deploy key can be limited to, from the generated schema. */\nexport type DeployKeyAction = NonNullable<\n components[\"schemas\"][\"PlatformCreateDeployKeyArgs\"][\"allowedActions\"]\n>[number];\n\n/**\n * True when `listedName` is how Convex lists a key created as `name`: the\n * requested name itself, or the requested name, a space, and one\n * parenthesized suffix without spaces. The suffix check keeps `my-key` from\n * matching a key created as `my-key (old)`.\n */\nconst isListedAs = (listedName: string, name: string) => {\n const prefix = `${name} (`;\n return (\n listedName === name ||\n (listedName.startsWith(prefix) && /^[^\\s()]+\\)$/.test(listedName.slice(prefix.length)))\n );\n};\n\n/**\n * True when the listing still has the key in state: by numeric id, or by\n * listed name for state written by 0.1.x, which has no id.\n */\nexport const isKeyListed = (\n entries: ReadonlyArray<typeof ListedKey.Type>,\n key: { readonly keyId: number | undefined; readonly uniqueName: string },\n): boolean =>\n entries.some((entry) =>\n key.keyId === undefined ? entry.name === key.uniqueName : entry.id === key.keyId,\n );\n\n/** The listed keys that Convex shows for a key created as `name`. */\nexport const keysListedAs = (\n entries: ReadonlyArray<typeof ListedKey.Type>,\n name: string,\n): ReadonlyArray<typeof ListedKey.Type> => entries.filter((entry) => isListedAs(entry.name, name));\n\n/**\n * The name that a resource instance requests: `name`, a dash, and the first\n * 12 hex characters of sha256(\"<fqn>:<instanceId>\"). Two resources, or the\n * old and the new generation of one replaced resource, never request the\n * same name. Pattern from Confect `packages/alchemy/src/internal/ResourceIdentity.ts`\n * (https://github.com/rjdellecese/confect, ISC license).\n */\nexport const requestedKeyName = (name: string, fqn: string, instanceId: string): string =>\n `${name}-${createHash(\"sha256\").update(`${fqn}:${instanceId}`).digest(\"hex\").slice(0, 12)}`;\n\n/** One row of list_deploy_keys and list_preview_deploy_keys. Only the fields the providers use. */\nexport const ListedKey = Schema.Struct({\n id: Schema.Number,\n name: Schema.String,\n});\n\n/** GET /deployments/{deployment_name}/list_deploy_keys */\nexport const DeployKeyList = Schema.Array(ListedKey);\n\n/** POST /deployments/{deployment_name}/create_deploy_key */\nexport const CreatedDeployKey = Schema.Struct({ deployKey: Schema.String });\n\n/**\n * A key cannot be tied to one resource: before the create, listed keys\n * already have the requested name, or after the create, not exactly one has it.\n */\nexport class DeployKeyRecoveryRequired extends Schema.TaggedError<DeployKeyRecoveryRequired>()(\n \"DeployKeyRecoveryRequired\",\n {\n /** Where the keys live, such as `deployment happy-otter-123` or `project 3145389`. */\n target: Schema.String,\n /** The requested name. */\n name: Schema.String,\n /** The listed keys with the requested name. */\n keys: Schema.Array(ListedKey),\n /** True when this run created a key before the listing check failed. */\n afterCreate: Schema.Boolean,\n },\n) {\n override get message() {\n const listed =\n this.keys.length === 0\n ? \"no key\"\n : this.keys.map((key) => `\"${key.name}\" (id ${key.id})`).join(\", \");\n return this.afterCreate\n ? `Convex created deploy key \"${this.name}\" on ${this.target}, but the listing shows ${listed} with that name, not exactly one, so this resource cannot track the new key. Alchemy does not revoke it. Delete the keys with that name in the Convex dashboard, then run the deploy again.`\n : `${this.target} already lists ${listed} with the name \"${this.name}\", but Alchemy state has no such key. An interrupted run probably created it, and its secret is lost. Delete these keys in the Convex dashboard, then run the deploy again.`;\n }\n}\n\n/**\n * Convex returned the Management API credential itself as the new key. This\n * happens with an OAuth token: Convex creates no key. A delete of the\n * returned secret would revoke the credential, so the resource does not keep it.\n */\nexport class DeployKeyIsCredential extends Schema.TaggedError<DeployKeyIsCredential>()(\n \"DeployKeyIsCredential\",\n { target: Schema.String, name: Schema.String },\n) {\n override get message() {\n return `Convex returned the Management API credential itself as deploy key \"${this.name}\" on ${this.target}. With an OAuth token, Convex creates no new key, and deleting the returned key would revoke the OAuth token. Alchemy does not keep it. Use a team access token in CONVEX_ACCESS_TOKEN, or the Convex CLI login.`;\n }\n}\n\n/**\n * Creates a key named `name` and ties it to the caller. Fails before the\n * create when keys with that name already exist. After the create, fails\n * when the secret is the Management API credential itself, or when the\n * listing does not show exactly one key with that name. It never revokes a\n * key: the returned secret can be a shared credential.\n */\nexport const createTrackedKey = (options: {\n readonly target: string;\n readonly name: string;\n readonly list: Effect.Effect<ReadonlyArray<typeof ListedKey.Type>, ConvexApiError>;\n readonly create: Effect.Effect<string, ConvexApiError>;\n readonly isCredentialToken: (secret: string) => Effect.Effect<boolean>;\n}) =>\n Effect.gen(function* () {\n const { target, name } = options;\n const existing = keysListedAs(yield* options.list, name);\n if (existing.length > 0) {\n return yield* new DeployKeyRecoveryRequired({\n target,\n name,\n keys: existing,\n afterCreate: false,\n });\n }\n const secret = yield* options.create;\n if (yield* options.isCredentialToken(secret)) {\n return yield* new DeployKeyIsCredential({ target, name });\n }\n const listed = keysListedAs(yield* options.list, name);\n const [only] = listed;\n if (only === undefined || listed.length !== 1) {\n return yield* new DeployKeyRecoveryRequired({\n target,\n name,\n keys: listed,\n afterCreate: true,\n });\n }\n return { keyId: only.id, uniqueName: only.name, secret: Redacted.make(secret) };\n });\n\n/**\n * The secret to send to a delete call, or undefined when a delete is not\n * safe. Only a key with a `keyId` in state is known to be a new key that\n * this resource owns: createTrackedKey found exactly one new listed key, and\n * the secret was not the credential. State from 0.1.x has no `keyId`, and its\n * secret can be an OAuth token that Convex returned as a key, so a delete\n * could revoke that OAuth token. Such a key stays, and a warning names it.\n */\nexport const secretToDelete = (\n key: {\n readonly keyId: number | undefined;\n readonly secret: Redacted.Redacted<string> | undefined;\n },\n label: string,\n) =>\n Effect.gen(function* () {\n if (key.keyId === undefined || !Redacted.isRedacted(key.secret)) {\n yield* Effect.logWarning(\n `${label} has no verified key id and secret in state (state from 0.1.x, for example), so Alchemy does not delete it. Delete it in the Convex dashboard.`,\n );\n return undefined;\n }\n return Redacted.value(key.secret);\n });\n\nexport interface DeployKeyProps {\n /** Deployment name, such as `project.prodDeploymentName`. */\n readonly deployment: string;\n /**\n * Key name. The provider requests this name, a dash, and a hash of the\n * resource identity, so two resources with one `name` get different keys.\n */\n readonly name: string;\n /** Limits the key to these actions. Convex grants every deployment action when omitted. */\n readonly allowedActions?: readonly DeployKeyAction[];\n}\n\nexport interface DeployKeyAttributes {\n /** The name Convex lists the key under, such as \"ci-3f2a1b0c9d8e\". */\n readonly uniqueName: string;\n /** Numeric id of the key in the listing. Undefined in state written by 0.1.x. */\n readonly keyId: number | undefined;\n readonly deployment: string;\n /** The secret, such as `prod:<deployment>|<token>`. Convex returns it only at creation. */\n readonly deployKey: Redacted.Redacted<string>;\n}\n\nexport type DeployKey = Resource<\n \"Convex.DeployKey\",\n DeployKeyProps,\n DeployKeyAttributes,\n never,\n Providers\n>;\nexport const DeployKey = Resource<DeployKey>(\"Convex.DeployKey\");\n\nexport const DeployKeyProvider = () =>\n Provider.effect(\n DeployKey,\n Effect.gen(function* () {\n const api = yield* ManagementApi;\n\n const listKeys = (deployment: string) =>\n api\n .request(\"list deploy keys\", (client) =>\n client.GET(\"/deployments/{deployment_name}/list_deploy_keys\", {\n params: { path: { deployment_name: deployment } },\n }),\n )\n .pipe(Effect.map((body) => Schema.decodeUnknownSync(DeployKeyList)(body)));\n\n /**\n * `id` is the secret: Convex accepts it, and it matches only the key\n * that this resource created. A second delete answers 404.\n */\n const deleteKey = (deployment: string, secret: string) =>\n retryIdempotentWrite(\n api.requestVoid(\"delete deploy key\", (client) =>\n client.POST(\"/deployments/{deployment_name}/delete_deploy_key\", {\n params: { path: { deployment_name: deployment } },\n body: { id: secret },\n }),\n ),\n );\n\n return {\n stables: [\"uniqueName\", \"deployment\"],\n\n diff: ({ olds, news, output }) => {\n if (!isResolved(news)) return Effect.succeed(undefined);\n // State written without the secret cannot hand a key to dependents,\n // and Convex cannot return it again, so only a new key helps.\n return Effect.succeed(\n (output !== undefined && output.deployKey === undefined) || havePropsChanged(olds, news)\n ? ({ action: \"replace\" } as const)\n : ({ action: \"noop\" } as const),\n );\n },\n\n read: Effect.fn(function* ({ output }) {\n // A key that is not in state has no known secret, so it is never adopted.\n if (output === undefined) return undefined;\n const entries = yield* absentAsUndefined(listKeys(output.deployment));\n return entries !== undefined && isKeyListed(entries, output) ? output : undefined;\n }),\n\n reconcile: Effect.fn(function* ({ fqn, instanceId, news, output }) {\n if (output !== undefined) {\n // The secret cannot be read back. A key that still exists keeps the one in state.\n if (isKeyListed(yield* listKeys(news.deployment), output)) return output;\n }\n const name = requestedKeyName(news.name, fqn, instanceId);\n const { keyId, uniqueName, secret } = yield* createTrackedKey({\n target: `deployment ${news.deployment}`,\n name,\n list: listKeys(news.deployment),\n create: api\n .request(\"create deploy key\", (client) =>\n client.POST(\"/deployments/{deployment_name}/create_deploy_key\", {\n params: { path: { deployment_name: news.deployment } },\n body: {\n name,\n ...(news.allowedActions === undefined\n ? {}\n : { allowedActions: [...news.allowedActions] }),\n },\n }),\n )\n .pipe(\n Effect.map((body) => Schema.decodeUnknownSync(CreatedDeployKey)(body).deployKey),\n ),\n isCredentialToken: api.isCredentialToken,\n });\n return { uniqueName, keyId, deployment: news.deployment, deployKey: secret };\n }),\n\n delete: Effect.fn(function* ({ output }) {\n // Only the secret names this key alone: 0.1.x state can hold the\n // listed name of another resource's key.\n const secret = yield* secretToDelete(\n { keyId: output.keyId, secret: output.deployKey },\n `Convex.DeployKey \"${output.uniqueName}\" on ${output.deployment}`,\n );\n if (secret === undefined) return;\n // 404 DeployKeyNotFound: an earlier attempt or someone else deleted it.\n yield* absentAsUndefined(deleteKey(output.deployment, secret));\n }),\n };\n }),\n );\n","// Convex.PreviewDeployKey: a project key that creates and deploys preview\n// deployments, such as the key CI passes to `npx convex deploy` for a branch.\n//\n// Convex returns the secret only once, from create_preview_deploy_key. The\n// identity rules are the same as for Convex.DeployKey: a requested name that\n// is unique for each resource instance, exactly one listed key after the\n// create with its numeric id in state, delete by the secret only, and never\n// keep a key that is the OAuth credential itself. A key cannot change after\n// creation, so every property change is a replacement.\nimport { Resource } from \"alchemy\";\nimport { havePropsChanged, isResolved } from \"alchemy/Diff\";\nimport * as Provider from \"alchemy/Provider\";\nimport * as Effect from \"effect/Effect\";\nimport * as Redacted from \"effect/Redacted\";\nimport * as Schema from \"effect/Schema\";\nimport {\n createTrackedKey,\n isKeyListed,\n ListedKey,\n requestedKeyName,\n secretToDelete,\n} from \"./DeployKey.ts\";\nimport { absentAsUndefined, ManagementApi, retryIdempotentWrite } from \"./ManagementApi.ts\";\nimport type { Providers } from \"./Providers.ts\";\n\n/** GET /projects/{project_id}/list_preview_deploy_keys */\nexport const PreviewDeployKeyList = Schema.Struct({ items: Schema.Array(ListedKey) });\n\n/** POST /projects/{project_id}/create_preview_deploy_key */\nexport const CreatedPreviewDeployKey = Schema.Struct({ previewDeployKey: Schema.String });\n\nexport interface PreviewDeployKeyProps {\n /** Numeric project id, such as `project.projectId`. */\n readonly projectId: number;\n /**\n * Key name. The provider requests this name, a dash, and a hash of the\n * resource identity, so two resources with one `name` get different keys.\n */\n readonly name: string;\n}\n\nexport interface PreviewDeployKeyAttributes {\n /** The name Convex lists the key under, such as \"ci-3f2a1b0c9d8e\". */\n readonly uniqueName: string;\n /** Numeric id of the key in the listing. Undefined in state written by 0.1.x. */\n readonly keyId: number | undefined;\n readonly projectId: number;\n /** The secret, such as `preview:<team>:<project>|<token>`. Convex returns it only at creation. */\n readonly previewDeployKey: Redacted.Redacted<string>;\n}\n\nexport type PreviewDeployKey = Resource<\n \"Convex.PreviewDeployKey\",\n PreviewDeployKeyProps,\n PreviewDeployKeyAttributes,\n never,\n Providers\n>;\nexport const PreviewDeployKey = Resource<PreviewDeployKey>(\"Convex.PreviewDeployKey\");\n\nexport const PreviewDeployKeyProvider = () =>\n Provider.effect(\n PreviewDeployKey,\n Effect.gen(function* () {\n const api = yield* ManagementApi;\n\n const listKeys = (projectId: number) =>\n api\n .request(\"list preview deploy keys\", (client) =>\n client.GET(\"/projects/{project_id}/list_preview_deploy_keys\", {\n params: { path: { project_id: projectId } },\n }),\n )\n .pipe(Effect.map((body) => Schema.decodeUnknownSync(PreviewDeployKeyList)(body).items));\n\n /**\n * `id` is the secret: Convex accepts it, and it matches only the key\n * that this resource created. A second delete answers 404.\n */\n const deleteKey = (projectId: number, secret: string) =>\n retryIdempotentWrite(\n api.requestVoid(\"delete preview deploy key\", (client) =>\n client.POST(\"/projects/{project_id}/delete_preview_deploy_key\", {\n params: { path: { project_id: projectId } },\n body: { id: secret },\n }),\n ),\n );\n\n return {\n stables: [\"uniqueName\", \"projectId\"],\n\n diff: ({ olds, news, output }) => {\n if (!isResolved(news)) return Effect.succeed(undefined);\n // State written without the secret cannot hand a key to dependents,\n // and Convex cannot return it again, so only a new key helps.\n return Effect.succeed(\n (output !== undefined && output.previewDeployKey === undefined) ||\n havePropsChanged(olds, news)\n ? ({ action: \"replace\" } as const)\n : ({ action: \"noop\" } as const),\n );\n },\n\n read: Effect.fn(function* ({ output }) {\n // A key that is not in state has no known secret, so it is never adopted.\n if (output === undefined) return undefined;\n const entries = yield* absentAsUndefined(listKeys(output.projectId));\n return entries !== undefined && isKeyListed(entries, output) ? output : undefined;\n }),\n\n reconcile: Effect.fn(function* ({ fqn, instanceId, news, output }) {\n if (output !== undefined) {\n // The secret cannot be read back. A key that still exists keeps the one in state.\n if (isKeyListed(yield* listKeys(news.projectId), output)) return output;\n }\n const name = requestedKeyName(news.name, fqn, instanceId);\n const { keyId, uniqueName, secret } = yield* createTrackedKey({\n target: `project ${news.projectId}`,\n name,\n list: listKeys(news.projectId),\n create: api\n .request(\"create preview deploy key\", (client) =>\n client.POST(\"/projects/{project_id}/create_preview_deploy_key\", {\n params: { path: { project_id: news.projectId } },\n body: { name },\n }),\n )\n .pipe(\n Effect.map(\n (body) =>\n Schema.decodeUnknownSync(CreatedPreviewDeployKey)(body).previewDeployKey,\n ),\n ),\n isCredentialToken: api.isCredentialToken,\n });\n return { uniqueName, keyId, projectId: news.projectId, previewDeployKey: secret };\n }),\n\n delete: Effect.fn(function* ({ output }) {\n // Only the secret names this key alone: 0.1.x state can hold the\n // listed name of another resource's key.\n const secret = yield* secretToDelete(\n { keyId: output.keyId, secret: output.previewDeployKey },\n `Convex.PreviewDeployKey \"${output.uniqueName}\" in project ${output.projectId}`,\n );\n if (secret === undefined) return;\n // 404 PreviewDeployKeyNotFound: an earlier attempt or someone else deleted it.\n yield* absentAsUndefined(deleteKey(output.projectId, secret));\n }),\n };\n }),\n );\n","// Convex.Project: a project in a Convex team, with its default production deployment.\n//\n// Deleting a project deletes all of its deployments and data, so this\n// resource never replaces a project and keeps it by default:\n//\n// - A name change is an update in place: PATCH /projects/{project_id} with\n// the new name. The slug stays the same.\n// - `team`, `deploymentType`, and `deploymentRegion` cannot change on an\n// existing project. A change fails with a typed error that names the fix.\n// - The removal policy is `retain`. Alchemy deletes the project only when the\n// resource has `RemovalPolicy.destroy()`.\n// - With state, every call addresses the project by `output.projectId`. Only\n// a resource without state looks a project up by name, and it takes over a\n// project that it finds only when adoption is on.\nimport { Resource } from \"alchemy\";\nimport { OwnedBySomeoneElse, Unowned } from \"alchemy/AdoptPolicy\";\nimport { isResolved } from \"alchemy/Diff\";\nimport * as Provider from \"alchemy/Provider\";\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\nimport type { components } from \"@convex-dev/platform/managementApi\";\nimport { shouldAdopt } from \"./Adoption.ts\";\nimport { absentAsUndefined, ConvexApiError, ManagementApi } from \"./ManagementApi.ts\";\nimport type { Providers } from \"./Providers.ts\";\n\n/** Region names the Management API accepts, from the generated schema. */\nexport type DeploymentRegion = components[\"schemas\"][\"RegionName\"];\n\nexport interface ProjectProps {\n /** Team slug or numeric team id. A slug costs one extra lookup per run. Cannot change. */\n readonly team: string | number;\n /** Display name. Convex derives the slug from it at creation. A change renames the project. */\n readonly name: string;\n /** Region of the deployment created with the project. Defaults to the team default. Cannot change. */\n readonly deploymentRegion?: DeploymentRegion;\n /** Deployment created with the project. Defaults to `prod`. Cannot change. */\n readonly deploymentType?: \"prod\" | \"dev\";\n}\n\nexport interface ProjectAttributes {\n readonly projectId: number;\n readonly slug: string;\n readonly name: string;\n readonly teamId: number;\n readonly prodDeploymentName: string | undefined;\n readonly prodDeploymentUrl: string | undefined;\n}\n\nexport type Project = Resource<\"Convex.Project\", ProjectProps, ProjectAttributes, never, Providers>;\nexport const Project = Resource<Project>(\"Convex.Project\", { defaultRemovalPolicy: \"retain\" });\n\n/** The props change the team of an existing project. Convex cannot move a project. */\nexport class ProjectTeamChange extends Schema.TaggedError<ProjectTeamChange>()(\n \"ProjectTeamChange\",\n { projectId: Schema.Number, teamId: Schema.Number, requestedTeamId: Schema.Number },\n) {\n override get message() {\n 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.`;\n }\n}\n\n/**\n * The props change `deploymentType` or `deploymentRegion` of an existing\n * project. Convex uses them only when it creates the project.\n */\nexport class ProjectDeploymentChange extends Schema.TaggedError<ProjectDeploymentChange>()(\n \"ProjectDeploymentChange\",\n {\n projectId: Schema.Number,\n field: Schema.Literals([\"deploymentType\", \"deploymentRegion\"]),\n current: Schema.UndefinedOr(Schema.String),\n requested: Schema.UndefinedOr(Schema.String),\n },\n) {\n override get message() {\n const show = (value: string | undefined) => (value === undefined ? \"(not set)\" : value);\n return `Convex.Project cannot change ${this.field} of project ${this.projectId} from ${show(this.current)} to ${show(this.requested)}: Convex uses it only when it creates a project. Set ${this.field} back to ${show(this.current)}. For a new deployment, create a new resource with a new logical id and move the data yourself.`;\n }\n}\n\n/** More than one project in the team has the name, so the lookup cannot choose one. */\nexport class AmbiguousProject extends Schema.TaggedError<AmbiguousProject>()(\"AmbiguousProject\", {\n teamId: Schema.Number,\n name: Schema.String,\n projectIds: Schema.Array(Schema.Number),\n}) {\n override get message() {\n return `Team ${this.teamId} has ${this.projectIds.length} projects named \"${this.name}\" (ids ${this.projectIds.join(\", \")}). Convex.Project does not choose one. Rename the projects in the Convex dashboard so that only one has this name, or use a different name.`;\n }\n}\n\n/** Convex answered a create call but then did not return the project. */\nexport class CreatedProjectNotFound extends Schema.TaggedError<CreatedProjectNotFound>()(\n \"CreatedProjectNotFound\",\n { projectId: Schema.Number },\n) {\n override get message() {\n return `Convex created project ${this.projectId} but GET /projects/${this.projectId} did not return it. Run the deploy again with --adopt to take over project ${this.projectId}.`;\n }\n}\n\n/** Fields of GET /projects/{id}, PATCH /projects/{id}, and GET /teams/{team}/projects rows that the provider uses. */\nconst ProjectDetails = Schema.Struct({\n id: Schema.Number,\n name: Schema.String,\n slug: Schema.String,\n teamId: Schema.Number,\n prodDeploymentName: Schema.NullOr(Schema.String),\n});\ntype ProjectDetails = typeof ProjectDetails.Type;\n\n/** GET /teams/{team_id}/projects: one page of projects. */\nexport const ProjectPage = Schema.Struct({\n items: Schema.Array(ProjectDetails),\n pagination: Schema.Struct({\n hasMore: Schema.Boolean,\n nextCursor: Schema.optionalKey(Schema.NullOr(Schema.String)),\n }),\n});\n\n/** POST /teams/{team_id}/create_project. Only the id: everything else comes from GET /projects/{id}. */\nconst CreatedProject = Schema.Struct({ projectId: Schema.Number });\n\nconst DeploymentRow = Schema.Struct({\n name: Schema.String,\n deploymentType: Schema.String,\n isDefault: Schema.Boolean,\n deploymentUrl: Schema.String,\n});\nconst DeploymentRows = Schema.Array(DeploymentRow);\n\n/**\n * Fails with {@link ProjectDeploymentChange} when a create-time prop\n * changed. An omitted `deploymentType` means `prod`.\n */\nconst checkCreateTimeProps = (projectId: number, olds: ProjectProps, news: ProjectProps) => {\n const oldType = olds.deploymentType ?? \"prod\";\n const newType = news.deploymentType ?? \"prod\";\n if (oldType !== newType) {\n return Effect.fail(\n new ProjectDeploymentChange({\n projectId,\n field: \"deploymentType\",\n current: oldType,\n requested: newType,\n }),\n );\n }\n if (olds.deploymentRegion !== news.deploymentRegion) {\n return Effect.fail(\n new ProjectDeploymentChange({\n projectId,\n field: \"deploymentRegion\",\n current: olds.deploymentRegion,\n requested: news.deploymentRegion,\n }),\n );\n }\n return Effect.void;\n};\n\nexport const ProjectProvider = () =>\n Provider.effect(\n Project,\n Effect.gen(function* () {\n const api = yield* ManagementApi;\n\n const prodDeployment = (projectId: number) =>\n api\n .request(\"list deployments\", (client) =>\n client.GET(\"/projects/{project_id}/list_deployments\", {\n params: { path: { project_id: projectId } },\n }),\n )\n .pipe(\n Effect.map((rows) =>\n Schema.decodeUnknownSync(DeploymentRows)(rows).find(\n (row) => row.deploymentType === \"prod\" && row.isDefault,\n ),\n ),\n );\n\n const attributesOf = (details: ProjectDetails) =>\n prodDeployment(details.id).pipe(\n Effect.map((prod): ProjectAttributes => ({\n projectId: details.id,\n slug: details.slug,\n name: details.name,\n teamId: details.teamId,\n prodDeploymentName: prod?.name ?? details.prodDeploymentName ?? undefined,\n prodDeploymentUrl: prod?.deploymentUrl,\n })),\n );\n\n const getById = (projectId: number) =>\n absentAsUndefined(\n api.request(\"get project\", (client) =>\n client.GET(\"/projects/{project_id}\", { params: { path: { project_id: projectId } } }),\n ),\n ).pipe(\n Effect.map((body) =>\n body === undefined ? undefined : Schema.decodeUnknownSync(ProjectDetails)(body),\n ),\n );\n\n /**\n * The one project in the team with exactly this name, or undefined.\n * Reads every page. `q` narrows the listing on the server, and the\n * exact match here decides. Pattern from Confect\n * `packages/alchemy/src/ConvexClient.ts` and `Project.ts`\n * (https://github.com/rjdellecese/confect, ISC license).\n */\n const findByName = (teamId: number, name: string) =>\n Effect.gen(function* () {\n const matches: ProjectDetails[] = [];\n const seenCursors = new Set<string>();\n let cursor: string | undefined;\n while (true) {\n const body = yield* api.request(\"list projects\", (client) =>\n client.GET(\"/teams/{team_id}/projects\", {\n params: {\n path: { team_id: teamId },\n query: { q: name, ...(cursor === undefined ? {} : { cursor }) },\n },\n }),\n );\n const page = Schema.decodeUnknownSync(ProjectPage)(body);\n matches.push(...page.items.filter((row) => row.name === name));\n if (!page.pagination.hasMore) break;\n const next = page.pagination.nextCursor;\n if (next === undefined || next === null || seenCursors.has(next)) {\n return yield* new ConvexApiError({\n operation: \"list projects\",\n status: 0,\n code: \"InvalidPagination\",\n message: `The project listing of team ${teamId} reports more pages but gives no new cursor. Convex.Project stops so that it does not miss a project named \"${name}\".`,\n });\n }\n seenCursors.add(next);\n cursor = next;\n }\n if (matches.length > 1) {\n return yield* new AmbiguousProject({\n teamId,\n name,\n projectIds: matches.map((row) => row.id),\n });\n }\n return matches[0];\n });\n\n /**\n * Brings an existing project to the desired name. The team cannot\n * change, so a project in another team fails.\n */\n const converge = (details: ProjectDetails, teamId: number, name: string) =>\n Effect.gen(function* () {\n if (details.teamId !== teamId) {\n return yield* new ProjectTeamChange({\n projectId: details.id,\n teamId: details.teamId,\n requestedTeamId: teamId,\n });\n }\n if (details.name === name) return yield* attributesOf(details);\n const updated = yield* api.request(\"update project\", (client) =>\n client.PATCH(\"/projects/{project_id}\", {\n params: { path: { project_id: details.id } },\n body: { name },\n }),\n );\n return yield* attributesOf(Schema.decodeUnknownSync(ProjectDetails)(updated));\n });\n\n return {\n stables: [\"projectId\", \"slug\", \"teamId\"],\n\n diff: Effect.fn(function* ({ olds, news, output }) {\n if (!isResolved(news)) return undefined;\n // Nothing exists yet, so nothing can be lost.\n if (output === undefined) return undefined;\n // A deploymentType or deploymentRegion change fails in reconcile,\n // before any API call. Reconcile compares with the props of the last\n // successful deploy. Here, `olds` can be the props of a refused\n // update, so a check here would also refuse the props that fix it.\n if (olds.team !== news.team) {\n const requestedTeamId = yield* api.resolveTeamId(news.team);\n if (requestedTeamId !== output.teamId) {\n return yield* new ProjectTeamChange({\n projectId: output.projectId,\n teamId: output.teamId,\n requestedTeamId,\n });\n }\n }\n // Never \"replace\". A name change is an update; reconcile renames\n // the project with PATCH. Otherwise the engine compares the props.\n return olds.name === news.name ? undefined : ({ action: \"update\" } as const);\n }),\n\n read: Effect.fn(function* ({ olds, output }) {\n if (output !== undefined) {\n const details = yield* getById(output.projectId);\n return details === undefined ? undefined : yield* attributesOf(details);\n }\n // No state: an existing project with this name belongs to someone\n // else until the user passes --adopt. Convex has no ownership tags.\n const existing = yield* findByName(yield* api.resolveTeamId(olds.team), olds.name);\n return existing === undefined ? undefined : Unowned(yield* attributesOf(existing));\n }),\n\n reconcile: Effect.fn(function* ({ fqn, olds, news, output }) {\n const teamId = yield* api.resolveTeamId(news.team);\n if (output !== undefined) {\n // State names the project. Never look it up by name here.\n if (olds !== undefined) yield* checkCreateTimeProps(output.projectId, olds, news);\n if (output.teamId !== teamId) {\n return yield* new ProjectTeamChange({\n projectId: output.projectId,\n teamId: output.teamId,\n requestedTeamId: teamId,\n });\n }\n const details = yield* getById(output.projectId);\n if (details !== undefined) return yield* converge(details, teamId, news.name);\n // The project in state was deleted outside Alchemy. Creating a\n // new project below loses nothing.\n } else {\n // No state: a project with this name is someone else's unless\n // adoption is on. The engine checked this in plan; this check\n // covers a project that appeared after the plan.\n const existing = yield* findByName(teamId, news.name);\n if (existing !== undefined) {\n if (!(yield* shouldAdopt(fqn))) {\n return yield* new OwnedBySomeoneElse({\n message: `Convex project ${existing.id} in team ${teamId} already has the name \"${news.name}\". Re-run with --adopt to take it over, or use a different name.`,\n resourceType: Project.Type,\n physicalName: news.name,\n });\n }\n return yield* converge(existing, teamId, news.name);\n }\n }\n const created = yield* api.request(\"create project\", (client) =>\n client.POST(\"/teams/{team_id}/create_project\", {\n params: { path: { team_id: teamId } },\n body: {\n projectName: news.name,\n deploymentType: news.deploymentType ?? \"prod\",\n ...(news.deploymentRegion === undefined\n ? {}\n : { deploymentRegion: news.deploymentRegion }),\n },\n }),\n );\n // Identity comes from the create response, never from a name lookup.\n const { projectId } = Schema.decodeUnknownSync(CreatedProject)(created);\n const details = yield* getById(projectId);\n if (details === undefined) return yield* new CreatedProjectNotFound({ projectId });\n return yield* converge(details, teamId, news.name);\n }),\n\n // Runs only with `RemovalPolicy.destroy()`: the default policy is `retain`.\n delete: Effect.fn(function* ({ output }) {\n yield* absentAsUndefined(\n api.requestVoid(\"delete project\", (client) =>\n client.POST(\"/projects/{project_id}/delete\", {\n params: { path: { project_id: output.projectId } },\n }),\n ),\n );\n }),\n };\n }),\n );\n","// The provider collection users merge into a stack:\n// providers: Layer.mergeAll(Cloudflare.providers(), Convex.providers())\nimport { CommandExecutorLive } from \"alchemy/Command\";\nimport * as Provider from \"alchemy/Provider\";\nimport * as Layer from \"effect/Layer\";\nimport { type Credentials, fromConfig } from \"./Credentials.ts\";\nimport {\n DefaultEnvironmentVariable,\n DefaultEnvironmentVariableProvider,\n} from \"./DefaultEnvironmentVariable.ts\";\nimport { Deploy, DeployProvider } from \"./Deploy.ts\";\nimport { DeployKey, DeployKeyProvider } from \"./DeployKey.ts\";\nimport { EnvironmentVariable, EnvironmentVariableProvider } from \"./EnvironmentVariable.ts\";\nimport { ManagementApiLive } from \"./ManagementApi.ts\";\nimport { PreviewDeployKey, PreviewDeployKeyProvider } from \"./PreviewDeployKey.ts\";\nimport { Project, ProjectProvider } from \"./Project.ts\";\n\nexport class Providers extends Provider.ProviderCollection<Providers>()(\"Convex\") {}\n\n/**\n * All Convex resource providers. Pass a credentials layer to override the\n * default lookup (`CONVEX_ACCESS_TOKEN`, then the Convex CLI login).\n */\nexport const providers = (credentials: Layer.Layer<Credentials> = fromConfig()) =>\n Layer.effect(\n Providers,\n Provider.collection([\n Project,\n DeployKey,\n PreviewDeployKey,\n EnvironmentVariable,\n DefaultEnvironmentVariable,\n Deploy,\n ]),\n ).pipe(\n Layer.provide(\n Layer.mergeAll(\n ProjectProvider(),\n DeployKeyProvider(),\n PreviewDeployKeyProvider(),\n EnvironmentVariableProvider(),\n DefaultEnvironmentVariableProvider(),\n DeployProvider(),\n ),\n ),\n Layer.provide(ManagementApiLive()),\n Layer.provide(credentials),\n // Convex.Deploy runs the Convex CLI through Alchemy's command executor.\n Layer.provide(CommandExecutorLive()),\n Layer.orDie,\n );\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samebase/alchemy-convex",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Convex resources for Alchemy v2: projects, deploy keys, environment variables, and a deploy step",
5
5
  "keywords": [
6
6
  "alchemy",
@@ -39,22 +39,25 @@
39
39
  "check": "vp fmt --check && vp lint && pnpm run typecheck && vp test --run",
40
40
  "typecheck": "tsc --noEmit",
41
41
  "test": "vp test --run",
42
- "test:live": "ALCHEMY_CONVEX_LIVE=1 vp test --run test/live",
42
+ "test:live": "node ./scripts/test-live.ts",
43
43
  "format": "vp fmt",
44
44
  "lint": "vp lint",
45
- "build": "rm -rf lib && tsc -p tsconfig.build.json",
46
- "prepack": "pnpm run build"
45
+ "build": "vp pack",
46
+ "prepack": "pnpm run build",
47
+ "prepare": "vp config --no-agent"
47
48
  },
48
49
  "dependencies": {
49
50
  "@convex-dev/platform": "0.1.11"
50
51
  },
51
52
  "devDependencies": {
53
+ "@arethetypeswrong/core": "^0.18.5",
52
54
  "@effect/platform-node": "4.0.0",
53
55
  "@effect/vitest": "4.0.0",
54
56
  "@types/node": "^24.0.0",
55
57
  "alchemy": "2.0.0-beta.80",
56
58
  "convex": "^1.45.0",
57
59
  "effect": "4.0.0",
60
+ "publint": "^0.3.25",
58
61
  "typescript": "^5.9.0",
59
62
  "vite-plus": "0.3.0",
60
63
  "vitest": "^4.0.0"
@@ -0,0 +1,30 @@
1
+ // Adoption check for reconcile.
2
+ //
3
+ // The engine checks ownership during plan: `read` returns `Unowned(...)` for
4
+ // an object that is not in state, and the engine then needs `--adopt`. A
5
+ // reconcile without state can still find an existing object, for example
6
+ // after a replacement, after an interrupted create, or when another run
7
+ // created it between plan and apply. Reconcile takes that object over only
8
+ // when adoption is on. The order is the same as in the engine: the
9
+ // resource's own `adopt(...)`, then the AdoptPolicy service, then the
10
+ // `--adopt` flag in AlchemyContext.
11
+ //
12
+ // Pattern from Confect, `packages/alchemy/src/internal/Lifecycle.ts`
13
+ // (https://github.com/rjdellecese/confect, ISC license).
14
+ import { AdoptPolicy } from "alchemy/AdoptPolicy";
15
+ import { AlchemyContext } from "alchemy/AlchemyContext";
16
+ import { Stack } from "alchemy/Stack";
17
+ import * as Effect from "effect/Effect";
18
+ import * as Option from "effect/Option";
19
+
20
+ /** True when the resource `fqn` may take over an object that is not in state. */
21
+ export const shouldAdopt = (fqn: string): Effect.Effect<boolean> =>
22
+ Effect.gen(function* () {
23
+ const stack = yield* Effect.serviceOption(Stack);
24
+ const own = Option.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
25
+ if (own !== undefined) return own;
26
+ const policy = yield* Effect.serviceOption(AdoptPolicy);
27
+ if (Option.isSome(policy)) return policy.value;
28
+ const context = yield* Effect.serviceOption(AlchemyContext);
29
+ return Option.isSome(context) && context.value.adopt;
30
+ });
@@ -6,14 +6,31 @@
6
6
  // credential, not with a deploy key. Setting a default and removing it are the
7
7
  // same call: update_default_environment_variables with `value: null` removes
8
8
  // the default, and removing an absent default also answers 200.
9
+ //
10
+ // A default that exists but is not in state belongs to someone else: its old
11
+ // value is lost when this resource writes it. Alchemy takes it over only with
12
+ // --adopt.
13
+ //
14
+ // The default's project, name, and deployment type cannot change. A change
15
+ // fails with VariableIdentityChange: use a new resource with a new logical
16
+ // id. Read and delete use the identity in the attributes: after a refused
17
+ // change, the props name the refused one.
9
18
  import { Resource } from "alchemy";
19
+ import { OwnedBySomeoneElse, Unowned } from "alchemy/AdoptPolicy";
10
20
  import { isResolved } from "alchemy/Diff";
11
21
  import * as Provider from "alchemy/Provider";
12
22
  import * as Effect from "effect/Effect";
13
23
  import * as Redacted from "effect/Redacted";
14
24
  import * as Schema from "effect/Schema";
15
25
  import type { components } from "@convex-dev/platform/managementApi";
16
- import { absentAsUndefined, ManagementApi, type ManagementApiService } from "./ManagementApi.ts";
26
+ import { shouldAdopt } from "./Adoption.ts";
27
+ import { VariableIdentityChange } from "./EnvironmentVariable.ts";
28
+ import {
29
+ absentAsUndefined,
30
+ ManagementApi,
31
+ type ManagementApiService,
32
+ retryIdempotentWrite,
33
+ } from "./ManagementApi.ts";
17
34
  import type { Providers } from "./Providers.ts";
18
35
 
19
36
  /** Deployment types a default can target, from the generated schema. */
@@ -52,11 +69,8 @@ export const listDefaults = (
52
69
  )
53
70
  .pipe(Effect.map((body) => Schema.decodeUnknownSync(DefaultEnvironmentVariableList)(body)));
54
71
 
55
- /**
56
- * Sets one default, or removes it when `value` is null. Convex answers 200
57
- * with an empty body, so this reads no response data.
58
- */
59
- export const updateDefault = (
72
+ /** One attempt to set one default, or to remove it when `value` is null. */
73
+ const writeDefault = (
60
74
  api: ManagementApiService,
61
75
  projectId: number,
62
76
  name: string,
@@ -78,6 +92,34 @@ export const updateDefault = (
78
92
  }),
79
93
  );
80
94
 
95
+ /**
96
+ * Sets one default, or removes it when `value` is null. Convex answers 200
97
+ * with an empty body, so this reads no response data. The call sets a value,
98
+ * so it runs again after a write conflict or a 5xx answer, for example when
99
+ * another resource writes a default of the same project at the same time.
100
+ */
101
+ export const updateDefault = (
102
+ api: ManagementApiService,
103
+ projectId: number,
104
+ name: string,
105
+ deploymentType: DeploymentType,
106
+ value: string | Redacted.Redacted<string> | null,
107
+ ) => retryIdempotentWrite(writeDefault(api, projectId, name, deploymentType, value));
108
+
109
+ /** The default's identity, or undefined when the default or the project is absent. */
110
+ const readDefault = (
111
+ api: ManagementApiService,
112
+ projectId: number,
113
+ name: string,
114
+ deploymentType: DeploymentType,
115
+ ) =>
116
+ // 404 ProjectNotFound: the project no longer exists, so neither does the default.
117
+ absentAsUndefined(listDefaults(api, projectId, name, deploymentType)).pipe(
118
+ Effect.map((list) =>
119
+ list === undefined ? undefined : findDefault(list, projectId, name, deploymentType),
120
+ ),
121
+ );
122
+
81
123
  /**
82
124
  * GET /projects/{project_id}/list_default_environment_variables. Only the
83
125
  * fields the provider uses: the row's `value` is the secret in plain text.
@@ -114,6 +156,34 @@ export const DefaultEnvironmentVariable = Resource<DefaultEnvironmentVariable>(
114
156
  "Convex.DefaultEnvironmentVariable",
115
157
  );
116
158
 
159
+ /** Fails with VariableIdentityChange when the props name another default than the one in state. */
160
+ const checkIdentity = (
161
+ output: DefaultEnvironmentVariableAttributes,
162
+ news: DefaultEnvironmentVariableProps,
163
+ ) => {
164
+ const changed =
165
+ output.projectId !== news.projectId
166
+ ? ({
167
+ field: "projectId",
168
+ current: `${output.projectId}`,
169
+ requested: `${news.projectId}`,
170
+ } as const)
171
+ : output.name !== news.name
172
+ ? ({ field: "name", current: output.name, requested: news.name } as const)
173
+ : output.deploymentType !== news.deploymentType
174
+ ? ({
175
+ field: "deploymentType",
176
+ current: output.deploymentType,
177
+ requested: news.deploymentType,
178
+ } as const)
179
+ : undefined;
180
+ return changed === undefined
181
+ ? Effect.void
182
+ : Effect.fail(
183
+ new VariableIdentityChange({ resourceType: DefaultEnvironmentVariable.Type, ...changed }),
184
+ );
185
+ };
186
+
117
187
  export const DefaultEnvironmentVariableProvider = () =>
118
188
  Provider.effect(
119
189
  DefaultEnvironmentVariable,
@@ -121,32 +191,60 @@ export const DefaultEnvironmentVariableProvider = () =>
121
191
  const api = yield* ManagementApi;
122
192
 
123
193
  return {
124
- stables: ["projectId", "name", "deploymentType"],
125
-
126
- diff: ({ olds, news }) => {
127
- if (!isResolved(news)) return Effect.succeed(undefined);
128
- // Otherwise undefined: the engine updates when any prop changed and
129
- // compares a Redacted value by content.
130
- return Effect.succeed(
131
- olds.projectId !== news.projectId ||
132
- olds.name !== news.name ||
133
- olds.deploymentType !== news.deploymentType
134
- ? ({ action: "replace" } as const)
135
- : undefined,
136
- );
137
- },
194
+ // Compares with the attributes: after a refused change, `olds` are
195
+ // the refused props. Otherwise undefined: the engine updates when any
196
+ // prop changed and compares a Redacted value by content.
197
+ diff: Effect.fn(function* ({ news, output }) {
198
+ if (!isResolved(news) || output === undefined) return undefined;
199
+ yield* checkIdentity(output, news);
200
+ return undefined;
201
+ }),
138
202
 
139
- read: Effect.fn(function* ({ olds }) {
140
- // 404 ProjectNotFound: the project no longer exists, so neither does the default.
141
- const list = yield* absentAsUndefined(
142
- listDefaults(api, olds.projectId, olds.name, olds.deploymentType),
143
- );
144
- if (list === undefined) return undefined;
145
- return findDefault(list, olds.projectId, olds.name, olds.deploymentType);
203
+ read: Effect.fn(function* ({ olds, output }) {
204
+ const at = output ?? olds;
205
+ const found = yield* readDefault(api, at.projectId, at.name, at.deploymentType);
206
+ if (found === undefined) return undefined;
207
+ // No state: the default belongs to someone else until the user passes --adopt.
208
+ return output ?? Unowned(found);
146
209
  }),
147
210
 
148
- reconcile: Effect.fn(function* ({ news }) {
149
- yield* updateDefault(api, news.projectId, news.name, news.deploymentType, news.value);
211
+ reconcile: Effect.fn(function* ({ fqn, news, output }) {
212
+ // Also here: with an unresolved prop in plan, diff could not check.
213
+ if (output !== undefined) yield* checkIdentity(output, news);
214
+ const write = writeDefault(
215
+ api,
216
+ news.projectId,
217
+ news.name,
218
+ news.deploymentType,
219
+ news.value,
220
+ );
221
+ if (output !== undefined || (yield* shouldAdopt(fqn))) {
222
+ // In state, or adopted: the default is ours to set.
223
+ yield* retryIdempotentWrite(write);
224
+ } else {
225
+ // Not in state: overwriting a default that exists loses its
226
+ // value, so that needs adoption. Each attempt checks first, so a
227
+ // retry after a write conflict never overwrites a default that
228
+ // another writer created in the meantime.
229
+ yield* retryIdempotentWrite(
230
+ Effect.gen(function* () {
231
+ const existing = yield* readDefault(
232
+ api,
233
+ news.projectId,
234
+ news.name,
235
+ news.deploymentType,
236
+ );
237
+ if (existing !== undefined) {
238
+ return yield* new OwnedBySomeoneElse({
239
+ message: `Project ${news.projectId} already has a ${news.deploymentType} default environment variable ${news.name}. Re-run with --adopt to take it over and overwrite its value, or use a different name.`,
240
+ resourceType: DefaultEnvironmentVariable.Type,
241
+ physicalName: news.name,
242
+ });
243
+ }
244
+ yield* write;
245
+ }),
246
+ );
247
+ }
150
248
  return {
151
249
  projectId: news.projectId,
152
250
  name: news.name,