@modern-admin/system-prisma 0.7.0 → 0.9.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.
@@ -6,12 +6,15 @@ export declare class PrismaAiTaskStore implements IAiTaskStore {
6
6
  constructor(taskDelegate: PrismaDelegate<TaskRow>, eventDelegate: PrismaDelegate<EventRow>);
7
7
  enqueue(input: AiTaskInput): Promise<AiTask>;
8
8
  get(id: string): Promise<AiTask | null>;
9
+ claim(id: string): Promise<AiTask | null>;
10
+ getByIdempotencyKey(idempotencyKey: string): Promise<AiTask | null>;
9
11
  list(filter?: Parameters<IAiTaskStore['list']>[0]): Promise<AiTask[]>;
10
12
  updateStatus(id: string, patch: {
11
13
  status: AiTaskStatus;
12
14
  progress?: number | null;
13
15
  output?: Record<string, unknown>;
14
16
  error?: string;
17
+ expectedStatus?: AiTaskStatus[];
15
18
  }): Promise<AiTask>;
16
19
  appendEvent(taskId: string, type: string, data: Record<string, unknown>): Promise<AiTaskEvent>;
17
20
  events(taskId: string, sinceId?: string): Promise<AiTaskEvent[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"ai-task-store.d.ts","sourceRoot":"","sources":["../../src/stores/ai-task-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,OAAO,EACb,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAIjD,qBAAa,iBAAkB,YAAW,YAAY;IAElD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAFhC,YACmB,YAAY,EAAE,cAAc,CAAC,OAAO,CAAC,EACrC,aAAa,EAAE,cAAc,CAAC,QAAQ,CAAC,EACtD;IAEE,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAcjD;IAEK,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG5C;IAEK,IAAI,CAAC,MAAM,GAAE,UAAU,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAe9E;IAEK,YAAY,CAChB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE;QACL,MAAM,EAAE,YAAY,CAAA;QACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAChC,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,GACA,OAAO,CAAC,MAAM,CAAC,CAejB;IAEK,WAAW,CACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,WAAW,CAAC,CAKtB;IAEK,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAQrE;CACF"}
1
+ {"version":3,"file":"ai-task-store.d.ts","sourceRoot":"","sources":["../../src/stores/ai-task-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,OAAO,EACb,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAIjD,qBAAa,iBAAkB,YAAW,YAAY;IAElD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAFhC,YACmB,YAAY,EAAE,cAAc,CAAC,OAAO,CAAC,EACrC,aAAa,EAAE,cAAc,CAAC,QAAQ,CAAC,EACtD;IAEE,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBjD;IAEK,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG5C;IAEK,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAO9C;IAEK,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxE;IAEK,IAAI,CAAC,MAAM,GAAE,UAAU,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAiB9E;IAEK,YAAY,CAChB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE;QACL,MAAM,EAAE,YAAY,CAAA;QACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAChC,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,cAAc,CAAC,EAAE,YAAY,EAAE,CAAA;KAChC,GACA,OAAO,CAAC,MAAM,CAAC,CA2BjB;IAEK,WAAW,CACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,WAAW,CAAC,CAKtB;IAEK,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAQrE;CACF"}
@@ -6,28 +6,54 @@ export class PrismaAiTaskStore {
6
6
  this.eventDelegate = eventDelegate;
7
7
  }
8
8
  async enqueue(input) {
9
- const row = await this.taskDelegate.create({
10
- data: {
11
- id: uuidv7(),
12
- kind: input.kind,
13
- resourceId: input.resourceId ?? null,
14
- recordId: input.recordId ?? null,
15
- userId: input.userId ?? null,
16
- status: 'pending',
17
- input: input.input ?? {},
18
- progress: null,
19
- },
20
- });
21
- return rowToTask(row);
9
+ try {
10
+ const row = await this.taskDelegate.create({
11
+ data: {
12
+ id: uuidv7(),
13
+ idempotencyKey: input.idempotencyKey ?? null,
14
+ kind: input.kind,
15
+ resourceId: input.resourceId ?? null,
16
+ recordId: input.recordId ?? null,
17
+ userId: input.userId ?? null,
18
+ status: 'pending',
19
+ input: input.input ?? {},
20
+ progress: null,
21
+ },
22
+ });
23
+ return rowToTask(row);
24
+ }
25
+ catch (error) {
26
+ if (!input.idempotencyKey)
27
+ throw error;
28
+ const existing = await this.getByIdempotencyKey(input.idempotencyKey);
29
+ if (!existing)
30
+ throw error;
31
+ return existing;
32
+ }
22
33
  }
23
34
  async get(id) {
24
35
  const row = await this.taskDelegate.findUnique({ where: { id } });
25
36
  return row ? rowToTask(row) : null;
26
37
  }
38
+ async claim(id) {
39
+ const result = await this.taskDelegate.updateMany({
40
+ where: { id, status: 'pending' },
41
+ data: { status: 'running', progress: 5, startedAt: new Date() },
42
+ });
43
+ if (result.count === 0)
44
+ return null;
45
+ return this.get(id);
46
+ }
47
+ async getByIdempotencyKey(idempotencyKey) {
48
+ const row = await this.taskDelegate.findUnique({ where: { idempotencyKey } });
49
+ return row ? rowToTask(row) : null;
50
+ }
27
51
  async list(filter = {}) {
28
52
  const where = {};
29
53
  if (filter.kind)
30
54
  where['kind'] = filter.kind;
55
+ if (filter.idempotencyKey)
56
+ where['idempotencyKey'] = filter.idempotencyKey;
31
57
  if (filter.status) {
32
58
  const list = Array.isArray(filter.status) ? filter.status : [filter.status];
33
59
  where['status'] = { in: list };
@@ -36,6 +62,8 @@ export class PrismaAiTaskStore {
36
62
  where['userId'] = filter.userId;
37
63
  if (filter.resourceId)
38
64
  where['resourceId'] = filter.resourceId;
65
+ if (filter.createdAfter)
66
+ where['createdAt'] = { gte: new Date(filter.createdAfter) };
39
67
  const rows = await this.taskDelegate.findMany({
40
68
  where,
41
69
  orderBy: { createdAt: 'desc' },
@@ -60,6 +88,19 @@ export class PrismaAiTaskStore {
60
88
  if (TERMINAL.includes(patch.status)) {
61
89
  data['finishedAt'] = new Date();
62
90
  }
91
+ if (patch.expectedStatus) {
92
+ // Atomic guarded write: the WHERE clause makes the update a no-op unless
93
+ // the row is still in an expected status, so a concurrent cancel/finalize
94
+ // is never overwritten. Return the current row either way.
95
+ await this.taskDelegate.updateMany({
96
+ where: { id, status: { in: patch.expectedStatus } },
97
+ data,
98
+ });
99
+ const row = await this.taskDelegate.findUnique({ where: { id } });
100
+ if (!row)
101
+ throw new Error(`AI task not found: ${id}`);
102
+ return rowToTask(row);
103
+ }
63
104
  const row = await this.taskDelegate.update({ where: { id }, data });
64
105
  return rowToTask(row);
65
106
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ai-task-store.js","sourceRoot":"","sources":["../../src/stores/ai-task-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ai-task-store.js","sourceRoot":"","sources":["../../src/stores/ai-task-store.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,SAAS,EACT,MAAM,GAQP,MAAM,oBAAoB,CAAA;AAG3B,MAAM,QAAQ,GAAmB,CAAC,WAAW,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAA;AAErE,MAAM,OAAO,iBAAiB;IAC5B,YACmB,YAAqC,EACrC,aAAuC;4BADvC,YAAY;6BACZ,aAAa;IAC7B,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,KAAkB;QAC9B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;gBACzC,IAAI,EAAE;oBACJ,EAAE,EAAE,MAAM,EAAE;oBACZ,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,IAAI;oBAC5C,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,IAAI;oBACpC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,IAAI;oBAChC,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,IAAI;oBAC5B,MAAM,EAAE,SAAS;oBACjB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE;oBACxB,QAAQ,EAAE,IAAI;iBACf;aACF,CAAC,CAAA;YACF,OAAO,SAAS,CAAC,GAAG,CAAC,CAAA;QACvB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,cAAc;gBAAE,MAAM,KAAK,CAAA;YACtC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;YACrE,IAAI,CAAC,QAAQ;gBAAE,MAAM,KAAK,CAAA;YAC1B,OAAO,QAAQ,CAAA;QACjB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAU;QAClB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;QACjE,OAAO,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACpC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,EAAU;QACpB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;YAChD,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE;YAChC,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE;SAChE,CAAC,CAAA;QACF,IAAI,MAAM,CAAC,KAAK,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QACnC,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACrB,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,cAAsB;QAC9C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,cAAc,EAAE,EAAE,CAAC,CAAA;QAC7E,OAAO,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACpC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAM,GAAwC,EAAE;QACzD,MAAM,KAAK,GAA4B,EAAE,CAAA;QACzC,IAAI,MAAM,CAAC,IAAI;YAAE,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAA;QAC5C,IAAI,MAAM,CAAC,cAAc;YAAE,KAAK,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAAC,cAAc,CAAA;QAC1E,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YAC3E,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;QAChC,CAAC;QACD,IAAI,MAAM,CAAC,MAAM;YAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,MAAM,CAAA;QAClD,IAAI,MAAM,CAAC,UAAU;YAAE,KAAK,CAAC,YAAY,CAAC,GAAG,MAAM,CAAC,UAAU,CAAA;QAC9D,IAAI,MAAM,CAAC,YAAY;YAAE,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAA;QACpF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC;YAC5C,KAAK;YACL,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;YAC9B,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D,CAAC,CAAA;QACF,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAC5B,CAAC;IAED,KAAK,CAAC,YAAY,CAChB,EAAU,EACV,KAMC;QAED,MAAM,IAAI,GAA4B,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAA;QAC9D,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS;YAAE,IAAI,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAA;QACnE,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,MAAM,CAAA;QAC7D,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC,KAAK,CAAA;QAC1D,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC/B,6DAA6D;YAC7D,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;YACrE,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS;gBAAE,IAAI,CAAC,WAAW,CAAC,GAAG,IAAI,IAAI,EAAE,CAAA;QACnE,CAAC;QACD,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,IAAI,CAAC,YAAY,CAAC,GAAG,IAAI,IAAI,EAAE,CAAA;QACjC,CAAC;QACD,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC;YACzB,yEAAyE;YACzE,0EAA0E;YAC1E,2DAA2D;YAC3D,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;gBACjC,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,cAAc,EAAE,EAAE;gBACnD,IAAI;aACL,CAAC,CAAA;YACF,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;YACjE,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAA;YACrD,OAAO,SAAS,CAAC,GAAG,CAAC,CAAA;QACvB,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QACnE,OAAO,SAAS,CAAC,GAAG,CAAC,CAAA;IACvB,CAAC;IAED,KAAK,CAAC,WAAW,CACf,MAAc,EACd,IAAY,EACZ,IAA6B;QAE7B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;YAC1C,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE;SAC3C,CAAC,CAAA;QACF,OAAO,UAAU,CAAC,GAAG,CAAC,CAAA;IACxB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,OAAgB;QAC3C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC;YAC5C,KAAK,EAAE,EAAE,MAAM,EAAE;YACjB,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;SAC9B,CAAC,CAAA;QACF,IAAI,CAAC,OAAO;YAAE,OAAO,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;QACxC,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,CAAA;QAClD,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IAC7D,CAAC;CACF"}
@@ -1,10 +1,16 @@
1
- import { type ActionLogEntry, type ILogStore, type IQueryableLogStore, type LogRow } from '@modern-admin/core';
1
+ import { type ActionLogRetention, type ActionLogEntry, type ILogStore, type IQueryableLogStore, type LogRow } from '@modern-admin/core';
2
2
  import type { PrismaDelegate } from '../types.js';
3
3
  export declare class PrismaLogStore implements IQueryableLogStore {
4
4
  private readonly delegate;
5
5
  constructor(delegate: PrismaDelegate<LogRow>);
6
6
  record(entry: ActionLogEntry): Promise<void>;
7
7
  list(filter?: Parameters<IQueryableLogStore['list']>[0]): Promise<ActionLogEntry[]>;
8
+ /**
9
+ * Delete action-log entries outside the configured retention bounds.
10
+ * `keepLast` is global and deletions use an explicit id snapshot so a
11
+ * concurrent write can never be caught by a broad predicate.
12
+ */
13
+ prune(retention: ActionLogRetention): Promise<number>;
8
14
  }
9
15
  /** Narrow type-test: a pure `ILogStore` is enough where readback isn't needed. */
10
16
  export type { ILogStore };
@@ -1 +1 @@
1
- {"version":3,"file":"log-store.d.ts","sourceRoot":"","sources":["../../src/stores/log-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,MAAM,EACZ,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAKjD,qBAAa,cAAe,YAAW,kBAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAArC,YAA6B,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,EAAI;IAE3D,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBjD;IAEK,IAAI,CAAC,MAAM,GAAE,UAAU,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CA2C5F;CACF;AAED,kFAAkF;AAClF,YAAY,EAAE,SAAS,EAAE,CAAA"}
1
+ {"version":3,"file":"log-store.d.ts","sourceRoot":"","sources":["../../src/stores/log-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,MAAM,EACZ,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAMjD,qBAAa,cAAe,YAAW,kBAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAArC,YAA6B,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,EAAI;IAE3D,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBjD;IAEK,IAAI,CAAC,MAAM,GAAE,UAAU,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CA2C5F;IAED;;;;OAIG;IACG,KAAK,CAAC,SAAS,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B1D;CACF;AAED,kFAAkF;AAClF,YAAY,EAAE,SAAS,EAAE,CAAA"}
@@ -1,6 +1,7 @@
1
1
  import { rowToLogEntry, uuidv7, } from '@modern-admin/core';
2
2
  /** Applied when the caller passes no `limit`. Matches `MemoryLogStore`. */
3
3
  const DEFAULT_LIST_LIMIT = 50;
4
+ const PRUNE_BATCH_SIZE = 1_000;
4
5
  export class PrismaLogStore {
5
6
  constructor(delegate) {
6
7
  this.delegate = delegate;
@@ -74,5 +75,38 @@ export class PrismaLogStore {
74
75
  });
75
76
  return rows.map(rowToLogEntry);
76
77
  }
78
+ /**
79
+ * Delete action-log entries outside the configured retention bounds.
80
+ * `keepLast` is global and deletions use an explicit id snapshot so a
81
+ * concurrent write can never be caught by a broad predicate.
82
+ */
83
+ async prune(retention) {
84
+ let removed = 0;
85
+ if (retention.keepDays !== undefined) {
86
+ const cutoff = new Date(Date.now() - retention.keepDays * 24 * 60 * 60 * 1000);
87
+ const result = await this.delegate.deleteMany({
88
+ where: { createdAt: { lt: cutoff } },
89
+ });
90
+ removed += result.count;
91
+ }
92
+ if (retention.keepLast !== undefined) {
93
+ const keep = Math.max(0, Math.trunc(retention.keepLast));
94
+ while (true) {
95
+ const obsolete = (await this.delegate.findMany({
96
+ orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],
97
+ select: { id: true },
98
+ ...(keep > 0 ? { skip: keep } : {}),
99
+ take: PRUNE_BATCH_SIZE,
100
+ }));
101
+ if (obsolete.length === 0)
102
+ break;
103
+ const result = await this.delegate.deleteMany({
104
+ where: { id: { in: obsolete.map((row) => row.id) } },
105
+ });
106
+ removed += result.count;
107
+ }
108
+ }
109
+ return removed;
110
+ }
77
111
  }
78
112
  //# sourceMappingURL=log-store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"log-store.js","sourceRoot":"","sources":["../../src/stores/log-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"log-store.js","sourceRoot":"","sources":["../../src/stores/log-store.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -33,6 +33,12 @@ export interface PrismaDelegate<TRow = any> {
33
33
  }): Promise<{
34
34
  count: number;
35
35
  }>;
36
+ updateMany(args: {
37
+ where?: any;
38
+ data: any;
39
+ }): Promise<{
40
+ count: number;
41
+ }>;
36
42
  count(args?: any): Promise<number>;
37
43
  /**
38
44
  * Present on every generated Prisma delegate. Optional here so hand-written
@@ -1 +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;IAClC;;;OAGG;IACH,OAAO,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;CACpC;AAED;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,UAAU,GAAG;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CAAA;AAE7C,eAAO,MAAM,cAAc;aACzB,GAAG,EAAE,OAAO;aACZ,OAAO,EAAE,WAAW;aACpB,eAAe,EAAE,mBAAmB;aACpC,MAAM,EAAE,UAAU;aAClB,OAAO,EAAE,WAAW;aACpB,MAAM,EAAE,UAAU;aAClB,WAAW,EAAE,eAAe;aAC5B,KAAK,EAAE,SAAS;CACR,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"}
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,UAAU,CAAC,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,GAAG,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACxE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAClC;;;OAGG;IACH,OAAO,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;CACpC;AAED;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,UAAU,GAAG;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CAAA;AAE7C,eAAO,MAAM,cAAc;aACzB,GAAG,EAAE,OAAO;aACZ,OAAO,EAAE,WAAW;aACpB,eAAe,EAAE,mBAAmB;aACpC,MAAM,EAAE,UAAU;aAClB,OAAO,EAAE,WAAW;aACpB,MAAM,EAAE,UAAU;aAClB,WAAW,EAAE,eAAe;aAC5B,KAAK,EAAE,SAAS;CACR,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.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAqCH,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"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAsCH,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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modern-admin/system-prisma",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Prisma-backed implementation of Modern Admin system stores (logs, history, webhooks, AI tasks).",
5
5
  "homepage": "https://github.com/modern-admin/modern-admin/tree/main/packages/system-prisma#readme",
6
6
  "keywords": [
@@ -45,7 +45,7 @@
45
45
  }
46
46
  },
47
47
  "dependencies": {
48
- "@modern-admin/core": "^0.7.0"
48
+ "@modern-admin/core": "^0.9.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@modern-admin/tsconfig": "0.4.0",
@@ -291,7 +291,8 @@ model MaHistory {
291
291
  }
292
292
 
293
293
  model MaAiTask {
294
- id String @id @default(uuid())
294
+ id String @id @default(uuid())
295
+ idempotencyKey String? @unique
295
296
  kind String
296
297
  resourceId String?
297
298
  recordId String?
@@ -21,19 +21,27 @@ export class PrismaAiTaskStore implements IAiTaskStore {
21
21
  ) {}
22
22
 
23
23
  async enqueue(input: AiTaskInput): Promise<AiTask> {
24
- const row = await this.taskDelegate.create({
25
- data: {
26
- id: uuidv7(),
27
- kind: input.kind,
28
- resourceId: input.resourceId ?? null,
29
- recordId: input.recordId ?? null,
30
- userId: input.userId ?? null,
31
- status: 'pending',
32
- input: input.input ?? {},
33
- progress: null,
34
- },
35
- })
36
- return rowToTask(row)
24
+ try {
25
+ const row = await this.taskDelegate.create({
26
+ data: {
27
+ id: uuidv7(),
28
+ idempotencyKey: input.idempotencyKey ?? null,
29
+ kind: input.kind,
30
+ resourceId: input.resourceId ?? null,
31
+ recordId: input.recordId ?? null,
32
+ userId: input.userId ?? null,
33
+ status: 'pending',
34
+ input: input.input ?? {},
35
+ progress: null,
36
+ },
37
+ })
38
+ return rowToTask(row)
39
+ } catch (error) {
40
+ if (!input.idempotencyKey) throw error
41
+ const existing = await this.getByIdempotencyKey(input.idempotencyKey)
42
+ if (!existing) throw error
43
+ return existing
44
+ }
37
45
  }
38
46
 
39
47
  async get(id: string): Promise<AiTask | null> {
@@ -41,15 +49,31 @@ export class PrismaAiTaskStore implements IAiTaskStore {
41
49
  return row ? rowToTask(row) : null
42
50
  }
43
51
 
52
+ async claim(id: string): Promise<AiTask | null> {
53
+ const result = await this.taskDelegate.updateMany({
54
+ where: { id, status: 'pending' },
55
+ data: { status: 'running', progress: 5, startedAt: new Date() },
56
+ })
57
+ if (result.count === 0) return null
58
+ return this.get(id)
59
+ }
60
+
61
+ async getByIdempotencyKey(idempotencyKey: string): Promise<AiTask | null> {
62
+ const row = await this.taskDelegate.findUnique({ where: { idempotencyKey } })
63
+ return row ? rowToTask(row) : null
64
+ }
65
+
44
66
  async list(filter: Parameters<IAiTaskStore['list']>[0] = {}): Promise<AiTask[]> {
45
67
  const where: Record<string, unknown> = {}
46
68
  if (filter.kind) where['kind'] = filter.kind
69
+ if (filter.idempotencyKey) where['idempotencyKey'] = filter.idempotencyKey
47
70
  if (filter.status) {
48
71
  const list = Array.isArray(filter.status) ? filter.status : [filter.status]
49
72
  where['status'] = { in: list }
50
73
  }
51
74
  if (filter.userId) where['userId'] = filter.userId
52
75
  if (filter.resourceId) where['resourceId'] = filter.resourceId
76
+ if (filter.createdAfter) where['createdAt'] = { gte: new Date(filter.createdAfter) }
53
77
  const rows = await this.taskDelegate.findMany({
54
78
  where,
55
79
  orderBy: { createdAt: 'desc' },
@@ -65,6 +89,7 @@ export class PrismaAiTaskStore implements IAiTaskStore {
65
89
  progress?: number | null
66
90
  output?: Record<string, unknown>
67
91
  error?: string
92
+ expectedStatus?: AiTaskStatus[]
68
93
  },
69
94
  ): Promise<AiTask> {
70
95
  const data: Record<string, unknown> = { status: patch.status }
@@ -79,6 +104,18 @@ export class PrismaAiTaskStore implements IAiTaskStore {
79
104
  if (TERMINAL.includes(patch.status)) {
80
105
  data['finishedAt'] = new Date()
81
106
  }
107
+ if (patch.expectedStatus) {
108
+ // Atomic guarded write: the WHERE clause makes the update a no-op unless
109
+ // the row is still in an expected status, so a concurrent cancel/finalize
110
+ // is never overwritten. Return the current row either way.
111
+ await this.taskDelegate.updateMany({
112
+ where: { id, status: { in: patch.expectedStatus } },
113
+ data,
114
+ })
115
+ const row = await this.taskDelegate.findUnique({ where: { id } })
116
+ if (!row) throw new Error(`AI task not found: ${id}`)
117
+ return rowToTask(row)
118
+ }
82
119
  const row = await this.taskDelegate.update({ where: { id }, data })
83
120
  return rowToTask(row)
84
121
  }
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  rowToLogEntry,
3
3
  uuidv7,
4
+ type ActionLogRetention,
4
5
  type ActionLogEntry,
5
6
  type ILogStore,
6
7
  type IQueryableLogStore,
@@ -10,6 +11,7 @@ import type { PrismaDelegate } from '../types.js'
10
11
 
11
12
  /** Applied when the caller passes no `limit`. Matches `MemoryLogStore`. */
12
13
  const DEFAULT_LIST_LIMIT = 50
14
+ const PRUNE_BATCH_SIZE = 1_000
13
15
 
14
16
  export class PrismaLogStore implements IQueryableLogStore {
15
17
  constructor(private readonly delegate: PrismaDelegate<LogRow>) {}
@@ -76,6 +78,43 @@ export class PrismaLogStore implements IQueryableLogStore {
76
78
  })
77
79
  return rows.map(rowToLogEntry)
78
80
  }
81
+
82
+ /**
83
+ * Delete action-log entries outside the configured retention bounds.
84
+ * `keepLast` is global and deletions use an explicit id snapshot so a
85
+ * concurrent write can never be caught by a broad predicate.
86
+ */
87
+ async prune(retention: ActionLogRetention): Promise<number> {
88
+ let removed = 0
89
+
90
+ if (retention.keepDays !== undefined) {
91
+ const cutoff = new Date(Date.now() - retention.keepDays * 24 * 60 * 60 * 1000)
92
+ const result = await this.delegate.deleteMany({
93
+ where: { createdAt: { lt: cutoff } },
94
+ })
95
+ removed += result.count
96
+ }
97
+
98
+ if (retention.keepLast !== undefined) {
99
+ const keep = Math.max(0, Math.trunc(retention.keepLast))
100
+ while (true) {
101
+ const obsolete = (await this.delegate.findMany({
102
+ orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],
103
+ select: { id: true },
104
+ ...(keep > 0 ? { skip: keep } : {}),
105
+ take: PRUNE_BATCH_SIZE,
106
+ })) as unknown as Array<{ id: string }>
107
+ if (obsolete.length === 0) break
108
+
109
+ const result = await this.delegate.deleteMany({
110
+ where: { id: { in: obsolete.map((row) => row.id) } },
111
+ })
112
+ removed += result.count
113
+ }
114
+ }
115
+
116
+ return removed
117
+ }
79
118
  }
80
119
 
81
120
  /** Narrow type-test: a pure `ILogStore` is enough where readback isn't needed. */
package/src/types.ts CHANGED
@@ -19,6 +19,7 @@ export interface PrismaDelegate<TRow = any> {
19
19
  upsert(args: { where: any; update: any; create: any }): Promise<TRow>
20
20
  delete(args: { where: any }): Promise<TRow>
21
21
  deleteMany(args?: { where?: any }): Promise<{ count: number }>
22
+ updateMany(args: { where?: any; data: any }): Promise<{ count: number }>
22
23
  count(args?: any): Promise<number>
23
24
  /**
24
25
  * Present on every generated Prisma delegate. Optional here so hand-written