@apiagex/database 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/admin-permission-repository.d.ts +7 -0
- package/dist/admin-permission-repository.d.ts.map +1 -0
- package/dist/admin-permission-repository.js +74 -0
- package/dist/admin-permission-repository.type.d.ts +10 -0
- package/dist/admin-permission-repository.type.d.ts.map +1 -0
- package/dist/admin-permission-repository.type.js +1 -0
- package/dist/api-token-repository.d.ts +7 -0
- package/dist/api-token-repository.d.ts.map +1 -0
- package/dist/api-token-repository.js +80 -0
- package/dist/api-token-repository.type.d.ts +19 -0
- package/dist/api-token-repository.type.d.ts.map +1 -0
- package/dist/api-token-repository.type.js +1 -0
- package/dist/entry-query.d.ts +4 -0
- package/dist/entry-query.d.ts.map +1 -0
- package/dist/entry-query.js +61 -0
- package/dist/entry-repository.d.ts +8 -0
- package/dist/entry-repository.d.ts.map +1 -0
- package/dist/entry-repository.js +181 -0
- package/dist/entry-repository.type.d.ts +32 -0
- package/dist/entry-repository.type.d.ts.map +1 -0
- package/dist/entry-repository.type.js +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/migrations-additive.d.ts +2 -0
- package/dist/migrations-additive.d.ts.map +1 -0
- package/dist/migrations-additive.js +71 -0
- package/dist/migrations.d.ts +5 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +175 -0
- package/dist/permission-repository.d.ts +6 -0
- package/dist/permission-repository.d.ts.map +1 -0
- package/dist/permission-repository.js +80 -0
- package/dist/permission-repository.type.d.ts +11 -0
- package/dist/permission-repository.type.d.ts.map +1 -0
- package/dist/permission-repository.type.js +1 -0
- package/dist/realtime-repository.d.ts +12 -0
- package/dist/realtime-repository.d.ts.map +1 -0
- package/dist/realtime-repository.js +98 -0
- package/dist/realtime-repository.type.d.ts +33 -0
- package/dist/realtime-repository.type.d.ts.map +1 -0
- package/dist/realtime-repository.type.js +1 -0
- package/dist/realtime-session-repository.d.ts +5 -0
- package/dist/realtime-session-repository.d.ts.map +1 -0
- package/dist/realtime-session-repository.js +38 -0
- package/dist/realtime-session-repository.type.d.ts +21 -0
- package/dist/realtime-session-repository.type.d.ts.map +1 -0
- package/dist/realtime-session-repository.type.js +1 -0
- package/dist/relation-errors.d.ts +20 -0
- package/dist/relation-errors.d.ts.map +1 -0
- package/dist/relation-errors.js +30 -0
- package/dist/relation-helpers.d.ts +14 -0
- package/dist/relation-helpers.d.ts.map +1 -0
- package/dist/relation-helpers.js +21 -0
- package/dist/role-repository.d.ts +8 -0
- package/dist/role-repository.d.ts.map +1 -0
- package/dist/role-repository.js +66 -0
- package/dist/role-repository.type.d.ts +15 -0
- package/dist/role-repository.type.d.ts.map +1 -0
- package/dist/role-repository.type.js +1 -0
- package/dist/schema-repository.d.ts +9 -0
- package/dist/schema-repository.d.ts.map +1 -0
- package/dist/schema-repository.js +155 -0
- package/dist/schema-repository.type.d.ts +45 -0
- package/dist/schema-repository.type.d.ts.map +1 -0
- package/dist/schema-repository.type.js +1 -0
- package/dist/schema.type.d.ts +9 -0
- package/dist/schema.type.d.ts.map +1 -0
- package/dist/schema.type.js +1 -0
- package/dist/sqlite.d.ts +6 -0
- package/dist/sqlite.d.ts.map +1 -0
- package/dist/sqlite.js +40 -0
- package/dist/user-repository.d.ts +11 -0
- package/dist/user-repository.d.ts.map +1 -0
- package/dist/user-repository.js +56 -0
- package/dist/user-repository.type.d.ts +15 -0
- package/dist/user-repository.type.d.ts.map +1 -0
- package/dist/user-repository.type.js +1 -0
- package/dist/webhook-repository.d.ts +15 -0
- package/dist/webhook-repository.d.ts.map +1 -0
- package/dist/webhook-repository.js +136 -0
- package/dist/webhook-repository.type.d.ts +79 -0
- package/dist/webhook-repository.type.d.ts.map +1 -0
- package/dist/webhook-repository.type.js +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const MVP_ADDITIVE_MIGRATIONS_SQL: readonly ["ALTER TABLE fields ADD COLUMN relation_type TEXT", "ALTER TABLE roles ADD COLUMN role_kind TEXT NOT NULL DEFAULT 'api'", "UPDATE roles SET role_kind = 'admin' WHERE is_owner = 1 OR name IN ('owner', 'admin', 'schema-manager', 'user-manager')", "CREATE 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 INTEGER NOT NULL DEFAULT 0,\n UNIQUE(role_id, action)\n )", "INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_admin_schemas', id, 'schemas', 1 FROM roles WHERE name = 'admin' AND role_kind = 'admin'", "INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_admin_entries', id, 'entries', 1 FROM roles WHERE name = 'admin' AND role_kind = 'admin'", "INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_admin_apiRoles', id, 'apiRoles', 1 FROM roles WHERE name = 'admin' AND role_kind = 'admin'", "INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_admin_apiUsers', id, 'apiUsers', 1 FROM roles WHERE name = 'admin' AND role_kind = 'admin'", "INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_admin_settings', id, 'settings', 1 FROM roles WHERE name = 'admin' AND role_kind = 'admin'", "INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_schema-manager_schemas', id, 'schemas', 1 FROM roles WHERE name = 'schema-manager' AND role_kind = 'admin'", "INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_schema-manager_entries', id, 'entries', 1 FROM roles WHERE name = 'schema-manager' AND role_kind = 'admin'", "INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_user-manager_apiRoles', id, 'apiRoles', 1 FROM roles WHERE name = 'user-manager' AND role_kind = 'admin'", "INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_user-manager_apiUsers', id, 'apiUsers', 1 FROM roles WHERE name = 'user-manager' AND role_kind = 'admin'", "CREATE 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 )", "CREATE 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 INTEGER NOT NULL DEFAULT 1,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )", "CREATE TABLE IF NOT EXISTS webhook_events (\n id TEXT PRIMARY KEY,\n event_type TEXT NOT NULL,\n schema_id TEXT NOT NULL,\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 )", "CREATE 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 )", "CREATE TABLE IF NOT EXISTS realtime_configs (schema_id TEXT PRIMARY KEY REFERENCES schemas(id) ON DELETE CASCADE, enabled INTEGER NOT NULL DEFAULT 0, events_json TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)", "CREATE TABLE IF NOT EXISTS realtime_events (sequence INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE, message_id TEXT NOT NULL UNIQUE, event_type TEXT NOT NULL, schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE, schema_slug TEXT NOT NULL, entry_id TEXT NOT NULL, entry_json TEXT NOT NULL, occurred_at TEXT NOT NULL, created_at TEXT NOT NULL)", "CREATE TABLE IF NOT EXISTS realtime_sessions (id TEXT PRIMARY KEY, token_hash TEXT NOT NULL UNIQUE, token_prefix TEXT NOT NULL, role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE, schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE, schema_slug TEXT NOT NULL, created_at TEXT NOT NULL, expires_at TEXT NOT NULL, used_at TEXT)"];
|
|
2
|
+
//# sourceMappingURL=migrations-additive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations-additive.d.ts","sourceRoot":"","sources":["../src/migrations-additive.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,2BAA2B,knJAsE9B,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export const MVP_ADDITIVE_MIGRATIONS_SQL = [
|
|
2
|
+
"ALTER TABLE fields ADD COLUMN relation_type TEXT",
|
|
3
|
+
"ALTER TABLE roles ADD COLUMN role_kind TEXT NOT NULL DEFAULT 'api'",
|
|
4
|
+
"UPDATE roles SET role_kind = 'admin' WHERE is_owner = 1 OR name IN ('owner', 'admin', 'schema-manager', 'user-manager')",
|
|
5
|
+
`CREATE TABLE IF NOT EXISTS admin_permissions (
|
|
6
|
+
id TEXT PRIMARY KEY,
|
|
7
|
+
role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
8
|
+
action TEXT NOT NULL,
|
|
9
|
+
allowed INTEGER NOT NULL DEFAULT 0,
|
|
10
|
+
UNIQUE(role_id, action)
|
|
11
|
+
)`,
|
|
12
|
+
"INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_admin_schemas', id, 'schemas', 1 FROM roles WHERE name = 'admin' AND role_kind = 'admin'",
|
|
13
|
+
"INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_admin_entries', id, 'entries', 1 FROM roles WHERE name = 'admin' AND role_kind = 'admin'",
|
|
14
|
+
"INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_admin_apiRoles', id, 'apiRoles', 1 FROM roles WHERE name = 'admin' AND role_kind = 'admin'",
|
|
15
|
+
"INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_admin_apiUsers', id, 'apiUsers', 1 FROM roles WHERE name = 'admin' AND role_kind = 'admin'",
|
|
16
|
+
"INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_admin_settings', id, 'settings', 1 FROM roles WHERE name = 'admin' AND role_kind = 'admin'",
|
|
17
|
+
"INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_schema-manager_schemas', id, 'schemas', 1 FROM roles WHERE name = 'schema-manager' AND role_kind = 'admin'",
|
|
18
|
+
"INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_schema-manager_entries', id, 'entries', 1 FROM roles WHERE name = 'schema-manager' AND role_kind = 'admin'",
|
|
19
|
+
"INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_user-manager_apiRoles', id, 'apiRoles', 1 FROM roles WHERE name = 'user-manager' AND role_kind = 'admin'",
|
|
20
|
+
"INSERT OR IGNORE INTO admin_permissions (id, role_id, action, allowed) SELECT 'admin_permission_user-manager_apiUsers', id, 'apiUsers', 1 FROM roles WHERE name = 'user-manager' AND role_kind = 'admin'",
|
|
21
|
+
`CREATE TABLE IF NOT EXISTS api_tokens (
|
|
22
|
+
id TEXT PRIMARY KEY,
|
|
23
|
+
role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
24
|
+
name TEXT NOT NULL,
|
|
25
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
26
|
+
token_prefix TEXT NOT NULL,
|
|
27
|
+
created_at TEXT NOT NULL,
|
|
28
|
+
last_used_at TEXT,
|
|
29
|
+
revoked_at TEXT
|
|
30
|
+
)`,
|
|
31
|
+
`CREATE TABLE IF NOT EXISTS webhooks (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
name TEXT NOT NULL,
|
|
34
|
+
url TEXT NOT NULL,
|
|
35
|
+
secret TEXT NOT NULL,
|
|
36
|
+
events_json TEXT NOT NULL,
|
|
37
|
+
schema_id TEXT REFERENCES schemas(id) ON DELETE SET NULL,
|
|
38
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
39
|
+
created_at TEXT NOT NULL,
|
|
40
|
+
updated_at TEXT NOT NULL
|
|
41
|
+
)`,
|
|
42
|
+
`CREATE TABLE IF NOT EXISTS webhook_events (
|
|
43
|
+
id TEXT PRIMARY KEY,
|
|
44
|
+
event_type TEXT NOT NULL,
|
|
45
|
+
schema_id TEXT NOT NULL,
|
|
46
|
+
schema_slug TEXT NOT NULL,
|
|
47
|
+
entry_id TEXT NOT NULL,
|
|
48
|
+
payload_json TEXT NOT NULL,
|
|
49
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
50
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
51
|
+
next_retry_at TEXT,
|
|
52
|
+
created_at TEXT NOT NULL,
|
|
53
|
+
updated_at TEXT NOT NULL
|
|
54
|
+
)`,
|
|
55
|
+
`CREATE TABLE IF NOT EXISTS webhook_deliveries (
|
|
56
|
+
id TEXT PRIMARY KEY,
|
|
57
|
+
event_id TEXT NOT NULL REFERENCES webhook_events(id) ON DELETE CASCADE,
|
|
58
|
+
webhook_id TEXT NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
|
|
59
|
+
url TEXT NOT NULL,
|
|
60
|
+
status TEXT NOT NULL,
|
|
61
|
+
status_code INTEGER,
|
|
62
|
+
response_body TEXT,
|
|
63
|
+
error TEXT,
|
|
64
|
+
attempt INTEGER NOT NULL,
|
|
65
|
+
created_at TEXT NOT NULL,
|
|
66
|
+
next_retry_at TEXT
|
|
67
|
+
)`,
|
|
68
|
+
`CREATE TABLE IF NOT EXISTS realtime_configs (schema_id TEXT PRIMARY KEY REFERENCES schemas(id) ON DELETE CASCADE, enabled INTEGER NOT NULL DEFAULT 0, events_json TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)`,
|
|
69
|
+
`CREATE TABLE IF NOT EXISTS realtime_events (sequence INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE, message_id TEXT NOT NULL UNIQUE, event_type TEXT NOT NULL, schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE, schema_slug TEXT NOT NULL, entry_id TEXT NOT NULL, entry_json TEXT NOT NULL, occurred_at TEXT NOT NULL, created_at TEXT NOT NULL)`,
|
|
70
|
+
`CREATE TABLE IF NOT EXISTS realtime_sessions (id TEXT PRIMARY KEY, token_hash TEXT NOT NULL UNIQUE, token_prefix TEXT NOT NULL, role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE, schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE, schema_slug TEXT NOT NULL, created_at TEXT NOT NULL, expires_at TEXT NOT NULL, used_at TEXT)`,
|
|
71
|
+
];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const MVP_MIGRATION_ID = "001_mvp_foundation";
|
|
2
|
+
export { MVP_ADDITIVE_MIGRATIONS_SQL } from "./migrations-additive.js";
|
|
3
|
+
export declare const MVP_TABLES: readonly ["migrations", "roles", "users", "schemas", "fields", "entries", "permissions", "admin_permissions", "api_tokens", "webhooks", "webhook_events", "webhook_deliveries", "realtime_configs", "realtime_events", "realtime_sessions"];
|
|
4
|
+
export declare const MVP_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 INTEGER NOT NULL DEFAULT 0,\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),\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 INTEGER NOT NULL DEFAULT 0,\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 INTEGER NOT NULL DEFAULT 0,\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 INTEGER NOT NULL DEFAULT 0,\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 INTEGER NOT NULL DEFAULT 1,\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,\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 INTEGER NOT NULL DEFAULT 0,\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 INTEGER PRIMARY KEY AUTOINCREMENT,\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";
|
|
5
|
+
//# sourceMappingURL=migrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,uBAAuB,CAAC;AAErD,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAEvE,eAAO,MAAM,UAAU,6OAgBb,CAAC;AAEX,eAAO,MAAM,kBAAkB,60IA2J9B,CAAC"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
export const MVP_MIGRATION_ID = "001_mvp_foundation";
|
|
2
|
+
export { MVP_ADDITIVE_MIGRATIONS_SQL } from "./migrations-additive.js";
|
|
3
|
+
export const MVP_TABLES = [
|
|
4
|
+
"migrations",
|
|
5
|
+
"roles",
|
|
6
|
+
"users",
|
|
7
|
+
"schemas",
|
|
8
|
+
"fields",
|
|
9
|
+
"entries",
|
|
10
|
+
"permissions",
|
|
11
|
+
"admin_permissions",
|
|
12
|
+
"api_tokens",
|
|
13
|
+
"webhooks",
|
|
14
|
+
"webhook_events",
|
|
15
|
+
"webhook_deliveries",
|
|
16
|
+
"realtime_configs",
|
|
17
|
+
"realtime_events",
|
|
18
|
+
"realtime_sessions",
|
|
19
|
+
];
|
|
20
|
+
export const MVP_FOUNDATION_SQL = `
|
|
21
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
22
|
+
id TEXT PRIMARY KEY,
|
|
23
|
+
applied_at TEXT NOT NULL
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
CREATE TABLE IF NOT EXISTS roles (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
name TEXT NOT NULL UNIQUE,
|
|
29
|
+
description TEXT NOT NULL DEFAULT '',
|
|
30
|
+
is_owner INTEGER NOT NULL DEFAULT 0,
|
|
31
|
+
role_kind TEXT NOT NULL DEFAULT 'api',
|
|
32
|
+
created_at TEXT NOT NULL,
|
|
33
|
+
updated_at TEXT NOT NULL
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
37
|
+
id TEXT PRIMARY KEY,
|
|
38
|
+
email TEXT NOT NULL UNIQUE,
|
|
39
|
+
password_hash TEXT NOT NULL,
|
|
40
|
+
role_id TEXT NOT NULL REFERENCES roles(id),
|
|
41
|
+
created_at TEXT NOT NULL,
|
|
42
|
+
updated_at TEXT NOT NULL
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
CREATE TABLE IF NOT EXISTS schemas (
|
|
46
|
+
id TEXT PRIMARY KEY,
|
|
47
|
+
name TEXT NOT NULL,
|
|
48
|
+
slug TEXT NOT NULL UNIQUE,
|
|
49
|
+
description TEXT NOT NULL DEFAULT '',
|
|
50
|
+
created_at TEXT NOT NULL,
|
|
51
|
+
updated_at TEXT NOT NULL
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE TABLE IF NOT EXISTS fields (
|
|
55
|
+
id TEXT PRIMARY KEY,
|
|
56
|
+
schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,
|
|
57
|
+
name TEXT NOT NULL,
|
|
58
|
+
slug TEXT NOT NULL,
|
|
59
|
+
type TEXT NOT NULL,
|
|
60
|
+
relation_schema_id TEXT REFERENCES schemas(id),
|
|
61
|
+
relation_type TEXT,
|
|
62
|
+
required INTEGER NOT NULL DEFAULT 0,
|
|
63
|
+
position INTEGER NOT NULL,
|
|
64
|
+
UNIQUE(schema_id, slug)
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
CREATE TABLE IF NOT EXISTS entries (
|
|
68
|
+
id TEXT PRIMARY KEY,
|
|
69
|
+
schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,
|
|
70
|
+
data_json TEXT NOT NULL,
|
|
71
|
+
created_at TEXT NOT NULL,
|
|
72
|
+
updated_at TEXT NOT NULL
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
CREATE TABLE IF NOT EXISTS permissions (
|
|
76
|
+
id TEXT PRIMARY KEY,
|
|
77
|
+
role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
78
|
+
schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,
|
|
79
|
+
action TEXT NOT NULL,
|
|
80
|
+
allowed INTEGER NOT NULL DEFAULT 0,
|
|
81
|
+
UNIQUE(role_id, schema_id, action)
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
CREATE TABLE IF NOT EXISTS admin_permissions (
|
|
85
|
+
id TEXT PRIMARY KEY,
|
|
86
|
+
role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
87
|
+
action TEXT NOT NULL,
|
|
88
|
+
allowed INTEGER NOT NULL DEFAULT 0,
|
|
89
|
+
UNIQUE(role_id, action)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
CREATE TABLE IF NOT EXISTS api_tokens (
|
|
93
|
+
id TEXT PRIMARY KEY,
|
|
94
|
+
role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
95
|
+
name TEXT NOT NULL,
|
|
96
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
97
|
+
token_prefix TEXT NOT NULL,
|
|
98
|
+
created_at TEXT NOT NULL,
|
|
99
|
+
last_used_at TEXT,
|
|
100
|
+
revoked_at TEXT
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
CREATE TABLE IF NOT EXISTS webhooks (
|
|
104
|
+
id TEXT PRIMARY KEY,
|
|
105
|
+
name TEXT NOT NULL,
|
|
106
|
+
url TEXT NOT NULL,
|
|
107
|
+
secret TEXT NOT NULL,
|
|
108
|
+
events_json TEXT NOT NULL,
|
|
109
|
+
schema_id TEXT REFERENCES schemas(id) ON DELETE SET NULL,
|
|
110
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
111
|
+
created_at TEXT NOT NULL,
|
|
112
|
+
updated_at TEXT NOT NULL
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
CREATE TABLE IF NOT EXISTS webhook_events (
|
|
116
|
+
id TEXT PRIMARY KEY,
|
|
117
|
+
event_type TEXT NOT NULL,
|
|
118
|
+
schema_id TEXT NOT NULL,
|
|
119
|
+
schema_slug TEXT NOT NULL,
|
|
120
|
+
entry_id TEXT NOT NULL,
|
|
121
|
+
payload_json TEXT NOT NULL,
|
|
122
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
123
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
124
|
+
next_retry_at TEXT,
|
|
125
|
+
created_at TEXT NOT NULL,
|
|
126
|
+
updated_at TEXT NOT NULL
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
CREATE TABLE IF NOT EXISTS webhook_deliveries (
|
|
130
|
+
id TEXT PRIMARY KEY,
|
|
131
|
+
event_id TEXT NOT NULL REFERENCES webhook_events(id) ON DELETE CASCADE,
|
|
132
|
+
webhook_id TEXT NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
|
|
133
|
+
url TEXT NOT NULL,
|
|
134
|
+
status TEXT NOT NULL,
|
|
135
|
+
status_code INTEGER,
|
|
136
|
+
response_body TEXT,
|
|
137
|
+
error TEXT,
|
|
138
|
+
attempt INTEGER NOT NULL,
|
|
139
|
+
created_at TEXT NOT NULL,
|
|
140
|
+
next_retry_at TEXT
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
CREATE TABLE IF NOT EXISTS realtime_configs (
|
|
144
|
+
schema_id TEXT PRIMARY KEY REFERENCES schemas(id) ON DELETE CASCADE,
|
|
145
|
+
enabled INTEGER NOT NULL DEFAULT 0,
|
|
146
|
+
events_json TEXT NOT NULL,
|
|
147
|
+
created_at TEXT NOT NULL,
|
|
148
|
+
updated_at TEXT NOT NULL
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
CREATE TABLE IF NOT EXISTS realtime_events (
|
|
152
|
+
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
153
|
+
id TEXT NOT NULL UNIQUE,
|
|
154
|
+
message_id TEXT NOT NULL UNIQUE,
|
|
155
|
+
event_type TEXT NOT NULL,
|
|
156
|
+
schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,
|
|
157
|
+
schema_slug TEXT NOT NULL,
|
|
158
|
+
entry_id TEXT NOT NULL,
|
|
159
|
+
entry_json TEXT NOT NULL,
|
|
160
|
+
occurred_at TEXT NOT NULL,
|
|
161
|
+
created_at TEXT NOT NULL
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
CREATE TABLE IF NOT EXISTS realtime_sessions (
|
|
165
|
+
id TEXT PRIMARY KEY,
|
|
166
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
167
|
+
token_prefix TEXT NOT NULL,
|
|
168
|
+
role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
|
169
|
+
schema_id TEXT NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,
|
|
170
|
+
schema_slug TEXT NOT NULL,
|
|
171
|
+
created_at TEXT NOT NULL,
|
|
172
|
+
expires_at TEXT NOT NULL,
|
|
173
|
+
used_at TEXT
|
|
174
|
+
);
|
|
175
|
+
`;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SqliteDatabase } from "./sqlite.js";
|
|
2
|
+
import type { PermissionAction, PermissionRecord, SetPermissionInput } from "./permission-repository.type.js";
|
|
3
|
+
export declare function setPermission(db: SqliteDatabase, input: SetPermissionInput): PermissionRecord;
|
|
4
|
+
export declare function listRolePermissions(db: SqliteDatabase, roleId: string): PermissionRecord[];
|
|
5
|
+
export declare function canRoleAccess(db: SqliteDatabase, roleId: string, schemaId: string, action: PermissionAction): boolean;
|
|
6
|
+
//# sourceMappingURL=permission-repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-repository.d.ts","sourceRoot":"","sources":["../src/permission-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,iCAAiC,CAAC;AAazC,wBAAgB,aAAa,CAC3B,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,kBAAkB,GACxB,gBAAgB,CAelB;AAED,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,cAAc,EAClB,MAAM,EAAE,MAAM,GACb,gBAAgB,EAAE,CAKpB;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,cAAc,EAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAQT"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getRoleById } from "./role-repository.js";
|
|
3
|
+
import { getSchemaById } from "./schema-repository.js";
|
|
4
|
+
const permissionActions = [
|
|
5
|
+
"getAll",
|
|
6
|
+
"get",
|
|
7
|
+
"create",
|
|
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);
|
|
15
|
+
if (existing) {
|
|
16
|
+
db.prepare("UPDATE permissions SET allowed = ? WHERE id = ?").run(input.allowed ? 1 : 0, existing.id);
|
|
17
|
+
return requirePermission(db, existing.id);
|
|
18
|
+
}
|
|
19
|
+
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);
|
|
21
|
+
return requirePermission(db, id);
|
|
22
|
+
}
|
|
23
|
+
export function listRolePermissions(db, roleId) {
|
|
24
|
+
const rows = db
|
|
25
|
+
.prepare(permissionSelectSql("WHERE role_id = ? ORDER BY schema_id ASC, action ASC"))
|
|
26
|
+
.all(roleId);
|
|
27
|
+
return rows.map(rowToPermission);
|
|
28
|
+
}
|
|
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")
|
|
34
|
+
return false;
|
|
35
|
+
const manage = findPermission(db, roleId, schemaId, "manage");
|
|
36
|
+
if (manage?.allowed)
|
|
37
|
+
return true;
|
|
38
|
+
const permission = findPermission(db, roleId, schemaId, action);
|
|
39
|
+
return Boolean(permission?.allowed);
|
|
40
|
+
}
|
|
41
|
+
function validatePermissionInput(db, input) {
|
|
42
|
+
if (!permissionActions.includes(input.action)) {
|
|
43
|
+
throw new Error("PERMISSION_ACTION_INVALID");
|
|
44
|
+
}
|
|
45
|
+
const role = getRoleById(db, input.roleId);
|
|
46
|
+
if (!role) {
|
|
47
|
+
throw new Error("ROLE_NOT_FOUND");
|
|
48
|
+
}
|
|
49
|
+
if (role.roleKind !== "api") {
|
|
50
|
+
throw new Error("ROLE_API_REQUIRED");
|
|
51
|
+
}
|
|
52
|
+
if (!getSchemaById(db, input.schemaId)) {
|
|
53
|
+
throw new Error("SCHEMA_NOT_FOUND");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function findPermission(db, roleId, schemaId, action) {
|
|
57
|
+
const row = db
|
|
58
|
+
.prepare(permissionSelectSql("WHERE role_id = ? AND schema_id = ? AND action = ?"))
|
|
59
|
+
.get(roleId, schemaId, action);
|
|
60
|
+
return row ? rowToPermission(row) : undefined;
|
|
61
|
+
}
|
|
62
|
+
function requirePermission(db, id) {
|
|
63
|
+
const row = db.prepare(permissionSelectSql("WHERE id = ?")).get(id);
|
|
64
|
+
if (!row) {
|
|
65
|
+
throw new Error("PERMISSION_NOT_FOUND");
|
|
66
|
+
}
|
|
67
|
+
return rowToPermission(row);
|
|
68
|
+
}
|
|
69
|
+
function rowToPermission(row) {
|
|
70
|
+
return {
|
|
71
|
+
id: row.id,
|
|
72
|
+
roleId: row.roleId,
|
|
73
|
+
schemaId: row.schemaId,
|
|
74
|
+
action: row.action,
|
|
75
|
+
allowed: Boolean(row.allowed),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function permissionSelectSql(suffix) {
|
|
79
|
+
return `SELECT id, role_id as roleId, schema_id as schemaId, action, allowed FROM permissions ${suffix}`;
|
|
80
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type PermissionAction = "getAll" | "get" | "create" | "update" | "delete" | "manage";
|
|
2
|
+
export type SetPermissionInput = {
|
|
3
|
+
roleId: string;
|
|
4
|
+
schemaId: string;
|
|
5
|
+
action: PermissionAction;
|
|
6
|
+
allowed: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type PermissionRecord = SetPermissionInput & {
|
|
9
|
+
id: string;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=permission-repository.type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-repository.type.d.ts","sourceRoot":"","sources":["../src/permission-repository.type.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE5F,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG;IAClD,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SqliteDatabase } from "./sqlite.js";
|
|
2
|
+
import type { RealtimeEventRecord, RealtimeConfigRecord, RealtimeEventType, RecordRealtimeEventInput, SetRealtimeConfigInput } from "./realtime-repository.type.js";
|
|
3
|
+
export declare function listRealtimeConfigs(db: SqliteDatabase): RealtimeConfigRecord[];
|
|
4
|
+
export declare function listRealtimeSettings(db: SqliteDatabase): RealtimeConfigRecord[];
|
|
5
|
+
export declare function getRealtimeConfig(db: SqliteDatabase, schemaId: string): RealtimeConfigRecord | undefined;
|
|
6
|
+
export declare function setRealtimeConfig(db: SqliteDatabase, input: SetRealtimeConfigInput): RealtimeConfigRecord;
|
|
7
|
+
export declare function isRealtimeEventEnabled(db: SqliteDatabase, schemaId: string, event: RealtimeEventType): boolean;
|
|
8
|
+
export declare function recordRealtimeEvent(db: SqliteDatabase, input: RecordRealtimeEventInput): RealtimeEventRecord;
|
|
9
|
+
export declare function listRealtimeEventsAfter(db: SqliteDatabase, schemaId: string, lastEventId: string, limit?: number): RealtimeEventRecord[];
|
|
10
|
+
export declare function listRecentRealtimeEvents(db: SqliteDatabase, limit?: number): RealtimeEventRecord[];
|
|
11
|
+
export declare function pruneRealtimeEvents(db: SqliteDatabase, schemaId: string, keepLatest?: number): number;
|
|
12
|
+
//# sourceMappingURL=realtime-repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-repository.d.ts","sourceRoot":"","sources":["../src/realtime-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACvB,MAAM,+BAA+B,CAAC;AAevC,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,cAAc,GAAG,oBAAoB,EAAE,CAG9E;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,cAAc,GAAG,oBAAoB,EAAE,CAY/E;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAGxG;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,sBAAsB,GAAG,oBAAoB,CAYzG;AAED,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAG9G;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,wBAAwB,GAAG,mBAAmB,CAU5G;AAED,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,cAAc,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,SAAK,GACT,mBAAmB,EAAE,CAOvB;AAED,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,SAAK,GAAG,mBAAmB,EAAE,CAI9F;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,SAAO,GAAG,MAAM,CAanG"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getSchemaById, listSchemas } from "./schema-repository.js";
|
|
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();
|
|
6
|
+
return rows.map(rowToRealtimeConfig);
|
|
7
|
+
}
|
|
8
|
+
export function listRealtimeSettings(db) {
|
|
9
|
+
const configs = new Map(listRealtimeConfigs(db).map((config) => [config.schemaId, config]));
|
|
10
|
+
const now = new Date().toISOString();
|
|
11
|
+
return listSchemas(db).map((schema) => configs.get(schema.id) ?? {
|
|
12
|
+
schemaId: schema.id,
|
|
13
|
+
enabled: false,
|
|
14
|
+
events: ["entry.created", "entry.updated", "entry.deleted"],
|
|
15
|
+
createdAt: now,
|
|
16
|
+
updatedAt: now,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function getRealtimeConfig(db, schemaId) {
|
|
20
|
+
const row = db.prepare(realtimeSelectSql("WHERE schema_id = ?")).get(schemaId);
|
|
21
|
+
return row ? rowToRealtimeConfig(row) : undefined;
|
|
22
|
+
}
|
|
23
|
+
export function setRealtimeConfig(db, input) {
|
|
24
|
+
const events = normalizeEvents(input.events);
|
|
25
|
+
if (!getSchemaById(db, input.schemaId))
|
|
26
|
+
throw new Error("SCHEMA_NOT_FOUND");
|
|
27
|
+
const now = new Date().toISOString();
|
|
28
|
+
db.prepare(`INSERT INTO realtime_configs (schema_id, enabled, events_json, created_at, updated_at)
|
|
29
|
+
VALUES (?, ?, ?, ?, ?)
|
|
30
|
+
ON CONFLICT(schema_id) DO UPDATE SET enabled = excluded.enabled,
|
|
31
|
+
events_json = excluded.events_json,
|
|
32
|
+
updated_at = excluded.updated_at`).run(input.schemaId, input.enabled ? 1 : 0, JSON.stringify(events), now, now);
|
|
33
|
+
return getRealtimeConfig(db, input.schemaId);
|
|
34
|
+
}
|
|
35
|
+
export function isRealtimeEventEnabled(db, schemaId, event) {
|
|
36
|
+
const config = getRealtimeConfig(db, schemaId);
|
|
37
|
+
return Boolean(config?.enabled && config.events.includes(event));
|
|
38
|
+
}
|
|
39
|
+
export function recordRealtimeEvent(db, input) {
|
|
40
|
+
const now = new Date().toISOString();
|
|
41
|
+
const id = `rte_${randomUUID()}`;
|
|
42
|
+
const messageId = `rtm_${randomUUID()}`;
|
|
43
|
+
db.prepare(`INSERT INTO realtime_events
|
|
44
|
+
(id, message_id, event_type, schema_id, schema_slug, entry_id, entry_json, occurred_at, created_at)
|
|
45
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, messageId, input.eventType, input.schemaId, input.schemaSlug, input.entry.id, JSON.stringify(input.entry), now, now);
|
|
46
|
+
return rowToRealtimeEvent(db.prepare(realtimeEventSelectSql("WHERE id = ?")).get(id));
|
|
47
|
+
}
|
|
48
|
+
export function listRealtimeEventsAfter(db, schemaId, lastEventId, limit = 50) {
|
|
49
|
+
const last = db.prepare("SELECT sequence FROM realtime_events WHERE id = ? AND schema_id = ?")
|
|
50
|
+
.get(lastEventId, schemaId);
|
|
51
|
+
if (!last)
|
|
52
|
+
return [];
|
|
53
|
+
const rows = db.prepare(realtimeEventSelectSql("WHERE schema_id = ? AND sequence > ? ORDER BY sequence ASC LIMIT ?"))
|
|
54
|
+
.all(schemaId, last.sequence, Math.max(1, Math.min(limit, 100)));
|
|
55
|
+
return rows.map(rowToRealtimeEvent);
|
|
56
|
+
}
|
|
57
|
+
export function listRecentRealtimeEvents(db, limit = 25) {
|
|
58
|
+
const rows = db.prepare(realtimeEventSelectSql("ORDER BY sequence DESC LIMIT ?"))
|
|
59
|
+
.all(Math.max(1, Math.min(limit, 100)));
|
|
60
|
+
return rows.map(rowToRealtimeEvent);
|
|
61
|
+
}
|
|
62
|
+
export function pruneRealtimeEvents(db, schemaId, keepLatest = 1000) {
|
|
63
|
+
const keep = Math.max(1, keepLatest);
|
|
64
|
+
const result = db.prepare(`DELETE FROM realtime_events
|
|
65
|
+
WHERE schema_id = ?
|
|
66
|
+
AND sequence NOT IN (
|
|
67
|
+
SELECT sequence FROM realtime_events
|
|
68
|
+
WHERE schema_id = ?
|
|
69
|
+
ORDER BY sequence DESC
|
|
70
|
+
LIMIT ?
|
|
71
|
+
)`).run(schemaId, schemaId, keep);
|
|
72
|
+
return result.changes;
|
|
73
|
+
}
|
|
74
|
+
function normalizeEvents(events) {
|
|
75
|
+
const unique = [...new Set(events)];
|
|
76
|
+
if (unique.length === 0 || unique.some((event) => !allowedEvents.has(event))) {
|
|
77
|
+
throw new Error("REALTIME_EVENTS_INVALID");
|
|
78
|
+
}
|
|
79
|
+
return unique;
|
|
80
|
+
}
|
|
81
|
+
function rowToRealtimeConfig(row) {
|
|
82
|
+
const { eventsJson: _eventsJson, ...record } = row;
|
|
83
|
+
return {
|
|
84
|
+
...record,
|
|
85
|
+
enabled: Boolean(row.enabled),
|
|
86
|
+
events: JSON.parse(row.eventsJson),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function realtimeSelectSql(suffix) {
|
|
90
|
+
return `SELECT schema_id as schemaId, enabled, events_json as eventsJson, created_at as createdAt, updated_at as updatedAt FROM realtime_configs ${suffix}`;
|
|
91
|
+
}
|
|
92
|
+
function rowToRealtimeEvent(row) {
|
|
93
|
+
const { entryJson: _entryJson, sequence: _sequence, ...record } = row;
|
|
94
|
+
return { ...record, entry: JSON.parse(row.entryJson) };
|
|
95
|
+
}
|
|
96
|
+
function realtimeEventSelectSql(suffix) {
|
|
97
|
+
return `SELECT sequence, id, message_id as messageId, event_type as eventType, schema_id as schemaId, schema_slug as schemaSlug, entry_id as entryId, entry_json as entryJson, occurred_at as occurredAt, created_at as createdAt FROM realtime_events ${suffix}`;
|
|
98
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { WebhookEventType } from "./webhook-repository.type.js";
|
|
2
|
+
import type { EntryRecord } from "./entry-repository.type.js";
|
|
3
|
+
export type RealtimeEventType = WebhookEventType;
|
|
4
|
+
export type RealtimeConfigRecord = {
|
|
5
|
+
schemaId: string;
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
events: RealtimeEventType[];
|
|
8
|
+
createdAt: string;
|
|
9
|
+
updatedAt: string;
|
|
10
|
+
};
|
|
11
|
+
export type SetRealtimeConfigInput = {
|
|
12
|
+
schemaId: string;
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
events: RealtimeEventType[];
|
|
15
|
+
};
|
|
16
|
+
export type RealtimeEventRecord = {
|
|
17
|
+
id: string;
|
|
18
|
+
messageId: string;
|
|
19
|
+
eventType: RealtimeEventType;
|
|
20
|
+
schemaId: string;
|
|
21
|
+
schemaSlug: string;
|
|
22
|
+
entryId: string;
|
|
23
|
+
entry: EntryRecord;
|
|
24
|
+
occurredAt: string;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
};
|
|
27
|
+
export type RecordRealtimeEventInput = {
|
|
28
|
+
eventType: RealtimeEventType;
|
|
29
|
+
schemaId: string;
|
|
30
|
+
schemaSlug: string;
|
|
31
|
+
entry: EntryRecord;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=realtime-repository.type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-repository.type.d.ts","sourceRoot":"","sources":["../src/realtime-repository.type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,CAAC;AAEjD,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,iBAAiB,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,WAAW,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,EAAE,iBAAiB,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,WAAW,CAAC;CACpB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SqliteDatabase } from "./sqlite.js";
|
|
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;
|
|
5
|
+
//# sourceMappingURL=realtime-session-repository.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
|
+
export function createRealtimeSession(db, input) {
|
|
3
|
+
const now = new Date();
|
|
4
|
+
const ttlSeconds = Math.max(30, Math.min(input.ttlSeconds ?? 300, 900));
|
|
5
|
+
const token = `rt_${randomBytes(32).toString("base64url")}`;
|
|
6
|
+
const id = randomUUID();
|
|
7
|
+
db.prepare(`INSERT INTO realtime_sessions
|
|
8
|
+
(id, token_hash, token_prefix, role_id, schema_id, schema_slug, created_at, expires_at, used_at)
|
|
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) };
|
|
11
|
+
}
|
|
12
|
+
export function consumeRealtimeSession(db, token, schemaSlug, now = new Date()) {
|
|
13
|
+
const row = db.prepare(sessionSelectSql("WHERE token_hash = ? AND schema_slug = ?"))
|
|
14
|
+
.get(hashToken(token), schemaSlug);
|
|
15
|
+
if (!row || row.usedAt || Date.parse(row.expiresAt) <= now.getTime())
|
|
16
|
+
return undefined;
|
|
17
|
+
const result = db.prepare("UPDATE realtime_sessions SET used_at = ? WHERE id = ? AND used_at IS NULL")
|
|
18
|
+
.run(now.toISOString(), row.id);
|
|
19
|
+
if (result.changes !== 1)
|
|
20
|
+
return undefined;
|
|
21
|
+
return getRealtimeSessionById(db, row.id);
|
|
22
|
+
}
|
|
23
|
+
function getRealtimeSessionById(db, id) {
|
|
24
|
+
const row = db.prepare(sessionSelectSql("WHERE id = ?")).get(id);
|
|
25
|
+
return row;
|
|
26
|
+
}
|
|
27
|
+
function requireRealtimeSession(db, id) {
|
|
28
|
+
const session = getRealtimeSessionById(db, id);
|
|
29
|
+
if (!session)
|
|
30
|
+
throw new Error("REALTIME_SESSION_NOT_FOUND");
|
|
31
|
+
return session;
|
|
32
|
+
}
|
|
33
|
+
function hashToken(token) {
|
|
34
|
+
return createHash("sha256").update(token).digest("hex");
|
|
35
|
+
}
|
|
36
|
+
function sessionSelectSql(suffix) {
|
|
37
|
+
return `SELECT id, token_prefix as tokenPrefix, role_id as roleId, schema_id as schemaId, schema_slug as schemaSlug, created_at as createdAt, expires_at as expiresAt, used_at as usedAt FROM realtime_sessions ${suffix}`;
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type RealtimeSessionRecord = {
|
|
2
|
+
id: string;
|
|
3
|
+
tokenPrefix: string;
|
|
4
|
+
roleId: string;
|
|
5
|
+
schemaId: string;
|
|
6
|
+
schemaSlug: string;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
expiresAt: string;
|
|
9
|
+
usedAt: string | null;
|
|
10
|
+
};
|
|
11
|
+
export type CreatedRealtimeSession = {
|
|
12
|
+
token: string;
|
|
13
|
+
session: RealtimeSessionRecord;
|
|
14
|
+
};
|
|
15
|
+
export type CreateRealtimeSessionInput = {
|
|
16
|
+
roleId: string;
|
|
17
|
+
schemaId: string;
|
|
18
|
+
schemaSlug: string;
|
|
19
|
+
ttlSeconds?: number;
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=realtime-session-repository.type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-session-repository.type.d.ts","sourceRoot":"","sources":["../src/realtime-session-repository.type.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,qBAAqB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|