Skip to content

Commit 3bdf39f

Browse files
committed
refactor(stack): remove redundant runtime mechanisms
1 parent db7ab4e commit 3bdf39f

24 files changed

Lines changed: 405 additions & 666 deletions

docs/adr/0017-simplified-managed-stack-architecture.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ and previously persisted sticky automatic ports remain hard failures and are
7272
never silently moved.
7373

7474
Every managed document records one concrete runtime selection. Native and
75-
container runtimes never mix. Omission defaults to Docker; callers may
76-
explicitly select Docker or Podman. There is no probing or auto-detection;
75+
container runtimes never mix. An omitted runtime selects native; when a
76+
container runtime is selected, an omitted engine defaults to Docker. Callers
77+
may explicitly select Docker or Podman. There is no probing or auto-detection;
7778
Podman is supported only on local Linux hosts. Persisted state records the
7879
resolved exact engine. Capability releases and
7980
workload artifacts are persisted as exact version pins (including their

packages/stack/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,6 @@ non-PostgreSQL capability lazy. `followLogs(...)` provides filterable live entri
4141
stateless client-polled cursor.
4242

4343
Database reset is intentionally outside the current API. Applying migrations, declarative schemas,
44-
and seeds remains the caller's responsibility.
44+
and seeds remains the caller's responsibility. The runtime bootstrap only reconciles the `_realtime`
45+
schema owner, closed database role passwords, and JWT settings in one transaction; the slim database
46+
artifact owns its initialization and migrations.

packages/stack/src/model/Compiler.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -459,9 +459,8 @@ const releaseFor = <T>(
459459

460460
const enabledSettings = (
461461
name: CapabilityName,
462-
capabilities: Readonly<Record<string, unknown>>,
462+
raw: unknown,
463463
): { enabled: boolean; activation: "eager" | "lazy"; settings: unknown; raw: unknown } => {
464-
const raw = capabilities[name];
465464
if (name === "database")
466465
return { enabled: true, activation: "eager", settings: extract(raw, "settings") ?? {}, raw };
467466
if (raw === undefined || raw === null) {
@@ -505,7 +504,7 @@ const materializeCapability = <T>(
505504
InvalidStackConfigError | StackVersionUnsupportedError,
506505
Path.Path
507506
> => {
508-
const selected = enabledSettings(module.name, { [module.name]: raw });
507+
const selected = enabledSettings(module.name, raw);
509508
const mergedInput = merge(module.defaultSettings, selected.settings);
510509
const materialized = module.materialize?.(mergedInput, projectRoot) ?? mergedInput;
511510
const normalized = normalizeFunctions
Lines changed: 61 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,19 @@
1-
import { Data, Effect, Redacted, Schema } from "effect";
1+
import { Data, Effect, Redacted } from "effect";
22

33
/** A small runtime-neutral SQL boundary used by the internal database bootstrap. */
44
export type DatabaseSqlValue = string | number | boolean | null;
55

6-
interface DatabaseRow {
7-
readonly [column: string]: unknown;
8-
}
9-
106
/** Login roles provisioned by the managed database template. */
11-
type DatabaseBootstrapRole =
12-
| "postgres"
13-
| "authenticator"
14-
| "pgbouncer"
15-
| "supabase_auth_admin"
16-
| "supabase_storage_admin"
17-
| "supabase_replication_admin"
18-
| "supabase_read_only_user";
19-
const DATABASE_BOOTSTRAP_ROLES: ReadonlyArray<DatabaseBootstrapRole> = [
7+
const DATABASE_BOOTSTRAP_ROLES = [
208
"postgres",
219
"authenticator",
2210
"pgbouncer",
2311
"supabase_auth_admin",
2412
"supabase_storage_admin",
2513
"supabase_replication_admin",
2614
"supabase_read_only_user",
27-
];
15+
] as const;
16+
type DatabaseBootstrapRole = (typeof DATABASE_BOOTSTRAP_ROLES)[number];
2817

2918
export type DatabaseBootstrapSetting =
3019
| {
@@ -36,28 +25,17 @@ export type DatabaseBootstrapSetting =
3625
readonly value: number;
3726
};
3827

39-
/** Values come from resolved managed secret slots and never become SQL text. */
40-
export interface DatabaseBootstrapCredentials {
41-
readonly roles?: Readonly<Partial<Record<DatabaseBootstrapRole, Redacted.Redacted<string>>>>;
42-
}
43-
44-
interface DatabaseBootstrapSettings {
28+
export interface DatabaseBootstrapOptions {
29+
/** One managed password shared by the closed login roles. */
30+
readonly databasePassword: Redacted.Redacted<string>;
31+
/** Managed JWT material applied to the database settings on each invocation. */
4532
readonly jwtSecret: Redacted.Redacted<string>;
4633
readonly jwtExpiry: number;
4734
}
4835

49-
export interface DatabaseBootstrapOptions {
50-
/** Ordered plan resolved for the pinned database release. */
51-
readonly revisions: ReadonlyArray<DatabaseBootstrapRevision>;
52-
readonly credentials?: DatabaseBootstrapCredentials;
53-
/** Configuration values are reconciled on every invocation, like role passwords. */
54-
readonly settings?: DatabaseBootstrapSettings;
55-
}
56-
5736
export class DatabaseBootstrapError extends Data.TaggedError("DatabaseBootstrapError")<{
5837
readonly message: string;
5938
readonly statement?: string;
60-
readonly revision?: string;
6139
/** Whether retrying the database operation may succeed once the server settles. */
6240
readonly retryable?: boolean;
6341
readonly cause?: unknown;
@@ -80,10 +58,6 @@ export interface DatabaseTransaction {
8058
readonly setDatabaseSetting: (
8159
setting: DatabaseBootstrapSetting,
8260
) => Effect.Effect<void, DatabaseBootstrapError>;
83-
readonly query: (
84-
statement: string,
85-
parameters?: ReadonlyArray<DatabaseSqlValue>,
86-
) => Effect.Effect<ReadonlyArray<DatabaseRow>, DatabaseBootstrapError>;
8761
}
8862

8963
export interface DatabaseSession {
@@ -97,157 +71,69 @@ export interface DatabaseSession {
9771
) => Effect.Effect<void, DatabaseBootstrapError>;
9872
}
9973

100-
interface DatabaseBootstrapRevision {
101-
readonly id: string;
102-
readonly statement: string;
103-
}
104-
105-
const TRACKING_SCHEMA = "supabase_internal";
106-
const TRACKING_TABLE = `${TRACKING_SCHEMA}.bootstrap_revisions`;
107-
108-
const TRACKING_SCHEMA_STATEMENT = `CREATE SCHEMA IF NOT EXISTS ${TRACKING_SCHEMA};`;
109-
const TRACKING_TABLE_STATEMENT = `CREATE TABLE IF NOT EXISTS ${TRACKING_TABLE} (
110-
revision text PRIMARY KEY,
111-
applied_at timestamptz NOT NULL DEFAULT now()
112-
);`;
113-
const APPLIED_REVISIONS_STATEMENT = `
114-
SELECT revision FROM ${TRACKING_TABLE} ORDER BY revision;
115-
`;
116-
const RECORD_REVISION_STATEMENT = `
117-
INSERT INTO ${TRACKING_TABLE} (revision) VALUES ($1)
118-
ON CONFLICT (revision) DO NOTHING;
119-
`;
74+
const REALTIME_SCHEMA_STATEMENT =
75+
"CREATE SCHEMA IF NOT EXISTS _realtime;\nALTER SCHEMA _realtime OWNER TO postgres;";
12076
const ADVISORY_LOCK_STATEMENT = `SELECT pg_advisory_xact_lock(hashtext('supabase_internal.bootstrap'));`;
12177

122-
const RevisionRowSchema = Schema.Struct({ revision: Schema.String });
123-
124-
const statementError = (error: DatabaseBootstrapError, statement: string, revision?: string) =>
78+
const statementError = (error: DatabaseBootstrapError, statement: string) =>
12579
new DatabaseBootstrapError({
12680
message: error.message,
12781
statement,
128-
...(revision === undefined ? {} : { revision }),
12982
...(error.retryable === undefined ? {} : { retryable: error.retryable }),
13083
...(error.cause === undefined ? {} : { cause: error.cause }),
13184
});
13285

133-
/** Runs all unapplied internal revisions, recording each only after it succeeds. */
86+
/** Reconciles the runtime-owned schema, roles, and settings in one transaction. */
13487
export const runDatabaseBootstrap = (
13588
session: DatabaseSession,
13689
options: DatabaseBootstrapOptions,
13790
): Effect.Effect<void, DatabaseBootstrapError> =>
138-
Effect.gen(function* () {
139-
const ids = new Set<string>();
140-
for (const revision of options.revisions) {
141-
if (revision.id.trim().length === 0 || ids.has(revision.id))
142-
return yield* new DatabaseBootstrapError({
143-
message: "Database bootstrap revision ids must be non-empty and unique",
144-
revision: revision.id,
145-
});
146-
ids.add(revision.id);
147-
}
148-
yield* session.transaction((transaction) =>
149-
Effect.gen(function* () {
150-
yield* transaction
151-
.execute(ADVISORY_LOCK_STATEMENT)
152-
.pipe(Effect.mapError((error) => statementError(error, ADVISORY_LOCK_STATEMENT)));
153-
yield* transaction
154-
.execute(TRACKING_SCHEMA_STATEMENT)
155-
.pipe(Effect.mapError((error) => statementError(error, TRACKING_SCHEMA_STATEMENT)));
156-
yield* transaction
157-
.execute(TRACKING_TABLE_STATEMENT)
158-
.pipe(Effect.mapError((error) => statementError(error, TRACKING_TABLE_STATEMENT)));
159-
}),
160-
);
161-
for (const revision of options.revisions) {
162-
yield* session.transaction((transaction) =>
163-
Effect.gen(function* () {
164-
// Re-check under a transaction-scoped advisory lock. This prevents
165-
// two owners from both applying a non-idempotent revision after
166-
// observing the same pre-lock snapshot.
167-
yield* transaction
168-
.execute(ADVISORY_LOCK_STATEMENT)
169-
.pipe(Effect.mapError((error) => statementError(error, ADVISORY_LOCK_STATEMENT)));
170-
const rows = yield* transaction
171-
.query(APPLIED_REVISIONS_STATEMENT)
172-
.pipe(Effect.mapError((error) => statementError(error, APPLIED_REVISIONS_STATEMENT)));
173-
const applied = new Set<string>();
174-
for (const row of rows) {
175-
const decoded = yield* Schema.decodeUnknownEffect(RevisionRowSchema)(row).pipe(
176-
Effect.mapError(
177-
(cause) =>
178-
new DatabaseBootstrapError({
179-
message: `Bootstrap tracking row is malformed: ${String(cause)}`,
180-
statement: APPLIED_REVISIONS_STATEMENT,
181-
}),
182-
),
183-
);
184-
applied.add(decoded.revision);
185-
}
186-
if (applied.has(revision.id)) return;
187-
yield* transaction
188-
.execute(revision.statement)
189-
.pipe(
190-
Effect.mapError((error) => statementError(error, revision.statement, revision.id)),
191-
);
192-
yield* transaction
193-
.execute(RECORD_REVISION_STATEMENT, [revision.id])
194-
.pipe(
195-
Effect.mapError((error) =>
196-
statementError(error, RECORD_REVISION_STATEMENT, revision.id),
197-
),
198-
);
199-
}),
200-
);
201-
}
202-
if (options.credentials?.roles !== undefined || options.settings !== undefined) {
203-
yield* session.transaction((transaction) =>
204-
Effect.gen(function* () {
205-
yield* transaction
206-
.execute(ADVISORY_LOCK_STATEMENT)
207-
.pipe(Effect.mapError((error) => statementError(error, ADVISORY_LOCK_STATEMENT)));
208-
if (options.credentials?.roles !== undefined) {
209-
for (const role of DATABASE_BOOTSTRAP_ROLES) {
210-
const password = options.credentials.roles[role];
211-
if (password === undefined) continue;
212-
yield* transaction.setRolePassword(role, password).pipe(
213-
Effect.mapError(
214-
() =>
215-
new DatabaseBootstrapError({
216-
message: `Unable to configure internal database role ${role}`,
217-
}),
218-
),
219-
);
220-
}
221-
}
222-
if (options.settings !== undefined) {
223-
yield* transaction
224-
.setDatabaseSetting({
225-
name: "app.settings.jwt_secret",
226-
value: options.settings.jwtSecret,
227-
})
228-
.pipe(
229-
Effect.mapError(
230-
() =>
231-
new DatabaseBootstrapError({
232-
message: "Unable to configure database JWT secret",
233-
}),
234-
),
235-
);
236-
yield* transaction
237-
.setDatabaseSetting({
238-
name: "app.settings.jwt_exp",
239-
value: options.settings.jwtExpiry,
240-
})
241-
.pipe(
242-
Effect.mapError(
243-
() =>
244-
new DatabaseBootstrapError({
245-
message: "Unable to configure database JWT expiry",
246-
}),
247-
),
248-
);
249-
}
250-
}),
251-
);
252-
}
253-
});
91+
session.transaction((transaction) =>
92+
Effect.gen(function* () {
93+
yield* transaction
94+
.execute(ADVISORY_LOCK_STATEMENT)
95+
.pipe(Effect.mapError((error) => statementError(error, ADVISORY_LOCK_STATEMENT)));
96+
yield* transaction
97+
.execute(REALTIME_SCHEMA_STATEMENT)
98+
.pipe(Effect.mapError((error) => statementError(error, REALTIME_SCHEMA_STATEMENT)));
99+
for (const role of DATABASE_BOOTSTRAP_ROLES) {
100+
yield* transaction.setRolePassword(role, options.databasePassword).pipe(
101+
Effect.mapError(
102+
(error) =>
103+
new DatabaseBootstrapError({
104+
message: `Unable to configure internal database role ${role}`,
105+
...(error.retryable === undefined ? {} : { retryable: error.retryable }),
106+
}),
107+
),
108+
);
109+
}
110+
yield* transaction
111+
.setDatabaseSetting({
112+
name: "app.settings.jwt_secret",
113+
value: options.jwtSecret,
114+
})
115+
.pipe(
116+
Effect.mapError(
117+
(error) =>
118+
new DatabaseBootstrapError({
119+
message: "Unable to configure database JWT secret",
120+
...(error.retryable === undefined ? {} : { retryable: error.retryable }),
121+
}),
122+
),
123+
);
124+
yield* transaction
125+
.setDatabaseSetting({
126+
name: "app.settings.jwt_exp",
127+
value: options.jwtExpiry,
128+
})
129+
.pipe(
130+
Effect.mapError(
131+
(error) =>
132+
new DatabaseBootstrapError({
133+
message: "Unable to configure database JWT expiry",
134+
...(error.retryable === undefined ? {} : { retryable: error.retryable }),
135+
}),
136+
),
137+
);
138+
}),
139+
);

0 commit comments

Comments
 (0)