@modern-admin/system-drizzle 0.1.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/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/schema/pg.d.ts +4982 -0
- package/dist/schema/pg.d.ts.map +1 -0
- package/dist/schema/pg.js +297 -0
- package/dist/schema/pg.js.map +1 -0
- package/dist/stores/ai-task-store.d.ts +20 -0
- package/dist/stores/ai-task-store.d.ts.map +1 -0
- package/dist/stores/ai-task-store.js +128 -0
- package/dist/stores/ai-task-store.js.map +1 -0
- package/dist/stores/cache-store.d.ts +23 -0
- package/dist/stores/cache-store.d.ts.map +1 -0
- package/dist/stores/cache-store.js +72 -0
- package/dist/stores/cache-store.js.map +1 -0
- package/dist/stores/config-store.d.ts +13 -0
- package/dist/stores/config-store.d.ts.map +1 -0
- package/dist/stores/config-store.js +60 -0
- package/dist/stores/config-store.js.map +1 -0
- package/dist/stores/history-store.d.ts +22 -0
- package/dist/stores/history-store.d.ts.map +1 -0
- package/dist/stores/history-store.js +62 -0
- package/dist/stores/history-store.js.map +1 -0
- package/dist/stores/log-store.d.ts +10 -0
- package/dist/stores/log-store.d.ts.map +1 -0
- package/dist/stores/log-store.js +62 -0
- package/dist/stores/log-store.js.map +1 -0
- package/dist/stores/webhook-store.d.ts +16 -0
- package/dist/stores/webhook-store.d.ts.map +1 -0
- package/dist/stores/webhook-store.js +129 -0
- package/dist/stores/webhook-store.js.map +1 -0
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/package.json +53 -0
- package/src/index.ts +65 -0
- package/src/schema/pg.ts +354 -0
- package/src/stores/ai-task-store.ts +174 -0
- package/src/stores/cache-store.ts +90 -0
- package/src/stores/config-store.ts +80 -0
- package/src/stores/history-store.ts +98 -0
- package/src/stores/log-store.ts +71 -0
- package/src/stores/webhook-store.ts +167 -0
- package/src/types.ts +27 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { uuidv7, type HistoryEntry, type HistoryOp, type IHistoryStore } from '@modern-admin/core'
|
|
2
|
+
import { and, desc, eq } from 'drizzle-orm'
|
|
3
|
+
import type { DrizzleLike, SystemTables } from '../types.js'
|
|
4
|
+
|
|
5
|
+
interface HistoryRow {
|
|
6
|
+
id: string
|
|
7
|
+
resourceId: string
|
|
8
|
+
recordId: string
|
|
9
|
+
op: string
|
|
10
|
+
userId: string | null
|
|
11
|
+
snapshot: unknown
|
|
12
|
+
snapshotBefore: unknown
|
|
13
|
+
diff: unknown
|
|
14
|
+
createdAt: Date
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const rowToEntry = (row: HistoryRow): HistoryEntry => ({
|
|
18
|
+
id: row.id,
|
|
19
|
+
resourceId: row.resourceId,
|
|
20
|
+
recordId: row.recordId,
|
|
21
|
+
op: row.op as HistoryOp,
|
|
22
|
+
...(row.userId !== null ? { userId: row.userId } : {}),
|
|
23
|
+
snapshot: (row.snapshot as Record<string, unknown>) ?? {},
|
|
24
|
+
...(row.snapshotBefore !== null && row.snapshotBefore !== undefined
|
|
25
|
+
? { snapshotBefore: row.snapshotBefore as Record<string, unknown> }
|
|
26
|
+
: {}),
|
|
27
|
+
createdAt: row.createdAt.toISOString(),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export class DrizzleHistoryStore implements IHistoryStore {
|
|
31
|
+
constructor(
|
|
32
|
+
private readonly db: DrizzleLike,
|
|
33
|
+
private readonly table: SystemTables['maHistory'],
|
|
34
|
+
) {}
|
|
35
|
+
|
|
36
|
+
async append(input: {
|
|
37
|
+
resourceId: string
|
|
38
|
+
recordId: string
|
|
39
|
+
op: HistoryOp
|
|
40
|
+
userId?: string
|
|
41
|
+
snapshot: Record<string, unknown>
|
|
42
|
+
snapshotBefore?: Record<string, unknown>
|
|
43
|
+
}): Promise<HistoryEntry> {
|
|
44
|
+
const rows = (await this.db
|
|
45
|
+
.insert(this.table)
|
|
46
|
+
.values({
|
|
47
|
+
id: uuidv7(),
|
|
48
|
+
resourceId: input.resourceId,
|
|
49
|
+
recordId: input.recordId,
|
|
50
|
+
op: input.op,
|
|
51
|
+
userId: input.userId ?? null,
|
|
52
|
+
snapshot: input.snapshot,
|
|
53
|
+
snapshotBefore: input.snapshotBefore ?? null,
|
|
54
|
+
})
|
|
55
|
+
.returning()) as HistoryRow[]
|
|
56
|
+
return rowToEntry(rows[0]!)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async list(
|
|
60
|
+
resourceId: string,
|
|
61
|
+
recordId: string,
|
|
62
|
+
options: { limit?: number; offset?: number } = {},
|
|
63
|
+
): Promise<HistoryEntry[]> {
|
|
64
|
+
const limit = options.limit ?? 50
|
|
65
|
+
const offset = options.offset ?? 0
|
|
66
|
+
let q = this.db
|
|
67
|
+
.select()
|
|
68
|
+
.from(this.table)
|
|
69
|
+
.where(
|
|
70
|
+
and(eq(this.table.resourceId, resourceId), eq(this.table.recordId, recordId)),
|
|
71
|
+
)
|
|
72
|
+
.orderBy(desc(this.table.createdAt))
|
|
73
|
+
.limit(limit)
|
|
74
|
+
if (offset > 0) q = q.offset(offset)
|
|
75
|
+
const rows = (await q) as HistoryRow[]
|
|
76
|
+
return rows.map(rowToEntry)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async get(resourceId: string, recordId: string, revisionId: string): Promise<HistoryEntry | null> {
|
|
80
|
+
const rows = (await this.db
|
|
81
|
+
.select()
|
|
82
|
+
.from(this.table)
|
|
83
|
+
.where(
|
|
84
|
+
and(
|
|
85
|
+
eq(this.table.id, revisionId),
|
|
86
|
+
eq(this.table.resourceId, resourceId),
|
|
87
|
+
eq(this.table.recordId, recordId),
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
.limit(1)) as HistoryRow[]
|
|
91
|
+
return rows[0] ? rowToEntry(rows[0]) : null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async latest(resourceId: string, recordId: string): Promise<HistoryEntry | null> {
|
|
95
|
+
const rows = (await this.list(resourceId, recordId, { limit: 1 })) as HistoryEntry[]
|
|
96
|
+
return rows[0] ?? null
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { uuidv7, type ActionLogEntry, type IQueryableLogStore } from '@modern-admin/core'
|
|
2
|
+
import { and, desc, eq, gte, inArray, lte, type SQL } from 'drizzle-orm'
|
|
3
|
+
import type { DrizzleLike, SystemTables } from '../types.js'
|
|
4
|
+
|
|
5
|
+
interface LogRow {
|
|
6
|
+
id: string
|
|
7
|
+
resourceId: string
|
|
8
|
+
action: string
|
|
9
|
+
recordId: string | null
|
|
10
|
+
recordIds: unknown
|
|
11
|
+
userId: string | null
|
|
12
|
+
payload: unknown
|
|
13
|
+
result: unknown
|
|
14
|
+
at: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const rowToEntry = (row: LogRow): ActionLogEntry => ({
|
|
18
|
+
id: row.id,
|
|
19
|
+
resourceId: row.resourceId,
|
|
20
|
+
action: row.action,
|
|
21
|
+
...(row.recordId !== null ? { recordId: row.recordId } : {}),
|
|
22
|
+
...(Array.isArray(row.recordIds) ? { recordIds: row.recordIds as string[] } : {}),
|
|
23
|
+
...(row.userId !== null ? { userId: row.userId } : {}),
|
|
24
|
+
...(row.payload !== null && row.payload !== undefined
|
|
25
|
+
? { payload: row.payload as Record<string, unknown> }
|
|
26
|
+
: {}),
|
|
27
|
+
...(row.result !== null && row.result !== undefined
|
|
28
|
+
? { result: row.result as Record<string, unknown> }
|
|
29
|
+
: {}),
|
|
30
|
+
at: Number(row.at),
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export class DrizzleLogStore implements IQueryableLogStore {
|
|
34
|
+
constructor(
|
|
35
|
+
private readonly db: DrizzleLike,
|
|
36
|
+
private readonly table: SystemTables['maLog'],
|
|
37
|
+
) {}
|
|
38
|
+
|
|
39
|
+
async record(entry: ActionLogEntry): Promise<void> {
|
|
40
|
+
await this.db.insert(this.table).values({
|
|
41
|
+
id: entry.id ?? uuidv7(),
|
|
42
|
+
resourceId: entry.resourceId,
|
|
43
|
+
action: entry.action,
|
|
44
|
+
recordId: entry.recordId ?? null,
|
|
45
|
+
recordIds: entry.recordIds ?? null,
|
|
46
|
+
userId: entry.userId ?? null,
|
|
47
|
+
payload: entry.payload ?? null,
|
|
48
|
+
result: entry.result ?? null,
|
|
49
|
+
at: entry.at,
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async list(filter: Parameters<IQueryableLogStore['list']>[0] = {}): Promise<ActionLogEntry[]> {
|
|
54
|
+
const conds: SQL[] = []
|
|
55
|
+
if (filter.resourceId) conds.push(eq(this.table.resourceId, filter.resourceId))
|
|
56
|
+
if (filter.recordId) conds.push(eq(this.table.recordId, filter.recordId))
|
|
57
|
+
if (filter.userId) conds.push(eq(this.table.userId, filter.userId))
|
|
58
|
+
if (filter.actions?.length) conds.push(inArray(this.table.action, filter.actions))
|
|
59
|
+
if (filter.from) conds.push(gte(this.table.at, filter.from.getTime()))
|
|
60
|
+
if (filter.to) conds.push(lte(this.table.at, filter.to.getTime()))
|
|
61
|
+
|
|
62
|
+
let q = this.db.select().from(this.table)
|
|
63
|
+
if (conds.length) q = q.where(conds.length === 1 ? conds[0] : and(...conds))
|
|
64
|
+
q = q.orderBy(desc(this.table.at))
|
|
65
|
+
if (filter.limit !== undefined) q = q.limit(filter.limit)
|
|
66
|
+
if (filter.offset !== undefined) q = q.offset(filter.offset)
|
|
67
|
+
|
|
68
|
+
const rows = (await q) as LogRow[]
|
|
69
|
+
return rows.map(rowToEntry)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import {
|
|
2
|
+
uuidv7,
|
|
3
|
+
type IWebhookStore,
|
|
4
|
+
type Webhook,
|
|
5
|
+
type WebhookDelivery,
|
|
6
|
+
type WebhookDeliveryStatus,
|
|
7
|
+
type WebhookInput,
|
|
8
|
+
} from '@modern-admin/core'
|
|
9
|
+
import { desc, eq } from 'drizzle-orm'
|
|
10
|
+
import type { DrizzleLike, SystemTables } from '../types.js'
|
|
11
|
+
|
|
12
|
+
interface WebhookRow {
|
|
13
|
+
id: string
|
|
14
|
+
name: string
|
|
15
|
+
url: string
|
|
16
|
+
events: unknown
|
|
17
|
+
resourceId: string | null
|
|
18
|
+
enabled: boolean
|
|
19
|
+
secret: string | null
|
|
20
|
+
headers: unknown
|
|
21
|
+
filters: unknown
|
|
22
|
+
payloadFields: unknown
|
|
23
|
+
createdAt: Date
|
|
24
|
+
updatedAt: Date
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface DeliveryRow {
|
|
28
|
+
id: string
|
|
29
|
+
webhookId: string
|
|
30
|
+
event: string
|
|
31
|
+
payload: unknown
|
|
32
|
+
status: string
|
|
33
|
+
responseStatus: number | null
|
|
34
|
+
responseBody: string | null
|
|
35
|
+
error: string | null
|
|
36
|
+
attempt: number
|
|
37
|
+
createdAt: Date
|
|
38
|
+
deliveredAt: Date | null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const rowToWebhook = (row: WebhookRow): Webhook => ({
|
|
42
|
+
id: row.id,
|
|
43
|
+
name: row.name,
|
|
44
|
+
url: row.url,
|
|
45
|
+
events: Array.isArray(row.events) ? (row.events as string[]) : [],
|
|
46
|
+
resourceId: row.resourceId,
|
|
47
|
+
enabled: row.enabled,
|
|
48
|
+
...(row.secret !== null ? { secret: row.secret } : {}),
|
|
49
|
+
headers: (row.headers as Record<string, string>) ?? {},
|
|
50
|
+
filters: (row.filters as Record<string, string>) ?? {},
|
|
51
|
+
payloadFields: Array.isArray(row.payloadFields) ? (row.payloadFields as string[]) : [],
|
|
52
|
+
createdAt: row.createdAt.toISOString(),
|
|
53
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const rowToDelivery = (row: DeliveryRow): WebhookDelivery => ({
|
|
57
|
+
id: row.id,
|
|
58
|
+
webhookId: row.webhookId,
|
|
59
|
+
event: row.event,
|
|
60
|
+
payload: (row.payload as Record<string, unknown>) ?? {},
|
|
61
|
+
status: row.status as WebhookDeliveryStatus,
|
|
62
|
+
...(row.responseStatus !== null ? { responseStatus: row.responseStatus } : {}),
|
|
63
|
+
...(row.responseBody !== null ? { responseBody: row.responseBody } : {}),
|
|
64
|
+
...(row.error !== null ? { error: row.error } : {}),
|
|
65
|
+
attempt: row.attempt,
|
|
66
|
+
createdAt: row.createdAt.toISOString(),
|
|
67
|
+
...(row.deliveredAt !== null ? { deliveredAt: row.deliveredAt.toISOString() } : {}),
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
export class DrizzleWebhookStore implements IWebhookStore {
|
|
71
|
+
constructor(
|
|
72
|
+
private readonly db: DrizzleLike,
|
|
73
|
+
private readonly hookTable: SystemTables['maWebhook'],
|
|
74
|
+
private readonly deliveryTable: SystemTables['maWebhookDelivery'],
|
|
75
|
+
) {}
|
|
76
|
+
|
|
77
|
+
async list(): Promise<Webhook[]> {
|
|
78
|
+
const rows = (await this.db
|
|
79
|
+
.select()
|
|
80
|
+
.from(this.hookTable)
|
|
81
|
+
.orderBy(desc(this.hookTable.createdAt))) as WebhookRow[]
|
|
82
|
+
return rows.map(rowToWebhook)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async get(id: string): Promise<Webhook | null> {
|
|
86
|
+
const rows = (await this.db
|
|
87
|
+
.select()
|
|
88
|
+
.from(this.hookTable)
|
|
89
|
+
.where(eq(this.hookTable.id, id))
|
|
90
|
+
.limit(1)) as WebhookRow[]
|
|
91
|
+
return rows[0] ? rowToWebhook(rows[0]) : null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async create(input: WebhookInput): Promise<Webhook> {
|
|
95
|
+
const rows = (await this.db
|
|
96
|
+
.insert(this.hookTable)
|
|
97
|
+
.values({
|
|
98
|
+
id: uuidv7(),
|
|
99
|
+
name: input.name,
|
|
100
|
+
url: input.url,
|
|
101
|
+
events: input.events,
|
|
102
|
+
resourceId: input.resourceId ?? null,
|
|
103
|
+
enabled: input.enabled ?? true,
|
|
104
|
+
secret: input.secret ?? null,
|
|
105
|
+
headers: input.headers ?? {},
|
|
106
|
+
filters: input.filters ?? {},
|
|
107
|
+
payloadFields: input.payloadFields ?? [],
|
|
108
|
+
})
|
|
109
|
+
.returning()) as WebhookRow[]
|
|
110
|
+
return rowToWebhook(rows[0]!)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async update(id: string, patch: Partial<WebhookInput>): Promise<Webhook> {
|
|
114
|
+
const data: Record<string, unknown> = { updatedAt: new Date() }
|
|
115
|
+
if (patch.name !== undefined) data['name'] = patch.name
|
|
116
|
+
if (patch.url !== undefined) data['url'] = patch.url
|
|
117
|
+
if (patch.events !== undefined) data['events'] = patch.events
|
|
118
|
+
if (patch.resourceId !== undefined) data['resourceId'] = patch.resourceId ?? null
|
|
119
|
+
if (patch.enabled !== undefined) data['enabled'] = patch.enabled
|
|
120
|
+
if (patch.secret !== undefined) data['secret'] = patch.secret
|
|
121
|
+
if (patch.headers !== undefined) data['headers'] = patch.headers
|
|
122
|
+
if (patch.filters !== undefined) data['filters'] = patch.filters
|
|
123
|
+
if (patch.payloadFields !== undefined) data['payloadFields'] = patch.payloadFields
|
|
124
|
+
const rows = (await this.db
|
|
125
|
+
.update(this.hookTable)
|
|
126
|
+
.set(data)
|
|
127
|
+
.where(eq(this.hookTable.id, id))
|
|
128
|
+
.returning()) as WebhookRow[]
|
|
129
|
+
if (!rows[0]) throw new Error(`Webhook not found: ${id}`)
|
|
130
|
+
return rowToWebhook(rows[0])
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async delete(id: string): Promise<void> {
|
|
134
|
+
await this.db.delete(this.hookTable).where(eq(this.hookTable.id, id))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async recordDelivery(
|
|
138
|
+
delivery: Omit<WebhookDelivery, 'id' | 'createdAt'>,
|
|
139
|
+
): Promise<WebhookDelivery> {
|
|
140
|
+
const rows = (await this.db
|
|
141
|
+
.insert(this.deliveryTable)
|
|
142
|
+
.values({
|
|
143
|
+
id: uuidv7(),
|
|
144
|
+
webhookId: delivery.webhookId,
|
|
145
|
+
event: delivery.event,
|
|
146
|
+
payload: delivery.payload,
|
|
147
|
+
status: delivery.status,
|
|
148
|
+
responseStatus: delivery.responseStatus ?? null,
|
|
149
|
+
responseBody: delivery.responseBody ?? null,
|
|
150
|
+
error: delivery.error ?? null,
|
|
151
|
+
attempt: delivery.attempt,
|
|
152
|
+
deliveredAt: delivery.deliveredAt ? new Date(delivery.deliveredAt) : null,
|
|
153
|
+
})
|
|
154
|
+
.returning()) as DeliveryRow[]
|
|
155
|
+
return rowToDelivery(rows[0]!)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async listDeliveries(webhookId: string, limit = 50): Promise<WebhookDelivery[]> {
|
|
159
|
+
const rows = (await this.db
|
|
160
|
+
.select()
|
|
161
|
+
.from(this.deliveryTable)
|
|
162
|
+
.where(eq(this.deliveryTable.webhookId, webhookId))
|
|
163
|
+
.orderBy(desc(this.deliveryTable.createdAt))
|
|
164
|
+
.limit(limit)) as DeliveryRow[]
|
|
165
|
+
return rows.map(rowToDelivery)
|
|
166
|
+
}
|
|
167
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural Drizzle typings used by the system stores.
|
|
3
|
+
*
|
|
4
|
+
* Drizzle's typed `db` carries a heavy dialect-specific generic load
|
|
5
|
+
* (`PgDatabase`, `MySqlDatabase`, `BetterSQLite3Database`, …). To stay
|
|
6
|
+
* dialect-agnostic at the package boundary we accept a `DrizzleLike`
|
|
7
|
+
* surface — runtime calls go through a small handful of methods every
|
|
8
|
+
* builder exposes. Hosts pass their concrete `drizzle(...)` instance and
|
|
9
|
+
* it satisfies this shape structurally.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import type { SystemTables } from './schema/pg.js'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Loose Drizzle client surface. The real `drizzle(...)` instance has a
|
|
18
|
+
* much richer typed API; we only depend on the methods we actually call.
|
|
19
|
+
*/
|
|
20
|
+
export type DrizzleLike = {
|
|
21
|
+
insert: (table: any) => any
|
|
22
|
+
select: (fields?: any) => any
|
|
23
|
+
update: (table: any) => any
|
|
24
|
+
delete: (table: any) => any
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type { SystemTables }
|