orm-doctor 1.0.0 → 1.0.1
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.
- package/LICENSE +22 -0
- package/package.json +1 -1
- package/src/advanced.js +7 -7
- package/src/scanner.js +5 -5
- package/src/ui.js +11 -11
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Noctis Nova
|
|
4
|
+
Built and maintained by Noctis Nova — https://noctisnova.com
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/package.json
CHANGED
package/src/advanced.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
2
|
* advanced.js — orm-doctor
|
|
3
3
|
*
|
|
4
4
|
* The advanced query-analysis engine. These rules catch the database bugs that
|
|
@@ -128,7 +128,7 @@ export async function scanUnsafeRawQueries(projectPath) {
|
|
|
128
128
|
`\`${name}\` is called with a dynamically-built SQL string — this is a SQL injection hole. ` +
|
|
129
129
|
"Any user input in that string can run arbitrary SQL. Use a tagged-template `prisma.$queryRaw\\`...\\`` " +
|
|
130
130
|
"with ${} placeholders (auto-parameterised), or pass values as separate parameters to the Unsafe variant.",
|
|
131
|
-
docs: "https://noctisnova.com/
|
|
131
|
+
docs: "https://noctisnova.com/tools/orm-doctor/database-safety-and-performance",
|
|
132
132
|
penalty: PENALTY_UNSAFE_RAW,
|
|
133
133
|
});
|
|
134
134
|
}
|
|
@@ -179,7 +179,7 @@ export async function scanMassMutations(projectPath) {
|
|
|
179
179
|
`\`${name}\` has no \`where\` clause — it will ${name === "deleteMany" ? "DELETE every row" : "UPDATE every row"} in the table. ` +
|
|
180
180
|
"A single accidental call wipes or rewrites all your data. Always scope mass mutations with a `where` filter " +
|
|
181
181
|
"(use `where: {}` explicitly only if you truly mean the whole table, and guard it).",
|
|
182
|
-
docs: "https://noctisnova.com/
|
|
182
|
+
docs: "https://noctisnova.com/tools/orm-doctor/database-safety-and-performance",
|
|
183
183
|
penalty: PENALTY_MASS_MUTATION,
|
|
184
184
|
});
|
|
185
185
|
}
|
|
@@ -228,7 +228,7 @@ export async function scanMissingPagination(projectPath) {
|
|
|
228
228
|
"`findMany()` has no `take` or `cursor` — it loads the ENTIRE table into memory. " +
|
|
229
229
|
"That's fine with 10 rows and fatal with 1,000,000: the query slows linearly and can OOM the server. " +
|
|
230
230
|
"Add `take` (and `cursor`/`skip`) to paginate.",
|
|
231
|
-
docs: "https://noctisnova.com/
|
|
231
|
+
docs: "https://noctisnova.com/tools/orm-doctor/orm-performance-checklist",
|
|
232
232
|
penalty: PENALTY_MISSING_PAGINATION,
|
|
233
233
|
});
|
|
234
234
|
}
|
|
@@ -291,7 +291,7 @@ export async function scanPrismaSingleton(projectPath) {
|
|
|
291
291
|
line,
|
|
292
292
|
snippet: trimSnippet(lines[line - 1] ?? "new PrismaClient()"),
|
|
293
293
|
message,
|
|
294
|
-
docs: "https://noctisnova.com/
|
|
294
|
+
docs: "https://noctisnova.com/tools/orm-doctor/database-safety-and-performance",
|
|
295
295
|
penalty: PENALTY_PRISMA_SINGLETON,
|
|
296
296
|
});
|
|
297
297
|
}
|
|
@@ -352,7 +352,7 @@ export async function scanMissingTransaction(projectPath) {
|
|
|
352
352
|
`This function performs ${writes.length} write operations that aren't wrapped in a transaction. ` +
|
|
353
353
|
"If the process crashes between writes, your data is left half-updated (e.g. money debited but not credited). " +
|
|
354
354
|
"Wrap dependent writes in `prisma.$transaction([...])` (or the interactive `$transaction(async (tx) => {...})`) so they all commit or all roll back.",
|
|
355
|
-
docs: "https://noctisnova.com/
|
|
355
|
+
docs: "https://noctisnova.com/tools/orm-doctor/database-safety-and-performance",
|
|
356
356
|
penalty: PENALTY_MISSING_TX,
|
|
357
357
|
});
|
|
358
358
|
}
|
|
@@ -428,7 +428,7 @@ export async function scanMissingRelationActions(schemaPath) {
|
|
|
428
428
|
`Relation \`${block.name}.${item.name}\` has no \`onDelete\` action. The default (\`Restrict\`/\`SetNull\`) ` +
|
|
429
429
|
"is often not what you want — deleting a parent can either fail unexpectedly or silently orphan children. " +
|
|
430
430
|
"Set it explicitly: `onDelete: Cascade` (delete children) or `Restrict` (block) so the behaviour is intentional.",
|
|
431
|
-
docs: "https://noctisnova.com/
|
|
431
|
+
docs: "https://noctisnova.com/tools/orm-doctor/database-safety-and-performance",
|
|
432
432
|
penalty: PENALTY_RELATION_ACTION,
|
|
433
433
|
});
|
|
434
434
|
}
|
package/src/scanner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
2
|
* scanner.js
|
|
3
3
|
* AST-based static analyzer for ORM/database bottleneck detection.
|
|
4
4
|
* Uses ts-morph for TypeScript traversal and @mrleebo/prisma-ast for schema parsing.
|
|
@@ -525,7 +525,7 @@ export async function scanSeedFiles(projectPath) {
|
|
|
525
525
|
message:
|
|
526
526
|
"Hardcoded ID in seed data — conflicts on re-seed and breaks across environments. " +
|
|
527
527
|
"Let Prisma generate IDs with @default(cuid()) and store references in variables.",
|
|
528
|
-
docs: "https://noctisnova.com/
|
|
528
|
+
docs: "https://noctisnova.com/tools/orm-doctor/seed-best-practices",
|
|
529
529
|
penalty: SEED_HARDCODED_ID_PENALTY,
|
|
530
530
|
});
|
|
531
531
|
}
|
|
@@ -554,7 +554,7 @@ export async function scanSeedFiles(projectPath) {
|
|
|
554
554
|
"Seed file creates records without first clearing existing data. " +
|
|
555
555
|
"Re-running the seed (common in CI) will throw duplicate key errors. " +
|
|
556
556
|
"Add prisma.<model>.deleteMany({}) at the top of your seed in dependency order.",
|
|
557
|
-
docs: "https://noctisnova.com/
|
|
557
|
+
docs: "https://noctisnova.com/tools/orm-doctor/seed-best-practices",
|
|
558
558
|
penalty: SEED_NO_TRUNCATE_PENALTY,
|
|
559
559
|
});
|
|
560
560
|
}
|
|
@@ -576,7 +576,7 @@ export async function scanSeedFiles(projectPath) {
|
|
|
576
576
|
"Seed file does not call prisma.$disconnect(). " +
|
|
577
577
|
"The Node.js process will hang in CI until the connection times out. " +
|
|
578
578
|
"Wrap your seed in try/finally and call prisma.$disconnect() in the finally block.",
|
|
579
|
-
docs: "https://noctisnova.com/
|
|
579
|
+
docs: "https://noctisnova.com/tools/orm-doctor/seed-best-practices",
|
|
580
580
|
penalty: SEED_NO_DISCONNECT_PENALTY,
|
|
581
581
|
});
|
|
582
582
|
}
|
|
@@ -597,7 +597,7 @@ export async function scanSeedFiles(projectPath) {
|
|
|
597
597
|
message:
|
|
598
598
|
`Seed file contains ${totalCreates} create() calls — this may exceed CI timeout limits (typically 30s). ` +
|
|
599
599
|
"Use createMany() with a data array for bulk inserts, or split into chunked batches with Promise.all.",
|
|
600
|
-
docs: "https://noctisnova.com/
|
|
600
|
+
docs: "https://noctisnova.com/tools/orm-doctor/seed-best-practices",
|
|
601
601
|
penalty: SEED_LARGE_BATCH_PENALTY,
|
|
602
602
|
});
|
|
603
603
|
}
|
package/src/ui.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
2
|
* ui.js
|
|
3
3
|
* Terminal UI components for orm-doctor.
|
|
4
4
|
* Produces a react-doctor-style numbered issue list with severity badges,
|
|
@@ -55,7 +55,7 @@ const RULE_META = {
|
|
|
55
55
|
"`$queryRawUnsafe(`SELECT * FROM users WHERE email = '${email}'`)` lets an attacker pass " +
|
|
56
56
|
"`' OR '1'='1` and dump your whole users table. This is the #1 most-exploited web vulnerability.",
|
|
57
57
|
severity: "critical",
|
|
58
|
-
docs: "https://noctisnova.com/
|
|
58
|
+
docs: "https://noctisnova.com/tools/orm-doctor/database-safety-and-performance",
|
|
59
59
|
},
|
|
60
60
|
|
|
61
61
|
"mass-mutation": {
|
|
@@ -71,7 +71,7 @@ const RULE_META = {
|
|
|
71
71
|
"`prisma.user.deleteMany()` with no where deletes every user in the database. There's no undo. " +
|
|
72
72
|
"This has ended companies — a missing where on a delete is a data-loss incident waiting to happen.",
|
|
73
73
|
severity: "critical",
|
|
74
|
-
docs: "https://noctisnova.com/
|
|
74
|
+
docs: "https://noctisnova.com/tools/orm-doctor/database-safety-and-performance",
|
|
75
75
|
},
|
|
76
76
|
|
|
77
77
|
"missing-pagination": {
|
|
@@ -87,7 +87,7 @@ const RULE_META = {
|
|
|
87
87
|
"A dashboard that does `findMany()` on an `events` table is instant at launch and times out (or OOMs " +
|
|
88
88
|
"the server) a year later when that table has 5 million rows. Pagination would have kept it at 20ms.",
|
|
89
89
|
severity: "warning",
|
|
90
|
-
docs: "https://noctisnova.com/
|
|
90
|
+
docs: "https://noctisnova.com/tools/orm-doctor/orm-performance-checklist",
|
|
91
91
|
},
|
|
92
92
|
|
|
93
93
|
"prisma-singleton": {
|
|
@@ -104,7 +104,7 @@ const RULE_META = {
|
|
|
104
104
|
"In Next.js dev, every file save creates another PrismaClient — after a few minutes you hit " +
|
|
105
105
|
"'too many connections' and the whole app stops talking to the database until you restart.",
|
|
106
106
|
severity: "warning",
|
|
107
|
-
docs: "https://noctisnova.com/
|
|
107
|
+
docs: "https://noctisnova.com/tools/orm-doctor/database-safety-and-performance",
|
|
108
108
|
},
|
|
109
109
|
|
|
110
110
|
"missing-transaction": {
|
|
@@ -120,7 +120,7 @@ const RULE_META = {
|
|
|
120
120
|
"A transfer that debits one account then credits another: if the server crashes between the two writes, " +
|
|
121
121
|
"money vanishes. A transaction guarantees both happen or neither does.",
|
|
122
122
|
severity: "warning",
|
|
123
|
-
docs: "https://noctisnova.com/
|
|
123
|
+
docs: "https://noctisnova.com/tools/orm-doctor/database-safety-and-performance",
|
|
124
124
|
},
|
|
125
125
|
|
|
126
126
|
"missing-relation-action": {
|
|
@@ -136,7 +136,7 @@ const RULE_META = {
|
|
|
136
136
|
"Deleting a User whose Posts have no onDelete either errors out ('foreign key constraint') or leaves " +
|
|
137
137
|
"orphaned Posts pointing at a user that no longer exists. Setting it explicitly removes the surprise.",
|
|
138
138
|
severity: "info",
|
|
139
|
-
docs: "https://noctisnova.com/
|
|
139
|
+
docs: "https://noctisnova.com/tools/orm-doctor/database-safety-and-performance",
|
|
140
140
|
},
|
|
141
141
|
|
|
142
142
|
"missing-index": {
|
|
@@ -170,7 +170,7 @@ const RULE_META = {
|
|
|
170
170
|
"Re-running a seed in CI after the first run throws a unique constraint violation and fails " +
|
|
171
171
|
"the build. Copying the seed to a new environment creates silent data conflicts.",
|
|
172
172
|
severity: "warning",
|
|
173
|
-
docs: "https://noctisnova.com/
|
|
173
|
+
docs: "https://noctisnova.com/tools/orm-doctor/seed-best-practices",
|
|
174
174
|
},
|
|
175
175
|
|
|
176
176
|
"seed-no-truncate": {
|
|
@@ -187,7 +187,7 @@ const RULE_META = {
|
|
|
187
187
|
"Your CI suite passes on the first run after a fresh database, then fails every subsequent " +
|
|
188
188
|
"run with 'Unique constraint failed' until someone manually wipes the database.",
|
|
189
189
|
severity: "warning",
|
|
190
|
-
docs: "https://noctisnova.com/
|
|
190
|
+
docs: "https://noctisnova.com/tools/orm-doctor/seed-best-practices",
|
|
191
191
|
},
|
|
192
192
|
|
|
193
193
|
"seed-no-disconnect": {
|
|
@@ -203,7 +203,7 @@ const RULE_META = {
|
|
|
203
203
|
"CI jobs appear to complete (seed ran successfully) but the process never exits — the job " +
|
|
204
204
|
"hits a timeout minutes later and is marked as failed.",
|
|
205
205
|
severity: "info",
|
|
206
|
-
docs: "https://noctisnova.com/
|
|
206
|
+
docs: "https://noctisnova.com/tools/orm-doctor/seed-best-practices",
|
|
207
207
|
},
|
|
208
208
|
|
|
209
209
|
"seed-large-batch": {
|
|
@@ -220,7 +220,7 @@ const RULE_META = {
|
|
|
220
220
|
"A seed with 200 individual create() calls takes 4–8 seconds locally and 20–60 seconds in " +
|
|
221
221
|
"CI depending on DB latency. Batching into createMany() cuts that to under 1 second.",
|
|
222
222
|
severity: "warning",
|
|
223
|
-
docs: "https://noctisnova.com/
|
|
223
|
+
docs: "https://noctisnova.com/tools/orm-doctor/seed-best-practices",
|
|
224
224
|
},
|
|
225
225
|
};
|
|
226
226
|
|