@derwinjs/db 0.10.0 → 0.12.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.
@@ -0,0 +1,48 @@
1
+ /**
2
+ * createPrismaCostRegressionEvaluator — Sprint 12 Phase 4 (QAP-135).
3
+ *
4
+ * PR-time gate that compares a proposed fix-attempt's projected cost against
5
+ * the historical median for similar fixes scoped by
6
+ * (projectId, classification, surface) within a trailing window. Composes with
7
+ * the pure helper from @derwinjs/core to compute the verdict.
8
+ *
9
+ * # No new migration
10
+ *
11
+ * Reads the existing `QAFixAttempt.costCents` field (Int?, INTEGER cents —
12
+ * legacy schema, same column BudgetGate aggregates over since Sprint 8
13
+ * Phase 3 / QAP-084). The factory divides by 100 to surface USD floats so
14
+ * the SDK contract surface stays in dollars; no schema migration is needed
15
+ * for QAP-135.
16
+ *
17
+ * # Cohort scoping
18
+ *
19
+ * The QAFixAttempt rows do NOT carry classification or surface directly —
20
+ * those live on the joined QATicket model. Cohort filter is therefore
21
+ * expressed as `where.ticket: { classification, surface }` (the relation
22
+ * field on QAFixAttempt is `ticket`, joined via `qaTicketId`).
23
+ *
24
+ * # Window field
25
+ *
26
+ * QAFixAttempt has `attemptedAt` (not `createdAt`); the trailing-window
27
+ * filter uses that column.
28
+ *
29
+ * # Tenant isolation
30
+ *
31
+ * Every read scopes by `projectId`. Cross-tenant probes naturally surface
32
+ * as `no_baseline_yet` (the cohort findMany returns no rows for the wrong
33
+ * tenant), which is indistinguishable from a real cohort with insufficient
34
+ * samples — Pattern D defense-in-depth.
35
+ */
36
+ import type { PrismaClient } from './prisma.js';
37
+ import { type CostRegressionEvaluator } from '@derwinjs/sdk';
38
+ export interface PrismaCostRegressionEvaluatorConfig {
39
+ /** Generated Prisma client. Pass an instance per process. */
40
+ prisma: PrismaClient;
41
+ /**
42
+ * Optional clock injection for deterministic window-boundary tests. Tests
43
+ * pass a fixed Date; production callers omit (defaults to `new Date()`).
44
+ */
45
+ now?: () => Date;
46
+ }
47
+ export declare function createPrismaCostRegressionEvaluator(config: PrismaCostRegressionEvaluatorConfig): CostRegressionEvaluator;
48
+ //# sourceMappingURL=cost-regression-evaluator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost-regression-evaluator.d.ts","sourceRoot":"","sources":["../src/cost-regression-evaluator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAEL,KAAK,uBAAuB,EAG7B,MAAM,eAAe,CAAC;AAKvB,MAAM,WAAW,mCAAmC;IAClD,6DAA6D;IAC7D,MAAM,EAAE,YAAY,CAAC;IACrB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AA6DD,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,mCAAmC,GAC1C,uBAAuB,CAkDzB"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * createPrismaCostRegressionEvaluator — Sprint 12 Phase 4 (QAP-135).
3
+ *
4
+ * PR-time gate that compares a proposed fix-attempt's projected cost against
5
+ * the historical median for similar fixes scoped by
6
+ * (projectId, classification, surface) within a trailing window. Composes with
7
+ * the pure helper from @derwinjs/core to compute the verdict.
8
+ *
9
+ * # No new migration
10
+ *
11
+ * Reads the existing `QAFixAttempt.costCents` field (Int?, INTEGER cents —
12
+ * legacy schema, same column BudgetGate aggregates over since Sprint 8
13
+ * Phase 3 / QAP-084). The factory divides by 100 to surface USD floats so
14
+ * the SDK contract surface stays in dollars; no schema migration is needed
15
+ * for QAP-135.
16
+ *
17
+ * # Cohort scoping
18
+ *
19
+ * The QAFixAttempt rows do NOT carry classification or surface directly —
20
+ * those live on the joined QATicket model. Cohort filter is therefore
21
+ * expressed as `where.ticket: { classification, surface }` (the relation
22
+ * field on QAFixAttempt is `ticket`, joined via `qaTicketId`).
23
+ *
24
+ * # Window field
25
+ *
26
+ * QAFixAttempt has `attemptedAt` (not `createdAt`); the trailing-window
27
+ * filter uses that column.
28
+ *
29
+ * # Tenant isolation
30
+ *
31
+ * Every read scopes by `projectId`. Cross-tenant probes naturally surface
32
+ * as `no_baseline_yet` (the cohort findMany returns no rows for the wrong
33
+ * tenant), which is indistinguishable from a real cohort with insufficient
34
+ * samples — Pattern D defense-in-depth.
35
+ */
36
+ import { CostRegressionEvaluatorError, } from '@derwinjs/sdk';
37
+ import { DEFAULT_WINDOW_MS, evaluateCostRegression } from '@derwinjs/core';
38
+ // ─── Validation ──────────────────────────────────────────────────────────
39
+ function validateInput(input) {
40
+ if (typeof input.projectId !== 'string' || input.projectId.length === 0) {
41
+ throw new CostRegressionEvaluatorError('invalid_input', 'CostRegressionEvaluator: projectId is required');
42
+ }
43
+ if (typeof input.classification !== 'string' || input.classification.length === 0) {
44
+ throw new CostRegressionEvaluatorError('invalid_input', 'CostRegressionEvaluator: classification is required');
45
+ }
46
+ if (typeof input.surface !== 'string' || input.surface.length === 0) {
47
+ throw new CostRegressionEvaluatorError('invalid_input', 'CostRegressionEvaluator: surface is required');
48
+ }
49
+ if (typeof input.attemptedFixCostUsd !== 'number' ||
50
+ !Number.isFinite(input.attemptedFixCostUsd) ||
51
+ input.attemptedFixCostUsd < 0) {
52
+ throw new CostRegressionEvaluatorError('invalid_input', 'CostRegressionEvaluator: attemptedFixCostUsd must be a non-negative finite number');
53
+ }
54
+ if (input.thresholdMultiplier !== undefined) {
55
+ if (!Number.isFinite(input.thresholdMultiplier) || input.thresholdMultiplier <= 0) {
56
+ throw new CostRegressionEvaluatorError('invalid_input', 'CostRegressionEvaluator: thresholdMultiplier must be a positive finite number');
57
+ }
58
+ }
59
+ if (input.minSamples !== undefined) {
60
+ if (!Number.isInteger(input.minSamples) || input.minSamples < 1) {
61
+ throw new CostRegressionEvaluatorError('invalid_input', 'CostRegressionEvaluator: minSamples must be a positive integer');
62
+ }
63
+ }
64
+ if (input.windowMs !== undefined) {
65
+ if (!Number.isFinite(input.windowMs) || input.windowMs <= 0) {
66
+ throw new CostRegressionEvaluatorError('invalid_input', 'CostRegressionEvaluator: windowMs must be a positive finite number');
67
+ }
68
+ }
69
+ }
70
+ // ─── Factory ─────────────────────────────────────────────────────────────
71
+ export function createPrismaCostRegressionEvaluator(config) {
72
+ const { prisma } = config;
73
+ const now = config.now ?? (() => new Date());
74
+ return {
75
+ async evaluate(input) {
76
+ validateInput(input);
77
+ const windowMs = input.windowMs ?? DEFAULT_WINDOW_MS;
78
+ const since = new Date(now().getTime() - windowMs);
79
+ let attempts;
80
+ try {
81
+ attempts = await prisma.qAFixAttempt.findMany({
82
+ where: {
83
+ projectId: input.projectId,
84
+ attemptedAt: { gte: since },
85
+ costCents: { not: null },
86
+ ticket: {
87
+ classification: input.classification,
88
+ // SurfaceCategory enum — SDK input mirrors QATicket.surface, so
89
+ // Prisma accepts it directly without a cast.
90
+ surface: input.surface,
91
+ },
92
+ },
93
+ select: { costCents: true },
94
+ });
95
+ }
96
+ catch (err) {
97
+ const message = err instanceof Error ? err.message : String(err);
98
+ throw new CostRegressionEvaluatorError('storage_failed', `cost-regression query failed: ${message}`, err);
99
+ }
100
+ // QAFixAttempt.costCents is INT cents (legacy schema, preserved for
101
+ // back-compat with the audit trail). Divide by 100 to surface USD
102
+ // floats so the contract surface stays in dollars. Same conversion
103
+ // BudgetGate applies (see packages/db/src/budget-gate.ts).
104
+ const costsUsd = [];
105
+ for (const a of attempts) {
106
+ if (typeof a.costCents === 'number') {
107
+ costsUsd.push(a.costCents / 100);
108
+ }
109
+ }
110
+ return evaluateCostRegression(input, costsUsd);
111
+ },
112
+ };
113
+ }
114
+ //# sourceMappingURL=cost-regression-evaluator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost-regression-evaluator.js","sourceRoot":"","sources":["../src/cost-regression-evaluator.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -36,5 +36,8 @@ export { createPrismaAutoPromotionEvaluator, DEFAULT_PROMOTION_THRESHOLDS, type
36
36
  export { createPrismaVisualBaselineStore, type PrismaVisualBaselineStoreConfig, } from './visual-baseline-store.js';
37
37
  export { createPrismaContractBaselineStore, type PrismaContractBaselineStoreConfig, } from './contract-baseline-store.js';
38
38
  export { createPrismaTenantFuzzConfigStore, type PrismaTenantFuzzConfigStoreConfig, } from './tenant-fuzz-config-store.js';
39
+ export { createPrismaRumSampleStore, RumSampleStoreError, type PrismaRumSampleStoreConfig, } from './rum-sample-store.js';
40
+ export { createPrismaMilestoneEventStore, type PrismaMilestoneEventStoreConfig, } from './milestone-event-store.js';
41
+ export { createPrismaCostRegressionEvaluator, type PrismaCostRegressionEvaluatorConfig, } from './cost-regression-evaluator.js';
39
42
  export * from './prisma.js';
40
43
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,YAAY,EAAG,cAAuB,CAAC;AAIpD,OAAO,EAAE,yBAAyB,EAAE,KAAK,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACjG,OAAO,EACL,6BAA6B,EAC7B,KAAK,6BAA6B,GACnC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EAAE,0BAA0B,EAAE,KAAK,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACpG,OAAO,EAAE,yBAAyB,EAAE,KAAK,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACjG,OAAO,EACL,6BAA6B,EAC7B,KAAK,6BAA6B,GACnC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,iCAAiC,EACjC,KAAK,iCAAiC,GACvC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,kCAAkC,EAClC,KAAK,kCAAkC,GACxC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,+BAA+B,EAC/B,KAAK,+BAA+B,GACrC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,8BAA8B,EAC9B,KAAK,8BAA8B,GACpC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,yBAAyB,EAAE,KAAK,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChG,OAAO,EACL,4BAA4B,EAC5B,SAAS,EACT,KAAK,4BAA4B,GAClC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uCAAuC,EACvC,KAAK,uCAAuC,GAC7C,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,qCAAqC,EACrC,KAAK,qCAAqC,GAC3C,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,iCAAiC,EACjC,SAAS,EACT,cAAc,EACd,KAAK,iCAAiC,GACvC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AACvF,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,EAC9B,KAAK,+BAA+B,GACrC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,4BAA4B,EAC5B,KAAK,4BAA4B,GAClC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,kCAAkC,EAClC,4BAA4B,EAC5B,KAAK,kCAAkC,GACxC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,+BAA+B,EAC/B,KAAK,+BAA+B,GACrC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,iCAAiC,EACjC,KAAK,iCAAiC,GACvC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,iCAAiC,EACjC,KAAK,iCAAiC,GACvC,MAAM,+BAA+B,CAAC;AA4BvC,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,YAAY,EAAG,cAAuB,CAAC;AAIpD,OAAO,EAAE,yBAAyB,EAAE,KAAK,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACjG,OAAO,EACL,6BAA6B,EAC7B,KAAK,6BAA6B,GACnC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EAAE,0BAA0B,EAAE,KAAK,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACpG,OAAO,EAAE,yBAAyB,EAAE,KAAK,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACjG,OAAO,EACL,6BAA6B,EAC7B,KAAK,6BAA6B,GACnC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,iCAAiC,EACjC,KAAK,iCAAiC,GACvC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,kCAAkC,EAClC,KAAK,kCAAkC,GACxC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,+BAA+B,EAC/B,KAAK,+BAA+B,GACrC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,8BAA8B,EAC9B,KAAK,8BAA8B,GACpC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,yBAAyB,EAAE,KAAK,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChG,OAAO,EACL,4BAA4B,EAC5B,SAAS,EACT,KAAK,4BAA4B,GAClC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uCAAuC,EACvC,KAAK,uCAAuC,GAC7C,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,qCAAqC,EACrC,KAAK,qCAAqC,GAC3C,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,iCAAiC,EACjC,SAAS,EACT,cAAc,EACd,KAAK,iCAAiC,GACvC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AACvF,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,EAC9B,KAAK,+BAA+B,GACrC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,4BAA4B,EAC5B,KAAK,4BAA4B,GAClC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,kCAAkC,EAClC,4BAA4B,EAC5B,KAAK,kCAAkC,GACxC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,+BAA+B,EAC/B,KAAK,+BAA+B,GACrC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,iCAAiC,EACjC,KAAK,iCAAiC,GACvC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,iCAAiC,EACjC,KAAK,iCAAiC,GACvC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,KAAK,0BAA0B,GAChC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,+BAA+B,EAC/B,KAAK,+BAA+B,GACrC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,mCAAmC,EACnC,KAAK,mCAAmC,GACzC,MAAM,gCAAgC,CAAC;AA4BxC,cAAc,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -37,6 +37,9 @@ export { createPrismaAutoPromotionEvaluator, DEFAULT_PROMOTION_THRESHOLDS, } fro
37
37
  export { createPrismaVisualBaselineStore, } from './visual-baseline-store.js';
38
38
  export { createPrismaContractBaselineStore, } from './contract-baseline-store.js';
39
39
  export { createPrismaTenantFuzzConfigStore, } from './tenant-fuzz-config-store.js';
40
+ export { createPrismaRumSampleStore, RumSampleStoreError, } from './rum-sample-store.js';
41
+ export { createPrismaMilestoneEventStore, } from './milestone-event-store.js';
42
+ export { createPrismaCostRegressionEvaluator, } from './cost-regression-evaluator.js';
40
43
  // ─── Prisma client re-export ─────────────────────────────────────────────
41
44
  //
42
45
  // @derwinjs/db ships its OWN generated Prisma client (output: prisma-client/
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,cAAuB,CAAC;AAEpD,4EAA4E;AAE5E,OAAO,EAAE,yBAAyB,EAAkC,MAAM,sBAAsB,CAAC;AACjG,OAAO,EACL,6BAA6B,GAE9B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,sBAAsB,EAA+B,MAAM,mBAAmB,CAAC;AACxF,OAAO,EAAE,0BAA0B,EAAmC,MAAM,uBAAuB,CAAC;AACpG,OAAO,EAAE,yBAAyB,EAAkC,MAAM,sBAAsB,CAAC;AACjG,OAAO,EACL,6BAA6B,GAE9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,iCAAiC,GAElC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,kCAAkC,GAEnC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,+BAA+B,GAEhC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,8BAA8B,GAE/B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,yBAAyB,EAAkC,MAAM,qBAAqB,CAAC;AAChG,OAAO,EACL,4BAA4B,EAC5B,SAAS,GAEV,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uCAAuC,GAExC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,qCAAqC,GAEtC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,iCAAiC,EACjC,SAAS,EACT,cAAc,GAEf,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAA+B,MAAM,kBAAkB,CAAC;AACvF,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,GAE/B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,4BAA4B,GAE7B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,kCAAkC,EAClC,4BAA4B,GAE7B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,+BAA+B,GAEhC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,iCAAiC,GAElC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,iCAAiC,GAElC,MAAM,+BAA+B,CAAC;AAEvC,4EAA4E;AAC5E,EAAE;AACF,6EAA6E;AAC7E,wEAAwE;AACxE,sEAAsE;AACtE,wEAAwE;AACxE,wEAAwE;AACxE,gDAAgD;AAChD,EAAE;AACF,+DAA+D;AAC/D,0EAA0E;AAC1E,yCAAyC;AACzC,+EAA+E;AAC/E,yCAAyC;AACzC,uEAAuE;AACvE,sEAAsE;AACtE,kEAAkE;AAClE,+BAA+B;AAC/B,EAAE;AACF,oBAAoB;AACpB,8DAA8D;AAC9D,4CAA4C;AAC5C,qEAAqE;AACrE,QAAQ;AACR,gDAAgD;AAEhD,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,cAAuB,CAAC;AAEpD,4EAA4E;AAE5E,OAAO,EAAE,yBAAyB,EAAkC,MAAM,sBAAsB,CAAC;AACjG,OAAO,EACL,6BAA6B,GAE9B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,sBAAsB,EAA+B,MAAM,mBAAmB,CAAC;AACxF,OAAO,EAAE,0BAA0B,EAAmC,MAAM,uBAAuB,CAAC;AACpG,OAAO,EAAE,yBAAyB,EAAkC,MAAM,sBAAsB,CAAC;AACjG,OAAO,EACL,6BAA6B,GAE9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,iCAAiC,GAElC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,kCAAkC,GAEnC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,+BAA+B,GAEhC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,8BAA8B,GAE/B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,yBAAyB,EAAkC,MAAM,qBAAqB,CAAC;AAChG,OAAO,EACL,4BAA4B,EAC5B,SAAS,GAEV,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uCAAuC,GAExC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,qCAAqC,GAEtC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,iCAAiC,EACjC,SAAS,EACT,cAAc,GAEf,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAA+B,MAAM,kBAAkB,CAAC;AACvF,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,GAE/B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,4BAA4B,GAE7B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,kCAAkC,EAClC,4BAA4B,GAE7B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,+BAA+B,GAEhC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,iCAAiC,GAElC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,iCAAiC,GAElC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,GAEpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,+BAA+B,GAEhC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,mCAAmC,GAEpC,MAAM,gCAAgC,CAAC;AAExC,4EAA4E;AAC5E,EAAE;AACF,6EAA6E;AAC7E,wEAAwE;AACxE,sEAAsE;AACtE,wEAAwE;AACxE,wEAAwE;AACxE,gDAAgD;AAChD,EAAE;AACF,+DAA+D;AAC/D,0EAA0E;AAC1E,yCAAyC;AACzC,+EAA+E;AAC/E,yCAAyC;AACzC,uEAAuE;AACvE,sEAAsE;AACtE,kEAAkE;AAClE,+BAA+B;AAC/B,EAAE;AACF,oBAAoB;AACpB,8DAA8D;AAC9D,4CAA4C;AAC5C,qEAAqE;AACrE,QAAQ;AACR,gDAAgD;AAEhD,cAAc,aAAa,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * createPrismaMilestoneEventStore — Prisma-backed implementation of the SDK
3
+ * MilestoneEventStore contract introduced by QAP-115.
4
+ *
5
+ * Sprint 11 Phase 5 (Cross-product tracking timeline). One row per
6
+ * milestone in the `milestone_events` table, project-scoped via FK. v1
7
+ * is read-mostly (CRUD); drag-to-reschedule deferred to Sprint 14.
8
+ *
9
+ * Tenant isolation: every method scopes by projectId. Pattern D applies —
10
+ * `update` / `delete` against a row owned by a different projectId
11
+ * surfaces as `not_found` rather than `tenant_mismatch` (defense-in-depth
12
+ * against tenant enumeration). The composite (id, projectId) unique-ish
13
+ * lookup on `update` / `delete` is what enforces this — Prisma's
14
+ * `update({ where: { id, projectId } })` raises P2025 when nothing
15
+ * matches, and `deleteMany({ where: { id, projectId } })` returns
16
+ * `count: 0`.
17
+ *
18
+ * Validation runs at the factory boundary BEFORE any Prisma call — empty
19
+ * `name` / `kind` / `createdBy` / `projectId` throw `invalid_input` so
20
+ * the error surfaces with a clear caller-bug code rather than as a
21
+ * Prisma constraint violation.
22
+ *
23
+ * The kind column is intentionally a free-form String (not a Prisma
24
+ * enum). The factory enforces non-empty + trims; consumers may emit
25
+ * custom kinds (the conflict-detection helpers in @derwinjs/core treat
26
+ * unknown kinds as "no conflict computed against this kind").
27
+ */
28
+ import { type PrismaClient } from './prisma.js';
29
+ import { type MilestoneEventStore } from '@derwinjs/sdk';
30
+ export interface PrismaMilestoneEventStoreConfig {
31
+ /** Generated Prisma client. Pass an instance per process. */
32
+ prisma: PrismaClient;
33
+ }
34
+ export declare function createPrismaMilestoneEventStore(config: PrismaMilestoneEventStoreConfig): MilestoneEventStore;
35
+ //# sourceMappingURL=milestone-event-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"milestone-event-store.d.ts","sourceRoot":"","sources":["../src/milestone-event-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAKL,KAAK,mBAAmB,EAEzB,MAAM,eAAe,CAAC;AAIvB,MAAM,WAAW,+BAA+B;IAC9C,6DAA6D;IAC7D,MAAM,EAAE,YAAY,CAAC;CACtB;AA6ED,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,+BAA+B,GACtC,mBAAmB,CAqJrB"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * createPrismaMilestoneEventStore — Prisma-backed implementation of the SDK
3
+ * MilestoneEventStore contract introduced by QAP-115.
4
+ *
5
+ * Sprint 11 Phase 5 (Cross-product tracking timeline). One row per
6
+ * milestone in the `milestone_events` table, project-scoped via FK. v1
7
+ * is read-mostly (CRUD); drag-to-reschedule deferred to Sprint 14.
8
+ *
9
+ * Tenant isolation: every method scopes by projectId. Pattern D applies —
10
+ * `update` / `delete` against a row owned by a different projectId
11
+ * surfaces as `not_found` rather than `tenant_mismatch` (defense-in-depth
12
+ * against tenant enumeration). The composite (id, projectId) unique-ish
13
+ * lookup on `update` / `delete` is what enforces this — Prisma's
14
+ * `update({ where: { id, projectId } })` raises P2025 when nothing
15
+ * matches, and `deleteMany({ where: { id, projectId } })` returns
16
+ * `count: 0`.
17
+ *
18
+ * Validation runs at the factory boundary BEFORE any Prisma call — empty
19
+ * `name` / `kind` / `createdBy` / `projectId` throw `invalid_input` so
20
+ * the error surfaces with a clear caller-bug code rather than as a
21
+ * Prisma constraint violation.
22
+ *
23
+ * The kind column is intentionally a free-form String (not a Prisma
24
+ * enum). The factory enforces non-empty + trims; consumers may emit
25
+ * custom kinds (the conflict-detection helpers in @derwinjs/core treat
26
+ * unknown kinds as "no conflict computed against this kind").
27
+ */
28
+ import { Prisma } from './prisma.js';
29
+ import { MilestoneEventStoreError, } from '@derwinjs/sdk';
30
+ // ─── Validation ──────────────────────────────────────────────────────────
31
+ function assertNonEmpty(value, fieldName) {
32
+ if (typeof value !== 'string' || value.trim() === '') {
33
+ throw new MilestoneEventStoreError('invalid_input', `createPrismaMilestoneEventStore: ${fieldName} is required and non-empty`);
34
+ }
35
+ }
36
+ function assertDate(value, fieldName) {
37
+ if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
38
+ throw new MilestoneEventStoreError('invalid_input', `createPrismaMilestoneEventStore: ${fieldName} must be a valid Date`);
39
+ }
40
+ }
41
+ function assertCreateInput(input) {
42
+ assertNonEmpty(input.projectId, 'projectId');
43
+ assertNonEmpty(input.name, 'name');
44
+ assertNonEmpty(input.kind, 'kind');
45
+ assertNonEmpty(input.createdBy, 'createdBy');
46
+ assertDate(input.startsAt, 'startsAt');
47
+ if (input.endsAt !== undefined) {
48
+ assertDate(input.endsAt, 'endsAt');
49
+ if (input.endsAt.getTime() < input.startsAt.getTime()) {
50
+ throw new MilestoneEventStoreError('invalid_input', 'createPrismaMilestoneEventStore: endsAt must be greater than or equal to startsAt');
51
+ }
52
+ }
53
+ if (input.description !== undefined && typeof input.description !== 'string') {
54
+ throw new MilestoneEventStoreError('invalid_input', 'createPrismaMilestoneEventStore: description must be a string when present');
55
+ }
56
+ }
57
+ function assertUpdatePatch(patch) {
58
+ if (patch.name !== undefined)
59
+ assertNonEmpty(patch.name, 'name');
60
+ if (patch.kind !== undefined)
61
+ assertNonEmpty(patch.kind, 'kind');
62
+ if (patch.startsAt !== undefined)
63
+ assertDate(patch.startsAt, 'startsAt');
64
+ if (patch.endsAt !== undefined && patch.endsAt !== null)
65
+ assertDate(patch.endsAt, 'endsAt');
66
+ if (patch.description !== undefined &&
67
+ patch.description !== null &&
68
+ typeof patch.description !== 'string') {
69
+ throw new MilestoneEventStoreError('invalid_input', 'createPrismaMilestoneEventStore: description must be a string or null when present');
70
+ }
71
+ // Cross-field check: if BOTH startsAt and endsAt are in the patch and
72
+ // both non-null, they must satisfy startsAt <= endsAt.
73
+ if (patch.startsAt !== undefined &&
74
+ patch.endsAt !== undefined &&
75
+ patch.endsAt !== null &&
76
+ patch.endsAt.getTime() < patch.startsAt.getTime()) {
77
+ throw new MilestoneEventStoreError('invalid_input', 'createPrismaMilestoneEventStore: endsAt must be greater than or equal to startsAt');
78
+ }
79
+ }
80
+ // ─── Factory ─────────────────────────────────────────────────────────────
81
+ export function createPrismaMilestoneEventStore(config) {
82
+ const { prisma } = config;
83
+ return {
84
+ async list(filter) {
85
+ assertNonEmpty(filter.projectId, 'projectId');
86
+ const where = { projectId: filter.projectId };
87
+ const now = Date.now();
88
+ if (typeof filter.sinceMs === 'number' && Number.isFinite(filter.sinceMs)) {
89
+ const since = new Date(now - filter.sinceMs);
90
+ // A range milestone is "in window" if its startsAt OR endsAt
91
+ // falls in the trailing window. Point-in-time milestones (no
92
+ // endsAt) match on startsAt only.
93
+ where.OR = [{ startsAt: { gte: since } }, { endsAt: { gte: since } }];
94
+ }
95
+ if (typeof filter.untilMs === 'number' && Number.isFinite(filter.untilMs)) {
96
+ const until = new Date(now + filter.untilMs);
97
+ where.startsAt = { ...where.startsAt, lte: until };
98
+ }
99
+ if (Array.isArray(filter.kinds) && filter.kinds.length > 0) {
100
+ where.kind = { in: filter.kinds };
101
+ }
102
+ try {
103
+ const rows = await prisma.milestoneEvent.findMany({
104
+ where,
105
+ orderBy: { startsAt: 'asc' },
106
+ });
107
+ return rows.map(toMilestoneEvent);
108
+ }
109
+ catch (err) {
110
+ throw new MilestoneEventStoreError('storage_failed', `createPrismaMilestoneEventStore: list raised: ${err.message}`, err);
111
+ }
112
+ },
113
+ async create(input) {
114
+ assertCreateInput(input);
115
+ try {
116
+ const row = await prisma.milestoneEvent.create({
117
+ data: {
118
+ projectId: input.projectId,
119
+ name: input.name,
120
+ description: input.description ?? null,
121
+ startsAt: input.startsAt,
122
+ endsAt: input.endsAt ?? null,
123
+ kind: input.kind,
124
+ createdBy: input.createdBy,
125
+ },
126
+ });
127
+ return toMilestoneEvent(row);
128
+ }
129
+ catch (err) {
130
+ if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2003') {
131
+ // FK violation on projectId — surface as invalid_input rather
132
+ // than storage_failed so the API layer can return 400 (the
133
+ // operator passed a nonexistent projectId, not a transient
134
+ // storage problem).
135
+ throw new MilestoneEventStoreError('invalid_input', `createPrismaMilestoneEventStore: projectId ${input.projectId} does not resolve to a Project row`, err);
136
+ }
137
+ throw new MilestoneEventStoreError('storage_failed', `createPrismaMilestoneEventStore: create raised: ${err.message}`, err);
138
+ }
139
+ },
140
+ async update(id, projectId, patch) {
141
+ assertNonEmpty(id, 'id');
142
+ assertNonEmpty(projectId, 'projectId');
143
+ assertUpdatePatch(patch);
144
+ // Two-step: updateMany returns count so we can distinguish
145
+ // not-found / wrong-tenant (count === 0) from a successful update
146
+ // without leaking existence via the standard `update` API's P2025.
147
+ try {
148
+ const data = {};
149
+ if (patch.name !== undefined)
150
+ data.name = patch.name;
151
+ if (patch.description !== undefined)
152
+ data.description = patch.description;
153
+ if (patch.startsAt !== undefined)
154
+ data.startsAt = patch.startsAt;
155
+ if (patch.endsAt !== undefined)
156
+ data.endsAt = patch.endsAt;
157
+ if (patch.kind !== undefined)
158
+ data.kind = patch.kind;
159
+ const result = await prisma.milestoneEvent.updateMany({
160
+ where: { id, projectId },
161
+ data,
162
+ });
163
+ if (result.count === 0) {
164
+ throw new MilestoneEventStoreError('not_found', `createPrismaMilestoneEventStore: milestone id=${id} not found in project ${projectId}`);
165
+ }
166
+ // Re-read to return the fresh row. updatedAt has been refreshed
167
+ // by Prisma's @updatedAt directive at this point.
168
+ const fresh = await prisma.milestoneEvent.findFirst({
169
+ where: { id, projectId },
170
+ });
171
+ if (fresh === null) {
172
+ // Concurrent delete between updateMany + findFirst. Rare; surface
173
+ // as not_found.
174
+ throw new MilestoneEventStoreError('not_found', `createPrismaMilestoneEventStore: milestone id=${id} disappeared after update in project ${projectId}`);
175
+ }
176
+ return toMilestoneEvent(fresh);
177
+ }
178
+ catch (err) {
179
+ if (err instanceof MilestoneEventStoreError)
180
+ throw err;
181
+ throw new MilestoneEventStoreError('storage_failed', `createPrismaMilestoneEventStore: update raised: ${err.message}`, err);
182
+ }
183
+ },
184
+ async delete(id, projectId) {
185
+ assertNonEmpty(id, 'id');
186
+ assertNonEmpty(projectId, 'projectId');
187
+ try {
188
+ const result = await prisma.milestoneEvent.deleteMany({
189
+ where: { id, projectId },
190
+ });
191
+ return result.count > 0;
192
+ }
193
+ catch (err) {
194
+ throw new MilestoneEventStoreError('storage_failed', `createPrismaMilestoneEventStore: delete raised: ${err.message}`, err);
195
+ }
196
+ },
197
+ };
198
+ }
199
+ /**
200
+ * Project a Prisma row to the SDK MilestoneEvent shape. Per memory
201
+ * rule 22, no defensive `as PrismaMilestoneEventRow` cast at the
202
+ * argument site — the inferred Prisma return type is exact after
203
+ * `prisma generate`. The inline interface above just documents the
204
+ * shape for readers; it's structurally compatible with the generated
205
+ * MilestoneEventGetPayload.
206
+ */
207
+ function toMilestoneEvent(row) {
208
+ return {
209
+ id: row.id,
210
+ projectId: row.projectId,
211
+ name: row.name,
212
+ description: row.description,
213
+ startsAt: row.startsAt,
214
+ endsAt: row.endsAt,
215
+ kind: row.kind,
216
+ createdBy: row.createdBy,
217
+ createdAt: row.createdAt,
218
+ updatedAt: row.updatedAt,
219
+ };
220
+ }
221
+ //# sourceMappingURL=milestone-event-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"milestone-event-store.js","sourceRoot":"","sources":["../src/milestone-event-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * createPrismaRumSampleStore — Prisma-backed implementation of the SDK
3
+ * RumSampleStore contract introduced by QAP-113.
4
+ *
5
+ * Sprint 11 Phase 3 (Test Surfaces — RUM ingestion). Samples are stored as
6
+ * one row per insert against the `RumSample` model. There is no uniqueness
7
+ * constraint on (projectId, metric) — a single page-load may emit multiple
8
+ * samples per metric, and operators need the full distribution to derive
9
+ * percentiles.
10
+ *
11
+ * Tenant isolation: every method scopes by projectId. Pattern D applies —
12
+ * unknown / wrong-project lookups return null from `getBaseline` rather
13
+ * than throwing or leaking existence (defense-in-depth against tenant
14
+ * enumeration). The "no samples in window" return is also null, so
15
+ * cross-tenant probes look identical to "no data this window".
16
+ *
17
+ * Percentile math: derived in-memory via array-index math after the
18
+ * `findMany` returns a value-sorted result set. No third-party stats
19
+ * library — RumSample row sets per (projectId, metric, window) cap at the
20
+ * tens-of-thousands order of magnitude, which fits comfortably in process
21
+ * memory for the percentile read.
22
+ */
23
+ import type { PrismaClient } from './prisma.js';
24
+ import type { RumSampleStore } from '@derwinjs/sdk';
25
+ export interface PrismaRumSampleStoreConfig {
26
+ /** Generated Prisma client. Pass an instance per process. */
27
+ prisma: PrismaClient;
28
+ }
29
+ /**
30
+ * Errors thrown by the Prisma-backed RumSampleStore. The store-level
31
+ * errors are kept inside @derwinjs/db (NOT promoted to @derwinjs/sdk)
32
+ * because they reflect storage-layer specifics; the route layer maps
33
+ * them to the operator-facing WebVitalsIngestorError before crossing
34
+ * the SDK boundary.
35
+ *
36
+ * The discriminating `code` field lets callers route handling:
37
+ * - `invalid_input` — caller bug rejected at the factory boundary; do
38
+ * not retry.
39
+ * - `io_failed` — underlying Prisma call raised; transient — retry
40
+ * or escalate.
41
+ */
42
+ export declare class RumSampleStoreError extends Error {
43
+ readonly code: 'invalid_input' | 'io_failed';
44
+ readonly cause?: unknown | undefined;
45
+ constructor(code: 'invalid_input' | 'io_failed', message: string, cause?: unknown | undefined);
46
+ }
47
+ export declare function createPrismaRumSampleStore(config: PrismaRumSampleStoreConfig): RumSampleStore;
48
+ //# sourceMappingURL=rum-sample-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rum-sample-store.d.ts","sourceRoot":"","sources":["../src/rum-sample-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,eAAe,CAAC;AAIjE,MAAM,WAAW,0BAA0B;IACzC,6DAA6D;IAC7D,MAAM,EAAE,YAAY,CAAC;CACtB;AAID;;;;;;;;;;;;GAYG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,IAAI,EAAE,eAAe,GAAG,WAAW;aAE1B,KAAK,CAAC,EAAE,OAAO;gBAFxB,IAAI,EAAE,eAAe,GAAG,WAAW,EACnD,OAAO,EAAE,MAAM,EACU,KAAK,CAAC,EAAE,OAAO,YAAA;CAM3C;AAmED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,0BAA0B,GAAG,cAAc,CAwF7F"}