@apiagex/database 0.6.4 → 0.8.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/README.md +10 -6
- package/dist/admin-permission-repository.d.ts +4 -4
- package/dist/admin-permission-repository.d.ts.map +1 -1
- package/dist/admin-permission-repository.js +19 -17
- package/dist/api-token-repository.d.ts +5 -5
- package/dist/api-token-repository.d.ts.map +1 -1
- package/dist/api-token-repository.js +19 -20
- package/dist/database-adapter.type.d.ts +18 -0
- package/dist/database-adapter.type.d.ts.map +1 -0
- package/dist/database-adapter.type.js +1 -0
- package/dist/entry-query.d.ts +2 -2
- package/dist/entry-query.d.ts.map +1 -1
- package/dist/entry-query.js +8 -7
- package/dist/entry-repository.d.ts +6 -6
- package/dist/entry-repository.d.ts.map +1 -1
- package/dist/entry-repository.js +46 -56
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/mysql-adapter.d.ts +18 -0
- package/dist/mysql-adapter.d.ts.map +1 -0
- package/dist/mysql-adapter.js +70 -0
- package/dist/permission-repository.d.ts +4 -4
- package/dist/permission-repository.d.ts.map +1 -1
- package/dist/permission-repository.js +24 -38
- package/dist/postgres-adapter.d.ts +18 -0
- package/dist/postgres-adapter.d.ts.map +1 -0
- package/dist/postgres-adapter.js +67 -0
- package/dist/provider-migrations.d.ts +14 -0
- package/dist/provider-migrations.d.ts.map +1 -0
- package/dist/provider-migrations.js +198 -0
- package/dist/realtime-repository.d.ts +11 -11
- package/dist/realtime-repository.d.ts.map +1 -1
- package/dist/realtime-repository.js +37 -27
- package/dist/realtime-session-repository.d.ts +3 -3
- package/dist/realtime-session-repository.d.ts.map +1 -1
- package/dist/realtime-session-repository.js +11 -11
- package/dist/relation-helpers.d.ts +3 -3
- package/dist/relation-helpers.d.ts.map +1 -1
- package/dist/relation-helpers.js +4 -6
- package/dist/role-repository.d.ts +6 -6
- package/dist/role-repository.d.ts.map +1 -1
- package/dist/role-repository.js +17 -24
- package/dist/schema-repository.d.ts +7 -7
- package/dist/schema-repository.d.ts.map +1 -1
- package/dist/schema-repository.js +63 -88
- package/dist/sqlite-adapter.d.ts +15 -0
- package/dist/sqlite-adapter.d.ts.map +1 -0
- package/dist/sqlite-adapter.js +48 -0
- package/dist/user-repository.d.ts +8 -6
- package/dist/user-repository.d.ts.map +1 -1
- package/dist/user-repository.js +17 -27
- package/dist/webhook-repository.d.ts +13 -13
- package/dist/webhook-repository.d.ts.map +1 -1
- package/dist/webhook-repository.js +47 -43
- package/package.json +6 -3
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getSchemaById, listSchemas } from "./schema-repository.js";
|
|
3
3
|
const allowedEvents = new Set(["entry.created", "entry.updated", "entry.deleted"]);
|
|
4
|
-
export function listRealtimeConfigs(db) {
|
|
5
|
-
const rows = db.prepare(realtimeSelectSql("ORDER BY updated_at DESC")).all();
|
|
4
|
+
export async function listRealtimeConfigs(db) {
|
|
5
|
+
const rows = await db.prepare(realtimeSelectSql("ORDER BY updated_at DESC")).all();
|
|
6
6
|
return rows.map(rowToRealtimeConfig);
|
|
7
7
|
}
|
|
8
|
-
export function listRealtimeSettings(db) {
|
|
9
|
-
const configs = new Map(listRealtimeConfigs(db).map((config) => [config.schemaId, config]));
|
|
8
|
+
export async function listRealtimeSettings(db) {
|
|
9
|
+
const configs = new Map((await listRealtimeConfigs(db)).map((config) => [config.schemaId, config]));
|
|
10
10
|
const now = new Date().toISOString();
|
|
11
|
-
return listSchemas(db).map((schema) => configs.get(schema.id) ?? {
|
|
11
|
+
return (await listSchemas(db)).map((schema) => configs.get(schema.id) ?? {
|
|
12
12
|
schemaId: schema.id,
|
|
13
13
|
enabled: false,
|
|
14
14
|
events: ["entry.created", "entry.updated", "entry.deleted"],
|
|
@@ -16,52 +16,62 @@ export function listRealtimeSettings(db) {
|
|
|
16
16
|
updatedAt: now,
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
-
export function getRealtimeConfig(db, schemaId) {
|
|
20
|
-
const row = db.prepare(realtimeSelectSql("WHERE schema_id = ?")).get(schemaId);
|
|
19
|
+
export async function getRealtimeConfig(db, schemaId) {
|
|
20
|
+
const row = await db.prepare(realtimeSelectSql("WHERE schema_id = ?")).get(schemaId);
|
|
21
21
|
return row ? rowToRealtimeConfig(row) : undefined;
|
|
22
22
|
}
|
|
23
|
-
export function setRealtimeConfig(db, input) {
|
|
23
|
+
export async function setRealtimeConfig(db, input) {
|
|
24
24
|
const events = normalizeEvents(input.events);
|
|
25
|
-
if (!getSchemaById(db, input.schemaId))
|
|
25
|
+
if (!(await getSchemaById(db, input.schemaId)))
|
|
26
26
|
throw new Error("SCHEMA_NOT_FOUND");
|
|
27
27
|
const now = new Date().toISOString();
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
const existing = await getRealtimeConfig(db, input.schemaId);
|
|
29
|
+
if (existing) {
|
|
30
|
+
await db.prepare("UPDATE realtime_configs SET enabled = ?, events_json = ?, updated_at = ? WHERE schema_id = ?")
|
|
31
|
+
.run(input.enabled ? 1 : 0, JSON.stringify(events), now, input.schemaId);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
await db.prepare("INSERT INTO realtime_configs (schema_id, enabled, events_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(input.schemaId, input.enabled ? 1 : 0, JSON.stringify(events), now, now);
|
|
35
|
+
}
|
|
36
|
+
const config = await getRealtimeConfig(db, input.schemaId);
|
|
37
|
+
if (!config)
|
|
38
|
+
throw new Error("REALTIME_CONFIG_NOT_FOUND");
|
|
39
|
+
return config;
|
|
34
40
|
}
|
|
35
|
-
export function isRealtimeEventEnabled(db, schemaId, event) {
|
|
36
|
-
const config = getRealtimeConfig(db, schemaId);
|
|
41
|
+
export async function isRealtimeEventEnabled(db, schemaId, event) {
|
|
42
|
+
const config = await getRealtimeConfig(db, schemaId);
|
|
37
43
|
return Boolean(config?.enabled && config.events.includes(event));
|
|
38
44
|
}
|
|
39
|
-
export function recordRealtimeEvent(db, input) {
|
|
45
|
+
export async function recordRealtimeEvent(db, input) {
|
|
40
46
|
const now = new Date().toISOString();
|
|
41
47
|
const id = `rte_${randomUUID()}`;
|
|
42
48
|
const messageId = `rtm_${randomUUID()}`;
|
|
43
|
-
db.prepare(`INSERT INTO realtime_events
|
|
49
|
+
await db.prepare(`INSERT INTO realtime_events
|
|
44
50
|
(id, message_id, event_type, schema_id, schema_slug, entry_id, entry_json, occurred_at, created_at)
|
|
45
51
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, messageId, input.eventType, input.schemaId, input.schemaSlug, input.entry.id, JSON.stringify(input.entry), now, now);
|
|
46
|
-
|
|
52
|
+
const row = await db.prepare(realtimeEventSelectSql("WHERE id = ?")).get(id);
|
|
53
|
+
if (!row)
|
|
54
|
+
throw new Error("REALTIME_EVENT_NOT_FOUND");
|
|
55
|
+
return rowToRealtimeEvent(row);
|
|
47
56
|
}
|
|
48
|
-
export function listRealtimeEventsAfter(db, schemaId, lastEventId, limit = 50) {
|
|
49
|
-
const last = db.prepare("SELECT sequence FROM realtime_events WHERE id = ? AND schema_id = ?")
|
|
57
|
+
export async function listRealtimeEventsAfter(db, schemaId, lastEventId, limit = 50) {
|
|
58
|
+
const last = await db.prepare("SELECT sequence FROM realtime_events WHERE id = ? AND schema_id = ?")
|
|
50
59
|
.get(lastEventId, schemaId);
|
|
51
60
|
if (!last)
|
|
52
61
|
return [];
|
|
53
|
-
const rows = db
|
|
62
|
+
const rows = await db
|
|
63
|
+
.prepare(realtimeEventSelectSql("WHERE schema_id = ? AND sequence > ? ORDER BY sequence ASC LIMIT ?"))
|
|
54
64
|
.all(schemaId, last.sequence, Math.max(1, Math.min(limit, 100)));
|
|
55
65
|
return rows.map(rowToRealtimeEvent);
|
|
56
66
|
}
|
|
57
|
-
export function listRecentRealtimeEvents(db, limit = 25) {
|
|
58
|
-
const rows = db.prepare(realtimeEventSelectSql("ORDER BY sequence DESC LIMIT ?"))
|
|
67
|
+
export async function listRecentRealtimeEvents(db, limit = 25) {
|
|
68
|
+
const rows = await db.prepare(realtimeEventSelectSql("ORDER BY sequence DESC LIMIT ?"))
|
|
59
69
|
.all(Math.max(1, Math.min(limit, 100)));
|
|
60
70
|
return rows.map(rowToRealtimeEvent);
|
|
61
71
|
}
|
|
62
|
-
export function pruneRealtimeEvents(db, schemaId, keepLatest = 1000) {
|
|
72
|
+
export async function pruneRealtimeEvents(db, schemaId, keepLatest = 1000) {
|
|
63
73
|
const keep = Math.max(1, keepLatest);
|
|
64
|
-
const result = db.prepare(`DELETE FROM realtime_events
|
|
74
|
+
const result = await db.prepare(`DELETE FROM realtime_events
|
|
65
75
|
WHERE schema_id = ?
|
|
66
76
|
AND sequence NOT IN (
|
|
67
77
|
SELECT sequence FROM realtime_events
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
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:
|
|
4
|
-
export declare function consumeRealtimeSession(db:
|
|
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,
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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,
|
|
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"}
|
package/dist/relation-helpers.js
CHANGED
|
@@ -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 {
|
|
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:
|
|
4
|
-
export declare function createAdminRole(db:
|
|
5
|
-
export declare function listRoles(db:
|
|
6
|
-
export declare function listAdminRoles(db:
|
|
7
|
-
export declare function getRoleById(db:
|
|
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,
|
|
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"}
|
package/dist/role-repository.js
CHANGED
|
@@ -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 {
|
|
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:
|
|
4
|
-
export declare function listSchemas(db:
|
|
5
|
-
export declare function getSchemaById(db:
|
|
6
|
-
export declare function getSchemaBySlug(db:
|
|
7
|
-
export declare function updateSchema(db:
|
|
8
|
-
export declare function deleteSchema(db:
|
|
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":"
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
db.prepare("UPDATE schemas SET name = ?, slug = ?, description = ?, updated_at = ? WHERE id = ?")
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
|
73
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
+
}
|