@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,79 @@
1
+ import type { CacheEntry, ICacheStore } from '@modern-admin/core'
2
+ import type { PrismaDelegate } from '../types.js'
3
+
4
+ interface CacheRow {
5
+ key: string
6
+ value: unknown
7
+ tags: unknown
8
+ expiresAt: Date | null
9
+ createdAt: Date
10
+ updatedAt: Date
11
+ }
12
+
13
+ const rowToEntry = (row: CacheRow): CacheEntry => ({
14
+ key: row.key,
15
+ value: row.value,
16
+ tags: Array.isArray(row.tags) ? (row.tags as string[]) : [],
17
+ expiresAt: row.expiresAt ? row.expiresAt.toISOString() : null,
18
+ createdAt: row.createdAt.toISOString(),
19
+ updatedAt: row.updatedAt.toISOString(),
20
+ })
21
+
22
+ export class PrismaCacheStore implements ICacheStore {
23
+ constructor(private readonly delegate: PrismaDelegate<CacheRow>) {}
24
+
25
+ async get(key: string): Promise<CacheEntry | null> {
26
+ const row = await this.delegate.findUnique({ where: { key } })
27
+ if (!row) return null
28
+ if (row.expiresAt && row.expiresAt.getTime() < Date.now()) {
29
+ await this.delegate.delete({ where: { key } }).catch(() => undefined)
30
+ return null
31
+ }
32
+ return rowToEntry(row)
33
+ }
34
+
35
+ async set(
36
+ key: string,
37
+ value: unknown,
38
+ options: { ttlMs?: number; tags?: string[] } = {},
39
+ ): Promise<void> {
40
+ const expiresAt = options.ttlMs ? new Date(Date.now() + options.ttlMs) : null
41
+ const tags = options.tags ?? []
42
+ await this.delegate.upsert({
43
+ where: { key },
44
+ create: { key, value: value ?? null, tags, expiresAt },
45
+ update: { value: value ?? null, tags, expiresAt },
46
+ })
47
+ }
48
+
49
+ async delete(key: string): Promise<void> {
50
+ await this.delegate.delete({ where: { key } }).catch(() => undefined)
51
+ }
52
+
53
+ /**
54
+ * Tag invalidation walks the table because Prisma's portable `Json` field
55
+ * doesn't support array-contains across all DB engines. For high-volume
56
+ * caches use a Postgres-specific implementation (`tags String[]`) or
57
+ * Redis. Here correctness > throughput by design.
58
+ */
59
+ async invalidateTags(tags: string[]): Promise<number> {
60
+ if (!tags.length) return 0
61
+ const set = new Set(tags)
62
+ const rows = await this.delegate.findMany({})
63
+ const targets = rows.filter((r) =>
64
+ Array.isArray(r.tags) && (r.tags as string[]).some((t) => set.has(t)),
65
+ )
66
+ if (!targets.length) return 0
67
+ const result = await this.delegate.deleteMany({
68
+ where: { key: { in: targets.map((r) => r.key) } },
69
+ })
70
+ return result.count
71
+ }
72
+
73
+ async prune(): Promise<number> {
74
+ const result = await this.delegate.deleteMany({
75
+ where: { expiresAt: { lt: new Date() } },
76
+ })
77
+ return result.count
78
+ }
79
+ }
@@ -0,0 +1,80 @@
1
+ import { type ConfigEntry, type ConfigScope, type IConfigStore, uuidv7 } from '@modern-admin/core'
2
+ import type { PrismaDelegate } from '../types.js'
3
+
4
+ interface ConfigRow {
5
+ id: string
6
+ scope: string
7
+ scopeId: string
8
+ key: string
9
+ value: unknown
10
+ updatedAt: Date
11
+ }
12
+
13
+ const GLOBAL_SCOPE_ID = ''
14
+ const encodeScopeId = (scopeId: string | null): string => scopeId ?? GLOBAL_SCOPE_ID
15
+ const decodeScopeId = (scopeId: string): string | null => scopeId === GLOBAL_SCOPE_ID ? null : scopeId
16
+
17
+ const rowToEntry = (row: ConfigRow): ConfigEntry => ({
18
+ scope: row.scope as ConfigScope,
19
+ scopeId: decodeScopeId(row.scopeId),
20
+ key: row.key,
21
+ value: row.value,
22
+ updatedAt: row.updatedAt.toISOString(),
23
+ })
24
+
25
+ export class PrismaConfigStore implements IConfigStore {
26
+ constructor(private readonly delegate: PrismaDelegate<ConfigRow>) {
27
+ }
28
+
29
+ async get(scope: ConfigScope, scopeId: string | null, key: string): Promise<unknown> {
30
+ // findFirst instead of findUnique: tolerates accidental duplicates and
31
+ // returns the most-recently-updated row rather than throwing.
32
+ const row = await this.delegate.findFirst({
33
+ where: { scope, scopeId: encodeScopeId(scopeId), key },
34
+ orderBy: { updatedAt: 'desc' },
35
+ })
36
+ return row?.value
37
+ }
38
+
39
+ async set(
40
+ scope: ConfigScope,
41
+ scopeId: string | null,
42
+ key: string,
43
+ value: unknown,
44
+ ): Promise<void> {
45
+ const encodedScopeId = encodeScopeId(scopeId)
46
+ // Use findFirst + update-by-id / create instead of upsert with a
47
+ // compound unique key. Prisma 7 + PrismaPg adapter can misidentify
48
+ // compound-key upserts and issue a second INSERT rather than an UPDATE,
49
+ // which violates the unique constraint. Anchoring the update on the
50
+ // surrogate id is always safe.
51
+ const existing = await this.delegate.findFirst({
52
+ where: { scope, scopeId: encodedScopeId, key },
53
+ })
54
+ if (existing) {
55
+ await this.delegate.update({
56
+ where: { id: existing.id },
57
+ data: { value: value ?? null },
58
+ })
59
+ } else {
60
+ await this.delegate.create({
61
+ data: { id: uuidv7(), scope, scopeId: encodedScopeId, key, value: value ?? null },
62
+ })
63
+ }
64
+ }
65
+
66
+ async delete(scope: ConfigScope, scopeId: string | null, key: string): Promise<void> {
67
+ // deleteMany avoids "record not found" and also cleans up any duplicates.
68
+ await this.delegate
69
+ .deleteMany({ where: { scope, scopeId: encodeScopeId(scopeId), key } })
70
+ .catch(() => undefined)
71
+ }
72
+
73
+ async list(scope: ConfigScope, scopeId: string | null): Promise<ConfigEntry[]> {
74
+ const rows = await this.delegate.findMany({
75
+ where: { scope, scopeId: encodeScopeId(scopeId) },
76
+ orderBy: { key: 'asc' },
77
+ })
78
+ return rows.map(rowToEntry)
79
+ }
80
+ }
@@ -0,0 +1,84 @@
1
+ import { uuidv7, type HistoryEntry, type HistoryOp, type IHistoryStore } from '@modern-admin/core'
2
+ import type { PrismaDelegate } from '../types.js'
3
+
4
+ interface HistoryRow {
5
+ id: string
6
+ resourceId: string
7
+ recordId: string
8
+ op: string
9
+ userId: string | null
10
+ snapshot: unknown
11
+ snapshotBefore: unknown
12
+ diff: unknown
13
+ createdAt: Date
14
+ }
15
+
16
+ const rowToEntry = (row: HistoryRow): HistoryEntry => ({
17
+ id: row.id,
18
+ resourceId: row.resourceId,
19
+ recordId: row.recordId,
20
+ op: row.op as HistoryOp,
21
+ ...(row.userId !== null ? { userId: row.userId } : {}),
22
+ snapshot: (row.snapshot as Record<string, unknown>) ?? {},
23
+ ...(row.snapshotBefore !== null && row.snapshotBefore !== undefined
24
+ ? { snapshotBefore: row.snapshotBefore as Record<string, unknown> }
25
+ : {}),
26
+ createdAt: row.createdAt.toISOString(),
27
+ })
28
+
29
+ export class PrismaHistoryStore implements IHistoryStore {
30
+ constructor(private readonly delegate: PrismaDelegate<HistoryRow>) {}
31
+
32
+ async append(input: {
33
+ resourceId: string
34
+ recordId: string
35
+ op: HistoryOp
36
+ userId?: string
37
+ snapshot: Record<string, unknown>
38
+ snapshotBefore?: Record<string, unknown>
39
+ }): Promise<HistoryEntry> {
40
+ const row = await this.delegate.create({
41
+ data: {
42
+ id: uuidv7(),
43
+ resourceId: input.resourceId,
44
+ recordId: input.recordId,
45
+ op: input.op,
46
+ userId: input.userId ?? null,
47
+ snapshot: input.snapshot,
48
+ snapshotBefore: input.snapshotBefore ?? null,
49
+ },
50
+ })
51
+ return rowToEntry(row)
52
+ }
53
+
54
+ async list(
55
+ resourceId: string,
56
+ recordId: string,
57
+ options: { limit?: number; offset?: number } = {},
58
+ ): Promise<HistoryEntry[]> {
59
+ const limit = options.limit ?? 50
60
+ const offset = options.offset ?? 0
61
+ const rows = await this.delegate.findMany({
62
+ where: { resourceId, recordId },
63
+ orderBy: { createdAt: 'desc' },
64
+ take: limit,
65
+ ...(offset > 0 ? { skip: offset } : {}),
66
+ })
67
+ return rows.map(rowToEntry)
68
+ }
69
+
70
+ async get(resourceId: string, recordId: string, revisionId: string): Promise<HistoryEntry | null> {
71
+ const row = await this.delegate.findFirst({
72
+ where: { id: revisionId, resourceId, recordId },
73
+ })
74
+ return row ? rowToEntry(row) : null
75
+ }
76
+
77
+ async latest(resourceId: string, recordId: string): Promise<HistoryEntry | null> {
78
+ const row = await this.delegate.findFirst({
79
+ where: { resourceId, recordId },
80
+ orderBy: { createdAt: 'desc' },
81
+ })
82
+ return row ? rowToEntry(row) : null
83
+ }
84
+ }
@@ -0,0 +1,77 @@
1
+ import { uuidv7, type ActionLogEntry, type ILogStore, type IQueryableLogStore } from '@modern-admin/core'
2
+ import type { PrismaDelegate } from '../types.js'
3
+
4
+ interface LogRow {
5
+ id: string
6
+ resourceId: string
7
+ action: string
8
+ recordId: string | null
9
+ recordIds: unknown
10
+ userId: string | null
11
+ payload: unknown
12
+ result: unknown
13
+ at: bigint
14
+ createdAt: Date
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 PrismaLogStore implements IQueryableLogStore {
34
+ constructor(private readonly delegate: PrismaDelegate<LogRow>) {}
35
+
36
+ async record(entry: ActionLogEntry): Promise<void> {
37
+ await this.delegate.create({
38
+ data: {
39
+ // Prefer the writer-supplied id (UUID v7 from `actionLoggingPlugin`)
40
+ // so React lists keyed on `entry.id` line up with the persisted row.
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: BigInt(entry.at),
50
+ },
51
+ })
52
+ }
53
+
54
+ async list(filter: Parameters<IQueryableLogStore['list']>[0] = {}): Promise<ActionLogEntry[]> {
55
+ const where: Record<string, unknown> = {}
56
+ if (filter.resourceId) where['resourceId'] = filter.resourceId
57
+ if (filter.recordId) where['recordId'] = filter.recordId
58
+ if (filter.userId) where['userId'] = filter.userId
59
+ if (filter.actions?.length) where['action'] = { in: filter.actions }
60
+ if (filter.from || filter.to) {
61
+ const range: Record<string, bigint> = {}
62
+ if (filter.from) range['gte'] = BigInt(filter.from.getTime())
63
+ if (filter.to) range['lte'] = BigInt(filter.to.getTime())
64
+ where['at'] = range
65
+ }
66
+ const rows = await this.delegate.findMany({
67
+ where,
68
+ orderBy: { at: 'desc' },
69
+ ...(filter.limit !== undefined ? { take: filter.limit } : {}),
70
+ ...(filter.offset !== undefined ? { skip: filter.offset } : {}),
71
+ })
72
+ return rows.map(rowToEntry)
73
+ }
74
+ }
75
+
76
+ /** Narrow type-test: a pure `ILogStore` is enough where readback isn't needed. */
77
+ export type { ILogStore }
@@ -0,0 +1,150 @@
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 type { PrismaDelegate } from '../types.js'
10
+
11
+ interface WebhookRow {
12
+ id: string
13
+ name: string
14
+ url: string
15
+ events: unknown
16
+ resourceId: string | null
17
+ enabled: boolean
18
+ secret: string | null
19
+ headers: unknown
20
+ filters: unknown
21
+ payloadFields: unknown
22
+ createdAt: Date
23
+ updatedAt: Date
24
+ }
25
+
26
+ interface DeliveryRow {
27
+ id: string
28
+ webhookId: string
29
+ event: string
30
+ payload: unknown
31
+ status: string
32
+ responseStatus: number | null
33
+ responseBody: string | null
34
+ error: string | null
35
+ attempt: number
36
+ createdAt: Date
37
+ deliveredAt: Date | null
38
+ }
39
+
40
+ const rowToWebhook = (row: WebhookRow): Webhook => ({
41
+ id: row.id,
42
+ name: row.name,
43
+ url: row.url,
44
+ events: Array.isArray(row.events) ? (row.events as string[]) : [],
45
+ resourceId: row.resourceId,
46
+ enabled: row.enabled,
47
+ ...(row.secret !== null ? { secret: row.secret } : {}),
48
+ headers: (row.headers as Record<string, string>) ?? {},
49
+ filters: (row.filters as Record<string, string>) ?? {},
50
+ payloadFields: Array.isArray(row.payloadFields) ? (row.payloadFields as string[]) : [],
51
+ createdAt: row.createdAt.toISOString(),
52
+ updatedAt: row.updatedAt.toISOString(),
53
+ })
54
+
55
+ const rowToDelivery = (row: DeliveryRow): WebhookDelivery => ({
56
+ id: row.id,
57
+ webhookId: row.webhookId,
58
+ event: row.event,
59
+ payload: (row.payload as Record<string, unknown>) ?? {},
60
+ status: row.status as WebhookDeliveryStatus,
61
+ ...(row.responseStatus !== null ? { responseStatus: row.responseStatus } : {}),
62
+ ...(row.responseBody !== null ? { responseBody: row.responseBody } : {}),
63
+ ...(row.error !== null ? { error: row.error } : {}),
64
+ attempt: row.attempt,
65
+ createdAt: row.createdAt.toISOString(),
66
+ ...(row.deliveredAt !== null ? { deliveredAt: row.deliveredAt.toISOString() } : {}),
67
+ })
68
+
69
+ export class PrismaWebhookStore implements IWebhookStore {
70
+ constructor(
71
+ private readonly webhookDelegate: PrismaDelegate<WebhookRow>,
72
+ private readonly deliveryDelegate: PrismaDelegate<DeliveryRow>,
73
+ ) {}
74
+
75
+ async list(): Promise<Webhook[]> {
76
+ const rows = await this.webhookDelegate.findMany({ orderBy: { createdAt: 'desc' } })
77
+ return rows.map(rowToWebhook)
78
+ }
79
+
80
+ async get(id: string): Promise<Webhook | null> {
81
+ const row = await this.webhookDelegate.findUnique({ where: { id } })
82
+ return row ? rowToWebhook(row) : null
83
+ }
84
+
85
+ async create(input: WebhookInput): Promise<Webhook> {
86
+ const row = await this.webhookDelegate.create({
87
+ data: {
88
+ id: uuidv7(),
89
+ name: input.name,
90
+ url: input.url,
91
+ events: input.events,
92
+ resourceId: input.resourceId ?? null,
93
+ enabled: input.enabled ?? true,
94
+ secret: input.secret ?? null,
95
+ headers: input.headers ?? {},
96
+ filters: input.filters ?? {},
97
+ payloadFields: input.payloadFields ?? [],
98
+ },
99
+ })
100
+ return rowToWebhook(row)
101
+ }
102
+
103
+ async update(id: string, patch: Partial<WebhookInput>): Promise<Webhook> {
104
+ const data: Record<string, unknown> = {}
105
+ if (patch.name !== undefined) data['name'] = patch.name
106
+ if (patch.url !== undefined) data['url'] = patch.url
107
+ if (patch.events !== undefined) data['events'] = patch.events
108
+ if (patch.resourceId !== undefined) data['resourceId'] = patch.resourceId ?? null
109
+ if (patch.enabled !== undefined) data['enabled'] = patch.enabled
110
+ if (patch.secret !== undefined) data['secret'] = patch.secret
111
+ if (patch.headers !== undefined) data['headers'] = patch.headers
112
+ if (patch.filters !== undefined) data['filters'] = patch.filters
113
+ if (patch.payloadFields !== undefined) data['payloadFields'] = patch.payloadFields
114
+ const row = await this.webhookDelegate.update({ where: { id }, data })
115
+ return rowToWebhook(row)
116
+ }
117
+
118
+ async delete(id: string): Promise<void> {
119
+ await this.webhookDelegate.delete({ where: { id } })
120
+ }
121
+
122
+ async recordDelivery(
123
+ delivery: Omit<WebhookDelivery, 'id' | 'createdAt'>,
124
+ ): Promise<WebhookDelivery> {
125
+ const row = await this.deliveryDelegate.create({
126
+ data: {
127
+ id: uuidv7(),
128
+ webhookId: delivery.webhookId,
129
+ event: delivery.event,
130
+ payload: delivery.payload,
131
+ status: delivery.status,
132
+ responseStatus: delivery.responseStatus ?? null,
133
+ responseBody: delivery.responseBody ?? null,
134
+ error: delivery.error ?? null,
135
+ attempt: delivery.attempt,
136
+ deliveredAt: delivery.deliveredAt ? new Date(delivery.deliveredAt) : null,
137
+ },
138
+ })
139
+ return rowToDelivery(row)
140
+ }
141
+
142
+ async listDeliveries(webhookId: string, limit = 50): Promise<WebhookDelivery[]> {
143
+ const rows = await this.deliveryDelegate.findMany({
144
+ where: { webhookId },
145
+ orderBy: { createdAt: 'desc' },
146
+ take: limit,
147
+ })
148
+ return rows.map(rowToDelivery)
149
+ }
150
+ }
package/src/types.ts ADDED
@@ -0,0 +1,70 @@
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
+
11
+
12
+
13
+ export interface PrismaDelegate<TRow = any> {
14
+ findMany(args?: any): Promise<TRow[]>
15
+ findUnique(args: { where: any }): Promise<TRow | null>
16
+ findFirst(args?: any): Promise<TRow | null>
17
+ create(args: { data: any }): Promise<TRow>
18
+ update(args: { where: any; data: any }): Promise<TRow>
19
+ upsert(args: { where: any; update: any; create: any }): Promise<TRow>
20
+ delete(args: { where: any }): Promise<TRow>
21
+ deleteMany(args?: { where?: any }): Promise<{ count: number }>
22
+ count(args?: any): Promise<number>
23
+ }
24
+
25
+ /**
26
+ * Whatever `prisma` instance the host injects. We index into it by model
27
+ * name (default: `maLog`, `maWebhook`, …); see `setupPrismaSystem`'s
28
+ * `models` option for renames.
29
+ *
30
+ * Uses `{ [K: string]: any }` (not `Record<string, PrismaDelegate>`) so that
31
+ * the generated `PrismaClient` — which carries utility methods like
32
+ * `$connect`, `$disconnect`, `$transaction` that are NOT `PrismaDelegate` —
33
+ * is directly assignable without a cast. TypeScript only waives the
34
+ * "missing index signature" check when the index value type is `any`; any
35
+ * other type (including `unknown`) still rejects `PrismaClient`. The shape
36
+ * of each delegate is validated at runtime in `resolveDelegate()`.
37
+ */
38
+
39
+ export type PrismaLike = { [K: string]: any }
40
+
41
+ export const DEFAULT_MODELS = {
42
+ log: 'maLog',
43
+ webhook: 'maWebhook',
44
+ webhookDelivery: 'maWebhookDelivery',
45
+ config: 'maConfig',
46
+ history: 'maHistory',
47
+ aiTask: 'maAiTask',
48
+ aiTaskEvent: 'maAiTaskEvent',
49
+ cache: 'maCache',
50
+ } as const
51
+
52
+ export type ModelKey = keyof typeof DEFAULT_MODELS
53
+ export type ModelOverrides = Partial<Record<ModelKey, string>>
54
+
55
+ export function resolveDelegate(
56
+ prisma: PrismaLike,
57
+ key: ModelKey,
58
+ overrides: ModelOverrides | undefined,
59
+ ): PrismaDelegate {
60
+ const name = overrides?.[key] ?? DEFAULT_MODELS[key]
61
+ const delegate = prisma[name] as PrismaDelegate | undefined
62
+ if (!delegate || typeof delegate.findMany !== 'function') {
63
+ throw new Error(
64
+ `[modern-admin/system-prisma] missing delegate "prisma.${name}". ` +
65
+ `Make sure the Modern Admin schema fragment is included in your schema.prisma ` +
66
+ `(see @modern-admin/system-prisma/schema), and that the Prisma client has been generated.`,
67
+ )
68
+ }
69
+ return delegate
70
+ }