@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.
- 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 +17 -0
- package/dist/mysql-adapter.d.ts.map +1 -0
- package/dist/mysql-adapter.js +63 -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 +17 -0
- package/dist/postgres-adapter.d.ts.map +1 -0
- package/dist/postgres-adapter.js +61 -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
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import mysql from "mysql2/promise";
|
|
2
|
+
import { migrateProviderFoundation } from "./provider-migrations.js";
|
|
3
|
+
export class MySqlApiagexDatabase {
|
|
4
|
+
connection;
|
|
5
|
+
provider = "mysql";
|
|
6
|
+
constructor(connection) {
|
|
7
|
+
this.connection = connection;
|
|
8
|
+
}
|
|
9
|
+
async exec(sql) {
|
|
10
|
+
for (const statement of splitMySqlStatements(sql)) {
|
|
11
|
+
await this.connection.query(statement);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
prepare(sql) {
|
|
15
|
+
return {
|
|
16
|
+
get: async (...params) => {
|
|
17
|
+
const [rows] = await this.connection.query(sql, params);
|
|
18
|
+
return (Array.isArray(rows) ? rows[0] : undefined);
|
|
19
|
+
},
|
|
20
|
+
all: async (...params) => {
|
|
21
|
+
const [rows] = await this.connection.query(sql, params);
|
|
22
|
+
return (Array.isArray(rows) ? rows : []);
|
|
23
|
+
},
|
|
24
|
+
run: async (...params) => {
|
|
25
|
+
const [result] = await this.connection.query(sql, params);
|
|
26
|
+
return toRunResult(result);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async transaction(callback) {
|
|
31
|
+
await this.connection.beginTransaction();
|
|
32
|
+
try {
|
|
33
|
+
const result = await callback();
|
|
34
|
+
await this.connection.commit();
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
await this.connection.rollback();
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async close() {
|
|
43
|
+
await this.connection.end();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export async function openMySqlAdapter(connectionString, options = {}) {
|
|
47
|
+
if (!connectionString?.trim())
|
|
48
|
+
throw new Error("DATABASE_URL_REQUIRED: mysql");
|
|
49
|
+
const connection = await mysql.createConnection(connectionString);
|
|
50
|
+
const database = new MySqlApiagexDatabase(connection);
|
|
51
|
+
if (options.migrate ?? true)
|
|
52
|
+
await migrateProviderFoundation(database, "mysql");
|
|
53
|
+
return database;
|
|
54
|
+
}
|
|
55
|
+
export function splitMySqlStatements(sql) {
|
|
56
|
+
return sql
|
|
57
|
+
.split(";")
|
|
58
|
+
.map((statement) => statement.trim())
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
function toRunResult(result) {
|
|
62
|
+
return { changes: result.affectedRows };
|
|
63
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApiagexDatabase } from "./database-adapter.type.js";
|
|
2
2
|
import type { PermissionAction, PermissionRecord, SetPermissionInput } from "./permission-repository.type.js";
|
|
3
|
-
export declare function setPermission(db:
|
|
4
|
-
export declare function listRolePermissions(db:
|
|
5
|
-
export declare function canRoleAccess(db:
|
|
3
|
+
export declare function setPermission(db: ApiagexDatabase, input: SetPermissionInput): Promise<PermissionRecord>;
|
|
4
|
+
export declare function listRolePermissions(db: ApiagexDatabase, roleId: string): Promise<PermissionRecord[]>;
|
|
5
|
+
export declare function canRoleAccess(db: ApiagexDatabase, roleId: string, schemaId: string, action: PermissionAction): Promise<boolean>;
|
|
6
6
|
//# sourceMappingURL=permission-repository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-repository.d.ts","sourceRoot":"","sources":["../src/permission-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"permission-repository.d.ts","sourceRoot":"","sources":["../src/permission-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,iCAAiC,CAAC;AAQzC,wBAAsB,aAAa,CACjC,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,gBAAgB,CAAC,CAY3B;AAED,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAK1G;AAED,wBAAsB,aAAa,CACjC,EAAE,EAAE,eAAe,EACnB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,OAAO,CAAC,CAOlB"}
|
|
@@ -1,69 +1,55 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getRoleById } from "./role-repository.js";
|
|
3
3
|
import { getSchemaById } from "./schema-repository.js";
|
|
4
|
-
const permissionActions = [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"update",
|
|
9
|
-
"delete",
|
|
10
|
-
"manage",
|
|
11
|
-
];
|
|
12
|
-
export function setPermission(db, input) {
|
|
13
|
-
validatePermissionInput(db, input);
|
|
14
|
-
const existing = findPermission(db, input.roleId, input.schemaId, input.action);
|
|
4
|
+
const permissionActions = ["getAll", "get", "create", "update", "delete", "manage"];
|
|
5
|
+
export async function setPermission(db, input) {
|
|
6
|
+
await validatePermissionInput(db, input);
|
|
7
|
+
const existing = await findPermission(db, input.roleId, input.schemaId, input.action);
|
|
15
8
|
if (existing) {
|
|
16
|
-
db.prepare("UPDATE permissions SET allowed = ? WHERE id = ?").run(input.allowed ? 1 : 0, existing.id);
|
|
9
|
+
await db.prepare("UPDATE permissions SET allowed = ? WHERE id = ?").run(input.allowed ? 1 : 0, existing.id);
|
|
17
10
|
return requirePermission(db, existing.id);
|
|
18
11
|
}
|
|
19
12
|
const id = randomUUID();
|
|
20
|
-
db.prepare("INSERT INTO permissions (id, role_id, schema_id, action, allowed) VALUES (?, ?, ?, ?, ?)").run(id, input.roleId, input.schemaId, input.action, input.allowed ? 1 : 0);
|
|
13
|
+
await db.prepare("INSERT INTO permissions (id, role_id, schema_id, action, allowed) VALUES (?, ?, ?, ?, ?)").run(id, input.roleId, input.schemaId, input.action, input.allowed ? 1 : 0);
|
|
21
14
|
return requirePermission(db, id);
|
|
22
15
|
}
|
|
23
|
-
export function listRolePermissions(db, roleId) {
|
|
24
|
-
const rows = db
|
|
16
|
+
export async function listRolePermissions(db, roleId) {
|
|
17
|
+
const rows = await db
|
|
25
18
|
.prepare(permissionSelectSql("WHERE role_id = ? ORDER BY schema_id ASC, action ASC"))
|
|
26
19
|
.all(roleId);
|
|
27
20
|
return rows.map(rowToPermission);
|
|
28
21
|
}
|
|
29
|
-
export function canRoleAccess(db, roleId, schemaId, action) {
|
|
30
|
-
const role = getRoleById(db, roleId);
|
|
31
|
-
if (!role)
|
|
32
|
-
return false;
|
|
33
|
-
if (role.roleKind !== "api")
|
|
22
|
+
export async function canRoleAccess(db, roleId, schemaId, action) {
|
|
23
|
+
const role = await getRoleById(db, roleId);
|
|
24
|
+
if (!role || role.roleKind !== "api")
|
|
34
25
|
return false;
|
|
35
|
-
const manage = findPermission(db, roleId, schemaId, "manage");
|
|
26
|
+
const manage = await findPermission(db, roleId, schemaId, "manage");
|
|
36
27
|
if (manage?.allowed)
|
|
37
28
|
return true;
|
|
38
|
-
const permission = findPermission(db, roleId, schemaId, action);
|
|
29
|
+
const permission = await findPermission(db, roleId, schemaId, action);
|
|
39
30
|
return Boolean(permission?.allowed);
|
|
40
31
|
}
|
|
41
|
-
function validatePermissionInput(db, input) {
|
|
42
|
-
if (!permissionActions.includes(input.action))
|
|
32
|
+
async function validatePermissionInput(db, input) {
|
|
33
|
+
if (!permissionActions.includes(input.action))
|
|
43
34
|
throw new Error("PERMISSION_ACTION_INVALID");
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (!role) {
|
|
35
|
+
const role = await getRoleById(db, input.roleId);
|
|
36
|
+
if (!role)
|
|
47
37
|
throw new Error("ROLE_NOT_FOUND");
|
|
48
|
-
|
|
49
|
-
if (role.roleKind !== "api") {
|
|
38
|
+
if (role.roleKind !== "api")
|
|
50
39
|
throw new Error("ROLE_API_REQUIRED");
|
|
51
|
-
|
|
52
|
-
if (!getSchemaById(db, input.schemaId)) {
|
|
40
|
+
if (!(await getSchemaById(db, input.schemaId)))
|
|
53
41
|
throw new Error("SCHEMA_NOT_FOUND");
|
|
54
|
-
}
|
|
55
42
|
}
|
|
56
|
-
function findPermission(db, roleId, schemaId, action) {
|
|
57
|
-
const row = db
|
|
43
|
+
async function findPermission(db, roleId, schemaId, action) {
|
|
44
|
+
const row = await db
|
|
58
45
|
.prepare(permissionSelectSql("WHERE role_id = ? AND schema_id = ? AND action = ?"))
|
|
59
46
|
.get(roleId, schemaId, action);
|
|
60
47
|
return row ? rowToPermission(row) : undefined;
|
|
61
48
|
}
|
|
62
|
-
function requirePermission(db, id) {
|
|
63
|
-
const row = db.prepare(permissionSelectSql("WHERE id = ?")).get(id);
|
|
64
|
-
if (!row)
|
|
49
|
+
async function requirePermission(db, id) {
|
|
50
|
+
const row = await db.prepare(permissionSelectSql("WHERE id = ?")).get(id);
|
|
51
|
+
if (!row)
|
|
65
52
|
throw new Error("PERMISSION_NOT_FOUND");
|
|
66
|
-
}
|
|
67
53
|
return rowToPermission(row);
|
|
68
54
|
}
|
|
69
55
|
function rowToPermission(row) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Client } from "pg";
|
|
2
|
+
import type { ApiagexDatabase, DatabaseStatement } from "./database-adapter.type.js";
|
|
3
|
+
export type PostgresAdapterOptions = {
|
|
4
|
+
migrate?: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare class PostgresApiagexDatabase implements ApiagexDatabase {
|
|
7
|
+
private readonly client;
|
|
8
|
+
readonly provider = "postgres";
|
|
9
|
+
constructor(client: Client);
|
|
10
|
+
exec(sql: string): Promise<void>;
|
|
11
|
+
prepare(sql: string): DatabaseStatement;
|
|
12
|
+
transaction<TResult>(callback: () => Promise<TResult>): Promise<TResult>;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export declare function openPostgresAdapter(connectionString: string | undefined, options?: PostgresAdapterOptions): Promise<ApiagexDatabase>;
|
|
16
|
+
export declare function convertPostgresPlaceholders(sql: string): string;
|
|
17
|
+
//# sourceMappingURL=postgres-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-adapter.d.ts","sourceRoot":"","sources":["../src/postgres-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC5B,OAAO,KAAK,EAAE,eAAe,EAAyC,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAG5H,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,qBAAa,uBAAwB,YAAW,eAAe;IAGjD,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,QAAQ,CAAC,QAAQ,cAAc;gBAEF,MAAM,EAAE,MAAM;IAErC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB;IAkBjC,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,wBAAsB,mBAAmB,CACvC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,CAO1B;AAED,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG/D"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Client } from "pg";
|
|
2
|
+
import { migrateProviderFoundation } from "./provider-migrations.js";
|
|
3
|
+
export class PostgresApiagexDatabase {
|
|
4
|
+
client;
|
|
5
|
+
provider = "postgres";
|
|
6
|
+
constructor(client) {
|
|
7
|
+
this.client = client;
|
|
8
|
+
}
|
|
9
|
+
async exec(sql) {
|
|
10
|
+
await this.client.query(sql);
|
|
11
|
+
}
|
|
12
|
+
prepare(sql) {
|
|
13
|
+
const queryText = convertPostgresPlaceholders(sql);
|
|
14
|
+
return {
|
|
15
|
+
get: async (...params) => {
|
|
16
|
+
const result = await this.client.query(queryText, params);
|
|
17
|
+
return result.rows[0];
|
|
18
|
+
},
|
|
19
|
+
all: async (...params) => {
|
|
20
|
+
const result = await this.client.query(queryText, params);
|
|
21
|
+
return result.rows;
|
|
22
|
+
},
|
|
23
|
+
run: async (...params) => {
|
|
24
|
+
const result = await this.client.query(queryText, params);
|
|
25
|
+
return toRunResult(result.rowCount);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async transaction(callback) {
|
|
30
|
+
await this.client.query("BEGIN");
|
|
31
|
+
try {
|
|
32
|
+
const result = await callback();
|
|
33
|
+
await this.client.query("COMMIT");
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
await this.client.query("ROLLBACK");
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async close() {
|
|
42
|
+
await this.client.end();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export async function openPostgresAdapter(connectionString, options = {}) {
|
|
46
|
+
if (!connectionString?.trim())
|
|
47
|
+
throw new Error("DATABASE_URL_REQUIRED: postgres");
|
|
48
|
+
const client = new Client({ connectionString });
|
|
49
|
+
await client.connect();
|
|
50
|
+
const database = new PostgresApiagexDatabase(client);
|
|
51
|
+
if (options.migrate ?? true)
|
|
52
|
+
await migrateProviderFoundation(database, "postgres");
|
|
53
|
+
return database;
|
|
54
|
+
}
|
|
55
|
+
export function convertPostgresPlaceholders(sql) {
|
|
56
|
+
let index = 0;
|
|
57
|
+
return sql.replaceAll("?", () => `$${++index}`);
|
|
58
|
+
}
|
|
59
|
+
function toRunResult(rowCount) {
|
|
60
|
+
return { changes: rowCount ?? 0 };
|
|
61
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DatabaseProvider } from "./database-adapter.type.js";
|
|
2
|
+
import type { ApiagexDatabase } from "./database-adapter.type.js";
|
|
3
|
+
import type { MvpTableName } from "./schema.type.js";
|
|
4
|
+
export type ProviderFoundationMigration = {
|
|
5
|
+
provider: DatabaseProvider;
|
|
6
|
+
foundationSql: string;
|
|
7
|
+
tables: readonly MvpTableName[];
|
|
8
|
+
};
|
|
9
|
+
export declare function getProviderFoundationMigration(provider: DatabaseProvider): ProviderFoundationMigration;
|
|
10
|
+
export declare function providerFoundationSql(provider: DatabaseProvider): string;
|
|
11
|
+
export declare function migrateProviderFoundation(db: ApiagexDatabase, provider: DatabaseProvider): Promise<void>;
|
|
12
|
+
export declare const POSTGRES_FOUNDATION_SQL = "\nCREATE TABLE IF NOT EXISTS migrations (\n id TEXT PRIMARY KEY,\n applied_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS roles (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n description TEXT NOT NULL DEFAULT '',\n is_owner BOOLEAN NOT NULL DEFAULT FALSE,\n role_kind TEXT NOT NULL DEFAULT 'api',\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS users (\n id TEXT PRIMARY KEY,\n email TEXT NOT NULL UNIQUE,\n password_hash TEXT NOT NULL,\n role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS schemas (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n slug TEXT NOT NULL UNIQUE,\n description TEXT NOT NULL DEFAULT '',\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS fields (\n id TEXT PRIMARY KEY,\n schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n slug TEXT NOT NULL,\n type TEXT NOT NULL,\n relation_schema_id TEXT REFERENCES schemas(id),\n relation_type TEXT,\n required BOOLEAN NOT NULL DEFAULT FALSE,\n position INTEGER NOT NULL,\n UNIQUE(schema_id, slug)\n);\n\nCREATE TABLE IF NOT EXISTS entries (\n id TEXT PRIMARY KEY,\n schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,\n data_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS permissions (\n id TEXT PRIMARY KEY,\n role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,\n schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,\n action TEXT NOT NULL,\n allowed BOOLEAN NOT NULL DEFAULT FALSE,\n UNIQUE(role_id, schema_id, action)\n);\n\nCREATE TABLE IF NOT EXISTS admin_permissions (\n id TEXT PRIMARY KEY,\n role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,\n action TEXT NOT NULL,\n allowed BOOLEAN NOT NULL DEFAULT FALSE,\n UNIQUE(role_id, action)\n);\n\nCREATE TABLE IF NOT EXISTS api_tokens (\n id TEXT PRIMARY KEY,\n role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n token_hash TEXT NOT NULL UNIQUE,\n token_prefix TEXT NOT NULL,\n created_at TEXT NOT NULL,\n last_used_at TEXT,\n revoked_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS webhooks (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n url TEXT NOT NULL,\n secret TEXT NOT NULL,\n events_json TEXT NOT NULL,\n schema_id TEXT REFERENCES schemas(id) ON DELETE SET NULL,\n active BOOLEAN NOT NULL DEFAULT TRUE,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS webhook_events (\n id TEXT PRIMARY KEY,\n event_type TEXT NOT NULL,\n schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,\n schema_slug TEXT NOT NULL,\n entry_id TEXT NOT NULL,\n payload_json TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n attempts INTEGER NOT NULL DEFAULT 0,\n next_retry_at TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS webhook_deliveries (\n id TEXT PRIMARY KEY,\n event_id TEXT NOT NULL REFERENCES webhook_events(id) ON DELETE CASCADE,\n webhook_id TEXT NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,\n url TEXT NOT NULL,\n status TEXT NOT NULL,\n status_code INTEGER,\n response_body TEXT,\n error TEXT,\n attempt INTEGER NOT NULL,\n created_at TEXT NOT NULL,\n next_retry_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS realtime_configs (\n schema_id TEXT PRIMARY KEY REFERENCES schemas(id) ON DELETE CASCADE,\n enabled BOOLEAN NOT NULL DEFAULT FALSE,\n events_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS realtime_events (\n sequence BIGSERIAL PRIMARY KEY,\n id TEXT NOT NULL UNIQUE,\n message_id TEXT NOT NULL UNIQUE,\n event_type TEXT NOT NULL,\n schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,\n schema_slug TEXT NOT NULL,\n entry_id TEXT NOT NULL,\n entry_json TEXT NOT NULL,\n occurred_at TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS realtime_sessions (\n id TEXT PRIMARY KEY,\n token_hash TEXT NOT NULL UNIQUE,\n token_prefix TEXT NOT NULL,\n role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,\n schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,\n schema_slug TEXT NOT NULL,\n created_at TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n used_at TEXT\n);\n";
|
|
13
|
+
export declare const MYSQL_FOUNDATION_SQL = "\nCREATE TABLE IF NOT EXISTS migrations (id VARCHAR(191) PRIMARY KEY, applied_at TEXT NOT NULL) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS roles (id VARCHAR(191) PRIMARY KEY, name VARCHAR(191) NOT NULL UNIQUE, description LONGTEXT NOT NULL, is_owner TINYINT(1) NOT NULL DEFAULT 0, role_kind VARCHAR(32) NOT NULL DEFAULT 'api', created_at TEXT NOT NULL, updated_at TEXT NOT NULL) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS users (id VARCHAR(191) PRIMARY KEY, email VARCHAR(191) NOT NULL UNIQUE, password_hash VARCHAR(191) NOT NULL, role_id VARCHAR(191) NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, CONSTRAINT fk_users_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS schemas (id VARCHAR(191) PRIMARY KEY, name VARCHAR(191) NOT NULL, slug VARCHAR(191) NOT NULL UNIQUE, description LONGTEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS fields (id VARCHAR(191) PRIMARY KEY, schema_id VARCHAR(191) NOT NULL, name VARCHAR(191) NOT NULL, slug VARCHAR(191) NOT NULL, type VARCHAR(64) NOT NULL, relation_schema_id VARCHAR(191), relation_type VARCHAR(64), required TINYINT(1) NOT NULL DEFAULT 0, position INTEGER NOT NULL, UNIQUE(schema_id, slug), CONSTRAINT fk_fields_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE, CONSTRAINT fk_fields_relation_schema FOREIGN KEY (relation_schema_id) REFERENCES schemas(id)) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS entries (id VARCHAR(191) PRIMARY KEY, schema_id VARCHAR(191) NOT NULL, data_json LONGTEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, CONSTRAINT fk_entries_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS permissions (id VARCHAR(191) PRIMARY KEY, role_id VARCHAR(191) NOT NULL, schema_id VARCHAR(191) NOT NULL, action VARCHAR(64) NOT NULL, allowed TINYINT(1) NOT NULL DEFAULT 0, UNIQUE(role_id, schema_id, action), CONSTRAINT fk_permissions_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE, CONSTRAINT fk_permissions_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS admin_permissions (id VARCHAR(191) PRIMARY KEY, role_id VARCHAR(191) NOT NULL, action VARCHAR(64) NOT NULL, allowed TINYINT(1) NOT NULL DEFAULT 0, UNIQUE(role_id, action), CONSTRAINT fk_admin_permissions_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS api_tokens (id VARCHAR(191) PRIMARY KEY, role_id VARCHAR(191) NOT NULL, name VARCHAR(191) NOT NULL, token_hash VARCHAR(191) NOT NULL UNIQUE, token_prefix VARCHAR(32) NOT NULL, created_at TEXT NOT NULL, last_used_at TEXT, revoked_at TEXT, CONSTRAINT fk_api_tokens_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS webhooks (id VARCHAR(191) PRIMARY KEY, name VARCHAR(191) NOT NULL, url TEXT NOT NULL, secret VARCHAR(191) NOT NULL, events_json LONGTEXT NOT NULL, schema_id VARCHAR(191), active TINYINT(1) NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, CONSTRAINT fk_webhooks_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE SET NULL) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS webhook_events (id VARCHAR(191) PRIMARY KEY, event_type VARCHAR(64) NOT NULL, schema_id VARCHAR(191) NOT NULL, schema_slug VARCHAR(191) NOT NULL, entry_id VARCHAR(191) NOT NULL, payload_json LONGTEXT NOT NULL, status VARCHAR(64) NOT NULL DEFAULT 'pending', attempts INTEGER NOT NULL DEFAULT 0, next_retry_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, CONSTRAINT fk_webhook_events_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS webhook_deliveries (id VARCHAR(191) PRIMARY KEY, event_id VARCHAR(191) NOT NULL, webhook_id VARCHAR(191) NOT NULL, url TEXT NOT NULL, status VARCHAR(64) NOT NULL, status_code INTEGER, response_body LONGTEXT, error LONGTEXT, attempt INTEGER NOT NULL, created_at TEXT NOT NULL, next_retry_at TEXT, CONSTRAINT fk_webhook_deliveries_event FOREIGN KEY (event_id) REFERENCES webhook_events(id) ON DELETE CASCADE, CONSTRAINT fk_webhook_deliveries_webhook FOREIGN KEY (webhook_id) REFERENCES webhooks(id) ON DELETE CASCADE) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS realtime_configs (schema_id VARCHAR(191) PRIMARY KEY, enabled TINYINT(1) NOT NULL DEFAULT 0, events_json LONGTEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, CONSTRAINT fk_realtime_configs_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS realtime_events (sequence BIGINT AUTO_INCREMENT PRIMARY KEY, id VARCHAR(191) NOT NULL UNIQUE, message_id VARCHAR(191) NOT NULL UNIQUE, event_type VARCHAR(64) NOT NULL, schema_id VARCHAR(191) NOT NULL, schema_slug VARCHAR(191) NOT NULL, entry_id VARCHAR(191) NOT NULL, entry_json LONGTEXT NOT NULL, occurred_at TEXT NOT NULL, created_at TEXT NOT NULL, CONSTRAINT fk_realtime_events_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE) ENGINE=InnoDB;\nCREATE TABLE IF NOT EXISTS realtime_sessions (id VARCHAR(191) PRIMARY KEY, token_hash VARCHAR(191) NOT NULL UNIQUE, token_prefix VARCHAR(32) NOT NULL, role_id VARCHAR(191) NOT NULL, schema_id VARCHAR(191) NOT NULL, schema_slug VARCHAR(191) NOT NULL, created_at TEXT NOT NULL, expires_at TEXT NOT NULL, used_at TEXT, CONSTRAINT fk_realtime_sessions_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE, CONSTRAINT fk_realtime_sessions_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE) ENGINE=InnoDB;\n";
|
|
14
|
+
//# sourceMappingURL=provider-migrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-migrations.d.ts","sourceRoot":"","sources":["../src/provider-migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAGlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;CACjC,CAAC;AAEF,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,gBAAgB,GAAG,2BAA2B,CAMtG;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAIxE;AAED,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,eAAe,EACnB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,eAAO,MAAM,uBAAuB,m5IA2JnC,CAAC;AAEF,eAAO,MAAM,oBAAoB,smLAgBhC,CAAC"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { MVP_FOUNDATION_SQL, MVP_TABLES } from "./migrations.js";
|
|
2
|
+
import { MVP_MIGRATION_ID } from "./migrations.js";
|
|
3
|
+
export function getProviderFoundationMigration(provider) {
|
|
4
|
+
return {
|
|
5
|
+
provider,
|
|
6
|
+
foundationSql: providerFoundationSql(provider),
|
|
7
|
+
tables: MVP_TABLES,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function providerFoundationSql(provider) {
|
|
11
|
+
if (provider === "sqlite")
|
|
12
|
+
return MVP_FOUNDATION_SQL;
|
|
13
|
+
if (provider === "postgres")
|
|
14
|
+
return POSTGRES_FOUNDATION_SQL;
|
|
15
|
+
return MYSQL_FOUNDATION_SQL;
|
|
16
|
+
}
|
|
17
|
+
export async function migrateProviderFoundation(db, provider) {
|
|
18
|
+
await db.exec(providerFoundationSql(provider));
|
|
19
|
+
const existing = await db.prepare("SELECT id, applied_at FROM migrations WHERE id = ?")
|
|
20
|
+
.get(MVP_MIGRATION_ID);
|
|
21
|
+
if (!existing) {
|
|
22
|
+
await db.prepare("INSERT INTO migrations (id, applied_at) VALUES (?, ?)")
|
|
23
|
+
.run(MVP_MIGRATION_ID, new Date().toISOString());
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export const POSTGRES_FOUNDATION_SQL = `
|
|
27
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
28
|
+
id TEXT PRIMARY KEY,
|
|
29
|
+
applied_at TEXT NOT NULL
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS roles (
|
|
33
|
+
id TEXT PRIMARY KEY,
|
|
34
|
+
name TEXT NOT NULL UNIQUE,
|
|
35
|
+
description TEXT NOT NULL DEFAULT '',
|
|
36
|
+
is_owner BOOLEAN NOT NULL DEFAULT FALSE,
|
|
37
|
+
role_kind TEXT NOT NULL DEFAULT 'api',
|
|
38
|
+
created_at TEXT NOT NULL,
|
|
39
|
+
updated_at TEXT NOT NULL
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
43
|
+
id TEXT PRIMARY KEY,
|
|
44
|
+
email TEXT NOT NULL UNIQUE,
|
|
45
|
+
password_hash TEXT NOT NULL,
|
|
46
|
+
role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
47
|
+
created_at TEXT NOT NULL,
|
|
48
|
+
updated_at TEXT NOT NULL
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE TABLE IF NOT EXISTS schemas (
|
|
52
|
+
id TEXT PRIMARY KEY,
|
|
53
|
+
name TEXT NOT NULL,
|
|
54
|
+
slug TEXT NOT NULL UNIQUE,
|
|
55
|
+
description TEXT NOT NULL DEFAULT '',
|
|
56
|
+
created_at TEXT NOT NULL,
|
|
57
|
+
updated_at TEXT NOT NULL
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
CREATE TABLE IF NOT EXISTS fields (
|
|
61
|
+
id TEXT PRIMARY KEY,
|
|
62
|
+
schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,
|
|
63
|
+
name TEXT NOT NULL,
|
|
64
|
+
slug TEXT NOT NULL,
|
|
65
|
+
type TEXT NOT NULL,
|
|
66
|
+
relation_schema_id TEXT REFERENCES schemas(id),
|
|
67
|
+
relation_type TEXT,
|
|
68
|
+
required BOOLEAN NOT NULL DEFAULT FALSE,
|
|
69
|
+
position INTEGER NOT NULL,
|
|
70
|
+
UNIQUE(schema_id, slug)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
CREATE TABLE IF NOT EXISTS entries (
|
|
74
|
+
id TEXT PRIMARY KEY,
|
|
75
|
+
schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,
|
|
76
|
+
data_json TEXT NOT NULL,
|
|
77
|
+
created_at TEXT NOT NULL,
|
|
78
|
+
updated_at TEXT NOT NULL
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
CREATE TABLE IF NOT EXISTS permissions (
|
|
82
|
+
id TEXT PRIMARY KEY,
|
|
83
|
+
role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
84
|
+
schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,
|
|
85
|
+
action TEXT NOT NULL,
|
|
86
|
+
allowed BOOLEAN NOT NULL DEFAULT FALSE,
|
|
87
|
+
UNIQUE(role_id, schema_id, action)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
CREATE TABLE IF NOT EXISTS admin_permissions (
|
|
91
|
+
id TEXT PRIMARY KEY,
|
|
92
|
+
role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
93
|
+
action TEXT NOT NULL,
|
|
94
|
+
allowed BOOLEAN NOT NULL DEFAULT FALSE,
|
|
95
|
+
UNIQUE(role_id, action)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
CREATE TABLE IF NOT EXISTS api_tokens (
|
|
99
|
+
id TEXT PRIMARY KEY,
|
|
100
|
+
role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
101
|
+
name TEXT NOT NULL,
|
|
102
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
103
|
+
token_prefix TEXT NOT NULL,
|
|
104
|
+
created_at TEXT NOT NULL,
|
|
105
|
+
last_used_at TEXT,
|
|
106
|
+
revoked_at TEXT
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
CREATE TABLE IF NOT EXISTS webhooks (
|
|
110
|
+
id TEXT PRIMARY KEY,
|
|
111
|
+
name TEXT NOT NULL,
|
|
112
|
+
url TEXT NOT NULL,
|
|
113
|
+
secret TEXT NOT NULL,
|
|
114
|
+
events_json TEXT NOT NULL,
|
|
115
|
+
schema_id TEXT REFERENCES schemas(id) ON DELETE SET NULL,
|
|
116
|
+
active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
117
|
+
created_at TEXT NOT NULL,
|
|
118
|
+
updated_at TEXT NOT NULL
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
CREATE TABLE IF NOT EXISTS webhook_events (
|
|
122
|
+
id TEXT PRIMARY KEY,
|
|
123
|
+
event_type TEXT NOT NULL,
|
|
124
|
+
schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,
|
|
125
|
+
schema_slug TEXT NOT NULL,
|
|
126
|
+
entry_id TEXT NOT NULL,
|
|
127
|
+
payload_json TEXT NOT NULL,
|
|
128
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
129
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
130
|
+
next_retry_at TEXT,
|
|
131
|
+
created_at TEXT NOT NULL,
|
|
132
|
+
updated_at TEXT NOT NULL
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
CREATE TABLE IF NOT EXISTS webhook_deliveries (
|
|
136
|
+
id TEXT PRIMARY KEY,
|
|
137
|
+
event_id TEXT NOT NULL REFERENCES webhook_events(id) ON DELETE CASCADE,
|
|
138
|
+
webhook_id TEXT NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
|
|
139
|
+
url TEXT NOT NULL,
|
|
140
|
+
status TEXT NOT NULL,
|
|
141
|
+
status_code INTEGER,
|
|
142
|
+
response_body TEXT,
|
|
143
|
+
error TEXT,
|
|
144
|
+
attempt INTEGER NOT NULL,
|
|
145
|
+
created_at TEXT NOT NULL,
|
|
146
|
+
next_retry_at TEXT
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
CREATE TABLE IF NOT EXISTS realtime_configs (
|
|
150
|
+
schema_id TEXT PRIMARY KEY REFERENCES schemas(id) ON DELETE CASCADE,
|
|
151
|
+
enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
|
152
|
+
events_json TEXT NOT NULL,
|
|
153
|
+
created_at TEXT NOT NULL,
|
|
154
|
+
updated_at TEXT NOT NULL
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
CREATE TABLE IF NOT EXISTS realtime_events (
|
|
158
|
+
sequence BIGSERIAL PRIMARY KEY,
|
|
159
|
+
id TEXT NOT NULL UNIQUE,
|
|
160
|
+
message_id TEXT NOT NULL UNIQUE,
|
|
161
|
+
event_type TEXT NOT NULL,
|
|
162
|
+
schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,
|
|
163
|
+
schema_slug TEXT NOT NULL,
|
|
164
|
+
entry_id TEXT NOT NULL,
|
|
165
|
+
entry_json TEXT NOT NULL,
|
|
166
|
+
occurred_at TEXT NOT NULL,
|
|
167
|
+
created_at TEXT NOT NULL
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
CREATE TABLE IF NOT EXISTS realtime_sessions (
|
|
171
|
+
id TEXT PRIMARY KEY,
|
|
172
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
173
|
+
token_prefix TEXT NOT NULL,
|
|
174
|
+
role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
175
|
+
schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,
|
|
176
|
+
schema_slug TEXT NOT NULL,
|
|
177
|
+
created_at TEXT NOT NULL,
|
|
178
|
+
expires_at TEXT NOT NULL,
|
|
179
|
+
used_at TEXT
|
|
180
|
+
);
|
|
181
|
+
`;
|
|
182
|
+
export const MYSQL_FOUNDATION_SQL = `
|
|
183
|
+
CREATE TABLE IF NOT EXISTS migrations (id VARCHAR(191) PRIMARY KEY, applied_at TEXT NOT NULL) ENGINE=InnoDB;
|
|
184
|
+
CREATE TABLE IF NOT EXISTS roles (id VARCHAR(191) PRIMARY KEY, name VARCHAR(191) NOT NULL UNIQUE, description LONGTEXT NOT NULL, is_owner TINYINT(1) NOT NULL DEFAULT 0, role_kind VARCHAR(32) NOT NULL DEFAULT 'api', created_at TEXT NOT NULL, updated_at TEXT NOT NULL) ENGINE=InnoDB;
|
|
185
|
+
CREATE TABLE IF NOT EXISTS users (id VARCHAR(191) PRIMARY KEY, email VARCHAR(191) NOT NULL UNIQUE, password_hash VARCHAR(191) NOT NULL, role_id VARCHAR(191) NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, CONSTRAINT fk_users_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE) ENGINE=InnoDB;
|
|
186
|
+
CREATE TABLE IF NOT EXISTS schemas (id VARCHAR(191) PRIMARY KEY, name VARCHAR(191) NOT NULL, slug VARCHAR(191) NOT NULL UNIQUE, description LONGTEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL) ENGINE=InnoDB;
|
|
187
|
+
CREATE TABLE IF NOT EXISTS fields (id VARCHAR(191) PRIMARY KEY, schema_id VARCHAR(191) NOT NULL, name VARCHAR(191) NOT NULL, slug VARCHAR(191) NOT NULL, type VARCHAR(64) NOT NULL, relation_schema_id VARCHAR(191), relation_type VARCHAR(64), required TINYINT(1) NOT NULL DEFAULT 0, position INTEGER NOT NULL, UNIQUE(schema_id, slug), CONSTRAINT fk_fields_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE, CONSTRAINT fk_fields_relation_schema FOREIGN KEY (relation_schema_id) REFERENCES schemas(id)) ENGINE=InnoDB;
|
|
188
|
+
CREATE TABLE IF NOT EXISTS entries (id VARCHAR(191) PRIMARY KEY, schema_id VARCHAR(191) NOT NULL, data_json LONGTEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, CONSTRAINT fk_entries_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE) ENGINE=InnoDB;
|
|
189
|
+
CREATE TABLE IF NOT EXISTS permissions (id VARCHAR(191) PRIMARY KEY, role_id VARCHAR(191) NOT NULL, schema_id VARCHAR(191) NOT NULL, action VARCHAR(64) NOT NULL, allowed TINYINT(1) NOT NULL DEFAULT 0, UNIQUE(role_id, schema_id, action), CONSTRAINT fk_permissions_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE, CONSTRAINT fk_permissions_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE) ENGINE=InnoDB;
|
|
190
|
+
CREATE TABLE IF NOT EXISTS admin_permissions (id VARCHAR(191) PRIMARY KEY, role_id VARCHAR(191) NOT NULL, action VARCHAR(64) NOT NULL, allowed TINYINT(1) NOT NULL DEFAULT 0, UNIQUE(role_id, action), CONSTRAINT fk_admin_permissions_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE) ENGINE=InnoDB;
|
|
191
|
+
CREATE TABLE IF NOT EXISTS api_tokens (id VARCHAR(191) PRIMARY KEY, role_id VARCHAR(191) NOT NULL, name VARCHAR(191) NOT NULL, token_hash VARCHAR(191) NOT NULL UNIQUE, token_prefix VARCHAR(32) NOT NULL, created_at TEXT NOT NULL, last_used_at TEXT, revoked_at TEXT, CONSTRAINT fk_api_tokens_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE) ENGINE=InnoDB;
|
|
192
|
+
CREATE TABLE IF NOT EXISTS webhooks (id VARCHAR(191) PRIMARY KEY, name VARCHAR(191) NOT NULL, url TEXT NOT NULL, secret VARCHAR(191) NOT NULL, events_json LONGTEXT NOT NULL, schema_id VARCHAR(191), active TINYINT(1) NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, CONSTRAINT fk_webhooks_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE SET NULL) ENGINE=InnoDB;
|
|
193
|
+
CREATE TABLE IF NOT EXISTS webhook_events (id VARCHAR(191) PRIMARY KEY, event_type VARCHAR(64) NOT NULL, schema_id VARCHAR(191) NOT NULL, schema_slug VARCHAR(191) NOT NULL, entry_id VARCHAR(191) NOT NULL, payload_json LONGTEXT NOT NULL, status VARCHAR(64) NOT NULL DEFAULT 'pending', attempts INTEGER NOT NULL DEFAULT 0, next_retry_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, CONSTRAINT fk_webhook_events_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE) ENGINE=InnoDB;
|
|
194
|
+
CREATE TABLE IF NOT EXISTS webhook_deliveries (id VARCHAR(191) PRIMARY KEY, event_id VARCHAR(191) NOT NULL, webhook_id VARCHAR(191) NOT NULL, url TEXT NOT NULL, status VARCHAR(64) NOT NULL, status_code INTEGER, response_body LONGTEXT, error LONGTEXT, attempt INTEGER NOT NULL, created_at TEXT NOT NULL, next_retry_at TEXT, CONSTRAINT fk_webhook_deliveries_event FOREIGN KEY (event_id) REFERENCES webhook_events(id) ON DELETE CASCADE, CONSTRAINT fk_webhook_deliveries_webhook FOREIGN KEY (webhook_id) REFERENCES webhooks(id) ON DELETE CASCADE) ENGINE=InnoDB;
|
|
195
|
+
CREATE TABLE IF NOT EXISTS realtime_configs (schema_id VARCHAR(191) PRIMARY KEY, enabled TINYINT(1) NOT NULL DEFAULT 0, events_json LONGTEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, CONSTRAINT fk_realtime_configs_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE) ENGINE=InnoDB;
|
|
196
|
+
CREATE TABLE IF NOT EXISTS realtime_events (sequence BIGINT AUTO_INCREMENT PRIMARY KEY, id VARCHAR(191) NOT NULL UNIQUE, message_id VARCHAR(191) NOT NULL UNIQUE, event_type VARCHAR(64) NOT NULL, schema_id VARCHAR(191) NOT NULL, schema_slug VARCHAR(191) NOT NULL, entry_id VARCHAR(191) NOT NULL, entry_json LONGTEXT NOT NULL, occurred_at TEXT NOT NULL, created_at TEXT NOT NULL, CONSTRAINT fk_realtime_events_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE) ENGINE=InnoDB;
|
|
197
|
+
CREATE TABLE IF NOT EXISTS realtime_sessions (id VARCHAR(191) PRIMARY KEY, token_hash VARCHAR(191) NOT NULL UNIQUE, token_prefix VARCHAR(32) NOT NULL, role_id VARCHAR(191) NOT NULL, schema_id VARCHAR(191) NOT NULL, schema_slug VARCHAR(191) NOT NULL, created_at TEXT NOT NULL, expires_at TEXT NOT NULL, used_at TEXT, CONSTRAINT fk_realtime_sessions_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE, CONSTRAINT fk_realtime_sessions_schema FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE) ENGINE=InnoDB;
|
|
198
|
+
`;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
export declare function listRealtimeConfigs(db:
|
|
4
|
-
export declare function listRealtimeSettings(db:
|
|
5
|
-
export declare function getRealtimeConfig(db:
|
|
6
|
-
export declare function setRealtimeConfig(db:
|
|
7
|
-
export declare function isRealtimeEventEnabled(db:
|
|
8
|
-
export declare function recordRealtimeEvent(db:
|
|
9
|
-
export declare function listRealtimeEventsAfter(db:
|
|
10
|
-
export declare function listRecentRealtimeEvents(db:
|
|
11
|
-
export declare function pruneRealtimeEvents(db:
|
|
1
|
+
import type { ApiagexDatabase } from "./database-adapter.type.js";
|
|
2
|
+
import type { RealtimeConfigRecord, RealtimeEventRecord, RealtimeEventType, RecordRealtimeEventInput, SetRealtimeConfigInput } from "./realtime-repository.type.js";
|
|
3
|
+
export declare function listRealtimeConfigs(db: ApiagexDatabase): Promise<RealtimeConfigRecord[]>;
|
|
4
|
+
export declare function listRealtimeSettings(db: ApiagexDatabase): Promise<RealtimeConfigRecord[]>;
|
|
5
|
+
export declare function getRealtimeConfig(db: ApiagexDatabase, schemaId: string): Promise<RealtimeConfigRecord | undefined>;
|
|
6
|
+
export declare function setRealtimeConfig(db: ApiagexDatabase, input: SetRealtimeConfigInput): Promise<RealtimeConfigRecord>;
|
|
7
|
+
export declare function isRealtimeEventEnabled(db: ApiagexDatabase, schemaId: string, event: RealtimeEventType): Promise<boolean>;
|
|
8
|
+
export declare function recordRealtimeEvent(db: ApiagexDatabase, input: RecordRealtimeEventInput): Promise<RealtimeEventRecord>;
|
|
9
|
+
export declare function listRealtimeEventsAfter(db: ApiagexDatabase, schemaId: string, lastEventId: string, limit?: number): Promise<RealtimeEventRecord[]>;
|
|
10
|
+
export declare function listRecentRealtimeEvents(db: ApiagexDatabase, limit?: number): Promise<RealtimeEventRecord[]>;
|
|
11
|
+
export declare function pruneRealtimeEvents(db: ApiagexDatabase, schemaId: string, keepLatest?: number): Promise<number>;
|
|
12
12
|
//# sourceMappingURL=realtime-repository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-repository.d.ts","sourceRoot":"","sources":["../src/realtime-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"realtime-repository.d.ts","sourceRoot":"","sources":["../src/realtime-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACvB,MAAM,+BAA+B,CAAC;AAgBvC,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAG9F;AAED,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAY/F;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,eAAe,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAG3C;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,oBAAoB,CAAC,CAgB/B;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,eAAe,EACnB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,mBAAmB,CAAC,CAY9B;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,eAAe,EACnB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,SAAK,GACT,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAQhC;AAED,wBAAsB,wBAAwB,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAI9G;AAED,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,SAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAanH"}
|
|
@@ -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
|