@rebasepro/server-postgres 0.25.1 → 0.26.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/dist/{BranchService-ClklxyAQ.js → BranchService-CWBnDhcv.js} +25 -9
- package/dist/BranchService-CWBnDhcv.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +13 -0
- package/dist/PostgresBootstrapper.d.ts +7 -0
- package/dist/auth/services.d.ts +8 -1
- package/dist/auth/verify-auth-tables.d.ts +21 -0
- package/dist/{backup-cli-gijeXe0u.js → backup-cli-PoRG-zEq.js} +3 -3
- package/dist/{backup-cli-gijeXe0u.js.map → backup-cli-PoRG-zEq.js.map} +1 -1
- package/dist/{boot-ddl-DYH95Qeg.js → boot-ddl-EsgqDkx0.js} +2 -2
- package/dist/{boot-ddl-DYH95Qeg.js.map → boot-ddl-EsgqDkx0.js.map} +1 -1
- package/dist/branch-diff-CxE4VlxT.js +1396 -0
- package/dist/branch-diff-CxE4VlxT.js.map +1 -0
- package/dist/branch-diff-report-DpvVf7hR.js +48 -0
- package/dist/branch-diff-report-DpvVf7hR.js.map +1 -0
- package/dist/branching/branch-diff-report.d.ts +7 -0
- package/dist/branching/branch-diff.d.ts +115 -0
- package/dist/{cli-console-CQrQ0lAY.js → cli-console-f0k6_TsJ.js} +38 -5
- package/dist/cli-console-f0k6_TsJ.js.map +1 -0
- package/dist/cli-console.d.ts +17 -1
- package/dist/{cli-errors-DiA0XFIS.js → cli-errors-DsYyNYzd.js} +53 -2
- package/dist/cli-errors-DsYyNYzd.js.map +1 -0
- package/dist/cli.js +102 -122
- package/dist/cli.js.map +1 -1
- package/dist/{collections-schema-version-T6cGvrDi.js → collections-schema-version-D7bDI0PJ.js} +13 -9
- package/dist/collections-schema-version-D7bDI0PJ.js.map +1 -0
- package/dist/{deferred-index-build-BE0I6sYU.js → deferred-index-build-CqaOv1Xx.js} +3 -3
- package/dist/{deferred-index-build-BE0I6sYU.js.map → deferred-index-build-CqaOv1Xx.js.map} +1 -1
- package/dist/{doctor-BT--1kq3.js → doctor-DW_mlTzz.js} +81 -33
- package/dist/doctor-DW_mlTzz.js.map +1 -0
- package/dist/{ensure-collection-policies-BnGOURpG.js → ensure-collection-policies-DLWgabQ2.js} +5 -5
- package/dist/{ensure-collection-policies-BnGOURpG.js.map → ensure-collection-policies-DLWgabQ2.js.map} +1 -1
- package/dist/{ensure-collection-tables-Cet4650m.js → ensure-collection-tables-CFIRq8ni.js} +5 -5
- package/dist/{ensure-collection-tables-Cet4650m.js.map → ensure-collection-tables-CFIRq8ni.js.map} +1 -1
- package/dist/{ensure-tables-l_OgFQjN.js → ensure-tables-4nA-tfwi.js} +4 -4
- package/dist/{ensure-tables-l_OgFQjN.js.map → ensure-tables-4nA-tfwi.js.map} +1 -1
- package/dist/{enum-labels-kept-nheku1pB.js → enum-labels-kept-PcPW65xy.js} +2 -2
- package/dist/{enum-labels-kept-nheku1pB.js.map → enum-labels-kept-PcPW65xy.js.map} +1 -1
- package/dist/history/HistoryService.d.ts +7 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.es.js +226 -8
- package/dist/index.es.js.map +1 -1
- package/dist/{db-subcommands-BPoHCxJR.js → migrate-baseline-B-2Ef4Mv.js} +266 -41
- package/dist/migrate-baseline-B-2Ef4Mv.js.map +1 -0
- package/dist/{migrate-policy-stamps-3eqfhes6.js → migrate-policy-stamps-BQtFHApx.js} +5 -5
- package/dist/{migrate-policy-stamps-3eqfhes6.js.map → migrate-policy-stamps-BQtFHApx.js.map} +1 -1
- package/dist/{open-rebase-EmqVmS14.js → open-rebase-C8jQ6s5a.js} +292 -46
- package/dist/open-rebase-C8jQ6s5a.js.map +1 -0
- package/dist/open-rebase.d.ts +9 -1
- package/dist/{pg-error-utils-CFEnm2m9.js → pg-error-utils-DhiUTlVx.js} +2 -2
- package/dist/{pg-error-utils-CFEnm2m9.js.map → pg-error-utils-DhiUTlVx.js.map} +1 -1
- package/dist/{push-index-prebuild-COd7i6wm.js → push-index-prebuild-UTvB5pqH.js} +3 -3
- package/dist/{push-index-prebuild-COd7i6wm.js.map → push-index-prebuild-UTvB5pqH.js.map} +1 -1
- package/dist/push-kept-defaults-BGOlOqRQ.js +137 -0
- package/dist/push-kept-defaults-BGOlOqRQ.js.map +1 -0
- package/dist/{push-legacy-renames-CLgYYwPE.js → push-legacy-renames-BwaKi8xF.js} +3 -3
- package/dist/{push-legacy-renames-CLgYYwPE.js.map → push-legacy-renames-BwaKi8xF.js.map} +1 -1
- package/dist/{push-policy-plan-D2gpQsdv.js → push-policy-plan-DQ2tWceD.js} +2 -2
- package/dist/{push-policy-plan-D2gpQsdv.js.map → push-policy-plan-DQ2tWceD.js.map} +1 -1
- package/dist/{rls-enforcement-DDyNoOSy.js → rls-enforcement-BOHuHVlc.js} +42 -3
- package/dist/rls-enforcement-BOHuHVlc.js.map +1 -0
- package/dist/schema/destructive-sql.d.ts +16 -0
- package/dist/schema/doctor-cli.js +3 -3
- package/dist/schema/introspect-db.js +1 -1
- package/dist/schema/migration-freshness.d.ts +69 -0
- package/dist/schema/push-kept-defaults.d.ts +57 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +47 -10
- package/dist/security/rls-enforcement.d.ts +18 -0
- package/dist/services/FetchService.d.ts +12 -0
- package/dist/services/channel-presence.d.ts +3 -1
- package/dist/services/nested-path.d.ts +16 -0
- package/dist/services/realtimeService.d.ts +18 -1
- package/dist/{websocket-BKZwsjmf.js → websocket-CKYc3V4N.js} +41 -12
- package/dist/websocket-CKYc3V4N.js.map +1 -0
- package/package.json +7 -7
- package/dist/BranchService-ClklxyAQ.js.map +0 -1
- package/dist/cli-console-CQrQ0lAY.js.map +0 -1
- package/dist/cli-errors-DiA0XFIS.js.map +0 -1
- package/dist/collections-schema-version-T6cGvrDi.js.map +0 -1
- package/dist/db-subcommands-BPoHCxJR.js.map +0 -1
- package/dist/doctor-BT--1kq3.js.map +0 -1
- package/dist/open-rebase-EmqVmS14.js.map +0 -1
- package/dist/rls-enforcement-DDyNoOSy.js.map +0 -1
- package/dist/websocket-BKZwsjmf.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BranchService-ClklxyAQ.js","names":[],"sources":["../src/services/BranchService.ts"],"sourcesContent":["/**\n * BranchService\n *\n * Manages database branching by creating/deleting PostgreSQL databases\n * using `CREATE DATABASE ... TEMPLATE`. Branch metadata is stored in the\n * `rebase.branches` table in the default (main) database, following the\n * same `rebase` schema convention used by entity_history, auth, etc.\n */\n\nimport { sql } from \"drizzle-orm\";\nimport { Client } from \"pg\";\nimport { BranchInfo } from \"@rebasepro/types\";\nimport { revokeInternalTableSql } from \"@rebasepro/common\";\nimport { DrizzleClient } from \"../interfaces\";\nimport { BranchRow } from \"../branch-prune\";\nimport { DatabasePoolManager } from \"../databasePoolManager\";\nimport { extractPgError, extractCauseMessage } from \"../utils/pg-error-utils\";\n\n/** Internal prefix applied to branch database names to avoid collisions. */\nconst BRANCH_DB_PREFIX = \"rb_\";\n\n/** `duplicate_database` — the target database name is already taken. */\nconst PG_DUPLICATE_DATABASE = \"42P04\";\n\n/** `object_in_use` — the database still has connections attached. */\nconst PG_OBJECT_IN_USE = \"55006\";\n\n/** How many times `--force` disconnects the other sessions before it reports one still there. */\nconst FORCE_ATTEMPTS = 3;\n\n/** The database every Postgres server has, from which another one can be closed and opened. */\nconst MAINTENANCE_DATABASE = \"postgres\";\n\n/**\n * The signals Node can catch that end a forced copy part-way: a closed\n * terminal or a lost SSH session (SIGHUP), Ctrl-C (SIGINT), a supervisor's\n * stop (SIGTERM) and Ctrl-\\ (SIGQUIT). SIGKILL cannot be caught; what it leaves\n * closed, {@link reopenClosedDatabases} opens again.\n */\nconst REOPEN_ON_SIGNALS: readonly NodeJS.Signals[] = [\"SIGHUP\", \"SIGINT\", \"SIGTERM\", \"SIGQUIT\"];\n\n/**\n * The class of the advisory lock a forced create holds on its source from the\n * moment it closes it until it opens it again: `(COPY_LOCK_CLASS,\n * hashtext(<source>))`, taken in the server's `postgres` database, where the\n * self-heal looks. Without it, the self-heal every `rebase db branch` command\n * runs could not tell a database a killed create left closed from one a live\n * create had just closed, and opened that one in the settle before the copy\n * starts; and a second forced create on the same source copied beside the\n * first and opened it under it.\n */\nconst COPY_LOCK_CLASS = 0x52424331;\n\n/** `ALTER DATABASE … ALLOW_CONNECTIONS true`, the statement that opens a closed database again. */\nfunction reopenStatement(dbName: string): string {\n return `ALTER DATABASE \"${dbName.replace(/\"/g, '\"\"')}\" ALLOW_CONNECTIONS true`;\n}\n\n/** What {@link reopenClosedDatabases} found closed, and what became of each. */\nexport interface ReopenedDatabases {\n /** Databases that refused new connections, and accept them again. */\n reopened: string[];\n /** Databases that refuse new connections and could not be opened, with Postgres's reason. */\n failed: { database: string; reason: string; statement: string }[];\n}\n\n/**\n * Open again the database `connectionString` names, and each of `others`,\n * where one refuses new connections.\n *\n * `rebase db branch create --force` closes its source with `ALLOW_CONNECTIONS\n * false` for the copy, and opens it again after, and on every signal it can\n * catch. A SIGKILL — an out-of-memory kill, a CI runner's hard stop — leaves\n * it closed: the app answers 503, and `rebase dev`, `console` and `push` all\n * fail, on the project's own database. Nothing else in a development database\n * closes one, so a closed one found here is that, and every `rebase db branch`\n * command calls this before it does anything else.\n *\n * Runs from the server's `postgres` database, since a closed database cannot\n * be connected to. A template is never touched (`template0` is closed by\n * design), nor a database a live forced create holds closed: one whose\n * {@link COPY_LOCK_CLASS} lock is held, or that a `CREATE DATABASE … TEMPLATE`\n * is copying right now. That create opens it itself.\n *\n * Best-effort: a server it cannot reach, or a catalogue it cannot read, is an\n * empty answer, and the command's own connection check speaks next.\n */\nexport async function reopenClosedDatabases(\n connectionString: string,\n others: readonly (string | null | undefined)[] = []\n): Promise<ReopenedDatabases> {\n const result: ReopenedDatabases = { reopened: [], failed: [] };\n let maintenanceUrl: string;\n let named: string;\n try {\n const url = new URL(connectionString);\n named = decodeURIComponent(url.pathname.slice(1));\n url.pathname = `/${MAINTENANCE_DATABASE}`;\n maintenanceUrl = url.toString();\n } catch {\n return result;\n }\n const names = [...new Set([named, ...others].map((name) => name?.trim() ?? \"\")\n .filter((name) => name !== \"\" && name !== MAINTENANCE_DATABASE))];\n if (names.length === 0) return result;\n\n const client = new Client({ connectionString: maintenanceUrl, connectionTimeoutMillis: 5000 });\n // A connection dropped while idle must not take the command down with it.\n client.on(\"error\", () => undefined);\n try {\n await client.connect();\n } catch {\n return result;\n }\n try {\n const closed = await client.query<{ datname: string }>(\n `SELECT d.datname\n FROM pg_database d\n WHERE d.datname = ANY($1::text[])\n AND NOT d.datallowconn\n AND NOT d.datistemplate\n AND NOT EXISTS (\n SELECT 1 FROM pg_stat_activity a\n WHERE a.state = 'active'\n AND position(('TEMPLATE \"' || replace(d.datname, '\"', '\"\"') || '\"') IN a.query) > 0)\n ORDER BY 1`,\n [names]\n );\n for (const { datname } of closed.rows) {\n // Held while it opens it, so a forced create starting now waits.\n const free = await client.query<{ locked: boolean }>(\n \"SELECT pg_try_advisory_lock($1, hashtext($2)) AS locked\",\n [COPY_LOCK_CLASS, datname]\n );\n if (free.rows[0]?.locked !== true) continue;\n const statement = reopenStatement(datname);\n try {\n await client.query(statement);\n result.reopened.push(datname);\n } catch (err) {\n result.failed.push({\n database: datname,\n reason: extractPgError(err)?.message ?? extractCauseMessage(err) ?? String(err),\n statement\n });\n } finally {\n await client.query(\"SELECT pg_advisory_unlock($1, hashtext($2))\", [COPY_LOCK_CLASS, datname])\n .catch(() => undefined);\n }\n }\n } catch {\n // The catalogue could not be read: nothing found to open.\n } finally {\n await client.end().catch(() => undefined);\n }\n return result;\n}\n\n/** What to say about {@link reopenClosedDatabases}'s answer: one line a database, or none. */\nexport function reopenedDatabasesNotice(result: ReopenedDatabases): string[] {\n return [\n ...result.reopened.map((database) =>\n `⚠ \"${database}\" was refusing every connection — an interrupted \\`rebase db branch create --force\\` ` +\n \"had closed it for the copy. It is open again.\"),\n ...result.failed.map(({ database, reason, statement }) =>\n `⚠ \"${database}\" refuses every connection — an interrupted \\`rebase db branch create --force\\` closed it — ` +\n `and could not be opened again (${reason}). Run, connected to the server's postgres database: ${statement};`)\n ];\n}\n\n/**\n * Describe a failed branch DDL statement in terms a user can act on.\n *\n * Drizzle reports failures as `Failed query: <sql> params:` and hides the real\n * PostgreSQL error in the `cause` chain, so matching on `err.message` never sees\n * the actual problem. Match on the PG error code instead — it survives wrapping\n * and, unlike the message text, is not locale-dependent.\n */\nfunction describeBranchDdlError(err: unknown, fallbackContext: string): Error {\n const pgError = extractPgError(err);\n\n if (pgError?.code === PG_DUPLICATE_DATABASE) {\n return new Error(`Database \"${fallbackContext}\" already exists on the server. Choose a different branch name.`);\n }\n if (pgError?.code === PG_OBJECT_IN_USE) {\n return new Error(\n `Cannot complete the operation: the database \"${fallbackContext}\" has active connections. ` +\n \"Close other clients or connections and try again.\"\n );\n }\n\n // Unknown failure: surface the real PG message rather than the Drizzle\n // wrapper, which would otherwise show the raw SQL and no reason at all.\n const detail = pgError?.message ?? extractCauseMessage(err);\n if (detail) return new Error(detail);\n return err instanceof Error ? err : new Error(String(err));\n}\n\n/** Fully-qualified metadata table in the rebase schema. */\nconst BRANCHES_TABLE = \"rebase.branches\";\n\n/**\n * The connected server cannot branch, so nothing was done.\n *\n * A refusal, not a fault: typed so the transports can forward it intact.\n * `websocket.ts` answers it with its own code and message rather than letting\n * it fall into the generic handler, which suppresses the text in production —\n * and the text is the only part that says what to do instead.\n */\nexport class BranchingUnsupportedError extends Error {\n /** Wire code, forwarded verbatim to the client. */\n readonly code = \"BRANCHING_UNSUPPORTED\";\n\n constructor(message: string) {\n super(message);\n this.name = \"BranchingUnsupportedError\";\n }\n}\n\n/**\n * What to say when the server serves one database.\n *\n * Deliberately the same argument the CLI's `refuseBranchOnManagedDatabase`\n * makes, because it is the same refusal — it just used to be available only to\n * whoever came in through argv. One paragraph, because this one has to fit in a\n * toast as well as a terminal.\n */\nconst BRANCHING_UNSUPPORTED_MESSAGE =\n \"Branching does not work on the managed development database. It copies a database with \"\n + \"CREATE DATABASE ... TEMPLATE, and the managed database is PGlite, which serves exactly one — \"\n + \"the copy would be the original, and every write you meant to sandbox would land in it. \"\n + \"Branching needs a real Postgres: run `rebase dev --docker`, or uncomment DATABASE_URL in .env \"\n + \"to point at your own.\";\n\n/**\n * Validate that a user-provided identifier only contains safe characters.\n * Throws if the value contains characters outside [a-zA-Z0-9_-].\n */\nfunction validateIdentifier(value: string, label: string): void {\n if (!/^[a-zA-Z0-9_-]+$/.test(value)) {\n throw new Error(`Invalid ${label}: only letters, digits, underscores, and hyphens are allowed.`);\n }\n}\n\n/**\n * Postgres truncates identifiers at NAMEDATALEN-1 = 63 bytes, and does it\n * silently. The prefix comes out of the same budget.\n */\nconst MAX_BRANCH_NAME_LENGTH = 63 - BRANCH_DB_PREFIX.length;\n\n/**\n * Check a user-provided branch name, and otherwise leave it exactly as given.\n *\n * This used to strip everything outside [a-zA-Z0-9_], so `my-feature` was\n * quietly created as `myfeature`: the name you typed was not the name `list`\n * gave back. Nothing ever needed that. Every identifier this service builds is\n * double-quoted (see `CREATE DATABASE` below), which is what makes a hyphen\n * safe, and `validateIdentifier` has always accepted hyphens for the `--from`\n * source database — the two disagreed about the same character class.\n *\n * Refusing a name we cannot represent is better than representing a different\n * one, which is also why the length is checked here rather than left to\n * Postgres, whose answer to an over-long identifier is a silent rename.\n */\nfunction assertValidBranchName(name: string): void {\n validateIdentifier(name, \"branch name\");\n if (name.length > MAX_BRANCH_NAME_LENGTH) {\n throw new Error(\n `Branch name \"${name}\" is too long: ${name.length} characters, maximum ${MAX_BRANCH_NAME_LENGTH}. ` +\n \"Postgres truncates identifiers past 63 bytes, which would give the branch a name you did not choose.\"\n );\n }\n}\n\n/**\n * Convert a user-facing branch name to the actual PostgreSQL database name.\n */\nfunction toBranchDbName(name: string): string {\n assertValidBranchName(name);\n return `${BRANCH_DB_PREFIX}${name}`;\n}\n\n/**\n * Where a server's branches are kept: the main database — what a branch is\n * copied from unless told otherwise, and what may never be dropped — and the\n * connection whose `rebase.branches` records them.\n */\nexport interface BranchHome {\n /** The main database's name. */\n database(): Promise<string>;\n /**\n * A connection on the database the registry is in. Asked for at each use:\n * a pool to the main database is closed before it is copied.\n */\n registry(): Promise<DrizzleClient>;\n}\n\nexport class BranchService {\n private readonly home: BranchHome;\n\n /**\n * @param db A connection on the server the branches are made on. Branch\n * DDL runs here.\n * @param home Where branches are kept. Without one, the database the\n * pool manager's connection string names, and `db`'s registry.\n */\n constructor(\n private db: DrizzleClient,\n private poolManager: DatabasePoolManager,\n home?: BranchHome\n ) {\n this.home = home ?? {\n database: async () => poolManager.defaultDatabaseName,\n registry: async () => db\n };\n }\n\n /**\n * Refuse a branch mutation the connected server cannot honour.\n *\n * The check lives here rather than in the CLI's argv parsing because the\n * CLI is not the only caller: Studio reaches `createBranch` over the\n * websocket, and before this it got \"Branch created successfully\" and a\n * detail pane promising isolation for a database that is the parent.\n *\n * Reads only, and only mutations are guarded: `listBranches` on a managed\n * database is an honest empty list, and refusing it would turn the Branches\n * pane into a failure with a \"Try again\" that can never succeed.\n */\n private async assertBranchingSupported(): Promise<void> {\n if (await this.poolManager.servesOneDatabase()) {\n throw new BranchingUnsupportedError(BRANCHING_UNSUPPORTED_MESSAGE);\n }\n }\n\n /**\n * Whether the `rebase.branches` registry exists. A database that has never\n * had a branch made on it may not have it, and reading it there is an\n * empty answer, not an error — nor a reason to create it. The CLI's `list`\n * and `info` ask this rather than running the DDL that creates it.\n */\n async hasBranchRegistry(): Promise<boolean> {\n const result = await (await this.home.registry()).execute(\n sql`SELECT to_regclass('rebase.branches') IS NOT NULL AS present`\n );\n const [row] = result.rows;\n return row !== undefined && Reflect.get(row, \"present\") === true;\n }\n\n /**\n * Ensure the `rebase.branches` metadata table exists in the default database.\n * Idempotent — safe to call on every startup.\n */\n async ensureBranchMetadataTable(): Promise<void> {\n // Create the rebase schema (idempotent — may already exist from auth/history init)\n const registry = await this.home.registry();\n await registry.execute(sql`CREATE SCHEMA IF NOT EXISTS rebase`);\n\n await registry.execute(sql.raw(`\n CREATE TABLE IF NOT EXISTS ${BRANCHES_TABLE} (\n name TEXT PRIMARY KEY,\n db_name TEXT NOT NULL UNIQUE,\n parent_db TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n metadata JSONB DEFAULT '{}'\n );\n `));\n\n // Not a collection, so no RLS — and it names every branch database on\n // this server. The driver's schema-wide grant reaches it, so revoke.\n await registry.execute(sql.raw(revokeInternalTableSql(\"rebase\", \"branches\")));\n }\n\n /**\n * Create a new branch database by templating the source database.\n *\n * Uses `CREATE DATABASE ... TEMPLATE` for an instant, full-fidelity copy\n * of both schema and data.\n *\n * @param name User-facing branch name (e.g., \"feature_auth\")\n * @param options.source Source database to clone; defaults to the main database.\n */\n async createBranch(\n name: string,\n options?: { source?: string; force?: boolean }\n ): Promise<BranchInfo> {\n await this.assertBranchingSupported();\n\n if (options?.source) {\n validateIdentifier(options.source, \"source database name\");\n }\n\n const dbName = toBranchDbName(name);\n const sourceDb = options?.source || await this.home.database();\n\n // Check if branch already exists\n const existing = await (await this.home.registry()).execute(\n sql`SELECT name FROM rebase.branches WHERE name = ${name} OR db_name = ${dbName}`\n );\n if ((existing.rows as unknown[]).length > 0) {\n throw new Error(`Branch \"${name}\" already exists.`);\n }\n\n // Disconnect any idle pools to the source DB so TEMPLATE works.\n // CREATE DATABASE ... TEMPLATE requires no other connections to the template.\n //\n // This only reaches pools inside *this* process. The CLI runs as its own\n // process, so a `rebase dev` holding the database open is untouched by\n // it — which is why the failure below has to name what is connected, and\n // why `--force` exists.\n await this.poolManager.disconnectDatabase(sourceDb);\n\n // Create the database using the source as a template.\n // Note: Identifiers must be double-quoted, not parameterized.\n const safeDbName = dbName.replace(/\"/g, '\"\"');\n const safeSourceDb = sourceDb.replace(/\"/g, '\"\"');\n const copy = async (): Promise<void> => {\n await this.db.execute(sql.raw(`CREATE DATABASE \"${safeDbName}\" TEMPLATE \"${safeSourceDb}\"`));\n };\n try {\n if (options?.force) {\n await this.whileClosedToNewConnections(sourceDb, () => this.disconnectingOthers(sourceDb, copy));\n } else {\n await copy();\n }\n } catch (err) {\n const pgError = extractPgError(err);\n if (pgError?.code === PG_OBJECT_IN_USE) {\n // The template — not the new database — is the one still in use.\n throw new Error(\n `Cannot create branch: the source database \"${sourceDb}\" ${options?.force\n ? \"still had connections after they were disconnected\"\n : \"has active connections\"}.\\n`\n + await this.describeBlockingConnections(sourceDb, { forced: options?.force === true })\n );\n }\n throw describeBranchDdlError(err, dbName);\n }\n\n // Record metadata in the registry — asked for again, as copying the\n // main database closed any pool on it.\n const now = new Date();\n await (await this.home.registry()).execute(\n sql`INSERT INTO rebase.branches (name, db_name, parent_db, created_at)\n VALUES (${name}, ${dbName}, ${sourceDb}, ${now.toISOString()})`\n );\n\n return {\n name,\n database: dbName,\n parentDatabase: sourceDb,\n createdAt: now\n };\n }\n\n /**\n * Delete a branch database and remove its metadata.\n * Cannot delete the main/default database.\n */\n async deleteBranch(name: string, options?: { force?: boolean }): Promise<void> {\n await this.assertBranchingSupported();\n\n assertValidBranchName(name);\n\n // Safety, first pass: a request that would target the default database\n // is refused before any metadata is read, so the answer costs nothing\n // and cannot depend on what a row happens to say.\n const mainDatabase = await this.home.database();\n if (toBranchDbName(name) === mainDatabase) {\n throw new Error(\"Cannot delete the main database.\");\n }\n\n // Verify the branch exists, and take the database name from the row\n // rather than deriving it again. A branch created before names stopped\n // being stripped is recorded as `rb_myfeature` while `my-feature` now\n // derives `rb_my-feature`; re-deriving would drop a database this row\n // never named — or, if that name happened to exist, somebody else's.\n // The stored value is the only one that is true by construction.\n const existing = await (await this.home.registry()).execute(\n sql`SELECT db_name FROM rebase.branches WHERE name = ${name}`\n );\n const existingRows = existing.rows as Record<string, unknown>[];\n if (existingRows.length === 0) {\n throw new Error(`Branch \"${name}\" not found.`);\n }\n const dbName = existingRows[0].db_name as string;\n\n // Safety, second pass: the first checked a name we derived, this checks\n // the one we are about to drop. They differ for any row written under\n // the old scheme, and it is this one that governs.\n if (dbName === mainDatabase) {\n throw new Error(\"Cannot delete the main database.\");\n }\n\n // Disconnect any pools to this branch before dropping. Same limit as\n // `createBranch`: in-process pools only.\n await this.poolManager.disconnectDatabase(dbName);\n\n // Drop the database\n const safeDbName = dbName.replace(/\"/g, '\"\"');\n const drop = async (): Promise<void> => {\n await this.db.execute(sql.raw(`DROP DATABASE \"${safeDbName}\"`));\n };\n try {\n if (options?.force) await this.disconnectingOthers(dbName, drop);\n else await drop();\n } catch (err) {\n const pgError = extractPgError(err);\n if (pgError?.code === PG_OBJECT_IN_USE) {\n throw new Error(\n `Cannot delete branch \"${name}\": the database ${options?.force\n ? \"still had connections after they were disconnected\"\n : \"has active connections\"}.\\n`\n + await this.describeBlockingConnections(dbName, { forced: options?.force === true })\n );\n }\n throw describeBranchDdlError(err, dbName);\n }\n\n // Remove metadata\n await (await this.home.registry()).execute(\n sql`DELETE FROM rebase.branches WHERE name = ${name}`\n );\n }\n\n /**\n * Who is holding the database open, in words a developer can act on.\n *\n * `CREATE DATABASE ... TEMPLATE` and `DROP DATABASE` both require that no\n * other session is connected, and the one thing the old message could not\n * say is the only thing worth knowing: *which* session. \"Close other\n * clients or connections and try again\" is advice you cannot follow when\n * you do not know there is a `rebase dev` in another terminal — and that is\n * the common case, because wanting a branch and running the app are the\n * same moment.\n *\n * `application_name` is the useful column: node-postgres sends none by\n * default, so a Rebase process shows as `(unnamed)` while DBeaver, pgAdmin\n * and psql all name themselves. Counting by it separates \"my own dev\n * server\" from \"the GUI I forgot about\" without any guessing.\n *\n * Best-effort by construction: this runs while reporting a failure, and a\n * diagnostic that throws would replace a real error with its own.\n */\n private async describeBlockingConnections(dbName: string, options: { forced?: boolean } = {}): Promise<string> {\n try {\n const result = await this.db.execute(sql`\n SELECT coalesce(nullif(application_name, ''), '(unnamed)') AS app,\n count(*)::int AS n\n FROM pg_stat_activity\n WHERE datname = ${dbName}\n AND pid <> pg_backend_pid()\n GROUP BY 1\n ORDER BY 2 DESC, 1\n `);\n const rows = result.rows as Record<string, unknown>[];\n if (rows.length === 0) {\n // The blocker went away between the failure and this query, or\n // it is a session Postgres does not attribute to this database.\n return \" Retry — whatever was connected has since disconnected.\";\n }\n\n const listed = rows\n .map((row) => ` ${row.n} × ${String(row.app)}`)\n .join(\"\\n\");\n\n // With --force they were disconnected already, and came back: the\n // one thing left to say is what to stop, not to pass --force.\n return ` Connected right now:\\n${listed}\\n`\n + (options.forced\n ? \" They reconnected as fast as they were disconnected. A running `rebase dev` is the\\n\"\n + \" usual one — stop it, then run this again.\"\n : \" A running `rebase dev` is the usual one — stop it, or re-run with --force to\\n\"\n + \" disconnect them for you.\");\n } catch {\n return options.forced\n ? \" Stop the clients connected to it — a running `rebase dev` is the usual one — then run this again.\"\n : \" Close other clients and connections, or re-run with --force.\";\n }\n }\n\n /**\n * Run `operation` with no other session on `dbName`, disconnecting them\n * first: what `--force` asks for.\n *\n * Disconnected, a client that reconnects — a running `rebase dev`'s\n * realtime listener does — can be back before `CREATE DATABASE … TEMPLATE`\n * or `DROP DATABASE` counts the sessions, and it failed on one about once\n * in seven tries, saying to pass the `--force` that had been passed. So a\n * refusal on a session still connected is retried, disconnecting again, a\n * few times before it is reported.\n */\n private async disconnectingOthers(dbName: string, operation: () => Promise<void>): Promise<void> {\n for (let attempt = 1; ; attempt++) {\n const closed = await this.terminateConnections(dbName);\n if (closed > 0) {\n // Postgres reports the backend as gone before its slot is\n // actually free, and the operation then fails on a connection\n // that no longer exists. A short settle first.\n await new Promise((resolve) => setTimeout(resolve, 250));\n }\n try {\n await operation();\n return;\n } catch (err) {\n if (attempt >= FORCE_ATTEMPTS || extractPgError(err)?.code !== PG_OBJECT_IN_USE) throw err;\n }\n }\n }\n\n /**\n * Run `operation` with `dbName` refusing new connections, and open it\n * again after, whatever happens.\n *\n * A client disconnected from the source — a running `rebase dev` holds a\n * pool and a realtime listener — cannot then reconnect before the copy has\n * counted the sessions on it. Postgres refuses `ALLOW_CONNECTIONS false` on\n * the database a session is connected to, so both statements run from the\n * server's `postgres` database, and they need the database's owner. Where\n * either is missing, the retries of {@link disconnectingOthers} are what is\n * left. A database found closed already — by a run killed before it could\n * open it — is copied as it is and opened after, like one this run closed.\n *\n * Stopped by a signal while closed ({@link REOPEN_ON_SIGNALS}), it opens\n * the database again before the process exits; a second signal waits for\n * that rather than cutting it short. One it could not open is named, with\n * the statement that does. No handler is left installed after the copy.\n *\n * From before it reads whether the database is open until after it opens\n * it, it holds the {@link COPY_LOCK_CLASS} lock, so the self-heal leaves\n * the database to it and a second forced create on the same source waits\n * for it. A process that dies holding it ends its session, which releases\n * it. A server the lock cannot be taken on is copied without one.\n */\n private async whileClosedToNewConnections(dbName: string, operation: () => Promise<void>): Promise<void> {\n const lock = dbName === MAINTENANCE_DATABASE ? null : await this.holdCopyLock(dbName);\n try {\n await this.closedToNewConnections(dbName, operation);\n } finally {\n await lock?.end().catch(() => undefined);\n }\n }\n\n /**\n * The {@link COPY_LOCK_CLASS} lock on `dbName`, on a client of its own, or\n * null where it cannot be taken.\n *\n * Its own client, never one checked out of the maintenance pool: opening\n * the database again ends that pool, and `pool.end()` waits for its\n * checked-out clients, so the create would wait on itself.\n */\n private async holdCopyLock(dbName: string): Promise<Client | null> {\n let client: Client;\n try {\n const { connectionString } = this.poolManager.getPool(MAINTENANCE_DATABASE).options;\n if (!connectionString) return null;\n client = new Client({ connectionString, connectionTimeoutMillis: 5000 });\n } catch {\n return null;\n }\n // A connection dropped while it waits must not take the command down with it.\n client.on(\"error\", () => undefined);\n try {\n await client.connect();\n await client.query(\"SELECT pg_advisory_lock($1, hashtext($2))\", [COPY_LOCK_CLASS, dbName]);\n return client;\n } catch {\n await client.end().catch(() => undefined);\n return null;\n }\n }\n\n /** {@link whileClosedToNewConnections} past its lock. */\n private async closedToNewConnections(dbName: string, operation: () => Promise<void>): Promise<void> {\n const safe = dbName.replace(/\"/g, '\"\"');\n const maintenance = dbName === MAINTENANCE_DATABASE ? null : this.poolManager.getDrizzle(MAINTENANCE_DATABASE);\n let closed = false;\n try {\n if (maintenance) {\n const state = await maintenance.execute(sql`SELECT datallowconn FROM pg_database WHERE datname = ${dbName}`);\n const [row] = state.rows as Record<string, unknown>[];\n if (row?.datallowconn === true) {\n await maintenance.execute(sql.raw(`ALTER DATABASE \"${safe}\" ALLOW_CONNECTIONS false`));\n }\n closed = row !== undefined;\n }\n } catch {\n // No `postgres` database to run it from, or not the owner.\n }\n if (!closed) {\n await this.poolManager.disconnectDatabase(MAINTENANCE_DATABASE);\n await operation();\n return;\n }\n\n let reopening: Promise<void> | undefined;\n const reopen = (): Promise<void> => {\n reopening ??= this.reopenToConnections(dbName);\n return reopening;\n };\n let caught: NodeJS.Signals | undefined;\n const release = (): void => {\n for (const signal of REOPEN_ON_SIGNALS) process.removeListener(signal, onSignal);\n };\n const onSignal = (signal: NodeJS.Signals): void => {\n if (caught) return;\n caught = signal;\n void reopen()\n .catch((err: unknown) => {\n console.error(err instanceof Error ? err.message : String(err));\n })\n .finally(() => {\n release();\n process.kill(process.pid, signal);\n });\n };\n for (const signal of REOPEN_ON_SIGNALS) {\n try {\n process.on(signal, onSignal);\n } catch {\n // A signal this platform cannot deliver (SIGQUIT on Windows).\n }\n }\n try {\n await operation();\n } finally {\n try {\n await reopen();\n } finally {\n // A caught signal releases them itself, once the database is open.\n if (!caught) release();\n }\n }\n }\n\n /** `ALLOW_CONNECTIONS true` again, from the maintenance database, or an error that says how to do it by hand. */\n private async reopenToConnections(dbName: string): Promise<void> {\n const statement = reopenStatement(dbName);\n try {\n await this.poolManager.getDrizzle(MAINTENANCE_DATABASE).execute(sql.raw(statement));\n } catch (err) {\n throw new Error(\n `\"${dbName}\" was closed to new connections for the copy and could not be opened again ` +\n `(${extractPgError(err)?.message ?? extractCauseMessage(err) ?? String(err)}). ` +\n `Nothing can connect to it until you run, from another database: ${statement};`\n );\n } finally {\n await this.poolManager.disconnectDatabase(MAINTENANCE_DATABASE);\n }\n }\n\n /**\n * Disconnect every other session on a database, and say how many.\n *\n * `pg_terminate_backend` rather than `pg_cancel_backend`: cancelling a\n * query leaves the session connected, and a connected session is exactly\n * what blocks the operation.\n *\n * The current backend is excluded — terminating the connection running this\n * statement would abort the command that asked for it.\n */\n private async terminateConnections(dbName: string): Promise<number> {\n const result = await this.db.execute(sql`\n SELECT pg_terminate_backend(pid)\n FROM pg_stat_activity\n WHERE datname = ${dbName}\n AND pid <> pg_backend_pid()\n `);\n\n return (result.rows as unknown[]).length;\n }\n\n /**\n * Everything prune needs to decide, in two queries.\n *\n * Reads the rows and the server's database list rather than joining them,\n * so the *disagreements* between the two are visible — a row whose database\n * is gone and a database with no row are the two things prune exists to\n * find, and a join hides both.\n */\n async pruneCandidates(): Promise<{ rows: BranchRow[]; databases: string[] }> {\n const rowResult = await (await this.home.registry()).execute(sql.raw(`\n SELECT name, db_name, created_at FROM ${BRANCHES_TABLE} ORDER BY created_at\n `));\n const rows = (rowResult.rows as Record<string, unknown>[]).map((row) => ({\n name: row.name as string,\n dbName: row.db_name as string,\n createdAt: new Date(row.created_at as string)\n }));\n\n const dbResult = await this.db.execute(sql`\n SELECT datname FROM pg_database WHERE NOT datistemplate\n `);\n const databases = (dbResult.rows as Record<string, unknown>[]).map((row) => String(row.datname));\n\n return { rows, databases };\n }\n\n /** Remove a metadata row whose database is already gone. */\n async forgetBranchRow(name: string): Promise<void> {\n await (await this.home.registry()).execute(sql`DELETE FROM rebase.branches WHERE name = ${name}`);\n }\n\n /**\n * Drop a database by name, with no metadata row required.\n *\n * `deleteBranch` deliberately takes its database name from the row, because\n * the row is the only value that is true by construction. Prune has the\n * opposite job — the cases it handles are exactly the ones where row and\n * database disagree — so it needs to name a database directly. Guarded the\n * same way regardless: the main database is never droppable.\n */\n async dropDatabase(dbName: string): Promise<void> {\n validateIdentifier(dbName, \"database name\");\n if (dbName === await this.home.database()) {\n throw new Error(\"Cannot delete the main database.\");\n }\n\n await this.poolManager.disconnectDatabase(dbName);\n try {\n await this.db.execute(sql.raw(`DROP DATABASE \"${dbName.replace(/\"/g, '\"\"')}\"`));\n } catch (err) {\n throw describeBranchDdlError(err, dbName);\n }\n }\n\n /**\n * List all branches recorded in the metadata table.\n * Optionally fetches database sizes from pg_database.\n */\n async listBranches(): Promise<BranchInfo[]> {\n const result = await (await this.home.registry()).execute(sql.raw(`\n SELECT \n b.name,\n b.db_name,\n b.parent_db,\n b.created_at,\n pg_database_size(b.db_name) as size_bytes\n FROM ${BRANCHES_TABLE} b\n JOIN pg_database d ON d.datname = b.db_name\n ORDER BY b.created_at DESC\n `));\n\n return (result.rows as Record<string, unknown>[]).map((row) => ({\n name: row.name as string,\n database: row.db_name as string,\n parentDatabase: row.parent_db as string,\n createdAt: new Date(row.created_at as string),\n sizeBytes: row.size_bytes != null ? Number(row.size_bytes) : undefined\n }));\n }\n\n /**\n * Get info about a specific branch.\n */\n async getBranchInfo(name: string): Promise<BranchInfo | undefined> {\n assertValidBranchName(name);\n\n const result = await (await this.home.registry()).execute(sql`\n SELECT\n b.name,\n b.db_name,\n b.parent_db,\n b.created_at\n FROM rebase.branches b\n WHERE b.name = ${name}\n `);\n\n const rows = result.rows as Record<string, unknown>[];\n if (rows.length === 0) return undefined;\n\n const row = rows[0];\n\n // Attempt to get size — may fail if the DB was externally dropped.\n // Same reason as `deleteBranch`: the size of a re-derived name is the\n // size of some other database, or of nothing.\n let sizeBytes: number | undefined;\n try {\n const dbName = row.db_name as string;\n const sizeResult = await this.db.execute(\n sql`SELECT pg_database_size(${dbName}) as size_bytes`\n );\n const sizeRows = sizeResult.rows as Record<string, unknown>[];\n if (sizeRows.length > 0 && sizeRows[0].size_bytes != null) {\n sizeBytes = Number(sizeRows[0].size_bytes);\n }\n } catch {\n // Database might not exist anymore\n }\n\n return {\n name: row.name as string,\n database: row.db_name as string,\n parentDatabase: row.parent_db as string,\n createdAt: new Date(row.created_at as string),\n sizeBytes\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cli-console-CQrQ0lAY.js","names":[],"sources":["../src/cli-console.ts"],"sourcesContent":["/**\n * `rebase console` — the driver's half.\n *\n * Loads the project's collections the way `rebase db push` does, opens the\n * database with {@link openRebase}, and either evaluates one piece of code\n * (`--eval`) or starts a Node REPL with `rebase` and `collections` in scope.\n * The `rebase` CLI resolves the database first (the managed one included) and\n * hands this process `DATABASE_URL`, exactly as it does for `db` commands.\n *\n * This is the boot side of `openRebase`: it is where the environment is read\n * — `DATABASE_URL`, and the `REBASE_DB_POOL_MAX` ceiling the managed\n * development database needs — and turned into explicit options.\n */\nimport arg from \"arg\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport readline from \"node:readline\";\nimport repl from \"node:repl\";\nimport { PassThrough } from \"node:stream\";\nimport tty from \"node:tty\";\nimport { inspect } from \"node:util\";\nimport { setLogOutput } from \"@rebasepro/server\";\nimport type { CollectionConfig } from \"@rebasepro/types\";\nimport { RELAYED_FLAGS } from \"./cli-flags\";\nimport { diagnoseDbError } from \"./cli-errors\";\nimport { out, outError, describeFailure } from \"./cli-output\";\nimport { poolMaxCeiling } from \"./connection\";\nimport { openRebase, type OpenedRebase } from \"./open-rebase\";\n\n/**\n * The flags `rebase console` takes. `--history` is the CLI's own: where it\n * keeps the REPL's history (`.rebase/console_history` in the project).\n */\nexport const CONSOLE_FLAGS = {\n \"--collections\": String,\n \"-c\": \"--collections\",\n \"--eval\": String,\n \"-e\": \"--eval\",\n \"--history\": String\n} satisfies arg.Spec;\n\n/** What the code the console runs can name. */\nexport interface ConsoleScope {\n rebase: OpenedRebase;\n collections: CollectionConfig[];\n}\n\ntype ScopedFunction = (rebase: OpenedRebase, collections: CollectionConfig[]) => Promise<unknown>;\ntype AsyncFunctionConstructor = new (...parameters: string[]) => ScopedFunction;\n\n/** The constructor of `async function` — what `new Function` is for a body that awaits. */\nconst AsyncFunction: AsyncFunctionConstructor = Object.getPrototypeOf(async () => undefined).constructor;\n\n/**\n * Run `code` with `rebase` and `collections` in scope and resolve with its\n * value.\n *\n * Read as an expression first, so `rebase.dataAsAdmin.posts.find()` is its own\n * result; code that is not one expression runs as a function body, where\n * `await` works everywhere and `return` gives the result.\n */\nexport async function evaluateInConsole(code: string, scope: ConsoleScope): Promise<unknown> {\n let run: ScopedFunction;\n try {\n run = new AsyncFunction(\"rebase\", \"collections\", `return (${code}\\n);`);\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error;\n run = new AsyncFunction(\"rebase\", \"collections\", code);\n }\n return run(scope.rebase, scope.collections);\n}\n\n/** A result as JSON, the way `--eval` prints it: dates as ISO strings, bigints as strings. */\nexport function consoleJson(value: unknown): string {\n return JSON.stringify(value, (_key, item: unknown) => typeof item === \"bigint\" ? item.toString() : item, 2) ?? \"undefined\";\n}\n\n/** The host and database a URL names, for the banner — never its credentials. */\nfunction describeDatabase(databaseUrl: string): string {\n try {\n const url = new URL(databaseUrl);\n return `${url.hostname}${url.port ? `:${url.port}` : \"\"}${url.pathname}`;\n } catch {\n return \"the configured database\";\n }\n}\n\n/** Where the REPL reads and writes; the terminal unless a caller hands it streams. */\nexport interface ConsoleStreams {\n input?: NodeJS.ReadableStream;\n output?: NodeJS.WritableStream;\n}\n\n/**\n * `rebase console [--eval <code>] [--collections <dir>]`, from the driver\n * line `[\"console\", …flags]`. Resolves with the exit code.\n */\nexport async function consoleCommand(args: string[], streams: ConsoleStreams = {}): Promise<number> {\n const flags = arg({ ...RELAYED_FLAGS, ...CONSOLE_FLAGS }, { argv: args.slice(1) });\n if (flags[\"--help\"]) {\n out(\"Run `rebase console --help` for the options — this is the driver's own entry point.\");\n return 0;\n }\n\n const databaseUrl = flags[\"--database-url\"] || process.env.DATABASE_URL;\n if (!databaseUrl) {\n outError(\"✗ No database to open: DATABASE_URL is not set. Set it in .env, or pass --database-url.\");\n return 1;\n }\n\n // The console's own lines are what the reader is here for; the driver's\n // info-level boot notes are not, unless they asked for them.\n if (process.env.LOG_LEVEL === undefined) process.env.LOG_LEVEL = \"warn\";\n\n // And whatever level they asked for, a log line is never part of the\n // answer: stdout holds the `--eval` result a pipe reads (`| jq .`) and the\n // REPL's values. The scaffold's `.env` sets LOG_LEVEL=info, and under\n // NODE_ENV=production warnings go to stdout too.\n setLogOutput(\"stderr\");\n try {\n return await openAndRun({\n databaseUrl,\n collections: flags[\"--collections\"],\n eval: flags[\"--eval\"],\n history: flags[\"--history\"]\n }, streams);\n } finally {\n setLogOutput();\n }\n}\n\n/** Whether a connection was reset or cut off before it answered, anywhere in the error's chain. */\nfunction connectionCutOff(error: unknown, depth = 0): boolean {\n if (!error || typeof error !== \"object\" || depth > 4) return false;\n if (\"code\" in error && error.code === \"ECONNRESET\") return true;\n if (error instanceof Error && /Connection terminated unexpectedly/.test(error.message)) return true;\n if (\"errors\" in error && Array.isArray(error.errors) && error.errors.some(inner => connectionCutOff(inner, depth + 1))) return true;\n return \"cause\" in error && connectionCutOff(error.cause, depth + 1);\n}\n\n/** Whether Postgres refused the connection for want of one: SQLSTATE 53300, anywhere in the error's chain. */\nfunction noFreeConnection(error: unknown, depth = 0): boolean {\n if (!error || typeof error !== \"object\" || depth > 4) return false;\n if (\"code\" in error && error.code === \"53300\") return true;\n return \"cause\" in error && noFreeConnection(error.cause, depth + 1);\n}\n\n/**\n * What the console says when the project's database will not open: the banner\n * every db command prints for a database it cannot reach, or one sentence —\n * never the driver's error serialised onto one line, stack and all.\n *\n * The managed development database accepts only a few connections at a time,\n * and a running `rebase dev` holds some of them. Its proxy refuses one over the\n * limit with SQLSTATE 53300; a database an earlier CLI started resets it\n * instead, and `read ECONNRESET` says nothing about why.\n */\nexport function describeOpenFailure(error: unknown, databaseUrl: string, managed: boolean): string {\n if (managed && (connectionCutOff(error) || noFreeConnection(error))) {\n return \"✗ The managed development database has no free connection: it accepts only a few at a time, \" +\n \"and other clients hold them — a running `rebase dev` holds some.\\n\" +\n \" Close another `rebase console`, psql or database GUI and run this again, \" +\n \"or stop `rebase dev` while you use the console.\";\n }\n return diagnoseDbError(error, databaseUrl) ?? describeFailure(error);\n}\n\n/** Open the project, run `--eval` or the REPL, and close it again. Resolves with the exit code. */\nasync function openAndRun(\n options: { databaseUrl: string; collections?: string; eval?: string; history?: string },\n streams: ConsoleStreams\n): Promise<number> {\n const { databaseUrl } = options;\n const collectionsPath = path.resolve(options.collections || path.join(\"..\", \"config\", \"collections\"));\n const { loadCollections } = await import(\"./schema/doctor\");\n const collections = fs.existsSync(collectionsPath) ? await loadCollections(collectionsPath) : [];\n\n const ceiling = poolMaxCeiling();\n let rebase: OpenedRebase;\n try {\n rebase = await openRebase({\n databaseUrl,\n collections,\n ...(ceiling !== null ? { pool: { max: ceiling } } : {})\n });\n } catch (error) {\n outError(describeOpenFailure(error, databaseUrl, process.env.REBASE_DEV_DATABASE_KIND === \"managed\"));\n return 1;\n }\n\n try {\n const code = options.eval;\n if (code !== undefined) {\n try {\n const result = await evaluateInConsole(code, { rebase, collections: rebase.collections });\n if (result !== undefined) out(consoleJson(result));\n return 0;\n } catch (error) {\n outError(describeFailure(error));\n return 1;\n }\n }\n\n await startRepl({ rebase, collections: rebase.collections }, {\n history: options.history,\n database: describeDatabase(databaseUrl),\n ...streams\n });\n return 0;\n } finally {\n await rebase.close();\n }\n}\n\n/** The interactive half: a Node REPL until `.exit`, Ctrl-D or the end of piped input. */\nasync function startRepl(scope: ConsoleScope, options: { history?: string; database: string } & ConsoleStreams): Promise<void> {\n const input = options.input ?? process.stdin;\n const output = options.output ?? process.stdout;\n const typed = input instanceof tty.ReadStream && input.isTTY;\n\n // For a person at a terminal. Piped, stdout is the REPL's answers, which\n // whatever reads the pipe is after, so the banner goes to stderr with the\n // rest of what the console says about itself.\n const say = typed ? out : outError;\n say(\"\");\n say(` rebase console — ${options.database}, ${scope.collections.length} collection(s)`);\n say(\" In scope: rebase (dataAsAdmin, sql, withAuth, close) and collections. Top-level await works.\");\n say(\" Try: await rebase.dataAsAdmin.<collection>.find({ limit: 5 })\");\n say(\"\");\n\n // Typed at a terminal, the REPL reads it as one. Anything else — a pipe, a\n // file, a stream handed in — reaches it a line at a time through `feed`\n // (see `feedLineByLine`).\n const feed = typed ? undefined : new PassThrough();\n // Colours for a terminal only: piped to a file or another program, the\n // escape codes are noise in the text.\n const colors = output instanceof tty.WriteStream && output.isTTY;\n // Started without a prompt, and given one once the scope is in place: a\n // line that arrives with the first prompt must already see `rebase`.\n const server = repl.start({\n prompt: \"\",\n useGlobal: false,\n ...(feed ? { input: feed, output, terminal: false } : { input, output }),\n writer: (value: unknown) => inspect(value, { colors, depth: 6 })\n });\n server.context.rebase = scope.rebase;\n server.context.collections = scope.collections;\n server.setPrompt(\"rebase> \");\n\n const history = options.history;\n if (history) {\n fs.mkdirSync(path.dirname(history), { recursive: true });\n await new Promise<void>((resolve) => {\n server.setupHistory(history, (error) => {\n if (error) outError(` (history is off: ${error.message})`);\n resolve();\n });\n });\n }\n\n const exited = new Promise<void>((resolve) => server.once(\"exit\", () => resolve()));\n server.displayPrompt();\n if (feed) await feedLineByLine(input, server, feed, exited);\n await exited;\n}\n\n/**\n * Hand input that is not typed at a terminal to the REPL a line at a time,\n * each once the REPL has finished the one before.\n *\n * Typed, the REPL holds keystrokes back while a top-level `await` is pending.\n * Piped, every line arrives at once and each is evaluated as it arrives, so in\n * `const { data } = await …` followed by `data.map(…)` the second line read\n * `data` before the first had assigned it.\n *\n * The REPL is paused as each line reaches it, and it resumes its input when it\n * displays its prompt again: once the line's value has settled, its error has\n * been printed, or it needs the next line to complete it. That resumption is\n * when the next line goes in. Lines are written one per chunk, because a\n * chunk holding several is split and every line of it handed over at once.\n */\nasync function feedLineByLine(\n source: NodeJS.ReadableStream,\n server: repl.REPLServer,\n feed: PassThrough,\n exited: Promise<void>\n): Promise<void> {\n let evaluated: (() => void) | undefined;\n const ready = () => {\n const resolve = evaluated;\n evaluated = undefined;\n resolve?.();\n };\n // Before the REPL's own handler, so a line it evaluates at once resumes\n // after this pause rather than before it.\n server.prependListener(\"line\", () => {\n if (!server.editorMode) server.pause();\n });\n // `.editor` collects lines with no prompt until Ctrl-D: they go straight in.\n server.on(\"line\", () => {\n if (!server.editorMode) return;\n server.resume();\n ready();\n });\n server.on(\"resume\", ready);\n\n const closed = exited.then(() => \"closed\" as const);\n const lines = readline.createInterface({ input: source, crlfDelay: Infinity });\n for await (const line of lines) {\n const done = new Promise<\"done\">((resolve) => { evaluated = () => resolve(\"done\"); });\n feed.write(`${line}\\n`);\n // `.exit`: the rest of the input is not for this REPL.\n if (await Promise.race([done, closed]) === \"closed\") break;\n }\n feed.end();\n}\n"],"mappings":"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"}
|