@apiagex/database 0.6.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +10 -6
  2. package/dist/admin-permission-repository.d.ts +4 -4
  3. package/dist/admin-permission-repository.d.ts.map +1 -1
  4. package/dist/admin-permission-repository.js +19 -17
  5. package/dist/api-token-repository.d.ts +5 -5
  6. package/dist/api-token-repository.d.ts.map +1 -1
  7. package/dist/api-token-repository.js +19 -20
  8. package/dist/database-adapter.type.d.ts +18 -0
  9. package/dist/database-adapter.type.d.ts.map +1 -0
  10. package/dist/database-adapter.type.js +1 -0
  11. package/dist/entry-query.d.ts +2 -2
  12. package/dist/entry-query.d.ts.map +1 -1
  13. package/dist/entry-query.js +8 -7
  14. package/dist/entry-repository.d.ts +6 -6
  15. package/dist/entry-repository.d.ts.map +1 -1
  16. package/dist/entry-repository.js +46 -56
  17. package/dist/index.d.ts +8 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +4 -0
  20. package/dist/mysql-adapter.d.ts +17 -0
  21. package/dist/mysql-adapter.d.ts.map +1 -0
  22. package/dist/mysql-adapter.js +63 -0
  23. package/dist/permission-repository.d.ts +4 -4
  24. package/dist/permission-repository.d.ts.map +1 -1
  25. package/dist/permission-repository.js +24 -38
  26. package/dist/postgres-adapter.d.ts +17 -0
  27. package/dist/postgres-adapter.d.ts.map +1 -0
  28. package/dist/postgres-adapter.js +61 -0
  29. package/dist/provider-migrations.d.ts +14 -0
  30. package/dist/provider-migrations.d.ts.map +1 -0
  31. package/dist/provider-migrations.js +198 -0
  32. package/dist/realtime-repository.d.ts +11 -11
  33. package/dist/realtime-repository.d.ts.map +1 -1
  34. package/dist/realtime-repository.js +37 -27
  35. package/dist/realtime-session-repository.d.ts +3 -3
  36. package/dist/realtime-session-repository.d.ts.map +1 -1
  37. package/dist/realtime-session-repository.js +11 -11
  38. package/dist/relation-helpers.d.ts +3 -3
  39. package/dist/relation-helpers.d.ts.map +1 -1
  40. package/dist/relation-helpers.js +4 -6
  41. package/dist/role-repository.d.ts +6 -6
  42. package/dist/role-repository.d.ts.map +1 -1
  43. package/dist/role-repository.js +17 -24
  44. package/dist/schema-repository.d.ts +7 -7
  45. package/dist/schema-repository.d.ts.map +1 -1
  46. package/dist/schema-repository.js +63 -88
  47. package/dist/sqlite-adapter.d.ts +15 -0
  48. package/dist/sqlite-adapter.d.ts.map +1 -0
  49. package/dist/sqlite-adapter.js +48 -0
  50. package/dist/user-repository.d.ts +8 -6
  51. package/dist/user-repository.d.ts.map +1 -1
  52. package/dist/user-repository.js +17 -27
  53. package/dist/webhook-repository.d.ts +13 -13
  54. package/dist/webhook-repository.d.ts.map +1 -1
  55. package/dist/webhook-repository.js +47 -43
  56. package/package.json +6 -3
@@ -1,5 +1,5 @@
1
- import type { SqliteDatabase } from "./sqlite.js";
1
+ import type { ApiagexDatabase } from "./database-adapter.type.js";
2
2
  import type { CreatedRealtimeSession, CreateRealtimeSessionInput, RealtimeSessionRecord } from "./realtime-session-repository.type.js";
3
- export declare function createRealtimeSession(db: SqliteDatabase, input: CreateRealtimeSessionInput): CreatedRealtimeSession;
4
- export declare function consumeRealtimeSession(db: SqliteDatabase, token: string, schemaSlug: string, now?: Date): RealtimeSessionRecord | undefined;
3
+ export declare function createRealtimeSession(db: ApiagexDatabase, input: CreateRealtimeSessionInput): Promise<CreatedRealtimeSession>;
4
+ export declare function consumeRealtimeSession(db: ApiagexDatabase, token: string, schemaSlug: string, now?: Date): Promise<RealtimeSessionRecord | undefined>;
5
5
  //# sourceMappingURL=realtime-session-repository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"realtime-session-repository.d.ts","sourceRoot":"","sources":["../src/realtime-session-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,sBAAsB,EACtB,0BAA0B,EAC1B,qBAAqB,EACtB,MAAM,uCAAuC,CAAC;AAI/C,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,0BAA0B,GAAG,sBAAsB,CAoBnH;AAED,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,GAAG,OAAa,GACf,qBAAqB,GAAG,SAAS,CAQnC"}
1
+ {"version":3,"file":"realtime-session-repository.d.ts","sourceRoot":"","sources":["../src/realtime-session-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EACV,sBAAsB,EACtB,0BAA0B,EAC1B,qBAAqB,EACtB,MAAM,uCAAuC,CAAC;AAI/C,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,sBAAsB,CAAC,CAoBjC;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,GAAG,OAAa,GACf,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAS5C"}
@@ -1,31 +1,31 @@
1
1
  import { createHash, randomBytes, randomUUID } from "node:crypto";
2
- export function createRealtimeSession(db, input) {
2
+ export async function createRealtimeSession(db, input) {
3
3
  const now = new Date();
4
4
  const ttlSeconds = Math.max(30, Math.min(input.ttlSeconds ?? 300, 900));
5
5
  const token = `rt_${randomBytes(32).toString("base64url")}`;
6
6
  const id = randomUUID();
7
- db.prepare(`INSERT INTO realtime_sessions
7
+ await db.prepare(`INSERT INTO realtime_sessions
8
8
  (id, token_hash, token_prefix, role_id, schema_id, schema_slug, created_at, expires_at, used_at)
9
9
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)`).run(id, hashToken(token), token.slice(0, 12), input.roleId, input.schemaId, input.schemaSlug, now.toISOString(), new Date(now.getTime() + ttlSeconds * 1000).toISOString());
10
- return { token, session: requireRealtimeSession(db, id) };
10
+ return { token, session: await requireRealtimeSession(db, id) };
11
11
  }
12
- export function consumeRealtimeSession(db, token, schemaSlug, now = new Date()) {
13
- const row = db.prepare(sessionSelectSql("WHERE token_hash = ? AND schema_slug = ?"))
12
+ export async function consumeRealtimeSession(db, token, schemaSlug, now = new Date()) {
13
+ const row = await db
14
+ .prepare(sessionSelectSql("WHERE token_hash = ? AND schema_slug = ?"))
14
15
  .get(hashToken(token), schemaSlug);
15
16
  if (!row || row.usedAt || Date.parse(row.expiresAt) <= now.getTime())
16
17
  return undefined;
17
- const result = db.prepare("UPDATE realtime_sessions SET used_at = ? WHERE id = ? AND used_at IS NULL")
18
+ const result = await db.prepare("UPDATE realtime_sessions SET used_at = ? WHERE id = ? AND used_at IS NULL")
18
19
  .run(now.toISOString(), row.id);
19
20
  if (result.changes !== 1)
20
21
  return undefined;
21
22
  return getRealtimeSessionById(db, row.id);
22
23
  }
23
- function getRealtimeSessionById(db, id) {
24
- const row = db.prepare(sessionSelectSql("WHERE id = ?")).get(id);
25
- return row;
24
+ async function getRealtimeSessionById(db, id) {
25
+ return db.prepare(sessionSelectSql("WHERE id = ?")).get(id);
26
26
  }
27
- function requireRealtimeSession(db, id) {
28
- const session = getRealtimeSessionById(db, id);
27
+ async function requireRealtimeSession(db, id) {
28
+ const session = await getRealtimeSessionById(db, id);
29
29
  if (!session)
30
30
  throw new Error("REALTIME_SESSION_NOT_FOUND");
31
31
  return session;
@@ -1,14 +1,14 @@
1
+ import type { ApiagexDatabase, DatabaseQueryParam } from "./database-adapter.type.js";
1
2
  import type { EntryData } from "./entry-repository.type.js";
2
3
  import type { FieldRecord, RelationType } from "./schema-repository.type.js";
3
- import type { SqliteDatabase } from "./sqlite.js";
4
4
  type EntryDataRow = {
5
5
  dataJson: string;
6
6
  id: string;
7
7
  };
8
8
  export declare function relationTypeOf(field: FieldRecord): RelationType;
9
9
  export declare function entryDataReferences(data: EntryData, entryId: string): boolean;
10
- export declare function listEntryDataRows(db: SqliteDatabase, where?: string, params?: unknown[]): EntryDataRow[];
10
+ export declare function listEntryDataRows(db: ApiagexDatabase, where?: string, params?: DatabaseQueryParam[]): Promise<EntryDataRow[]>;
11
11
  export declare function parseEntryData(dataJson: string): EntryData;
12
- export declare function schemaEntriesUseField(db: SqliteDatabase, schemaId: string, fieldSlug: string): boolean;
12
+ export declare function schemaEntriesUseField(db: ApiagexDatabase, schemaId: string, fieldSlug: string): Promise<boolean>;
13
13
  export {};
14
14
  //# sourceMappingURL=relation-helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"relation-helpers.d.ts","sourceRoot":"","sources":["../src/relation-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,KAAK,YAAY,GAAG;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,CAE/D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAI7E;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,SAAK,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,YAAY,EAAE,CAIxG;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAE1D;AAED,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,cAAc,EAClB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAMT"}
1
+ {"version":3,"file":"relation-helpers.d.ts","sourceRoot":"","sources":["../src/relation-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACtF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE7E,KAAK,YAAY,GAAG;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,CAE/D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAI7E;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,eAAe,EACnB,KAAK,SAAK,EACV,MAAM,GAAE,kBAAkB,EAAO,GAChC,OAAO,CAAC,YAAY,EAAE,CAAC,CAEzB;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAE1D;AAED,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,eAAe,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAMlB"}
@@ -4,16 +4,14 @@ export function relationTypeOf(field) {
4
4
  export function entryDataReferences(data, entryId) {
5
5
  return Object.values(data).some((value) => value === entryId || (Array.isArray(value) && value.includes(entryId)));
6
6
  }
7
- export function listEntryDataRows(db, where = "", params = []) {
8
- return db
9
- .prepare(`SELECT id, data_json as dataJson FROM entries ${where}`)
10
- .all(...params);
7
+ export async function listEntryDataRows(db, where = "", params = []) {
8
+ return db.prepare(`SELECT id, data_json as dataJson FROM entries ${where}`).all(...params);
11
9
  }
12
10
  export function parseEntryData(dataJson) {
13
11
  return JSON.parse(dataJson);
14
12
  }
15
- export function schemaEntriesUseField(db, schemaId, fieldSlug) {
16
- const rows = listEntryDataRows(db, "WHERE schema_id = ?", [schemaId]);
13
+ export async function schemaEntriesUseField(db, schemaId, fieldSlug) {
14
+ const rows = await listEntryDataRows(db, "WHERE schema_id = ?", [schemaId]);
17
15
  return rows.some((row) => {
18
16
  const value = parseEntryData(row.dataJson)[fieldSlug];
19
17
  return value !== undefined && value !== null && value !== "";
@@ -1,8 +1,8 @@
1
- import type { SqliteDatabase } from "./sqlite.js";
1
+ import type { ApiagexDatabase } from "./database-adapter.type.js";
2
2
  import type { CreateRoleInput, RoleRecord } from "./role-repository.type.js";
3
- export declare function createRole(db: SqliteDatabase, input: CreateRoleInput): RoleRecord;
4
- export declare function createAdminRole(db: SqliteDatabase, input: CreateRoleInput): RoleRecord;
5
- export declare function listRoles(db: SqliteDatabase): RoleRecord[];
6
- export declare function listAdminRoles(db: SqliteDatabase): RoleRecord[];
7
- export declare function getRoleById(db: SqliteDatabase, id: string): RoleRecord | undefined;
3
+ export declare function createRole(db: ApiagexDatabase, input: CreateRoleInput): Promise<RoleRecord>;
4
+ export declare function createAdminRole(db: ApiagexDatabase, input: CreateRoleInput): Promise<RoleRecord>;
5
+ export declare function listRoles(db: ApiagexDatabase): Promise<RoleRecord[]>;
6
+ export declare function listAdminRoles(db: ApiagexDatabase): Promise<RoleRecord[]>;
7
+ export declare function getRoleById(db: ApiagexDatabase, id: string): Promise<RoleRecord | undefined>;
8
8
  //# sourceMappingURL=role-repository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"role-repository.d.ts","sourceRoot":"","sources":["../src/role-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAO7E,wBAAgB,UAAU,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,GAAG,UAAU,CAQjF;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,GAAG,UAAU,CAQtF;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,cAAc,GAAG,UAAU,EAAE,CAK1D;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,cAAc,GAAG,UAAU,EAAE,CAK/D;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAGlF"}
1
+ {"version":3,"file":"role-repository.d.ts","sourceRoot":"","sources":["../src/role-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAO7E,wBAAsB,UAAU,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAQjG;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAQtG;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAG1E;AAED,wBAAsB,cAAc,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAK/E;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAGlG"}
@@ -1,61 +1,54 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  const roleNamePattern = /^[a-z][a-z0-9-]*$/;
3
3
  const adminRoleNames = new Set(["owner", "admin", "schema-manager", "user-manager"]);
4
- export function createRole(db, input) {
4
+ export async function createRole(db, input) {
5
5
  validateApiRole(input);
6
6
  const id = randomUUID();
7
7
  const now = new Date().toISOString();
8
- db.prepare("INSERT INTO roles (id, name, description, is_owner, role_kind, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, input.name, input.description ?? "", 0, "api", now, now);
8
+ await db.prepare("INSERT INTO roles (id, name, description, is_owner, role_kind, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, input.name, input.description ?? "", 0, "api", now, now);
9
9
  return requireRole(db, id);
10
10
  }
11
- export function createAdminRole(db, input) {
11
+ export async function createAdminRole(db, input) {
12
12
  validateAdminRole(input);
13
13
  const id = randomUUID();
14
14
  const now = new Date().toISOString();
15
- db.prepare("INSERT INTO roles (id, name, description, is_owner, role_kind, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, input.name, input.description ?? "", 0, "admin", now, now);
15
+ await db.prepare("INSERT INTO roles (id, name, description, is_owner, role_kind, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, input.name, input.description ?? "", 0, "admin", now, now);
16
16
  return requireRole(db, id);
17
17
  }
18
- export function listRoles(db) {
19
- const rows = db
20
- .prepare(roleSelectSql("WHERE role_kind = 'api' ORDER BY created_at ASC"))
21
- .all();
18
+ export async function listRoles(db) {
19
+ const rows = await db.prepare(roleSelectSql("WHERE role_kind = 'api' ORDER BY created_at ASC")).all();
22
20
  return rows.map(rowToRole);
23
21
  }
24
- export function listAdminRoles(db) {
25
- const rows = db
22
+ export async function listAdminRoles(db) {
23
+ const rows = await db
26
24
  .prepare(roleSelectSql("WHERE role_kind = 'admin' ORDER BY is_owner DESC, created_at ASC"))
27
25
  .all();
28
26
  return rows.map(rowToRole);
29
27
  }
30
- export function getRoleById(db, id) {
31
- const row = db.prepare(roleSelectSql("WHERE id = ?")).get(id);
28
+ export async function getRoleById(db, id) {
29
+ const row = await db.prepare(roleSelectSql("WHERE id = ?")).get(id);
32
30
  return row ? rowToRole(row) : undefined;
33
31
  }
34
32
  function validateApiRole(input) {
35
33
  validateRoleName(input);
36
- if (input.name === "owner") {
34
+ if (input.name === "owner")
37
35
  throw new Error("ROLE_OWNER_RESERVED");
38
- }
39
- if (adminRoleNames.has(input.name)) {
36
+ if (adminRoleNames.has(input.name))
40
37
  throw new Error("ROLE_ADMIN_RESERVED");
41
- }
42
38
  }
43
39
  function validateAdminRole(input) {
44
40
  validateRoleName(input);
45
- if (input.name === "owner") {
41
+ if (input.name === "owner")
46
42
  throw new Error("ROLE_OWNER_RESERVED");
47
- }
48
43
  }
49
44
  function validateRoleName(input) {
50
- if (!roleNamePattern.test(input.name)) {
45
+ if (!roleNamePattern.test(input.name))
51
46
  throw new Error("ROLE_NAME_INVALID");
52
- }
53
47
  }
54
- function requireRole(db, id) {
55
- const role = getRoleById(db, id);
56
- if (!role) {
48
+ async function requireRole(db, id) {
49
+ const role = await getRoleById(db, id);
50
+ if (!role)
57
51
  throw new Error("ROLE_NOT_FOUND");
58
- }
59
52
  return role;
60
53
  }
61
54
  function rowToRole(row) {
@@ -1,9 +1,9 @@
1
- import type { SqliteDatabase } from "./sqlite.js";
1
+ import type { ApiagexDatabase } from "./database-adapter.type.js";
2
2
  import type { CreateSchemaInput, SchemaRecord, UpdateSchemaInput } from "./schema-repository.type.js";
3
- export declare function createSchema(db: SqliteDatabase, input: CreateSchemaInput): SchemaRecord;
4
- export declare function listSchemas(db: SqliteDatabase): SchemaRecord[];
5
- export declare function getSchemaById(db: SqliteDatabase, id: string): SchemaRecord | undefined;
6
- export declare function getSchemaBySlug(db: SqliteDatabase, slug: string): SchemaRecord | undefined;
7
- export declare function updateSchema(db: SqliteDatabase, id: string, input: UpdateSchemaInput): SchemaRecord;
8
- export declare function deleteSchema(db: SqliteDatabase, id: string): void;
3
+ export declare function createSchema(db: ApiagexDatabase, input: CreateSchemaInput): Promise<SchemaRecord>;
4
+ export declare function listSchemas(db: ApiagexDatabase): Promise<SchemaRecord[]>;
5
+ export declare function getSchemaById(db: ApiagexDatabase, id: string): Promise<SchemaRecord | undefined>;
6
+ export declare function getSchemaBySlug(db: ApiagexDatabase, slug: string): Promise<SchemaRecord | undefined>;
7
+ export declare function updateSchema(db: ApiagexDatabase, id: string, input: UpdateSchemaInput): Promise<SchemaRecord>;
8
+ export declare function deleteSchema(db: ApiagexDatabase, id: string): Promise<void>;
9
9
  //# sourceMappingURL=schema-repository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-repository.d.ts","sourceRoot":"","sources":["../src/schema-repository.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAEV,iBAAiB,EAIjB,YAAY,EACZ,iBAAiB,EAClB,MAAM,6BAA6B,CAAC;AAoBrC,wBAAgB,YAAY,CAC1B,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,iBAAiB,GACvB,YAAY,CAmBd;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,cAAc,GAAG,YAAY,EAAE,CAK9D;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,cAAc,EAClB,EAAE,EAAE,MAAM,GACT,YAAY,GAAG,SAAS,CAQ1B;AAED,wBAAgB,eAAe,CAC7B,EAAE,EAAE,cAAc,EAClB,IAAI,EAAE,MAAM,GACX,YAAY,GAAG,SAAS,CAK1B;AAED,wBAAgB,YAAY,CAC1B,EAAE,EAAE,cAAc,EAClB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,iBAAiB,GACvB,YAAY,CAsBd;AA6BD,wBAAgB,YAAY,CAAC,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAMjE"}
1
+ {"version":3,"file":"schema-repository.d.ts","sourceRoot":"","sources":["../src/schema-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAOlE,OAAO,KAAK,EAEV,iBAAiB,EAIjB,YAAY,EACZ,iBAAiB,EAClB,MAAM,6BAA6B,CAAC;AAMrC,wBAAsB,YAAY,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC,CAkBvG;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAI9E;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAKtG;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAG1G;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,eAAe,EACnB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,YAAY,CAAC,CAmBvB;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjF"}
@@ -2,153 +2,128 @@ import { randomUUID } from "node:crypto";
2
2
  import { relationErrors, relationFieldUpdateUnsafe, relationSchemaReferenced, } from "./relation-errors.js";
3
3
  import { schemaEntriesUseField } from "./relation-helpers.js";
4
4
  const slugPattern = /^[a-z][a-z0-9-]*$/;
5
- const fieldTypes = [
6
- "text",
7
- "longText",
8
- "number",
9
- "boolean",
10
- "date",
11
- "json",
12
- "media",
13
- "relation",
14
- ];
15
- const relationTypes = [
16
- "oneToOne",
17
- "oneToMany",
18
- "manyToOne",
19
- "manyToMany",
20
- ];
21
- export function createSchema(db, input) {
22
- validateSchemaInput(db, input);
5
+ const fieldTypes = ["text", "longText", "number", "boolean", "date", "json", "media", "relation"];
6
+ const relationTypes = ["oneToOne", "oneToMany", "manyToOne", "manyToMany"];
7
+ export async function createSchema(db, input) {
8
+ await validateSchemaInput(db, input);
23
9
  const schemaId = randomUUID();
24
10
  const now = new Date().toISOString();
25
11
  const description = input.description ?? "";
26
- const create = db.transaction(() => {
27
- db.prepare("INSERT INTO schemas (id, name, slug, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(schemaId, input.name, input.slug, description, now, now);
28
- input.fields.forEach((field, index) => insertField(db, schemaId, field, index));
12
+ await db.transaction(async () => {
13
+ await db.prepare("INSERT INTO schemas (id, name, slug, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(schemaId, input.name, input.slug, description, now, now);
14
+ for (const [index, field] of input.fields.entries()) {
15
+ await insertField(db, schemaId, field, index);
16
+ }
29
17
  });
30
- create();
31
- const created = getSchemaById(db, schemaId);
32
- if (!created) {
18
+ const created = await getSchemaById(db, schemaId);
19
+ if (!created)
33
20
  throw new Error("SCHEMA_CREATE_FAILED");
34
- }
35
21
  return created;
36
22
  }
37
- export function listSchemas(db) {
38
- const rows = db.prepare("SELECT id FROM schemas ORDER BY created_at ASC").all();
39
- return rows.map((row) => getSchemaById(db, row.id)).filter(isSchemaRecord);
23
+ export async function listSchemas(db) {
24
+ const rows = await db.prepare("SELECT id FROM schemas ORDER BY created_at ASC").all();
25
+ const schemas = await Promise.all(rows.map((row) => getSchemaById(db, row.id)));
26
+ return schemas.filter(isSchemaRecord);
40
27
  }
41
- export function getSchemaById(db, id) {
42
- const schema = db.prepare("SELECT id, name, slug, description FROM schemas WHERE id = ?").get(id);
43
- if (!schema) {
44
- return undefined;
45
- }
46
- return { ...schema, fields: listFields(db, schema.id) };
28
+ export async function getSchemaById(db, id) {
29
+ const schema = await db
30
+ .prepare("SELECT id, name, slug, description FROM schemas WHERE id = ?")
31
+ .get(id);
32
+ return schema ? { ...schema, fields: await listFields(db, schema.id) } : undefined;
47
33
  }
48
- export function getSchemaBySlug(db, slug) {
49
- const row = db.prepare("SELECT id FROM schemas WHERE slug = ?").get(slug);
34
+ export async function getSchemaBySlug(db, slug) {
35
+ const row = await db.prepare("SELECT id FROM schemas WHERE slug = ?").get(slug);
50
36
  return row ? getSchemaById(db, row.id) : undefined;
51
37
  }
52
- export function updateSchema(db, id, input) {
53
- if (!getSchemaById(db, id)) {
38
+ export async function updateSchema(db, id, input) {
39
+ if (!(await getSchemaById(db, id)))
54
40
  throw new Error("SCHEMA_NOT_FOUND");
55
- }
56
- assertSafeRelationUpdate(db, id, input);
57
- validateSchemaInput(db, input);
41
+ await assertSafeRelationUpdate(db, id, input);
42
+ await validateSchemaInput(db, input);
58
43
  const now = new Date().toISOString();
59
44
  const description = input.description ?? "";
60
- const update = db.transaction(() => {
61
- db.prepare("UPDATE schemas SET name = ?, slug = ?, description = ?, updated_at = ? WHERE id = ?").run(input.name, input.slug, description, now, id);
62
- db.prepare("DELETE FROM fields WHERE schema_id = ?").run(id);
63
- input.fields.forEach((field, index) => insertField(db, id, field, index));
45
+ await db.transaction(async () => {
46
+ await db.prepare("UPDATE schemas SET name = ?, slug = ?, description = ?, updated_at = ? WHERE id = ?")
47
+ .run(input.name, input.slug, description, now, id);
48
+ await db.prepare("DELETE FROM fields WHERE schema_id = ?").run(id);
49
+ for (const [index, field] of input.fields.entries()) {
50
+ await insertField(db, id, field, index);
51
+ }
64
52
  });
65
- update();
66
- const updated = getSchemaById(db, id);
67
- if (!updated) {
53
+ const updated = await getSchemaById(db, id);
54
+ if (!updated)
68
55
  throw new Error("SCHEMA_UPDATE_FAILED");
69
- }
70
56
  return updated;
71
57
  }
72
- function assertSafeRelationUpdate(db, schemaId, input) {
73
- const current = getSchemaById(db, schemaId);
58
+ export async function deleteSchema(db, id) {
59
+ await assertSchemaNotReferenced(db, id);
60
+ const result = await db.prepare("DELETE FROM schemas WHERE id = ?").run(id);
61
+ if (result.changes === 0)
62
+ throw new Error("SCHEMA_NOT_FOUND");
63
+ }
64
+ async function assertSafeRelationUpdate(db, schemaId, input) {
65
+ const current = await getSchemaById(db, schemaId);
74
66
  if (!current)
75
67
  return;
76
68
  const nextBySlug = new Map(input.fields.map((field) => [field.slug, field]));
77
69
  for (const currentField of current.fields) {
78
70
  if (currentField.type !== "relation")
79
71
  continue;
80
- if (!schemaEntriesUseField(db, schemaId, currentField.slug))
72
+ if (!(await schemaEntriesUseField(db, schemaId, currentField.slug)))
81
73
  continue;
82
74
  const nextField = nextBySlug.get(currentField.slug);
83
- if (!nextField || nextField.type !== "relation") {
75
+ if (!nextField || nextField.type !== "relation")
84
76
  throw new Error(relationFieldUpdateUnsafe(currentField.slug));
85
- }
86
77
  const currentRelationType = currentField.relationType ?? "manyToOne";
87
78
  const nextRelationType = nextField.relationType ?? "manyToOne";
88
- if (currentField.relationSchemaId !== nextField.relationSchemaId ||
89
- currentRelationType !== nextRelationType) {
79
+ if (currentField.relationSchemaId !== nextField.relationSchemaId || currentRelationType !== nextRelationType) {
90
80
  throw new Error(relationFieldUpdateUnsafe(currentField.slug));
91
81
  }
92
82
  }
93
83
  }
94
- export function deleteSchema(db, id) {
95
- assertSchemaNotReferenced(db, id);
96
- const result = db.prepare("DELETE FROM schemas WHERE id = ?").run(id);
97
- if (result.changes === 0) {
98
- throw new Error("SCHEMA_NOT_FOUND");
99
- }
100
- }
101
- function assertSchemaNotReferenced(db, id) {
102
- const dependent = db
84
+ async function assertSchemaNotReferenced(db, id) {
85
+ const dependent = await db
103
86
  .prepare("SELECT schema_id as schemaId FROM fields WHERE relation_schema_id = ? AND schema_id != ? LIMIT 1")
104
87
  .get(id, id);
105
- if (dependent) {
88
+ if (dependent)
106
89
  throw new Error(relationSchemaReferenced(id));
107
- }
108
90
  }
109
- function insertField(db, schemaId, field, position) {
110
- db.prepare("INSERT INTO fields (id, schema_id, name, slug, type, relation_schema_id, relation_type, required, position) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(randomUUID(), schemaId, field.name, field.slug, field.type, field.relationSchemaId ?? null, field.relationType ?? null, field.required ? 1 : 0, position);
91
+ async function insertField(db, schemaId, field, position) {
92
+ await db.prepare("INSERT INTO fields (id, schema_id, name, slug, type, relation_schema_id, relation_type, required, position) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(randomUUID(), schemaId, field.name, field.slug, field.type, field.relationSchemaId ?? null, field.relationType ?? null, field.required ? 1 : 0, position);
111
93
  }
112
- function listFields(db, schemaId) {
113
- const rows = db
94
+ async function listFields(db, schemaId) {
95
+ const rows = await db
114
96
  .prepare("SELECT id, schema_id as schemaId, name, slug, type, relation_schema_id as relationSchemaId, relation_type as relationType, required, position FROM fields WHERE schema_id = ? ORDER BY position ASC")
115
97
  .all(schemaId);
116
98
  return rows.map((row) => ({ ...row, required: Boolean(row.required) }));
117
99
  }
118
- function validateSchemaInput(db, input) {
100
+ async function validateSchemaInput(db, input) {
119
101
  validateSlug(input.slug, "SCHEMA_SLUG_INVALID");
120
- if (input.fields.length === 0) {
102
+ if (input.fields.length === 0)
121
103
  throw new Error("SCHEMA_FIELDS_REQUIRED");
122
- }
123
104
  for (const field of input.fields) {
124
105
  validateSlug(field.slug, "FIELD_SLUG_INVALID");
125
- if (!fieldTypes.includes(field.type)) {
106
+ if (!fieldTypes.includes(field.type))
126
107
  throw new Error("FIELD_TYPE_INVALID");
127
- }
128
- validateRelationMetadata(db, field);
108
+ await validateRelationMetadata(db, field);
129
109
  }
130
110
  }
131
- function validateRelationMetadata(db, field) {
111
+ async function validateRelationMetadata(db, field) {
132
112
  if (field.type !== "relation") {
133
- if (field.relationSchemaId || field.relationType) {
113
+ if (field.relationSchemaId || field.relationType)
134
114
  throw new Error(relationErrors.metadataForNonRelationField);
135
- }
136
115
  return;
137
116
  }
138
- if (!field.relationSchemaId) {
117
+ if (!field.relationSchemaId)
139
118
  throw new Error(relationErrors.targetRequired);
140
- }
141
- if (!getSchemaById(db, field.relationSchemaId)) {
119
+ if (!(await getSchemaById(db, field.relationSchemaId)))
142
120
  throw new Error(relationErrors.targetMissing);
143
- }
144
- if (field.relationType && !relationTypes.includes(field.relationType)) {
121
+ if (field.relationType && !relationTypes.includes(field.relationType))
145
122
  throw new Error(relationErrors.typeInvalid);
146
- }
147
123
  }
148
124
  function validateSlug(slug, error) {
149
- if (!slugPattern.test(slug)) {
125
+ if (!slugPattern.test(slug))
150
126
  throw new Error(error);
151
- }
152
127
  }
153
128
  function isSchemaRecord(value) {
154
129
  return Boolean(value);
@@ -0,0 +1,15 @@
1
+ import type { ApiagexDatabase, DatabaseStatement } from "./database-adapter.type.js";
2
+ import { type SqliteDatabase } from "./sqlite.js";
3
+ export declare class SqliteApiagexDatabase implements ApiagexDatabase {
4
+ private readonly db;
5
+ readonly provider = "sqlite";
6
+ constructor(db: SqliteDatabase);
7
+ exec(sql: string): Promise<void>;
8
+ prepare(sql: string): DatabaseStatement;
9
+ transaction<TResult>(callback: () => Promise<TResult>): Promise<TResult>;
10
+ close(): Promise<void>;
11
+ }
12
+ export declare function openSqliteAdapter(path?: string): ApiagexDatabase;
13
+ export declare function openMigratedSqliteAdapter(path?: string): ApiagexDatabase;
14
+ export declare function wrapSqliteDatabase(db: SqliteDatabase): ApiagexDatabase;
15
+ //# sourceMappingURL=sqlite-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-adapter.d.ts","sourceRoot":"","sources":["../src/sqlite-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAyC,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC5H,OAAO,EAA0C,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE1F,qBAAa,qBAAsB,YAAW,eAAe;IAG/C,OAAO,CAAC,QAAQ,CAAC,EAAE;IAF/B,QAAQ,CAAC,QAAQ,YAAY;gBAEA,EAAE,EAAE,cAAc;IAEzC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB;IASjC,WAAW,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAYxE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B;AAED,wBAAgB,iBAAiB,CAAC,IAAI,SAAa,GAAG,eAAe,CAEpE;AAED,wBAAgB,yBAAyB,CAAC,IAAI,SAAa,GAAG,eAAe,CAI5E;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,cAAc,GAAG,eAAe,CAEtE"}
@@ -0,0 +1,48 @@
1
+ import { migrateMvpDatabase, openSqliteDatabase } from "./sqlite.js";
2
+ export class SqliteApiagexDatabase {
3
+ db;
4
+ provider = "sqlite";
5
+ constructor(db) {
6
+ this.db = db;
7
+ }
8
+ async exec(sql) {
9
+ this.db.exec(sql);
10
+ }
11
+ prepare(sql) {
12
+ const statement = this.db.prepare(sql);
13
+ return {
14
+ get: async (...params) => statement.get(...params),
15
+ all: async (...params) => statement.all(...params),
16
+ run: async (...params) => toRunResult(statement.run(...params)),
17
+ };
18
+ }
19
+ async transaction(callback) {
20
+ this.db.exec("BEGIN");
21
+ try {
22
+ const result = await callback();
23
+ this.db.exec("COMMIT");
24
+ return result;
25
+ }
26
+ catch (error) {
27
+ this.db.exec("ROLLBACK");
28
+ throw error;
29
+ }
30
+ }
31
+ async close() {
32
+ this.db.close();
33
+ }
34
+ }
35
+ export function openSqliteAdapter(path = ":memory:") {
36
+ return new SqliteApiagexDatabase(openSqliteDatabase(path));
37
+ }
38
+ export function openMigratedSqliteAdapter(path = ":memory:") {
39
+ const db = openSqliteDatabase(path);
40
+ migrateMvpDatabase(db);
41
+ return new SqliteApiagexDatabase(db);
42
+ }
43
+ export function wrapSqliteDatabase(db) {
44
+ return new SqliteApiagexDatabase(db);
45
+ }
46
+ function toRunResult(result) {
47
+ return { changes: result.changes };
48
+ }
@@ -1,11 +1,13 @@
1
- import type { SqliteDatabase } from "./sqlite.js";
1
+ import type { ApiagexDatabase } from "./database-adapter.type.js";
2
2
  import type { CreateUserInput, UserRecord } from "./user-repository.type.js";
3
- export declare function createUser(db: SqliteDatabase, input: CreateUserInput): UserRecord;
4
- export declare function listUsers(db: SqliteDatabase): UserRecord[];
5
- export declare function getUserById(db: SqliteDatabase, id: string): UserRecord | undefined;
6
- export declare function getUserPasswordHashByEmail(db: SqliteDatabase, email: string): {
3
+ type UserPasswordRecord = {
7
4
  id: string;
8
5
  passwordHash: string;
9
6
  roleId: string;
10
- } | undefined;
7
+ };
8
+ export declare function createUser(db: ApiagexDatabase, input: CreateUserInput): Promise<UserRecord>;
9
+ export declare function listUsers(db: ApiagexDatabase): Promise<UserRecord[]>;
10
+ export declare function getUserById(db: ApiagexDatabase, id: string): Promise<UserRecord | undefined>;
11
+ export declare function getUserPasswordHashByEmail(db: ApiagexDatabase, email: string): Promise<UserPasswordRecord | undefined>;
12
+ export {};
11
13
  //# sourceMappingURL=user-repository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"user-repository.d.ts","sourceRoot":"","sources":["../src/user-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAI7E,wBAAgB,UAAU,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,GAAG,UAAU,CASjF;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,cAAc,GAAG,UAAU,EAAE,CAK1D;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAIlF;AAED,wBAAgB,0BAA0B,CACxC,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,MAAM,GACZ;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAQlE"}
1
+ {"version":3,"file":"user-repository.d.ts","sourceRoot":"","sources":["../src/user-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElE,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE7E,KAAK,kBAAkB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAG/E,wBAAsB,UAAU,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CASjG;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAE1E;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAElG;AAED,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAQzC"}
@@ -1,51 +1,41 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { getRoleById } from "./role-repository.js";
3
- export function createUser(db, input) {
3
+ export async function createUser(db, input) {
4
4
  const email = normalizeEmail(input.email);
5
- validateUser(db, { ...input, email });
5
+ await validateUser(db, { ...input, email });
6
6
  const id = randomUUID();
7
7
  const now = new Date().toISOString();
8
- db.prepare("INSERT INTO users (id, email, password_hash, role_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(id, email, input.passwordHash, input.roleId, now, now);
8
+ await db.prepare("INSERT INTO users (id, email, password_hash, role_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(id, email, input.passwordHash, input.roleId, now, now);
9
9
  return requireUser(db, id);
10
10
  }
11
- export function listUsers(db) {
12
- const rows = db
13
- .prepare(userSelectSql("WHERE roles.role_kind = 'api' ORDER BY users.created_at ASC"))
14
- .all();
15
- return rows;
11
+ export async function listUsers(db) {
12
+ return db.prepare(userSelectSql("WHERE roles.role_kind = 'api' ORDER BY users.created_at ASC")).all();
16
13
  }
17
- export function getUserById(db, id) {
18
- return db
19
- .prepare(userSelectSql("WHERE users.id = ? AND roles.role_kind = 'api'"))
20
- .get(id);
14
+ export async function getUserById(db, id) {
15
+ return db.prepare(userSelectSql("WHERE users.id = ? AND roles.role_kind = 'api'")).get(id);
21
16
  }
22
- export function getUserPasswordHashByEmail(db, email) {
17
+ export async function getUserPasswordHashByEmail(db, email) {
23
18
  return db
24
19
  .prepare(`SELECT users.id, users.password_hash as passwordHash, users.role_id as roleId
25
20
  FROM users JOIN roles ON roles.id = users.role_id
26
21
  WHERE users.email = ? AND roles.role_kind = 'api'`)
27
22
  .get(normalizeEmail(email));
28
23
  }
29
- function validateUser(db, input) {
30
- if (!input.email.includes("@")) {
24
+ async function validateUser(db, input) {
25
+ if (!input.email.includes("@"))
31
26
  throw new Error("USER_EMAIL_INVALID");
32
- }
33
- if (!input.passwordHash) {
27
+ if (!input.passwordHash)
34
28
  throw new Error("USER_PASSWORD_HASH_REQUIRED");
35
- }
36
- const role = getRoleById(db, input.roleId);
37
- if (!role) {
29
+ const role = await getRoleById(db, input.roleId);
30
+ if (!role)
38
31
  throw new Error("ROLE_NOT_FOUND");
39
- }
40
- if (role.roleKind !== "api") {
32
+ if (role.roleKind !== "api")
41
33
  throw new Error("ROLE_API_REQUIRED");
42
- }
43
34
  }
44
- function requireUser(db, id) {
45
- const user = getUserById(db, id);
46
- if (!user) {
35
+ async function requireUser(db, id) {
36
+ const user = await getUserById(db, id);
37
+ if (!user)
47
38
  throw new Error("USER_NOT_FOUND");
48
- }
49
39
  return user;
50
40
  }
51
41
  function normalizeEmail(email) {