@ultimat3/cli
Advanced tools
| // The framework's OWN tables, as one table: which package declares each, which relations it | ||
| // creates, and the DDL. One list, read by the one applier, so "installed in dev and not in | ||
| // production" is not a state this framework can be in — `startQueue` is on every boot path there | ||
| // is, `ROLE=migrate` included. | ||
| // | ||
| // A framework table an app has to install by hand is a table that will be missing in production on | ||
| // the one code path that needs it, and it surfaces as a Postgres `42P01` from inside a worker. | ||
| import { SQL_AUDIT_TABLE, SQL_IDEMPOTENCY_TABLE } from '@ultimat3/action'; | ||
| import { AUTH_TABLE_NAMES, AUTH_TABLES, SQL_AUTH_LIMIT_TABLES } from '@ultimat3/auth'; | ||
| import { SQL_RATE_LIMIT_TABLE } from '@ultimat3/http'; | ||
| import { SQL_JOBS_TABLE } from '@ultimat3/jobs'; | ||
| import { SQL_NOTIFY_DELIVERIES_TABLE, SQL_NOTIFY_INBOX_TABLE } from '@ultimat3/notify'; | ||
| import { FrameworkSchemaFailedError } from './schema-errors'; | ||
| export interface FrameworkSchema { | ||
| /** The package whose source declares the DDL — where to look when a column is wrong. */ | ||
| readonly pkg: string; | ||
| /** | ||
| * Every relation this entry creates. Read by the refusal below, so an operator learns which | ||
| * tables were being installed rather than only which statement failed — and pinned against the | ||
| * DDL text by `framework-schema.test.ts`, so a row cannot claim a table its SQL never creates. | ||
| */ | ||
| readonly tables: readonly string[]; | ||
| /** One or more statements each; `;` separates. */ | ||
| readonly ddl: readonly string[]; | ||
| } | ||
| /** | ||
| * Applied unconditionally, whether or not this boot installs a store behind it. | ||
| * | ||
| * `create table if not exists` on an unused table costs one round trip at boot. The alternative | ||
| * costs a request: a store installed later must never be the thing that discovers the schema was | ||
| * never applied, and several of these are installed AFTER this runs — `defineAuth` builds its | ||
| * limiter when the app's modules import, and `setNotifyStores` is an app's boot line. | ||
| * | ||
| * Ordered so a foreign key never precedes its target. Only `AUTH_TABLES` has any, and they are | ||
| * internal to that entry, which is why it ships as an ordered list rather than one string. | ||
| */ | ||
| export const FRAMEWORK_SCHEMA: readonly FrameworkSchema[] = Object.freeze([ | ||
| Object.freeze({ | ||
| pkg: '@ultimat3/jobs', | ||
| tables: Object.freeze([ | ||
| 'x_jobs', | ||
| 'x_job_steps', | ||
| 'x_backfills', | ||
| 'x_outbox', | ||
| 'x_scheduler_state', | ||
| 'x_scheduler_leader', | ||
| 'x_job_leases', | ||
| 'x_job_events', | ||
| ]), | ||
| ddl: Object.freeze([SQL_JOBS_TABLE]), | ||
| }), | ||
| Object.freeze({ | ||
| pkg: '@ultimat3/action', | ||
| tables: Object.freeze(['x_idempotency']), | ||
| ddl: Object.freeze([SQL_IDEMPOTENCY_TABLE]), | ||
| }), | ||
| // The DDL only, and deliberately NO `setAuditSink`: there is no default audit sink on purpose, | ||
| // so `X_AUDIT_SINK_MISSING` keeps firing at boot for an app that declares `audit: true` and | ||
| // installs none. | ||
| Object.freeze({ | ||
| pkg: '@ultimat3/action', | ||
| tables: Object.freeze(['x_audit']), | ||
| ddl: Object.freeze([SQL_AUDIT_TABLE]), | ||
| }), | ||
| Object.freeze({ | ||
| pkg: '@ultimat3/http', | ||
| tables: Object.freeze(['x_rate_limit']), | ||
| ddl: Object.freeze([SQL_RATE_LIMIT_TABLE]), | ||
| }), | ||
| Object.freeze({ | ||
| pkg: '@ultimat3/auth', | ||
| tables: Object.freeze(['x_auth_failures', 'x_auth_lockouts']), | ||
| ddl: Object.freeze([SQL_AUTH_LIMIT_TABLES]), | ||
| }), | ||
| /** | ||
| * The five tables `BuiltinAdapter` reads, and the oldest hole in this list. | ||
| * | ||
| * `packages/auth/src/tables.ts` exports them "so an app can paste them into a migration", and | ||
| * nothing in the framework has ever applied them — while `x db gen` diffs `describeEntities()` | ||
| * and these are not `entity()` declarations, so neither half was a file anybody could | ||
| * hand-write. `examples/dummy/CLAUDE.md` records the consequence in its own words: nobody can | ||
| * hold a session in the reference app. Applied here on exactly the rule the rate-limit and audit | ||
| * rows already follow. | ||
| * | ||
| * `AUTH_TABLE_NAMES` rather than five literals: @ultimat3/auth already publishes the list, and a | ||
| * second copy is a second thing to keep right when a table is added. | ||
| */ | ||
| Object.freeze({ | ||
| pkg: '@ultimat3/auth', | ||
| tables: AUTH_TABLE_NAMES, | ||
| ddl: AUTH_TABLES, | ||
| }), | ||
| /** | ||
| * The delivery ledger is what stops a replayed notifier job sending twice, and it is the entry | ||
| * whose absence is least visible: without the table the ledger's first `claim` raises `42P01` | ||
| * from inside a worker, which reads as a dead-lettered notification rather than as a missing | ||
| * schema. Installed whether or not this boot calls `setNotifyStores`, for the same reason as | ||
| * every row above it — that call is an APP's boot line and runs after this one. | ||
| */ | ||
| Object.freeze({ | ||
| pkg: '@ultimat3/notify', | ||
| tables: Object.freeze(['x_notify_deliveries']), | ||
| ddl: Object.freeze([SQL_NOTIFY_DELIVERIES_TABLE]), | ||
| }), | ||
| Object.freeze({ | ||
| pkg: '@ultimat3/notify', | ||
| tables: Object.freeze(['x_notify_inbox']), | ||
| ddl: Object.freeze([SQL_NOTIFY_INBOX_TABLE]), | ||
| }), | ||
| ]); | ||
| /** Every relation this boot creates, flattened. */ | ||
| export const frameworkTableNames = (): readonly string[] => | ||
| FRAMEWORK_SCHEMA.flatMap((entry) => [...entry.tables]); | ||
| /** | ||
| * PGlite speaks the extended protocol, which carries one statement per round trip, so the DDL is | ||
| * applied statement by statement. Safe to split on `;`: every constant is fixed, with no semicolon | ||
| * inside a literal, and each package's own SQL test is where that stays true. | ||
| */ | ||
| export const schemaStatements = (ddl: readonly string[]): readonly string[] => | ||
| ddl.flatMap((text) => text.split(';')).filter((statement) => statement.trim().length > 0); | ||
| /** One statement, executed. The caller owns the connection; this file owns no database import. */ | ||
| export type SchemaExecutor = (statement: string) => Promise<unknown>; | ||
| /** | ||
| * Apply every entry, in order, and answer what was created. | ||
| * | ||
| * The refusal is the point of the `pkg`/`tables` columns: a raw `permission denied for schema | ||
| * public` names neither the framework table it was creating nor the package that wants it, and a | ||
| * boot failure is read by an operator who has no source tree open. | ||
| */ | ||
| export async function applyFrameworkSchema(execute: SchemaExecutor): Promise<readonly string[]> { | ||
| for (const entry of FRAMEWORK_SCHEMA) { | ||
| for (const statement of schemaStatements(entry.ddl)) { | ||
| try { | ||
| await execute(statement); | ||
| } catch (error) { | ||
| throw new FrameworkSchemaFailedError({ | ||
| pkg: entry.pkg, | ||
| tables: entry.tables, | ||
| cause: error, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| return frameworkTableNames(); | ||
| } |
| // The one code raised while installing the framework's own tables. Apart from `errors.ts` for the | ||
| // reason `packages/jobs/src/backfill-errors.ts` is apart from that package's: one file, one job, | ||
| // and `errors.ts` is at 486 lines against the 500 the `filesize` step enforces. The code, its | ||
| // title and its registration stay in `error-codes.ts`, where every other CLI code lives. | ||
| import { renderThrowable, UltimateError } from '@ultimat3/core'; | ||
| /** | ||
| * A statement in `FRAMEWORK_SCHEMA` did not apply. Raised in place of the driver's own rejection, | ||
| * which names neither the framework table being created nor the package that wants it — and a boot | ||
| * failure is read by an operator with no source tree open. | ||
| * | ||
| * `renderThrowable` and never `${cause}`: a `catch` binding is annotated by nobody, and a pool | ||
| * rejection is routinely an object whose `toString` throws. | ||
| */ | ||
| export class FrameworkSchemaFailedError extends UltimateError { | ||
| constructor(input: { pkg: string; tables: readonly string[]; cause: unknown }) { | ||
| super({ | ||
| code: 'X_FRAMEWORK_SCHEMA_FAILED', | ||
| cause: `${input.pkg} could not create ${input.tables.join(', ')}: ${renderThrowable(input.cause)}`, | ||
| // An EDIT plus the command that CONFIRMS it, which is the house shape for a repair the gate | ||
| // cannot perform: the two real causes are a role without `create`, and a relation of that | ||
| // name already present with an incompatible shape. | ||
| fix: `grant create on schema public to the role in DATABASE_URL, then re-run: x db migrate`, | ||
| meta: { pkg: input.pkg, tables: [...input.tables] }, | ||
| }); | ||
| } | ||
| } |
+29
-28
| { | ||
| "name": "@ultimat3/cli", | ||
| "version": "12.0.0", | ||
| "version": "13.0.0", | ||
| "description": "The `x` binary: new, dev, build, verify, generate, db, mcp, doctor, deploy", | ||
@@ -40,31 +40,32 @@ "license": "MIT", | ||
| "@babel/core": "^7.28.4", | ||
| "@ultimat3/action": "12.0.0", | ||
| "@ultimat3/admin": "12.0.0", | ||
| "@ultimat3/ai": "12.0.0", | ||
| "@ultimat3/auth": "12.0.0", | ||
| "@ultimat3/cache": "12.0.0", | ||
| "@ultimat3/core": "12.0.0", | ||
| "@ultimat3/db": "12.0.0", | ||
| "@ultimat3/entity": "12.0.0", | ||
| "@ultimat3/flags": "12.0.0", | ||
| "@ultimat3/http": "12.0.0", | ||
| "@ultimat3/i18n": "12.0.0", | ||
| "@ultimat3/jobs": "12.0.0", | ||
| "@ultimat3/mail": "12.0.0", | ||
| "@ultimat3/manifest": "12.0.0", | ||
| "@ultimat3/mcp": "12.0.0", | ||
| "@ultimat3/money": "12.0.0", | ||
| "@ultimat3/policy": "12.0.0", | ||
| "@ultimat3/pwa": "12.0.0", | ||
| "@ultimat3/query": "12.0.0", | ||
| "@ultimat3/realtime": "12.0.0", | ||
| "@ultimat3/render": "12.0.0", | ||
| "@ultimat3/schema": "12.0.0", | ||
| "@ultimat3/scraping": "12.0.0", | ||
| "@ultimat3/seo": "12.0.0", | ||
| "@ultimat3/storage": "12.0.0", | ||
| "@ultimat3/testing": "12.0.0", | ||
| "@ultimat3/time": "12.0.0", | ||
| "@ultimat3/action": "13.0.0", | ||
| "@ultimat3/admin": "13.0.0", | ||
| "@ultimat3/ai": "13.0.0", | ||
| "@ultimat3/auth": "13.0.0", | ||
| "@ultimat3/cache": "13.0.0", | ||
| "@ultimat3/core": "13.0.0", | ||
| "@ultimat3/db": "13.0.0", | ||
| "@ultimat3/entity": "13.0.0", | ||
| "@ultimat3/flags": "13.0.0", | ||
| "@ultimat3/http": "13.0.0", | ||
| "@ultimat3/i18n": "13.0.0", | ||
| "@ultimat3/jobs": "13.0.0", | ||
| "@ultimat3/mail": "13.0.0", | ||
| "@ultimat3/manifest": "13.0.0", | ||
| "@ultimat3/mcp": "13.0.0", | ||
| "@ultimat3/money": "13.0.0", | ||
| "@ultimat3/notify": "13.0.0", | ||
| "@ultimat3/policy": "13.0.0", | ||
| "@ultimat3/pwa": "13.0.0", | ||
| "@ultimat3/query": "13.0.0", | ||
| "@ultimat3/realtime": "13.0.0", | ||
| "@ultimat3/render": "13.0.0", | ||
| "@ultimat3/schema": "13.0.0", | ||
| "@ultimat3/scraping": "13.0.0", | ||
| "@ultimat3/seo": "13.0.0", | ||
| "@ultimat3/storage": "13.0.0", | ||
| "@ultimat3/testing": "13.0.0", | ||
| "@ultimat3/time": "13.0.0", | ||
| "babel-preset-solid": "^1.9.15" | ||
| } | ||
| } |
+10
-36
@@ -11,7 +11,4 @@ // The database and the job queue, started together and released together. Split from | ||
| resetIdempotency, | ||
| SQL_AUDIT_TABLE, | ||
| SQL_IDEMPOTENCY_TABLE, | ||
| setIdempotencyStore, | ||
| } from '@ultimat3/action'; | ||
| import { SQL_AUTH_LIMIT_TABLES } from '@ultimat3/auth'; | ||
| import type { DbClient, PgliteClient, PostgresClient, SqlFragment } from '@ultimat3/db'; | ||
@@ -27,3 +24,2 @@ import { | ||
| import type { Tx } from '@ultimat3/entity'; | ||
| import { SQL_RATE_LIMIT_TABLE } from '@ultimat3/http'; | ||
| import type { EventBus, JobDriver, OutboxStore, PgExecutor } from '@ultimat3/jobs'; | ||
@@ -37,3 +33,2 @@ import { | ||
| resetJobsFacade, | ||
| SQL_JOBS_TABLE, | ||
| setEventBus, | ||
@@ -45,2 +40,3 @@ setJobDriver, | ||
| import type { DevServices } from './dev-services'; | ||
| import { applyFrameworkSchema } from './framework-schema'; | ||
| import type { RuntimeOverrides } from './runtime-overrides'; | ||
@@ -120,36 +116,14 @@ | ||
| * | ||
| * PGlite speaks the extended protocol, which carries one statement per round trip, so the DDL is | ||
| * applied statement by statement. Safe to split on `;`: every constant is fixed, with no semicolon | ||
| * inside a literal, and each package's own SQL test is where that stays true. | ||
| * The LIST is `FRAMEWORK_SCHEMA` and lives in `framework-schema.ts`, not here: this function is on | ||
| * every boot path the framework has — `x dev`, each served role, `x jobs`, `x db backfill`, | ||
| * `x mcp serve` and `ROLE=migrate` all reach it through `startQueue` — so the list it reads is the | ||
| * one place a framework table can be forgotten, and it is worth being a table somebody can read | ||
| * rather than an array literal inside a boot function. | ||
| * | ||
| * `SQL_IDEMPOTENCY_TABLE`, `SQL_RATE_LIMIT_TABLE` and `SQL_AUTH_LIMIT_TABLES` are here and not in | ||
| * `@ultimat3/action`, `@ultimat3/http` or `@ultimat3/auth` because a package that holds no | ||
| * database dependency cannot apply its own schema | ||
| * — the same reason `SQL_JOBS_TABLE` is applied here. Each one absent is the same failure at a | ||
| * different door: a retried `POST /api/payments/charge` charges the card twice, and the FIRST | ||
| * request a `rateLimitStore` deployment serves dies on a missing `x_rate_limit` relation. The | ||
| * table is installed whether or not this boot passes `runtime.rateLimitStore` — `create table if | ||
| * not exists` on an unused table costs one round trip at boot, and a store installed later must | ||
| * not be the thing that discovers the schema was never applied. The auth pair is the strongest | ||
| * case for that rule: `defineAuth` builds its limiter when the APP's modules import, which is | ||
| * after this, so the first failed sign-in would otherwise be what discovers the missing relation. | ||
| * Each package's DDL is here and not in `@ultimat3/action`, `@ultimat3/http`, `@ultimat3/auth` or | ||
| * `@ultimat3/notify` because a package that holds no database dependency cannot apply its own | ||
| * schema — the same reason `SQL_JOBS_TABLE` is applied by the boot. | ||
| */ | ||
| async function applySchema(client: DevDbClient): Promise<void> { | ||
| for (const ddl of [ | ||
| SQL_JOBS_TABLE, | ||
| SQL_IDEMPOTENCY_TABLE, | ||
| // The DDL only, and deliberately NO `setAuditSink` beside `setIdempotencyStore` below: there | ||
| // is no default audit sink on purpose, so `X_AUDIT_SINK_MISSING` keeps firing at boot for an | ||
| // app that declares `audit: true` and installs none. Applying the table without installing a | ||
| // sink is the same call `SQL_RATE_LIMIT_TABLE` already makes — one round trip at boot on a | ||
| // possibly-unused table, against `postgresAuditSink` failing its first write with | ||
| // `relation "x_audit" does not exist`. | ||
| SQL_AUDIT_TABLE, | ||
| SQL_RATE_LIMIT_TABLE, | ||
| SQL_AUTH_LIMIT_TABLES, | ||
| ]) { | ||
| for (const statement of ddl.split(';')) { | ||
| if (statement.trim().length > 0) await client.execute(raw(statement)); | ||
| } | ||
| } | ||
| await applyFrameworkSchema((statement) => client.execute(raw(statement))); | ||
| } | ||
@@ -156,0 +130,0 @@ |
@@ -33,2 +33,3 @@ // Every framework package's error codes, present in this process before `x errors` answers. | ||
| '@ultimat3/money', | ||
| '@ultimat3/notify', | ||
| '@ultimat3/policy', | ||
@@ -35,0 +36,0 @@ '@ultimat3/pwa', |
@@ -52,2 +52,6 @@ // The X_* codes owned by @ultimat3/cli, and nothing else: the two lists, their titles, the one | ||
| 'X_RELEASE_VERSION_SKEW', | ||
| // The framework's own tables, refused by name rather than by the driver's rejection: a raw | ||
| // `permission denied for schema public` says which statement failed and neither which framework | ||
| // table it was creating nor which package wants it. | ||
| 'X_FRAMEWORK_SCHEMA_FAILED', | ||
| 'X_STORAGE_UNWRITABLE', | ||
@@ -202,2 +206,3 @@ 'X_STORAGE_SECRET_DEV', | ||
| X_ERROR_CODE_UNRESOLVED: 'an error code is written as a name this repository cannot resolve', | ||
| X_FRAMEWORK_SCHEMA_FAILED: 'a framework table could not be created at boot', | ||
| X_STORAGE_UNWRITABLE: 'the storage disk this process needs cannot be written to', | ||
@@ -204,0 +209,0 @@ X_STORAGE_SECRET_DEV: 'upload grants would be signed with the shipped development key', |
+10
-0
@@ -199,2 +199,11 @@ // Public API of @ultimat3/cli. Explicit re-exports only: create-ultimate and the test suite build | ||
| export { checkFlagReads, declaredFlags, readsFlag } from './flag-reads'; | ||
| export type { FrameworkSchema, SchemaExecutor } from './framework-schema'; | ||
| // The framework's own tables, as data. Exported so `scripts/` can read the applier's list without | ||
| // re-deriving it — the shape a ratchet over declared-but-never-applied DDL needs. | ||
| export { | ||
| applyFrameworkSchema, | ||
| FRAMEWORK_SCHEMA, | ||
| frameworkTableNames, | ||
| schemaStatements, | ||
| } from './framework-schema'; | ||
| export type { Guard } from './guards'; | ||
@@ -243,2 +252,3 @@ export { findingProblem, GUARD_DIR, guardFindings, guardPaths } from './guards'; | ||
| export { COMMANDS, cliVersion, commandFor, SPECS } from './registry'; | ||
| export { FrameworkSchemaFailedError } from './schema-errors'; | ||
| export type { MigratedApp, ServedApp, ServeOptions, StartedApp } from './serve'; | ||
@@ -245,0 +255,0 @@ export { |
@@ -99,2 +99,5 @@ // `errors.explain`: one runnable command per error code. Its own file because the CLI's fix table | ||
| // the one that inspects the binding rather than guessing between a volume and a bucket. | ||
| // `x db migrate --json` and not `x doctor`: this fires from inside `startQueue`, so the command | ||
| // that re-runs exactly the failing step is the migrate role, and it reports what it applied. | ||
| X_FRAMEWORK_SCHEMA_FAILED: 'x db migrate --json', | ||
| X_STORAGE_UNWRITABLE: 'x doctor --json', | ||
@@ -101,0 +104,0 @@ X_STORAGE_SECRET_DEV: 'export STORAGE_SIGNING_SECRET="$(openssl rand -hex 32)"', |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
1799934
0.44%234
0.86%34265
0.48%30
3.45%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated