@govcore/schema 0.1.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/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/migrate.d.ts +11 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +64 -0
- package/dist/schema.d.ts +2024 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +231 -0
- package/migrations/0000_platform_init.sql +97 -0
- package/migrations/0001_platform_security.sql +59 -0
- package/migrations/0002_platform_federation_support.sql +103 -0
- package/package.json +35 -0
- package/src/index.ts +21 -0
- package/src/migrate.ts +85 -0
- package/src/schema.ts +311 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @govcore/schema
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f2f3743: Add `@govcore/schema`: the platform schema (identity, tenancy, auth, audit) in a
|
|
8
|
+
dedicated `govcore` Postgres schema, with the `orgScoped` tenancy-column helper
|
|
9
|
+
and `CORE_SCHEMA_VERSION`. Roles are `text` (app-defined via `@govcore/rbac`),
|
|
10
|
+
not a fixed enum.
|
|
11
|
+
|
|
12
|
+
Also adds federation (`org_connections`, `cross_org_links`), support access
|
|
13
|
+
(`break_glass_sessions`, `act_as_sessions`), and instance config
|
|
14
|
+
(`instance_settings`, `platform_config`).
|
|
15
|
+
|
|
16
|
+
Ships authored migrations (`0000_platform_init`, `0001_platform_security` —
|
|
17
|
+
append-only audit trigger + Row-Level Security with `FORCE ROW LEVEL SECURITY`;
|
|
18
|
+
`0002_platform_federation_support` — federation tables with a both-participant
|
|
19
|
+
RLS check) and the `govcore-migrate` runner (separate, non-edge `./migrate`
|
|
20
|
+
entrypoint; tracked in `govcore.__govcore_migrations`; runs as the owner/DDL role).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rob Allred
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @govcore/schema
|
|
2
|
+
|
|
3
|
+
Platform table definitions, enums, the authored migrations, and the
|
|
4
|
+
`govcore-migrate` runner. All platform tables live in the `govcore` Postgres
|
|
5
|
+
schema. See the design doc §5 and §13.1–§13.2.
|
|
6
|
+
|
|
7
|
+
## Two entrypoints
|
|
8
|
+
|
|
9
|
+
- **`@govcore/schema`** — table definitions + `orgScoped` + `CORE_SCHEMA_VERSION`.
|
|
10
|
+
Edge-safe (only `drizzle-orm/pg-core`); importable into Next middleware.
|
|
11
|
+
- **`@govcore/schema/migrate`** — the migration runner (`postgres` + `node:fs`).
|
|
12
|
+
**Not** edge-safe; server-only.
|
|
13
|
+
|
|
14
|
+
## Migrations
|
|
15
|
+
|
|
16
|
+
Authored SQL in [`migrations/`](./migrations), applied in lexical order by
|
|
17
|
+
`govcore-migrate`, each in its own transaction, tracked in
|
|
18
|
+
`govcore.__govcore_migrations` (separate from an app's own Drizzle journal).
|
|
19
|
+
|
|
20
|
+
| File | Contents |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `0000_platform_init.sql` | identity/tenancy/auth/audit tables, FKs, indexes |
|
|
23
|
+
| `0001_platform_security.sql` | append-only `audit_log` trigger; RLS + `FORCE ROW LEVEL SECURITY` on `users` / memberships / `audit_log` |
|
|
24
|
+
| `0002_platform_federation_support.sql` | federation (`org_connections`, `cross_org_links`), support (`break_glass_sessions`, `act_as_sessions`), instance config (`instance_settings`, `platform_config`); federation RLS (both-participant check). Support/instance tables are app-authorized, not under org-GUC RLS |
|
|
25
|
+
|
|
26
|
+
> Core **owns** this DDL (design §5). It must stay in sync with
|
|
27
|
+
> [`src/schema.ts`](./src/schema.ts). A schema-conformance test is a follow-up.
|
|
28
|
+
|
|
29
|
+
## Two-role database (design §13.2)
|
|
30
|
+
|
|
31
|
+
- **`govcore-migrate`** runs as the **owner / DDL** role:
|
|
32
|
+
set `GOVCORE_MIGRATE_DATABASE_URL` (falls back to `DATABASE_URL`).
|
|
33
|
+
- The **app runtime** connects as a separate **non-owner** role, which RLS binds.
|
|
34
|
+
|
|
35
|
+
## Tenant isolation (design §13.1)
|
|
36
|
+
|
|
37
|
+
RLS policies key off `current_setting('app.current_org')`. The runtime sets it
|
|
38
|
+
**transaction-locally** per request — `select set_config('app.current_org', $org, true)`
|
|
39
|
+
— inside the tenant transaction (the `@govcore/server` `tenantAction`, later phase).
|
|
40
|
+
Unset GUC ⇒ all rows hidden (deny by default).
|
|
41
|
+
|
|
42
|
+
## Roles are `text`, not an enum
|
|
43
|
+
|
|
44
|
+
Per-org and per-user `role` columns are `text`, not a fixed `pgEnum` — GovCore
|
|
45
|
+
ships no role vocabulary. The app defines valid roles via `@govcore/rbac`'s
|
|
46
|
+
`createRbac`.
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// app schema: re-export core tables, add domain tables scoped to the tenant
|
|
52
|
+
export * from '@govcore/schema'
|
|
53
|
+
import { organizations, orgScoped } from '@govcore/schema'
|
|
54
|
+
export const permits = pgTable('permits', { ...orgScoped(organizations), title: text('title') })
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```jsonc
|
|
58
|
+
// app package.json — platform migrations run before the app's own
|
|
59
|
+
"db:migrate": "govcore-migrate && drizzle-kit migrate"
|
|
60
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
|
|
2
|
+
export * from './schema';
|
|
3
|
+
/**
|
|
4
|
+
* A Drizzle Postgres database **or** transaction handle. Functions in the
|
|
5
|
+
* tenancy/audit packages accept this so callers inject their own client (the
|
|
6
|
+
* app's `postgres-js` db, or a `tx` inside a transaction — which extends it).
|
|
7
|
+
*/
|
|
8
|
+
export type GovcoreDb = PgDatabase<PgQueryResultHKT, any, any>;
|
|
9
|
+
/**
|
|
10
|
+
* Bumped whenever the platform schema changes. Written to instance settings on
|
|
11
|
+
* boot for observability ("this instance runs platform schema vN", design §5),
|
|
12
|
+
* and stamped into backup archives (design §13.5). Not load-bearing for
|
|
13
|
+
* correctness — the migrations are — but useful for diagnostics.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CORE_SCHEMA_VERSION = "0.0.0";
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAEvE,cAAc,UAAU,CAAA;AAExB;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,gBAAgB,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,UAAU,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// @govcore/schema — platform table definitions + schema version.
|
|
2
|
+
// Edge-safe entrypoint (no DB client). The migrate runner is ./migrate.
|
|
3
|
+
export * from './schema';
|
|
4
|
+
/**
|
|
5
|
+
* Bumped whenever the platform schema changes. Written to instance settings on
|
|
6
|
+
* boot for observability ("this instance runs platform schema vN", design §5),
|
|
7
|
+
* and stamped into backup archives (design §13.5). Not load-bearing for
|
|
8
|
+
* correctness — the migrations are — but useful for diagnostics.
|
|
9
|
+
*/
|
|
10
|
+
export const CORE_SCHEMA_VERSION = '0.0.0';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface MigrateOptions {
|
|
2
|
+
/** Connection string for the owner/DDL role. Defaults to env. */
|
|
3
|
+
connectionString?: string;
|
|
4
|
+
/** Optional logger; defaults to console.log. */
|
|
5
|
+
log?: (message: string) => void;
|
|
6
|
+
}
|
|
7
|
+
/** Apply all pending platform migrations. Idempotent — already-applied files are skipped. */
|
|
8
|
+
export declare function migrate(options?: MigrateOptions): Promise<{
|
|
9
|
+
applied: string[];
|
|
10
|
+
}>;
|
|
11
|
+
//# sourceMappingURL=migrate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gDAAgD;IAChD,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAChC;AAED,6FAA6F;AAC7F,wBAAsB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAiD1F"}
|
package/dist/migrate.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// @govcore/schema/migrate — the govcore-migrate runner.
|
|
2
|
+
//
|
|
3
|
+
// NOT edge-safe (uses node:fs + a postgres client). Applies the authored
|
|
4
|
+
// platform migrations in ../migrations in lexical order, each in its own
|
|
5
|
+
// transaction, tracked in `govcore.__govcore_migrations` — kept separate from
|
|
6
|
+
// the app's own Drizzle journal so the two migration streams never overlap
|
|
7
|
+
// (design §5).
|
|
8
|
+
//
|
|
9
|
+
// Runs as the OWNER / DDL role (design §13.2): set GOVCORE_MIGRATE_DATABASE_URL
|
|
10
|
+
// to the owning role's connection string; falls back to DATABASE_URL. The app
|
|
11
|
+
// runtime connects as a separate NON-OWNER role, which RLS binds.
|
|
12
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
13
|
+
import { dirname, join } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import postgres from 'postgres';
|
|
16
|
+
const MIGRATIONS_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'migrations');
|
|
17
|
+
/** Apply all pending platform migrations. Idempotent — already-applied files are skipped. */
|
|
18
|
+
export async function migrate(options = {}) {
|
|
19
|
+
const connectionString = options.connectionString ??
|
|
20
|
+
process.env.GOVCORE_MIGRATE_DATABASE_URL ??
|
|
21
|
+
process.env.DATABASE_URL;
|
|
22
|
+
if (!connectionString) {
|
|
23
|
+
throw new Error('govcore-migrate: set GOVCORE_MIGRATE_DATABASE_URL (owner role) or DATABASE_URL');
|
|
24
|
+
}
|
|
25
|
+
const log = options.log ?? ((m) => console.log(m));
|
|
26
|
+
const sql = postgres(connectionString, { max: 1 });
|
|
27
|
+
const applied = [];
|
|
28
|
+
try {
|
|
29
|
+
await sql.unsafe('CREATE SCHEMA IF NOT EXISTS govcore');
|
|
30
|
+
await sql.unsafe(`CREATE TABLE IF NOT EXISTS govcore.__govcore_migrations (
|
|
31
|
+
name text PRIMARY KEY,
|
|
32
|
+
applied_at timestamptz NOT NULL DEFAULT now()
|
|
33
|
+
)`);
|
|
34
|
+
const done = new Set((await sql `SELECT name FROM govcore.__govcore_migrations`).map((r) => r.name));
|
|
35
|
+
const files = readdirSync(MIGRATIONS_DIR)
|
|
36
|
+
.filter((f) => f.endsWith('.sql'))
|
|
37
|
+
.sort();
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
if (done.has(file))
|
|
40
|
+
continue;
|
|
41
|
+
const ddl = readFileSync(join(MIGRATIONS_DIR, file), 'utf8');
|
|
42
|
+
await sql.begin(async (tx) => {
|
|
43
|
+
await tx.unsafe(ddl);
|
|
44
|
+
await tx `INSERT INTO govcore.__govcore_migrations (name) VALUES (${file})`;
|
|
45
|
+
});
|
|
46
|
+
applied.push(file);
|
|
47
|
+
log(`govcore-migrate: applied ${file}`);
|
|
48
|
+
}
|
|
49
|
+
log(applied.length
|
|
50
|
+
? `govcore-migrate: applied ${applied.length} migration(s)`
|
|
51
|
+
: 'govcore-migrate: already up to date');
|
|
52
|
+
return { applied };
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
await sql.end();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Run when invoked as the `govcore-migrate` bin.
|
|
59
|
+
if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
|
|
60
|
+
migrate().catch((err) => {
|
|
61
|
+
console.error(err);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
64
|
+
}
|