@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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/dist/admin-permission-repository.d.ts +7 -0
  4. package/dist/admin-permission-repository.d.ts.map +1 -0
  5. package/dist/admin-permission-repository.js +74 -0
  6. package/dist/admin-permission-repository.type.d.ts +10 -0
  7. package/dist/admin-permission-repository.type.d.ts.map +1 -0
  8. package/dist/admin-permission-repository.type.js +1 -0
  9. package/dist/api-token-repository.d.ts +7 -0
  10. package/dist/api-token-repository.d.ts.map +1 -0
  11. package/dist/api-token-repository.js +80 -0
  12. package/dist/api-token-repository.type.d.ts +19 -0
  13. package/dist/api-token-repository.type.d.ts.map +1 -0
  14. package/dist/api-token-repository.type.js +1 -0
  15. package/dist/entry-query.d.ts +4 -0
  16. package/dist/entry-query.d.ts.map +1 -0
  17. package/dist/entry-query.js +61 -0
  18. package/dist/entry-repository.d.ts +8 -0
  19. package/dist/entry-repository.d.ts.map +1 -0
  20. package/dist/entry-repository.js +181 -0
  21. package/dist/entry-repository.type.d.ts +32 -0
  22. package/dist/entry-repository.type.d.ts.map +1 -0
  23. package/dist/entry-repository.type.js +1 -0
  24. package/dist/index.d.ts +29 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +15 -0
  27. package/dist/migrations-additive.d.ts +2 -0
  28. package/dist/migrations-additive.d.ts.map +1 -0
  29. package/dist/migrations-additive.js +71 -0
  30. package/dist/migrations.d.ts +5 -0
  31. package/dist/migrations.d.ts.map +1 -0
  32. package/dist/migrations.js +175 -0
  33. package/dist/permission-repository.d.ts +6 -0
  34. package/dist/permission-repository.d.ts.map +1 -0
  35. package/dist/permission-repository.js +80 -0
  36. package/dist/permission-repository.type.d.ts +11 -0
  37. package/dist/permission-repository.type.d.ts.map +1 -0
  38. package/dist/permission-repository.type.js +1 -0
  39. package/dist/realtime-repository.d.ts +12 -0
  40. package/dist/realtime-repository.d.ts.map +1 -0
  41. package/dist/realtime-repository.js +98 -0
  42. package/dist/realtime-repository.type.d.ts +33 -0
  43. package/dist/realtime-repository.type.d.ts.map +1 -0
  44. package/dist/realtime-repository.type.js +1 -0
  45. package/dist/realtime-session-repository.d.ts +5 -0
  46. package/dist/realtime-session-repository.d.ts.map +1 -0
  47. package/dist/realtime-session-repository.js +38 -0
  48. package/dist/realtime-session-repository.type.d.ts +21 -0
  49. package/dist/realtime-session-repository.type.d.ts.map +1 -0
  50. package/dist/realtime-session-repository.type.js +1 -0
  51. package/dist/relation-errors.d.ts +20 -0
  52. package/dist/relation-errors.d.ts.map +1 -0
  53. package/dist/relation-errors.js +30 -0
  54. package/dist/relation-helpers.d.ts +14 -0
  55. package/dist/relation-helpers.d.ts.map +1 -0
  56. package/dist/relation-helpers.js +21 -0
  57. package/dist/role-repository.d.ts +8 -0
  58. package/dist/role-repository.d.ts.map +1 -0
  59. package/dist/role-repository.js +66 -0
  60. package/dist/role-repository.type.d.ts +15 -0
  61. package/dist/role-repository.type.d.ts.map +1 -0
  62. package/dist/role-repository.type.js +1 -0
  63. package/dist/schema-repository.d.ts +9 -0
  64. package/dist/schema-repository.d.ts.map +1 -0
  65. package/dist/schema-repository.js +155 -0
  66. package/dist/schema-repository.type.d.ts +45 -0
  67. package/dist/schema-repository.type.d.ts.map +1 -0
  68. package/dist/schema-repository.type.js +1 -0
  69. package/dist/schema.type.d.ts +9 -0
  70. package/dist/schema.type.d.ts.map +1 -0
  71. package/dist/schema.type.js +1 -0
  72. package/dist/sqlite.d.ts +6 -0
  73. package/dist/sqlite.d.ts.map +1 -0
  74. package/dist/sqlite.js +40 -0
  75. package/dist/user-repository.d.ts +11 -0
  76. package/dist/user-repository.d.ts.map +1 -0
  77. package/dist/user-repository.js +56 -0
  78. package/dist/user-repository.type.d.ts +15 -0
  79. package/dist/user-repository.type.d.ts.map +1 -0
  80. package/dist/user-repository.type.js +1 -0
  81. package/dist/webhook-repository.d.ts +15 -0
  82. package/dist/webhook-repository.d.ts.map +1 -0
  83. package/dist/webhook-repository.js +136 -0
  84. package/dist/webhook-repository.type.d.ts +79 -0
  85. package/dist/webhook-repository.type.d.ts.map +1 -0
  86. package/dist/webhook-repository.type.js +1 -0
  87. package/package.json +43 -0
@@ -0,0 +1,136 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { getSchemaById } from "./schema-repository.js";
3
+ const allowedEvents = new Set(["entry.created", "entry.updated", "entry.deleted"]);
4
+ export function createWebhook(db, input) {
5
+ const draft = normalizeWebhookDraft(db, input);
6
+ const id = randomUUID();
7
+ const now = new Date().toISOString();
8
+ db.prepare(`INSERT INTO webhooks (id, name, url, secret, events_json, schema_id, active, created_at, updated_at)
9
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, draft.name, draft.url, draft.secret, JSON.stringify(draft.events), draft.schemaId, draft.active ? 1 : 0, now, now);
10
+ return publicWebhook(requireWebhook(db, id));
11
+ }
12
+ export function updateWebhook(db, webhookId, input) {
13
+ const current = requireWebhook(db, webhookId);
14
+ const draft = normalizeWebhookDraft(db, { ...input, secret: input.secret ?? current.secret });
15
+ const now = new Date().toISOString();
16
+ db.prepare(`UPDATE webhooks SET name = ?, url = ?, secret = ?, events_json = ?, schema_id = ?, active = ?, updated_at = ?
17
+ WHERE id = ?`).run(draft.name, draft.url, draft.secret, JSON.stringify(draft.events), draft.schemaId, draft.active ? 1 : 0, now, webhookId);
18
+ return publicWebhook(requireWebhook(db, webhookId));
19
+ }
20
+ export function listWebhooks(db) {
21
+ const rows = db.prepare(webhookSelectSql("ORDER BY created_at DESC")).all();
22
+ return rows.map(rowToWebhook).map(publicWebhook);
23
+ }
24
+ export function deleteWebhook(db, webhookId) {
25
+ const result = db.prepare("DELETE FROM webhooks WHERE id = ?").run(webhookId);
26
+ return result.changes > 0;
27
+ }
28
+ export function enqueueWebhookEvent(db, input) {
29
+ const id = randomUUID();
30
+ const now = new Date().toISOString();
31
+ const payload = {
32
+ event: input.eventType,
33
+ schema: { id: input.schemaId, slug: input.schemaSlug },
34
+ entry: input.entry,
35
+ occurredAt: now,
36
+ };
37
+ db.prepare(`INSERT INTO webhook_events
38
+ (id, event_type, schema_id, schema_slug, entry_id, payload_json, status, attempts, next_retry_at, created_at, updated_at)
39
+ VALUES (?, ?, ?, ?, ?, ?, 'pending', 0, NULL, ?, ?)`).run(id, input.eventType, input.schemaId, input.schemaSlug, input.entry.id, JSON.stringify(payload), now, now);
40
+ return requireWebhookEvent(db, id);
41
+ }
42
+ export function listPendingWebhookEvents(db, now = new Date().toISOString(), limit = 20) {
43
+ const rows = db.prepare(eventSelectSql("WHERE status = 'pending' AND (next_retry_at IS NULL OR next_retry_at <= ?) ORDER BY created_at ASC LIMIT ?"))
44
+ .all(now, limit);
45
+ return rows.map(rowToEvent);
46
+ }
47
+ export function listMatchingWebhooks(db, event) {
48
+ const rows = db.prepare(webhookSelectSql("WHERE active = 1 AND (schema_id IS NULL OR schema_id = ?) ORDER BY created_at ASC"))
49
+ .all(event.schemaId);
50
+ return rows.map(rowToWebhook).filter((webhook) => webhook.events.includes(event.eventType));
51
+ }
52
+ export function listWebhookDeliveries(db, webhookId) {
53
+ const suffix = webhookId ? "WHERE webhook_id = ? ORDER BY created_at DESC" : "ORDER BY created_at DESC";
54
+ const rows = webhookId
55
+ ? db.prepare(deliverySelectSql(suffix)).all(webhookId)
56
+ : db.prepare(deliverySelectSql(suffix)).all();
57
+ return rows;
58
+ }
59
+ export function countWebhookDeliveryAttempts(db, eventId, webhookId) {
60
+ const row = db.prepare("SELECT COUNT(*) as count FROM webhook_deliveries WHERE event_id = ? AND webhook_id = ?")
61
+ .get(eventId, webhookId);
62
+ return row.count;
63
+ }
64
+ export function hasSuccessfulWebhookDelivery(db, eventId, webhookId) {
65
+ const row = db.prepare("SELECT id FROM webhook_deliveries WHERE event_id = ? AND webhook_id = ? AND status = 'success' LIMIT 1")
66
+ .get(eventId, webhookId);
67
+ return Boolean(row);
68
+ }
69
+ export function recordWebhookDelivery(db, input) {
70
+ const id = input.id ?? randomUUID();
71
+ const now = new Date().toISOString();
72
+ db.prepare(`INSERT INTO webhook_deliveries
73
+ (id, event_id, webhook_id, url, status, status_code, response_body, error, attempt, created_at, next_retry_at)
74
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, input.eventId, input.webhookId, input.url, input.status, input.statusCode ?? null, trim(input.responseBody), trim(input.error), input.attempt, now, input.nextRetryAt ?? null);
75
+ return db.prepare(deliverySelectSql("WHERE id = ?")).get(id);
76
+ }
77
+ export function updateWebhookEventStatus(db, eventId, status, attempts, nextRetryAt) {
78
+ db.prepare("UPDATE webhook_events SET status = ?, attempts = ?, next_retry_at = ?, updated_at = ? WHERE id = ?")
79
+ .run(status, attempts, nextRetryAt, new Date().toISOString(), eventId);
80
+ }
81
+ function normalizeWebhookDraft(db, input) {
82
+ const events = [...new Set(input.events)];
83
+ if (!input.name.trim())
84
+ throw new Error("WEBHOOK_NAME_REQUIRED");
85
+ if (!isHttpUrl(input.url))
86
+ throw new Error("WEBHOOK_URL_INVALID");
87
+ if (events.length === 0 || events.some((event) => !allowedEvents.has(event)))
88
+ throw new Error("WEBHOOK_EVENTS_INVALID");
89
+ if (input.schemaId && !getSchemaById(db, input.schemaId))
90
+ throw new Error("SCHEMA_NOT_FOUND");
91
+ const secret = input.secret?.trim() || randomUUID();
92
+ return { name: input.name.trim(), url: input.url.trim(), secret, events, schemaId: input.schemaId ?? null, active: input.active ?? true };
93
+ }
94
+ function requireWebhook(db, webhookId) {
95
+ const row = db.prepare(webhookSelectSql("WHERE id = ?")).get(webhookId);
96
+ if (!row)
97
+ throw new Error("WEBHOOK_NOT_FOUND");
98
+ return rowToWebhook(row);
99
+ }
100
+ function requireWebhookEvent(db, eventId) {
101
+ const row = db.prepare(eventSelectSql("WHERE id = ?")).get(eventId);
102
+ if (!row)
103
+ throw new Error("WEBHOOK_EVENT_NOT_FOUND");
104
+ return rowToEvent(row);
105
+ }
106
+ function rowToWebhook(row) {
107
+ return { ...row, active: Boolean(row.active), events: JSON.parse(row.eventsJson) };
108
+ }
109
+ function rowToEvent(row) {
110
+ return { ...row, payload: JSON.parse(row.payloadJson) };
111
+ }
112
+ function publicWebhook(webhook) {
113
+ const { secret: _secret, ...record } = webhook;
114
+ return record;
115
+ }
116
+ function isHttpUrl(url) {
117
+ try {
118
+ const parsed = new URL(url);
119
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
120
+ }
121
+ catch {
122
+ return false;
123
+ }
124
+ }
125
+ function trim(value) {
126
+ return value ? value.slice(0, 500) : null;
127
+ }
128
+ function webhookSelectSql(suffix) {
129
+ return `SELECT id, name, url, secret, events_json as eventsJson, schema_id as schemaId, active, created_at as createdAt, updated_at as updatedAt FROM webhooks ${suffix}`;
130
+ }
131
+ function eventSelectSql(suffix) {
132
+ return `SELECT id, event_type as eventType, schema_id as schemaId, schema_slug as schemaSlug, entry_id as entryId, payload_json as payloadJson, status, attempts, next_retry_at as nextRetryAt, created_at as createdAt, updated_at as updatedAt FROM webhook_events ${suffix}`;
133
+ }
134
+ function deliverySelectSql(suffix) {
135
+ return `SELECT id, event_id as eventId, webhook_id as webhookId, url, status, status_code as statusCode, response_body as responseBody, error, attempt, created_at as createdAt, next_retry_at as nextRetryAt FROM webhook_deliveries ${suffix}`;
136
+ }
@@ -0,0 +1,79 @@
1
+ import type { EntryRecord } from "./entry-repository.type.js";
2
+ export type WebhookEventType = "entry.created" | "entry.updated" | "entry.deleted";
3
+ export type WebhookEventStatus = "delivered" | "failed" | "pending";
4
+ export type WebhookDeliveryStatus = "failed" | "success";
5
+ export type WebhookRecord = {
6
+ id: string;
7
+ name: string;
8
+ url: string;
9
+ events: WebhookEventType[];
10
+ schemaId: string | null;
11
+ active: boolean;
12
+ createdAt: string;
13
+ updatedAt: string;
14
+ };
15
+ export type WebhookSecretRecord = WebhookRecord & {
16
+ secret: string;
17
+ };
18
+ export type WebhookDraft = {
19
+ name: string;
20
+ url: string;
21
+ secret?: string;
22
+ events: WebhookEventType[];
23
+ schemaId?: string | null;
24
+ active?: boolean;
25
+ };
26
+ export type WebhookEventRecord = {
27
+ id: string;
28
+ eventType: WebhookEventType;
29
+ schemaId: string;
30
+ schemaSlug: string;
31
+ entryId: string;
32
+ payload: WebhookPayload;
33
+ status: WebhookEventStatus;
34
+ attempts: number;
35
+ nextRetryAt: string | null;
36
+ createdAt: string;
37
+ updatedAt: string;
38
+ };
39
+ export type WebhookDeliveryRecord = {
40
+ id: string;
41
+ eventId: string;
42
+ webhookId: string;
43
+ url: string;
44
+ status: WebhookDeliveryStatus;
45
+ statusCode: number | null;
46
+ responseBody: string | null;
47
+ error: string | null;
48
+ attempt: number;
49
+ createdAt: string;
50
+ nextRetryAt: string | null;
51
+ };
52
+ export type WebhookPayload = {
53
+ event: WebhookEventType;
54
+ schema: {
55
+ id: string;
56
+ slug: string;
57
+ };
58
+ entry: EntryRecord;
59
+ occurredAt: string;
60
+ };
61
+ export type EnqueueWebhookEventInput = {
62
+ eventType: WebhookEventType;
63
+ schemaId: string;
64
+ schemaSlug: string;
65
+ entry: EntryRecord;
66
+ };
67
+ export type RecordWebhookDeliveryInput = {
68
+ id?: string;
69
+ eventId: string;
70
+ webhookId: string;
71
+ url: string;
72
+ status: WebhookDeliveryStatus;
73
+ statusCode?: number | null;
74
+ responseBody?: string | null;
75
+ error?: string | null;
76
+ attempt: number;
77
+ nextRetryAt?: string | null;
78
+ };
79
+ //# sourceMappingURL=webhook-repository.type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-repository.type.d.ts","sourceRoot":"","sources":["../src/webhook-repository.type.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@apiagex/database",
3
+ "version": "0.6.2",
4
+ "description": "Kysely database adapter package for Apiagex CMS.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/bestmaa/apiagex.git",
10
+ "directory": "packages/database"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/bestmaa/apiagex/issues"
14
+ },
15
+ "homepage": "https://github.com/bestmaa/apiagex#readme",
16
+ "keywords": [
17
+ "apiagex",
18
+ "headless-cms",
19
+ "cms",
20
+ "sqlite"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "exports": {
31
+ ".": "./dist/index.js"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc -b",
35
+ "test": "vitest run"
36
+ },
37
+ "dependencies": {
38
+ "better-sqlite3": "^12.9.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/better-sqlite3": "^7.6.13"
42
+ }
43
+ }