@reaatech/prompt-version-control-server 0.1.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 (136) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +261 -0
  4. package/dist/api/routes/deployments.d.ts +4 -0
  5. package/dist/api/routes/deployments.d.ts.map +1 -0
  6. package/dist/api/routes/deployments.js +48 -0
  7. package/dist/api/routes/deployments.js.map +1 -0
  8. package/dist/api/routes/docs.d.ts +4 -0
  9. package/dist/api/routes/docs.d.ts.map +1 -0
  10. package/dist/api/routes/docs.js +75 -0
  11. package/dist/api/routes/docs.js.map +1 -0
  12. package/dist/api/routes/evaluations.d.ts +4 -0
  13. package/dist/api/routes/evaluations.d.ts.map +1 -0
  14. package/dist/api/routes/evaluations.js +63 -0
  15. package/dist/api/routes/evaluations.js.map +1 -0
  16. package/dist/api/routes/metrics.d.ts +4 -0
  17. package/dist/api/routes/metrics.d.ts.map +1 -0
  18. package/dist/api/routes/metrics.js +38 -0
  19. package/dist/api/routes/metrics.js.map +1 -0
  20. package/dist/api/routes/promotions.d.ts +4 -0
  21. package/dist/api/routes/promotions.d.ts.map +1 -0
  22. package/dist/api/routes/promotions.js +95 -0
  23. package/dist/api/routes/promotions.js.map +1 -0
  24. package/dist/api/routes/prompts.d.ts +4 -0
  25. package/dist/api/routes/prompts.d.ts.map +1 -0
  26. package/dist/api/routes/prompts.js +80 -0
  27. package/dist/api/routes/prompts.js.map +1 -0
  28. package/dist/api/routes/render.d.ts +4 -0
  29. package/dist/api/routes/render.d.ts.map +1 -0
  30. package/dist/api/routes/render.js +44 -0
  31. package/dist/api/routes/render.js.map +1 -0
  32. package/dist/api/routes/tags.d.ts +4 -0
  33. package/dist/api/routes/tags.d.ts.map +1 -0
  34. package/dist/api/routes/tags.js +41 -0
  35. package/dist/api/routes/tags.js.map +1 -0
  36. package/dist/api/routes/webhooks.d.ts +4 -0
  37. package/dist/api/routes/webhooks.d.ts.map +1 -0
  38. package/dist/api/routes/webhooks.js +49 -0
  39. package/dist/api/routes/webhooks.js.map +1 -0
  40. package/dist/db/client.d.ts +3 -0
  41. package/dist/db/client.d.ts.map +1 -0
  42. package/dist/db/client.js +21 -0
  43. package/dist/db/client.js.map +1 -0
  44. package/dist/db/redis.d.ts +3 -0
  45. package/dist/db/redis.d.ts.map +1 -0
  46. package/dist/db/redis.js +18 -0
  47. package/dist/db/redis.js.map +1 -0
  48. package/dist/errors.d.ts +22 -0
  49. package/dist/errors.d.ts.map +1 -0
  50. package/dist/errors.js +39 -0
  51. package/dist/errors.js.map +1 -0
  52. package/dist/index.d.ts +4 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +83 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/middleware/audit.d.ts +3 -0
  57. package/dist/middleware/audit.d.ts.map +1 -0
  58. package/dist/middleware/audit.js +39 -0
  59. package/dist/middleware/audit.js.map +1 -0
  60. package/dist/middleware/auth.d.ts +3 -0
  61. package/dist/middleware/auth.d.ts.map +1 -0
  62. package/dist/middleware/auth.js +49 -0
  63. package/dist/middleware/auth.js.map +1 -0
  64. package/dist/middleware/error-handler.d.ts +3 -0
  65. package/dist/middleware/error-handler.d.ts.map +1 -0
  66. package/dist/middleware/error-handler.js +36 -0
  67. package/dist/middleware/error-handler.js.map +1 -0
  68. package/dist/middleware/metrics.d.ts +3 -0
  69. package/dist/middleware/metrics.d.ts.map +1 -0
  70. package/dist/middleware/metrics.js +13 -0
  71. package/dist/middleware/metrics.js.map +1 -0
  72. package/dist/middleware/rate-limit.d.ts +8 -0
  73. package/dist/middleware/rate-limit.d.ts.map +1 -0
  74. package/dist/middleware/rate-limit.js +89 -0
  75. package/dist/middleware/rate-limit.js.map +1 -0
  76. package/dist/middleware/request-id.d.ts +3 -0
  77. package/dist/middleware/request-id.d.ts.map +1 -0
  78. package/dist/middleware/request-id.js +10 -0
  79. package/dist/middleware/request-id.js.map +1 -0
  80. package/dist/services/audit.service.d.ts +6 -0
  81. package/dist/services/audit.service.d.ts.map +1 -0
  82. package/dist/services/audit.service.js +26 -0
  83. package/dist/services/audit.service.js.map +1 -0
  84. package/dist/services/deployment.service.d.ts +101 -0
  85. package/dist/services/deployment.service.d.ts.map +1 -0
  86. package/dist/services/deployment.service.js +127 -0
  87. package/dist/services/deployment.service.js.map +1 -0
  88. package/dist/services/diff.engine.d.ts +13 -0
  89. package/dist/services/diff.engine.d.ts.map +1 -0
  90. package/dist/services/diff.engine.js +101 -0
  91. package/dist/services/diff.engine.js.map +1 -0
  92. package/dist/services/eval.service.d.ts +55 -0
  93. package/dist/services/eval.service.d.ts.map +1 -0
  94. package/dist/services/eval.service.js +151 -0
  95. package/dist/services/eval.service.js.map +1 -0
  96. package/dist/services/event.bus.d.ts +18 -0
  97. package/dist/services/event.bus.d.ts.map +1 -0
  98. package/dist/services/event.bus.js +35 -0
  99. package/dist/services/event.bus.js.map +1 -0
  100. package/dist/services/metric.service.d.ts +33 -0
  101. package/dist/services/metric.service.d.ts.map +1 -0
  102. package/dist/services/metric.service.js +64 -0
  103. package/dist/services/metric.service.js.map +1 -0
  104. package/dist/services/prometheus.service.d.ts +7 -0
  105. package/dist/services/prometheus.service.d.ts.map +1 -0
  106. package/dist/services/prometheus.service.js +27 -0
  107. package/dist/services/prometheus.service.js.map +1 -0
  108. package/dist/services/prompt.service.d.ts +59 -0
  109. package/dist/services/prompt.service.d.ts.map +1 -0
  110. package/dist/services/prompt.service.js +144 -0
  111. package/dist/services/prompt.service.js.map +1 -0
  112. package/dist/services/tag.service.d.ts +46 -0
  113. package/dist/services/tag.service.d.ts.map +1 -0
  114. package/dist/services/tag.service.js +84 -0
  115. package/dist/services/tag.service.js.map +1 -0
  116. package/dist/services/webhook.service.d.ts +50 -0
  117. package/dist/services/webhook.service.d.ts.map +1 -0
  118. package/dist/services/webhook.service.js +196 -0
  119. package/dist/services/webhook.service.js.map +1 -0
  120. package/dist/types/hono.d.ts +10 -0
  121. package/dist/types/hono.d.ts.map +1 -0
  122. package/dist/types/hono.js +2 -0
  123. package/dist/types/hono.js.map +1 -0
  124. package/dist/utils/context.d.ts +3 -0
  125. package/dist/utils/context.d.ts.map +1 -0
  126. package/dist/utils/context.js +9 -0
  127. package/dist/utils/context.js.map +1 -0
  128. package/dist/utils/logger.d.ts +3 -0
  129. package/dist/utils/logger.d.ts.map +1 -0
  130. package/dist/utils/logger.js +20 -0
  131. package/dist/utils/logger.js.map +1 -0
  132. package/dist/utils/pagination.d.ts +10 -0
  133. package/dist/utils/pagination.d.ts.map +1 -0
  134. package/dist/utils/pagination.js +10 -0
  135. package/dist/utils/pagination.js.map +1 -0
  136. package/package.json +74 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ export declare function auditMiddleware(resourceType: string, action: 'create' | 'update' | 'delete' | 'promote' | 'rollback'): MiddlewareHandler;
3
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/middleware/audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAK9C,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,GAC9D,iBAAiB,CAoCnB"}
@@ -0,0 +1,39 @@
1
+ import { auditService } from '../services/audit.service.js';
2
+ import { getProjectId } from '../utils/context.js';
3
+ import { logger } from '../utils/logger.js';
4
+ export function auditMiddleware(resourceType, action) {
5
+ return async (c, next) => {
6
+ await next();
7
+ let projectId;
8
+ try {
9
+ projectId = getProjectId(c);
10
+ }
11
+ catch {
12
+ return;
13
+ }
14
+ const apiKey = c.get('apiKey');
15
+ const requestId = c.get('requestId');
16
+ if (!apiKey)
17
+ return;
18
+ const resourceId = c.req.param('id') || 'unknown';
19
+ try {
20
+ await auditService.log({
21
+ actorId: apiKey.id,
22
+ action,
23
+ resourceType,
24
+ resourceId,
25
+ projectId,
26
+ ipAddress: c.req.header('x-forwarded-for') || 'unknown',
27
+ requestId: requestId ?? null,
28
+ before: null,
29
+ after: null,
30
+ });
31
+ }
32
+ catch (err) {
33
+ // Audit logging must never fail the request or crash the process.
34
+ // Log the failure so operators can detect a misconfigured audit store.
35
+ logger.error({ err }, 'audit log failed');
36
+ }
37
+ };
38
+ }
39
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/middleware/audit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,UAAU,eAAe,CAC7B,YAAoB,EACpB,MAA+D;IAE/D,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,MAAM,IAAI,EAAE,CAAC;QAEb,IAAI,SAAiB,CAAC;QACtB,IAAI,CAAC;YACH,SAAS,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAA+B,CAAC;QAC7D,MAAM,SAAS,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,CAAuB,CAAC;QAE3D,IAAI,CAAC,MAAM;YAAE,OAAO;QAEpB,MAAM,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC;QAElD,IAAI,CAAC;YACH,MAAM,YAAY,CAAC,GAAG,CAAC;gBACrB,OAAO,EAAE,MAAM,CAAC,EAAE;gBAClB,MAAM;gBACN,YAAY;gBACZ,UAAU;gBACV,SAAS;gBACT,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,iBAAiB,CAAC,IAAI,SAAS;gBACvD,SAAS,EAAE,SAAS,IAAI,IAAI;gBAC5B,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,IAAI;aACZ,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,kEAAkE;YAClE,uEAAuE;YACvE,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,kBAAkB,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ export declare const authMiddleware: MiddlewareHandler;
3
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAO9C,eAAO,MAAM,cAAc,EAAE,iBAiD5B,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { hashApiKey } from '@reaatech/prompt-version-control-shared';
2
+ import { prisma } from '../db/client.js';
3
+ import { UnauthorizedError } from '../errors.js';
4
+ const LAST_USED_THROTTLE_MS = 60_000;
5
+ const lastUsedCache = new Map();
6
+ export const authMiddleware = async (c, next) => {
7
+ const header = c.req.header('authorization');
8
+ if (!header?.startsWith('Bearer ')) {
9
+ throw new UnauthorizedError('Missing or invalid authorization header');
10
+ }
11
+ const key = header.slice(7);
12
+ if (key.length === 0) {
13
+ throw new UnauthorizedError('Invalid API key');
14
+ }
15
+ const keyHash = hashApiKey(key);
16
+ const apiKey = await prisma.apiKey.findUnique({
17
+ where: { keyHash },
18
+ include: { project: true },
19
+ });
20
+ if (!apiKey) {
21
+ throw new UnauthorizedError('Invalid API key');
22
+ }
23
+ if (apiKey.expiresAt && apiKey.expiresAt < new Date()) {
24
+ throw new UnauthorizedError('API key expired');
25
+ }
26
+ c.set('apiKey', apiKey);
27
+ c.set('project', apiKey.project);
28
+ c.set('projectId', apiKey.projectId);
29
+ // Throttle lastUsedAt updates so high-QPS workloads don't write on every
30
+ // request. Note: this cache is per-process — in a multi-instance deployment
31
+ // each instance writes independently, which is acceptable for a usage
32
+ // timestamp that does not need strict consistency.
33
+ const now = Date.now();
34
+ const last = lastUsedCache.get(apiKey.id) ?? 0;
35
+ if (now - last >= LAST_USED_THROTTLE_MS) {
36
+ lastUsedCache.set(apiKey.id, now);
37
+ try {
38
+ await prisma.apiKey.update({
39
+ where: { id: apiKey.id },
40
+ data: { lastUsedAt: new Date(now) },
41
+ });
42
+ }
43
+ catch {
44
+ // Best-effort — never fail the request because the bookkeeping write failed.
45
+ }
46
+ }
47
+ await next();
48
+ };
49
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAErE,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,qBAAqB,GAAG,MAAM,CAAC;AACrC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAC;AAEhD,MAAM,CAAC,MAAM,cAAc,GAAsB,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IACjE,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,iBAAiB,CAAC,yCAAyC,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAEhC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC;QAC5C,KAAK,EAAE,EAAE,OAAO,EAAE;QAClB,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;KAC3B,CAAC,CAAC;IAEH,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IACjD,CAAC;IAED,IAAI,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC;QACtD,MAAM,IAAI,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IACjD,CAAC;IAED,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxB,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IAErC,yEAAyE;IACzE,4EAA4E;IAC5E,sEAAsE;IACtE,mDAAmD;IACnD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,GAAG,GAAG,IAAI,IAAI,qBAAqB,EAAE,CAAC;QACxC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;gBACzB,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE;gBACxB,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE;aACpC,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,6EAA6E;QAC/E,CAAC;IACH,CAAC;IAED,MAAM,IAAI,EAAE,CAAC;AACf,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ErrorHandler } from 'hono';
2
+ export declare const errorHandler: ErrorHandler;
3
+ //# sourceMappingURL=error-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAKzC,eAAO,MAAM,YAAY,EAAE,YA8C1B,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { ZodError } from 'zod';
2
+ import { AppError, ValidationError } from '../errors.js';
3
+ import { logger } from '../utils/logger.js';
4
+ export const errorHandler = (err, c) => {
5
+ const requestId = c.get('requestId');
6
+ if (err instanceof ZodError) {
7
+ const fields = {};
8
+ for (const issue of err.issues) {
9
+ const path = issue.path.join('.');
10
+ fields[path] = fields[path] ?? [];
11
+ fields[path].push(issue.message);
12
+ }
13
+ const appErr = new ValidationError(fields);
14
+ logger.warn({ requestId, code: appErr.code, fields }, 'validation error');
15
+ return c.json({
16
+ error: { code: appErr.code, message: appErr.message, details: appErr.details },
17
+ requestId,
18
+ timestamp: new Date().toISOString(),
19
+ }, appErr.status);
20
+ }
21
+ if (err instanceof AppError) {
22
+ logger.warn({ requestId, code: err.code, status: err.status, details: err.details }, err.message);
23
+ return c.json({
24
+ error: { code: err.code, message: err.message, details: err.details },
25
+ requestId,
26
+ timestamp: new Date().toISOString(),
27
+ }, err.status);
28
+ }
29
+ logger.error({ requestId, err }, 'unhandled error');
30
+ return c.json({
31
+ error: { code: 'INTERNAL_ERROR', message: 'Internal server error' },
32
+ requestId,
33
+ timestamp: new Date().toISOString(),
34
+ }, 500);
35
+ };
36
+ //# sourceMappingURL=error-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAC/B,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,CAAC,MAAM,YAAY,GAAiB,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;IACnD,MAAM,SAAS,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,CAAuB,CAAC;IAE3D,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;QAC5B,MAAM,MAAM,GAA6B,EAAE,CAAC;QAC5C,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC;QAC1E,OAAO,CAAC,CAAC,IAAI,CACX;YACE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE;YAC9E,SAAS;YACT,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,EACD,MAAM,CAAC,MAAsC,CAC9C,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,CACT,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,EACvE,GAAG,CAAC,OAAO,CACZ,CAAC;QACF,OAAO,CAAC,CAAC,IAAI,CACX;YACE,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE;YACrE,SAAS;YACT,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,EACD,GAAG,CAAC,MAAsC,CAC3C,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,iBAAiB,CAAC,CAAC;IACpD,OAAO,CAAC,CAAC,IAAI,CACX;QACE,KAAK,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,uBAAuB,EAAE;QACnE,SAAS;QACT,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,EACD,GAAG,CACJ,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ export declare const metricsMiddleware: MiddlewareHandler;
3
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/middleware/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAG9C,eAAO,MAAM,iBAAiB,EAAE,iBAc/B,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { httpRequestDuration } from '../services/prometheus.service.js';
2
+ export const metricsMiddleware = async (c, next) => {
3
+ const start = performance.now();
4
+ await next();
5
+ const duration = (performance.now() - start) / 1000;
6
+ const route = c.req.routePath ?? 'unknown';
7
+ httpRequestDuration.observe({
8
+ method: c.req.method,
9
+ route,
10
+ status: String(c.res.status),
11
+ }, duration);
12
+ };
13
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/middleware/metrics.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAExE,MAAM,CAAC,MAAM,iBAAiB,GAAsB,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IACpE,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAChC,MAAM,IAAI,EAAE,CAAC;IACb,MAAM,QAAQ,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;IAEpD,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,IAAI,SAAS,CAAC;IAC3C,mBAAmB,CAAC,OAAO,CACzB;QACE,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM;QACpB,KAAK;QACL,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;KAC7B,EACD,QAAQ,CACT,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ interface RateLimitOpts {
3
+ windowMs: number;
4
+ max: number;
5
+ }
6
+ export declare function rateLimit(opts: RateLimitOpts): MiddlewareHandler;
7
+ export {};
8
+ //# sourceMappingURL=rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAK9C,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AA2FD,wBAAgB,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,iBAAiB,CAWhE"}
@@ -0,0 +1,89 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { redis } from '../db/redis.js';
3
+ import { RateLimitError } from '../errors.js';
4
+ import { logger } from '../utils/logger.js';
5
+ /**
6
+ * Derive a stable, non-sensitive identifier for rate limiting. We never put a
7
+ * raw API key into a Map key, Redis key, or log line.
8
+ */
9
+ function rateLimitId(c) {
10
+ const auth = c.req.header('authorization');
11
+ if (auth?.startsWith('Bearer ')) {
12
+ const key = auth.slice(7);
13
+ if (key.length > 0) {
14
+ return `key:${createHash('sha256').update(key).digest('hex').slice(0, 32)}`;
15
+ }
16
+ }
17
+ const xff = c.req.header('x-forwarded-for');
18
+ if (xff) {
19
+ // Take only the first IP (the client) and hash it for symmetry with key path.
20
+ const client = xff.split(',')[0]?.trim();
21
+ return `ip:${createHash('sha256').update(client).digest('hex').slice(0, 32)}`;
22
+ }
23
+ return 'anonymous';
24
+ }
25
+ function inMemoryRateLimit(opts) {
26
+ const store = new Map();
27
+ const cleanupInterval = setInterval(() => {
28
+ const now = Date.now();
29
+ for (const [key, entry] of store.entries()) {
30
+ if (entry.resetAt < now) {
31
+ store.delete(key);
32
+ }
33
+ }
34
+ }, 60_000);
35
+ cleanupInterval.unref?.();
36
+ return async (c, next) => {
37
+ const key = rateLimitId(c);
38
+ const now = Date.now();
39
+ const entry = store.get(key);
40
+ if (!entry || entry.resetAt < now) {
41
+ store.set(key, { count: 1, resetAt: now + opts.windowMs });
42
+ }
43
+ else {
44
+ entry.count++;
45
+ if (entry.count > opts.max) {
46
+ const retryAfter = Math.ceil((entry.resetAt - now) / 1000);
47
+ throw new RateLimitError(retryAfter);
48
+ }
49
+ }
50
+ await next();
51
+ };
52
+ }
53
+ function redisRateLimit(opts) {
54
+ return async (c, next) => {
55
+ if (!redis)
56
+ return await next();
57
+ const key = rateLimitId(c);
58
+ const redisKey = `rate_limit:${key}`;
59
+ const now = Date.now();
60
+ const windowStart = now - opts.windowMs;
61
+ const member = `${now}-${randomBytes(8).toString('hex')}`;
62
+ const pipeline = redis.multi();
63
+ pipeline.zremrangebyscore(redisKey, 0, windowStart);
64
+ pipeline.zcard(redisKey);
65
+ pipeline.zadd(redisKey, now, member);
66
+ pipeline.pexpire(redisKey, opts.windowMs);
67
+ const results = await pipeline.exec();
68
+ const count = results?.[1]?.[1] ?? 0;
69
+ if (count >= opts.max) {
70
+ const ttl = await redis.pttl(redisKey);
71
+ const retryAfter = Math.ceil(Math.max(ttl, 0) / 1000);
72
+ throw new RateLimitError(retryAfter);
73
+ }
74
+ await next();
75
+ };
76
+ }
77
+ export function rateLimit(opts) {
78
+ if (redis) {
79
+ return redisRateLimit(opts);
80
+ }
81
+ if (process.env.NODE_ENV === 'production') {
82
+ // Redis-backed rate limiting is strongly recommended for multi-instance
83
+ // deployments. In-memory counters are per-process and won't coordinate
84
+ // across instances behind a load balancer.
85
+ logger.warn('REDIS_URL not set — using in-memory rate limiting (per-process, not shared)');
86
+ }
87
+ return inMemoryRateLimit(opts);
88
+ }
89
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/middleware/rate-limit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ export declare const requestIdMiddleware: MiddlewareHandler;
3
+ //# sourceMappingURL=request-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-id.d.ts","sourceRoot":"","sources":["../../src/middleware/request-id.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAI9C,eAAO,MAAM,mBAAmB,EAAE,iBAMjC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ const REQUEST_ID_RE = /^[\w-]{1,128}$/;
3
+ export const requestIdMiddleware = async (c, next) => {
4
+ const rawId = c.req.header('x-request-id');
5
+ const requestId = rawId && REQUEST_ID_RE.test(rawId) ? rawId : randomUUID();
6
+ c.set('requestId', requestId);
7
+ c.header('x-request-id', requestId);
8
+ await next();
9
+ };
10
+ //# sourceMappingURL=request-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-id.js","sourceRoot":"","sources":["../../src/middleware/request-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,MAAM,aAAa,GAAG,gBAAgB,CAAC;AAEvC,MAAM,CAAC,MAAM,mBAAmB,GAAsB,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IACtE,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,KAAK,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;IAC5E,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IAC9B,CAAC,CAAC,MAAM,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IACpC,MAAM,IAAI,EAAE,CAAC;AACf,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { AuditLog } from '@prisma/client';
2
+ export declare class AuditService {
3
+ log(entry: Omit<AuditLog, 'id' | 'createdAt'>): Promise<void>;
4
+ }
5
+ export declare const auditService: AuditService;
6
+ //# sourceMappingURL=audit.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.service.d.ts","sourceRoot":"","sources":["../../src/services/audit.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAU,MAAM,gBAAgB,CAAC;AAIvD,qBAAa,YAAY;IACjB,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAmBpE;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { prisma } from '../db/client.js';
2
+ import { logger } from '../utils/logger.js';
3
+ export class AuditService {
4
+ async log(entry) {
5
+ try {
6
+ await prisma.auditLog.create({
7
+ data: {
8
+ actorId: entry.actorId,
9
+ action: entry.action,
10
+ resourceType: entry.resourceType,
11
+ resourceId: entry.resourceId,
12
+ projectId: entry.projectId,
13
+ before: entry.before,
14
+ after: entry.after,
15
+ ipAddress: entry.ipAddress,
16
+ requestId: entry.requestId,
17
+ },
18
+ });
19
+ }
20
+ catch (err) {
21
+ logger.error({ err, entry }, 'failed to write audit log');
22
+ }
23
+ }
24
+ }
25
+ export const auditService = new AuditService();
26
+ //# sourceMappingURL=audit.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.service.js","sourceRoot":"","sources":["../../src/services/audit.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,OAAO,YAAY;IACvB,KAAK,CAAC,GAAG,CAAC,KAAyC;QACjD,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC3B,IAAI,EAAE;oBACJ,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,YAAY,EAAE,KAAK,CAAC,YAAY;oBAChC,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;oBAC1B,MAAM,EAAE,KAAK,CAAC,MAA+B;oBAC7C,KAAK,EAAE,KAAK,CAAC,KAA8B;oBAC3C,SAAS,EAAE,KAAK,CAAC,SAAS;oBAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;iBAC3B;aACF,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,2BAA2B,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;CACF;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,YAAY,EAAE,CAAC"}
@@ -0,0 +1,101 @@
1
+ import type { DeploymentStatus } from '@prisma/client';
2
+ export interface DeploymentVariant {
3
+ versionId: string;
4
+ weight: number;
5
+ isControl: boolean;
6
+ }
7
+ export declare class DeploymentService {
8
+ createDeployment(projectId: string, promptId: string, name: string, variants: DeploymentVariant[]): Promise<{
9
+ variants: {
10
+ id: string;
11
+ weight: number;
12
+ isControl: boolean;
13
+ versionId: string;
14
+ deploymentId: string;
15
+ }[];
16
+ } & {
17
+ id: string;
18
+ projectId: string;
19
+ name: string;
20
+ createdAt: Date;
21
+ updatedAt: Date;
22
+ promptId: string;
23
+ status: import("@prisma/client").$Enums.DeploymentStatus;
24
+ config: import("@prisma/client/runtime/library").JsonValue;
25
+ }>;
26
+ listDeployments(projectId: string, promptId?: string): Promise<({
27
+ variants: ({
28
+ version: {
29
+ number: number;
30
+ id: string;
31
+ createdAt: Date;
32
+ template: string;
33
+ variables: import("@prisma/client/runtime/library").JsonValue;
34
+ metadata: import("@prisma/client/runtime/library").JsonValue | null;
35
+ promptId: string;
36
+ content: string;
37
+ checksum: string;
38
+ };
39
+ } & {
40
+ id: string;
41
+ weight: number;
42
+ isControl: boolean;
43
+ versionId: string;
44
+ deploymentId: string;
45
+ })[];
46
+ } & {
47
+ id: string;
48
+ projectId: string;
49
+ name: string;
50
+ createdAt: Date;
51
+ updatedAt: Date;
52
+ promptId: string;
53
+ status: import("@prisma/client").$Enums.DeploymentStatus;
54
+ config: import("@prisma/client/runtime/library").JsonValue;
55
+ })[]>;
56
+ getDeployment(projectId: string, id: string): Promise<{
57
+ variants: ({
58
+ version: {
59
+ number: number;
60
+ id: string;
61
+ createdAt: Date;
62
+ template: string;
63
+ variables: import("@prisma/client/runtime/library").JsonValue;
64
+ metadata: import("@prisma/client/runtime/library").JsonValue | null;
65
+ promptId: string;
66
+ content: string;
67
+ checksum: string;
68
+ };
69
+ } & {
70
+ id: string;
71
+ weight: number;
72
+ isControl: boolean;
73
+ versionId: string;
74
+ deploymentId: string;
75
+ })[];
76
+ } & {
77
+ id: string;
78
+ projectId: string;
79
+ name: string;
80
+ createdAt: Date;
81
+ updatedAt: Date;
82
+ promptId: string;
83
+ status: import("@prisma/client").$Enums.DeploymentStatus;
84
+ config: import("@prisma/client/runtime/library").JsonValue;
85
+ }>;
86
+ resolveVersion(projectId: string, deploymentId: string, sessionId?: string): Promise<string>;
87
+ updateStatus(projectId: string, id: string, status: DeploymentStatus): Promise<{
88
+ id: string;
89
+ projectId: string;
90
+ name: string;
91
+ createdAt: Date;
92
+ updatedAt: Date;
93
+ promptId: string;
94
+ status: import("@prisma/client").$Enums.DeploymentStatus;
95
+ config: import("@prisma/client/runtime/library").JsonValue;
96
+ }>;
97
+ private selectByWeight;
98
+ private selectByHash;
99
+ }
100
+ export declare const deploymentService: DeploymentService;
101
+ //# sourceMappingURL=deployment.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment.service.d.ts","sourceRoot":"","sources":["../../src/services/deployment.service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAKvD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,qBAAa,iBAAiB;IACtB,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,iBAAiB,EAAE;;;;;;;;;;;;;;;;;;IA+CzB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAQpD,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAW3C,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IA0B1E,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB;;;;;;;;;;IAc1E,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,YAAY;CAgBrB;AAED,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
@@ -0,0 +1,127 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { prisma } from '../db/client.js';
3
+ import { redis } from '../db/redis.js';
4
+ import { AppError, NotFoundError } from '../errors.js';
5
+ export class DeploymentService {
6
+ async createDeployment(projectId, promptId, name, variants) {
7
+ const totalWeight = variants.reduce((sum, v) => sum + v.weight, 0);
8
+ if (totalWeight !== 100) {
9
+ throw new AppError('INVALID_WEIGHTS', 400, `Variant weights must sum to 100, got ${totalWeight}`);
10
+ }
11
+ // Verify the prompt belongs to the caller's project.
12
+ const prompt = await prisma.prompt.findFirst({
13
+ where: { id: promptId, projectId },
14
+ select: { id: true },
15
+ });
16
+ if (!prompt) {
17
+ throw new NotFoundError('Prompt', promptId);
18
+ }
19
+ // Verify every versionId belongs to the prompt (and therefore the project).
20
+ const versionIds = variants.map((v) => v.versionId);
21
+ const versions = await prisma.version.findMany({
22
+ where: { id: { in: versionIds }, promptId },
23
+ select: { id: true },
24
+ });
25
+ if (versions.length !== versionIds.length) {
26
+ throw new NotFoundError('Version', 'one or more variant versionIds');
27
+ }
28
+ const deployment = await prisma.deployment.create({
29
+ data: {
30
+ projectId,
31
+ promptId,
32
+ name,
33
+ status: 'paused',
34
+ config: { stickySessions: true },
35
+ variants: {
36
+ create: variants,
37
+ },
38
+ },
39
+ include: { variants: true },
40
+ });
41
+ return deployment;
42
+ }
43
+ async listDeployments(projectId, promptId) {
44
+ return prisma.deployment.findMany({
45
+ where: { projectId, promptId, status: { not: 'archived' } },
46
+ include: { variants: { include: { version: true } } },
47
+ orderBy: { createdAt: 'desc' },
48
+ });
49
+ }
50
+ async getDeployment(projectId, id) {
51
+ const deployment = await prisma.deployment.findFirst({
52
+ where: { id, projectId },
53
+ include: { variants: { include: { version: true } } },
54
+ });
55
+ if (!deployment) {
56
+ throw new NotFoundError('Deployment', id);
57
+ }
58
+ return deployment;
59
+ }
60
+ async resolveVersion(projectId, deploymentId, sessionId) {
61
+ const deployment = await this.getDeployment(projectId, deploymentId);
62
+ if (deployment.status !== 'active') {
63
+ throw new AppError('DEPLOYMENT_INACTIVE', 409, `Deployment is ${deployment.status}`);
64
+ }
65
+ if (sessionId && deployment.config?.stickySessions) {
66
+ if (redis) {
67
+ const stickyKey = `sticky:${deploymentId}:${sessionId}`;
68
+ const cached = await redis.get(stickyKey);
69
+ if (cached) {
70
+ return cached;
71
+ }
72
+ const selected = this.selectByHash(sessionId, deployment.variants);
73
+ await redis.setex(stickyKey, 86400, selected); // 24h TTL
74
+ return selected;
75
+ }
76
+ const stickyVersion = this.selectByHash(sessionId, deployment.variants);
77
+ if (stickyVersion)
78
+ return stickyVersion;
79
+ }
80
+ return this.selectByWeight(deployment.variants);
81
+ }
82
+ async updateStatus(projectId, id, status) {
83
+ const deployment = await prisma.deployment.findFirst({
84
+ where: { id, projectId },
85
+ select: { id: true },
86
+ });
87
+ if (!deployment) {
88
+ throw new NotFoundError('Deployment', id);
89
+ }
90
+ return prisma.deployment.update({
91
+ where: { id },
92
+ data: { status },
93
+ });
94
+ }
95
+ selectByWeight(variants) {
96
+ if (variants.length === 0) {
97
+ throw new AppError('DEPLOYMENT_EMPTY', 500, 'No variants configured');
98
+ }
99
+ const rand = Math.random() * 100;
100
+ let cumulative = 0;
101
+ for (const variant of variants) {
102
+ cumulative += variant.weight;
103
+ if (rand <= cumulative) {
104
+ return variant.versionId;
105
+ }
106
+ }
107
+ return variants[variants.length - 1]?.versionId;
108
+ }
109
+ selectByHash(sessionId, variants) {
110
+ if (variants.length === 0) {
111
+ throw new AppError('DEPLOYMENT_EMPTY', 500, 'No variants configured');
112
+ }
113
+ const hash = createHash('sha256').update(sessionId).digest('hex');
114
+ const intValue = Number.parseInt(hash.slice(0, 8), 16);
115
+ const bucket = intValue % 100;
116
+ let cumulative = 0;
117
+ for (const variant of variants) {
118
+ cumulative += variant.weight;
119
+ if (bucket < cumulative) {
120
+ return variant.versionId;
121
+ }
122
+ }
123
+ return variants[variants.length - 1]?.versionId;
124
+ }
125
+ }
126
+ export const deploymentService = new DeploymentService();
127
+ //# sourceMappingURL=deployment.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment.service.js","sourceRoot":"","sources":["../../src/services/deployment.service.ts"],"names":[],"mappings":"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"}