@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.
- package/README.md +170 -8
- package/dist/cjs/context.js +28 -0
- package/dist/cjs/distributionBaselinePoller.js +87 -0
- package/dist/cjs/distributionDrift.js +170 -0
- package/dist/cjs/drizzle.js +204 -0
- package/dist/cjs/frameworkAdapter.js +2 -0
- package/dist/cjs/httpFetch.js +59 -0
- package/dist/cjs/idempotency.js +132 -0
- package/dist/cjs/idempotencySettingsPoller.js +71 -0
- package/dist/cjs/index.js +96 -0
- package/dist/cjs/invariantInference.js +90 -0
- package/dist/cjs/invariantRules.js +142 -0
- package/dist/cjs/knex.js +86 -0
- package/dist/cjs/kysely.js +175 -0
- package/dist/cjs/legacyPrisma.js +130 -0
- package/dist/cjs/mikroorm.js +74 -0
- package/dist/cjs/mongoSchemaReporter.js +85 -0
- package/dist/cjs/mongoose.js +335 -0
- package/dist/cjs/nPlusOne.js +0 -0
- package/dist/cjs/nest.js +136 -0
- package/dist/cjs/nextjs.js +180 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/prisma.js +363 -0
- package/dist/cjs/proto.gen.js +4 -0
- package/dist/cjs/registry.js +100 -0
- package/dist/cjs/retryStorm.js +70 -0
- package/dist/cjs/rulesPoller.js +77 -0
- package/dist/cjs/sdk.js +1231 -0
- package/dist/cjs/sequelize.js +108 -0
- package/dist/cjs/shape.js +45 -0
- package/dist/cjs/stack.js +51 -0
- package/dist/cjs/transport.js +71 -0
- package/dist/cjs/typeorm.js +200 -0
- package/dist/context.d.ts +32 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +18 -0
- package/dist/context.js.map +1 -1
- package/dist/distributionBaselinePoller.d.ts +36 -0
- package/dist/distributionBaselinePoller.d.ts.map +1 -0
- package/dist/distributionBaselinePoller.js +84 -0
- package/dist/distributionBaselinePoller.js.map +1 -0
- package/dist/distributionDrift.d.ts +41 -0
- package/dist/distributionDrift.d.ts.map +1 -0
- package/dist/distributionDrift.js +161 -0
- package/dist/distributionDrift.js.map +1 -0
- package/dist/drizzle.d.ts +22 -0
- package/dist/drizzle.d.ts.map +1 -1
- package/dist/drizzle.js +129 -1
- package/dist/drizzle.js.map +1 -1
- package/dist/frameworkAdapter.d.ts +5 -0
- package/dist/frameworkAdapter.d.ts.map +1 -0
- package/dist/frameworkAdapter.js +2 -0
- package/dist/frameworkAdapter.js.map +1 -0
- package/dist/httpFetch.d.ts +13 -0
- package/dist/httpFetch.d.ts.map +1 -0
- package/dist/httpFetch.js +54 -0
- package/dist/httpFetch.js.map +1 -0
- package/dist/idempotency.d.ts +26 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +124 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/idempotencySettingsPoller.d.ts +23 -0
- package/dist/idempotencySettingsPoller.d.ts.map +1 -0
- package/dist/idempotencySettingsPoller.js +68 -0
- package/dist/idempotencySettingsPoller.js.map +1 -0
- package/dist/index.d.ts +33 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/dist/invariantInference.d.ts +28 -0
- package/dist/invariantInference.d.ts.map +1 -0
- package/dist/invariantInference.js +87 -0
- package/dist/invariantInference.js.map +1 -0
- package/dist/invariantRules.d.ts +16 -0
- package/dist/invariantRules.d.ts.map +1 -0
- package/dist/invariantRules.js +104 -0
- package/dist/invariantRules.js.map +1 -0
- package/dist/knex.d.ts.map +1 -1
- package/dist/knex.js +1 -0
- package/dist/knex.js.map +1 -1
- package/dist/kysely.d.ts.map +1 -1
- package/dist/kysely.js +2 -1
- package/dist/kysely.js.map +1 -1
- package/dist/legacyPrisma.d.ts +6 -0
- package/dist/legacyPrisma.d.ts.map +1 -0
- package/dist/legacyPrisma.js +128 -0
- package/dist/legacyPrisma.js.map +1 -0
- package/dist/mikroorm.d.ts +2 -0
- package/dist/mikroorm.d.ts.map +1 -0
- package/dist/mikroorm.js +72 -0
- package/dist/mikroorm.js.map +1 -0
- package/dist/mongoSchemaReporter.d.ts +23 -0
- package/dist/mongoSchemaReporter.d.ts.map +1 -0
- package/dist/mongoSchemaReporter.js +82 -0
- package/dist/mongoSchemaReporter.js.map +1 -0
- package/dist/mongoose.d.ts +48 -0
- package/dist/mongoose.d.ts.map +1 -0
- package/dist/mongoose.js +331 -0
- package/dist/mongoose.js.map +1 -0
- package/dist/nPlusOne.d.ts +52 -0
- package/dist/nPlusOne.d.ts.map +1 -0
- package/dist/nPlusOne.js +0 -0
- package/dist/nPlusOne.js.map +1 -0
- package/dist/nest.d.ts +31 -0
- package/dist/nest.d.ts.map +1 -0
- package/dist/nest.js +133 -0
- package/dist/nest.js.map +1 -0
- package/dist/nextjs.d.ts +27 -0
- package/dist/nextjs.d.ts.map +1 -0
- package/dist/nextjs.js +178 -0
- package/dist/nextjs.js.map +1 -0
- package/dist/prisma.d.ts +21 -5
- package/dist/prisma.d.ts.map +1 -1
- package/dist/prisma.js +247 -22
- package/dist/prisma.js.map +1 -1
- package/dist/proto.gen.d.ts +19 -1
- package/dist/proto.gen.d.ts.map +1 -1
- package/dist/registry.d.ts +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +24 -0
- package/dist/registry.js.map +1 -1
- package/dist/retryStorm.d.ts +42 -0
- package/dist/retryStorm.d.ts.map +1 -0
- package/dist/retryStorm.js +65 -0
- package/dist/retryStorm.js.map +1 -0
- package/dist/rulesPoller.d.ts +26 -0
- package/dist/rulesPoller.d.ts.map +1 -0
- package/dist/rulesPoller.js +74 -0
- package/dist/rulesPoller.js.map +1 -0
- package/dist/sdk.d.ts +53 -6
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +663 -34
- package/dist/sdk.js.map +1 -1
- package/dist/sequelize.d.ts.map +1 -1
- package/dist/sequelize.js +4 -3
- package/dist/sequelize.js.map +1 -1
- package/dist/stack.d.ts.map +1 -1
- package/dist/stack.js +6 -3
- package/dist/stack.js.map +1 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +2 -1
- package/dist/transport.js.map +1 -1
- package/dist/typeorm.d.ts +1 -0
- package/dist/typeorm.d.ts.map +1 -1
- package/dist/typeorm.js +133 -1
- package/dist/typeorm.js.map +1 -1
- package/package.json +20 -5
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PotentialRetryStorm = void 0;
|
|
4
|
+
exports.detectRetryStorm = detectRetryStorm;
|
|
5
|
+
exports.describeRetryStorm = describeRetryStorm;
|
|
6
|
+
// A call counts as failed for this purpose if it never got a real response
|
|
7
|
+
// (status 0, the SDK's convention for a network error or timeout — see
|
|
8
|
+
// sdk.ts's patchedFetch) or the server itself reported failure (5xx). 4xx is
|
|
9
|
+
// deliberately excluded: a repeated 404 or 401 is a caller bug, not a
|
|
10
|
+
// struggling dependency being hammered.
|
|
11
|
+
function isFailingCall(call) {
|
|
12
|
+
return call.status === 0 || call.status >= 500;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Groups a request's recorded outbound calls by (host, method, pathname —
|
|
16
|
+
* query string stripped) and flags any group that both repeats at or above
|
|
17
|
+
* the threshold AND includes at least one failing call.
|
|
18
|
+
*
|
|
19
|
+
* Both conditions matter: repetition alone doesn't distinguish a retry storm
|
|
20
|
+
* from legitimate pagination or batch fetching against a healthy dependency
|
|
21
|
+
* (all 200s) — requiring at least one failure targets specifically "the app
|
|
22
|
+
* kept calling a target that was failing," the actual retry-amplification
|
|
23
|
+
* pattern, not "the app called the same endpoint several times." Query
|
|
24
|
+
* string is stripped before grouping because a real retry commonly varies a
|
|
25
|
+
* request id or cache-busting param per attempt — grouping on the exact
|
|
26
|
+
* path would make every retry look like a distinct, one-off call and never
|
|
27
|
+
* cross the threshold.
|
|
28
|
+
*/
|
|
29
|
+
function detectRetryStorm(calls, threshold) {
|
|
30
|
+
if (threshold <= 0)
|
|
31
|
+
return [];
|
|
32
|
+
const groups = new Map();
|
|
33
|
+
for (const call of calls) {
|
|
34
|
+
const pathname = call.path.split('?')[0] ?? call.path;
|
|
35
|
+
const key = `${call.host} ${call.method} ${pathname}`;
|
|
36
|
+
let g = groups.get(key);
|
|
37
|
+
if (!g) {
|
|
38
|
+
g = { host: call.host, method: call.method, path: pathname, count: 0, failureCount: 0 };
|
|
39
|
+
groups.set(key, g);
|
|
40
|
+
}
|
|
41
|
+
g.count++;
|
|
42
|
+
if (isFailingCall(call))
|
|
43
|
+
g.failureCount++;
|
|
44
|
+
}
|
|
45
|
+
return [...groups.values()]
|
|
46
|
+
.filter((g) => g.count >= threshold && g.failureCount > 0)
|
|
47
|
+
.sort((a, b) => b.count - a.count);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A stable, digit-free description — same fingerprinting reasoning as
|
|
51
|
+
* describeNPlusOne: the exact count varies request to request, and must not
|
|
52
|
+
* appear in the message or two occurrences of the same underlying storm
|
|
53
|
+
* would never fingerprint into the same issue.
|
|
54
|
+
*/
|
|
55
|
+
function describeRetryStorm(finding) {
|
|
56
|
+
return `Repeated failing calls to ${finding.method} ${finding.host}${finding.path} — possible retry storm without backoff`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A real Error subclass, not a plain Error with `.name` reassigned — same
|
|
60
|
+
* reasoning as PotentialNPlusOneQuery: capture() derives the payload's
|
|
61
|
+
* error.type (which becomes the issue title verbatim) from
|
|
62
|
+
* err.constructor.name, which for a plain Error is always "Error" no matter
|
|
63
|
+
* what `.name` gets set to afterward.
|
|
64
|
+
*/
|
|
65
|
+
class PotentialRetryStorm extends Error {
|
|
66
|
+
constructor(finding) {
|
|
67
|
+
super(describeRetryStorm(finding));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.PotentialRetryStorm = PotentialRetryStorm;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RulesPoller = void 0;
|
|
4
|
+
const httpFetch_js_1 = require("./httpFetch.js");
|
|
5
|
+
const WARN_INTERVAL_MS = 60_000;
|
|
6
|
+
// Periodically fetches the tenant's business-invariant rules from the
|
|
7
|
+
// control plane and caches the enabled subset in memory — the mechanism
|
|
8
|
+
// that lets a dashboard-authored rule take effect in a running process
|
|
9
|
+
// without a recompile/redeploy. Same start()/stop()/setInterval+unref shape
|
|
10
|
+
// as BatchTransport (transport.ts), the SDK's other background timer.
|
|
11
|
+
class RulesPoller {
|
|
12
|
+
rules = [];
|
|
13
|
+
timer = null;
|
|
14
|
+
lastWarnAt = 0;
|
|
15
|
+
endpoint;
|
|
16
|
+
apiKey;
|
|
17
|
+
intervalMs;
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
this.endpoint = opts.endpoint;
|
|
20
|
+
this.apiKey = opts.apiKey;
|
|
21
|
+
this.intervalMs = opts.intervalMs ?? 60_000;
|
|
22
|
+
}
|
|
23
|
+
start() {
|
|
24
|
+
void this.refresh(); // don't wait a full interval before rules are usable
|
|
25
|
+
this.timer = setInterval(() => void this.refresh(), this.intervalMs);
|
|
26
|
+
if (this.timer.unref)
|
|
27
|
+
this.timer.unref(); // don't block process exit
|
|
28
|
+
}
|
|
29
|
+
stop() {
|
|
30
|
+
if (this.timer) {
|
|
31
|
+
clearInterval(this.timer);
|
|
32
|
+
this.timer = null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// The rules currently cached, already filtered to enabled — the control
|
|
36
|
+
// plane returns every rule (dashboard needs to see disabled ones to
|
|
37
|
+
// manage them), so the filtering happens once here rather than on every
|
|
38
|
+
// query in the Prisma extension.
|
|
39
|
+
getEnabledRules() {
|
|
40
|
+
return this.rules;
|
|
41
|
+
}
|
|
42
|
+
async refresh() {
|
|
43
|
+
try {
|
|
44
|
+
const res = await (0, httpFetch_js_1.httpFetch)(this.endpoint, {
|
|
45
|
+
headers: { 'X-Reprova-Key': this.apiKey },
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
this.warnOnce(`reprova: rules fetch returned ${res.status}`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const body = (await res.json());
|
|
52
|
+
this.applyResponse(body);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
this.warnOnce(`reprova: rules fetch error: ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Applies an already-fetched response body directly, with no network call
|
|
59
|
+
// — used by replay mode to seed a one-time snapshot the CLI fetched ahead
|
|
60
|
+
// of time (there's no live control plane to poll from inside a replayed
|
|
61
|
+
// app), so detection logic (invariantRules.ts) sees exactly the same
|
|
62
|
+
// shape it would from a real refresh().
|
|
63
|
+
seed(body) {
|
|
64
|
+
this.applyResponse(body);
|
|
65
|
+
}
|
|
66
|
+
applyResponse(body) {
|
|
67
|
+
this.rules = (body.rules ?? []).filter((r) => r.enabled);
|
|
68
|
+
}
|
|
69
|
+
warnOnce(msg) {
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
if (now - this.lastWarnAt > WARN_INTERVAL_MS) {
|
|
72
|
+
this.lastWarnAt = now;
|
|
73
|
+
console.warn(msg);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.RulesPoller = RulesPoller;
|