@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.
Files changed (46) hide show
  1. package/dist/index.d.ts +30 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +48 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/schema/pg.d.ts +4982 -0
  6. package/dist/schema/pg.d.ts.map +1 -0
  7. package/dist/schema/pg.js +297 -0
  8. package/dist/schema/pg.js.map +1 -0
  9. package/dist/stores/ai-task-store.d.ts +20 -0
  10. package/dist/stores/ai-task-store.d.ts.map +1 -0
  11. package/dist/stores/ai-task-store.js +128 -0
  12. package/dist/stores/ai-task-store.js.map +1 -0
  13. package/dist/stores/cache-store.d.ts +23 -0
  14. package/dist/stores/cache-store.d.ts.map +1 -0
  15. package/dist/stores/cache-store.js +72 -0
  16. package/dist/stores/cache-store.js.map +1 -0
  17. package/dist/stores/config-store.d.ts +13 -0
  18. package/dist/stores/config-store.d.ts.map +1 -0
  19. package/dist/stores/config-store.js +60 -0
  20. package/dist/stores/config-store.js.map +1 -0
  21. package/dist/stores/history-store.d.ts +22 -0
  22. package/dist/stores/history-store.d.ts.map +1 -0
  23. package/dist/stores/history-store.js +62 -0
  24. package/dist/stores/history-store.js.map +1 -0
  25. package/dist/stores/log-store.d.ts +10 -0
  26. package/dist/stores/log-store.d.ts.map +1 -0
  27. package/dist/stores/log-store.js +62 -0
  28. package/dist/stores/log-store.js.map +1 -0
  29. package/dist/stores/webhook-store.d.ts +16 -0
  30. package/dist/stores/webhook-store.d.ts.map +1 -0
  31. package/dist/stores/webhook-store.js +129 -0
  32. package/dist/stores/webhook-store.js.map +1 -0
  33. package/dist/types.d.ts +23 -0
  34. package/dist/types.d.ts.map +1 -0
  35. package/dist/types.js +12 -0
  36. package/dist/types.js.map +1 -0
  37. package/package.json +53 -0
  38. package/src/index.ts +65 -0
  39. package/src/schema/pg.ts +354 -0
  40. package/src/stores/ai-task-store.ts +174 -0
  41. package/src/stores/cache-store.ts +90 -0
  42. package/src/stores/config-store.ts +80 -0
  43. package/src/stores/history-store.ts +98 -0
  44. package/src/stores/log-store.ts +71 -0
  45. package/src/stores/webhook-store.ts +167 -0
  46. 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 }