@adaptic/backend-legacy 0.0.71 → 0.0.72

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 (61) hide show
  1. package/config/jwtConfig.cjs +52 -0
  2. package/config/jwtConfig.d.ts +16 -0
  3. package/config/jwtConfig.d.ts.map +1 -0
  4. package/config/jwtConfig.js.map +1 -0
  5. package/config/metrics.cjs +261 -0
  6. package/config/metrics.d.ts +88 -0
  7. package/config/metrics.d.ts.map +1 -0
  8. package/config/metrics.js.map +1 -0
  9. package/config/persisted-queries.cjs +122 -0
  10. package/config/persisted-queries.d.ts +40 -0
  11. package/config/persisted-queries.d.ts.map +1 -0
  12. package/config/persisted-queries.js.map +1 -0
  13. package/config/tracing.cjs +128 -0
  14. package/config/tracing.d.ts +24 -0
  15. package/config/tracing.d.ts.map +1 -0
  16. package/config/tracing.js.map +1 -0
  17. package/middleware/audit-logger.cjs +223 -0
  18. package/middleware/audit-logger.d.ts +85 -0
  19. package/middleware/audit-logger.d.ts.map +1 -0
  20. package/middleware/audit-logger.js.map +1 -0
  21. package/middleware/auth.cjs +44 -0
  22. package/middleware/auth.d.ts +6 -0
  23. package/middleware/auth.d.ts.map +1 -0
  24. package/middleware/auth.js.map +1 -0
  25. package/middleware/graphql-validation-plugin.cjs +164 -0
  26. package/middleware/graphql-validation-plugin.d.ts +37 -0
  27. package/middleware/graphql-validation-plugin.d.ts.map +1 -0
  28. package/middleware/graphql-validation-plugin.js.map +1 -0
  29. package/middleware/index.cjs +46 -0
  30. package/middleware/index.d.ts +13 -0
  31. package/middleware/index.d.ts.map +1 -0
  32. package/middleware/index.js.map +1 -0
  33. package/middleware/input-validator.cjs +220 -0
  34. package/middleware/input-validator.d.ts +63 -0
  35. package/middleware/input-validator.d.ts.map +1 -0
  36. package/middleware/input-validator.js.map +1 -0
  37. package/middleware/query-complexity.cjs +182 -0
  38. package/middleware/query-complexity.d.ts +56 -0
  39. package/middleware/query-complexity.d.ts.map +1 -0
  40. package/middleware/query-complexity.js.map +1 -0
  41. package/middleware/rate-limiter.cjs +112 -0
  42. package/middleware/rate-limiter.d.ts +16 -0
  43. package/middleware/rate-limiter.d.ts.map +1 -0
  44. package/middleware/rate-limiter.js.map +1 -0
  45. package/middleware/soft-delete.cjs +175 -0
  46. package/middleware/soft-delete.d.ts +146 -0
  47. package/middleware/soft-delete.d.ts.map +1 -0
  48. package/middleware/soft-delete.js.map +1 -0
  49. package/middleware/types.cjs +17 -0
  50. package/middleware/types.d.ts +87 -0
  51. package/middleware/types.d.ts.map +1 -0
  52. package/middleware/types.js.map +1 -0
  53. package/middleware/validation-examples.cjs +403 -0
  54. package/middleware/validation-examples.d.ts +76 -0
  55. package/middleware/validation-examples.d.ts.map +1 -0
  56. package/middleware/validation-examples.js.map +1 -0
  57. package/package.json +4 -1
  58. package/validators/allocation-validator.cjs +85 -0
  59. package/validators/allocation-validator.d.ts +32 -0
  60. package/validators/allocation-validator.d.ts.map +1 -0
  61. package/validators/allocation-validator.js.map +1 -0
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkQueryComplexity = checkQueryComplexity;
4
+ exports.createQueryComplexityPlugin = createQueryComplexityPlugin;
5
+ const graphql_query_complexity_1 = require("graphql-query-complexity");
6
+ const logger_1 = require("../utils/logger.cjs");
7
+ /**
8
+ * Default query complexity limits per authentication level.
9
+ * These can be overridden via environment variables.
10
+ */
11
+ const DEFAULT_MAX_COMPLEXITY_AUTHENTICATED = 1000;
12
+ const DEFAULT_MAX_COMPLEXITY_UNAUTHENTICATED = 200;
13
+ const DEFAULT_MAX_DEPTH = 10;
14
+ /**
15
+ * Resolves the maximum allowed query complexity from environment variables or defaults.
16
+ *
17
+ * @param isAuthenticated - Whether the request is from an authenticated user
18
+ * @returns The maximum allowed complexity score
19
+ */
20
+ function getMaxComplexity(isAuthenticated) {
21
+ if (isAuthenticated) {
22
+ const envValue = process.env.GRAPHQL_MAX_COMPLEXITY_AUTH;
23
+ if (envValue) {
24
+ const parsed = parseInt(envValue, 10);
25
+ if (!isNaN(parsed) && parsed > 0)
26
+ return parsed;
27
+ }
28
+ return DEFAULT_MAX_COMPLEXITY_AUTHENTICATED;
29
+ }
30
+ const envValue = process.env.GRAPHQL_MAX_COMPLEXITY_UNAUTH;
31
+ if (envValue) {
32
+ const parsed = parseInt(envValue, 10);
33
+ if (!isNaN(parsed) && parsed > 0)
34
+ return parsed;
35
+ }
36
+ return DEFAULT_MAX_COMPLEXITY_UNAUTHENTICATED;
37
+ }
38
+ /**
39
+ * Resolves the maximum allowed query depth.
40
+ */
41
+ function getMaxDepth() {
42
+ const envValue = process.env.GRAPHQL_MAX_DEPTH;
43
+ if (envValue) {
44
+ const parsed = parseInt(envValue, 10);
45
+ if (!isNaN(parsed) && parsed > 0)
46
+ return parsed;
47
+ }
48
+ return DEFAULT_MAX_DEPTH;
49
+ }
50
+ /**
51
+ * Computes the complexity of a GraphQL document against a schema.
52
+ *
53
+ * Uses two estimators:
54
+ * 1. fieldExtensionsEstimator: Reads complexity from field extensions (set via schema directives)
55
+ * 2. simpleEstimator: Assigns a default cost of 1 per field as fallback
56
+ *
57
+ * @param schema - The GraphQL schema
58
+ * @param document - The parsed GraphQL document (AST)
59
+ * @param variables - Query variables (used for list size estimation)
60
+ * @param isAuthenticated - Whether the request is authenticated (affects limits)
61
+ * @returns The complexity check result
62
+ */
63
+ function checkQueryComplexity(schema, document, variables, isAuthenticated) {
64
+ const maxComplexity = getMaxComplexity(isAuthenticated);
65
+ try {
66
+ const complexity = (0, graphql_query_complexity_1.getComplexity)({
67
+ schema,
68
+ query: document,
69
+ variables,
70
+ estimators: [
71
+ (0, graphql_query_complexity_1.fieldExtensionsEstimator)(),
72
+ (0, graphql_query_complexity_1.simpleEstimator)({ defaultComplexity: 1 }),
73
+ ],
74
+ });
75
+ return {
76
+ complexity,
77
+ exceeded: complexity > maxComplexity,
78
+ maxComplexity,
79
+ };
80
+ }
81
+ catch (estimationError) {
82
+ logger_1.logger.warn('Failed to estimate query complexity', {
83
+ error: estimationError instanceof Error
84
+ ? estimationError.message
85
+ : String(estimationError),
86
+ });
87
+ // On failure, allow the query to proceed to avoid blocking legitimate requests
88
+ return {
89
+ complexity: 0,
90
+ exceeded: false,
91
+ maxComplexity,
92
+ };
93
+ }
94
+ }
95
+ /**
96
+ * Checks query depth by counting nested selections.
97
+ *
98
+ * @param document - The parsed GraphQL document (AST)
99
+ * @returns The maximum depth found in the query
100
+ */
101
+ function computeQueryDepth(document) {
102
+ let maxDepth = 0;
103
+ function traverse(node, depth) {
104
+ if (depth > maxDepth) {
105
+ maxDepth = depth;
106
+ }
107
+ if (node.selectionSet) {
108
+ for (const selection of node.selectionSet.selections) {
109
+ traverse(selection, depth + 1);
110
+ }
111
+ }
112
+ }
113
+ for (const definition of document.definitions) {
114
+ traverse(definition, 0);
115
+ }
116
+ return maxDepth;
117
+ }
118
+ /**
119
+ * Creates an Apollo Server plugin that enforces query complexity and depth limits.
120
+ *
121
+ * The plugin runs before query execution and rejects queries that exceed
122
+ * configured complexity or depth thresholds.
123
+ *
124
+ * Environment variables:
125
+ * - GRAPHQL_MAX_COMPLEXITY_AUTH: Max complexity for authenticated users (default: 1000)
126
+ * - GRAPHQL_MAX_COMPLEXITY_UNAUTH: Max complexity for unauthenticated users (default: 200)
127
+ * - GRAPHQL_MAX_DEPTH: Max query depth (default: 10)
128
+ * - GRAPHQL_COMPLEXITY_ENABLED: Explicit on/off ('true'/'false'). Defaults to on in production/staging.
129
+ *
130
+ * @param schema - The GraphQL schema to validate against
131
+ * @returns Apollo Server plugin configuration
132
+ */
133
+ function createQueryComplexityPlugin(schema) {
134
+ const isEnabled = () => {
135
+ const explicitSetting = process.env.GRAPHQL_COMPLEXITY_ENABLED;
136
+ if (explicitSetting !== undefined) {
137
+ return explicitSetting === 'true' || explicitSetting === '1';
138
+ }
139
+ const env = process.env.NODE_ENV || 'development';
140
+ return env === 'production' || env === 'staging';
141
+ };
142
+ return {
143
+ async requestDidStart() {
144
+ return {
145
+ async didResolveOperation(requestContext) {
146
+ if (!isEnabled())
147
+ return;
148
+ const { document, request, contextValue } = requestContext;
149
+ const variables = (request.variables || {});
150
+ const isAuthenticated = Boolean(contextValue.user);
151
+ // Check depth limit
152
+ const maxDepth = getMaxDepth();
153
+ const depth = computeQueryDepth(document);
154
+ if (depth > maxDepth) {
155
+ logger_1.logger.warn('Query depth exceeded', {
156
+ depth,
157
+ maxDepth,
158
+ isAuthenticated,
159
+ });
160
+ throw new Error(`Query depth of ${depth} exceeds maximum allowed depth of ${maxDepth}`);
161
+ }
162
+ // Check complexity limit
163
+ const result = checkQueryComplexity(schema, document, variables, isAuthenticated);
164
+ if (result.exceeded) {
165
+ logger_1.logger.warn('Query complexity exceeded', {
166
+ complexity: result.complexity,
167
+ maxComplexity: result.maxComplexity,
168
+ isAuthenticated,
169
+ });
170
+ throw new Error(`Query complexity of ${result.complexity} exceeds maximum allowed complexity of ${result.maxComplexity}`);
171
+ }
172
+ logger_1.logger.debug('Query complexity check passed', {
173
+ complexity: result.complexity,
174
+ maxComplexity: result.maxComplexity,
175
+ depth,
176
+ });
177
+ },
178
+ };
179
+ },
180
+ };
181
+ }
182
+ //# sourceMappingURL=query-complexity.js.map
@@ -0,0 +1,56 @@
1
+ import { GraphQLSchema, DocumentNode } from 'graphql';
2
+ /**
3
+ * Result of a query complexity check.
4
+ */
5
+ interface ComplexityCheckResult {
6
+ /** The computed complexity score */
7
+ complexity: number;
8
+ /** Whether the query exceeds the maximum allowed complexity */
9
+ exceeded: boolean;
10
+ /** The maximum allowed complexity for this request */
11
+ maxComplexity: number;
12
+ }
13
+ /**
14
+ * Computes the complexity of a GraphQL document against a schema.
15
+ *
16
+ * Uses two estimators:
17
+ * 1. fieldExtensionsEstimator: Reads complexity from field extensions (set via schema directives)
18
+ * 2. simpleEstimator: Assigns a default cost of 1 per field as fallback
19
+ *
20
+ * @param schema - The GraphQL schema
21
+ * @param document - The parsed GraphQL document (AST)
22
+ * @param variables - Query variables (used for list size estimation)
23
+ * @param isAuthenticated - Whether the request is authenticated (affects limits)
24
+ * @returns The complexity check result
25
+ */
26
+ export declare function checkQueryComplexity(schema: GraphQLSchema, document: DocumentNode, variables: Record<string, unknown>, isAuthenticated: boolean): ComplexityCheckResult;
27
+ /**
28
+ * Creates an Apollo Server plugin that enforces query complexity and depth limits.
29
+ *
30
+ * The plugin runs before query execution and rejects queries that exceed
31
+ * configured complexity or depth thresholds.
32
+ *
33
+ * Environment variables:
34
+ * - GRAPHQL_MAX_COMPLEXITY_AUTH: Max complexity for authenticated users (default: 1000)
35
+ * - GRAPHQL_MAX_COMPLEXITY_UNAUTH: Max complexity for unauthenticated users (default: 200)
36
+ * - GRAPHQL_MAX_DEPTH: Max query depth (default: 10)
37
+ * - GRAPHQL_COMPLEXITY_ENABLED: Explicit on/off ('true'/'false'). Defaults to on in production/staging.
38
+ *
39
+ * @param schema - The GraphQL schema to validate against
40
+ * @returns Apollo Server plugin configuration
41
+ */
42
+ export declare function createQueryComplexityPlugin(schema: GraphQLSchema): {
43
+ requestDidStart: () => Promise<{
44
+ didResolveOperation: (requestContext: {
45
+ document: DocumentNode;
46
+ request: {
47
+ variables?: Record<string, unknown> | null;
48
+ };
49
+ contextValue: {
50
+ user?: unknown;
51
+ };
52
+ }) => Promise<void>;
53
+ }>;
54
+ };
55
+ export {};
56
+ //# sourceMappingURL=query-complexity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-complexity.d.ts","sourceRoot":"","sources":["../../src/middleware/query-complexity.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AA+CtD;;GAEG;AACH,UAAU,qBAAqB;IAC7B,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,QAAQ,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,YAAY,EACtB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,eAAe,EAAE,OAAO,GACvB,qBAAqB,CAiCvB;AAgDD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,aAAa,GAAG;IAClE,eAAe,EAAE,MAAM,OAAO,CAAC;QAC7B,mBAAmB,EAAE,CAAC,cAAc,EAAE;YACpC,QAAQ,EAAE,YAAY,CAAC;YACvB,OAAO,EAAE;gBAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;aAAE,CAAC;YACxD,YAAY,EAAE;gBAAE,IAAI,CAAC,EAAE,OAAO,CAAA;aAAE,CAAC;SAClC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACrB,CAAC,CAAC;CACJ,CAgEA"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-complexity.js","sourceRoot":"","sources":["../../src/middleware/query-complexity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ // Integration: add to server.ts - app.use('/graphql', graphqlRateLimiter) and app.use('/auth', authRateLimiter)
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.authRateLimiter = exports.graphqlRateLimiter = void 0;
5
+ /**
6
+ * Checks whether a request carries a valid-looking authentication token.
7
+ * Does not verify the token -- only checks for its presence in the
8
+ * Authorization header as a Bearer token with three dot-separated parts
9
+ * (standard JWT structure).
10
+ */
11
+ function isAuthenticated(req) {
12
+ const authHeader = req.headers.authorization || '';
13
+ if (!authHeader.startsWith('Bearer ')) {
14
+ return false;
15
+ }
16
+ const token = authHeader.slice(7);
17
+ // Google OAuth tokens (ya29.) and JWTs (three dot-separated segments) count
18
+ if (token.startsWith('ya29.')) {
19
+ return true;
20
+ }
21
+ return token.split('.').length === 3;
22
+ }
23
+ /**
24
+ * Creates a simple in-memory rate limiter middleware with separate limits
25
+ * for authenticated and unauthenticated requests.
26
+ *
27
+ * Response headers (when standardHeaders is enabled):
28
+ * X-RateLimit-Limit - maximum requests allowed in the current window
29
+ * X-RateLimit-Remaining - requests remaining in the current window
30
+ * X-RateLimit-Reset - seconds until the current window resets
31
+ * Retry-After - seconds to wait before retrying (only on 429)
32
+ *
33
+ * @param config - Rate limit configuration
34
+ * @returns Express middleware function
35
+ */
36
+ function createRateLimiter(config) {
37
+ const store = {};
38
+ // Clean up expired entries every minute
39
+ setInterval(() => {
40
+ const now = Date.now();
41
+ Object.keys(store).forEach((key) => {
42
+ if (store[key].resetTime < now) {
43
+ delete store[key];
44
+ }
45
+ });
46
+ }, 60000);
47
+ return (req, res, next) => {
48
+ const identifier = req.ip || req.connection.remoteAddress || 'unknown';
49
+ const authenticated = isAuthenticated(req);
50
+ const effectiveMax = authenticated
51
+ ? config.maxAuthenticated
52
+ : config.maxUnauthenticated;
53
+ const storeKey = `${identifier}:${authenticated ? 'auth' : 'anon'}`;
54
+ const now = Date.now();
55
+ if (!store[storeKey] || store[storeKey].resetTime < now) {
56
+ store[storeKey] = {
57
+ count: 1,
58
+ resetTime: now + config.windowMs,
59
+ };
60
+ }
61
+ else {
62
+ store[storeKey].count += 1;
63
+ }
64
+ const current = store[storeKey];
65
+ const remaining = Math.max(0, effectiveMax - current.count);
66
+ const resetSeconds = Math.ceil((current.resetTime - now) / 1000);
67
+ // Add rate limit headers
68
+ if (config.standardHeaders !== false) {
69
+ res.setHeader('X-RateLimit-Limit', effectiveMax.toString());
70
+ res.setHeader('X-RateLimit-Remaining', remaining.toString());
71
+ res.setHeader('X-RateLimit-Reset', resetSeconds.toString());
72
+ }
73
+ if (current.count > effectiveMax) {
74
+ // Include Retry-After header on 429 responses (RFC 6585 / RFC 7231)
75
+ res.setHeader('Retry-After', resetSeconds.toString());
76
+ res.status(429).json(config.message);
77
+ return;
78
+ }
79
+ next();
80
+ };
81
+ }
82
+ /**
83
+ * Rate limiter for GraphQL endpoint.
84
+ *
85
+ * Authenticated requests: 1000 requests per 15 minutes (configurable via RATE_LIMIT_MAX)
86
+ * Unauthenticated requests: 200 requests per 15 minutes (configurable via RATE_LIMIT_MAX_UNAUTH)
87
+ */
88
+ exports.graphqlRateLimiter = createRateLimiter({
89
+ windowMs: 15 * 60 * 1000, // 15 minutes
90
+ maxAuthenticated: parseInt(process.env.RATE_LIMIT_MAX || '1000', 10),
91
+ maxUnauthenticated: parseInt(process.env.RATE_LIMIT_MAX_UNAUTH || '200', 10),
92
+ standardHeaders: true,
93
+ legacyHeaders: false,
94
+ message: {
95
+ errors: [{ message: 'Too many requests, please try again later.' }],
96
+ },
97
+ });
98
+ /**
99
+ * Rate limiter for authentication endpoints.
100
+ *
101
+ * Authenticated requests: 50 requests per 15 minutes
102
+ * Unauthenticated requests: 20 requests per 15 minutes
103
+ */
104
+ exports.authRateLimiter = createRateLimiter({
105
+ windowMs: 15 * 60 * 1000, // 15 minutes
106
+ maxAuthenticated: 50,
107
+ maxUnauthenticated: 20,
108
+ standardHeaders: true,
109
+ legacyHeaders: false,
110
+ message: { errors: [{ message: 'Too many authentication attempts.' }] },
111
+ });
112
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1,16 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ /**
3
+ * Rate limiter for GraphQL endpoint.
4
+ *
5
+ * Authenticated requests: 1000 requests per 15 minutes (configurable via RATE_LIMIT_MAX)
6
+ * Unauthenticated requests: 200 requests per 15 minutes (configurable via RATE_LIMIT_MAX_UNAUTH)
7
+ */
8
+ export declare const graphqlRateLimiter: (req: Request, res: Response, next: NextFunction) => void;
9
+ /**
10
+ * Rate limiter for authentication endpoints.
11
+ *
12
+ * Authenticated requests: 50 requests per 15 minutes
13
+ * Unauthenticated requests: 20 requests per 15 minutes
14
+ */
15
+ export declare const authRateLimiter: (req: Request, res: Response, next: NextFunction) => void;
16
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limiter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAyG1D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,QA9ChB,OAAO,OAAO,QAAQ,QAAQ,YAAY,KAAG,IAuD1D,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QA/Db,OAAO,OAAO,QAAQ,QAAQ,YAAY,KAAG,IAsE1D,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/middleware/rate-limiter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ /**
3
+ * Soft Delete Utilities
4
+ *
5
+ * Provides utilities for soft-delete behavior on critical models.
6
+ * Instead of removing records from the database, a `deletedAt` timestamp
7
+ * is set, and query helpers filter out soft-deleted records by default.
8
+ *
9
+ * Models with soft delete support: User, BrokerageAccount, Trade, Action
10
+ *
11
+ * Usage patterns:
12
+ * 1. Use `softDeleteFilter()` in where clauses to exclude deleted records
13
+ * 2. Use `softDeleteRecord()` to soft-delete a record (set deletedAt)
14
+ * 3. Use `hardDelete()` for permanent administrative deletion
15
+ * 4. Pass `includeDeleted: true` to admin queries to see all records
16
+ *
17
+ * Note: Prisma 6 removed the $use() middleware API. Soft delete behavior
18
+ * is implemented via utility functions called from resolvers and services.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.SOFT_DELETE_MODELS = void 0;
22
+ exports.softDeleteFilter = softDeleteFilter;
23
+ exports.deletedOnlyFilter = deletedOnlyFilter;
24
+ exports.isSoftDeleteModel = isSoftDeleteModel;
25
+ exports.softDeleteRecord = softDeleteRecord;
26
+ exports.restoreRecord = restoreRecord;
27
+ exports.hardDelete = hardDelete;
28
+ const logger_1 = require("../utils/logger.cjs");
29
+ /**
30
+ * Models that support soft deletion via the deletedAt field.
31
+ * Only these models have the deletedAt column in the database.
32
+ */
33
+ const SOFT_DELETE_MODELS = new Set([
34
+ 'User',
35
+ 'BrokerageAccount',
36
+ 'Trade',
37
+ 'Action',
38
+ ]);
39
+ exports.SOFT_DELETE_MODELS = SOFT_DELETE_MODELS;
40
+ /**
41
+ * Returns a where clause filter that excludes soft-deleted records.
42
+ * Can be spread into any Prisma where clause for soft-delete-aware queries.
43
+ *
44
+ * @param includeDeleted - When true, returns an empty filter (includes deleted records)
45
+ * @returns An object to spread into a Prisma where clause
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * // Exclude soft-deleted users (default)
50
+ * const users = await prisma.user.findMany({
51
+ * where: { role: 'USER', ...softDeleteFilter() },
52
+ * });
53
+ *
54
+ * // Include soft-deleted users (admin query)
55
+ * const allUsers = await prisma.user.findMany({
56
+ * where: { role: 'USER', ...softDeleteFilter(true) },
57
+ * });
58
+ * ```
59
+ */
60
+ function softDeleteFilter(includeDeleted = false) {
61
+ if (includeDeleted) {
62
+ return {};
63
+ }
64
+ return { deletedAt: null };
65
+ }
66
+ /**
67
+ * Returns a where clause filter for finding only soft-deleted records.
68
+ * Useful for admin interfaces that need to list deleted records for restoration.
69
+ *
70
+ * @returns An object to spread into a Prisma where clause
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * // Find only soft-deleted users
75
+ * const deletedUsers = await prisma.user.findMany({
76
+ * where: { ...deletedOnlyFilter() },
77
+ * });
78
+ * ```
79
+ */
80
+ function deletedOnlyFilter() {
81
+ return { deletedAt: { not: null } };
82
+ }
83
+ /**
84
+ * Checks whether a given model supports soft deletion.
85
+ *
86
+ * @param modelName - The Prisma model name (PascalCase)
87
+ * @returns True if the model has a deletedAt field
88
+ */
89
+ function isSoftDeleteModel(modelName) {
90
+ return SOFT_DELETE_MODELS.has(modelName);
91
+ }
92
+ /**
93
+ * Soft-deletes a record by setting its deletedAt timestamp.
94
+ * This is the recommended way to "delete" records in soft-delete-enabled models.
95
+ *
96
+ * @param delegate - The Prisma model delegate (e.g., prisma.user)
97
+ * @param id - The record ID to soft-delete
98
+ * @param modelName - The model name for logging purposes
99
+ * @returns The updated record
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * // Soft-delete a user
104
+ * await softDeleteRecord(prisma.user, 'user-123', 'User');
105
+ * ```
106
+ */
107
+ async function softDeleteRecord(delegate, id, modelName) {
108
+ logger_1.logger.info('Soft delete: Setting deletedAt on record', {
109
+ model: modelName,
110
+ id,
111
+ });
112
+ return delegate.update({
113
+ where: { id },
114
+ data: { deletedAt: new Date() },
115
+ });
116
+ }
117
+ /**
118
+ * Restores a soft-deleted record by clearing its deletedAt timestamp.
119
+ *
120
+ * @param delegate - The Prisma model delegate (e.g., prisma.user)
121
+ * @param id - The record ID to restore
122
+ * @param modelName - The model name for logging purposes
123
+ * @returns The updated record
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * // Restore a soft-deleted user
128
+ * await restoreRecord(prisma.user, 'user-123', 'User');
129
+ * ```
130
+ */
131
+ async function restoreRecord(delegate, id, modelName) {
132
+ logger_1.logger.info('Soft delete: Restoring record (clearing deletedAt)', {
133
+ model: modelName,
134
+ id,
135
+ });
136
+ return delegate.update({
137
+ where: { id },
138
+ data: { deletedAt: null },
139
+ });
140
+ }
141
+ /**
142
+ * Mapping from model names to their database table names.
143
+ * Used for raw SQL hard-delete operations.
144
+ */
145
+ const TABLE_NAME_MAP = {
146
+ User: 'users',
147
+ BrokerageAccount: 'brokerage_accounts',
148
+ Trade: 'trades',
149
+ Action: 'actions',
150
+ };
151
+ /**
152
+ * Permanently deletes a record from the database.
153
+ * This bypasses soft delete and removes the row entirely.
154
+ * Should only be used for administrative cleanup operations.
155
+ *
156
+ * @param prisma - The Prisma client instance (with $executeRawUnsafe)
157
+ * @param model - The model name (PascalCase)
158
+ * @param id - The record ID to permanently delete
159
+ * @returns The number of rows deleted
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * // Permanently delete a soft-deleted user
164
+ * const deleted = await hardDelete(prisma, 'User', 'user-123');
165
+ * ```
166
+ */
167
+ async function hardDelete(prisma, model, id) {
168
+ const tableName = TABLE_NAME_MAP[model];
169
+ if (!tableName) {
170
+ throw new Error(`Model "${model}" does not support hard delete`);
171
+ }
172
+ logger_1.logger.warn('Hard delete: Permanently removing record', { model, id });
173
+ return prisma.$executeRawUnsafe(`DELETE FROM "${tableName}" WHERE "id" = $1`, id);
174
+ }
175
+ //# sourceMappingURL=soft-delete.js.map