@modern-admin/system-prisma 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 (41) hide show
  1. package/dist/index.d.ts +43 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +53 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/stores/ai-task-store.d.ts +43 -0
  6. package/dist/stores/ai-task-store.d.ts.map +1 -0
  7. package/dist/stores/ai-task-store.js +108 -0
  8. package/dist/stores/ai-task-store.js.map +1 -0
  9. package/dist/stores/cache-store.d.ts +30 -0
  10. package/dist/stores/cache-store.d.ts.map +1 -0
  11. package/dist/stores/cache-store.js +61 -0
  12. package/dist/stores/cache-store.js.map +1 -0
  13. package/dist/stores/config-store.d.ts +20 -0
  14. package/dist/stores/config-store.d.ts.map +1 -0
  15. package/dist/stores/config-store.js +61 -0
  16. package/dist/stores/config-store.js.map +1 -0
  17. package/dist/stores/history-store.d.ts +33 -0
  18. package/dist/stores/history-store.d.ts.map +1 -0
  19. package/dist/stores/history-store.js +57 -0
  20. package/dist/stores/history-store.js.map +1 -0
  21. package/dist/stores/log-store.d.ts +23 -0
  22. package/dist/stores/log-store.d.ts.map +1 -0
  23. package/dist/stores/log-store.js +65 -0
  24. package/dist/stores/log-store.js.map +1 -0
  25. package/dist/stores/webhook-store.d.ts +43 -0
  26. package/dist/stores/webhook-store.d.ts.map +1 -0
  27. package/dist/stores/webhook-store.js +111 -0
  28. package/dist/stores/webhook-store.js.map +1 -0
  29. package/dist/types.d.ts +67 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/dist/types.js +30 -0
  32. package/dist/types.js.map +1 -0
  33. package/package.json +52 -0
  34. package/src/index.ts +88 -0
  35. package/src/stores/ai-task-store.ts +154 -0
  36. package/src/stores/cache-store.ts +79 -0
  37. package/src/stores/config-store.ts +80 -0
  38. package/src/stores/history-store.ts +84 -0
  39. package/src/stores/log-store.ts +77 -0
  40. package/src/stores/webhook-store.ts +150 -0
  41. package/src/types.ts +70 -0
@@ -0,0 +1,65 @@
1
+ import { uuidv7 } from '@modern-admin/core';
2
+ const rowToEntry = (row) => ({
3
+ id: row.id,
4
+ resourceId: row.resourceId,
5
+ action: row.action,
6
+ ...(row.recordId !== null ? { recordId: row.recordId } : {}),
7
+ ...(Array.isArray(row.recordIds) ? { recordIds: row.recordIds } : {}),
8
+ ...(row.userId !== null ? { userId: row.userId } : {}),
9
+ ...(row.payload !== null && row.payload !== undefined
10
+ ? { payload: row.payload }
11
+ : {}),
12
+ ...(row.result !== null && row.result !== undefined
13
+ ? { result: row.result }
14
+ : {}),
15
+ at: Number(row.at),
16
+ });
17
+ export class PrismaLogStore {
18
+ constructor(delegate) {
19
+ this.delegate = delegate;
20
+ }
21
+ async record(entry) {
22
+ await this.delegate.create({
23
+ data: {
24
+ // Prefer the writer-supplied id (UUID v7 from `actionLoggingPlugin`)
25
+ // so React lists keyed on `entry.id` line up with the persisted row.
26
+ id: entry.id ?? uuidv7(),
27
+ resourceId: entry.resourceId,
28
+ action: entry.action,
29
+ recordId: entry.recordId ?? null,
30
+ recordIds: entry.recordIds ?? null,
31
+ userId: entry.userId ?? null,
32
+ payload: entry.payload ?? null,
33
+ result: entry.result ?? null,
34
+ at: BigInt(entry.at),
35
+ },
36
+ });
37
+ }
38
+ async list(filter = {}) {
39
+ const where = {};
40
+ if (filter.resourceId)
41
+ where['resourceId'] = filter.resourceId;
42
+ if (filter.recordId)
43
+ where['recordId'] = filter.recordId;
44
+ if (filter.userId)
45
+ where['userId'] = filter.userId;
46
+ if (filter.actions?.length)
47
+ where['action'] = { in: filter.actions };
48
+ if (filter.from || filter.to) {
49
+ const range = {};
50
+ if (filter.from)
51
+ range['gte'] = BigInt(filter.from.getTime());
52
+ if (filter.to)
53
+ range['lte'] = BigInt(filter.to.getTime());
54
+ where['at'] = range;
55
+ }
56
+ const rows = await this.delegate.findMany({
57
+ where,
58
+ orderBy: { at: 'desc' },
59
+ ...(filter.limit !== undefined ? { take: filter.limit } : {}),
60
+ ...(filter.offset !== undefined ? { skip: filter.offset } : {}),
61
+ });
62
+ return rows.map(rowToEntry);
63
+ }
64
+ }
65
+ //# sourceMappingURL=log-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-store.js","sourceRoot":"","sources":["../../src/stores/log-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAgE,MAAM,oBAAoB,CAAA;AAgBzG,MAAM,UAAU,GAAG,CAAC,GAAW,EAAkB,EAAE,CAAC,CAAC;IACnD,EAAE,EAAE,GAAG,CAAC,EAAE;IACV,UAAU,EAAE,GAAG,CAAC,UAAU;IAC1B,MAAM,EAAE,GAAG,CAAC,MAAM;IAClB,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,SAAqB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACjF,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,GAAG,CAAC,GAAG,CAAC,OAAO,KAAK,IAAI,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS;QACnD,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAkC,EAAE;QACrD,CAAC,CAAC,EAAE,CAAC;IACP,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;QACjD,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAiC,EAAE;QACnD,CAAC,CAAC,EAAE,CAAC;IACP,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;CACnB,CAAC,CAAA;AAEF,MAAM,OAAO,cAAc;IACzB,YAA6B,QAAgC;QAAhC,aAAQ,GAAR,QAAQ,CAAwB;IAAG,CAAC;IAEjE,KAAK,CAAC,MAAM,CAAC,KAAqB;QAChC,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YACzB,IAAI,EAAE;gBACJ,qEAAqE;gBACrE,qEAAqE;gBACrE,EAAE,EAAE,KAAK,CAAC,EAAE,IAAI,MAAM,EAAE;gBACxB,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,IAAI;gBAChC,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI;gBAClC,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,IAAI;gBAC5B,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,IAAI;gBAC9B,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,IAAI;gBAC5B,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;aACrB;SACF,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,SAAoD,EAAE;QAC/D,MAAM,KAAK,GAA4B,EAAE,CAAA;QACzC,IAAI,MAAM,CAAC,UAAU;YAAE,KAAK,CAAC,YAAY,CAAC,GAAG,MAAM,CAAC,UAAU,CAAA;QAC9D,IAAI,MAAM,CAAC,QAAQ;YAAE,KAAK,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAA;QACxD,IAAI,MAAM,CAAC,MAAM;YAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,MAAM,CAAA;QAClD,IAAI,MAAM,CAAC,OAAO,EAAE,MAAM;YAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;QACpE,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,KAAK,GAA2B,EAAE,CAAA;YACxC,IAAI,MAAM,CAAC,IAAI;gBAAE,KAAK,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;YAC7D,IAAI,MAAM,CAAC,EAAE;gBAAE,KAAK,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;YACzD,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAA;QACrB,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACxC,KAAK;YACL,OAAO,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;YACvB,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChE,CAAC,CAAA;QACF,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IAC7B,CAAC;CACF"}
@@ -0,0 +1,43 @@
1
+ import { type IWebhookStore, type Webhook, type WebhookDelivery, type WebhookInput } from '@modern-admin/core';
2
+ import type { PrismaDelegate } from '../types.js';
3
+ interface WebhookRow {
4
+ id: string;
5
+ name: string;
6
+ url: string;
7
+ events: unknown;
8
+ resourceId: string | null;
9
+ enabled: boolean;
10
+ secret: string | null;
11
+ headers: unknown;
12
+ filters: unknown;
13
+ payloadFields: unknown;
14
+ createdAt: Date;
15
+ updatedAt: Date;
16
+ }
17
+ interface DeliveryRow {
18
+ id: string;
19
+ webhookId: string;
20
+ event: string;
21
+ payload: unknown;
22
+ status: string;
23
+ responseStatus: number | null;
24
+ responseBody: string | null;
25
+ error: string | null;
26
+ attempt: number;
27
+ createdAt: Date;
28
+ deliveredAt: Date | null;
29
+ }
30
+ export declare class PrismaWebhookStore implements IWebhookStore {
31
+ private readonly webhookDelegate;
32
+ private readonly deliveryDelegate;
33
+ constructor(webhookDelegate: PrismaDelegate<WebhookRow>, deliveryDelegate: PrismaDelegate<DeliveryRow>);
34
+ list(): Promise<Webhook[]>;
35
+ get(id: string): Promise<Webhook | null>;
36
+ create(input: WebhookInput): Promise<Webhook>;
37
+ update(id: string, patch: Partial<WebhookInput>): Promise<Webhook>;
38
+ delete(id: string): Promise<void>;
39
+ recordDelivery(delivery: Omit<WebhookDelivery, 'id' | 'createdAt'>): Promise<WebhookDelivery>;
40
+ listDeliveries(webhookId: string, limit?: number): Promise<WebhookDelivery[]>;
41
+ }
42
+ export {};
43
+ //# sourceMappingURL=webhook-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-store.d.ts","sourceRoot":"","sources":["../../src/stores/webhook-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,OAAO,EACZ,KAAK,eAAe,EAEpB,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,OAAO,CAAA;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,aAAa,EAAE,OAAO,CAAA;IACtB,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;CAChB;AAED,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;IACf,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;CACzB;AA+BD,qBAAa,kBAAmB,YAAW,aAAa;IAEpD,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBADhB,eAAe,EAAE,cAAc,CAAC,UAAU,CAAC,EAC3C,gBAAgB,EAAE,cAAc,CAAC,WAAW,CAAC;IAG1D,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAK1B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAKxC,MAAM,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC;IAkB7C,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAelE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjC,cAAc,CAClB,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,WAAW,CAAC,GAClD,OAAO,CAAC,eAAe,CAAC;IAkBrB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;CAQhF"}
@@ -0,0 +1,111 @@
1
+ import { uuidv7, } from '@modern-admin/core';
2
+ const rowToWebhook = (row) => ({
3
+ id: row.id,
4
+ name: row.name,
5
+ url: row.url,
6
+ events: Array.isArray(row.events) ? row.events : [],
7
+ resourceId: row.resourceId,
8
+ enabled: row.enabled,
9
+ ...(row.secret !== null ? { secret: row.secret } : {}),
10
+ headers: row.headers ?? {},
11
+ filters: row.filters ?? {},
12
+ payloadFields: Array.isArray(row.payloadFields) ? row.payloadFields : [],
13
+ createdAt: row.createdAt.toISOString(),
14
+ updatedAt: row.updatedAt.toISOString(),
15
+ });
16
+ const rowToDelivery = (row) => ({
17
+ id: row.id,
18
+ webhookId: row.webhookId,
19
+ event: row.event,
20
+ payload: row.payload ?? {},
21
+ status: row.status,
22
+ ...(row.responseStatus !== null ? { responseStatus: row.responseStatus } : {}),
23
+ ...(row.responseBody !== null ? { responseBody: row.responseBody } : {}),
24
+ ...(row.error !== null ? { error: row.error } : {}),
25
+ attempt: row.attempt,
26
+ createdAt: row.createdAt.toISOString(),
27
+ ...(row.deliveredAt !== null ? { deliveredAt: row.deliveredAt.toISOString() } : {}),
28
+ });
29
+ export class PrismaWebhookStore {
30
+ constructor(webhookDelegate, deliveryDelegate) {
31
+ this.webhookDelegate = webhookDelegate;
32
+ this.deliveryDelegate = deliveryDelegate;
33
+ }
34
+ async list() {
35
+ const rows = await this.webhookDelegate.findMany({ orderBy: { createdAt: 'desc' } });
36
+ return rows.map(rowToWebhook);
37
+ }
38
+ async get(id) {
39
+ const row = await this.webhookDelegate.findUnique({ where: { id } });
40
+ return row ? rowToWebhook(row) : null;
41
+ }
42
+ async create(input) {
43
+ const row = await this.webhookDelegate.create({
44
+ data: {
45
+ id: uuidv7(),
46
+ name: input.name,
47
+ url: input.url,
48
+ events: input.events,
49
+ resourceId: input.resourceId ?? null,
50
+ enabled: input.enabled ?? true,
51
+ secret: input.secret ?? null,
52
+ headers: input.headers ?? {},
53
+ filters: input.filters ?? {},
54
+ payloadFields: input.payloadFields ?? [],
55
+ },
56
+ });
57
+ return rowToWebhook(row);
58
+ }
59
+ async update(id, patch) {
60
+ const data = {};
61
+ if (patch.name !== undefined)
62
+ data['name'] = patch.name;
63
+ if (patch.url !== undefined)
64
+ data['url'] = patch.url;
65
+ if (patch.events !== undefined)
66
+ data['events'] = patch.events;
67
+ if (patch.resourceId !== undefined)
68
+ data['resourceId'] = patch.resourceId ?? null;
69
+ if (patch.enabled !== undefined)
70
+ data['enabled'] = patch.enabled;
71
+ if (patch.secret !== undefined)
72
+ data['secret'] = patch.secret;
73
+ if (patch.headers !== undefined)
74
+ data['headers'] = patch.headers;
75
+ if (patch.filters !== undefined)
76
+ data['filters'] = patch.filters;
77
+ if (patch.payloadFields !== undefined)
78
+ data['payloadFields'] = patch.payloadFields;
79
+ const row = await this.webhookDelegate.update({ where: { id }, data });
80
+ return rowToWebhook(row);
81
+ }
82
+ async delete(id) {
83
+ await this.webhookDelegate.delete({ where: { id } });
84
+ }
85
+ async recordDelivery(delivery) {
86
+ const row = await this.deliveryDelegate.create({
87
+ data: {
88
+ id: uuidv7(),
89
+ webhookId: delivery.webhookId,
90
+ event: delivery.event,
91
+ payload: delivery.payload,
92
+ status: delivery.status,
93
+ responseStatus: delivery.responseStatus ?? null,
94
+ responseBody: delivery.responseBody ?? null,
95
+ error: delivery.error ?? null,
96
+ attempt: delivery.attempt,
97
+ deliveredAt: delivery.deliveredAt ? new Date(delivery.deliveredAt) : null,
98
+ },
99
+ });
100
+ return rowToDelivery(row);
101
+ }
102
+ async listDeliveries(webhookId, limit = 50) {
103
+ const rows = await this.deliveryDelegate.findMany({
104
+ where: { webhookId },
105
+ orderBy: { createdAt: 'desc' },
106
+ take: limit,
107
+ });
108
+ return rows.map(rowToDelivery);
109
+ }
110
+ }
111
+ //# sourceMappingURL=webhook-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-store.js","sourceRoot":"","sources":["../../src/stores/webhook-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Structural Prisma typings used by the system stores.
3
+ *
4
+ * Prisma generates a fully-typed client per project, so we can't import a
5
+ * concrete `PrismaClient` here. Instead, every store consumes a minimal
6
+ * delegate shape — the same handful of methods Prisma always emits for a
7
+ * model. The host's actual generated client matches this surface
8
+ * structurally, no casts required.
9
+ */
10
+ export interface PrismaDelegate<TRow = any> {
11
+ findMany(args?: any): Promise<TRow[]>;
12
+ findUnique(args: {
13
+ where: any;
14
+ }): Promise<TRow | null>;
15
+ findFirst(args?: any): Promise<TRow | null>;
16
+ create(args: {
17
+ data: any;
18
+ }): Promise<TRow>;
19
+ update(args: {
20
+ where: any;
21
+ data: any;
22
+ }): Promise<TRow>;
23
+ upsert(args: {
24
+ where: any;
25
+ update: any;
26
+ create: any;
27
+ }): Promise<TRow>;
28
+ delete(args: {
29
+ where: any;
30
+ }): Promise<TRow>;
31
+ deleteMany(args?: {
32
+ where?: any;
33
+ }): Promise<{
34
+ count: number;
35
+ }>;
36
+ count(args?: any): Promise<number>;
37
+ }
38
+ /**
39
+ * Whatever `prisma` instance the host injects. We index into it by model
40
+ * name (default: `maLog`, `maWebhook`, …); see `setupPrismaSystem`'s
41
+ * `models` option for renames.
42
+ *
43
+ * Uses `{ [K: string]: any }` (not `Record<string, PrismaDelegate>`) so that
44
+ * the generated `PrismaClient` — which carries utility methods like
45
+ * `$connect`, `$disconnect`, `$transaction` that are NOT `PrismaDelegate` —
46
+ * is directly assignable without a cast. TypeScript only waives the
47
+ * "missing index signature" check when the index value type is `any`; any
48
+ * other type (including `unknown`) still rejects `PrismaClient`. The shape
49
+ * of each delegate is validated at runtime in `resolveDelegate()`.
50
+ */
51
+ export type PrismaLike = {
52
+ [K: string]: any;
53
+ };
54
+ export declare const DEFAULT_MODELS: {
55
+ readonly log: "maLog";
56
+ readonly webhook: "maWebhook";
57
+ readonly webhookDelivery: "maWebhookDelivery";
58
+ readonly config: "maConfig";
59
+ readonly history: "maHistory";
60
+ readonly aiTask: "maAiTask";
61
+ readonly aiTaskEvent: "maAiTaskEvent";
62
+ readonly cache: "maCache";
63
+ };
64
+ export type ModelKey = keyof typeof DEFAULT_MODELS;
65
+ export type ModelOverrides = Partial<Record<ModelKey, string>>;
66
+ export declare function resolveDelegate(prisma: PrismaLike, key: ModelKey, overrides: ModelOverrides | undefined): PrismaDelegate;
67
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,WAAW,cAAc,CAAC,IAAI,GAAG,GAAG;IACxC,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IACrC,UAAU,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;IACtD,SAAS,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;IAC3C,MAAM,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1C,MAAM,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,GAAG,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtD,MAAM,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,GAAG,CAAC;QAAC,MAAM,EAAE,GAAG,CAAC;QAAC,MAAM,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrE,MAAM,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3C,UAAU,CAAC,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC9D,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACnC;AAED;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,UAAU,GAAG;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CAAA;AAE7C,eAAO,MAAM,cAAc;;;;;;;;;CASjB,CAAA;AAEV,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,cAAc,CAAA;AAClD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAA;AAE9D,wBAAgB,eAAe,CAC7B,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,QAAQ,EACb,SAAS,EAAE,cAAc,GAAG,SAAS,GACpC,cAAc,CAWhB"}
package/dist/types.js ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Structural Prisma typings used by the system stores.
3
+ *
4
+ * Prisma generates a fully-typed client per project, so we can't import a
5
+ * concrete `PrismaClient` here. Instead, every store consumes a minimal
6
+ * delegate shape — the same handful of methods Prisma always emits for a
7
+ * model. The host's actual generated client matches this surface
8
+ * structurally, no casts required.
9
+ */
10
+ export const DEFAULT_MODELS = {
11
+ log: 'maLog',
12
+ webhook: 'maWebhook',
13
+ webhookDelivery: 'maWebhookDelivery',
14
+ config: 'maConfig',
15
+ history: 'maHistory',
16
+ aiTask: 'maAiTask',
17
+ aiTaskEvent: 'maAiTaskEvent',
18
+ cache: 'maCache',
19
+ };
20
+ export function resolveDelegate(prisma, key, overrides) {
21
+ const name = overrides?.[key] ?? DEFAULT_MODELS[key];
22
+ const delegate = prisma[name];
23
+ if (!delegate || typeof delegate.findMany !== 'function') {
24
+ throw new Error(`[modern-admin/system-prisma] missing delegate "prisma.${name}". ` +
25
+ `Make sure the Modern Admin schema fragment is included in your schema.prisma ` +
26
+ `(see @modern-admin/system-prisma/schema), and that the Prisma client has been generated.`);
27
+ }
28
+ return delegate;
29
+ }
30
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgCH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,GAAG,EAAE,OAAO;IACZ,OAAO,EAAE,WAAW;IACpB,eAAe,EAAE,mBAAmB;IACpC,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,WAAW;IACpB,MAAM,EAAE,UAAU;IAClB,WAAW,EAAE,eAAe;IAC5B,KAAK,EAAE,SAAS;CACR,CAAA;AAKV,MAAM,UAAU,eAAe,CAC7B,MAAkB,EAClB,GAAa,EACb,SAAqC;IAErC,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC,GAAG,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,CAAA;IACpD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAA+B,CAAA;IAC3D,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CACb,yDAAyD,IAAI,KAAK;YAClE,+EAA+E;YAC/E,0FAA0F,CAC3F,CAAA;IACH,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@modern-admin/system-prisma",
3
+ "version": "0.1.0",
4
+ "description": "Prisma-backed implementation of Modern Admin system stores (logs, history, webhooks, AI tasks).",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/modern-admin/modern-admin.git",
10
+ "directory": "packages/system-prisma"
11
+ },
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "src"
23
+ ],
24
+ "publishConfig": {
25
+ "registry": "https://registry.npmjs.org",
26
+ "access": "public",
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "default": "./dist/index.js"
33
+ }
34
+ }
35
+ },
36
+ "dependencies": {
37
+ "@modern-admin/core": "0.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "@modern-admin/tsconfig": "0.1.0",
41
+ "@types/bun": "^1.3.13",
42
+ "typescript": "^6.0.3"
43
+ },
44
+ "peerDependencies": {
45
+ "@prisma/client": ">=7"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "@prisma/client": {
49
+ "optional": true
50
+ }
51
+ }
52
+ }
package/src/index.ts ADDED
@@ -0,0 +1,88 @@
1
+ // @modern-admin/system-prisma — Prisma-backed implementations of the
2
+ // runtime system stores defined in `@modern-admin/core/system`.
3
+ //
4
+ // Quick start:
5
+ // 1. Copy `prisma/modern-admin.prisma` into your application's schema
6
+ // (or include via Prisma multi-file schema).
7
+ // 2. Run your usual Prisma migration workflow.
8
+ // 3. Wire stores to subsystems:
9
+ //
10
+ // import { PrismaClient } from '@prisma/client'
11
+ // import { setupPrismaSystem } from '@modern-admin/system-prisma'
12
+ // import { actionLoggingPlugin } from '@modern-admin-pro/feature-logging'
13
+ //
14
+ // const prisma = new PrismaClient()
15
+ // const system = setupPrismaSystem(prisma)
16
+ //
17
+ // new ModernAdmin({
18
+ // databases: [...],
19
+ // plugins: [actionLoggingPlugin({ store: system.logStore })],
20
+ // })
21
+ //
22
+ // If you renamed the shipped models, pass `{ models: { log: 'myLog', ... } }`.
23
+
24
+ import type { ISystemStores } from '@modern-admin/core'
25
+ import { PrismaLogStore } from './stores/log-store.js'
26
+ import { PrismaWebhookStore } from './stores/webhook-store.js'
27
+ import { PrismaConfigStore } from './stores/config-store.js'
28
+ import { PrismaHistoryStore } from './stores/history-store.js'
29
+ import { PrismaAiTaskStore } from './stores/ai-task-store.js'
30
+ import { PrismaCacheStore } from './stores/cache-store.js'
31
+ import { type ModelOverrides, type PrismaLike, resolveDelegate } from './types.js'
32
+
33
+ export interface PrismaSystemOptions {
34
+ /**
35
+ * Override Prisma client property names if you renamed the shipped
36
+ * models. Defaults match the camelCased model identifiers from
37
+ * `prisma/modern-admin.prisma`:
38
+ *
39
+ * { log: 'maLog', webhook: 'maWebhook', webhookDelivery: 'maWebhookDelivery',
40
+ * config: 'maConfig', history: 'maHistory', aiTask: 'maAiTask',
41
+ * aiTaskEvent: 'maAiTaskEvent', cache: 'maCache' }
42
+ */
43
+ models?: ModelOverrides
44
+ }
45
+
46
+ export interface PrismaSystem extends ISystemStores {
47
+ logStore: PrismaLogStore
48
+ webhookStore: PrismaWebhookStore
49
+ configStore: PrismaConfigStore
50
+ historyStore: PrismaHistoryStore
51
+ aiTaskStore: PrismaAiTaskStore
52
+ cacheStore: PrismaCacheStore
53
+ }
54
+
55
+ /**
56
+ * Build the full bundle of system stores from a Prisma client instance.
57
+ * The host owns the client lifecycle (connect/disconnect); this just wires
58
+ * the model delegates to thin store classes.
59
+ */
60
+ export function setupPrismaSystem(
61
+ prisma: PrismaLike,
62
+ options: PrismaSystemOptions = {},
63
+ ): PrismaSystem {
64
+ const m = options.models
65
+ return {
66
+ logStore: new PrismaLogStore(resolveDelegate(prisma, 'log', m)),
67
+ webhookStore: new PrismaWebhookStore(
68
+ resolveDelegate(prisma, 'webhook', m),
69
+ resolveDelegate(prisma, 'webhookDelivery', m),
70
+ ),
71
+ configStore: new PrismaConfigStore(resolveDelegate(prisma, 'config', m)),
72
+ historyStore: new PrismaHistoryStore(resolveDelegate(prisma, 'history', m)),
73
+ aiTaskStore: new PrismaAiTaskStore(
74
+ resolveDelegate(prisma, 'aiTask', m),
75
+ resolveDelegate(prisma, 'aiTaskEvent', m),
76
+ ),
77
+ cacheStore: new PrismaCacheStore(resolveDelegate(prisma, 'cache', m)),
78
+ }
79
+ }
80
+
81
+ export { PrismaLogStore } from './stores/log-store.js'
82
+ export { PrismaWebhookStore } from './stores/webhook-store.js'
83
+ export { PrismaConfigStore } from './stores/config-store.js'
84
+ export { PrismaHistoryStore } from './stores/history-store.js'
85
+ export { PrismaAiTaskStore } from './stores/ai-task-store.js'
86
+ export { PrismaCacheStore } from './stores/cache-store.js'
87
+ export type { PrismaDelegate, PrismaLike, ModelOverrides, ModelKey } from './types.js'
88
+ export { DEFAULT_MODELS } from './types.js'
@@ -0,0 +1,154 @@
1
+ import {
2
+ uuidv7,
3
+ type AiTask,
4
+ type AiTaskEvent,
5
+ type AiTaskInput,
6
+ type AiTaskStatus,
7
+ type IAiTaskStore,
8
+ } from '@modern-admin/core'
9
+ import type { PrismaDelegate } from '../types.js'
10
+
11
+ interface TaskRow {
12
+ id: string
13
+ kind: string
14
+ resourceId: string | null
15
+ recordId: string | null
16
+ userId: string | null
17
+ status: string
18
+ input: unknown
19
+ output: unknown
20
+ error: string | null
21
+ progress: number | null
22
+ createdAt: Date
23
+ updatedAt: Date
24
+ startedAt: Date | null
25
+ finishedAt: Date | null
26
+ }
27
+
28
+ interface EventRow {
29
+ id: string
30
+ taskId: string
31
+ type: string
32
+ data: unknown
33
+ createdAt: Date
34
+ }
35
+
36
+ const rowToTask = (row: TaskRow): AiTask => ({
37
+ id: row.id,
38
+ kind: row.kind,
39
+ ...(row.resourceId !== null ? { resourceId: row.resourceId } : {}),
40
+ ...(row.recordId !== null ? { recordId: row.recordId } : {}),
41
+ ...(row.userId !== null ? { userId: row.userId } : {}),
42
+ status: row.status as AiTaskStatus,
43
+ input: (row.input as Record<string, unknown>) ?? {},
44
+ ...(row.output !== null && row.output !== undefined
45
+ ? { output: row.output as Record<string, unknown> }
46
+ : {}),
47
+ ...(row.error !== null ? { error: row.error } : {}),
48
+ progress: row.progress,
49
+ createdAt: row.createdAt.toISOString(),
50
+ updatedAt: row.updatedAt.toISOString(),
51
+ ...(row.startedAt !== null ? { startedAt: row.startedAt.toISOString() } : {}),
52
+ ...(row.finishedAt !== null ? { finishedAt: row.finishedAt.toISOString() } : {}),
53
+ })
54
+
55
+ const rowToEvent = (row: EventRow): AiTaskEvent => ({
56
+ id: row.id,
57
+ taskId: row.taskId,
58
+ type: row.type,
59
+ data: (row.data as Record<string, unknown>) ?? {},
60
+ createdAt: row.createdAt.toISOString(),
61
+ })
62
+
63
+ const TERMINAL: AiTaskStatus[] = ['succeeded', 'failed', 'cancelled']
64
+
65
+ export class PrismaAiTaskStore implements IAiTaskStore {
66
+ constructor(
67
+ private readonly taskDelegate: PrismaDelegate<TaskRow>,
68
+ private readonly eventDelegate: PrismaDelegate<EventRow>,
69
+ ) {}
70
+
71
+ async enqueue(input: AiTaskInput): Promise<AiTask> {
72
+ const row = await this.taskDelegate.create({
73
+ data: {
74
+ id: uuidv7(),
75
+ kind: input.kind,
76
+ resourceId: input.resourceId ?? null,
77
+ recordId: input.recordId ?? null,
78
+ userId: input.userId ?? null,
79
+ status: 'pending',
80
+ input: input.input ?? {},
81
+ progress: null,
82
+ },
83
+ })
84
+ return rowToTask(row)
85
+ }
86
+
87
+ async get(id: string): Promise<AiTask | null> {
88
+ const row = await this.taskDelegate.findUnique({ where: { id } })
89
+ return row ? rowToTask(row) : null
90
+ }
91
+
92
+ async list(filter: Parameters<IAiTaskStore['list']>[0] = {}): Promise<AiTask[]> {
93
+ const where: Record<string, unknown> = {}
94
+ if (filter.kind) where['kind'] = filter.kind
95
+ if (filter.status) {
96
+ const list = Array.isArray(filter.status) ? filter.status : [filter.status]
97
+ where['status'] = { in: list }
98
+ }
99
+ if (filter.userId) where['userId'] = filter.userId
100
+ if (filter.resourceId) where['resourceId'] = filter.resourceId
101
+ const rows = await this.taskDelegate.findMany({
102
+ where,
103
+ orderBy: { createdAt: 'desc' },
104
+ ...(filter.limit !== undefined ? { take: filter.limit } : {}),
105
+ })
106
+ return rows.map(rowToTask)
107
+ }
108
+
109
+ async updateStatus(
110
+ id: string,
111
+ patch: {
112
+ status: AiTaskStatus
113
+ progress?: number | null
114
+ output?: Record<string, unknown>
115
+ error?: string
116
+ },
117
+ ): Promise<AiTask> {
118
+ const data: Record<string, unknown> = { status: patch.status }
119
+ if (patch.progress !== undefined) data['progress'] = patch.progress
120
+ if (patch.output !== undefined) data['output'] = patch.output
121
+ if (patch.error !== undefined) data['error'] = patch.error
122
+ if (patch.status === 'running') {
123
+ // only set startedAt the first time we transition to running
124
+ const current = await this.taskDelegate.findUnique({ where: { id } })
125
+ if (current && !current.startedAt) data['startedAt'] = new Date()
126
+ }
127
+ if (TERMINAL.includes(patch.status)) {
128
+ data['finishedAt'] = new Date()
129
+ }
130
+ const row = await this.taskDelegate.update({ where: { id }, data })
131
+ return rowToTask(row)
132
+ }
133
+
134
+ async appendEvent(
135
+ taskId: string,
136
+ type: string,
137
+ data: Record<string, unknown>,
138
+ ): Promise<AiTaskEvent> {
139
+ const row = await this.eventDelegate.create({
140
+ data: { id: uuidv7(), taskId, type, data },
141
+ })
142
+ return rowToEvent(row)
143
+ }
144
+
145
+ async events(taskId: string, sinceId?: string): Promise<AiTaskEvent[]> {
146
+ const all = await this.eventDelegate.findMany({
147
+ where: { taskId },
148
+ orderBy: { createdAt: 'asc' },
149
+ })
150
+ if (!sinceId) return all.map(rowToEvent)
151
+ const idx = all.findIndex((r) => r.id === sinceId)
152
+ return (idx < 0 ? all : all.slice(idx + 1)).map(rowToEvent)
153
+ }
154
+ }