@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,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;