@samebase/alchemy-convex 0.27.2 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -18
- package/lib/{HubClient-Bs5_j18U.js → HubClient-BcegkjaP.js} +9 -6
- package/lib/HubClient-BcegkjaP.js.map +1 -0
- package/lib/cli.js +1 -1
- package/lib/index.d.ts +7 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +6 -2
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Deployment.ts +17 -7
- package/src/SharedHubClient.ts +5 -1
- package/src/cli.ts +0 -0
- package/lib/HubClient-Bs5_j18U.js.map +0 -1
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/Credentials.ts","../src/ManagementApi.ts","../src/Providers.ts","../src/Push.ts","../src/Deployment.ts","../src/Component.ts","../src/Project.ts","../src/HubClient.ts","../src/RunRecord.ts","../src/SharedHubClient.ts","../src/State.ts"],"mappings":";;;;;;;;;;;;;;;;;UA6BiB;;WAEN,aAAa,SAAS;;WAEtB;;UAGM;;WAEN,aAAa,OAAO,OAAO;;WAE3B,gBAAgB,OAAO;;WAEvB,WAAW,OAAO,OAAO,SAAS;;;qBAGhC,oBAAoB;;qBAKpB;;qBAGA;;;;qBAOA,yBAAyB;;;;;;;;;qBAiCzB,kBAAiB,MAAM,MAAM;;qBA0B7B,aAAc;WAChB,cAAc,SAAS;WACvB,YAAY,SAAS;MAC5B,MAAM,MAAM;;;KC3GJ,mBAAmB,kBAAkB;KACrC,mBAAmB,kBAAkB;;UAGhC,YAAY;WAClB,OAAO;WACP;WACA,UAAU;;;;;;;;;;;;;qBAQR,uBAAuB;UAuBnB;;WAEN,UAAU,GACjB,mBACA,MAAM,QAAQ,qBAAqB,QAAQ,YAAY,QACpD,OAAO,OAAO,GAAG;;WAEb,cACP,mBACA,MAAM,QAAQ,qBAAqB,QAAQ,0BACxC,OAAO,aAAa;;WAEhB,oBAAoB,GAC3B,mBACA,6BACA,WAAW,SAAS,kBACpB,MAAM,QAAQ,qBAAqB,QAAQ,YAAY,QACpD,OAAO,OAAO,GAAG;;;;;;;;WAQb,gBACP,sCACG,OAAO,eAAe;;;;;;;;WAQlB,oBAAoB,mBAAmB,OAAO;;;;;;;WAO9C,mBAAmB,OAAO;;;;;WAK1B,YAAY,OAAO,SAAS,qBAAqB;;;qBAM/C,sBAAsB;;;;qBC/FtB,kBAAkB;;;;;;qBAOlB,YAAa,cAAa,MAAM,MAAM,iBAA2B,MAAA,MAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/Credentials.ts","../src/ManagementApi.ts","../src/Providers.ts","../src/Push.ts","../src/Deployment.ts","../src/Component.ts","../src/Project.ts","../src/HubClient.ts","../src/RunRecord.ts","../src/SharedHubClient.ts","../src/State.ts"],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as PushTargetMismatch, B as DEPLOY_KEY_ENV, C as DeploymentProvider, D as ConvexAppNotFound, E as CodePushFailed, F as ManagementApi, H as fromValues, I as ManagementApiLive, L as ACCESS_TOKEN_ENV, N as parseDeployOutput, O as ConvexCliNotFound, P as ConvexApiError, R as Credentials, S as DeploymentIdentityChange, T as ProductionDeploymentNotFound, V as fromConfig, _ as ProjectProvider, b as AmbiguousDeployment, c as RUN_RECORD_KEY, d as withRunRecord, f as AmbiguousProject, g as ProjectNotFound, h as ProjectDeploymentChange, i as HubClientProvider, j as VariablesOwnedBySomeoneElse, k as EnvironmentNeedsDeployKey, l as RunRecordError, m as Project, n as HubClient, o as STORE_PROJECT_NAME, p as CreatedProjectNotFound, r as HubClientFailed, s as state, t as HUB_PROJECT_NAME, u as runRecord, v as ProjectTeamChange, w as DeploymentResource, x as Deployment, y as AbsoluteRootDir, z as CredentialsError } from "./HubClient-
|
|
1
|
+
import { A as PushTargetMismatch, B as DEPLOY_KEY_ENV, C as DeploymentProvider, D as ConvexAppNotFound, E as CodePushFailed, F as ManagementApi, H as fromValues, I as ManagementApiLive, L as ACCESS_TOKEN_ENV, N as parseDeployOutput, O as ConvexCliNotFound, P as ConvexApiError, R as Credentials, S as DeploymentIdentityChange, T as ProductionDeploymentNotFound, V as fromConfig, _ as ProjectProvider, b as AmbiguousDeployment, c as RUN_RECORD_KEY, d as withRunRecord, f as AmbiguousProject, g as ProjectNotFound, h as ProjectDeploymentChange, i as HubClientProvider, j as VariablesOwnedBySomeoneElse, k as EnvironmentNeedsDeployKey, l as RunRecordError, m as Project, n as HubClient, o as STORE_PROJECT_NAME, p as CreatedProjectNotFound, r as HubClientFailed, s as state, t as HUB_PROJECT_NAME, u as runRecord, v as ProjectTeamChange, w as DeploymentResource, x as Deployment, y as AbsoluteRootDir, z as CredentialsError } from "./HubClient-BcegkjaP.js";
|
|
2
2
|
import { toPath } from "alchemy/FQN";
|
|
3
3
|
import { Stack } from "alchemy/Stack";
|
|
4
4
|
import * as Effect from "effect/Effect";
|
|
@@ -97,7 +97,11 @@ const offAttributes = (deploymentType) => ({
|
|
|
97
97
|
repository: ""
|
|
98
98
|
});
|
|
99
99
|
/** One request gave no usable answer. The reason holds no secret. */
|
|
100
|
-
var SharedHubClientFailed = class extends Schema.TaggedError()("SharedHubClientFailed", { reason: Schema.String }) {
|
|
100
|
+
var SharedHubClientFailed = class extends Schema.TaggedError()("SharedHubClientFailed", { reason: Schema.String }) {
|
|
101
|
+
get message() {
|
|
102
|
+
return `Convex.SharedHubClient: ${this.reason}.`;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
101
105
|
const OidcToken = Schema.Struct({ value: Schema.NonEmptyString });
|
|
102
106
|
/** The 201 answer of `POST /hub-clients`. */
|
|
103
107
|
const NewClient = Schema.Struct({
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/Component.ts","../src/SharedHubClient.ts","../src/Providers.ts"],"sourcesContent":["import { toPath } from \"alchemy/FQN\";\nimport type { NamespaceNode } from \"alchemy/Namespace\";\nimport { Stack } from \"alchemy/Stack\";\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\nimport type { Deployment } from \"./Deployment.ts\";\n\n/** A component's resources occupy this namespace in the deployment's Alchemy stack and stage. */\nexport interface ComponentBinding {\n readonly type: \"component\";\n readonly path: string;\n readonly namespace: readonly string[];\n}\n\n/**\n * Associate a native namespace with an installed component. `component` is the\n * installed name from the app's `convex.config.ts`; a nested install joins the\n * names with slashes, such as `\"workspace/mail\"`. An undefined namespace\n * associates the stack root. This records display metadata; it does not install\n * the component, set its environment, or change removal policies.\n */\nexport const bindComponent = (options: {\n deployment: Deployment;\n component: string;\n namespace: NamespaceNode | undefined;\n}) =>\n Effect.gen(function* () {\n if (options.component === \"\") {\n return yield* Effect.die(new Error(\"The component name is empty.\"));\n }\n const namespace = toPath(options.namespace);\n const sid = `convex.component:${JSON.stringify(namespace)}`;\n const stack = yield* Stack;\n const previous = stack.bindings[options.deployment.FQN]?.find((binding) => binding.sid === sid);\n // Alchemy keeps the last binding with a given sid. Reject conflicting owners before that happens.\n if (previous !== undefined) {\n const existing = Schema.decodeUnknownSync(Schema.Struct({ path: Schema.String }))(\n previous.data,\n );\n if (existing.path !== options.component) {\n return yield* Effect.die(\n new Error(\n `Alchemy namespace ${JSON.stringify(namespace)} is already bound to ${existing.path}.`,\n ),\n );\n }\n return;\n }\n yield* options.deployment.bind(sid, {\n type: \"component\",\n path: options.component,\n namespace,\n });\n });\n","// Convex.SharedHubClient: one deployment of an app as a client of the central identity hub, for an\n// app that deploys in its own GitHub Actions run, on any host, and on a laptop as a stage of the same\n// stack.\n//\n// A run in GitHub Actions asks the hub for a new client with a GitHub OIDC token, so the stack needs\n// no key and no Samebase account. The token names the repository. The hub reads\n// `/samebase-verification.json` at the origin of the return address, which must name the same\n// repository, and registers the client on that address. The answer holds the client's own\n// registration access token (RFC 7591), and each later deploy reads the client on the hub with that\n// token (RFC 7592). The hub keeps only hashes, so the state is the only record of the secret and the\n// token.\n//\n// The answer to a production registration also holds the app's initial access token (RFC 7591\n// section 1.2) and the repository id. The state of stage `prod` keeps them. A laptop runs the stack\n// as a stage `dev-<name>`: it reads them with `SharedHubClient.read`, and a client with\n// `deploymentType: \"development\"` asks the hub with the token in place of the GitHub token (RFC 133).\n//\n// A push removes a variable that a deploy stops passing, so a failure here must not drop working\n// variables or stop the deploy. A renewal that fails keeps the saved values. Only a client that\n// cannot work asks for a new one: it is gone, or it has another return address or type. If that\n// request fails, the run gives the off values, so the app has guest sign-in and not a broken button.\n// The off values are the `no-hub` variables of a checkout without a hub: Convex needs the variables,\n// and the app hides the button for this client ID.\n//\n// No text in this file quotes an answer or a fetch error, so no log line can hold a secret.\nimport { Resource } from \"alchemy\";\nimport { isResolved } from \"alchemy/Diff\";\nimport * as Output from \"alchemy/Output\";\nimport * as Provider from \"alchemy/Provider\";\nimport { Stack } from \"alchemy/Stack\";\nimport { State } from \"alchemy/State\";\nimport * as Effect from \"effect/Effect\";\nimport * as Option from \"effect/Option\";\nimport * as Redacted from \"effect/Redacted\";\nimport * as Schema from \"effect/Schema\";\nimport type { Providers } from \"./Providers.ts\";\n\n/** A hung service must not hold a deploy: the time limit of one request. */\nconst REQUEST_TIMEOUT_MS = 30_000;\n\n/** The variables of a deployment without a hub client, as the starter writes them. */\nconst OFF_VALUES = { issuer: \"http://127.0.0.1:1\", clientId: \"no-hub\", clientSecret: \"no-hub\" };\n\ninterface CommonProps {\n /** The stable name of the app, set once in the stack. */\n readonly app: string;\n /**\n * The return address of the hub's sign-ins, such as `${site.siteUrl}/auth/hub/callback`: an https\n * URL whose origin serves `/samebase-verification.json` with the numeric GitHub id of this\n * repository, `{ \"repository\": \"123\" }`. It can come from a resource that this run creates. A\n * development client can also have http on a loopback host, which needs no file.\n */\n readonly redirectUri: string;\n /** Recorded on the hub's client, such as the commit. The hub records \"unknown\" without it. */\n readonly softwareVersion?: string;\n}\n\n/**\n * A production or preview client asks with the GitHub OIDC token of the run. A development client\n * asks with the app's initial access token, which `SharedHubClient.read` gives from stage `prod`.\n */\nexport type SharedHubClientProps =\n | (CommonProps & {\n readonly deploymentType: \"production\" | \"preview\";\n readonly initialAccessToken?: never;\n })\n | (CommonProps & {\n readonly deploymentType: \"development\";\n /** Empty before the first production registration: the client gets the off values. */\n readonly initialAccessToken: Redacted.Redacted<string>;\n });\n\nconst DeploymentType = Schema.Literals([\"production\", \"preview\", \"development\"]);\n\nconst Attributes = Schema.Struct({\n /** `off`: no client, and the values below are the off values. */\n status: Schema.Literals([\"on\", \"off\", \"suspended\"]),\n /** The issuer of the hub, which the deployment names to sign in. */\n issuer: Schema.String,\n clientId: Schema.String,\n /** Returned only at registration. */\n clientSecret: Schema.Redacted(Schema.String),\n /** Returned only at registration. It reads and deletes this client on the hub, and nothing else. */\n registrationAccessToken: Schema.Redacted(Schema.String),\n registrationClientUri: Schema.String,\n deploymentType: DeploymentType,\n /** The return address that the client serves. Empty for the off values. */\n redirectUri: Schema.String,\n /**\n * Returned only at a production registration (RFC 7591 section 1.2). It registers development\n * clients of this app. Empty for a preview, for a development client, and for the off values.\n */\n initialAccessToken: Schema.Redacted(Schema.String),\n /** The numeric GitHub id of the repository, with the initial access token. Empty without it. */\n repository: Schema.String,\n});\n\n/**\n * The attributes keep the real values of a suspended client, so that an unblock needs no new client.\n * `SharedHubClient.variables` gives the values that the app gets.\n */\nexport interface SharedHubClientAttributes extends Schema.Schema.Type<typeof Attributes> {}\n\n/**\n * The attributes of a saved row, or undefined when the row does not have this version's shape, such\n * as a row that 0.26 or earlier saved without the token fields. Only a schema issue gives undefined;\n * any other failure of the decoder stops the run with a fixed message. The text of a schema issue\n * can quote a value of the row, so it is never kept.\n */\nconst savedAttributes = (value: unknown) =>\n Schema.decodeUnknownEffect(Attributes)(value).pipe(\n Effect.map((attributes): SharedHubClientAttributes | undefined => attributes),\n Effect.catch(() => Effect.succeed(undefined)),\n Effect.catchDefect(() =>\n Effect.die(\n new SharedHubClientFailed({\n reason: \"the state holds attributes of Convex.SharedHubClient that it cannot read\",\n }),\n ),\n ),\n );\n\n/** The attributes of no client. */\nconst offAttributes = (\n deploymentType: SharedHubClientAttributes[\"deploymentType\"],\n): SharedHubClientAttributes => ({\n status: \"off\",\n issuer: OFF_VALUES.issuer,\n clientId: OFF_VALUES.clientId,\n clientSecret: Redacted.make(OFF_VALUES.clientSecret),\n registrationAccessToken: Redacted.make(\"\"),\n registrationClientUri: \"\",\n deploymentType,\n redirectUri: \"\",\n initialAccessToken: Redacted.make(\"\"),\n repository: \"\",\n});\n\n/** One request gave no usable answer. The reason holds no secret. */\nclass SharedHubClientFailed extends Schema.TaggedError<SharedHubClientFailed>()(\n \"SharedHubClientFailed\",\n { reason: Schema.String },\n) {}\n\nconst OidcToken = Schema.Struct({ value: Schema.NonEmptyString });\n\n/** The 201 answer of `POST /hub-clients`. */\nconst NewClient = Schema.Struct({\n issuer: Schema.NonEmptyString,\n client_id: Schema.NonEmptyString,\n client_secret: Schema.NonEmptyString,\n registration_access_token: Schema.NonEmptyString,\n registration_client_uri: Schema.NonEmptyString,\n redirect_uri: Schema.NonEmptyString,\n /**\n * The terms of the shared sign-in service, which the log shows once, for the new client. Exactly\n * this page: the log line must not print a value that the answer chose.\n */\n terms: Schema.Literal(\"https://samebase.com/terms\"),\n /** Only in the answer to a production registration with the GitHub token. */\n initial_access_token: Schema.optionalKey(Schema.NonEmptyString),\n repository: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^\\d+$/))),\n});\n\n/** The other answers of `POST /hub-clients`, by status. */\nconst REFUSALS: Readonly<Record<number, string>> = {\n 400: \"the identity hub refused the request: the return address must be https on a DNS name, and the app name at most 64 characters\",\n 401: \"the identity hub refused the GitHub OIDC token\",\n 403: \"the identity hub does not allow this repository or return address\",\n 404: \"the identity hub has no /hub-clients\",\n 429: \"the identity hub limits new clients\",\n 502: \"GitHub's keys are not available to the identity hub\",\n};\n\n/** The reasons of a 403 that a log line shows. Any other text of the answer is never logged. */\nconst NOT_ALLOWED = new Map([\n [\"paused\", \"the identity hub pauses new clients of third-party apps\"],\n [\"run_not_allowed\", \"the identity hub gives clients only to runs of main and of pull requests\"],\n [\"blocked\", \"Samebase blocked this repository or its owner\"],\n [\"suspended\", \"the identity hub suspended this repository\"],\n [\"too_many_apps\", \"the repository has as many apps on the identity hub as it may\"],\n [\"too_many_clients\", \"the app has as many clients on the identity hub as it may\"],\n [\"app_taken\", \"the app's name belongs to another app on the identity hub\"],\n [\"host_unreachable\", \"the identity hub could not reach the host of the return address\"],\n [\n \"verification_file_missing\",\n \"the host of the return address does not serve /samebase-verification.json\",\n ],\n [\"verification_file_invalid\", \"/samebase-verification.json on the host holds no repository id\"],\n [\"repository_mismatch\", \"/samebase-verification.json on the host names another repository\"],\n [\"app_mismatch\", \"the initial access token belongs to another app on the identity hub\"],\n]);\n\n/** The windows of a 429, by the name that the answer gives. */\nconst RATE_LIMITED = new Map([\n [\"repository\", \"the identity hub gives one repository at most 30 new clients an hour\"],\n [\"owner\", \"the identity hub gives one owner at most 100 new clients an hour\"],\n [\"new_owners\", \"the identity hub takes at most 50 new owners an hour\"],\n]);\n\nconst Refusal = Schema.Struct({ reason: Schema.String });\nconst RateLimited = Schema.Struct({ window: Schema.String });\n\n/** The text of a refusal: a known reason of a 403 or window of a 429, else the text of its status. */\nconst refusalOf = (status: number, body: unknown) => {\n const known =\n status === 403\n ? NOT_ALLOWED.get(\n Option.getOrUndefined(Schema.decodeUnknownOption(Refusal)(body))?.reason ?? \"\",\n )\n : status === 429\n ? RATE_LIMITED.get(\n Option.getOrUndefined(Schema.decodeUnknownOption(RateLimited)(body))?.window ?? \"\",\n )\n : undefined;\n return known ?? REFUSALS[status] ?? \"the identity hub refused the request\";\n};\n\nconst send = (who: string, url: string, token: string, init: { method: string; body?: string }) =>\n Effect.tryPromise({\n try: async () => {\n const response = await fetch(url, {\n ...init,\n headers: { Authorization: `Bearer ${token}`, \"Content-Type\": \"application/json\" },\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n const body: unknown = await response.json().catch(() => undefined);\n return { status: response.status, body };\n },\n catch: () => new SharedHubClientFailed({ reason: `${who} gave no answer` }),\n });\n\nconst decode =\n <S extends Schema.Codec<unknown, unknown>>(schema: S, who: string) =>\n (value: unknown) =>\n Schema.decodeUnknownEffect(schema)(value).pipe(\n Effect.mapError(() => new SharedHubClientFailed({ reason: `${who} gave a bad answer` })),\n );\n\n/** The OIDC token of this run, read right before the call, because it lives for minutes only. */\nconst readOidcToken = (requestUrl: string, requestToken: string) =>\n Effect.gen(function* () {\n const { body, status } = yield* send(\n \"GitHub\",\n `${requestUrl}&audience=samebase`,\n requestToken,\n { method: \"GET\" },\n );\n if (status !== 200) {\n return yield* new SharedHubClientFailed({\n reason: `GitHub gave no OIDC token (HTTP ${status})`,\n });\n }\n return (yield* decode(OidcToken, \"GitHub\")(body)).value;\n });\n\n/** `POST /hub-clients` with `bearer`: the GitHub OIDC token, or the app's initial access token. */\nconst requestNewClient = (props: SharedHubClientProps, bearer: string) =>\n Effect.gen(function* () {\n const { body, status } = yield* send(\n \"The identity hub\",\n `${process.env[\"HUB_URL\"] ?? \"https://auth.samebase.com\"}/hub-clients`,\n bearer,\n {\n method: \"POST\",\n body: JSON.stringify({\n app: props.app,\n redirectUri: props.redirectUri,\n softwareVersion: props.softwareVersion ?? \"unknown\",\n }),\n },\n );\n if (status !== 201) {\n const reason =\n status === 401 && props.deploymentType === \"development\"\n ? \"the identity hub refused the initial access token\"\n : refusalOf(status, body);\n return yield* new SharedHubClientFailed({ reason: `${reason} (HTTP ${status})` });\n }\n const client = yield* decode(NewClient, \"The identity hub\")(body);\n // The hub registers the address that the request sent. The log names the request's, so no\n // value that the answer chose is printed.\n if (client.redirect_uri !== props.redirectUri) {\n return yield* new SharedHubClientFailed({ reason: \"The identity hub gave a bad answer\" });\n }\n yield* Effect.logInfo(\n `Convex.SharedHubClient: ${props.app} on ${props.redirectUri} has a new client of the identity hub. Its use is under ${client.terms}.`,\n );\n // Only a production registration gives the app's token (RFC 7591); any other answer's is dropped.\n const production = props.deploymentType === \"production\";\n return {\n status: \"on\" as const,\n issuer: client.issuer,\n clientId: client.client_id,\n clientSecret: Redacted.make(client.client_secret),\n registrationAccessToken: Redacted.make(client.registration_access_token),\n registrationClientUri: client.registration_client_uri,\n deploymentType: props.deploymentType,\n redirectUri: props.redirectUri,\n initialAccessToken: Redacted.make(production ? (client.initial_access_token ?? \"\") : \"\"),\n repository: production ? (client.repository ?? \"\") : \"\",\n };\n });\n\n/** A new client, or the off values. */\nconst newClient = (props: SharedHubClientProps) => {\n const label = `Convex.SharedHubClient: ${props.app} on ${props.redirectUri} deploys without the hub`;\n const off = offAttributes(props.deploymentType);\n const request = (bearer: Effect.Effect<string, SharedHubClientFailed>) =>\n bearer.pipe(\n Effect.flatMap((token) => requestNewClient(props, token)),\n Effect.catchTag(\"SharedHubClientFailed\", (error) =>\n Effect.logWarning(`${label}: ${error.reason}. The next deploy asks again.`).pipe(\n Effect.as(off),\n ),\n ),\n );\n\n if (props.deploymentType === \"development\") {\n const token = Redacted.value(props.initialAccessToken);\n return token === \"\"\n ? Effect.logInfo(\n \"Convex.SharedHubClient: deploy to main once for Google sign-in on this laptop.\",\n ).pipe(Effect.as(off))\n : request(Effect.succeed(token));\n }\n\n const requestUrl = process.env[\"ACTIONS_ID_TOKEN_REQUEST_URL\"];\n const requestToken = process.env[\"ACTIONS_ID_TOKEN_REQUEST_TOKEN\"];\n // A laptop, or another CI. In a workflow, `id-token: write` gives both, so a workflow without them\n // is a mistake to show, not a normal run.\n if (!requestUrl || !requestToken) {\n return (\n process.env[\"GITHUB_ACTIONS\"] === \"true\"\n ? Effect.logWarning(`${label}: this workflow needs the permission id-token: write.`)\n : Effect.logInfo(`${label}: this run has no GitHub OIDC token.`)\n ).pipe(Effect.as(off));\n }\n return request(readOidcToken(requestUrl, requestToken));\n};\n\n/** The client after a read on the hub, or undefined when the hub says that the client is gone. */\nconst renew = (saved: SharedHubClientAttributes) =>\n Effect.gen(function* () {\n const { status } = yield* send(\n \"The identity hub\",\n saved.registrationClientUri,\n Redacted.value(saved.registrationAccessToken),\n { method: \"GET\" },\n );\n switch (status) {\n case 200:\n return { ...saved, status: \"on\" as const };\n case 401:\n return undefined;\n case 403:\n yield* Effect.logWarning(\n `Convex.SharedHubClient: the hub suspended the client of ${saved.redirectUri}. It keeps its values, and nobody can sign in with it.`,\n );\n return { ...saved, status: \"suspended\" as const };\n default:\n return yield* new SharedHubClientFailed({\n reason: `The identity hub answered HTTP ${status}`,\n });\n }\n }).pipe(\n Effect.catchTag(\"SharedHubClientFailed\", (error) =>\n Effect.logWarning(\n `Convex.SharedHubClient: ${saved.redirectUri} keeps the saved client: ${error.reason}.`,\n ).pipe(Effect.as(saved)),\n ),\n );\n\n/** A saved row of this resource, whose `attr` `savedAttributes` reads. */\nconst SavedRow = Schema.Struct({\n resourceType: Schema.Literal(\"IdentityHub.SharedClient\"),\n attr: Schema.Unknown,\n});\n\n/**\n * The attributes of the client `id` in another stage of this stack, such as\n * `read(\"HubClient\", { stage: \"prod\" })` in the stage of a laptop, as Outputs: the\n * `initialAccessToken` and the `repository` of the production client. Without that row, the off\n * attributes, with `deploymentType: \"production\"`.\n *\n * `SharedHubClient.ref` is not enough: Alchemy fails the plan with `InvalidReferenceError` when the\n * row is missing, and a laptop before the first deploy to main has none. This read runs when the\n * plan resolves it, with the State service of the running stack and its `get`, so it never creates\n * or upgrades a store. The store revives each secret as `Redacted`, as it does for every read.\n */\nconst read = (id: string, options: { readonly stage: string }) =>\n Output.fromEffect(\n Effect.gen(function* () {\n const stack = yield* Stack;\n const state = yield* yield* State;\n const row = yield* state\n .get({ stack: stack.name, stage: options.stage, fqn: id })\n .pipe(Effect.orDie);\n if (row === undefined) return offAttributes(\"production\");\n const { attr } = yield* Schema.decodeUnknownEffect(SavedRow)(row).pipe(\n Effect.catchCause(() =>\n Effect.die(\n new SharedHubClientFailed({\n reason: `the row ${id} of stage ${options.stage} is not a Convex.SharedHubClient`,\n }),\n ),\n ),\n );\n const saved = yield* savedAttributes(attr);\n if (saved === undefined) {\n return yield* Effect.die(\n new SharedHubClientFailed({\n reason: `the row ${id} of stage ${options.stage} holds attributes that this version cannot read`,\n }),\n );\n }\n return saved;\n }),\n );\n\n/**\n * The three values that the app's deployment gets, such as `HUB_ISSUER`, `HUB_CLIENT_ID`, and\n * `HUB_CLIENT_SECRET`, as Outputs: the client's values when `status` is `on`, else the off values,\n * so that a suspended client gives guest sign-in and keeps its values in state.\n */\nconst variables = (client: SharedHubClient) =>\n Output.map(Output.of(client), (attributes) =>\n attributes.status === \"on\"\n ? {\n issuer: attributes.issuer,\n clientId: attributes.clientId,\n clientSecret: attributes.clientSecret,\n }\n : {\n issuer: OFF_VALUES.issuer,\n clientId: OFF_VALUES.clientId,\n clientSecret: Redacted.make(OFF_VALUES.clientSecret),\n },\n );\n\nexport type SharedHubClient = Resource<\n \"IdentityHub.SharedClient\",\n SharedHubClientProps,\n SharedHubClientAttributes,\n never,\n Providers\n>;\nexport const SharedHubClient = Resource<SharedHubClient>(\"IdentityHub.SharedClient\")({\n read,\n variables,\n});\n\nexport const SharedHubClientProvider = () =>\n Provider.succeed(SharedHubClient, {\n // Always an update, so that each deploy reaches the hub. No attribute is stable, so a push\n // reads the values that this run ended with. The same values cause no other push.\n diff: ({ news }) =>\n Effect.succeed(isResolved(news) ? ({ action: \"update\" } as const) : undefined),\n\n read: ({ output }) => Effect.succeed(output),\n\n // Only a client of the same return address and type is renewed. A saved row without this\n // version's shape, such as one that 0.26 or earlier saved, is no client: the run asks for a new\n // one, which replaces the saved client, and for a production client also replaces the app's\n // initial access token.\n reconcile: Effect.fn(function* ({ news, output }) {\n const saved =\n output === undefined || output.status === \"off\"\n ? undefined\n : yield* savedAttributes(output);\n if (output !== undefined && output.status !== \"off\" && saved === undefined) {\n yield* Effect.logWarning(\n `Convex.SharedHubClient: the saved attributes of ${news.app} on ${news.redirectUri} cannot be read by this version, so this run registers a new client, which replaces the saved one.`,\n );\n }\n const renewed =\n saved?.redirectUri === news.redirectUri && saved.deploymentType === news.deploymentType\n ? yield* renew(saved)\n : undefined;\n return renewed ?? (yield* newClient(news));\n }),\n\n // A failed delete does not stop a destroy: the removal date of a preview or development client\n // cleans it up. A destroy can leave a production deployment running, so its client stays.\n delete: ({ output }) =>\n output.deploymentType === \"production\" || output.status === \"off\"\n ? Effect.void\n : Effect.gen(function* () {\n const { status } = yield* send(\n \"The identity hub\",\n output.registrationClientUri,\n Redacted.value(output.registrationAccessToken),\n { method: \"DELETE\" },\n );\n // 401: the client is gone already.\n if (status !== 204 && status !== 401) {\n return yield* new SharedHubClientFailed({\n reason: `The identity hub answered HTTP ${status}`,\n });\n }\n }).pipe(\n Effect.catchTag(\"SharedHubClientFailed\", (error) =>\n Effect.logWarning(\n `Convex.SharedHubClient: the client of ${output.redirectUri} stays on the hub until its removal date: ${error.reason}.`,\n ),\n ),\n ),\n });\n","// The provider collection users merge into a stack:\n// providers: Layer.mergeAll(Cloudflare.providers(), Convex.providers())\nimport * as Provider from \"alchemy/Provider\";\nimport * as Layer from \"effect/Layer\";\nimport { type Credentials, fromConfig } from \"./Credentials.ts\";\nimport { DeploymentProvider, DeploymentResource } from \"./Deployment.ts\";\nimport { HubClient, HubClientProvider } from \"./HubClient.ts\";\nimport { ManagementApiLive } from \"./ManagementApi.ts\";\nimport { Project, ProjectProvider } from \"./Project.ts\";\nimport { SharedHubClient, SharedHubClientProvider } from \"./SharedHubClient.ts\";\n\nexport class Providers extends Provider.ProviderCollection<Providers>()(\"Convex\") {}\n\n/**\n * The Convex resource providers. Pass a credentials layer to override the\n * default lookup (`CONVEX_ACCESS_TOKEN`, then the Convex CLI login, and\n * `CONVEX_DEPLOY_KEY`).\n */\nexport const providers = (credentials: Layer.Layer<Credentials> = fromConfig()) =>\n Layer.effect(\n Providers,\n Provider.collection([Project, DeploymentResource, HubClient, SharedHubClient]),\n ).pipe(\n Layer.provide(\n Layer.mergeAll(\n ProjectProvider(),\n DeploymentProvider(),\n HubClientProvider(),\n SharedHubClientProvider(),\n ),\n ),\n Layer.provide(ManagementApiLive()),\n Layer.provide(credentials),\n Layer.orDie,\n );\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,iBAAiB,YAK5B,OAAO,IAAI,aAAa;CACtB,IAAI,QAAQ,cAAc,IACxB,OAAO,OAAO,OAAO,oBAAI,IAAI,MAAM,8BAA8B,CAAC;CAEpE,MAAM,YAAY,OAAO,QAAQ,SAAS;CAC1C,MAAM,MAAM,oBAAoB,KAAK,UAAU,SAAS;CAExD,MAAM,YAAW,OADI,MAAA,CACE,SAAS,QAAQ,WAAW,IAAI,EAAE,MAAM,YAAY,QAAQ,QAAQ,GAAG;CAE9F,IAAI,aAAa,KAAA,GAAW;EAC1B,MAAM,WAAW,OAAO,kBAAkB,OAAO,OAAO,EAAE,MAAM,OAAO,OAAO,CAAC,CAAC,CAAC,CAC/E,SAAS,IACX;EACA,IAAI,SAAS,SAAS,QAAQ,WAC5B,OAAO,OAAO,OAAO,oBACnB,IAAI,MACF,qBAAqB,KAAK,UAAU,SAAS,EAAE,uBAAuB,SAAS,KAAK,EACtF,CACF;EAEF;CACF;CACA,OAAO,QAAQ,WAAW,KAAK,KAAK;EAClC,MAAM;EACN,MAAM,QAAQ;EACd;CACF,CAAC;AACH,CAAC;;;;ACfH,MAAM,qBAAqB;;AAG3B,MAAM,aAAa;CAAE,QAAQ;CAAsB,UAAU;CAAU,cAAc;AAAS;AA+B9F,MAAM,iBAAiB,OAAO,SAAS;CAAC;CAAc;CAAW;AAAa,CAAC;AAE/E,MAAM,aAAa,OAAO,OAAO;;CAE/B,QAAQ,OAAO,SAAS;EAAC;EAAM;EAAO;CAAW,CAAC;;CAElD,QAAQ,OAAO;CACf,UAAU,OAAO;;CAEjB,cAAc,OAAO,SAAS,OAAO,MAAM;;CAE3C,yBAAyB,OAAO,SAAS,OAAO,MAAM;CACtD,uBAAuB,OAAO;CAC9B,gBAAgB;;CAEhB,aAAa,OAAO;;;;;CAKpB,oBAAoB,OAAO,SAAS,OAAO,MAAM;;CAEjD,YAAY,OAAO;AACrB,CAAC;;;;;;;AAcD,MAAM,mBAAmB,UACvB,OAAO,oBAAoB,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,KAC5C,OAAO,KAAK,eAAsD,UAAU,GAC5E,OAAO,YAAY,OAAO,QAAQ,KAAA,CAAS,CAAC,GAC5C,OAAO,kBACL,OAAO,IACL,IAAI,sBAAsB,EACxB,QAAQ,2EACV,CAAC,CACH,CACF,CACF;;AAGF,MAAM,iBACJ,oBAC+B;CAC/B,QAAQ;CACR,QAAQ,WAAW;CACnB,UAAU,WAAW;CACrB,cAAc,SAAS,KAAK,WAAW,YAAY;CACnD,yBAAyB,SAAS,KAAK,EAAE;CACzC,uBAAuB;CACvB;CACA,aAAa;CACb,oBAAoB,SAAS,KAAK,EAAE;CACpC,YAAY;AACd;;AAGA,IAAM,wBAAN,cAAoC,OAAO,YAAmC,CAAC,CAC7E,yBACA,EAAE,QAAQ,OAAO,OAAO,CAC1B,CAAC,CAAC,CAAC;AAEH,MAAM,YAAY,OAAO,OAAO,EAAE,OAAO,OAAO,eAAe,CAAC;;AAGhE,MAAM,YAAY,OAAO,OAAO;CAC9B,QAAQ,OAAO;CACf,WAAW,OAAO;CAClB,eAAe,OAAO;CACtB,2BAA2B,OAAO;CAClC,yBAAyB,OAAO;CAChC,cAAc,OAAO;;;;;CAKrB,OAAO,OAAO,QAAQ,4BAA4B;;CAElD,sBAAsB,OAAO,YAAY,OAAO,cAAc;CAC9D,YAAY,OAAO,YAAY,OAAO,OAAO,MAAM,OAAO,UAAU,OAAO,CAAC,CAAC;AAC/E,CAAC;;AAGD,MAAM,WAA6C;CACjD,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACP;;AAGA,MAAM,8BAAc,IAAI,IAAI;CAC1B,CAAC,UAAU,yDAAyD;CACpE,CAAC,mBAAmB,0EAA0E;CAC9F,CAAC,WAAW,+CAA+C;CAC3D,CAAC,aAAa,4CAA4C;CAC1D,CAAC,iBAAiB,+DAA+D;CACjF,CAAC,oBAAoB,2DAA2D;CAChF,CAAC,aAAa,2DAA2D;CACzE,CAAC,oBAAoB,iEAAiE;CACtF,CACE,6BACA,2EACF;CACA,CAAC,6BAA6B,gEAAgE;CAC9F,CAAC,uBAAuB,kEAAkE;CAC1F,CAAC,gBAAgB,qEAAqE;AACxF,CAAC;;AAGD,MAAM,+BAAe,IAAI,IAAI;CAC3B,CAAC,cAAc,sEAAsE;CACrF,CAAC,SAAS,kEAAkE;CAC5E,CAAC,cAAc,sDAAsD;AACvE,CAAC;AAED,MAAM,UAAU,OAAO,OAAO,EAAE,QAAQ,OAAO,OAAO,CAAC;AACvD,MAAM,cAAc,OAAO,OAAO,EAAE,QAAQ,OAAO,OAAO,CAAC;;AAG3D,MAAM,aAAa,QAAgB,SAAkB;CAWnD,QATE,WAAW,MACP,YAAY,IACV,OAAO,eAAe,OAAO,oBAAoB,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,EAC9E,IACA,WAAW,MACT,aAAa,IACX,OAAO,eAAe,OAAO,oBAAoB,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,EAClF,IACA,KAAA,MACQ,SAAS,WAAW;AACtC;AAEA,MAAM,QAAQ,KAAa,KAAa,OAAe,SACrD,OAAO,WAAW;CAChB,KAAK,YAAY;EACf,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,GAAG;GACH,SAAS;IAAE,eAAe,UAAU;IAAS,gBAAgB;GAAmB;GAChF,QAAQ,YAAY,QAAQ,kBAAkB;EAChD,CAAC;EACD,MAAM,OAAgB,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS;EACjE,OAAO;GAAE,QAAQ,SAAS;GAAQ;EAAK;CACzC;CACA,aAAa,IAAI,sBAAsB,EAAE,QAAQ,GAAG,IAAI,iBAAiB,CAAC;AAC5E,CAAC;AAEH,MAAM,UACuC,QAAW,SACrD,UACC,OAAO,oBAAoB,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KACxC,OAAO,eAAe,IAAI,sBAAsB,EAAE,QAAQ,GAAG,IAAI,oBAAoB,CAAC,CAAC,CACzF;;AAGJ,MAAM,iBAAiB,YAAoB,iBACzC,OAAO,IAAI,aAAa;CACtB,MAAM,EAAE,MAAM,WAAW,OAAO,KAC9B,UACA,GAAG,WAAW,qBACd,cACA,EAAE,QAAQ,MAAM,CAClB;CACA,IAAI,WAAW,KACb,OAAO,OAAO,IAAI,sBAAsB,EACtC,QAAQ,mCAAmC,OAAO,GACpD,CAAC;CAEH,QAAQ,OAAO,OAAO,WAAW,QAAQ,CAAC,CAAC,IAAI,EAAA,CAAG;AACpD,CAAC;;AAGH,MAAM,oBAAoB,OAA6B,WACrD,OAAO,IAAI,aAAa;CACtB,MAAM,EAAE,MAAM,WAAW,OAAO,KAC9B,oBACA,GAAG,QAAQ,IAAI,cAAc,4BAA4B,eACzD,QACA;EACE,QAAQ;EACR,MAAM,KAAK,UAAU;GACnB,KAAK,MAAM;GACX,aAAa,MAAM;GACnB,iBAAiB,MAAM,mBAAmB;EAC5C,CAAC;CACH,CACF;CACA,IAAI,WAAW,KAKb,OAAO,OAAO,IAAI,sBAAsB,EAAE,QAAQ,GAHhD,WAAW,OAAO,MAAM,mBAAmB,gBACvC,sDACA,UAAU,QAAQ,IAAI,EACgC,SAAS,OAAO,GAAG,CAAC;CAElF,MAAM,SAAS,OAAO,OAAO,WAAW,kBAAkB,CAAC,CAAC,IAAI;CAGhE,IAAI,OAAO,iBAAiB,MAAM,aAChC,OAAO,OAAO,IAAI,sBAAsB,EAAE,QAAQ,qCAAqC,CAAC;CAE1F,OAAO,OAAO,QACZ,2BAA2B,MAAM,IAAI,MAAM,MAAM,YAAY,0DAA0D,OAAO,MAAM,EACtI;CAEA,MAAM,aAAa,MAAM,mBAAmB;CAC5C,OAAO;EACL,QAAQ;EACR,QAAQ,OAAO;EACf,UAAU,OAAO;EACjB,cAAc,SAAS,KAAK,OAAO,aAAa;EAChD,yBAAyB,SAAS,KAAK,OAAO,yBAAyB;EACvE,uBAAuB,OAAO;EAC9B,gBAAgB,MAAM;EACtB,aAAa,MAAM;EACnB,oBAAoB,SAAS,KAAK,aAAc,OAAO,wBAAwB,KAAM,EAAE;EACvF,YAAY,aAAc,OAAO,cAAc,KAAM;CACvD;AACF,CAAC;;AAGH,MAAM,aAAa,UAAgC;CACjD,MAAM,QAAQ,2BAA2B,MAAM,IAAI,MAAM,MAAM,YAAY;CAC3E,MAAM,MAAM,cAAc,MAAM,cAAc;CAC9C,MAAM,WAAW,WACf,OAAO,KACL,OAAO,SAAS,UAAU,iBAAiB,OAAO,KAAK,CAAC,GACxD,OAAO,SAAS,0BAA0B,UACxC,OAAO,WAAW,GAAG,MAAM,IAAI,MAAM,OAAO,8BAA8B,CAAC,CAAC,KAC1E,OAAO,GAAG,GAAG,CACf,CACF,CACF;CAEF,IAAI,MAAM,mBAAmB,eAAe;EAC1C,MAAM,QAAQ,SAAS,MAAM,MAAM,kBAAkB;EACrD,OAAO,UAAU,KACb,OAAO,QACL,gFACF,CAAC,CAAC,KAAK,OAAO,GAAG,GAAG,CAAC,IACrB,QAAQ,OAAO,QAAQ,KAAK,CAAC;CACnC;CAEA,MAAM,aAAa,QAAQ,IAAI;CAC/B,MAAM,eAAe,QAAQ,IAAI;CAGjC,IAAI,CAAC,cAAc,CAAC,cAClB,QACE,QAAQ,IAAI,sBAAsB,SAC9B,OAAO,WAAW,GAAG,MAAM,sDAAsD,IACjF,OAAO,QAAQ,GAAG,MAAM,qCAAqC,EAAA,CACjE,KAAK,OAAO,GAAG,GAAG,CAAC;CAEvB,OAAO,QAAQ,cAAc,YAAY,YAAY,CAAC;AACxD;;AAGA,MAAM,SAAS,UACb,OAAO,IAAI,aAAa;CACtB,MAAM,EAAE,WAAW,OAAO,KACxB,oBACA,MAAM,uBACN,SAAS,MAAM,MAAM,uBAAuB,GAC5C,EAAE,QAAQ,MAAM,CAClB;CACA,QAAQ,QAAR;EACE,KAAK,KACH,OAAO;GAAE,GAAG;GAAO,QAAQ;EAAc;EAC3C,KAAK,KACH;EACF,KAAK;GACH,OAAO,OAAO,WACZ,2DAA2D,MAAM,YAAY,uDAC/E;GACA,OAAO;IAAE,GAAG;IAAO,QAAQ;GAAqB;EAClD,SACE,OAAO,OAAO,IAAI,sBAAsB,EACtC,QAAQ,kCAAkC,SAC5C,CAAC;CACL;AACF,CAAC,CAAC,CAAC,KACD,OAAO,SAAS,0BAA0B,UACxC,OAAO,WACL,2BAA2B,MAAM,YAAY,2BAA2B,MAAM,OAAO,EACvF,CAAC,CAAC,KAAK,OAAO,GAAG,KAAK,CAAC,CACzB,CACF;;AAGF,MAAM,WAAW,OAAO,OAAO;CAC7B,cAAc,OAAO,QAAQ,0BAA0B;CACvD,MAAM,OAAO;AACf,CAAC;;;;;;;;;;;;AAaD,MAAM,QAAQ,IAAY,YACxB,OAAO,WACL,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO;CAErB,MAAM,MAAM,QAAO,OADE,OAAO,MAAA,CAEzB,IAAI;EAAE,OAAO,MAAM;EAAM,OAAO,QAAQ;EAAO,KAAK;CAAG,CAAC,CAAC,CACzD,KAAK,OAAO,KAAK;CACpB,IAAI,QAAQ,KAAA,GAAW,OAAO,cAAc,YAAY;CACxD,MAAM,EAAE,SAAS,OAAO,OAAO,oBAAoB,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,KAChE,OAAO,iBACL,OAAO,IACL,IAAI,sBAAsB,EACxB,QAAQ,WAAW,GAAG,YAAY,QAAQ,MAAM,kCAClD,CAAC,CACH,CACF,CACF;CACA,MAAM,QAAQ,OAAO,gBAAgB,IAAI;CACzC,IAAI,UAAU,KAAA,GACZ,OAAO,OAAO,OAAO,IACnB,IAAI,sBAAsB,EACxB,QAAQ,WAAW,GAAG,YAAY,QAAQ,MAAM,iDAClD,CAAC,CACH;CAEF,OAAO;AACT,CAAC,CACH;;;;;;AAOF,MAAM,aAAa,WACjB,OAAO,IAAI,OAAO,GAAG,MAAM,IAAI,eAC7B,WAAW,WAAW,OAClB;CACE,QAAQ,WAAW;CACnB,UAAU,WAAW;CACrB,cAAc,WAAW;AAC3B,IACA;CACE,QAAQ,WAAW;CACnB,UAAU,WAAW;CACrB,cAAc,SAAS,KAAK,WAAW,YAAY;AACrD,CACN;AASF,MAAa,kBAAkB,SAA0B,0BAA0B,CAAC,CAAC;CACnF;CACA;AACF,CAAC;AAED,MAAa,gCACX,SAAS,QAAQ,iBAAiB;CAGhC,OAAO,EAAE,WACP,OAAO,QAAQ,WAAW,IAAI,IAAK,EAAE,QAAQ,SAAS,IAAc,KAAA,CAAS;CAE/E,OAAO,EAAE,aAAa,OAAO,QAAQ,MAAM;CAM3C,WAAW,OAAO,GAAG,WAAW,EAAE,MAAM,UAAU;EAChD,MAAM,QACJ,WAAW,KAAA,KAAa,OAAO,WAAW,QACtC,KAAA,IACA,OAAO,gBAAgB,MAAM;EACnC,IAAI,WAAW,KAAA,KAAa,OAAO,WAAW,SAAS,UAAU,KAAA,GAC/D,OAAO,OAAO,WACZ,mDAAmD,KAAK,IAAI,MAAM,KAAK,YAAY,mGACrF;EAMF,QAHE,OAAO,gBAAgB,KAAK,eAAe,MAAM,mBAAmB,KAAK,iBACrE,OAAO,MAAM,KAAK,IAClB,KAAA,OACa,OAAO,UAAU,IAAI;CAC1C,CAAC;CAID,SAAS,EAAE,aACT,OAAO,mBAAmB,gBAAgB,OAAO,WAAW,QACxD,OAAO,OACP,OAAO,IAAI,aAAa;EACtB,MAAM,EAAE,WAAW,OAAO,KACxB,oBACA,OAAO,uBACP,SAAS,MAAM,OAAO,uBAAuB,GAC7C,EAAE,QAAQ,SAAS,CACrB;EAEA,IAAI,WAAW,OAAO,WAAW,KAC/B,OAAO,OAAO,IAAI,sBAAsB,EACtC,QAAQ,kCAAkC,SAC5C,CAAC;CAEL,CAAC,CAAC,CAAC,KACD,OAAO,SAAS,0BAA0B,UACxC,OAAO,WACL,yCAAyC,OAAO,YAAY,4CAA4C,MAAM,OAAO,EACvH,CACF,CACF;AACR,CAAC;;;AChfH,IAAa,YAAb,cAA+B,SAAS,mBAA8B,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;;;;;;AAOnF,MAAa,aAAa,cAAwC,WAAW,MAC3E,MAAM,OACJ,WACA,SAAS,WAAW;CAAC;CAAS;CAAoB;CAAW;AAAe,CAAC,CAC/E,CAAC,CAAC,KACA,MAAM,QACJ,MAAM,SACJ,gBAAgB,GAChB,mBAAmB,GACnB,kBAAkB,GAClB,wBAAwB,CAC1B,CACF,GACA,MAAM,QAAQ,kBAAkB,CAAC,GACjC,MAAM,QAAQ,WAAW,GACzB,MAAM,KACR"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/Component.ts","../src/SharedHubClient.ts","../src/Providers.ts"],"sourcesContent":["import { toPath } from \"alchemy/FQN\";\nimport type { NamespaceNode } from \"alchemy/Namespace\";\nimport { Stack } from \"alchemy/Stack\";\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\nimport type { Deployment } from \"./Deployment.ts\";\n\n/** A component's resources occupy this namespace in the deployment's Alchemy stack and stage. */\nexport interface ComponentBinding {\n readonly type: \"component\";\n readonly path: string;\n readonly namespace: readonly string[];\n}\n\n/**\n * Associate a native namespace with an installed component. `component` is the\n * installed name from the app's `convex.config.ts`; a nested install joins the\n * names with slashes, such as `\"workspace/mail\"`. An undefined namespace\n * associates the stack root. This records display metadata; it does not install\n * the component, set its environment, or change removal policies.\n */\nexport const bindComponent = (options: {\n deployment: Deployment;\n component: string;\n namespace: NamespaceNode | undefined;\n}) =>\n Effect.gen(function* () {\n if (options.component === \"\") {\n return yield* Effect.die(new Error(\"The component name is empty.\"));\n }\n const namespace = toPath(options.namespace);\n const sid = `convex.component:${JSON.stringify(namespace)}`;\n const stack = yield* Stack;\n const previous = stack.bindings[options.deployment.FQN]?.find((binding) => binding.sid === sid);\n // Alchemy keeps the last binding with a given sid. Reject conflicting owners before that happens.\n if (previous !== undefined) {\n const existing = Schema.decodeUnknownSync(Schema.Struct({ path: Schema.String }))(\n previous.data,\n );\n if (existing.path !== options.component) {\n return yield* Effect.die(\n new Error(\n `Alchemy namespace ${JSON.stringify(namespace)} is already bound to ${existing.path}.`,\n ),\n );\n }\n return;\n }\n yield* options.deployment.bind(sid, {\n type: \"component\",\n path: options.component,\n namespace,\n });\n });\n","// Convex.SharedHubClient: one deployment of an app as a client of the central identity hub, for an\n// app that deploys in its own GitHub Actions run, on any host, and on a laptop as a stage of the same\n// stack.\n//\n// A run in GitHub Actions asks the hub for a new client with a GitHub OIDC token, so the stack needs\n// no key and no Samebase account. The token names the repository. The hub reads\n// `/samebase-verification.json` at the origin of the return address, which must name the same\n// repository, and registers the client on that address. The answer holds the client's own\n// registration access token (RFC 7591), and each later deploy reads the client on the hub with that\n// token (RFC 7592). The hub keeps only hashes, so the state is the only record of the secret and the\n// token.\n//\n// The answer to a production registration also holds the app's initial access token (RFC 7591\n// section 1.2) and the repository id. The state of stage `prod` keeps them. A laptop runs the stack\n// as a stage `dev-<name>`: it reads them with `SharedHubClient.read`, and a client with\n// `deploymentType: \"development\"` asks the hub with the token in place of the GitHub token (RFC 133).\n//\n// A push removes a variable that a deploy stops passing, so a failure here must not drop working\n// variables or stop the deploy. A renewal that fails keeps the saved values. Only a client that\n// cannot work asks for a new one: it is gone, or it has another return address or type. If that\n// request fails, the run gives the off values, so the app has guest sign-in and not a broken button.\n// The off values are the `no-hub` variables of a checkout without a hub: Convex needs the variables,\n// and the app hides the button for this client ID.\n//\n// No text in this file quotes an answer or a fetch error, so no log line can hold a secret.\nimport { Resource } from \"alchemy\";\nimport { isResolved } from \"alchemy/Diff\";\nimport * as Output from \"alchemy/Output\";\nimport * as Provider from \"alchemy/Provider\";\nimport { Stack } from \"alchemy/Stack\";\nimport { State } from \"alchemy/State\";\nimport * as Effect from \"effect/Effect\";\nimport * as Option from \"effect/Option\";\nimport * as Redacted from \"effect/Redacted\";\nimport * as Schema from \"effect/Schema\";\nimport type { Providers } from \"./Providers.ts\";\n\n/** A hung service must not hold a deploy: the time limit of one request. */\nconst REQUEST_TIMEOUT_MS = 30_000;\n\n/** The variables of a deployment without a hub client, as the starter writes them. */\nconst OFF_VALUES = { issuer: \"http://127.0.0.1:1\", clientId: \"no-hub\", clientSecret: \"no-hub\" };\n\ninterface CommonProps {\n /** The stable name of the app, set once in the stack. */\n readonly app: string;\n /**\n * The return address of the hub's sign-ins, such as `${site.siteUrl}/auth/hub/callback`: an https\n * URL whose origin serves `/samebase-verification.json` with the numeric GitHub id of this\n * repository, `{ \"repository\": \"123\" }`. It can come from a resource that this run creates. A\n * development client can also have http on a loopback host, which needs no file.\n */\n readonly redirectUri: string;\n /** Recorded on the hub's client, such as the commit. The hub records \"unknown\" without it. */\n readonly softwareVersion?: string;\n}\n\n/**\n * A production or preview client asks with the GitHub OIDC token of the run. A development client\n * asks with the app's initial access token, which `SharedHubClient.read` gives from stage `prod`.\n */\nexport type SharedHubClientProps =\n | (CommonProps & {\n readonly deploymentType: \"production\" | \"preview\";\n readonly initialAccessToken?: never;\n })\n | (CommonProps & {\n readonly deploymentType: \"development\";\n /** Empty before the first production registration: the client gets the off values. */\n readonly initialAccessToken: Redacted.Redacted<string>;\n });\n\nconst DeploymentType = Schema.Literals([\"production\", \"preview\", \"development\"]);\n\nconst Attributes = Schema.Struct({\n /** `off`: no client, and the values below are the off values. */\n status: Schema.Literals([\"on\", \"off\", \"suspended\"]),\n /** The issuer of the hub, which the deployment names to sign in. */\n issuer: Schema.String,\n clientId: Schema.String,\n /** Returned only at registration. */\n clientSecret: Schema.Redacted(Schema.String),\n /** Returned only at registration. It reads and deletes this client on the hub, and nothing else. */\n registrationAccessToken: Schema.Redacted(Schema.String),\n registrationClientUri: Schema.String,\n deploymentType: DeploymentType,\n /** The return address that the client serves. Empty for the off values. */\n redirectUri: Schema.String,\n /**\n * Returned only at a production registration (RFC 7591 section 1.2). It registers development\n * clients of this app. Empty for a preview, for a development client, and for the off values.\n */\n initialAccessToken: Schema.Redacted(Schema.String),\n /** The numeric GitHub id of the repository, with the initial access token. Empty without it. */\n repository: Schema.String,\n});\n\n/**\n * The attributes keep the real values of a suspended client, so that an unblock needs no new client.\n * `SharedHubClient.variables` gives the values that the app gets.\n */\nexport interface SharedHubClientAttributes extends Schema.Schema.Type<typeof Attributes> {}\n\n/**\n * The attributes of a saved row, or undefined when the row does not have this version's shape, such\n * as a row that 0.26 or earlier saved without the token fields. Only a schema issue gives undefined;\n * any other failure of the decoder stops the run with a fixed message. The text of a schema issue\n * can quote a value of the row, so it is never kept.\n */\nconst savedAttributes = (value: unknown) =>\n Schema.decodeUnknownEffect(Attributes)(value).pipe(\n Effect.map((attributes): SharedHubClientAttributes | undefined => attributes),\n Effect.catch(() => Effect.succeed(undefined)),\n Effect.catchDefect(() =>\n Effect.die(\n new SharedHubClientFailed({\n reason: \"the state holds attributes of Convex.SharedHubClient that it cannot read\",\n }),\n ),\n ),\n );\n\n/** The attributes of no client. */\nconst offAttributes = (\n deploymentType: SharedHubClientAttributes[\"deploymentType\"],\n): SharedHubClientAttributes => ({\n status: \"off\",\n issuer: OFF_VALUES.issuer,\n clientId: OFF_VALUES.clientId,\n clientSecret: Redacted.make(OFF_VALUES.clientSecret),\n registrationAccessToken: Redacted.make(\"\"),\n registrationClientUri: \"\",\n deploymentType,\n redirectUri: \"\",\n initialAccessToken: Redacted.make(\"\"),\n repository: \"\",\n});\n\n/** One request gave no usable answer. The reason holds no secret. */\nclass SharedHubClientFailed extends Schema.TaggedError<SharedHubClientFailed>()(\n \"SharedHubClientFailed\",\n { reason: Schema.String },\n) {\n override get message() {\n return `Convex.SharedHubClient: ${this.reason}.`;\n }\n}\n\nconst OidcToken = Schema.Struct({ value: Schema.NonEmptyString });\n\n/** The 201 answer of `POST /hub-clients`. */\nconst NewClient = Schema.Struct({\n issuer: Schema.NonEmptyString,\n client_id: Schema.NonEmptyString,\n client_secret: Schema.NonEmptyString,\n registration_access_token: Schema.NonEmptyString,\n registration_client_uri: Schema.NonEmptyString,\n redirect_uri: Schema.NonEmptyString,\n /**\n * The terms of the shared sign-in service, which the log shows once, for the new client. Exactly\n * this page: the log line must not print a value that the answer chose.\n */\n terms: Schema.Literal(\"https://samebase.com/terms\"),\n /** Only in the answer to a production registration with the GitHub token. */\n initial_access_token: Schema.optionalKey(Schema.NonEmptyString),\n repository: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^\\d+$/))),\n});\n\n/** The other answers of `POST /hub-clients`, by status. */\nconst REFUSALS: Readonly<Record<number, string>> = {\n 400: \"the identity hub refused the request: the return address must be https on a DNS name, and the app name at most 64 characters\",\n 401: \"the identity hub refused the GitHub OIDC token\",\n 403: \"the identity hub does not allow this repository or return address\",\n 404: \"the identity hub has no /hub-clients\",\n 429: \"the identity hub limits new clients\",\n 502: \"GitHub's keys are not available to the identity hub\",\n};\n\n/** The reasons of a 403 that a log line shows. Any other text of the answer is never logged. */\nconst NOT_ALLOWED = new Map([\n [\"paused\", \"the identity hub pauses new clients of third-party apps\"],\n [\"run_not_allowed\", \"the identity hub gives clients only to runs of main and of pull requests\"],\n [\"blocked\", \"Samebase blocked this repository or its owner\"],\n [\"suspended\", \"the identity hub suspended this repository\"],\n [\"too_many_apps\", \"the repository has as many apps on the identity hub as it may\"],\n [\"too_many_clients\", \"the app has as many clients on the identity hub as it may\"],\n [\"app_taken\", \"the app's name belongs to another app on the identity hub\"],\n [\"host_unreachable\", \"the identity hub could not reach the host of the return address\"],\n [\n \"verification_file_missing\",\n \"the host of the return address does not serve /samebase-verification.json\",\n ],\n [\"verification_file_invalid\", \"/samebase-verification.json on the host holds no repository id\"],\n [\"repository_mismatch\", \"/samebase-verification.json on the host names another repository\"],\n [\"app_mismatch\", \"the initial access token belongs to another app on the identity hub\"],\n]);\n\n/** The windows of a 429, by the name that the answer gives. */\nconst RATE_LIMITED = new Map([\n [\"repository\", \"the identity hub gives one repository at most 30 new clients an hour\"],\n [\"owner\", \"the identity hub gives one owner at most 100 new clients an hour\"],\n [\"new_owners\", \"the identity hub takes at most 50 new owners an hour\"],\n]);\n\nconst Refusal = Schema.Struct({ reason: Schema.String });\nconst RateLimited = Schema.Struct({ window: Schema.String });\n\n/** The text of a refusal: a known reason of a 403 or window of a 429, else the text of its status. */\nconst refusalOf = (status: number, body: unknown) => {\n const known =\n status === 403\n ? NOT_ALLOWED.get(\n Option.getOrUndefined(Schema.decodeUnknownOption(Refusal)(body))?.reason ?? \"\",\n )\n : status === 429\n ? RATE_LIMITED.get(\n Option.getOrUndefined(Schema.decodeUnknownOption(RateLimited)(body))?.window ?? \"\",\n )\n : undefined;\n return known ?? REFUSALS[status] ?? \"the identity hub refused the request\";\n};\n\nconst send = (who: string, url: string, token: string, init: { method: string; body?: string }) =>\n Effect.tryPromise({\n try: async () => {\n const response = await fetch(url, {\n ...init,\n headers: { Authorization: `Bearer ${token}`, \"Content-Type\": \"application/json\" },\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n const body: unknown = await response.json().catch(() => undefined);\n return { status: response.status, body };\n },\n catch: () => new SharedHubClientFailed({ reason: `${who} gave no answer` }),\n });\n\nconst decode =\n <S extends Schema.Codec<unknown, unknown>>(schema: S, who: string) =>\n (value: unknown) =>\n Schema.decodeUnknownEffect(schema)(value).pipe(\n Effect.mapError(() => new SharedHubClientFailed({ reason: `${who} gave a bad answer` })),\n );\n\n/** The OIDC token of this run, read right before the call, because it lives for minutes only. */\nconst readOidcToken = (requestUrl: string, requestToken: string) =>\n Effect.gen(function* () {\n const { body, status } = yield* send(\n \"GitHub\",\n `${requestUrl}&audience=samebase`,\n requestToken,\n { method: \"GET\" },\n );\n if (status !== 200) {\n return yield* new SharedHubClientFailed({\n reason: `GitHub gave no OIDC token (HTTP ${status})`,\n });\n }\n return (yield* decode(OidcToken, \"GitHub\")(body)).value;\n });\n\n/** `POST /hub-clients` with `bearer`: the GitHub OIDC token, or the app's initial access token. */\nconst requestNewClient = (props: SharedHubClientProps, bearer: string) =>\n Effect.gen(function* () {\n const { body, status } = yield* send(\n \"The identity hub\",\n `${process.env[\"HUB_URL\"] ?? \"https://auth.samebase.com\"}/hub-clients`,\n bearer,\n {\n method: \"POST\",\n body: JSON.stringify({\n app: props.app,\n redirectUri: props.redirectUri,\n softwareVersion: props.softwareVersion ?? \"unknown\",\n }),\n },\n );\n if (status !== 201) {\n const reason =\n status === 401 && props.deploymentType === \"development\"\n ? \"the identity hub refused the initial access token\"\n : refusalOf(status, body);\n return yield* new SharedHubClientFailed({ reason: `${reason} (HTTP ${status})` });\n }\n const client = yield* decode(NewClient, \"The identity hub\")(body);\n // The hub registers the address that the request sent. The log names the request's, so no\n // value that the answer chose is printed.\n if (client.redirect_uri !== props.redirectUri) {\n return yield* new SharedHubClientFailed({ reason: \"The identity hub gave a bad answer\" });\n }\n yield* Effect.logInfo(\n `Convex.SharedHubClient: ${props.app} on ${props.redirectUri} has a new client of the identity hub. Its use is under ${client.terms}.`,\n );\n // Only a production registration gives the app's token (RFC 7591); any other answer's is dropped.\n const production = props.deploymentType === \"production\";\n return {\n status: \"on\" as const,\n issuer: client.issuer,\n clientId: client.client_id,\n clientSecret: Redacted.make(client.client_secret),\n registrationAccessToken: Redacted.make(client.registration_access_token),\n registrationClientUri: client.registration_client_uri,\n deploymentType: props.deploymentType,\n redirectUri: props.redirectUri,\n initialAccessToken: Redacted.make(production ? (client.initial_access_token ?? \"\") : \"\"),\n repository: production ? (client.repository ?? \"\") : \"\",\n };\n });\n\n/** A new client, or the off values. */\nconst newClient = (props: SharedHubClientProps) => {\n const label = `Convex.SharedHubClient: ${props.app} on ${props.redirectUri} deploys without the hub`;\n const off = offAttributes(props.deploymentType);\n const request = (bearer: Effect.Effect<string, SharedHubClientFailed>) =>\n bearer.pipe(\n Effect.flatMap((token) => requestNewClient(props, token)),\n Effect.catchTag(\"SharedHubClientFailed\", (error) =>\n Effect.logWarning(`${label}: ${error.reason}. The next deploy asks again.`).pipe(\n Effect.as(off),\n ),\n ),\n );\n\n if (props.deploymentType === \"development\") {\n const token = Redacted.value(props.initialAccessToken);\n return token === \"\"\n ? Effect.logInfo(\n \"Convex.SharedHubClient: deploy to main once for Google sign-in on this laptop.\",\n ).pipe(Effect.as(off))\n : request(Effect.succeed(token));\n }\n\n const requestUrl = process.env[\"ACTIONS_ID_TOKEN_REQUEST_URL\"];\n const requestToken = process.env[\"ACTIONS_ID_TOKEN_REQUEST_TOKEN\"];\n // A laptop, or another CI. In a workflow, `id-token: write` gives both, so a workflow without them\n // is a mistake to show, not a normal run.\n if (!requestUrl || !requestToken) {\n return (\n process.env[\"GITHUB_ACTIONS\"] === \"true\"\n ? Effect.logWarning(`${label}: this workflow needs the permission id-token: write.`)\n : Effect.logInfo(`${label}: this run has no GitHub OIDC token.`)\n ).pipe(Effect.as(off));\n }\n return request(readOidcToken(requestUrl, requestToken));\n};\n\n/** The client after a read on the hub, or undefined when the hub says that the client is gone. */\nconst renew = (saved: SharedHubClientAttributes) =>\n Effect.gen(function* () {\n const { status } = yield* send(\n \"The identity hub\",\n saved.registrationClientUri,\n Redacted.value(saved.registrationAccessToken),\n { method: \"GET\" },\n );\n switch (status) {\n case 200:\n return { ...saved, status: \"on\" as const };\n case 401:\n return undefined;\n case 403:\n yield* Effect.logWarning(\n `Convex.SharedHubClient: the hub suspended the client of ${saved.redirectUri}. It keeps its values, and nobody can sign in with it.`,\n );\n return { ...saved, status: \"suspended\" as const };\n default:\n return yield* new SharedHubClientFailed({\n reason: `The identity hub answered HTTP ${status}`,\n });\n }\n }).pipe(\n Effect.catchTag(\"SharedHubClientFailed\", (error) =>\n Effect.logWarning(\n `Convex.SharedHubClient: ${saved.redirectUri} keeps the saved client: ${error.reason}.`,\n ).pipe(Effect.as(saved)),\n ),\n );\n\n/** A saved row of this resource, whose `attr` `savedAttributes` reads. */\nconst SavedRow = Schema.Struct({\n resourceType: Schema.Literal(\"IdentityHub.SharedClient\"),\n attr: Schema.Unknown,\n});\n\n/**\n * The attributes of the client `id` in another stage of this stack, such as\n * `read(\"HubClient\", { stage: \"prod\" })` in the stage of a laptop, as Outputs: the\n * `initialAccessToken` and the `repository` of the production client. Without that row, the off\n * attributes, with `deploymentType: \"production\"`.\n *\n * `SharedHubClient.ref` is not enough: Alchemy fails the plan with `InvalidReferenceError` when the\n * row is missing, and a laptop before the first deploy to main has none. This read runs when the\n * plan resolves it, with the State service of the running stack and its `get`, so it never creates\n * or upgrades a store. The store revives each secret as `Redacted`, as it does for every read.\n */\nconst read = (id: string, options: { readonly stage: string }) =>\n Output.fromEffect(\n Effect.gen(function* () {\n const stack = yield* Stack;\n const state = yield* yield* State;\n const row = yield* state\n .get({ stack: stack.name, stage: options.stage, fqn: id })\n .pipe(Effect.orDie);\n if (row === undefined) return offAttributes(\"production\");\n const { attr } = yield* Schema.decodeUnknownEffect(SavedRow)(row).pipe(\n Effect.catchCause(() =>\n Effect.die(\n new SharedHubClientFailed({\n reason: `the row ${id} of stage ${options.stage} is not a Convex.SharedHubClient`,\n }),\n ),\n ),\n );\n const saved = yield* savedAttributes(attr);\n if (saved === undefined) {\n return yield* Effect.die(\n new SharedHubClientFailed({\n reason: `the row ${id} of stage ${options.stage} holds attributes that this version cannot read`,\n }),\n );\n }\n return saved;\n }),\n );\n\n/**\n * The three values that the app's deployment gets, such as `HUB_ISSUER`, `HUB_CLIENT_ID`, and\n * `HUB_CLIENT_SECRET`, as Outputs: the client's values when `status` is `on`, else the off values,\n * so that a suspended client gives guest sign-in and keeps its values in state.\n */\nconst variables = (client: SharedHubClient) =>\n Output.map(Output.of(client), (attributes) =>\n attributes.status === \"on\"\n ? {\n issuer: attributes.issuer,\n clientId: attributes.clientId,\n clientSecret: attributes.clientSecret,\n }\n : {\n issuer: OFF_VALUES.issuer,\n clientId: OFF_VALUES.clientId,\n clientSecret: Redacted.make(OFF_VALUES.clientSecret),\n },\n );\n\nexport type SharedHubClient = Resource<\n \"IdentityHub.SharedClient\",\n SharedHubClientProps,\n SharedHubClientAttributes,\n never,\n Providers\n>;\nexport const SharedHubClient = Resource<SharedHubClient>(\"IdentityHub.SharedClient\")({\n read,\n variables,\n});\n\nexport const SharedHubClientProvider = () =>\n Provider.succeed(SharedHubClient, {\n // Always an update, so that each deploy reaches the hub. No attribute is stable, so a push\n // reads the values that this run ended with. The same values cause no other push.\n diff: ({ news }) =>\n Effect.succeed(isResolved(news) ? ({ action: \"update\" } as const) : undefined),\n\n read: ({ output }) => Effect.succeed(output),\n\n // Only a client of the same return address and type is renewed. A saved row without this\n // version's shape, such as one that 0.26 or earlier saved, is no client: the run asks for a new\n // one, which replaces the saved client, and for a production client also replaces the app's\n // initial access token.\n reconcile: Effect.fn(function* ({ news, output }) {\n const saved =\n output === undefined || output.status === \"off\"\n ? undefined\n : yield* savedAttributes(output);\n if (output !== undefined && output.status !== \"off\" && saved === undefined) {\n yield* Effect.logWarning(\n `Convex.SharedHubClient: the saved attributes of ${news.app} on ${news.redirectUri} cannot be read by this version, so this run registers a new client, which replaces the saved one.`,\n );\n }\n const renewed =\n saved?.redirectUri === news.redirectUri && saved.deploymentType === news.deploymentType\n ? yield* renew(saved)\n : undefined;\n return renewed ?? (yield* newClient(news));\n }),\n\n // A failed delete does not stop a destroy: the removal date of a preview or development client\n // cleans it up. A destroy can leave a production deployment running, so its client stays.\n delete: ({ output }) =>\n output.deploymentType === \"production\" || output.status === \"off\"\n ? Effect.void\n : Effect.gen(function* () {\n const { status } = yield* send(\n \"The identity hub\",\n output.registrationClientUri,\n Redacted.value(output.registrationAccessToken),\n { method: \"DELETE\" },\n );\n // 401: the client is gone already.\n if (status !== 204 && status !== 401) {\n return yield* new SharedHubClientFailed({\n reason: `The identity hub answered HTTP ${status}`,\n });\n }\n }).pipe(\n Effect.catchTag(\"SharedHubClientFailed\", (error) =>\n Effect.logWarning(\n `Convex.SharedHubClient: the client of ${output.redirectUri} stays on the hub until its removal date: ${error.reason}.`,\n ),\n ),\n ),\n });\n","// The provider collection users merge into a stack:\n// providers: Layer.mergeAll(Cloudflare.providers(), Convex.providers())\nimport * as Provider from \"alchemy/Provider\";\nimport * as Layer from \"effect/Layer\";\nimport { type Credentials, fromConfig } from \"./Credentials.ts\";\nimport { DeploymentProvider, DeploymentResource } from \"./Deployment.ts\";\nimport { HubClient, HubClientProvider } from \"./HubClient.ts\";\nimport { ManagementApiLive } from \"./ManagementApi.ts\";\nimport { Project, ProjectProvider } from \"./Project.ts\";\nimport { SharedHubClient, SharedHubClientProvider } from \"./SharedHubClient.ts\";\n\nexport class Providers extends Provider.ProviderCollection<Providers>()(\"Convex\") {}\n\n/**\n * The Convex resource providers. Pass a credentials layer to override the\n * default lookup (`CONVEX_ACCESS_TOKEN`, then the Convex CLI login, and\n * `CONVEX_DEPLOY_KEY`).\n */\nexport const providers = (credentials: Layer.Layer<Credentials> = fromConfig()) =>\n Layer.effect(\n Providers,\n Provider.collection([Project, DeploymentResource, HubClient, SharedHubClient]),\n ).pipe(\n Layer.provide(\n Layer.mergeAll(\n ProjectProvider(),\n DeploymentProvider(),\n HubClientProvider(),\n SharedHubClientProvider(),\n ),\n ),\n Layer.provide(ManagementApiLive()),\n Layer.provide(credentials),\n Layer.orDie,\n );\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/Deployment.ts
CHANGED
|
@@ -113,8 +113,11 @@ export type DeploymentProps =
|
|
|
113
113
|
/** A dev deployment or a preview deployment that this resource creates. Cannot change. */
|
|
114
114
|
readonly type: "dev" | "preview";
|
|
115
115
|
/**
|
|
116
|
-
* The dev reference, such as `staging`, or the
|
|
117
|
-
*
|
|
116
|
+
* The dev reference, such as `staging`, or the Convex name of a dev
|
|
117
|
+
* deployment that exists, such as `happy-animal-123` from `.env.local`,
|
|
118
|
+
* which `adopt` takes over; a name that matches nothing creates a dev
|
|
119
|
+
* deployment with that reference. Or the preview name, such as the
|
|
120
|
+
* stage name. Unique in the project. Cannot change.
|
|
118
121
|
*/
|
|
119
122
|
readonly name: string;
|
|
120
123
|
});
|
|
@@ -334,7 +337,9 @@ const checkIdentity = (current: DeploymentTarget, news: DeploymentProps) => {
|
|
|
334
337
|
}
|
|
335
338
|
if (current.type !== news.type) return change("type", current.type, news.type);
|
|
336
339
|
const currentName = nameOf(current);
|
|
337
|
-
|
|
340
|
+
// A dev deployment is also named by its Convex name, as `findByName` finds it.
|
|
341
|
+
const named = news.type === "dev" && current.name === news.name;
|
|
342
|
+
if (news.type !== "prod" && currentName !== news.name && !named) {
|
|
338
343
|
return change("name", currentName ?? "(none)", news.name);
|
|
339
344
|
}
|
|
340
345
|
return Effect.void;
|
|
@@ -454,8 +459,10 @@ export const DeploymentProvider = () =>
|
|
|
454
459
|
);
|
|
455
460
|
|
|
456
461
|
/**
|
|
457
|
-
* The one dev deployment with this reference, or the
|
|
458
|
-
* deployment with this preview name, or undefined.
|
|
462
|
+
* The one dev deployment with this reference or this Convex name, or the
|
|
463
|
+
* one preview deployment with this preview name, or undefined. The name
|
|
464
|
+
* form takes the dev deployment that `npx convex dev` made, such as the
|
|
465
|
+
* one `.env.local` selects, which has a reference the stack never chose.
|
|
459
466
|
*/
|
|
460
467
|
const findByName = (
|
|
461
468
|
management: ManagementApiService,
|
|
@@ -467,7 +474,9 @@ export const DeploymentProvider = () =>
|
|
|
467
474
|
const matches = (yield* listDeployments(management, projectId, type)).filter(
|
|
468
475
|
(row) =>
|
|
469
476
|
row.deploymentType === type &&
|
|
470
|
-
(type === "dev"
|
|
477
|
+
(type === "dev"
|
|
478
|
+
? row.reference === name || row.name === name
|
|
479
|
+
: row.previewIdentifier === name),
|
|
471
480
|
);
|
|
472
481
|
if (matches.length > 1) {
|
|
473
482
|
return yield* new AmbiguousDeployment({
|
|
@@ -503,10 +512,11 @@ export const DeploymentProvider = () =>
|
|
|
503
512
|
),
|
|
504
513
|
);
|
|
505
514
|
const reference = info.reference ?? "";
|
|
515
|
+
// A dev address names the reference or the Convex name, as `findByName` finds it.
|
|
506
516
|
if (
|
|
507
517
|
info.projectId !== address.projectId ||
|
|
508
518
|
info.deploymentType !== address.type ||
|
|
509
|
-
(address.type === "dev" && reference !== address.name)
|
|
519
|
+
(address.type === "dev" && reference !== address.name && deployment !== address.name)
|
|
510
520
|
) {
|
|
511
521
|
return undefined;
|
|
512
522
|
}
|
package/src/SharedHubClient.ts
CHANGED
|
@@ -140,7 +140,11 @@ const offAttributes = (
|
|
|
140
140
|
class SharedHubClientFailed extends Schema.TaggedError<SharedHubClientFailed>()(
|
|
141
141
|
"SharedHubClientFailed",
|
|
142
142
|
{ reason: Schema.String },
|
|
143
|
-
) {
|
|
143
|
+
) {
|
|
144
|
+
override get message() {
|
|
145
|
+
return `Convex.SharedHubClient: ${this.reason}.`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
144
148
|
|
|
145
149
|
const OidcToken = Schema.Struct({ value: Schema.NonEmptyString });
|
|
146
150
|
|
package/src/cli.ts
CHANGED
|
File without changes
|