@reprova/sdk 0.4.0 → 0.6.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 (147) hide show
  1. package/README.md +170 -8
  2. package/dist/cjs/context.js +28 -0
  3. package/dist/cjs/distributionBaselinePoller.js +87 -0
  4. package/dist/cjs/distributionDrift.js +170 -0
  5. package/dist/cjs/drizzle.js +204 -0
  6. package/dist/cjs/frameworkAdapter.js +2 -0
  7. package/dist/cjs/httpFetch.js +59 -0
  8. package/dist/cjs/idempotency.js +132 -0
  9. package/dist/cjs/idempotencySettingsPoller.js +71 -0
  10. package/dist/cjs/index.js +96 -0
  11. package/dist/cjs/invariantInference.js +90 -0
  12. package/dist/cjs/invariantRules.js +142 -0
  13. package/dist/cjs/knex.js +86 -0
  14. package/dist/cjs/kysely.js +175 -0
  15. package/dist/cjs/legacyPrisma.js +130 -0
  16. package/dist/cjs/mikroorm.js +74 -0
  17. package/dist/cjs/mongoSchemaReporter.js +85 -0
  18. package/dist/cjs/mongoose.js +335 -0
  19. package/dist/cjs/nPlusOne.js +0 -0
  20. package/dist/cjs/nest.js +136 -0
  21. package/dist/cjs/nextjs.js +180 -0
  22. package/dist/cjs/package.json +1 -0
  23. package/dist/cjs/prisma.js +363 -0
  24. package/dist/cjs/proto.gen.js +4 -0
  25. package/dist/cjs/registry.js +100 -0
  26. package/dist/cjs/retryStorm.js +70 -0
  27. package/dist/cjs/rulesPoller.js +77 -0
  28. package/dist/cjs/sdk.js +1231 -0
  29. package/dist/cjs/sequelize.js +108 -0
  30. package/dist/cjs/shape.js +45 -0
  31. package/dist/cjs/stack.js +51 -0
  32. package/dist/cjs/transport.js +71 -0
  33. package/dist/cjs/typeorm.js +200 -0
  34. package/dist/context.d.ts +32 -0
  35. package/dist/context.d.ts.map +1 -1
  36. package/dist/context.js +18 -0
  37. package/dist/context.js.map +1 -1
  38. package/dist/distributionBaselinePoller.d.ts +36 -0
  39. package/dist/distributionBaselinePoller.d.ts.map +1 -0
  40. package/dist/distributionBaselinePoller.js +84 -0
  41. package/dist/distributionBaselinePoller.js.map +1 -0
  42. package/dist/distributionDrift.d.ts +41 -0
  43. package/dist/distributionDrift.d.ts.map +1 -0
  44. package/dist/distributionDrift.js +161 -0
  45. package/dist/distributionDrift.js.map +1 -0
  46. package/dist/drizzle.d.ts +22 -0
  47. package/dist/drizzle.d.ts.map +1 -1
  48. package/dist/drizzle.js +129 -1
  49. package/dist/drizzle.js.map +1 -1
  50. package/dist/frameworkAdapter.d.ts +5 -0
  51. package/dist/frameworkAdapter.d.ts.map +1 -0
  52. package/dist/frameworkAdapter.js +2 -0
  53. package/dist/frameworkAdapter.js.map +1 -0
  54. package/dist/httpFetch.d.ts +13 -0
  55. package/dist/httpFetch.d.ts.map +1 -0
  56. package/dist/httpFetch.js +54 -0
  57. package/dist/httpFetch.js.map +1 -0
  58. package/dist/idempotency.d.ts +26 -0
  59. package/dist/idempotency.d.ts.map +1 -0
  60. package/dist/idempotency.js +124 -0
  61. package/dist/idempotency.js.map +1 -0
  62. package/dist/idempotencySettingsPoller.d.ts +23 -0
  63. package/dist/idempotencySettingsPoller.d.ts.map +1 -0
  64. package/dist/idempotencySettingsPoller.js +68 -0
  65. package/dist/idempotencySettingsPoller.js.map +1 -0
  66. package/dist/index.d.ts +33 -6
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +19 -4
  69. package/dist/index.js.map +1 -1
  70. package/dist/invariantInference.d.ts +28 -0
  71. package/dist/invariantInference.d.ts.map +1 -0
  72. package/dist/invariantInference.js +87 -0
  73. package/dist/invariantInference.js.map +1 -0
  74. package/dist/invariantRules.d.ts +16 -0
  75. package/dist/invariantRules.d.ts.map +1 -0
  76. package/dist/invariantRules.js +104 -0
  77. package/dist/invariantRules.js.map +1 -0
  78. package/dist/knex.d.ts.map +1 -1
  79. package/dist/knex.js +1 -0
  80. package/dist/knex.js.map +1 -1
  81. package/dist/kysely.d.ts.map +1 -1
  82. package/dist/kysely.js +2 -1
  83. package/dist/kysely.js.map +1 -1
  84. package/dist/legacyPrisma.d.ts +6 -0
  85. package/dist/legacyPrisma.d.ts.map +1 -0
  86. package/dist/legacyPrisma.js +128 -0
  87. package/dist/legacyPrisma.js.map +1 -0
  88. package/dist/mikroorm.d.ts +2 -0
  89. package/dist/mikroorm.d.ts.map +1 -0
  90. package/dist/mikroorm.js +72 -0
  91. package/dist/mikroorm.js.map +1 -0
  92. package/dist/mongoSchemaReporter.d.ts +23 -0
  93. package/dist/mongoSchemaReporter.d.ts.map +1 -0
  94. package/dist/mongoSchemaReporter.js +82 -0
  95. package/dist/mongoSchemaReporter.js.map +1 -0
  96. package/dist/mongoose.d.ts +48 -0
  97. package/dist/mongoose.d.ts.map +1 -0
  98. package/dist/mongoose.js +331 -0
  99. package/dist/mongoose.js.map +1 -0
  100. package/dist/nPlusOne.d.ts +52 -0
  101. package/dist/nPlusOne.d.ts.map +1 -0
  102. package/dist/nPlusOne.js +0 -0
  103. package/dist/nPlusOne.js.map +1 -0
  104. package/dist/nest.d.ts +31 -0
  105. package/dist/nest.d.ts.map +1 -0
  106. package/dist/nest.js +133 -0
  107. package/dist/nest.js.map +1 -0
  108. package/dist/nextjs.d.ts +27 -0
  109. package/dist/nextjs.d.ts.map +1 -0
  110. package/dist/nextjs.js +178 -0
  111. package/dist/nextjs.js.map +1 -0
  112. package/dist/prisma.d.ts +21 -5
  113. package/dist/prisma.d.ts.map +1 -1
  114. package/dist/prisma.js +247 -22
  115. package/dist/prisma.js.map +1 -1
  116. package/dist/proto.gen.d.ts +19 -1
  117. package/dist/proto.gen.d.ts.map +1 -1
  118. package/dist/registry.d.ts +1 -0
  119. package/dist/registry.d.ts.map +1 -1
  120. package/dist/registry.js +24 -0
  121. package/dist/registry.js.map +1 -1
  122. package/dist/retryStorm.d.ts +42 -0
  123. package/dist/retryStorm.d.ts.map +1 -0
  124. package/dist/retryStorm.js +65 -0
  125. package/dist/retryStorm.js.map +1 -0
  126. package/dist/rulesPoller.d.ts +26 -0
  127. package/dist/rulesPoller.d.ts.map +1 -0
  128. package/dist/rulesPoller.js +74 -0
  129. package/dist/rulesPoller.js.map +1 -0
  130. package/dist/sdk.d.ts +53 -6
  131. package/dist/sdk.d.ts.map +1 -1
  132. package/dist/sdk.js +663 -34
  133. package/dist/sdk.js.map +1 -1
  134. package/dist/sequelize.d.ts.map +1 -1
  135. package/dist/sequelize.js +4 -3
  136. package/dist/sequelize.js.map +1 -1
  137. package/dist/stack.d.ts.map +1 -1
  138. package/dist/stack.js +6 -3
  139. package/dist/stack.js.map +1 -1
  140. package/dist/transport.d.ts.map +1 -1
  141. package/dist/transport.js +2 -1
  142. package/dist/transport.js.map +1 -1
  143. package/dist/typeorm.d.ts +1 -0
  144. package/dist/typeorm.d.ts.map +1 -1
  145. package/dist/typeorm.js +133 -1
  146. package/dist/typeorm.js.map +1 -1
  147. package/package.json +20 -5
@@ -0,0 +1,331 @@
1
+ import { contextStorage } from './context.js';
2
+ import { shapeArgs } from './shape.js';
3
+ const SELECT_QUERY_OPS = ['find', 'findOne', 'count', 'countDocuments', 'distinct', 'estimatedDocumentCount'];
4
+ const UPDATE_QUERY_OPS = ['updateOne', 'updateMany', 'findOneAndUpdate', 'findOneAndReplace'];
5
+ const DELETE_QUERY_OPS = ['deleteOne', 'deleteMany', 'findOneAndDelete', 'findOneAndRemove', 'remove'];
6
+ // _id(s) from a query/document result, whatever shape it happens to be
7
+ // (array of docs, single doc, null, or a driver-level count/ack object with
8
+ // no docs at all — idsOf simply yields nothing for the last case).
9
+ function idsOf(value) {
10
+ if (!value)
11
+ return [];
12
+ const docs = Array.isArray(value) ? value : [value];
13
+ const ids = [];
14
+ for (const d of docs) {
15
+ const id = d?._id;
16
+ if (id !== undefined && id !== null)
17
+ ids.push(String(id));
18
+ }
19
+ return ids;
20
+ }
21
+ // updateMany/deleteMany/findOneAndUpdate-family results carry no document
22
+ // ids at all (just {acknowledged, modifiedCount|deletedCount|matchedCount}
23
+ // or a plain number for count()) — this is the bulk-op analog of
24
+ // FootprintQuery.where_shape existing for "no concrete rows, but here's how
25
+ // many and what shape of filter."
26
+ function affectedCountOf(result) {
27
+ if (typeof result === 'number')
28
+ return result;
29
+ const r = result;
30
+ return r?.modifiedCount ?? r?.deletedCount ?? r?.matchedCount ?? 0;
31
+ }
32
+ // The MongoDB driver connection pool that Mongoose 5.x bundles dispatches a
33
+ // query's response from inside the pooled connection's own socket callback
34
+ // — whose AsyncLocalStorage context is fixed to whatever was ambient when
35
+ // that connection was first used, not the context of whichever query is
36
+ // currently awaiting a result on it. Confirmed empirically: a Mongoose
37
+ // post-hook loses ALS context as soon as a single prior query has run
38
+ // outside a captured request, which every real app does (background jobs,
39
+ // startup queries, connection warmup) — so relying on contextStorage in the
40
+ // post-hook alone silently drops footprints in production, not just in
41
+ // contrived tests. Pre-hooks, by contrast, run synchronously on the
42
+ // caller's own stack before any driver I/O, so they still see the correct
43
+ // context. Capture it there and thread it through to the post-hook via a
44
+ // WeakMap keyed on the query/document instance — Kareem (Mongoose's
45
+ // middleware runner) preserves that same instance as `this` across pre and
46
+ // post for one operation, so the lookup is exact, not a guess.
47
+ const ctxByHost = new WeakMap();
48
+ function captureCtx(host) {
49
+ const ctx = contextStorage.getStore();
50
+ if (ctx && host && typeof host === 'object')
51
+ ctxByHost.set(host, ctx);
52
+ }
53
+ function resolveCtx(host) {
54
+ return contextStorage.getStore() ?? (host && typeof host === 'object' ? ctxByHost.get(host) : undefined);
55
+ }
56
+ // Find-or-create the (collection, op) entry for this request and fold in
57
+ // whatever this call learned — same merge-by-(table,op) pattern mikroorm.ts
58
+ // uses, extended with a where_shape/count fallback for bulk ops that never
59
+ // expose which specific documents they touched.
60
+ function recordFootprint(ctx, collection, op, pks, affectedCount, whereShape) {
61
+ if (!ctx || !collection)
62
+ return;
63
+ let entry = ctx.footprint.find((f) => f.model === collection && f.op === op);
64
+ if (!entry) {
65
+ entry = { model: collection, op, pks: [], count: 0 };
66
+ ctx.footprint.push(entry);
67
+ }
68
+ for (const pk of pks)
69
+ if (!entry.pks.includes(pk))
70
+ entry.pks.push(pk);
71
+ entry.count = entry.pks.length > 0 ? entry.pks.length : Math.max(entry.count, affectedCount);
72
+ if (whereShape && !entry.where_shape)
73
+ entry.where_shape = whereShape;
74
+ }
75
+ function recordFromQuery(query, op, result) {
76
+ const ctx = resolveCtx(query);
77
+ const collection = query.model?.collection?.collectionName;
78
+ let whereShape;
79
+ try {
80
+ whereShape = query.getFilter ? shapeArgs(query.getFilter()) : undefined;
81
+ }
82
+ catch {
83
+ /* filter introspection best-effort */
84
+ }
85
+ recordFootprint(ctx, collection, op, idsOf(result), affectedCountOf(result), whereShape);
86
+ }
87
+ // Whether a document was new AT THE TIME save() was called — snapshotted in
88
+ // pre('save') because by the time post('save') runs, Mongoose has already
89
+ // reset doc.isNew to false regardless of which case this was, so isNew
90
+ // alone can't distinguish insert from update from inside the post hook.
91
+ const wasNewByDoc = new WeakMap();
92
+ // Installs Reprova's footprint hooks on a schema. Call BEFORE compiling any
93
+ // model from it — e.g. `mongoose.plugin(reprovaMongoosePlugin)` once,
94
+ // before any `mongoose.model(...)` calls, so every subsequently-compiled
95
+ // model gets it. Registers plain functions, no decorators/classes needed.
96
+ export function reprovaMongoosePlugin(schema) {
97
+ for (const opName of SELECT_QUERY_OPS) {
98
+ schema.pre(opName, function () {
99
+ captureCtx(this);
100
+ });
101
+ schema.post(opName, function (result) {
102
+ try {
103
+ recordFromQuery(this, 'select', result);
104
+ }
105
+ catch {
106
+ /* never break the host query */
107
+ }
108
+ });
109
+ }
110
+ for (const opName of UPDATE_QUERY_OPS) {
111
+ schema.pre(opName, function () {
112
+ captureCtx(this);
113
+ });
114
+ schema.post(opName, function (result) {
115
+ try {
116
+ recordFromQuery(this, 'update', result);
117
+ }
118
+ catch {
119
+ /* never break the host query */
120
+ }
121
+ });
122
+ }
123
+ for (const opName of DELETE_QUERY_OPS) {
124
+ schema.pre(opName, function () {
125
+ captureCtx(this);
126
+ });
127
+ schema.post(opName, function (result) {
128
+ try {
129
+ recordFromQuery(this, 'delete', result);
130
+ }
131
+ catch {
132
+ /* never break the host query */
133
+ }
134
+ });
135
+ }
136
+ schema.pre('save', function () {
137
+ try {
138
+ wasNewByDoc.set(this, !!this.isNew);
139
+ }
140
+ catch {
141
+ /* never break the host save */
142
+ }
143
+ captureCtx(this);
144
+ });
145
+ schema.post('save', function () {
146
+ try {
147
+ const wasNew = wasNewByDoc.get(this) ?? !!this.isNew;
148
+ wasNewByDoc.delete(this);
149
+ recordFootprint(resolveCtx(this), this.collection?.collectionName, wasNew ? 'insert' : 'update', idsOf(this), 1);
150
+ }
151
+ catch {
152
+ /* never break the host save */
153
+ }
154
+ });
155
+ // insertMany's `this` is the shared Model, not a per-call instance, so it
156
+ // can't be used as the WeakMap key (concurrent calls would clobber each
157
+ // other's captured context) — the `docs` array IS per-call and Kareem
158
+ // passes the same reference to both hooks, so key on that instead.
159
+ schema.pre('insertMany', function (docs) {
160
+ captureCtx(docs);
161
+ });
162
+ schema.post('insertMany', function (docs) {
163
+ try {
164
+ const ids = idsOf(docs);
165
+ recordFootprint(resolveCtx(docs), this.collection?.collectionName, 'insert', ids, ids.length);
166
+ }
167
+ catch {
168
+ /* never break the host insertMany */
169
+ }
170
+ });
171
+ }
172
+ // --- Retroactive instrumentation for already-compiled models -------------
173
+ //
174
+ // `mongoose.plugin(reprovaMongoosePlugin)` only reaches schemas compiled
175
+ // AFTER the call — confirmed empirically that Mongoose bakes its Kareem
176
+ // hook chain in at `mongoose.model(...)` time, so `schema.pre/post` added
177
+ // to an already-compiled model's schema afterward are simply never
178
+ // consulted again. That ordering requirement forces apps whose own
179
+ // bootstrap already compiles a model before Reprova can be wired in (e.g.
180
+ // a multi-file loader architecture that connects Mongoose, compiles
181
+ // models, THEN sets up the rest) to route `Reprova.init()` to wherever
182
+ // runs earliest, rather than wherever's most natural.
183
+ //
184
+ // This covers that case by wrapping each already-compiled model's own
185
+ // query/document methods directly instead of relying on schema hooks — a
186
+ // plain method override works regardless of compile order. It also
187
+ // sidesteps the pooled-connection ALS-loss problem the schema-hook path
188
+ // needs the WeakMap for (see captureCtx/resolveCtx above): the context is
189
+ // captured synchronously into a closure the instant the wrapped method is
190
+ // called, before any driver I/O, so the `.then()` continuation always has
191
+ // the right value on hand regardless of what AsyncLocalStorage does.
192
+ //
193
+ // Idempotent and safe to call repeatedly (e.g. instrumentMongoose() called
194
+ // more than once) — already-patched models are skipped via `patchedModels`.
195
+ const patchedModels = new WeakSet();
196
+ function wrapQueryReturningMethod(model, methodName, op) {
197
+ const original = model[methodName];
198
+ if (typeof original !== 'function')
199
+ return;
200
+ model[methodName] = function (...args) {
201
+ const ctx = contextStorage.getStore();
202
+ const query = original.apply(this, args);
203
+ if (!query || typeof query.exec !== 'function')
204
+ return query;
205
+ const originalExec = query.exec.bind(query);
206
+ query.exec = (...execArgs) => originalExec(...execArgs).then((result) => {
207
+ try {
208
+ const collection = model.collection?.collectionName;
209
+ let whereShape;
210
+ try {
211
+ whereShape = query.getFilter ? shapeArgs(query.getFilter()) : undefined;
212
+ }
213
+ catch {
214
+ /* filter introspection best-effort */
215
+ }
216
+ recordFootprint(ctx, collection, op, idsOf(result), affectedCountOf(result), whereShape);
217
+ }
218
+ catch {
219
+ /* never break the host query */
220
+ }
221
+ return result;
222
+ });
223
+ return query;
224
+ };
225
+ }
226
+ function patchCompiledModel(model) {
227
+ if (patchedModels.has(model))
228
+ return;
229
+ patchedModels.add(model);
230
+ for (const methodName of SELECT_QUERY_OPS)
231
+ wrapQueryReturningMethod(model, methodName, 'select');
232
+ for (const methodName of UPDATE_QUERY_OPS)
233
+ wrapQueryReturningMethod(model, methodName, 'update');
234
+ for (const methodName of DELETE_QUERY_OPS)
235
+ wrapQueryReturningMethod(model, methodName, 'delete');
236
+ const proto = model.prototype;
237
+ const originalSave = proto?.save;
238
+ if (proto && typeof originalSave === 'function') {
239
+ proto.save = function (...args) {
240
+ const ctx = contextStorage.getStore();
241
+ const wasNew = !!this.isNew;
242
+ return originalSave.apply(this, args).then((result) => {
243
+ try {
244
+ recordFootprint(ctx, this.collection?.collectionName, wasNew ? 'insert' : 'update', idsOf(this), 1);
245
+ }
246
+ catch {
247
+ /* never break the host save */
248
+ }
249
+ return result;
250
+ });
251
+ };
252
+ }
253
+ const originalInsertMany = model.insertMany;
254
+ if (typeof originalInsertMany === 'function') {
255
+ model.insertMany = function (...args) {
256
+ const ctx = contextStorage.getStore();
257
+ return originalInsertMany.apply(this, args).then((docs) => {
258
+ try {
259
+ const ids = idsOf(docs);
260
+ recordFootprint(ctx, model.collection?.collectionName, 'insert', ids, ids.length);
261
+ }
262
+ catch {
263
+ /* never break the host insertMany */
264
+ }
265
+ return docs;
266
+ });
267
+ };
268
+ }
269
+ }
270
+ // Called once from Reprova.instrumentMongoose() alongside the plugin
271
+ // registration above — covers every model that already exists on this
272
+ // mongoose instance at that moment. Any model compiled AFTER this call is
273
+ // still handled by reprovaMongoosePlugin via mongoose.plugin() as before.
274
+ export function instrumentAlreadyCompiledModels(mongooseInstance) {
275
+ let names = [];
276
+ try {
277
+ names = mongooseInstance.modelNames();
278
+ }
279
+ catch {
280
+ return;
281
+ }
282
+ for (const name of names) {
283
+ try {
284
+ patchCompiledModel(mongooseInstance.model(name));
285
+ }
286
+ catch {
287
+ /* one bad model shouldn't block instrumenting the rest */
288
+ }
289
+ }
290
+ }
291
+ export function discoverMongooseSchema(mongooseInstance) {
292
+ const models = [];
293
+ const edges = [];
294
+ let names = [];
295
+ try {
296
+ names = mongooseInstance.modelNames();
297
+ }
298
+ catch {
299
+ return { models, edges };
300
+ }
301
+ for (const name of names) {
302
+ let model;
303
+ try {
304
+ model = mongooseInstance.model(name);
305
+ }
306
+ catch {
307
+ continue;
308
+ }
309
+ const collection = model.collection?.collectionName;
310
+ if (!collection)
311
+ continue;
312
+ models.push({ model: name, collection });
313
+ try {
314
+ model.schema.eachPath((pathName, schemaType) => {
315
+ // Direct ref: `{ type: Schema.Types.ObjectId, ref: 'X' }`. Array ref:
316
+ // `[{ type: Schema.Types.ObjectId, ref: 'X' }]` — Mongoose exposes
317
+ // the array element's own SchemaType (with its own `.options.ref`)
318
+ // via `.caster` on the array path's SchemaType.
319
+ const ref = schemaType?.options?.ref ?? schemaType?.caster?.options?.ref;
320
+ if (ref) {
321
+ edges.push({ from_collection: collection, from_field: pathName, to_model: ref, array: !!schemaType?.caster });
322
+ }
323
+ });
324
+ }
325
+ catch {
326
+ /* schema introspection best-effort — one bad path shouldn't drop the whole model */
327
+ }
328
+ }
329
+ return { models, edges };
330
+ }
331
+ //# sourceMappingURL=mongoose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongoose.js","sourceRoot":"","sources":["../src/mongoose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import type { FootprintQuery } from './context.js';
2
+ export interface NPlusOneFinding {
3
+ model: string;
4
+ op: string;
5
+ count: number;
6
+ }
7
+ /**
8
+ * Groups a request's recorded footprint by (model, op) and returns every
9
+ * group whose invocation count meets or exceeds the threshold — the
10
+ * signature of the same query shape running as N separate round-trips
11
+ * instead of one batched query.
12
+ *
13
+ * This can't mistake an efficient query for N+1: a Prisma `include` (and the
14
+ * equivalent join/relation-load in the other adapters) produces exactly ONE
15
+ * footprint entry per relation per query, however many rows it returns —
16
+ * see prisma.ts's extractNestedFootprints, which aggregates across all rows
17
+ * of a single call rather than pushing one entry per row. What this counts
18
+ * is entries, i.e. actual separate calls into $allOperations (or the
19
+ * equivalent per-adapter hook) — a `for` loop firing the same
20
+ * findUnique/findFirst N times is exactly N entries with the same
21
+ * (model, op), which is the real signal.
22
+ *
23
+ * $raw queries are excluded: each one is its own distinct SQL text, not a
24
+ * repeatable "shape" in the same sense, so counting them together would
25
+ * conflate unrelated raw queries into a false positive.
26
+ */
27
+ export declare function detectNPlusOne(footprint: FootprintQuery[], threshold: number): NPlusOneFinding[];
28
+ /**
29
+ * A stable, digit-free description of the finding, suitable as an Error
30
+ * message for capture. Deliberately excludes the exact count: the control
31
+ * plane fingerprints occurrences by error type + this message (see
32
+ * fingerprint.go's fallback path for stack-less errors), so two occurrences
33
+ * of the same underlying N+1 pattern — say 6 calls one time, 9 the next —
34
+ * must produce byte-identical messages to fingerprint into the same issue
35
+ * instead of each becoming its own issue. The actual counts are still
36
+ * visible in the capture's data_footprint (one entry per call), so nothing
37
+ * is lost by leaving them out of the message.
38
+ */
39
+ export declare function describeNPlusOne(finding: NPlusOneFinding): string;
40
+ /**
41
+ * A real named Error subclass, not a plain `new Error()` with `.name` set
42
+ * afterward — `capture()` derives the payload's `error.type` (which the
43
+ * control plane uses verbatim to build the issue title, see ingest.go's
44
+ * buildTitle) from `err.constructor.name`, which for a plain Error is
45
+ * always "Error" regardless of what `.name` gets reassigned to. Subclassing
46
+ * is the only way `error.type` actually reads "PotentialNPlusOneQuery"
47
+ * instead of a generic "Error" in the dashboard.
48
+ */
49
+ export declare class PotentialNPlusOneQuery extends Error {
50
+ constructor(finding: NPlusOneFinding);
51
+ }
52
+ //# sourceMappingURL=nPlusOne.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nPlusOne.d.ts","sourceRoot":"","sources":["../src/nPlusOne.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,cAAc,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,CAahG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAEjE;AAED;;;;;;;;GAQG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,eAAe;CAGrC"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nPlusOne.js","sourceRoot":"","sources":["../src/nPlusOne.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,cAAc,CAAC,SAA2B,EAAE,SAAiB;IAC3E,IAAI,SAAS,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAE9B,MAAM,MAAM,GAAG,IAAI,GAAG,EAA2B,CAAC;IAClD,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;YAAE,SAAS;QACjC,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,QAAQ;YAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;;YAC1B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;AACpG,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAwB;IACvD,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,EAAE,uEAAuE,CAAC;AAC/G,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC/C,YAAY,OAAwB;QAClC,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;IACnC,CAAC;CACF"}
package/dist/nest.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ interface HttpArgumentsHostLike {
2
+ getResponse(): unknown;
3
+ getRequest(): unknown;
4
+ }
5
+ export interface ArgumentsHostLike {
6
+ switchToHttp(): HttpArgumentsHostLike;
7
+ }
8
+ export interface ReprovaNestFilter {
9
+ catch(exception: unknown, host: ArgumentsHostLike): void;
10
+ }
11
+ export interface NestAppLike {
12
+ useGlobalFilters(...filters: ReprovaNestFilter[]): unknown;
13
+ useGlobalInterceptors(...interceptors: ReprovaNestInterceptor[]): unknown;
14
+ }
15
+ export interface CallHandlerLike {
16
+ handle(): unknown;
17
+ }
18
+ export interface ReprovaNestInterceptor {
19
+ intercept(context: ArgumentsHostLike, next: CallHandlerLike): unknown;
20
+ }
21
+ export interface NestExceptionFilterOptions {
22
+ capture?: (err: Error) => void;
23
+ }
24
+ export declare function createNestExceptionFilter(opts?: NestExceptionFilterOptions): ReprovaNestFilter;
25
+ export interface NestContextInterceptorOptions {
26
+ resolveContext: (req: unknown) => unknown;
27
+ runInContext: (ctx: unknown, fn: () => unknown) => unknown;
28
+ }
29
+ export declare function createNestContextInterceptor(opts: NestContextInterceptorOptions): ReprovaNestInterceptor;
30
+ export {};
31
+ //# sourceMappingURL=nest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nest.d.ts","sourceRoot":"","sources":["../src/nest.ts"],"names":[],"mappings":"AAgCA,UAAU,qBAAqB;IAC7B,WAAW,IAAI,OAAO,CAAC;IACvB,UAAU,IAAI,OAAO,CAAC;CACvB;AACD,MAAM,WAAW,iBAAiB;IAChC,YAAY,IAAI,qBAAqB,CAAC;CACvC;AACD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC1D;AAID,MAAM,WAAW,WAAW;IAC1B,gBAAgB,CAAC,GAAG,OAAO,EAAE,iBAAiB,EAAE,GAAG,OAAO,CAAC;IAC3D,qBAAqB,CAAC,GAAG,YAAY,EAAE,sBAAsB,EAAE,GAAG,OAAO,CAAC;CAC3E;AAKD,MAAM,WAAW,eAAe;IAC9B,MAAM,IAAI,OAAO,CAAC;CACnB;AACD,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC;CACvE;AAED,MAAM,WAAW,0BAA0B;IAIzC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CAChC;AAkCD,wBAAgB,yBAAyB,CAAC,IAAI,GAAE,0BAA+B,GAAG,iBAAiB,CA2DlG;AA4BD,MAAM,WAAW,6BAA6B;IAC5C,cAAc,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IAC1C,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,OAAO,KAAK,OAAO,CAAC;CAC5D;AAuCD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,6BAA6B,GAAG,sBAAsB,CA2BxG"}
package/dist/nest.js ADDED
@@ -0,0 +1,133 @@
1
+ import { parseStack } from './stack.js';
2
+ // Nest's HttpException contract, detected structurally: anything with
3
+ // getStatus() and getResponse() is treated as an intentional HTTP response
4
+ // (a 404, a validation 400, ...) exactly like Nest's own default filter does.
5
+ function isHttpExceptionLike(e) {
6
+ return (typeof e === 'object' &&
7
+ e !== null &&
8
+ typeof e.getStatus === 'function' &&
9
+ typeof e.getResponse === 'function');
10
+ }
11
+ function toError(exception) {
12
+ if (exception instanceof Error)
13
+ return exception;
14
+ return new Error(typeof exception === 'string' ? exception : JSON.stringify(exception));
15
+ }
16
+ // Express: res.status(n).json(body). Fastify: reply.status(n).send(body).
17
+ // Both expose status(); the payload method is feature-detected. Guarded —
18
+ // an exception filter must never itself throw.
19
+ function sendJson(res, status, body) {
20
+ try {
21
+ const r = res;
22
+ if (typeof r.status !== 'function')
23
+ return;
24
+ const chained = r.status(status);
25
+ if (typeof chained.json === 'function')
26
+ chained.json(body);
27
+ else if (typeof chained.send === 'function')
28
+ chained.send(body);
29
+ }
30
+ catch {
31
+ /* never break the response path from inside the filter */
32
+ }
33
+ }
34
+ export function createNestExceptionFilter(opts = {}) {
35
+ // Same activation rule as replayErrorHandler: the CLI sets REPROVA_REPLAY=1
36
+ // when it boots the app for replay; checked once at construction.
37
+ const replay = process.env.REPROVA_REPLAY === '1';
38
+ return {
39
+ catch(exception, host) {
40
+ const httpEx = isHttpExceptionLike(exception);
41
+ let status = 500;
42
+ if (httpEx) {
43
+ try {
44
+ status = exception.getStatus();
45
+ }
46
+ catch {
47
+ status = 500;
48
+ }
49
+ }
50
+ const res = host.switchToHttp().getResponse();
51
+ const err = toError(exception);
52
+ // Replay contract (only for server-side failures — intentional 4xx
53
+ // responses replay as themselves): emit the structured error the CLI's
54
+ // verdict comparison expects, via the SAME parseStack used at capture.
55
+ if (replay && status >= 500) {
56
+ sendJson(res, 500, {
57
+ error: {
58
+ type: err.constructor?.name ?? err.name ?? 'Error',
59
+ message: err.message,
60
+ stack: parseStack(err),
61
+ },
62
+ });
63
+ return;
64
+ }
65
+ // Capture server-side failures with the ORIGINAL exception. 4xx
66
+ // HttpExceptions are intentional control flow, exactly as Nest treats
67
+ // them — not captured (a silent 500 from elsewhere still gets the 5xx
68
+ // hook as backstop).
69
+ if (status >= 500 && opts.capture) {
70
+ try {
71
+ opts.capture(err);
72
+ }
73
+ catch {
74
+ /* capture must never break the response */
75
+ }
76
+ }
77
+ // Respond the way Nest's own BaseExceptionFilter would have.
78
+ if (httpEx) {
79
+ let body;
80
+ try {
81
+ body = exception.getResponse();
82
+ }
83
+ catch {
84
+ body = { statusCode: status, message: err.message };
85
+ }
86
+ sendJson(res, status, typeof body === 'string' ? { statusCode: status, message: body } : body);
87
+ }
88
+ else {
89
+ sendJson(res, 500, { statusCode: 500, message: 'Internal server error' });
90
+ }
91
+ },
92
+ };
93
+ }
94
+ function wrapSubscriptionWithContext(source, runInContext, ctx) {
95
+ if (!source || typeof source.subscribe !== 'function')
96
+ return source;
97
+ const obs = source;
98
+ const originalSubscribe = obs.subscribe.bind(obs);
99
+ obs.subscribe = (...args) => runInContext(ctx, () => originalSubscribe(...args));
100
+ return obs;
101
+ }
102
+ export function createNestContextInterceptor(opts) {
103
+ return {
104
+ intercept(context, next) {
105
+ let req;
106
+ try {
107
+ req = context.switchToHttp().getRequest();
108
+ }
109
+ catch {
110
+ req = undefined;
111
+ }
112
+ let ctx;
113
+ try {
114
+ ctx = opts.resolveContext(req);
115
+ }
116
+ catch {
117
+ ctx = undefined;
118
+ }
119
+ if (!ctx)
120
+ return next.handle();
121
+ // next.handle() itself must be called exactly once — on failure here
122
+ // there is nothing safe to retry (a second call could re-invoke the
123
+ // controller method, double-executing whatever it does), so this is
124
+ // deliberately not wrapped in a try/catch that falls back to a bare
125
+ // next.handle(). Constructing the Observable is cheap/lazy (no pipe or
126
+ // controller work happens yet); the actual context wrap lives inside
127
+ // wrapSubscriptionWithContext, applied to whatever subscribes to it
128
+ // later, whenever that turns out to be.
129
+ return wrapSubscriptionWithContext(next.handle(), opts.runInContext, ctx);
130
+ },
131
+ };
132
+ }
133
+ //# sourceMappingURL=nest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nest.js","sourceRoot":"","sources":["../src/nest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { ReprovaOptions } from './sdk.js';
2
+ export interface NextAdapterOptions extends ReprovaOptions {
3
+ prisma?: unknown;
4
+ dmmf?: unknown;
5
+ }
6
+ export interface TrpcMiddlewareResultLike {
7
+ ok: boolean;
8
+ error?: {
9
+ code: string;
10
+ cause?: unknown;
11
+ message?: string;
12
+ };
13
+ }
14
+ export interface TrpcMiddlewareParamsLike {
15
+ next: () => Promise<TrpcMiddlewareResultLike>;
16
+ }
17
+ export type ReprovaTrpcMiddleware = (opts: TrpcMiddlewareParamsLike) => Promise<TrpcMiddlewareResultLike>;
18
+ export interface ReprovaNextAdapter {
19
+ ensureInit(): Promise<void>;
20
+ captureError(err: unknown): void;
21
+ wrapRouteHandler<H extends (req: Request, ...rest: any[]) => Response | Promise<Response>>(handler: H): H;
22
+ withRequestContext<T>(req: Request, fn: () => T | Promise<T>): Promise<T>;
23
+ wrapApiHandler<Req, Res>(handler: (req: Req, res: Res) => void | Promise<void>): (req: Req, res: Res) => Promise<void>;
24
+ trpcMiddleware: ReprovaTrpcMiddleware;
25
+ }
26
+ export declare function createNextAdapter(opts: NextAdapterOptions): ReprovaNextAdapter;
27
+ //# sourceMappingURL=nextjs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../src/nextjs.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AA8C/C,MAAM,WAAW,kBAAmB,SAAQ,cAAc;IAGxD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAQD,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7D;AACD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,OAAO,CAAC,wBAAwB,CAAC,CAAC;CAC/C;AAQD,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,wBAAwB,KAAK,OAAO,CAAC,wBAAwB,CAAC,CAAC;AAE1G,MAAM,WAAW,kBAAkB;IAOjC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAM5B,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAejC,gBAAgB,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;IAU1G,kBAAkB,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAM1E,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAGvH,cAAc,EAAE,qBAAqB,CAAC;CACvC;AA8FD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,kBAAkB,CAoF9E"}