@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,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* snapshotsRepo — adminium_schema_snapshots (07-meta-store.md §3.14).
|
|
3
|
+
*
|
|
4
|
+
* Immutable record of one introspection / schema-import run. The `schema`
|
|
5
|
+
* JSON is the normalized schema model owned by 05-introspection-engine.md —
|
|
6
|
+
* opaque here. Exactly one snapshot per connection is active; activation is
|
|
7
|
+
* app-enforced inside a transaction. Equal checksum to the latest snapshot
|
|
8
|
+
* ⇒ no-op (the existing row is returned, nothing is inserted).
|
|
9
|
+
*/
|
|
10
|
+
import type { MetaDb } from '../connect.js';
|
|
11
|
+
export declare const SNAPSHOT_SOURCES: readonly ["introspection", "schema-import"];
|
|
12
|
+
export type SnapshotSource = (typeof SNAPSHOT_SOURCES)[number];
|
|
13
|
+
export interface SchemaSnapshot {
|
|
14
|
+
id: string;
|
|
15
|
+
connectionId: string;
|
|
16
|
+
source: string;
|
|
17
|
+
importFormat: string | null;
|
|
18
|
+
engineVersion: string | null;
|
|
19
|
+
/** Normalized schema model JSON — opaque to the meta store. */
|
|
20
|
+
schema: unknown;
|
|
21
|
+
stats: Record<string, {
|
|
22
|
+
rowCount: number;
|
|
23
|
+
}> | null;
|
|
24
|
+
checksum: string;
|
|
25
|
+
isActive: boolean;
|
|
26
|
+
createdBy: string | null;
|
|
27
|
+
createdAt: number;
|
|
28
|
+
}
|
|
29
|
+
/** Listing row without the (potentially multi-MB) schema payload. */
|
|
30
|
+
export interface SchemaSnapshotMeta {
|
|
31
|
+
id: string;
|
|
32
|
+
connectionId: string;
|
|
33
|
+
source: string;
|
|
34
|
+
importFormat: string | null;
|
|
35
|
+
engineVersion: string | null;
|
|
36
|
+
checksum: string;
|
|
37
|
+
isActive: boolean;
|
|
38
|
+
createdBy: string | null;
|
|
39
|
+
createdAt: number;
|
|
40
|
+
}
|
|
41
|
+
export interface CreateSnapshotInput {
|
|
42
|
+
connectionId: string;
|
|
43
|
+
source: SnapshotSource;
|
|
44
|
+
importFormat?: string | null;
|
|
45
|
+
engineVersion?: string | null;
|
|
46
|
+
schema: unknown;
|
|
47
|
+
stats?: Record<string, {
|
|
48
|
+
rowCount: number;
|
|
49
|
+
}> | null;
|
|
50
|
+
/** SHA-256 of the canonicalized schema — computed by the caller (server). */
|
|
51
|
+
checksum: string;
|
|
52
|
+
createdBy?: string | null;
|
|
53
|
+
/** Default true: the new snapshot becomes the connection's active one. */
|
|
54
|
+
activate?: boolean;
|
|
55
|
+
}
|
|
56
|
+
export interface CreateSnapshotResult {
|
|
57
|
+
snapshot: SchemaSnapshot;
|
|
58
|
+
/** True when the checksum matched the latest snapshot and no row was written. */
|
|
59
|
+
noop: boolean;
|
|
60
|
+
}
|
|
61
|
+
export declare function snapshotsRepo(meta: MetaDb): {
|
|
62
|
+
/**
|
|
63
|
+
* Persist a snapshot; inside one transaction the previous active row is
|
|
64
|
+
* deactivated so exactly one stays active per connection (§3.14). Equal
|
|
65
|
+
* checksum to the latest snapshot short-circuits to a no-op.
|
|
66
|
+
*/
|
|
67
|
+
create(input: CreateSnapshotInput, at?: number): Promise<CreateSnapshotResult>;
|
|
68
|
+
findById(id: string): Promise<SchemaSnapshot | null>;
|
|
69
|
+
/** The connection's active snapshot; falls back to the newest row. */
|
|
70
|
+
latest(connectionId: string): Promise<SchemaSnapshot | null>;
|
|
71
|
+
/** The snapshot immediately before the given one (diff default: previous vs latest). */
|
|
72
|
+
previous(connectionId: string, beforeSnapshotId: string): Promise<SchemaSnapshot | null>;
|
|
73
|
+
/** History (newest first) without schema payloads. */
|
|
74
|
+
listForConnection(connectionId: string): Promise<SchemaSnapshotMeta[]>;
|
|
75
|
+
/** Flip the active snapshot (rollback flows). */
|
|
76
|
+
activate(id: string): Promise<boolean>;
|
|
77
|
+
};
|
|
78
|
+
export type SnapshotsRepo = ReturnType<typeof snapshotsRepo>;
|
|
79
|
+
//# sourceMappingURL=snapshots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshots.d.ts","sourceRoot":"","sources":["../../src/repos/snapshots.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAK5C,eAAO,MAAM,gBAAgB,6CAA8C,CAAC;AAC5E,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,+DAA+D;IAC/D,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qEAAqE;AACrE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;IACpD,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,cAAc,CAAC;IACzB,iFAAiF;IACjF,IAAI,EAAE,OAAO,CAAC;CACf;AA4CD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM;IAItC;;;;OAIG;kBACiB,mBAAmB,OAAM,MAAM,GAAgB,OAAO,CAAC,oBAAoB,CAAC;iBA6C7E,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAS1D,sEAAsE;yBAC3C,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAsBlE,wFAAwF;2BAC3D,MAAM,oBAAoB,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAkB9F,sDAAsD;oCAChB,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAW5E,iDAAiD;iBAC9B,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;EAsB/C;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* snapshotsRepo — adminium_schema_snapshots (07-meta-store.md §3.14).
|
|
3
|
+
*
|
|
4
|
+
* Immutable record of one introspection / schema-import run. The `schema`
|
|
5
|
+
* JSON is the normalized schema model owned by 05-introspection-engine.md —
|
|
6
|
+
* opaque here. Exactly one snapshot per connection is active; activation is
|
|
7
|
+
* app-enforced inside a transaction. Equal checksum to the latest snapshot
|
|
8
|
+
* ⇒ no-op (the existing row is returned, nothing is inserted).
|
|
9
|
+
*/
|
|
10
|
+
import { newId } from '../ids.js';
|
|
11
|
+
import { MetaValidationError, packJson, readBool, readJson, readJsonOrNull, writeBool } from './util.js';
|
|
12
|
+
export const SNAPSHOT_SOURCES = ['introspection', 'schema-import'];
|
|
13
|
+
function decode(row) {
|
|
14
|
+
return {
|
|
15
|
+
id: row.id,
|
|
16
|
+
connectionId: row.connectionId,
|
|
17
|
+
source: row.source,
|
|
18
|
+
importFormat: row.importFormat,
|
|
19
|
+
engineVersion: row.engineVersion,
|
|
20
|
+
schema: readJson(row.schema),
|
|
21
|
+
stats: readJsonOrNull(row.stats),
|
|
22
|
+
checksum: row.checksum,
|
|
23
|
+
isActive: readBool(row.isActive),
|
|
24
|
+
createdBy: row.createdBy,
|
|
25
|
+
createdAt: row.createdAt,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function decodeMeta(row) {
|
|
29
|
+
return {
|
|
30
|
+
id: row.id,
|
|
31
|
+
connectionId: row.connectionId,
|
|
32
|
+
source: row.source,
|
|
33
|
+
importFormat: row.importFormat,
|
|
34
|
+
engineVersion: row.engineVersion,
|
|
35
|
+
checksum: row.checksum,
|
|
36
|
+
isActive: readBool(row.isActive),
|
|
37
|
+
createdBy: row.createdBy,
|
|
38
|
+
createdAt: row.createdAt,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const META_COLUMNS = [
|
|
42
|
+
'id',
|
|
43
|
+
'connectionId',
|
|
44
|
+
'source',
|
|
45
|
+
'importFormat',
|
|
46
|
+
'engineVersion',
|
|
47
|
+
'checksum',
|
|
48
|
+
'isActive',
|
|
49
|
+
'createdBy',
|
|
50
|
+
'createdAt',
|
|
51
|
+
];
|
|
52
|
+
export function snapshotsRepo(meta) {
|
|
53
|
+
const { db } = meta;
|
|
54
|
+
return {
|
|
55
|
+
/**
|
|
56
|
+
* Persist a snapshot; inside one transaction the previous active row is
|
|
57
|
+
* deactivated so exactly one stays active per connection (§3.14). Equal
|
|
58
|
+
* checksum to the latest snapshot short-circuits to a no-op.
|
|
59
|
+
*/
|
|
60
|
+
async create(input, at = Date.now()) {
|
|
61
|
+
if (!SNAPSHOT_SOURCES.includes(input.source)) {
|
|
62
|
+
throw new MetaValidationError(`invalid snapshot source ${JSON.stringify(input.source)}`);
|
|
63
|
+
}
|
|
64
|
+
if (input.checksum.length === 0) {
|
|
65
|
+
throw new MetaValidationError('snapshot checksum must not be empty');
|
|
66
|
+
}
|
|
67
|
+
return db.transaction().execute(async (trx) => {
|
|
68
|
+
const latest = await trx
|
|
69
|
+
.selectFrom('adminium_schema_snapshots')
|
|
70
|
+
.selectAll()
|
|
71
|
+
.where('connectionId', '=', input.connectionId)
|
|
72
|
+
.orderBy('createdAt', 'desc')
|
|
73
|
+
.orderBy('id', 'desc')
|
|
74
|
+
.limit(1)
|
|
75
|
+
.executeTakeFirst();
|
|
76
|
+
if (latest !== undefined && latest.checksum === input.checksum) {
|
|
77
|
+
return { snapshot: decode(latest), noop: true };
|
|
78
|
+
}
|
|
79
|
+
const activate = input.activate ?? true;
|
|
80
|
+
const row = {
|
|
81
|
+
id: newId('snap'),
|
|
82
|
+
connectionId: input.connectionId,
|
|
83
|
+
source: input.source,
|
|
84
|
+
importFormat: input.importFormat ?? null,
|
|
85
|
+
engineVersion: input.engineVersion ?? null,
|
|
86
|
+
schema: packJson(input.schema),
|
|
87
|
+
stats: input.stats === null || input.stats === undefined ? null : packJson(input.stats),
|
|
88
|
+
checksum: input.checksum,
|
|
89
|
+
isActive: writeBool(meta, activate),
|
|
90
|
+
createdBy: input.createdBy ?? null,
|
|
91
|
+
createdAt: at,
|
|
92
|
+
};
|
|
93
|
+
if (activate) {
|
|
94
|
+
await trx
|
|
95
|
+
.updateTable('adminium_schema_snapshots')
|
|
96
|
+
.set({ isActive: writeBool(meta, false) })
|
|
97
|
+
.where('connectionId', '=', input.connectionId)
|
|
98
|
+
.execute();
|
|
99
|
+
}
|
|
100
|
+
await trx.insertInto('adminium_schema_snapshots').values(row).execute();
|
|
101
|
+
return { snapshot: { ...decode(row), schema: input.schema }, noop: false };
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
async findById(id) {
|
|
105
|
+
const row = await db
|
|
106
|
+
.selectFrom('adminium_schema_snapshots')
|
|
107
|
+
.selectAll()
|
|
108
|
+
.where('id', '=', id)
|
|
109
|
+
.executeTakeFirst();
|
|
110
|
+
return row === undefined ? null : decode(row);
|
|
111
|
+
},
|
|
112
|
+
/** The connection's active snapshot; falls back to the newest row. */
|
|
113
|
+
async latest(connectionId) {
|
|
114
|
+
const active = await db
|
|
115
|
+
.selectFrom('adminium_schema_snapshots')
|
|
116
|
+
.selectAll()
|
|
117
|
+
.where('connectionId', '=', connectionId)
|
|
118
|
+
.where('isActive', '=', writeBool(meta, true))
|
|
119
|
+
.orderBy('createdAt', 'desc')
|
|
120
|
+
.orderBy('id', 'desc')
|
|
121
|
+
.limit(1)
|
|
122
|
+
.executeTakeFirst();
|
|
123
|
+
if (active !== undefined)
|
|
124
|
+
return decode(active);
|
|
125
|
+
const newest = await db
|
|
126
|
+
.selectFrom('adminium_schema_snapshots')
|
|
127
|
+
.selectAll()
|
|
128
|
+
.where('connectionId', '=', connectionId)
|
|
129
|
+
.orderBy('createdAt', 'desc')
|
|
130
|
+
.orderBy('id', 'desc')
|
|
131
|
+
.limit(1)
|
|
132
|
+
.executeTakeFirst();
|
|
133
|
+
return newest === undefined ? null : decode(newest);
|
|
134
|
+
},
|
|
135
|
+
/** The snapshot immediately before the given one (diff default: previous vs latest). */
|
|
136
|
+
async previous(connectionId, beforeSnapshotId) {
|
|
137
|
+
const anchor = await this.findById(beforeSnapshotId);
|
|
138
|
+
if (anchor === null)
|
|
139
|
+
return null;
|
|
140
|
+
const row = await db
|
|
141
|
+
.selectFrom('adminium_schema_snapshots')
|
|
142
|
+
.selectAll()
|
|
143
|
+
.where('connectionId', '=', connectionId)
|
|
144
|
+
.where('id', '!=', anchor.id)
|
|
145
|
+
.where('createdAt', '<=', anchor.createdAt)
|
|
146
|
+
.orderBy('createdAt', 'desc')
|
|
147
|
+
.orderBy('id', 'desc')
|
|
148
|
+
.limit(1)
|
|
149
|
+
.executeTakeFirst();
|
|
150
|
+
// Same-millisecond rows tie on createdAt; ULID ids break the tie.
|
|
151
|
+
if (row !== undefined && row.createdAt === anchor.createdAt && row.id > anchor.id)
|
|
152
|
+
return null;
|
|
153
|
+
return row === undefined ? null : decode(row);
|
|
154
|
+
},
|
|
155
|
+
/** History (newest first) without schema payloads. */
|
|
156
|
+
async listForConnection(connectionId) {
|
|
157
|
+
const rows = await db
|
|
158
|
+
.selectFrom('adminium_schema_snapshots')
|
|
159
|
+
.select(META_COLUMNS)
|
|
160
|
+
.where('connectionId', '=', connectionId)
|
|
161
|
+
.orderBy('createdAt', 'desc')
|
|
162
|
+
.orderBy('id', 'desc')
|
|
163
|
+
.execute();
|
|
164
|
+
return rows.map(decodeMeta);
|
|
165
|
+
},
|
|
166
|
+
/** Flip the active snapshot (rollback flows). */
|
|
167
|
+
async activate(id) {
|
|
168
|
+
return db.transaction().execute(async (trx) => {
|
|
169
|
+
const row = await trx
|
|
170
|
+
.selectFrom('adminium_schema_snapshots')
|
|
171
|
+
.select(['id', 'connectionId'])
|
|
172
|
+
.where('id', '=', id)
|
|
173
|
+
.executeTakeFirst();
|
|
174
|
+
if (row === undefined)
|
|
175
|
+
return false;
|
|
176
|
+
await trx
|
|
177
|
+
.updateTable('adminium_schema_snapshots')
|
|
178
|
+
.set({ isActive: writeBool(meta, false) })
|
|
179
|
+
.where('connectionId', '=', row.connectionId)
|
|
180
|
+
.execute();
|
|
181
|
+
await trx
|
|
182
|
+
.updateTable('adminium_schema_snapshots')
|
|
183
|
+
.set({ isActive: writeBool(meta, true) })
|
|
184
|
+
.where('id', '=', id)
|
|
185
|
+
.execute();
|
|
186
|
+
return true;
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=snapshots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshots.js","sourceRoot":"","sources":["../../src/repos/snapshots.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* userPrefsRepo — adminium_user_prefs (07-meta-store.md §3.4) plus the
|
|
3
|
+
* BRIEF §7 preference resolution (§7.2): system default → global settings
|
|
4
|
+
* override → non-NULL user axis, with `dir` derived from locale unless
|
|
5
|
+
* explicitly overridden. Clearing an axis = writing NULL, not deleting the row.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import type { MetaDb } from '../connect.js';
|
|
9
|
+
import { accentSchema, densitySchema, dirSchema, localeSchema, themeSchema } from '../schema/json-payloads.js';
|
|
10
|
+
export interface UserPrefs {
|
|
11
|
+
userId: string;
|
|
12
|
+
theme: z.infer<typeof themeSchema> | null;
|
|
13
|
+
accent: z.infer<typeof accentSchema> | null;
|
|
14
|
+
density: z.infer<typeof densitySchema> | null;
|
|
15
|
+
locale: z.infer<typeof localeSchema> | null;
|
|
16
|
+
dir: z.infer<typeof dirSchema> | null;
|
|
17
|
+
uiState: Record<string, unknown> | null;
|
|
18
|
+
updatedAt: number;
|
|
19
|
+
}
|
|
20
|
+
export type UserPrefsPatch = Partial<Omit<UserPrefs, 'userId' | 'updatedAt'>>;
|
|
21
|
+
export type PrefSource = 'system' | 'global' | 'user';
|
|
22
|
+
export interface ResolvedPrefs {
|
|
23
|
+
theme: z.infer<typeof themeSchema>;
|
|
24
|
+
accent: z.infer<typeof accentSchema>;
|
|
25
|
+
density: z.infer<typeof densitySchema>;
|
|
26
|
+
locale: z.infer<typeof localeSchema>;
|
|
27
|
+
dir: z.infer<typeof dirSchema>;
|
|
28
|
+
/** Per-axis provenance for the "using workspace default" affordances. */
|
|
29
|
+
source: {
|
|
30
|
+
theme: PrefSource;
|
|
31
|
+
accent: PrefSource;
|
|
32
|
+
density: PrefSource;
|
|
33
|
+
locale: PrefSource;
|
|
34
|
+
dir: PrefSource;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** BRIEF §7 baseline — a fresh install without a single settings row. */
|
|
38
|
+
export declare const SYSTEM_PREF_DEFAULTS: {
|
|
39
|
+
readonly theme: "system";
|
|
40
|
+
readonly accent: "indigo";
|
|
41
|
+
readonly density: "comfortable";
|
|
42
|
+
readonly locale: "en_US";
|
|
43
|
+
};
|
|
44
|
+
export declare function dirForLocale(locale: string): 'ltr' | 'rtl';
|
|
45
|
+
export declare function userPrefsRepo(meta: MetaDb): {
|
|
46
|
+
get: (userId: string) => Promise<UserPrefs | null>;
|
|
47
|
+
/**
|
|
48
|
+
* Upsert the single prefs row. Only the axes present in `patch` change;
|
|
49
|
+
* `null` clears an axis back to "inherit".
|
|
50
|
+
*/
|
|
51
|
+
set(userId: string, patch: UserPrefsPatch, at?: number): Promise<UserPrefs>;
|
|
52
|
+
/** §7.2 merge, implemented exactly once, server-side. */
|
|
53
|
+
resolve(userId: string | null): Promise<ResolvedPrefs>;
|
|
54
|
+
};
|
|
55
|
+
export type UserPrefsRepo = ReturnType<typeof userPrefsRepo>;
|
|
56
|
+
//# sourceMappingURL=user-prefs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-prefs.d.ts","sourceRoot":"","sources":["../../src/repos/user-prefs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EACL,YAAY,EACZ,aAAa,EACb,SAAS,EACT,YAAY,EACZ,WAAW,EAEZ,MAAM,4BAA4B,CAAC;AAIpC,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,GAAG,IAAI,CAAC;IAC1C,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,GAAG,IAAI,CAAC;IAC5C,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,GAAG,IAAI,CAAC;IAC9C,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,GAAG,IAAI,CAAC;IAC5C,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,GAAG,IAAI,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC;AAE9E,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;IACnC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;IACrC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;IACvC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;IACrC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;IAC/B,yEAAyE;IACzE,MAAM,EAAE;QAAE,KAAK,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,UAAU,CAAC;QAAC,OAAO,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,UAAU,CAAC;QAAC,GAAG,EAAE,UAAU,CAAA;KAAE,CAAC;CAC7G;AAUD,yEAAyE;AACzE,eAAO,MAAM,oBAAoB;;;;;CAKvB,CAAC;AAEX,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,CAE1D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM;kBAIb,MAAM,KAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAsB3D;;;OAGG;gBACe,MAAM,SAAS,cAAc,OAAM,MAAM,GAAgB,OAAO,CAAC,SAAS,CAAC;IAiD7F,yDAAyD;oBACnC,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC;EAqD/D;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* userPrefsRepo — adminium_user_prefs (07-meta-store.md §3.4) plus the
|
|
3
|
+
* BRIEF §7 preference resolution (§7.2): system default → global settings
|
|
4
|
+
* override → non-NULL user axis, with `dir` derived from locale unless
|
|
5
|
+
* explicitly overridden. Clearing an axis = writing NULL, not deleting the row.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { accentSchema, densitySchema, dirSchema, localeSchema, themeSchema, uiStateSchema, } from '../schema/json-payloads.js';
|
|
9
|
+
import { MetaValidationError, packJson, readJsonOrNull } from './util.js';
|
|
10
|
+
import { settingsRepo } from './settings.js';
|
|
11
|
+
const AXES = {
|
|
12
|
+
theme: themeSchema,
|
|
13
|
+
accent: accentSchema,
|
|
14
|
+
density: densitySchema,
|
|
15
|
+
locale: localeSchema,
|
|
16
|
+
dir: dirSchema,
|
|
17
|
+
};
|
|
18
|
+
/** BRIEF §7 baseline — a fresh install without a single settings row. */
|
|
19
|
+
export const SYSTEM_PREF_DEFAULTS = {
|
|
20
|
+
theme: 'system',
|
|
21
|
+
accent: 'indigo',
|
|
22
|
+
density: 'comfortable',
|
|
23
|
+
locale: 'en_US',
|
|
24
|
+
};
|
|
25
|
+
export function dirForLocale(locale) {
|
|
26
|
+
return locale === 'ar_EG' ? 'rtl' : 'ltr';
|
|
27
|
+
}
|
|
28
|
+
export function userPrefsRepo(meta) {
|
|
29
|
+
const { db } = meta;
|
|
30
|
+
const settings = settingsRepo(meta);
|
|
31
|
+
async function get(userId) {
|
|
32
|
+
const row = await db
|
|
33
|
+
.selectFrom('adminium_user_prefs')
|
|
34
|
+
.selectAll()
|
|
35
|
+
.where('userId', '=', userId)
|
|
36
|
+
.executeTakeFirst();
|
|
37
|
+
if (!row)
|
|
38
|
+
return null;
|
|
39
|
+
return {
|
|
40
|
+
userId: row.userId,
|
|
41
|
+
theme: row.theme === null ? null : themeSchema.parse(row.theme),
|
|
42
|
+
accent: row.accent === null ? null : accentSchema.parse(row.accent),
|
|
43
|
+
density: row.density === null ? null : densitySchema.parse(row.density),
|
|
44
|
+
locale: row.locale === null ? null : localeSchema.parse(row.locale),
|
|
45
|
+
dir: row.dir === null ? null : dirSchema.parse(row.dir),
|
|
46
|
+
uiState: readJsonOrNull(row.uiState),
|
|
47
|
+
updatedAt: row.updatedAt,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
get,
|
|
52
|
+
/**
|
|
53
|
+
* Upsert the single prefs row. Only the axes present in `patch` change;
|
|
54
|
+
* `null` clears an axis back to "inherit".
|
|
55
|
+
*/
|
|
56
|
+
async set(userId, patch, at = Date.now()) {
|
|
57
|
+
const values = {};
|
|
58
|
+
for (const axis of ['theme', 'accent', 'density', 'locale', 'dir']) {
|
|
59
|
+
const v = patch[axis];
|
|
60
|
+
if (v === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
if (v !== null) {
|
|
63
|
+
const parsed = AXES[axis].safeParse(v);
|
|
64
|
+
if (!parsed.success) {
|
|
65
|
+
throw new MetaValidationError(`invalid pref ${axis}: ${JSON.stringify(v)}`, parsed.error.issues);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
values[axis] = v;
|
|
69
|
+
}
|
|
70
|
+
if (patch.uiState !== undefined) {
|
|
71
|
+
values.uiState = patch.uiState === null ? null : packJson(uiStateSchema.parse(patch.uiState));
|
|
72
|
+
}
|
|
73
|
+
const res = await db
|
|
74
|
+
.updateTable('adminium_user_prefs')
|
|
75
|
+
.set({ ...values, updatedAt: at })
|
|
76
|
+
.where('userId', '=', userId)
|
|
77
|
+
.executeTakeFirst();
|
|
78
|
+
if (Number(res.numUpdatedRows) === 0) {
|
|
79
|
+
await db
|
|
80
|
+
.insertInto('adminium_user_prefs')
|
|
81
|
+
.values({
|
|
82
|
+
userId,
|
|
83
|
+
theme: values.theme ?? null,
|
|
84
|
+
accent: values.accent ?? null,
|
|
85
|
+
density: values.density ?? null,
|
|
86
|
+
locale: values.locale ?? null,
|
|
87
|
+
dir: values.dir ?? null,
|
|
88
|
+
uiState: values.uiState ?? null,
|
|
89
|
+
updatedAt: at,
|
|
90
|
+
})
|
|
91
|
+
.execute();
|
|
92
|
+
}
|
|
93
|
+
const row = await get(userId);
|
|
94
|
+
if (!row)
|
|
95
|
+
throw new Error('user prefs row disappeared during set');
|
|
96
|
+
return row;
|
|
97
|
+
},
|
|
98
|
+
/** §7.2 merge, implemented exactly once, server-side. */
|
|
99
|
+
async resolve(userId) {
|
|
100
|
+
const glob = {
|
|
101
|
+
theme: await settings.get('appearance.theme'),
|
|
102
|
+
accent: await settings.get('appearance.accent'),
|
|
103
|
+
density: await settings.get('appearance.density'),
|
|
104
|
+
locale: await settings.get('locale.default'),
|
|
105
|
+
};
|
|
106
|
+
const globIsDefault = {
|
|
107
|
+
theme: glob.theme === SYSTEM_PREF_DEFAULTS.theme,
|
|
108
|
+
accent: glob.accent === SYSTEM_PREF_DEFAULTS.accent,
|
|
109
|
+
density: glob.density === SYSTEM_PREF_DEFAULTS.density,
|
|
110
|
+
locale: glob.locale === SYSTEM_PREF_DEFAULTS.locale,
|
|
111
|
+
};
|
|
112
|
+
const user = userId === null ? null : await get(userId);
|
|
113
|
+
const pick = (axis) => {
|
|
114
|
+
const u = user?.[axis];
|
|
115
|
+
if (u !== null && u !== undefined) {
|
|
116
|
+
return { value: u, source: 'user' };
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
value: glob[axis],
|
|
120
|
+
source: globIsDefault[axis] ? 'system' : 'global',
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
const theme = pick('theme');
|
|
124
|
+
const accent = pick('accent');
|
|
125
|
+
const density = pick('density');
|
|
126
|
+
const locale = pick('locale');
|
|
127
|
+
const dir = user?.dir !== null && user?.dir !== undefined
|
|
128
|
+
? { value: user.dir, source: 'user' }
|
|
129
|
+
: { value: dirForLocale(locale.value), source: locale.source };
|
|
130
|
+
return {
|
|
131
|
+
theme: theme.value,
|
|
132
|
+
accent: accent.value,
|
|
133
|
+
density: density.value,
|
|
134
|
+
locale: locale.value,
|
|
135
|
+
dir: dir.value,
|
|
136
|
+
source: {
|
|
137
|
+
theme: theme.source,
|
|
138
|
+
accent: accent.source,
|
|
139
|
+
density: density.source,
|
|
140
|
+
locale: locale.source,
|
|
141
|
+
dir: dir.source,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=user-prefs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-prefs.js","sourceRoot":"","sources":["../../src/repos/user-prefs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usersRepo — adminium_users (07-meta-store.md §3.3).
|
|
3
|
+
* Thin data access only: password hashing happens in the server.
|
|
4
|
+
*/
|
|
5
|
+
import type { MetaDb } from '../connect.js';
|
|
6
|
+
import { type UserStatus } from '../schema/json-payloads.js';
|
|
7
|
+
export interface User {
|
|
8
|
+
id: string;
|
|
9
|
+
email: string;
|
|
10
|
+
name: string;
|
|
11
|
+
passwordHash: string | null;
|
|
12
|
+
status: UserStatus;
|
|
13
|
+
totpSecretEncrypted: string | null;
|
|
14
|
+
totpEnabled: boolean;
|
|
15
|
+
recoveryCodes: string[] | null;
|
|
16
|
+
avatarFileId: string | null;
|
|
17
|
+
lastLoginAt: number | null;
|
|
18
|
+
createdAt: number;
|
|
19
|
+
updatedAt: number;
|
|
20
|
+
}
|
|
21
|
+
export interface CreateUserInput {
|
|
22
|
+
email: string;
|
|
23
|
+
name: string;
|
|
24
|
+
passwordHash?: string | null;
|
|
25
|
+
status?: UserStatus;
|
|
26
|
+
}
|
|
27
|
+
export declare function usersRepo(meta: MetaDb): {
|
|
28
|
+
create(input: CreateUserInput, at?: number): Promise<User>;
|
|
29
|
+
findByEmail(email: string): Promise<User | null>;
|
|
30
|
+
findById(id: string): Promise<User | null>;
|
|
31
|
+
updatePassword(userId: string, passwordHash: string, at?: number): Promise<boolean>;
|
|
32
|
+
updateStatus(userId: string, status: UserStatus, at?: number): Promise<boolean>;
|
|
33
|
+
setRecoveryCodes(userId: string, codes: string[] | null, at?: number): Promise<boolean>;
|
|
34
|
+
recordLogin(userId: string, at?: number): Promise<void>;
|
|
35
|
+
count(): Promise<number>;
|
|
36
|
+
};
|
|
37
|
+
export type UsersRepo = ReturnType<typeof usersRepo>;
|
|
38
|
+
//# sourceMappingURL=users.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/repos/users.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAyC,KAAK,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAIpG,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,UAAU,CAAC;IACnB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAmBD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM;kBAGd,eAAe,OAAM,MAAM,GAAgB,OAAO,CAAC,IAAI,CAAC;uBAwBnD,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;iBASnC,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;2BAKnB,MAAM,gBAAgB,MAAM,OAAM,MAAM,GAAgB,OAAO,CAAC,OAAO,CAAC;yBAS1E,MAAM,UAAU,UAAU,OAAM,MAAM,GAAgB,OAAO,CAAC,OAAO,CAAC;6BASlE,MAAM,SAAS,MAAM,EAAE,GAAG,IAAI,OAAM,MAAM,GAAgB,OAAO,CAAC,OAAO,CAAC;wBAU/E,MAAM,OAAM,MAAM,GAAgB,OAAO,CAAC,IAAI,CAAC;aAQ1D,OAAO,CAAC,MAAM,CAAC;EAQjC;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usersRepo — adminium_users (07-meta-store.md §3.3).
|
|
3
|
+
* Thin data access only: password hashing happens in the server.
|
|
4
|
+
*/
|
|
5
|
+
import { newId } from '../ids.js';
|
|
6
|
+
import { recoveryCodesSchema, userStatusSchema } from '../schema/json-payloads.js';
|
|
7
|
+
import { MetaValidationError, packJson, readBool, readJsonOrNull, writeBool } from './util.js';
|
|
8
|
+
function mapUser(row) {
|
|
9
|
+
return {
|
|
10
|
+
id: row.id,
|
|
11
|
+
email: row.email,
|
|
12
|
+
name: row.name,
|
|
13
|
+
passwordHash: row.passwordHash,
|
|
14
|
+
status: userStatusSchema.parse(row.status),
|
|
15
|
+
totpSecretEncrypted: row.totpSecretEncrypted,
|
|
16
|
+
totpEnabled: readBool(row.totpEnabled),
|
|
17
|
+
recoveryCodes: readJsonOrNull(row.recoveryCodes),
|
|
18
|
+
avatarFileId: row.avatarFileId,
|
|
19
|
+
lastLoginAt: row.lastLoginAt,
|
|
20
|
+
createdAt: row.createdAt,
|
|
21
|
+
updatedAt: row.updatedAt,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function usersRepo(meta) {
|
|
25
|
+
const { db } = meta;
|
|
26
|
+
return {
|
|
27
|
+
async create(input, at = Date.now()) {
|
|
28
|
+
const status = userStatusSchema.parse(input.status ?? 'active');
|
|
29
|
+
const email = input.email.trim().toLowerCase();
|
|
30
|
+
if (email.length === 0 || email.length > 320 || !email.includes('@')) {
|
|
31
|
+
throw new MetaValidationError(`invalid email: ${JSON.stringify(input.email)}`);
|
|
32
|
+
}
|
|
33
|
+
const row = {
|
|
34
|
+
id: newId('usr'),
|
|
35
|
+
email,
|
|
36
|
+
name: input.name,
|
|
37
|
+
passwordHash: input.passwordHash ?? null,
|
|
38
|
+
status,
|
|
39
|
+
totpSecretEncrypted: null,
|
|
40
|
+
totpEnabled: writeBool(meta, false),
|
|
41
|
+
recoveryCodes: null,
|
|
42
|
+
avatarFileId: null,
|
|
43
|
+
lastLoginAt: null,
|
|
44
|
+
createdAt: at,
|
|
45
|
+
updatedAt: at,
|
|
46
|
+
};
|
|
47
|
+
await db.insertInto('adminium_users').values(row).execute();
|
|
48
|
+
return mapUser(row);
|
|
49
|
+
},
|
|
50
|
+
async findByEmail(email) {
|
|
51
|
+
const row = await db
|
|
52
|
+
.selectFrom('adminium_users')
|
|
53
|
+
.selectAll()
|
|
54
|
+
.where('email', '=', email.trim().toLowerCase())
|
|
55
|
+
.executeTakeFirst();
|
|
56
|
+
return row ? mapUser(row) : null;
|
|
57
|
+
},
|
|
58
|
+
async findById(id) {
|
|
59
|
+
const row = await db.selectFrom('adminium_users').selectAll().where('id', '=', id).executeTakeFirst();
|
|
60
|
+
return row ? mapUser(row) : null;
|
|
61
|
+
},
|
|
62
|
+
async updatePassword(userId, passwordHash, at = Date.now()) {
|
|
63
|
+
const res = await db
|
|
64
|
+
.updateTable('adminium_users')
|
|
65
|
+
.set({ passwordHash, updatedAt: at })
|
|
66
|
+
.where('id', '=', userId)
|
|
67
|
+
.executeTakeFirst();
|
|
68
|
+
return Number(res.numUpdatedRows) === 1;
|
|
69
|
+
},
|
|
70
|
+
async updateStatus(userId, status, at = Date.now()) {
|
|
71
|
+
const res = await db
|
|
72
|
+
.updateTable('adminium_users')
|
|
73
|
+
.set({ status: userStatusSchema.parse(status), updatedAt: at })
|
|
74
|
+
.where('id', '=', userId)
|
|
75
|
+
.executeTakeFirst();
|
|
76
|
+
return Number(res.numUpdatedRows) === 1;
|
|
77
|
+
},
|
|
78
|
+
async setRecoveryCodes(userId, codes, at = Date.now()) {
|
|
79
|
+
const value = codes === null ? null : packJson(recoveryCodesSchema.parse(codes));
|
|
80
|
+
const res = await db
|
|
81
|
+
.updateTable('adminium_users')
|
|
82
|
+
.set({ recoveryCodes: value, updatedAt: at })
|
|
83
|
+
.where('id', '=', userId)
|
|
84
|
+
.executeTakeFirst();
|
|
85
|
+
return Number(res.numUpdatedRows) === 1;
|
|
86
|
+
},
|
|
87
|
+
async recordLogin(userId, at = Date.now()) {
|
|
88
|
+
await db
|
|
89
|
+
.updateTable('adminium_users')
|
|
90
|
+
.set({ lastLoginAt: at, updatedAt: at })
|
|
91
|
+
.where('id', '=', userId)
|
|
92
|
+
.execute();
|
|
93
|
+
},
|
|
94
|
+
async count() {
|
|
95
|
+
const row = await db
|
|
96
|
+
.selectFrom('adminium_users')
|
|
97
|
+
.select(({ fn }) => fn.countAll().as('n'))
|
|
98
|
+
.executeTakeFirst();
|
|
99
|
+
return Number(row?.n ?? 0);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=users.js.map
|