Skip to content
This repository was archived by the owner on Oct 31, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,3 @@
node_modules
.env
.alchemy/
dist
.tanstack
22 changes: 4 additions & 18 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,15 @@
# AGENTS.md

A Convex app that mounts the identity-broker and alchemyState components: the base app of one team,
which serves its sign-in hub and its Alchemy state. See README.md. Each component's own rules live
in its repository.
A Convex app that mounts the identity-broker component: the sign-in hub of one team. See README.md.
Each component's own rules live in its repository.

- `convex/connectors.ts` lists the providers the hub offers. A provider that needs credentials also
gets its variables declared in `convex/convex.config.ts`.
- `convex/broker.ts` defines the broker, `convex/http.ts` mounts it, `convex/identityBroker.ts`
exports the admin functions for `npx convex run`, and `convex/crons.ts` runs the cleanup.
- `convex/state.ts`, `convex/reader.ts`, and `convex/auth.config.ts` serve the Alchemy state store,
from the exports of `@samebase/alchemy-convex`. A push replaces every function, so a function that
leaves these files leaves every stack of the team without its state.
- `convex/http.ts` mounts the broker; `convex/identityBroker.ts` exports the admin functions for
`npx convex run`; `convex/crons.ts` runs the cleanup.
- `alchemy.run.ts` is the stack `samebase/team-base` (adopted project, adopted production
deployment, push); `scripts/check-keys.ts` checks the key set inside the `deploy` script the push
runs and never creates it; `scripts/init-keys.ts` creates it once, by hand.
- `src/` is the console of the base, a TanStack Start site that `convex/http.ts` serves through
convex-hosting. `convex/console.ts` holds its reads: each one needs a session, and the state comes
through the redacted `reader.repository`, so a secret never reaches the browser. A page cannot use
the paths of the hub or of Convex Auth: `/authorize`, `/continue`, `/token`, `/userinfo`,
`/connectors/*`, `/.well-known/*`, `/auth/*`.
- The console has no sign-up. Run `authAdmin:createOrUpdatePasswordAccount` from the Functions page
of the Convex dashboard, with the email and password you choose, not in a terminal. To end an
account's access at once, delete its row in `users`. A new password does not end its sessions.
- `pnpm run deploy:dev` builds, uploads, and pushes to the development deployment. It must run once
before `pnpm run dev`: `convex/http.ts` imports the build.
- Never use `npx convex import --replace-all` on this deployment, also not with `--component`: it
deletes the rows of the other components.
- Never use the U+2014 em dash character. No `any`, no type assertions except `as const`.
Expand Down
59 changes: 20 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
# team-base

The base app of the Samebase team on [Convex](https://convex.dev): the code of the production
deployment of the project `samebase`, which holds the team-level components.

- `identityBroker`, from [identity-broker](https://github.com/samebase/identity-broker), is the
sign-in hub. It holds the team's Google and GitHub connections once and signs users in to all its
apps. It keeps no user accounts; each app creates its own user from the `id_token` it gets.
- `alchemyState`, from [alchemy-convex](https://github.com/samebase/alchemy-convex), is the Alchemy
state of every stack of the team.
The sign-in hub of the Samebase team on [Convex](https://convex.dev): the code of the production
deployment of the project `samebase`. It mounts `identityBroker` from
[identity-broker](https://github.com/samebase/identity-broker), holds the team's Google and GitHub
connections once, and signs users in to all its apps. It keeps no user accounts; each app creates
its own user from the `id_token` it gets.

The repository is public and holds no secrets. The values of the team are the constants in
`alchemy.run.ts` and the lines in `convex/connectors.ts`, so a copy of it with other values runs
another team's base.
another team's hub.

## Deploy

`alchemy.run.ts` is the stack, in the shape of every app of the team with one stage: `prod` is the
production deployment of the Convex project `samebase`, and the push is the `deploy` package script,
which checks the key set and pushes the functions. The stack is named after the repository,
`samebase/team-base`. Its state is in the deployment that it deploys, next to the state of the other
stacks of the team. The hub has no previews: no app points at one, and Google cannot sign in on one,
since only the production callback is registered at Google. `.github/workflows/alchemy.yml` deploys
each push to `main`; pull requests run the checks only.
`samebase/team-base`. Its state is in the Convex project `alchemy-state-store`, with the state of
the other stacks of the team. The hub has no previews: no app points at one, and Google cannot sign
in on one, since only the production callback is registered at Google.
`.github/workflows/alchemy.yml` deploys each push to `main`; pull requests run the checks only.

1. In the repository, set five secrets: `CONVEX_ACCESS_TOKEN`, the team's Convex access token, with
which the run mints the deploy key itself; `GOOGLE_CLIENT_ID` with `GOOGLE_CLIENT_SECRET`, the
Expand All @@ -43,21 +40,6 @@ Back up `IDENTITY_BROKER_KEYS` somewhere safe. It holds the signing keys and the
hub that loses it issues new subjects to every user of every app. An established hub that lost the
variable gets the backup restored by hand; its deploys stop until then.

## Base and seed

A push replaces every function of a deployment. `Convex.state()` of `@samebase/alchemy-convex`
pushes a small store app, a seed, to a project `samebase` that has no base, and it never pushes over
a base. Thus this app serves the same store functions as the seed: `convex/state.ts` with the host
`base`, `convex/reader.ts`, and `convex/auth.config.ts`. The `alchemyState` component has no name in
`convex/convex.config.ts`, so this app serves the rows that the seed wrote.

The store version of this base is the store version of the `@samebase/alchemy-convex` in
`package.json`. The stack and the app share that one pin. When the store version rises, this
repository moves first and each consumer follows. A consumer of the team fails on a base that is
older or newer than the consumer.

Push a base for the first time when no stack of the team runs: the push of a seed has no lock.

## Issuer

The hub answers at the deployment's `.convex.site` URL and at the custom domain `auth.samebase.com`.
Expand Down Expand Up @@ -96,7 +78,7 @@ const client =
});
```

The resource finds this base by the name of its project, and it runs `identityBroker:registerClient`
The resource finds this hub by the name of its project, and it runs `identityBroker:registerClient`
with a short-lived key that the Convex credential of the stack mints. So the app has no token of its
own. The first client of an `app` creates the app as a first-party app, which sends the user
straight to the connector without the chooser. For an app that is not the team's own, run
Expand All @@ -110,16 +92,15 @@ See the component's
## Operate

The admin functions are internal: `npx convex run --prod identityBroker:<name>`, from a checkout
that selects the project `samebase`. The deploy key of the base is an administrator credential: it
can read and replace the keys, read every secret in the Alchemy state of the team, and run every
internal function. Every stack of the team mints keys of the base with the team's Convex token, for
its state and for its hub client. These keys can run internal functions only, and they end with the
run. The team token itself reaches the base, so treat it as an administrator credential too. The
Convex CLI ignores `--prod` when `CONVEX_DEPLOY_KEY` names another deployment, so unset it before an
admin command. A cron runs the broker's cleanup daily: expired requests and codes, and sign-in
records older than 90 days. Idle preview clients are kept: the broker prunes them only when the host
sets `previewClientIdleDays`, and this hub does not, because a consumer's `Convex.HubClient` does
not register a pruned client again.
that selects the project `samebase`. The deploy key of the hub is an administrator credential: it
can read and replace the keys and run every internal function. Every stack of the team mints keys of
the hub with the team's Convex token, for its hub client. These keys can run internal functions
only, and they end with the run. The team token itself reaches the hub, so treat it as an
administrator credential too. The Convex CLI ignores `--prod` when `CONVEX_DEPLOY_KEY` names another
deployment, so unset it before an admin command. A cron runs the broker's cleanup daily: expired
requests and codes, and sign-in records older than 90 days. Idle preview clients are kept: the
broker prunes them only when the host sets `previewClientIdleDays`, and this hub does not, because a
consumer's `Convex.HubClient` does not register a pruned client again.

Never run `npx convex import --replace-all` on this deployment, also not with `--component`: it also
deletes the rows of the other components. To restore the tables of one component, import a ZIP of
Expand Down
13 changes: 6 additions & 7 deletions alchemy.run.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// The stack of the team's base app, with one stage: `prod` is the production deployment of the
// Convex project `samebase`, and nothing else is deployed. The base holds the team-level
// components of that deployment: the sign-in hub (`identityBroker`) and the Alchemy state of every
// stack of the team (`alchemyState`). The hub has no previews: no app points at one, and Google
// cannot sign in on one, since only the production callback is registered at Google. The push is
// the `deploy` package script, which checks the key set and pushes the functions.
// The stack of the team's identity hub, with one stage: `prod` is the production deployment of the
// Convex project `samebase`, and nothing else is deployed. The hub has no previews: no app points at
// one, and Google cannot sign in on one, since only the production callback is registered at Google.
// The push is the `deploy` package script, which checks the key set and pushes the functions. The
// state of this stack is in the project `alchemy-state-store`, with the state of every other stack.
//
// The stack is named after the repository, `owner/name`, as every stack of the team is. The
// repository was `samebase/identity-hub`, and this stack never reads the rows of that old stack.
Expand Down Expand Up @@ -51,7 +50,7 @@ export default Alchemy.Stack(
path: "./convex",
command: ["node", "--run", "deploy"],
// The push inputs besides the functions, package.json, and the lockfile.
include: ["scripts/**", "tsconfig*.json", "src/**", "public/**", "vite.config.ts"],
include: ["scripts/**", "tsconfig*.json"],
} satisfies Convex.Source;
// The variables of the deployment. The secret is a prop, so the state store holds it next to
// the deployment it serves.
Expand Down
16 changes: 0 additions & 16 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,10 @@
* @module
*/

import type * as auth from "../auth.js";
import type * as authAdmin from "../authAdmin.js";
import type * as broker from "../broker.js";
import type * as connectors from "../connectors.js";
import type * as console from "../console.js";
import type * as crons from "../crons.js";
import type * as http from "../http.js";
import type * as identityBroker from "../identityBroker.js";
import type * as reader from "../reader.js";
import type * as state from "../state.js";

import type {
ApiFromModules,
Expand All @@ -26,16 +20,10 @@ import type {
} from "convex/server";

declare const fullApi: ApiFromModules<{
auth: typeof auth;
authAdmin: typeof authAdmin;
broker: typeof broker;
connectors: typeof connectors;
console: typeof console;
crons: typeof crons;
http: typeof http;
identityBroker: typeof identityBroker;
reader: typeof reader;
state: typeof state;
}>;

/**
Expand Down Expand Up @@ -66,8 +54,4 @@ export declare const internal: FilterApi<

export declare const components: {
identityBroker: import("@samebase/identity-broker/_generated/component.js").ComponentApi<"identityBroker">;
alchemyState: import("@samebase/alchemy-convex/state/_generated/component.js").ComponentApi<"alchemyState">;
auth: import("@convex-dev/auth/_generated/component.js").ComponentApi<"auth">;
authPassword: import("@convex-dev/auth/components/password/_generated/component.js").ComponentApi<"authPassword">;
hosting: import("@samebase/convex-hosting/_generated/component.js").ComponentApi<"hosting">;
};
34 changes: 16 additions & 18 deletions convex/_generated/dataModel.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,29 @@
* @module
*/

import type {
DataModelFromSchemaDefinition,
DocumentByName,
TableNamesInDataModel,
SystemTableNames,
} from "convex/server";
import { AnyDataModel } from "convex/server";
import type { GenericId } from "convex/values";
import schema from "../schema.js";

/**
* No `schema.ts` file found!
*
* This generated code has permissive types like `Doc = any` because
* Convex doesn't know your schema. If you'd like more type safety, see
* https://docs.convex.dev/using/schemas for instructions on how to add a
* schema file.
*
* After you change a schema, rerun codegen with `npx convex dev`.
*/

/**
* The names of all of your Convex tables.
*/
export type TableNames = TableNamesInDataModel<DataModel>;
export type TableNames = string;

/**
* The type of a document stored in Convex.
*
* @typeParam TableName - A string literal type of the table name (like "users").
*/
export type Doc<TableName extends TableNames> = DocumentByName<
DataModel,
TableName
>;
export type Doc = any;

/**
* An identifier for a document in Convex.
Expand All @@ -42,10 +42,8 @@ export type Doc<TableName extends TableNames> = DocumentByName<
*
* IDs are just strings at runtime, but this type can be used to distinguish them from other
* strings when type checking.
*
* @typeParam TableName - A string literal type of the table name (like "users").
*/
export type Id<TableName extends TableNames | SystemTableNames> =
export type Id<TableName extends TableNames = TableNames> =
GenericId<TableName>;

/**
Expand All @@ -57,4 +55,4 @@ export type Id<TableName extends TableNames | SystemTableNames> =
* This type is used to parameterize methods like `queryGeneric` and
* `mutationGeneric` to make them type-safe.
*/
export type DataModel = DataModelFromSchemaDefinition<typeof schema>;
export type DataModel = AnyDataModel;
2 changes: 0 additions & 2 deletions convex/_generated/server.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ type Env = {
readonly CONVEX_SITE_URL: string;
readonly AUTH_GITHUB_CLIENT_ID: string;
readonly AUTH_GITHUB_CLIENT_SECRET: string;
readonly AUTH_JWKS: string;
readonly AUTH_PRIVATE_KEY: string;
readonly GOOGLE_CLIENT_ID: string;
readonly GOOGLE_CLIENT_SECRET: string;
readonly IDENTITY_BROKER_KEYS: string;
Expand Down
18 changes: 0 additions & 18 deletions convex/auth.config.ts

This file was deleted.

94 changes: 0 additions & 94 deletions convex/auth.ts

This file was deleted.

Loading
Loading