@mosaic-code/prisma-deadlock-avoidance-tests 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 (43) hide show
  1. package/LICENSE +207 -0
  2. package/README.md +248 -0
  3. package/dist/assertions/row-assertion.d.ts +18 -0
  4. package/dist/assertions/row-assertion.d.ts.map +1 -0
  5. package/dist/assertions/row-assertion.js +53 -0
  6. package/dist/assertions/row-assertion.js.map +1 -0
  7. package/dist/assertions/table-assertion.d.ts +17 -0
  8. package/dist/assertions/table-assertion.d.ts.map +1 -0
  9. package/dist/assertions/table-assertion.js +33 -0
  10. package/dist/assertions/table-assertion.js.map +1 -0
  11. package/dist/extension.d.ts +60 -0
  12. package/dist/extension.d.ts.map +1 -0
  13. package/dist/extension.js +322 -0
  14. package/dist/extension.js.map +1 -0
  15. package/dist/graphs/row-graph.d.ts +61 -0
  16. package/dist/graphs/row-graph.d.ts.map +1 -0
  17. package/dist/graphs/row-graph.js +231 -0
  18. package/dist/graphs/row-graph.js.map +1 -0
  19. package/dist/graphs/table-graph.d.ts +61 -0
  20. package/dist/graphs/table-graph.d.ts.map +1 -0
  21. package/dist/graphs/table-graph.js +123 -0
  22. package/dist/graphs/table-graph.js.map +1 -0
  23. package/dist/index.d.ts +6 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +8 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/types.d.ts +98 -0
  28. package/dist/types.d.ts.map +1 -0
  29. package/dist/types.js +2 -0
  30. package/dist/types.js.map +1 -0
  31. package/dist/utils/caller-extractor.d.ts +27 -0
  32. package/dist/utils/caller-extractor.d.ts.map +1 -0
  33. package/dist/utils/caller-extractor.js +144 -0
  34. package/dist/utils/caller-extractor.js.map +1 -0
  35. package/dist/utils/primary-key-extractor.d.ts +23 -0
  36. package/dist/utils/primary-key-extractor.d.ts.map +1 -0
  37. package/dist/utils/primary-key-extractor.js +128 -0
  38. package/dist/utils/primary-key-extractor.js.map +1 -0
  39. package/dist/utils/raw-query-parser.d.ts +17 -0
  40. package/dist/utils/raw-query-parser.d.ts.map +1 -0
  41. package/dist/utils/raw-query-parser.js +58 -0
  42. package/dist/utils/raw-query-parser.js.map +1 -0
  43. package/package.json +79 -0
@@ -0,0 +1,61 @@
1
+ import { Graph } from '@dagrejs/graphlib';
2
+ import type { CallerInfo, TableEdgeLabel, TableCycleInfo } from '../types.js';
3
+ /**
4
+ * Manages a directed graph of table lock ordering.
5
+ * Nodes represent tables, edges represent "table A was locked before table B".
6
+ */
7
+ export declare class TableLockGraph {
8
+ private graph;
9
+ constructor();
10
+ /**
11
+ * Add an edge indicating that `from` table was locked before `to` table.
12
+ * Deduplicates callers by file:line.
13
+ */
14
+ addEdge(from: string, to: string, caller: CallerInfo): void;
15
+ /**
16
+ * Merge edges from another graph into this one.
17
+ * Used to merge transaction graphs into the global graph.
18
+ */
19
+ mergeFrom(other: TableLockGraph): void;
20
+ /**
21
+ * Check if the graph has any cycles
22
+ */
23
+ hasCycles(): boolean;
24
+ /**
25
+ * Find all cycles in the graph
26
+ */
27
+ findCycles(): string[][];
28
+ /**
29
+ * Find cycles and build detailed cycle info including callers
30
+ */
31
+ findCyclesWithCallers(): TableCycleInfo[];
32
+ /**
33
+ * Build detailed cycle info for a cycle
34
+ */
35
+ private buildCycleInfo;
36
+ /**
37
+ * Get the edge label between two tables
38
+ */
39
+ getEdge(from: string, to: string): TableEdgeLabel | undefined;
40
+ /**
41
+ * Get all tables in the graph
42
+ */
43
+ getTables(): string[];
44
+ /**
45
+ * Get all edges in the graph
46
+ */
47
+ getEdges(): Array<{
48
+ from: string;
49
+ to: string;
50
+ label: TableEdgeLabel;
51
+ }>;
52
+ /**
53
+ * Clear all data from the graph
54
+ */
55
+ reset(): void;
56
+ /**
57
+ * Get the underlying graphlib Graph (for testing)
58
+ */
59
+ getUnderlyingGraph(): Graph;
60
+ }
61
+ //# sourceMappingURL=table-graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-graph.d.ts","sourceRoot":"","sources":["../../src/graphs/table-graph.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAO,MAAM,mBAAmB,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAG7E;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAO;;IAMpB;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IAsB3D;;;OAGG;IACH,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAStC;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,UAAU,IAAI,MAAM,EAAE,EAAE;IAIxB;;OAEG;IACH,qBAAqB,IAAI,cAAc,EAAE;IAKzC;;OAEG;IACH,OAAO,CAAC,cAAc;IAwBtB;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI7D;;OAEG;IACH,SAAS,IAAI,MAAM,EAAE;IAIrB;;OAEG;IACH,QAAQ,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,cAAc,CAAA;KAAE,CAAC;IAQtE;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,kBAAkB,IAAI,KAAK;CAG5B"}
@@ -0,0 +1,123 @@
1
+ import { Graph, alg } from '@dagrejs/graphlib';
2
+ import { addCallerIfUnique } from '../utils/caller-extractor.js';
3
+ /**
4
+ * Manages a directed graph of table lock ordering.
5
+ * Nodes represent tables, edges represent "table A was locked before table B".
6
+ */
7
+ export class TableLockGraph {
8
+ graph;
9
+ constructor() {
10
+ this.graph = new Graph({ directed: true });
11
+ }
12
+ /**
13
+ * Add an edge indicating that `from` table was locked before `to` table.
14
+ * Deduplicates callers by file:line.
15
+ */
16
+ addEdge(from, to, caller) {
17
+ // Ensure nodes exist
18
+ if (!this.graph.hasNode(from)) {
19
+ this.graph.setNode(from);
20
+ }
21
+ if (!this.graph.hasNode(to)) {
22
+ this.graph.setNode(to);
23
+ }
24
+ // Get or create edge label
25
+ const existingLabel = this.graph.edge(from, to);
26
+ if (existingLabel) {
27
+ // Deduplicate callers by file:line
28
+ addCallerIfUnique(existingLabel.callers, caller);
29
+ }
30
+ else {
31
+ this.graph.setEdge(from, to, { callers: [caller] });
32
+ }
33
+ }
34
+ /**
35
+ * Merge edges from another graph into this one.
36
+ * Used to merge transaction graphs into the global graph.
37
+ */
38
+ mergeFrom(other) {
39
+ for (const edge of other.graph.edges()) {
40
+ const label = other.graph.edge(edge);
41
+ for (const caller of label.callers) {
42
+ this.addEdge(edge.v, edge.w, caller);
43
+ }
44
+ }
45
+ }
46
+ /**
47
+ * Check if the graph has any cycles
48
+ */
49
+ hasCycles() {
50
+ return !alg.isAcyclic(this.graph);
51
+ }
52
+ /**
53
+ * Find all cycles in the graph
54
+ */
55
+ findCycles() {
56
+ return alg.findCycles(this.graph);
57
+ }
58
+ /**
59
+ * Find cycles and build detailed cycle info including callers
60
+ */
61
+ findCyclesWithCallers() {
62
+ const cycles = this.findCycles();
63
+ return cycles.map((cycle) => this.buildCycleInfo(cycle));
64
+ }
65
+ /**
66
+ * Build detailed cycle info for a cycle
67
+ */
68
+ buildCycleInfo(cycle) {
69
+ const callers = [];
70
+ // For each consecutive pair of tables in the cycle, find the edge callers
71
+ for (let i = 0; i < cycle.length; i++) {
72
+ const from = cycle[i];
73
+ const to = cycle[(i + 1) % cycle.length];
74
+ const label = this.graph.edge(from, to);
75
+ if (label && label.callers.length > 0) {
76
+ // Add the first caller for this edge
77
+ callers.push({
78
+ table: to,
79
+ caller: label.callers[0],
80
+ });
81
+ }
82
+ }
83
+ return {
84
+ tables: cycle,
85
+ callers,
86
+ };
87
+ }
88
+ /**
89
+ * Get the edge label between two tables
90
+ */
91
+ getEdge(from, to) {
92
+ return this.graph.edge(from, to);
93
+ }
94
+ /**
95
+ * Get all tables in the graph
96
+ */
97
+ getTables() {
98
+ return this.graph.nodes();
99
+ }
100
+ /**
101
+ * Get all edges in the graph
102
+ */
103
+ getEdges() {
104
+ return this.graph.edges().map((e) => ({
105
+ from: e.v,
106
+ to: e.w,
107
+ label: this.graph.edge(e),
108
+ }));
109
+ }
110
+ /**
111
+ * Clear all data from the graph
112
+ */
113
+ reset() {
114
+ this.graph = new Graph({ directed: true });
115
+ }
116
+ /**
117
+ * Get the underlying graphlib Graph (for testing)
118
+ */
119
+ getUnderlyingGraph() {
120
+ return this.graph;
121
+ }
122
+ }
123
+ //# sourceMappingURL=table-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-graph.js","sourceRoot":"","sources":["../../src/graphs/table-graph.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { withDeadlockDetection } from './extension.js';
2
+ export { assertConsistentTableLocking, assertConsistentRowLocking, assertNoDeadlockRisk, resetDeadlockDetection, trackForUpdate, } from './extension.js';
3
+ export { TableLockingAssertionError } from './assertions/table-assertion.js';
4
+ export { RowLockingAssertionError } from './assertions/row-assertion.js';
5
+ export type { CallerInfo, StrictMode, RowLockingOptions, TableCycleInfo, RowCycleInfo, DeadlockDetectionConfig, } from './types.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAGtD,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,cAAc,GACf,MAAM,gBAAgB,CAAA;AAGvB,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAA;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAGxE,YAAY,EACV,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,uBAAuB,GACxB,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // Main extension
2
+ export { withDeadlockDetection } from './extension.js';
3
+ // Assertion functions
4
+ export { assertConsistentTableLocking, assertConsistentRowLocking, assertNoDeadlockRisk, resetDeadlockDetection, trackForUpdate, } from './extension.js';
5
+ // Error types
6
+ export { TableLockingAssertionError } from './assertions/table-assertion.js';
7
+ export { RowLockingAssertionError } from './assertions/row-assertion.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iBAAiB;AACjB,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAEtD,sBAAsB;AACtB,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,cAAc,GACf,MAAM,gBAAgB,CAAA;AAEvB,cAAc;AACd,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAA;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Information about where a locking operation was called from
3
+ */
4
+ export interface CallerInfo {
5
+ file: string;
6
+ line: number;
7
+ column: number;
8
+ functionName?: string;
9
+ }
10
+ /**
11
+ * Operations that acquire locks on database records
12
+ */
13
+ export type LockingOperation = 'findUniqueForUpdate' | 'findFirstForUpdate' | 'findManyForUpdate' | 'update' | 'updateMany' | 'delete' | 'deleteMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'upsert' | '$queryRaw' | '$queryRawUnsafe' | '$executeRaw' | '$executeRawUnsafe';
14
+ /**
15
+ * Strict ordering modes for row locking assertions
16
+ * - 'ASC': Primary keys must always be locked in ascending order
17
+ * - 'DESC': Primary keys must always be locked in descending order
18
+ * - true: Primary keys must be consistently ordered (either all ASC or all DESC)
19
+ * - false: No ordering requirement, only cycles are violations
20
+ */
21
+ export type StrictMode = 'ASC' | 'DESC' | true | false;
22
+ /**
23
+ * Options for assertConsistentRowLocking
24
+ */
25
+ export interface RowLockingOptions {
26
+ /** Specific tables to check. If empty/undefined, checks all tables. */
27
+ tables?: string[];
28
+ /** Ordering strictness mode. Defaults to false. */
29
+ strict?: StrictMode;
30
+ }
31
+ /**
32
+ * Information about a cycle detected in table locking order
33
+ */
34
+ export interface TableCycleInfo {
35
+ /** Tables involved in the cycle, in order */
36
+ tables: string[];
37
+ /** Callers that contributed to the cycle */
38
+ callers: Array<{
39
+ table: string;
40
+ caller: CallerInfo;
41
+ }>;
42
+ }
43
+ /**
44
+ * Information about a cycle or ordering violation in row locking
45
+ */
46
+ export interface RowCycleInfo {
47
+ /** The table where the violation occurred */
48
+ table: string;
49
+ /** Primary keys involved in the cycle/violation */
50
+ primaryKeys: Array<string | number>;
51
+ /** Callers that contributed to the issue */
52
+ callers: CallerInfo[];
53
+ /** Optional message describing the violation */
54
+ message?: string;
55
+ }
56
+ /**
57
+ * Label stored on table graph edges
58
+ */
59
+ export interface TableEdgeLabel {
60
+ /** All callers that created this edge (deduplicated by file:line) */
61
+ callers: CallerInfo[];
62
+ }
63
+ /**
64
+ * Label stored on row graph edges
65
+ */
66
+ export interface RowEdgeLabel {
67
+ /** All callers that created this edge */
68
+ callers: CallerInfo[];
69
+ /** The table this edge belongs to */
70
+ table: string;
71
+ }
72
+ /**
73
+ * Configuration options for the deadlock detection extension
74
+ */
75
+ export interface DeadlockDetectionConfig {
76
+ /** Whether detection is enabled. Defaults to true. */
77
+ enabled?: boolean;
78
+ }
79
+ /**
80
+ * Model metadata from Prisma DMMF
81
+ */
82
+ export interface ModelMeta {
83
+ name: string;
84
+ dbName: string | null;
85
+ fields: ModelField[];
86
+ }
87
+ /**
88
+ * Field metadata from Prisma DMMF
89
+ */
90
+ export interface ModelField {
91
+ name: string;
92
+ kind: 'scalar' | 'object' | 'enum' | 'unsupported';
93
+ type: string;
94
+ dbName: string | null;
95
+ isId: boolean;
96
+ isUnique: boolean;
97
+ }
98
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GACxB,qBAAqB,GACrB,oBAAoB,GACpB,mBAAmB,GACnB,QAAQ,GACR,YAAY,GACZ,QAAQ,GACR,YAAY,GACZ,QAAQ,GACR,YAAY,GACZ,qBAAqB,GACrB,QAAQ,GACR,WAAW,GACX,iBAAiB,GACjB,aAAa,GACb,mBAAmB,CAAA;AAEvB;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,GAAG,KAAK,CAAA;AAEtD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,mDAAmD;IACnD,MAAM,CAAC,EAAE,UAAU,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,4CAA4C;IAC5C,OAAO,EAAE,KAAK,CAAC;QACb,KAAK,EAAE,MAAM,CAAA;QACb,MAAM,EAAE,UAAU,CAAA;KACnB,CAAC,CAAA;CACH;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,mDAAmD;IACnD,WAAW,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAA;IACnC,4CAA4C;IAC5C,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,OAAO,EAAE,UAAU,EAAE,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,EAAE,UAAU,EAAE,CAAA;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,aAAa,CAAA;IAClD,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,EAAE,OAAO,CAAA;CAClB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,27 @@
1
+ import type { CallerInfo } from '../types.js';
2
+ /**
3
+ * Extract the first relevant caller from the current stack trace.
4
+ * Filters out node_modules, Prisma internals, and this library's code.
5
+ *
6
+ * @returns CallerInfo for the first non-library caller, or a fallback if none found
7
+ */
8
+ export declare function extractCaller(): CallerInfo;
9
+ /**
10
+ * Create a unique key for a caller based on file and line.
11
+ * Used for deduplication in graphs.
12
+ */
13
+ export declare function callerKey(caller: CallerInfo): string;
14
+ /**
15
+ * Add a caller to an array if it's not already present (deduplicated by file:line).
16
+ * Modifies the array in place.
17
+ *
18
+ * @param callers Array of callers to add to
19
+ * @param caller Caller to add if unique
20
+ */
21
+ export declare function addCallerIfUnique(callers: CallerInfo[], caller: CallerInfo): void;
22
+ /**
23
+ * Format a CallerInfo as a human-readable string.
24
+ * File paths are made relative to the current working directory.
25
+ */
26
+ export declare function formatCaller(caller: CallerInfo): string;
27
+ //# sourceMappingURL=caller-extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caller-extractor.d.ts","sourceRoot":"","sources":["../../src/utils/caller-extractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AA+F7C;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,UAAU,CAmB1C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAMjF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAOvD"}
@@ -0,0 +1,144 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { relative } from 'node:path';
3
+ /**
4
+ * Get the current working directory for relative path calculation
5
+ */
6
+ function getCwd() {
7
+ // Try to get from process.cwd() first, fall back to the URL of this module
8
+ try {
9
+ return process.cwd();
10
+ }
11
+ catch {
12
+ // Fallback: use this file's location
13
+ const moduleUrl = import.meta.url;
14
+ const modulePath = fileURLToPath(moduleUrl);
15
+ return modulePath.slice(0, modulePath.lastIndexOf('/'));
16
+ }
17
+ }
18
+ /**
19
+ * Make a file path relative to the current working directory if possible.
20
+ * Returns the original path if it cannot be made relative.
21
+ */
22
+ function makeRelativePath(filePath) {
23
+ if (filePath === 'unknown') {
24
+ return filePath;
25
+ }
26
+ // Don't try to make non-file paths relative
27
+ if (filePath.startsWith('node:') || filePath.startsWith('(node:')) {
28
+ return filePath;
29
+ }
30
+ try {
31
+ const cwd = getCwd();
32
+ return relative(cwd, filePath);
33
+ }
34
+ catch {
35
+ // If relative path calculation fails, return original
36
+ return filePath;
37
+ }
38
+ }
39
+ /**
40
+ * Patterns to filter out from stack traces.
41
+ * These represent library code that should not be considered the "caller"
42
+ */
43
+ const IGNORE_PATTERNS = [
44
+ /node_modules/,
45
+ /\.prisma[\\/]client/,
46
+ /prisma-deadlock-avoidance-tests[\\/](?:src|dist)[\\/]/,
47
+ /prisma-select-for-update[\\/](?:src|dist)[\\/]/,
48
+ /\(node:/,
49
+ /^node:/,
50
+ ];
51
+ /**
52
+ * Parse a V8 stack frame line and extract location info.
53
+ * Handles both formats:
54
+ * - " at functionName (file:line:column)"
55
+ * - " at file:line:column"
56
+ */
57
+ function parseStackFrame(line) {
58
+ // Try format: "at functionName (file:line:column)"
59
+ const withFnMatch = line.match(/at\s+(.+?)\s+\((.+):(\d+):(\d+)\)/);
60
+ if (withFnMatch) {
61
+ const [, functionName, file, lineStr, columnStr] = withFnMatch;
62
+ return {
63
+ file,
64
+ line: parseInt(lineStr, 10),
65
+ column: parseInt(columnStr, 10),
66
+ functionName: functionName || undefined,
67
+ };
68
+ }
69
+ // Try format: "at file:line:column"
70
+ const withoutFnMatch = line.match(/at\s+(.+):(\d+):(\d+)/);
71
+ if (withoutFnMatch) {
72
+ const [, file, lineStr, columnStr] = withoutFnMatch;
73
+ return {
74
+ file,
75
+ line: parseInt(lineStr, 10),
76
+ column: parseInt(columnStr, 10),
77
+ };
78
+ }
79
+ return null;
80
+ }
81
+ /**
82
+ * Check if a file path should be ignored
83
+ */
84
+ function shouldIgnore(file) {
85
+ return IGNORE_PATTERNS.some((pattern) => pattern.test(file));
86
+ }
87
+ /**
88
+ * Extract the first relevant caller from the current stack trace.
89
+ * Filters out node_modules, Prisma internals, and this library's code.
90
+ *
91
+ * @returns CallerInfo for the first non-library caller, or a fallback if none found
92
+ */
93
+ export function extractCaller() {
94
+ const stack = new Error().stack ?? '';
95
+ const lines = stack.split('\n').slice(1); // Skip "Error" line
96
+ for (const line of lines) {
97
+ const parsed = parseStackFrame(line);
98
+ if (!parsed)
99
+ continue;
100
+ if (!shouldIgnore(parsed.file)) {
101
+ return parsed;
102
+ }
103
+ }
104
+ // Fallback if no relevant frame found
105
+ return {
106
+ file: 'unknown',
107
+ line: 0,
108
+ column: 0,
109
+ };
110
+ }
111
+ /**
112
+ * Create a unique key for a caller based on file and line.
113
+ * Used for deduplication in graphs.
114
+ */
115
+ export function callerKey(caller) {
116
+ return `${caller.file}:${caller.line}`;
117
+ }
118
+ /**
119
+ * Add a caller to an array if it's not already present (deduplicated by file:line).
120
+ * Modifies the array in place.
121
+ *
122
+ * @param callers Array of callers to add to
123
+ * @param caller Caller to add if unique
124
+ */
125
+ export function addCallerIfUnique(callers, caller) {
126
+ const key = callerKey(caller);
127
+ const alreadyExists = callers.some((c) => callerKey(c) === key);
128
+ if (!alreadyExists) {
129
+ callers.push(caller);
130
+ }
131
+ }
132
+ /**
133
+ * Format a CallerInfo as a human-readable string.
134
+ * File paths are made relative to the current working directory.
135
+ */
136
+ export function formatCaller(caller) {
137
+ const relativeFile = makeRelativePath(caller.file);
138
+ const location = `${relativeFile}:${caller.line}:${caller.column}`;
139
+ if (caller.functionName) {
140
+ return `${caller.functionName} (${location})`;
141
+ }
142
+ return location;
143
+ }
144
+ //# sourceMappingURL=caller-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caller-extractor.js","sourceRoot":"","sources":["../../src/utils/caller-extractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { ModelMeta, ModelField } from '../types.js';
2
+ /**
3
+ * Get model metadata from Prisma DMMF
4
+ */
5
+ export declare function getModelMeta(modelName: string): ModelMeta | null;
6
+ /**
7
+ * Get the primary key field(s) for a model
8
+ */
9
+ export declare function getPrimaryKeyFields(model: ModelMeta): ModelField[];
10
+ /**
11
+ * Extract primary key value from a single result object
12
+ */
13
+ export declare function extractPrimaryKey(model: ModelMeta, result: Record<string, unknown>): string | null;
14
+ /**
15
+ * Extract primary keys from query results (array or single object)
16
+ */
17
+ export declare function extractPrimaryKeys(modelName: string, result: unknown): string[];
18
+ /**
19
+ * Extract primary keys from a raw result, handling both model field names
20
+ * and database column names
21
+ */
22
+ export declare function extractPrimaryKeysFromRaw(modelName: string, results: Record<string, unknown>[]): string[];
23
+ //# sourceMappingURL=primary-key-extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primary-key-extractor.d.ts","sourceRoot":"","sources":["../../src/utils/primary-key-extractor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExD;;GAEG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAsBhE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,EAAE,CAElE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,GAAG,IAAI,CA4Bf;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,OAAO,GACd,MAAM,EAAE,CAyBV;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GACjC,MAAM,EAAE,CA6CV"}
@@ -0,0 +1,128 @@
1
+ import { Prisma } from '@prisma/client';
2
+ /**
3
+ * Get model metadata from Prisma DMMF
4
+ */
5
+ export function getModelMeta(modelName) {
6
+ const dmmf = Prisma.dmmf;
7
+ const model = dmmf.datamodel.models.find((m) => m.name.toLowerCase() === modelName.toLowerCase());
8
+ if (!model) {
9
+ return null;
10
+ }
11
+ return {
12
+ name: model.name,
13
+ dbName: model.dbName ?? null,
14
+ fields: model.fields.map((f) => ({
15
+ name: f.name,
16
+ kind: f.kind,
17
+ type: f.type,
18
+ dbName: f.dbName ?? null,
19
+ isId: f.isId ?? false,
20
+ isUnique: f.isUnique ?? false,
21
+ })),
22
+ };
23
+ }
24
+ /**
25
+ * Get the primary key field(s) for a model
26
+ */
27
+ export function getPrimaryKeyFields(model) {
28
+ return model.fields.filter((f) => f.isId);
29
+ }
30
+ /**
31
+ * Extract primary key value from a single result object
32
+ */
33
+ export function extractPrimaryKey(model, result) {
34
+ const pkFields = getPrimaryKeyFields(model);
35
+ if (pkFields.length === 0) {
36
+ return null;
37
+ }
38
+ if (pkFields.length === 1) {
39
+ // Single primary key
40
+ const field = pkFields[0];
41
+ const value = result[field.name];
42
+ if (value !== undefined && value !== null) {
43
+ return String(value);
44
+ }
45
+ return null;
46
+ }
47
+ // Composite primary key - serialize as JSON
48
+ const pkValues = {};
49
+ for (const field of pkFields) {
50
+ const value = result[field.name];
51
+ if (value === undefined || value === null) {
52
+ return null;
53
+ }
54
+ pkValues[field.name] = value;
55
+ }
56
+ return JSON.stringify(pkValues);
57
+ }
58
+ /**
59
+ * Extract primary keys from query results (array or single object)
60
+ */
61
+ export function extractPrimaryKeys(modelName, result) {
62
+ const model = getModelMeta(modelName);
63
+ if (!model) {
64
+ return [];
65
+ }
66
+ const keys = [];
67
+ if (Array.isArray(result)) {
68
+ for (const item of result) {
69
+ if (item && typeof item === 'object') {
70
+ const pk = extractPrimaryKey(model, item);
71
+ if (pk !== null) {
72
+ keys.push(pk);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ else if (result && typeof result === 'object') {
78
+ const pk = extractPrimaryKey(model, result);
79
+ if (pk !== null) {
80
+ keys.push(pk);
81
+ }
82
+ }
83
+ return keys;
84
+ }
85
+ /**
86
+ * Extract primary keys from a raw result, handling both model field names
87
+ * and database column names
88
+ */
89
+ export function extractPrimaryKeysFromRaw(modelName, results) {
90
+ const model = getModelMeta(modelName);
91
+ if (!model) {
92
+ return [];
93
+ }
94
+ const pkFields = getPrimaryKeyFields(model);
95
+ if (pkFields.length === 0) {
96
+ return [];
97
+ }
98
+ const keys = [];
99
+ for (const result of results) {
100
+ if (pkFields.length === 1) {
101
+ const field = pkFields[0];
102
+ // Try model field name first, then db column name
103
+ const value = result[field.name] ?? (field.dbName ? result[field.dbName] : undefined);
104
+ if (value !== undefined && value !== null) {
105
+ keys.push(String(value));
106
+ }
107
+ }
108
+ else {
109
+ // Composite key
110
+ const pkValues = {};
111
+ let allFound = true;
112
+ for (const field of pkFields) {
113
+ const value = result[field.name] ??
114
+ (field.dbName ? result[field.dbName] : undefined);
115
+ if (value === undefined || value === null) {
116
+ allFound = false;
117
+ break;
118
+ }
119
+ pkValues[field.name] = value;
120
+ }
121
+ if (allFound) {
122
+ keys.push(JSON.stringify(pkValues));
123
+ }
124
+ }
125
+ }
126
+ return keys;
127
+ }
128
+ //# sourceMappingURL=primary-key-extractor.js.map