@adminiumjs/meta 0.1.0-rc.1
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/LICENSE +661 -0
- package/dist/bootstrap.d.ts +103 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +225 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/columns.d.ts +44 -0
- package/dist/columns.d.ts.map +1 -0
- package/dist/columns.js +57 -0
- package/dist/columns.js.map +1 -0
- package/dist/connect.d.ts +38 -0
- package/dist/connect.d.ts.map +1 -0
- package/dist/connect.js +57 -0
- package/dist/connect.js.map +1 -0
- package/dist/ids.d.ts +79 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/ids.js +165 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/0001_core_auth.d.ts +9 -0
- package/dist/migrations/0001_core_auth.d.ts.map +1 -0
- package/dist/migrations/0001_core_auth.js +102 -0
- package/dist/migrations/0001_core_auth.js.map +1 -0
- package/dist/migrations/0002_rbac.d.ts +8 -0
- package/dist/migrations/0002_rbac.d.ts.map +1 -0
- package/dist/migrations/0002_rbac.js +75 -0
- package/dist/migrations/0002_rbac.js.map +1 -0
- package/dist/migrations/0003_connections_schema.d.ts +9 -0
- package/dist/migrations/0003_connections_schema.d.ts.map +1 -0
- package/dist/migrations/0003_connections_schema.js +100 -0
- package/dist/migrations/0003_connections_schema.js.map +1 -0
- package/dist/migrations/0004_pages_views.d.ts +8 -0
- package/dist/migrations/0004_pages_views.d.ts.map +1 -0
- package/dist/migrations/0004_pages_views.js +59 -0
- package/dist/migrations/0004_pages_views.js.map +1 -0
- package/dist/migrations/0005_ops.d.ts +9 -0
- package/dist/migrations/0005_ops.d.ts.map +1 -0
- package/dist/migrations/0005_ops.js +104 -0
- package/dist/migrations/0005_ops.js.map +1 -0
- package/dist/migrations/0006_platform.d.ts +11 -0
- package/dist/migrations/0006_platform.d.ts.map +1 -0
- package/dist/migrations/0006_platform.js +259 -0
- package/dist/migrations/0006_platform.js.map +1 -0
- package/dist/migrations/0007_llm_runs.d.ts +33 -0
- package/dist/migrations/0007_llm_runs.d.ts.map +1 -0
- package/dist/migrations/0007_llm_runs.js +62 -0
- package/dist/migrations/0007_llm_runs.js.map +1 -0
- package/dist/migrations/0008_llm_overrides.d.ts +18 -0
- package/dist/migrations/0008_llm_overrides.d.ts.map +1 -0
- package/dist/migrations/0008_llm_overrides.js +24 -0
- package/dist/migrations/0008_llm_overrides.js.map +1 -0
- package/dist/migrations/0009_views_kind.d.ts +20 -0
- package/dist/migrations/0009_views_kind.d.ts.map +1 -0
- package/dist/migrations/0009_views_kind.js +24 -0
- package/dist/migrations/0009_views_kind.js.map +1 -0
- package/dist/migrations/0010_llm_prompt_version_width.d.ts +19 -0
- package/dist/migrations/0010_llm_prompt_version_width.d.ts.map +1 -0
- package/dist/migrations/0010_llm_prompt_version_width.js +28 -0
- package/dist/migrations/0010_llm_prompt_version_width.js.map +1 -0
- package/dist/migrations/index.d.ts +29 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +43 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrator.d.ts +83 -0
- package/dist/migrator.d.ts.map +1 -0
- package/dist/migrator.js +173 -0
- package/dist/migrator.js.map +1 -0
- package/dist/prefix.d.ts +19 -0
- package/dist/prefix.d.ts.map +1 -0
- package/dist/prefix.js +33 -0
- package/dist/prefix.js.map +1 -0
- package/dist/repos/api-keys.d.ts +31 -0
- package/dist/repos/api-keys.d.ts.map +1 -0
- package/dist/repos/api-keys.js +62 -0
- package/dist/repos/api-keys.js.map +1 -0
- package/dist/repos/audit.d.ts +51 -0
- package/dist/repos/audit.d.ts.map +1 -0
- package/dist/repos/audit.js +78 -0
- package/dist/repos/audit.js.map +1 -0
- package/dist/repos/connections.d.ts +90 -0
- package/dist/repos/connections.d.ts.map +1 -0
- package/dist/repos/connections.js +191 -0
- package/dist/repos/connections.js.map +1 -0
- package/dist/repos/email-templates.d.ts +47 -0
- package/dist/repos/email-templates.d.ts.map +1 -0
- package/dist/repos/email-templates.js +93 -0
- package/dist/repos/email-templates.js.map +1 -0
- package/dist/repos/exports.d.ts +67 -0
- package/dist/repos/exports.d.ts.map +1 -0
- package/dist/repos/exports.js +132 -0
- package/dist/repos/exports.js.map +1 -0
- package/dist/repos/files.d.ts +52 -0
- package/dist/repos/files.d.ts.map +1 -0
- package/dist/repos/files.js +86 -0
- package/dist/repos/files.js.map +1 -0
- package/dist/repos/imports.d.ts +71 -0
- package/dist/repos/imports.d.ts.map +1 -0
- package/dist/repos/imports.js +111 -0
- package/dist/repos/imports.js.map +1 -0
- package/dist/repos/index.d.ts +26 -0
- package/dist/repos/index.d.ts.map +1 -0
- package/dist/repos/index.js +26 -0
- package/dist/repos/index.js.map +1 -0
- package/dist/repos/jobs.d.ts +71 -0
- package/dist/repos/jobs.d.ts.map +1 -0
- package/dist/repos/jobs.js +187 -0
- package/dist/repos/jobs.js.map +1 -0
- package/dist/repos/llm-overrides.d.ts +89 -0
- package/dist/repos/llm-overrides.d.ts.map +1 -0
- package/dist/repos/llm-overrides.js +205 -0
- package/dist/repos/llm-overrides.js.map +1 -0
- package/dist/repos/llm-runs.d.ts +114 -0
- package/dist/repos/llm-runs.d.ts.map +1 -0
- package/dist/repos/llm-runs.js +214 -0
- package/dist/repos/llm-runs.js.map +1 -0
- package/dist/repos/notification-prefs.d.ts +34 -0
- package/dist/repos/notification-prefs.d.ts.map +1 -0
- package/dist/repos/notification-prefs.js +80 -0
- package/dist/repos/notification-prefs.js.map +1 -0
- package/dist/repos/notifications.d.ts +60 -0
- package/dist/repos/notifications.d.ts.map +1 -0
- package/dist/repos/notifications.js +107 -0
- package/dist/repos/notifications.js.map +1 -0
- package/dist/repos/overrides.d.ts +59 -0
- package/dist/repos/overrides.d.ts.map +1 -0
- package/dist/repos/overrides.js +143 -0
- package/dist/repos/overrides.js.map +1 -0
- package/dist/repos/pages.d.ts +192 -0
- package/dist/repos/pages.d.ts.map +1 -0
- package/dist/repos/pages.js +374 -0
- package/dist/repos/pages.js.map +1 -0
- package/dist/repos/password-resets.d.ts +32 -0
- package/dist/repos/password-resets.d.ts.map +1 -0
- package/dist/repos/password-resets.js +56 -0
- package/dist/repos/password-resets.js.map +1 -0
- package/dist/repos/permissions.d.ts +28 -0
- package/dist/repos/permissions.d.ts.map +1 -0
- package/dist/repos/permissions.js +100 -0
- package/dist/repos/permissions.js.map +1 -0
- package/dist/repos/roles.d.ts +33 -0
- package/dist/repos/roles.d.ts.map +1 -0
- package/dist/repos/roles.js +89 -0
- package/dist/repos/roles.js.map +1 -0
- package/dist/repos/scheduled-reports.d.ts +73 -0
- package/dist/repos/scheduled-reports.d.ts.map +1 -0
- package/dist/repos/scheduled-reports.js +132 -0
- package/dist/repos/scheduled-reports.js.map +1 -0
- package/dist/repos/sessions.d.ts +33 -0
- package/dist/repos/sessions.d.ts.map +1 -0
- package/dist/repos/sessions.js +79 -0
- package/dist/repos/sessions.js.map +1 -0
- package/dist/repos/settings.d.ts +25 -0
- package/dist/repos/settings.d.ts.map +1 -0
- package/dist/repos/settings.js +73 -0
- package/dist/repos/settings.js.map +1 -0
- package/dist/repos/snapshots.d.ts +79 -0
- package/dist/repos/snapshots.d.ts.map +1 -0
- package/dist/repos/snapshots.js +191 -0
- package/dist/repos/snapshots.js.map +1 -0
- package/dist/repos/user-prefs.d.ts +56 -0
- package/dist/repos/user-prefs.d.ts.map +1 -0
- package/dist/repos/user-prefs.js +147 -0
- package/dist/repos/user-prefs.js.map +1 -0
- package/dist/repos/users.d.ts +38 -0
- package/dist/repos/users.d.ts.map +1 -0
- package/dist/repos/users.js +103 -0
- package/dist/repos/users.js.map +1 -0
- package/dist/repos/util.d.ts +45 -0
- package/dist/repos/util.d.ts.map +1 -0
- package/dist/repos/util.js +81 -0
- package/dist/repos/util.js.map +1 -0
- package/dist/repos/views.d.ts +75 -0
- package/dist/repos/views.d.ts.map +1 -0
- package/dist/repos/views.js +180 -0
- package/dist/repos/views.js.map +1 -0
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/json-payloads.d.ts +479 -0
- package/dist/schema/json-payloads.d.ts.map +1 -0
- package/dist/schema/json-payloads.js +340 -0
- package/dist/schema/json-payloads.js.map +1 -0
- package/dist/schema/settings-registry.d.ts +187 -0
- package/dist/schema/settings-registry.d.ts.map +1 -0
- package/dist/schema/settings-registry.js +183 -0
- package/dist/schema/settings-registry.js.map +1 -0
- package/dist/schema/tables.d.ts +568 -0
- package/dist/schema/tables.d.ts.map +1 -0
- package/dist/schema/tables.js +51 -0
- package/dist/schema/tables.js.map +1 -0
- package/package.json +33 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-run bootstrap (07-meta-store.md §6): migrations + built-in role and
|
|
3
|
+
* permission seeds + `system.*` settings, plus the guarded creation of the
|
|
4
|
+
* first super admin. Re-running is safe — every step is guarded by existence
|
|
5
|
+
* checks and seeds use slug/key natural-key upserts, so upgrades can add new
|
|
6
|
+
* built-in permission rows without touching user edits.
|
|
7
|
+
*/
|
|
8
|
+
import type { MetaDb } from './connect.js';
|
|
9
|
+
import { type SystemActionKey } from './schema/json-payloads.js';
|
|
10
|
+
import { type User } from './repos/users.js';
|
|
11
|
+
export interface BuiltinRoleDef {
|
|
12
|
+
slug: string;
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
/** System action grants seeded as `{ allowed: true }` rows (§6 step 3). */
|
|
16
|
+
systemActions: readonly SystemActionKey[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The four built-in roles (§3.8, §6). Super-admin gets every system action
|
|
20
|
+
* (the RBAC layer short-circuits for `super-admin` anyway); admin gets the §6
|
|
21
|
+
* management set; editor/viewer get table/page grants dynamically at
|
|
22
|
+
* generation time, no system actions.
|
|
23
|
+
*
|
|
24
|
+
* Reserved-key footprint: "every system action" includes ALL FOUR
|
|
25
|
+
* `RESERVED_SYSTEM_ACTION_KEYS` (automations/webhooks/manifests/sql.run — no
|
|
26
|
+
* v1 enforcement point). That is deliberate and super-admin-ONLY: the role is
|
|
27
|
+
* definitionally full-access (and the RBAC layer short-circuits for
|
|
28
|
+
* `super-admin` anyway), so its reserved rows are inert declarations of
|
|
29
|
+
* intent, not capabilities. Every other role seeds enforced keys only —
|
|
30
|
+
* read-only surfaces render stored grants verbatim (e.g. the API-keys scope
|
|
31
|
+
* chips), and a reserved key there would advertise a feature that does not
|
|
32
|
+
* exist. When a deferred feature lands, re-add its key to the role defs in
|
|
33
|
+
* the same change that moves it out of `RESERVED_SYSTEM_ACTION_KEYS`;
|
|
34
|
+
* `seedBuiltinRoles` runs at every boot and backfills the missing rows on
|
|
35
|
+
* existing installs.
|
|
36
|
+
*/
|
|
37
|
+
export declare const BUILTIN_ROLES: readonly BuiltinRoleDef[];
|
|
38
|
+
export declare class FirstUserExistsError extends Error {
|
|
39
|
+
name: string;
|
|
40
|
+
constructor();
|
|
41
|
+
}
|
|
42
|
+
export declare class BootstrapStateError extends Error {
|
|
43
|
+
name: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Seed the built-in roles and their permission baselines. Idempotent
|
|
47
|
+
* natural-key upserts: existing roles are left untouched (user renames of
|
|
48
|
+
* non-super-admin built-ins survive), and only missing permission rows are
|
|
49
|
+
* inserted (user permission edits survive upgrades).
|
|
50
|
+
*/
|
|
51
|
+
export declare function seedBuiltinRoles(meta: MetaDb, at?: number): Promise<{
|
|
52
|
+
createdRoles: string[];
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Seed the `system.*` settings keys (§6 step 4). Only system identity keys
|
|
56
|
+
* are written — behavioral settings stay unset so a fresh install resolves to
|
|
57
|
+
* registry defaults (indigo / system theme / en_US) without a settings row.
|
|
58
|
+
*/
|
|
59
|
+
export declare function seedSystemSettings(meta: MetaDb, at?: number): Promise<void>;
|
|
60
|
+
export interface FirstRunResult {
|
|
61
|
+
appliedMigrations: string[];
|
|
62
|
+
createdRoles: string[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Everything a fresh database needs from a single call: apply all pending
|
|
66
|
+
* migrations, seed built-in roles/permissions, seed system settings.
|
|
67
|
+
* Safe to run at every boot (§6).
|
|
68
|
+
*/
|
|
69
|
+
export declare function firstRun(meta: MetaDb, at?: number): Promise<FirstRunResult>;
|
|
70
|
+
export interface CreateFirstSuperAdminInput {
|
|
71
|
+
email: string;
|
|
72
|
+
/** Defaults to the email local part. */
|
|
73
|
+
name?: string;
|
|
74
|
+
/** argon2id — hashing happens in the server. */
|
|
75
|
+
passwordHash: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Create the very first user and grant `super-admin` (§6 step 2). This is the
|
|
79
|
+
* entire attack surface of a self-hosted first boot, so it is once-only by
|
|
80
|
+
* CONSTRUCTION rather than by a check-then-act read:
|
|
81
|
+
*
|
|
82
|
+
* everything runs in ONE transaction that first INSERTs the
|
|
83
|
+
* `system.superAdminCreatedAt` claim row. `adminium_settings.key` is a PRIMARY
|
|
84
|
+
* KEY, so a second — or a concurrently racing — attempt either sees the row
|
|
85
|
+
* (fast path below) or loses the INSERT to a duplicate-key violation, and its
|
|
86
|
+
* whole transaction rolls back. Either way exactly one super admin can ever be
|
|
87
|
+
* created, on every dialect and across processes/replicas; a plain
|
|
88
|
+
* `count() === 0` read could not promise that (two racers both read zero).
|
|
89
|
+
*
|
|
90
|
+
* Throws {@link FirstUserExistsError} when the claim is already taken or any
|
|
91
|
+
* user already exists.
|
|
92
|
+
*/
|
|
93
|
+
export declare function createFirstSuperAdmin(meta: MetaDb, input: CreateFirstSuperAdminInput, at?: number): Promise<User>;
|
|
94
|
+
/**
|
|
95
|
+
* Zero users AND an unclaimed bootstrap ⇒ setup mode: the server serves only
|
|
96
|
+
* /setup/* (§6 step 1).
|
|
97
|
+
*
|
|
98
|
+
* The claim is checked as well as the user count so that bootstrap is
|
|
99
|
+
* PERMANENTLY closed once it has run: deleting every user later must not
|
|
100
|
+
* re-open an unauthenticated super-admin-creation endpoint (M10-T04).
|
|
101
|
+
*/
|
|
102
|
+
export declare function isBootstrapRequired(meta: MetaDb): Promise<boolean>;
|
|
103
|
+
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAIrF,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAGxD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,aAAa,EAAE,SAAS,eAAe,EAAE,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,cAAc,EAyBlD,CAAC;AAEF,qBAAa,oBAAqB,SAAQ,KAAK;IACpC,IAAI,SAA0B;;CAIxC;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IACnC,IAAI,SAAyB;CACvC;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAE,MAAmB,GAAG,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAsBjH;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAE,MAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAS7F;AAED,MAAM,WAAW,cAAc;IAC7B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAE,MAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,CAK7F;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;CACtB;AAQD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,0BAA0B,EACjC,EAAE,GAAE,MAAmB,GACtB,OAAO,CAAC,IAAI,CAAC,CA4Cf;AAqBD;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQxE"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-run bootstrap (07-meta-store.md §6): migrations + built-in role and
|
|
3
|
+
* permission seeds + `system.*` settings, plus the guarded creation of the
|
|
4
|
+
* first super admin. Re-running is safe — every step is guarded by existence
|
|
5
|
+
* checks and seeds use slug/key natural-key upserts, so upgrades can add new
|
|
6
|
+
* built-in permission rows without touching user edits.
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
import { applyMigrations } from './migrator.js';
|
|
10
|
+
import { SYSTEM_ACTION_KEYS } from './schema/json-payloads.js';
|
|
11
|
+
import { permissionsRepo } from './repos/permissions.js';
|
|
12
|
+
import { rolesRepo } from './repos/roles.js';
|
|
13
|
+
import { settingsRepo } from './repos/settings.js';
|
|
14
|
+
import { usersRepo } from './repos/users.js';
|
|
15
|
+
import { packJson } from './repos/util.js';
|
|
16
|
+
/**
|
|
17
|
+
* The four built-in roles (§3.8, §6). Super-admin gets every system action
|
|
18
|
+
* (the RBAC layer short-circuits for `super-admin` anyway); admin gets the §6
|
|
19
|
+
* management set; editor/viewer get table/page grants dynamically at
|
|
20
|
+
* generation time, no system actions.
|
|
21
|
+
*
|
|
22
|
+
* Reserved-key footprint: "every system action" includes ALL FOUR
|
|
23
|
+
* `RESERVED_SYSTEM_ACTION_KEYS` (automations/webhooks/manifests/sql.run — no
|
|
24
|
+
* v1 enforcement point). That is deliberate and super-admin-ONLY: the role is
|
|
25
|
+
* definitionally full-access (and the RBAC layer short-circuits for
|
|
26
|
+
* `super-admin` anyway), so its reserved rows are inert declarations of
|
|
27
|
+
* intent, not capabilities. Every other role seeds enforced keys only —
|
|
28
|
+
* read-only surfaces render stored grants verbatim (e.g. the API-keys scope
|
|
29
|
+
* chips), and a reserved key there would advertise a feature that does not
|
|
30
|
+
* exist. When a deferred feature lands, re-add its key to the role defs in
|
|
31
|
+
* the same change that moves it out of `RESERVED_SYSTEM_ACTION_KEYS`;
|
|
32
|
+
* `seedBuiltinRoles` runs at every boot and backfills the missing rows on
|
|
33
|
+
* existing installs.
|
|
34
|
+
*/
|
|
35
|
+
export const BUILTIN_ROLES = [
|
|
36
|
+
{
|
|
37
|
+
slug: 'super-admin',
|
|
38
|
+
name: 'Super Admin',
|
|
39
|
+
description: 'Full access to everything, including users, roles, and settings.',
|
|
40
|
+
systemActions: SYSTEM_ACTION_KEYS,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
slug: 'admin',
|
|
44
|
+
name: 'Admin',
|
|
45
|
+
description: 'Manages connections, schema, and LLM assist.',
|
|
46
|
+
systemActions: ['connections.manage', 'schema.remap', 'llm.run'],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
slug: 'editor',
|
|
50
|
+
name: 'Editor',
|
|
51
|
+
description: 'Reads, creates, and updates records; views pages.',
|
|
52
|
+
systemActions: [],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
slug: 'viewer',
|
|
56
|
+
name: 'Viewer',
|
|
57
|
+
description: 'Read-only access to records and pages.',
|
|
58
|
+
systemActions: [],
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
export class FirstUserExistsError extends Error {
|
|
62
|
+
name = 'FirstUserExistsError';
|
|
63
|
+
constructor() {
|
|
64
|
+
super('createFirstSuperAdmin is only allowed when zero users exist.');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export class BootstrapStateError extends Error {
|
|
68
|
+
name = 'BootstrapStateError';
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Seed the built-in roles and their permission baselines. Idempotent
|
|
72
|
+
* natural-key upserts: existing roles are left untouched (user renames of
|
|
73
|
+
* non-super-admin built-ins survive), and only missing permission rows are
|
|
74
|
+
* inserted (user permission edits survive upgrades).
|
|
75
|
+
*/
|
|
76
|
+
export async function seedBuiltinRoles(meta, at = Date.now()) {
|
|
77
|
+
const roles = rolesRepo(meta);
|
|
78
|
+
const permissions = permissionsRepo(meta);
|
|
79
|
+
const createdRoles = [];
|
|
80
|
+
for (const def of BUILTIN_ROLES) {
|
|
81
|
+
let role = await roles.findBySlug(def.slug);
|
|
82
|
+
if (!role) {
|
|
83
|
+
role = await roles.create({ slug: def.slug, name: def.name, description: def.description, isBuiltin: true }, at);
|
|
84
|
+
createdRoles.push(def.slug);
|
|
85
|
+
}
|
|
86
|
+
for (const action of def.systemActions) {
|
|
87
|
+
const existing = await permissions.find(role.id, 'system', action);
|
|
88
|
+
if (!existing) {
|
|
89
|
+
await permissions.grant(role.id, 'system', action, { allowed: true });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { createdRoles };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Seed the `system.*` settings keys (§6 step 4). Only system identity keys
|
|
97
|
+
* are written — behavioral settings stay unset so a fresh install resolves to
|
|
98
|
+
* registry defaults (indigo / system theme / en_US) without a settings row.
|
|
99
|
+
*/
|
|
100
|
+
export async function seedSystemSettings(meta, at = Date.now()) {
|
|
101
|
+
const settings = settingsRepo(meta);
|
|
102
|
+
if ((await settings.get('system.instanceId')) === null) {
|
|
103
|
+
await settings.set('system.instanceId', randomUUID(), { at });
|
|
104
|
+
}
|
|
105
|
+
if ((await settings.get('system.bootstrappedAt')) === null) {
|
|
106
|
+
await settings.set('system.bootstrappedAt', at, { at });
|
|
107
|
+
await settings.set('system.configVersion', 1, { at });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Everything a fresh database needs from a single call: apply all pending
|
|
112
|
+
* migrations, seed built-in roles/permissions, seed system settings.
|
|
113
|
+
* Safe to run at every boot (§6).
|
|
114
|
+
*/
|
|
115
|
+
export async function firstRun(meta, at = Date.now()) {
|
|
116
|
+
const { applied } = await applyMigrations(meta.db, { dialect: meta.dialect });
|
|
117
|
+
const { createdRoles } = await seedBuiltinRoles(meta, at);
|
|
118
|
+
await seedSystemSettings(meta, at);
|
|
119
|
+
return { appliedMigrations: applied, createdRoles };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The settings key whose ROW PRESENCE claims the one-and-only first-run
|
|
123
|
+
* bootstrap (M10-T04). See the registry entry for why it exists.
|
|
124
|
+
*/
|
|
125
|
+
const SUPER_ADMIN_CLAIM_KEY = 'system.superAdminCreatedAt';
|
|
126
|
+
/**
|
|
127
|
+
* Create the very first user and grant `super-admin` (§6 step 2). This is the
|
|
128
|
+
* entire attack surface of a self-hosted first boot, so it is once-only by
|
|
129
|
+
* CONSTRUCTION rather than by a check-then-act read:
|
|
130
|
+
*
|
|
131
|
+
* everything runs in ONE transaction that first INSERTs the
|
|
132
|
+
* `system.superAdminCreatedAt` claim row. `adminium_settings.key` is a PRIMARY
|
|
133
|
+
* KEY, so a second — or a concurrently racing — attempt either sees the row
|
|
134
|
+
* (fast path below) or loses the INSERT to a duplicate-key violation, and its
|
|
135
|
+
* whole transaction rolls back. Either way exactly one super admin can ever be
|
|
136
|
+
* created, on every dialect and across processes/replicas; a plain
|
|
137
|
+
* `count() === 0` read could not promise that (two racers both read zero).
|
|
138
|
+
*
|
|
139
|
+
* Throws {@link FirstUserExistsError} when the claim is already taken or any
|
|
140
|
+
* user already exists.
|
|
141
|
+
*/
|
|
142
|
+
export async function createFirstSuperAdmin(meta, input, at = Date.now()) {
|
|
143
|
+
return meta.db.transaction().execute(async (trx) => {
|
|
144
|
+
const tmeta = { db: trx, dialect: meta.dialect };
|
|
145
|
+
const users = usersRepo(tmeta);
|
|
146
|
+
const roles = rolesRepo(tmeta);
|
|
147
|
+
// Fast, readable rejections for the common (non-racing) cases.
|
|
148
|
+
const claimed = await trx
|
|
149
|
+
.selectFrom('adminium_settings')
|
|
150
|
+
.select('key')
|
|
151
|
+
.where('key', '=', SUPER_ADMIN_CLAIM_KEY)
|
|
152
|
+
.executeTakeFirst();
|
|
153
|
+
if (claimed !== undefined)
|
|
154
|
+
throw new FirstUserExistsError();
|
|
155
|
+
if ((await users.count()) > 0)
|
|
156
|
+
throw new FirstUserExistsError();
|
|
157
|
+
const superAdmin = await roles.findBySlug('super-admin');
|
|
158
|
+
if (!superAdmin) {
|
|
159
|
+
throw new BootstrapStateError('built-in roles missing — run firstRun() before createFirstSuperAdmin().');
|
|
160
|
+
}
|
|
161
|
+
// The claim itself. A racer that passed the reads above collides here and
|
|
162
|
+
// its transaction aborts — this INSERT, not the reads, is the guarantee.
|
|
163
|
+
try {
|
|
164
|
+
await trx
|
|
165
|
+
.insertInto('adminium_settings')
|
|
166
|
+
.values({ key: SUPER_ADMIN_CLAIM_KEY, value: packJson(at), updatedAt: at, updatedBy: null })
|
|
167
|
+
.execute();
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
// The only row that can already hold this PK is another bootstrap's
|
|
171
|
+
// claim (nothing else ever writes this key), so a failed INSERT means
|
|
172
|
+
// we lost the race. Anything genuinely unexpected still surfaces: it
|
|
173
|
+
// would have to be a non-duplicate write error, which we rethrow.
|
|
174
|
+
if (isDuplicateKeyError(error))
|
|
175
|
+
throw new FirstUserExistsError();
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
const name = input.name ?? input.email.split('@')[0] ?? input.email;
|
|
179
|
+
const user = await users.create({ email: input.email, name, passwordHash: input.passwordHash, status: 'active' }, at);
|
|
180
|
+
await roles.assignToUser(user.id, superAdmin.id, null, at);
|
|
181
|
+
return user;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Duplicate-key detection across the three v1 dialects: SQLite
|
|
186
|
+
* (`SQLITE_CONSTRAINT_PRIMARYKEY` / "UNIQUE constraint failed"), Postgres
|
|
187
|
+
* (SQLSTATE 23505), MySQL/MariaDB (errno 1062 / `ER_DUP_ENTRY`). Falls back to
|
|
188
|
+
* a message probe so an unrecognized driver shape still reads as a duplicate
|
|
189
|
+
* rather than a 500 on the security-critical path.
|
|
190
|
+
*/
|
|
191
|
+
function isDuplicateKeyError(error) {
|
|
192
|
+
if (error === null || typeof error !== 'object')
|
|
193
|
+
return false;
|
|
194
|
+
const e = error;
|
|
195
|
+
if (typeof e.code === 'string') {
|
|
196
|
+
if (e.code === '23505')
|
|
197
|
+
return true; // Postgres unique_violation
|
|
198
|
+
if (e.code.startsWith('SQLITE_CONSTRAINT'))
|
|
199
|
+
return true;
|
|
200
|
+
if (e.code === 'ER_DUP_ENTRY')
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
if (e.errno === 1062)
|
|
204
|
+
return true; // MySQL/MariaDB
|
|
205
|
+
return typeof e.message === 'string' && /duplicate|unique constraint/i.test(e.message);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Zero users AND an unclaimed bootstrap ⇒ setup mode: the server serves only
|
|
209
|
+
* /setup/* (§6 step 1).
|
|
210
|
+
*
|
|
211
|
+
* The claim is checked as well as the user count so that bootstrap is
|
|
212
|
+
* PERMANENTLY closed once it has run: deleting every user later must not
|
|
213
|
+
* re-open an unauthenticated super-admin-creation endpoint (M10-T04).
|
|
214
|
+
*/
|
|
215
|
+
export async function isBootstrapRequired(meta) {
|
|
216
|
+
if ((await usersRepo(meta).count()) > 0)
|
|
217
|
+
return false;
|
|
218
|
+
const claimed = await meta.db
|
|
219
|
+
.selectFrom('adminium_settings')
|
|
220
|
+
.select('key')
|
|
221
|
+
.where('key', '=', SUPER_ADMIN_CLAIM_KEY)
|
|
222
|
+
.executeTakeFirst();
|
|
223
|
+
return claimed === undefined;
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable column-type helpers (07-meta-store.md §2.1).
|
|
3
|
+
*
|
|
4
|
+
* Every migration receives a {@link ColumnHelpers} instance built for the
|
|
5
|
+
* target dialect; this module is one of the few places allowed to branch on
|
|
6
|
+
* dialect (07-meta-store.md acceptance #1).
|
|
7
|
+
*
|
|
8
|
+
* | helper | PostgreSQL | MySQL/MariaDB | SQLite |
|
|
9
|
+
* |----------|--------------|---------------|-----------|
|
|
10
|
+
* | id | varchar(36) | char(36) | char(36) |
|
|
11
|
+
* | str(n) | varchar(n) | varchar(n) | text |
|
|
12
|
+
* | text | text | text | text |
|
|
13
|
+
* | json | jsonb | json | text |
|
|
14
|
+
* | bool | boolean | tinyint(1) | integer |
|
|
15
|
+
* | int | integer | integer | integer |
|
|
16
|
+
* | bigint | bigint | bigint | integer |
|
|
17
|
+
* | ts | bigint | bigint | integer | epoch ms UTC
|
|
18
|
+
*/
|
|
19
|
+
import { type ColumnDataType, type RawBuilder } from 'kysely';
|
|
20
|
+
export type MetaDialect = 'postgres' | 'mysql' | 'sqlite';
|
|
21
|
+
/** What Kysely's `addColumn` accepts: a known data-type string or raw SQL. */
|
|
22
|
+
export type PortableColumnType = ColumnDataType | RawBuilder<unknown>;
|
|
23
|
+
export interface ColumnHelpers {
|
|
24
|
+
readonly dialect: MetaDialect;
|
|
25
|
+
/** Type-prefixed ULID primary/foreign key column. */
|
|
26
|
+
readonly id: PortableColumnType;
|
|
27
|
+
/** Indexable/unique string, n ≤ 320. */
|
|
28
|
+
str(n: number): PortableColumnType;
|
|
29
|
+
/** Unbounded text — never indexed. */
|
|
30
|
+
readonly text: PortableColumnType;
|
|
31
|
+
/** Opaque JSON payload — serialized/parsed + Zod-validated in repos, never queried with JSON operators. */
|
|
32
|
+
readonly json: PortableColumnType;
|
|
33
|
+
/** Boolean — repos coerce reads to JS boolean. */
|
|
34
|
+
readonly bool: PortableColumnType;
|
|
35
|
+
readonly int: PortableColumnType;
|
|
36
|
+
/** Values always < 2^53; returned as JS number. */
|
|
37
|
+
readonly bigint: PortableColumnType;
|
|
38
|
+
/** Epoch milliseconds UTC — never native datetime (07-meta-store.md §2.1). */
|
|
39
|
+
readonly ts: PortableColumnType;
|
|
40
|
+
/** Dialect-correct boolean literal for DDL `DEFAULT` clauses. */
|
|
41
|
+
boolDefault(value: boolean): RawBuilder<unknown>;
|
|
42
|
+
}
|
|
43
|
+
export declare function columnHelpers(dialect: MetaDialect): ColumnHelpers;
|
|
44
|
+
//# sourceMappingURL=columns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"columns.d.ts","sourceRoot":"","sources":["../src/columns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAO,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEnE,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1D,8EAA8E;AAC9E,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;AAEtE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,EAAE,kBAAkB,CAAC;IAChC,wCAAwC;IACxC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAAC;IACnC,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,2GAA2G;IAC3G,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,kBAAkB,CAAC;IACjC,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,EAAE,kBAAkB,CAAC;IAChC,iEAAiE;IACjE,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;CAClD;AAMD,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,aAAa,CAkCjE"}
|
package/dist/columns.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable column-type helpers (07-meta-store.md §2.1).
|
|
3
|
+
*
|
|
4
|
+
* Every migration receives a {@link ColumnHelpers} instance built for the
|
|
5
|
+
* target dialect; this module is one of the few places allowed to branch on
|
|
6
|
+
* dialect (07-meta-store.md acceptance #1).
|
|
7
|
+
*
|
|
8
|
+
* | helper | PostgreSQL | MySQL/MariaDB | SQLite |
|
|
9
|
+
* |----------|--------------|---------------|-----------|
|
|
10
|
+
* | id | varchar(36) | char(36) | char(36) |
|
|
11
|
+
* | str(n) | varchar(n) | varchar(n) | text |
|
|
12
|
+
* | text | text | text | text |
|
|
13
|
+
* | json | jsonb | json | text |
|
|
14
|
+
* | bool | boolean | tinyint(1) | integer |
|
|
15
|
+
* | int | integer | integer | integer |
|
|
16
|
+
* | bigint | bigint | bigint | integer |
|
|
17
|
+
* | ts | bigint | bigint | integer | epoch ms UTC
|
|
18
|
+
*/
|
|
19
|
+
import { sql } from 'kysely';
|
|
20
|
+
// 2048 covers the longest legitimate short-string columns (URLs in notifications/webhooks);
|
|
21
|
+
// anything longer belongs in a `text` column. MySQL utf8mb4 varchar(2048) is fine un-indexed.
|
|
22
|
+
const MAX_STR = 2048;
|
|
23
|
+
export function columnHelpers(dialect) {
|
|
24
|
+
const str = (n) => {
|
|
25
|
+
if (!Number.isInteger(n) || n < 1 || n > MAX_STR) {
|
|
26
|
+
throw new RangeError(`str(n): n must be an integer in [1, ${MAX_STR}], got ${n}`);
|
|
27
|
+
}
|
|
28
|
+
return dialect === 'sqlite' ? 'text' : `varchar(${n})`;
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
dialect,
|
|
32
|
+
// Ids are type-prefixed ULIDs of VARIABLE length (3-4 char prefix + '_' +
|
|
33
|
+
// 26 ULID chars, e.g. usr_… = 30, view_… = 31). Postgres `char(36)`
|
|
34
|
+
// (bpchar) blank-pads to 36 on write and hands the padding back on every
|
|
35
|
+
// read — 'view_…' comes back as 'view_… ', breaking id round-trips.
|
|
36
|
+
// MySQL strips CHAR pad spaces at retrieval and SQLite ignores the length
|
|
37
|
+
// entirely, so only Postgres deviates from the 07-meta-store.md §2.1 table.
|
|
38
|
+
//
|
|
39
|
+
// DECISION (2026-07-20, pre-release): this change alters the DDL that the
|
|
40
|
+
// already-written migrations 0001–0009 emit on postgres, with no
|
|
41
|
+
// compensating ALTER, and the checksum ledger cannot see it (it hashes
|
|
42
|
+
// migration sources, not this helper). That is acceptable exactly once:
|
|
43
|
+
// no Adminium release exists and no deployed pg meta store predates it.
|
|
44
|
+
// After v1.0 ships, a column-type change like this MUST ship as a new
|
|
45
|
+
// migration instead — never edit this helper's emitted types again.
|
|
46
|
+
id: dialect === 'postgres' ? 'varchar(36)' : sql `char(36)`,
|
|
47
|
+
str,
|
|
48
|
+
text: 'text',
|
|
49
|
+
json: dialect === 'postgres' ? 'jsonb' : dialect === 'mysql' ? 'json' : 'text',
|
|
50
|
+
bool: dialect === 'postgres' ? 'boolean' : dialect === 'mysql' ? sql `tinyint(1)` : 'integer',
|
|
51
|
+
int: 'integer',
|
|
52
|
+
bigint: dialect === 'sqlite' ? 'integer' : 'bigint',
|
|
53
|
+
ts: dialect === 'sqlite' ? 'integer' : 'bigint',
|
|
54
|
+
boolDefault: (value) => dialect === 'postgres' ? (value ? sql `true` : sql `false`) : value ? sql `1` : sql `0`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=columns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"columns.js","sourceRoot":"","sources":["../src/columns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,GAAG,EAAwC,MAAM,QAAQ,CAAC;AA4BnE,4FAA4F;AAC5F,8FAA8F;AAC9F,MAAM,OAAO,GAAG,IAAI,CAAC;AAErB,MAAM,UAAU,aAAa,CAAC,OAAoB;IAChD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAsB,EAAE;QAC5C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,OAAO,EAAE,CAAC;YACjD,MAAM,IAAI,UAAU,CAAC,uCAAuC,OAAO,UAAU,CAAC,EAAE,CAAC,CAAC;QACpF,CAAC;QACD,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,WAAW,CAAC,GAAsB,CAAC;IAC7E,CAAC,CAAC;IACF,OAAO;QACL,OAAO;QACP,0EAA0E;QAC1E,oEAAoE;QACpE,yEAAyE;QACzE,wEAAwE;QACxE,0EAA0E;QAC1E,4EAA4E;QAC5E,EAAE;QACF,0EAA0E;QAC1E,iEAAiE;QACjE,uEAAuE;QACvE,wEAAwE;QACxE,wEAAwE;QACxE,sEAAsE;QACtE,oEAAoE;QACpE,EAAE,EAAE,OAAO,KAAK,UAAU,CAAC,CAAC,CAAE,aAAgC,CAAC,CAAC,CAAC,GAAG,CAAA,UAAU;QAC9E,GAAG;QACH,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;QAC9E,IAAI,EAAE,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAA,YAAY,CAAC,CAAC,CAAC,SAAS;QAC5F,GAAG,EAAE,SAAS;QACd,MAAM,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;QACnD,EAAE,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;QAC/C,WAAW,EAAE,CAAC,KAAc,EAAE,EAAE,CAC9B,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAA,MAAM,CAAC,CAAC,CAAC,GAAG,CAAA,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAA,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA,GAAG;KACtF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta-store Kysely factories (07-meta-store.md §1 connect.ts, §5.2).
|
|
3
|
+
*
|
|
4
|
+
* Database drivers are injected by the caller (apps/server or tests):
|
|
5
|
+
* each factory accepts the corresponding Kysely dialect config carrying a
|
|
6
|
+
* live driver instance/pool — this package declares no driver dependencies.
|
|
7
|
+
*
|
|
8
|
+
* The meta instance runs `CamelCasePlugin` (07-meta-store.md §2.2): TS models
|
|
9
|
+
* are camelCase, physical columns snake_case. The dynamic source-DB instance
|
|
10
|
+
* (owned by the server) keeps raw names and never touches adminium_* tables.
|
|
11
|
+
*/
|
|
12
|
+
import { Kysely, type MysqlDialectConfig, type PostgresDialectConfig, type SqliteDialectConfig } from 'kysely';
|
|
13
|
+
import type { MetaDialect } from './columns.js';
|
|
14
|
+
import type { MetaDB } from './schema/tables.js';
|
|
15
|
+
/** The single handle every repo and the migrator operate on. */
|
|
16
|
+
export interface MetaDb {
|
|
17
|
+
db: Kysely<MetaDB>;
|
|
18
|
+
dialect: MetaDialect;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* SQLite meta-store around an injected better-sqlite3-compatible database.
|
|
22
|
+
* Call {@link initMetaDb} (or `applyMigrations`/`firstRun`, which do it for
|
|
23
|
+
* you) before FK-dependent work — SQLite needs `PRAGMA foreign_keys = ON`
|
|
24
|
+
* per connection (07-meta-store.md §2.2).
|
|
25
|
+
*/
|
|
26
|
+
export declare function createSqliteMetaDb(config: SqliteDialectConfig): MetaDb;
|
|
27
|
+
/**
|
|
28
|
+
* PostgreSQL meta-store around an injected `pg` Pool. The pool must parse
|
|
29
|
+
* int8 as JS number (all ts values < 2^53 — 07-meta-store.md §2.1).
|
|
30
|
+
*/
|
|
31
|
+
export declare function createPostgresMetaDb(config: PostgresDialectConfig): MetaDb;
|
|
32
|
+
/** MySQL/MariaDB meta-store around an injected `mysql2` pool. */
|
|
33
|
+
export declare function createMysqlMetaDb(config: MysqlDialectConfig): MetaDb;
|
|
34
|
+
/** One-time per-connection setup (currently: SQLite FK pragma). */
|
|
35
|
+
export declare function initMetaDb(meta: MetaDb): Promise<void>;
|
|
36
|
+
/** Release the underlying pool/connection. */
|
|
37
|
+
export declare function destroyMetaDb(meta: MetaDb): Promise<void>;
|
|
38
|
+
//# sourceMappingURL=connect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAEL,MAAM,EAKN,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACzB,MAAM,QAAQ,CAAC;AAEhB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,gEAAgE;AAChE,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACnB,OAAO,EAAE,WAAW,CAAC;CACtB;AAiBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAEtE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAE1E;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAEpE;AAED,mEAAmE;AACnE,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5D;AAED,8CAA8C;AAC9C,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/D"}
|
package/dist/connect.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta-store Kysely factories (07-meta-store.md §1 connect.ts, §5.2).
|
|
3
|
+
*
|
|
4
|
+
* Database drivers are injected by the caller (apps/server or tests):
|
|
5
|
+
* each factory accepts the corresponding Kysely dialect config carrying a
|
|
6
|
+
* live driver instance/pool — this package declares no driver dependencies.
|
|
7
|
+
*
|
|
8
|
+
* The meta instance runs `CamelCasePlugin` (07-meta-store.md §2.2): TS models
|
|
9
|
+
* are camelCase, physical columns snake_case. The dynamic source-DB instance
|
|
10
|
+
* (owned by the server) keeps raw names and never touches adminium_* tables.
|
|
11
|
+
*/
|
|
12
|
+
import { CamelCasePlugin, Kysely, MysqlDialect, PostgresDialect, SqliteDialect, } from 'kysely';
|
|
13
|
+
import { enableSqliteForeignKeys } from './migrator.js';
|
|
14
|
+
function build(dialect, kyselyDialect) {
|
|
15
|
+
return {
|
|
16
|
+
db: new Kysely({
|
|
17
|
+
dialect: kyselyDialect,
|
|
18
|
+
// Case-mapping is for COLUMN names only. Without maintainNestedObjectKeys
|
|
19
|
+
// the plugin also recursively camelCases plain-object VALUES — which
|
|
20
|
+
// silently corrupts driver-decoded JSON payloads (pg `jsonb` and mysql2
|
|
21
|
+
// `json` return parsed objects): a stored {"en_US": …} label came back
|
|
22
|
+
// {"enUS": …}. SQLite never hit it because its JSON is text.
|
|
23
|
+
plugins: [new CamelCasePlugin({ maintainNestedObjectKeys: true })],
|
|
24
|
+
}),
|
|
25
|
+
dialect,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* SQLite meta-store around an injected better-sqlite3-compatible database.
|
|
30
|
+
* Call {@link initMetaDb} (or `applyMigrations`/`firstRun`, which do it for
|
|
31
|
+
* you) before FK-dependent work — SQLite needs `PRAGMA foreign_keys = ON`
|
|
32
|
+
* per connection (07-meta-store.md §2.2).
|
|
33
|
+
*/
|
|
34
|
+
export function createSqliteMetaDb(config) {
|
|
35
|
+
return build('sqlite', new SqliteDialect(config));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* PostgreSQL meta-store around an injected `pg` Pool. The pool must parse
|
|
39
|
+
* int8 as JS number (all ts values < 2^53 — 07-meta-store.md §2.1).
|
|
40
|
+
*/
|
|
41
|
+
export function createPostgresMetaDb(config) {
|
|
42
|
+
return build('postgres', new PostgresDialect(config));
|
|
43
|
+
}
|
|
44
|
+
/** MySQL/MariaDB meta-store around an injected `mysql2` pool. */
|
|
45
|
+
export function createMysqlMetaDb(config) {
|
|
46
|
+
return build('mysql', new MysqlDialect(config));
|
|
47
|
+
}
|
|
48
|
+
/** One-time per-connection setup (currently: SQLite FK pragma). */
|
|
49
|
+
export async function initMetaDb(meta) {
|
|
50
|
+
if (meta.dialect === 'sqlite')
|
|
51
|
+
await enableSqliteForeignKeys(meta.db);
|
|
52
|
+
}
|
|
53
|
+
/** Release the underlying pool/connection. */
|
|
54
|
+
export async function destroyMetaDb(meta) {
|
|
55
|
+
await meta.db.destroy();
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=connect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.js","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,eAAe,EACf,MAAM,EACN,YAAY,EACZ,eAAe,EACf,aAAa,GAKd,MAAM,QAAQ,CAAC;AAGhB,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AASxD,SAAS,KAAK,CAAC,OAAoB,EAAE,aAAsB;IACzD,OAAO;QACL,EAAE,EAAE,IAAI,MAAM,CAAS;YACrB,OAAO,EAAE,aAAa;YACtB,0EAA0E;YAC1E,qEAAqE;YACrE,wEAAwE;YACxE,uEAAuE;YACvE,6DAA6D;YAC7D,OAAO,EAAE,CAAC,IAAI,eAAe,CAAC,EAAE,wBAAwB,EAAE,IAAI,EAAE,CAAC,CAAC;SACnE,CAAC;QACF,OAAO;KACR,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAA2B;IAC5D,OAAO,KAAK,CAAC,QAAQ,EAAE,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;AACpD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAA6B;IAChE,OAAO,KAAK,CAAC,UAAU,EAAE,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,iBAAiB,CAAC,MAA0B;IAC1D,OAAO,KAAK,CAAC,OAAO,EAAE,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,IAAI,IAAI,CAAC,OAAO,KAAK,QAAQ;QAAE,MAAM,uBAAuB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACxE,CAAC;AAED,8CAA8C;AAC9C,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY;IAC9C,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC;AAC1B,CAAC"}
|
package/dist/ids.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-prefixed monotonic ULID generator for every adminium_* primary key.
|
|
3
|
+
*
|
|
4
|
+
* Format: `<prefix>_<26-char Crockford-base32 ULID>` — e.g. `usr_01J8ME7Q2RZX4V9T6W3YB0KD5N`.
|
|
5
|
+
* The longest registered prefix is 4 chars, so every id fits the portable
|
|
6
|
+
* `char(36)` id column (07-meta-store.md §2.1) with room to spare.
|
|
7
|
+
*
|
|
8
|
+
* ULIDs are lexicographically time-sortable (48-bit ms timestamp + 80-bit
|
|
9
|
+
* randomness); within one millisecond the random part is incremented
|
|
10
|
+
* (monotonic mode) so ids generated by one process always sort in creation
|
|
11
|
+
* order (08-server-api.md §1.5 cursor pagination relies on this).
|
|
12
|
+
*/
|
|
13
|
+
declare const ULID_LEN: number;
|
|
14
|
+
/**
|
|
15
|
+
* Prefix registry: every id-bearing adminium_* table has exactly one prefix
|
|
16
|
+
* (07-meta-store.md §3). Junction/KV tables (user_roles, user_prefs,
|
|
17
|
+
* notification_prefs, settings, changelog_seen, migrations ledger) have
|
|
18
|
+
* natural/composite keys and no prefix.
|
|
19
|
+
*/
|
|
20
|
+
export declare const ID_PREFIXES: {
|
|
21
|
+
readonly usr: "adminium_users";
|
|
22
|
+
readonly sess: "adminium_sessions";
|
|
23
|
+
readonly rst: "adminium_password_resets";
|
|
24
|
+
readonly key: "adminium_api_keys";
|
|
25
|
+
readonly role: "adminium_roles";
|
|
26
|
+
readonly perm: "adminium_role_permissions";
|
|
27
|
+
readonly aud: "adminium_audit_log";
|
|
28
|
+
readonly job: "adminium_jobs";
|
|
29
|
+
readonly conn: "adminium_connections";
|
|
30
|
+
readonly snap: "adminium_schema_snapshots";
|
|
31
|
+
readonly ovr: "adminium_schema_overrides";
|
|
32
|
+
readonly page: "adminium_pages";
|
|
33
|
+
readonly view: "adminium_views";
|
|
34
|
+
readonly run: "adminium_llm_runs";
|
|
35
|
+
readonly ntf: "adminium_notifications";
|
|
36
|
+
readonly auto: "adminium_automations";
|
|
37
|
+
readonly arun: "adminium_automation_runs";
|
|
38
|
+
readonly rep: "adminium_scheduled_reports";
|
|
39
|
+
readonly exp: "adminium_exports";
|
|
40
|
+
readonly imp: "adminium_imports";
|
|
41
|
+
readonly file: "adminium_files";
|
|
42
|
+
readonly tpl: "adminium_email_templates";
|
|
43
|
+
readonly wh: "adminium_webhooks";
|
|
44
|
+
readonly whd: "adminium_webhook_deliveries";
|
|
45
|
+
readonly flag: "adminium_feature_flags";
|
|
46
|
+
readonly mft: "adminium_manifests";
|
|
47
|
+
};
|
|
48
|
+
export type IdPrefix = keyof typeof ID_PREFIXES;
|
|
49
|
+
/** Maximum column width every id must fit (07-meta-store.md §2.1). */
|
|
50
|
+
export declare const ID_MAX_LENGTH = 36;
|
|
51
|
+
export declare class InvalidIdError extends Error {
|
|
52
|
+
name: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Generate a new type-prefixed monotonic ULID: `newId('usr')` → `usr_01J…`.
|
|
56
|
+
* Ids from the same process are strictly increasing even within one
|
|
57
|
+
* millisecond or across a backwards clock step.
|
|
58
|
+
*/
|
|
59
|
+
export declare function newId(prefix: IdPrefix): string;
|
|
60
|
+
export interface ParsedId {
|
|
61
|
+
/** Registered prefix, e.g. `usr`. */
|
|
62
|
+
prefix: IdPrefix;
|
|
63
|
+
/** The table this id belongs to, e.g. `adminium_users`. */
|
|
64
|
+
table: string;
|
|
65
|
+
/** The bare 26-char ULID. */
|
|
66
|
+
ulid: string;
|
|
67
|
+
/** Millisecond timestamp encoded in the ULID. */
|
|
68
|
+
time: number;
|
|
69
|
+
}
|
|
70
|
+
/** Parse and validate a prefixed id; throws {@link InvalidIdError} on any malformed input. */
|
|
71
|
+
export declare function parseId(id: string): ParsedId;
|
|
72
|
+
/** True when `id` is a well-formed prefixed ULID (optionally of a specific prefix). */
|
|
73
|
+
export declare function isId(id: unknown, prefix?: IdPrefix): boolean;
|
|
74
|
+
/** Millisecond timestamp embedded in a prefixed ULID. */
|
|
75
|
+
export declare function idTime(id: string): number;
|
|
76
|
+
/** Internal — deterministic tests only. */
|
|
77
|
+
export declare function _resetIdState(): void;
|
|
78
|
+
export { ULID_LEN as _ULID_LENGTH };
|
|
79
|
+
//# sourceMappingURL=ids.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../src/ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAQH,QAAA,MAAM,QAAQ,QAAsB,CAAC;AAErC;;;;;GAKG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2Bd,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,WAAW,CAAC;AAOhD,sEAAsE;AACtE,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC,qBAAa,cAAe,SAAQ,KAAK;IAC9B,IAAI,SAAoB;CAClC;AAiDD;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAc9C;AAED,MAAM,WAAW,QAAQ;IACvB,qCAAqC;IACrC,MAAM,EAAE,QAAQ,CAAC;IACjB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8FAA8F;AAC9F,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,CAY5C;AAED,uFAAuF;AACvF,wBAAgB,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,CAQ5D;AAED,yDAAyD;AACzD,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED,2CAA2C;AAC3C,wBAAgB,aAAa,IAAI,IAAI,CAGpC;AAED,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,CAAC"}
|