@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.
Files changed (194) hide show
  1. package/LICENSE +661 -0
  2. package/dist/bootstrap.d.ts +103 -0
  3. package/dist/bootstrap.d.ts.map +1 -0
  4. package/dist/bootstrap.js +225 -0
  5. package/dist/bootstrap.js.map +1 -0
  6. package/dist/columns.d.ts +44 -0
  7. package/dist/columns.d.ts.map +1 -0
  8. package/dist/columns.js +57 -0
  9. package/dist/columns.js.map +1 -0
  10. package/dist/connect.d.ts +38 -0
  11. package/dist/connect.d.ts.map +1 -0
  12. package/dist/connect.js +57 -0
  13. package/dist/connect.js.map +1 -0
  14. package/dist/ids.d.ts +79 -0
  15. package/dist/ids.d.ts.map +1 -0
  16. package/dist/ids.js +165 -0
  17. package/dist/ids.js.map +1 -0
  18. package/dist/index.d.ts +21 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +21 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/migrations/0001_core_auth.d.ts +9 -0
  23. package/dist/migrations/0001_core_auth.d.ts.map +1 -0
  24. package/dist/migrations/0001_core_auth.js +102 -0
  25. package/dist/migrations/0001_core_auth.js.map +1 -0
  26. package/dist/migrations/0002_rbac.d.ts +8 -0
  27. package/dist/migrations/0002_rbac.d.ts.map +1 -0
  28. package/dist/migrations/0002_rbac.js +75 -0
  29. package/dist/migrations/0002_rbac.js.map +1 -0
  30. package/dist/migrations/0003_connections_schema.d.ts +9 -0
  31. package/dist/migrations/0003_connections_schema.d.ts.map +1 -0
  32. package/dist/migrations/0003_connections_schema.js +100 -0
  33. package/dist/migrations/0003_connections_schema.js.map +1 -0
  34. package/dist/migrations/0004_pages_views.d.ts +8 -0
  35. package/dist/migrations/0004_pages_views.d.ts.map +1 -0
  36. package/dist/migrations/0004_pages_views.js +59 -0
  37. package/dist/migrations/0004_pages_views.js.map +1 -0
  38. package/dist/migrations/0005_ops.d.ts +9 -0
  39. package/dist/migrations/0005_ops.d.ts.map +1 -0
  40. package/dist/migrations/0005_ops.js +104 -0
  41. package/dist/migrations/0005_ops.js.map +1 -0
  42. package/dist/migrations/0006_platform.d.ts +11 -0
  43. package/dist/migrations/0006_platform.d.ts.map +1 -0
  44. package/dist/migrations/0006_platform.js +259 -0
  45. package/dist/migrations/0006_platform.js.map +1 -0
  46. package/dist/migrations/0007_llm_runs.d.ts +33 -0
  47. package/dist/migrations/0007_llm_runs.d.ts.map +1 -0
  48. package/dist/migrations/0007_llm_runs.js +62 -0
  49. package/dist/migrations/0007_llm_runs.js.map +1 -0
  50. package/dist/migrations/0008_llm_overrides.d.ts +18 -0
  51. package/dist/migrations/0008_llm_overrides.d.ts.map +1 -0
  52. package/dist/migrations/0008_llm_overrides.js +24 -0
  53. package/dist/migrations/0008_llm_overrides.js.map +1 -0
  54. package/dist/migrations/0009_views_kind.d.ts +20 -0
  55. package/dist/migrations/0009_views_kind.d.ts.map +1 -0
  56. package/dist/migrations/0009_views_kind.js +24 -0
  57. package/dist/migrations/0009_views_kind.js.map +1 -0
  58. package/dist/migrations/0010_llm_prompt_version_width.d.ts +19 -0
  59. package/dist/migrations/0010_llm_prompt_version_width.d.ts.map +1 -0
  60. package/dist/migrations/0010_llm_prompt_version_width.js +28 -0
  61. package/dist/migrations/0010_llm_prompt_version_width.js.map +1 -0
  62. package/dist/migrations/index.d.ts +29 -0
  63. package/dist/migrations/index.d.ts.map +1 -0
  64. package/dist/migrations/index.js +43 -0
  65. package/dist/migrations/index.js.map +1 -0
  66. package/dist/migrator.d.ts +83 -0
  67. package/dist/migrator.d.ts.map +1 -0
  68. package/dist/migrator.js +173 -0
  69. package/dist/migrator.js.map +1 -0
  70. package/dist/prefix.d.ts +19 -0
  71. package/dist/prefix.d.ts.map +1 -0
  72. package/dist/prefix.js +33 -0
  73. package/dist/prefix.js.map +1 -0
  74. package/dist/repos/api-keys.d.ts +31 -0
  75. package/dist/repos/api-keys.d.ts.map +1 -0
  76. package/dist/repos/api-keys.js +62 -0
  77. package/dist/repos/api-keys.js.map +1 -0
  78. package/dist/repos/audit.d.ts +51 -0
  79. package/dist/repos/audit.d.ts.map +1 -0
  80. package/dist/repos/audit.js +78 -0
  81. package/dist/repos/audit.js.map +1 -0
  82. package/dist/repos/connections.d.ts +90 -0
  83. package/dist/repos/connections.d.ts.map +1 -0
  84. package/dist/repos/connections.js +191 -0
  85. package/dist/repos/connections.js.map +1 -0
  86. package/dist/repos/email-templates.d.ts +47 -0
  87. package/dist/repos/email-templates.d.ts.map +1 -0
  88. package/dist/repos/email-templates.js +93 -0
  89. package/dist/repos/email-templates.js.map +1 -0
  90. package/dist/repos/exports.d.ts +67 -0
  91. package/dist/repos/exports.d.ts.map +1 -0
  92. package/dist/repos/exports.js +132 -0
  93. package/dist/repos/exports.js.map +1 -0
  94. package/dist/repos/files.d.ts +52 -0
  95. package/dist/repos/files.d.ts.map +1 -0
  96. package/dist/repos/files.js +86 -0
  97. package/dist/repos/files.js.map +1 -0
  98. package/dist/repos/imports.d.ts +71 -0
  99. package/dist/repos/imports.d.ts.map +1 -0
  100. package/dist/repos/imports.js +111 -0
  101. package/dist/repos/imports.js.map +1 -0
  102. package/dist/repos/index.d.ts +26 -0
  103. package/dist/repos/index.d.ts.map +1 -0
  104. package/dist/repos/index.js +26 -0
  105. package/dist/repos/index.js.map +1 -0
  106. package/dist/repos/jobs.d.ts +71 -0
  107. package/dist/repos/jobs.d.ts.map +1 -0
  108. package/dist/repos/jobs.js +187 -0
  109. package/dist/repos/jobs.js.map +1 -0
  110. package/dist/repos/llm-overrides.d.ts +89 -0
  111. package/dist/repos/llm-overrides.d.ts.map +1 -0
  112. package/dist/repos/llm-overrides.js +205 -0
  113. package/dist/repos/llm-overrides.js.map +1 -0
  114. package/dist/repos/llm-runs.d.ts +114 -0
  115. package/dist/repos/llm-runs.d.ts.map +1 -0
  116. package/dist/repos/llm-runs.js +214 -0
  117. package/dist/repos/llm-runs.js.map +1 -0
  118. package/dist/repos/notification-prefs.d.ts +34 -0
  119. package/dist/repos/notification-prefs.d.ts.map +1 -0
  120. package/dist/repos/notification-prefs.js +80 -0
  121. package/dist/repos/notification-prefs.js.map +1 -0
  122. package/dist/repos/notifications.d.ts +60 -0
  123. package/dist/repos/notifications.d.ts.map +1 -0
  124. package/dist/repos/notifications.js +107 -0
  125. package/dist/repos/notifications.js.map +1 -0
  126. package/dist/repos/overrides.d.ts +59 -0
  127. package/dist/repos/overrides.d.ts.map +1 -0
  128. package/dist/repos/overrides.js +143 -0
  129. package/dist/repos/overrides.js.map +1 -0
  130. package/dist/repos/pages.d.ts +192 -0
  131. package/dist/repos/pages.d.ts.map +1 -0
  132. package/dist/repos/pages.js +374 -0
  133. package/dist/repos/pages.js.map +1 -0
  134. package/dist/repos/password-resets.d.ts +32 -0
  135. package/dist/repos/password-resets.d.ts.map +1 -0
  136. package/dist/repos/password-resets.js +56 -0
  137. package/dist/repos/password-resets.js.map +1 -0
  138. package/dist/repos/permissions.d.ts +28 -0
  139. package/dist/repos/permissions.d.ts.map +1 -0
  140. package/dist/repos/permissions.js +100 -0
  141. package/dist/repos/permissions.js.map +1 -0
  142. package/dist/repos/roles.d.ts +33 -0
  143. package/dist/repos/roles.d.ts.map +1 -0
  144. package/dist/repos/roles.js +89 -0
  145. package/dist/repos/roles.js.map +1 -0
  146. package/dist/repos/scheduled-reports.d.ts +73 -0
  147. package/dist/repos/scheduled-reports.d.ts.map +1 -0
  148. package/dist/repos/scheduled-reports.js +132 -0
  149. package/dist/repos/scheduled-reports.js.map +1 -0
  150. package/dist/repos/sessions.d.ts +33 -0
  151. package/dist/repos/sessions.d.ts.map +1 -0
  152. package/dist/repos/sessions.js +79 -0
  153. package/dist/repos/sessions.js.map +1 -0
  154. package/dist/repos/settings.d.ts +25 -0
  155. package/dist/repos/settings.d.ts.map +1 -0
  156. package/dist/repos/settings.js +73 -0
  157. package/dist/repos/settings.js.map +1 -0
  158. package/dist/repos/snapshots.d.ts +79 -0
  159. package/dist/repos/snapshots.d.ts.map +1 -0
  160. package/dist/repos/snapshots.js +191 -0
  161. package/dist/repos/snapshots.js.map +1 -0
  162. package/dist/repos/user-prefs.d.ts +56 -0
  163. package/dist/repos/user-prefs.d.ts.map +1 -0
  164. package/dist/repos/user-prefs.js +147 -0
  165. package/dist/repos/user-prefs.js.map +1 -0
  166. package/dist/repos/users.d.ts +38 -0
  167. package/dist/repos/users.d.ts.map +1 -0
  168. package/dist/repos/users.js +103 -0
  169. package/dist/repos/users.js.map +1 -0
  170. package/dist/repos/util.d.ts +45 -0
  171. package/dist/repos/util.d.ts.map +1 -0
  172. package/dist/repos/util.js +81 -0
  173. package/dist/repos/util.js.map +1 -0
  174. package/dist/repos/views.d.ts +75 -0
  175. package/dist/repos/views.d.ts.map +1 -0
  176. package/dist/repos/views.js +180 -0
  177. package/dist/repos/views.js.map +1 -0
  178. package/dist/schema/index.d.ts +4 -0
  179. package/dist/schema/index.d.ts.map +1 -0
  180. package/dist/schema/index.js +4 -0
  181. package/dist/schema/index.js.map +1 -0
  182. package/dist/schema/json-payloads.d.ts +479 -0
  183. package/dist/schema/json-payloads.d.ts.map +1 -0
  184. package/dist/schema/json-payloads.js +340 -0
  185. package/dist/schema/json-payloads.js.map +1 -0
  186. package/dist/schema/settings-registry.d.ts +187 -0
  187. package/dist/schema/settings-registry.d.ts.map +1 -0
  188. package/dist/schema/settings-registry.js +183 -0
  189. package/dist/schema/settings-registry.js.map +1 -0
  190. package/dist/schema/tables.d.ts +568 -0
  191. package/dist/schema/tables.d.ts.map +1 -0
  192. package/dist/schema/tables.js +51 -0
  193. package/dist/schema/tables.js.map +1 -0
  194. 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"}
@@ -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"}
@@ -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"}