ts-prorm-orm 1.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 (310) hide show
  1. package/CHANGELOG.md +1111 -0
  2. package/LICENSE +21 -0
  3. package/README.md +573 -0
  4. package/dist/audit/history-query.js +180 -0
  5. package/dist/audit/index.js +23 -0
  6. package/dist/audit/logger.js +236 -0
  7. package/dist/cache/cache-manager.js +84 -0
  8. package/dist/cache/index.js +16 -0
  9. package/dist/cache/redis-cluster-cache.js +557 -0
  10. package/dist/cli.js +2200 -0
  11. package/dist/compliance/audit-trail.js +68 -0
  12. package/dist/compliance/backup-verification.js +685 -0
  13. package/dist/compliance/breach-detector.js +505 -0
  14. package/dist/compliance/consent-record.js +227 -0
  15. package/dist/compliance/consent-versioning.js +331 -0
  16. package/dist/compliance/cross-border-log.js +530 -0
  17. package/dist/compliance/data-classifier.js +258 -0
  18. package/dist/compliance/data-lineage.js +565 -0
  19. package/dist/compliance/data-masker.js +303 -0
  20. package/dist/compliance/data-portability.js +265 -0
  21. package/dist/compliance/data-retention.js +195 -0
  22. package/dist/compliance/dsar-workflow.js +342 -0
  23. package/dist/compliance/field-encryption.js +223 -0
  24. package/dist/compliance/immutable-record.js +148 -0
  25. package/dist/compliance/index.js +260 -0
  26. package/dist/compliance/privacy-impact-assessment.js +364 -0
  27. package/dist/compliance/pseudonymization.js +269 -0
  28. package/dist/compliance/query-firewall.js +1182 -0
  29. package/dist/compliance/rate-limiter.js +580 -0
  30. package/dist/compliance/right-to-erasure.js +117 -0
  31. package/dist/compliance/row-level-security.js +246 -0
  32. package/dist/compliance/security-decorator.js +574 -0
  33. package/dist/compliance/security-monitor.js +525 -0
  34. package/dist/compliance/sensitive-data-discovery.js +476 -0
  35. package/dist/compliance/session-isolation.js +472 -0
  36. package/dist/compliance/tls-enforcer.js +108 -0
  37. package/dist/compliance/worm-storage.js +712 -0
  38. package/dist/connection-manager.js +198 -0
  39. package/dist/connection-pool.js +519 -0
  40. package/dist/decorators/audit.js +136 -0
  41. package/dist/decorators/belongs-to-many.js +115 -0
  42. package/dist/decorators/belongs-to.js +115 -0
  43. package/dist/decorators/check.js +435 -0
  44. package/dist/decorators/collate.js +329 -0
  45. package/dist/decorators/comment.js +205 -0
  46. package/dist/decorators/database-settings.js +236 -0
  47. package/dist/decorators/default.js +244 -0
  48. package/dist/decorators/encryption.js +235 -0
  49. package/dist/decorators/engine.js +97 -0
  50. package/dist/decorators/fk-constraints.js +594 -0
  51. package/dist/decorators/foreign-table.js +136 -0
  52. package/dist/decorators/generated.js +274 -0
  53. package/dist/decorators/has-many.js +127 -0
  54. package/dist/decorators/has-one.js +116 -0
  55. package/dist/decorators/hstore.js +129 -0
  56. package/dist/decorators/index.js +355 -0
  57. package/dist/decorators/json-column.js +83 -0
  58. package/dist/decorators/jsonb.js +101 -0
  59. package/dist/decorators/orm-decorators.js +425 -0
  60. package/dist/decorators/permissions.js +294 -0
  61. package/dist/decorators/procedure.js +210 -0
  62. package/dist/decorators/query-options.js +556 -0
  63. package/dist/decorators/range.js +167 -0
  64. package/dist/decorators/set-column.js +83 -0
  65. package/dist/decorators/spatial.js +114 -0
  66. package/dist/decorators/storage.js +580 -0
  67. package/dist/decorators/timezone.js +512 -0
  68. package/dist/decorators/trigger.js +90 -0
  69. package/dist/decorators/uuid.js +135 -0
  70. package/dist/decorators/view.js +258 -0
  71. package/dist/diagrams/chen-diagram.js +354 -0
  72. package/dist/diagrams/class-diagram.js +384 -0
  73. package/dist/diagrams/dependency-diagram.js +432 -0
  74. package/dist/diagrams/er-diagram.js +605 -0
  75. package/dist/diagrams/flow-diagram.js +394 -0
  76. package/dist/diagrams/gantt-diagram.js +411 -0
  77. package/dist/diagrams/index-diagram.js +353 -0
  78. package/dist/diagrams/index.js +184 -0
  79. package/dist/diagrams/migration-diagram.js +316 -0
  80. package/dist/diagrams/model-diagram.js +616 -0
  81. package/dist/diagrams/package-diagram.js +376 -0
  82. package/dist/diagrams/palette.js +85 -0
  83. package/dist/diagrams/relational-diagram.js +455 -0
  84. package/dist/diagrams/schemadoc-diagram.js +309 -0
  85. package/dist/diagrams/sequence-diagram.js +307 -0
  86. package/dist/diagrams/state-diagram.js +344 -0
  87. package/dist/diagrams/svg-dom.js +111 -0
  88. package/dist/diagrams/tree-diagram.js +250 -0
  89. package/dist/dialects/clickhouse/index.js +1742 -0
  90. package/dist/dialects/cockroachdb/index.js +4677 -0
  91. package/dist/dialects/cratedb/index.js +441 -0
  92. package/dist/dialects/databricks/index.js +517 -0
  93. package/dist/dialects/db2/index.js +2225 -0
  94. package/dist/dialects/dialect.js +720 -0
  95. package/dist/dialects/duckdb/index.js +2014 -0
  96. package/dist/dialects/exasol/index.js +357 -0
  97. package/dist/dialects/firebird/index.js +495 -0
  98. package/dist/dialects/greenplum/index.js +457 -0
  99. package/dist/dialects/hana/index.js +1741 -0
  100. package/dist/dialects/mariadb/index.js +3590 -0
  101. package/dist/dialects/mssql/index.js +2296 -0
  102. package/dist/dialects/mysql/index.js +4058 -0
  103. package/dist/dialects/oracle/index.js +2999 -0
  104. package/dist/dialects/postgres/index.js +5319 -0
  105. package/dist/dialects/query-stream-helper.js +152 -0
  106. package/dist/dialects/questdb/index.js +433 -0
  107. package/dist/dialects/redshift/index.js +2277 -0
  108. package/dist/dialects/singlestore/index.js +354 -0
  109. package/dist/dialects/snowflake/index.js +2082 -0
  110. package/dist/dialects/spanner/index.js +1768 -0
  111. package/dist/dialects/sqlite/index.js +3382 -0
  112. package/dist/dialects/tidb/index.js +377 -0
  113. package/dist/dialects/timescaledb/index.js +164 -0
  114. package/dist/dialects/trino/index.js +420 -0
  115. package/dist/dialects/turso/index.js +227 -0
  116. package/dist/dialects/vertica/index.js +317 -0
  117. package/dist/dialects/yugabytedb/index.js +424 -0
  118. package/dist/errors/index.js +472 -0
  119. package/dist/errors/utils.js +340 -0
  120. package/dist/errors.js +21 -0
  121. package/dist/extensions/catalog/cloud-warehouse-features.js +207 -0
  122. package/dist/extensions/catalog/mssql-features.js +147 -0
  123. package/dist/extensions/catalog/mysql-mariadb-plugins.js +229 -0
  124. package/dist/extensions/catalog/oracle-db2-features.js +196 -0
  125. package/dist/extensions/catalog/postgres-extensions.js +516 -0
  126. package/dist/extensions/index.js +71 -0
  127. package/dist/extensions/types.js +13 -0
  128. package/dist/foreign-data.js +173 -0
  129. package/dist/hooks/hooks-manager.js +350 -0
  130. package/dist/hooks/index.js +37 -0
  131. package/dist/index.js +463 -0
  132. package/dist/logging.js +335 -0
  133. package/dist/migrations/index.js +40 -0
  134. package/dist/migrations/migration.js +196 -0
  135. package/dist/migrations/migrator.js +411 -0
  136. package/dist/migrations/prormmigration.js +275 -0
  137. package/dist/migrations/query-interface.js +435 -0
  138. package/dist/migrations/seeder.js +353 -0
  139. package/dist/models/associations.js +852 -0
  140. package/dist/models/constraints.js +288 -0
  141. package/dist/models/data-types.js +2518 -0
  142. package/dist/models/decorators.js +445 -0
  143. package/dist/models/index.js +33 -0
  144. package/dist/models/indexes.js +531 -0
  145. package/dist/models/methods.js +382 -0
  146. package/dist/models/model-manager.js +103 -0
  147. package/dist/models/model.js +5349 -0
  148. package/dist/models/operators.js +67 -0
  149. package/dist/models/scopes.js +189 -0
  150. package/dist/models/typescript-types.js +26 -0
  151. package/dist/nosql/aerospike/index.js +205 -0
  152. package/dist/nosql/allegrograph/index.js +169 -0
  153. package/dist/nosql/arangodb/index.js +364 -0
  154. package/dist/nosql/azure-blob/index.js +206 -0
  155. package/dist/nosql/beanstalkd/index.js +231 -0
  156. package/dist/nosql/beequeue/index.js +210 -0
  157. package/dist/nosql/bigchaindb/index.js +195 -0
  158. package/dist/nosql/bigtable/index.js +224 -0
  159. package/dist/nosql/blazegraph/index.js +173 -0
  160. package/dist/nosql/bullmq/index.js +191 -0
  161. package/dist/nosql/cassandra/index.js +174 -0
  162. package/dist/nosql/chroma/index.js +190 -0
  163. package/dist/nosql/cloudflare-kv/index.js +220 -0
  164. package/dist/nosql/coherence/index.js +200 -0
  165. package/dist/nosql/cosmosdb/index.js +157 -0
  166. package/dist/nosql/couchbase/index.js +213 -0
  167. package/dist/nosql/dax/index.js +212 -0
  168. package/dist/nosql/deno-kv/index.js +206 -0
  169. package/dist/nosql/dgraph/index.js +171 -0
  170. package/dist/nosql/doris/index.js +169 -0
  171. package/dist/nosql/druid/index.js +162 -0
  172. package/dist/nosql/dynamodb/index.js +937 -0
  173. package/dist/nosql/elasticsearch/index.js +377 -0
  174. package/dist/nosql/etcd/index.js +502 -0
  175. package/dist/nosql/eventhubs/index.js +213 -0
  176. package/dist/nosql/eventstore/index.js +254 -0
  177. package/dist/nosql/faunadb/index.js +188 -0
  178. package/dist/nosql/firestore/index.js +177 -0
  179. package/dist/nosql/fluree/index.js +148 -0
  180. package/dist/nosql/fuseki/index.js +170 -0
  181. package/dist/nosql/gcs/index.js +172 -0
  182. package/dist/nosql/gearman/index.js +160 -0
  183. package/dist/nosql/geode/index.js +196 -0
  184. package/dist/nosql/graphdb/index.js +169 -0
  185. package/dist/nosql/graylog/index.js +188 -0
  186. package/dist/nosql/gridgain/index.js +171 -0
  187. package/dist/nosql/hazelcast/index.js +162 -0
  188. package/dist/nosql/hbase/index.js +230 -0
  189. package/dist/nosql/ignite/index.js +173 -0
  190. package/dist/nosql/immudb/index.js +184 -0
  191. package/dist/nosql/index.js +232 -0
  192. package/dist/nosql/infinispan/index.js +200 -0
  193. package/dist/nosql/influxdb/index.js +0 -0
  194. package/dist/nosql/kafka/index.js +234 -0
  195. package/dist/nosql/keyspaces/index.js +189 -0
  196. package/dist/nosql/kinesis/index.js +253 -0
  197. package/dist/nosql/leveldb/index.js +153 -0
  198. package/dist/nosql/lmdb/index.js +160 -0
  199. package/dist/nosql/loki/index.js +201 -0
  200. package/dist/nosql/marklogic/index.js +204 -0
  201. package/dist/nosql/materialize/index.js +145 -0
  202. package/dist/nosql/meilisearch/index.js +154 -0
  203. package/dist/nosql/memcached/index.js +223 -0
  204. package/dist/nosql/milvus/index.js +410 -0
  205. package/dist/nosql/minio/index.js +265 -0
  206. package/dist/nosql/momento/index.js +179 -0
  207. package/dist/nosql/mongodb/index.js +461 -0
  208. package/dist/nosql/nats/index.js +247 -0
  209. package/dist/nosql/nedb/index.js +164 -0
  210. package/dist/nosql/neo4j/index.js +450 -0
  211. package/dist/nosql/neptune/index.js +470 -0
  212. package/dist/nosql/nsq/index.js +200 -0
  213. package/dist/nosql/opensearch/index.js +186 -0
  214. package/dist/nosql/orientdb/index.js +175 -0
  215. package/dist/nosql/papertrail/index.js +200 -0
  216. package/dist/nosql/pinecone/index.js +0 -0
  217. package/dist/nosql/pinot/index.js +133 -0
  218. package/dist/nosql/pouchdb/index.js +172 -0
  219. package/dist/nosql/prometheus/index.js +174 -0
  220. package/dist/nosql/provendb/index.js +147 -0
  221. package/dist/nosql/pubsub/index.js +187 -0
  222. package/dist/nosql/pulsar/index.js +232 -0
  223. package/dist/nosql/qdrant/index.js +295 -0
  224. package/dist/nosql/qldb/index.js +192 -0
  225. package/dist/nosql/r2/index.js +281 -0
  226. package/dist/nosql/rabbitmq/index.js +237 -0
  227. package/dist/nosql/ravendb/index.js +175 -0
  228. package/dist/nosql/redis/index.js +607 -0
  229. package/dist/nosql/redpanda/index.js +237 -0
  230. package/dist/nosql/resque/index.js +203 -0
  231. package/dist/nosql/rethinkdb/index.js +232 -0
  232. package/dist/nosql/rocksdb/index.js +152 -0
  233. package/dist/nosql/rockset/index.js +126 -0
  234. package/dist/nosql/s3/index.js +298 -0
  235. package/dist/nosql/scylladb/index.js +178 -0
  236. package/dist/nosql/signoz/index.js +227 -0
  237. package/dist/nosql/sns/index.js +201 -0
  238. package/dist/nosql/solr/index.js +231 -0
  239. package/dist/nosql/splunk/index.js +227 -0
  240. package/dist/nosql/sqs/index.js +246 -0
  241. package/dist/nosql/stardog/index.js +169 -0
  242. package/dist/nosql/starrocks/index.js +170 -0
  243. package/dist/nosql/store.js +2 -0
  244. package/dist/nosql/sumologic/index.js +213 -0
  245. package/dist/nosql/surrealdb/index.js +179 -0
  246. package/dist/nosql/terminusdb/index.js +151 -0
  247. package/dist/nosql/tigergraph/index.js +469 -0
  248. package/dist/nosql/typesense/index.js +148 -0
  249. package/dist/nosql/unqlite/index.js +157 -0
  250. package/dist/nosql/upstash/index.js +167 -0
  251. package/dist/nosql/vercel-kv/index.js +166 -0
  252. package/dist/nosql/victoriametrics/index.js +213 -0
  253. package/dist/nosql/virtuoso/index.js +169 -0
  254. package/dist/nosql/weaviate/index.js +276 -0
  255. package/dist/operators/index.js +108 -0
  256. package/dist/operators.js +2690 -0
  257. package/dist/prisma-migrate/index.js +38 -0
  258. package/dist/prisma-migrate/migration-generator.js +125 -0
  259. package/dist/prisma-migrate/model-generator.js +172 -0
  260. package/dist/prisma-migrate/relations.js +100 -0
  261. package/dist/prisma-migrate/schema-parser.js +167 -0
  262. package/dist/prisma-migrate/type-mapper.js +40 -0
  263. package/dist/prorm.js +6832 -0
  264. package/dist/query-builders/cte-builder.js +80 -0
  265. package/dist/query-builders/functions/aggregate.js +390 -0
  266. package/dist/query-builders/functions/conditional.js +503 -0
  267. package/dist/query-builders/functions/datetime.js +695 -0
  268. package/dist/query-builders/functions/fulltext.js +439 -0
  269. package/dist/query-builders/functions/index.js +93 -0
  270. package/dist/query-builders/functions/json.js +427 -0
  271. package/dist/query-builders/functions/math.js +399 -0
  272. package/dist/query-builders/functions/string.js +518 -0
  273. package/dist/query-builders/functions/window.js +328 -0
  274. package/dist/query-builders/include-builder.js +323 -0
  275. package/dist/query-builders/index-expression-builder.js +242 -0
  276. package/dist/query-builders/index.js +161 -0
  277. package/dist/query-builders/insert-builder.js +164 -0
  278. package/dist/query-builders/model-helpers.js +38 -0
  279. package/dist/query-builders/order-limit-builder.js +239 -0
  280. package/dist/query-builders/sql-compiler.js +2040 -0
  281. package/dist/query-builders/subquery-builder.js +152 -0
  282. package/dist/query-builders/update-builder.js +182 -0
  283. package/dist/query-builders/view-builder.js +218 -0
  284. package/dist/query-builders/where-builder.js +1532 -0
  285. package/dist/query-interface.js +622 -0
  286. package/dist/query-optimizers/batch-optimizer.js +426 -0
  287. package/dist/query-optimizers/explain-plans.js +415 -0
  288. package/dist/query-optimizers/index.js +51 -0
  289. package/dist/query-optimizers/prepared-statement-cache.js +423 -0
  290. package/dist/query-optimizers/query-hints.js +438 -0
  291. package/dist/query-optimizers/query-optimizer.js +278 -0
  292. package/dist/query-optimizers/slow-query-logger.js +271 -0
  293. package/dist/replica-manager.js +507 -0
  294. package/dist/schema/index.js +15 -0
  295. package/dist/schema/migration-generator.js +374 -0
  296. package/dist/schema/schema-differ.js +549 -0
  297. package/dist/schema/types.js +6 -0
  298. package/dist/sql-constants.js +300 -0
  299. package/dist/sqlite-advanced.js +1047 -0
  300. package/dist/streams/index.js +12 -0
  301. package/dist/streams/transforms.js +178 -0
  302. package/dist/transaction.js +456 -0
  303. package/dist/types/index.js +180 -0
  304. package/dist/user-management.js +127 -0
  305. package/dist/utils/date.js +300 -0
  306. package/dist/utils/index.js +1079 -0
  307. package/dist/utils/string.js +161 -0
  308. package/dist/validators/index.js +19 -0
  309. package/dist/validators/validator.js +911 -0
  310. package/package.json +190 -0
@@ -0,0 +1,580 @@
1
+ "use strict";
2
+ /**
3
+ * API Rate Limiter
4
+ *
5
+ * A comprehensive rate limiting system that:
6
+ * - Limits queries per user/role to prevent enumeration attacks
7
+ * - Per-IP rate limiting
8
+ * - Per-endpoint rate limiting
9
+ * - Configurable thresholds with block/throttle actions
10
+ * - OWASP Top 10, NIS2, DORA compliance
11
+ *
12
+ * Usage:
13
+ * // Initialize rate limiter
14
+ * const limiter = RateLimiter.create(prorm, {
15
+ * user: { perMinute: 100, perHour: 1000 },
16
+ * ip: { perMinute: 50, perHour: 500 },
17
+ * endpoint: { perMinute: 200, perHour: 2000 },
18
+ * onLimitExceeded: (info) => console.log('Rate limited:', info),
19
+ * });
20
+ *
21
+ * // Check if request is allowed
22
+ * const result = limiter.checkLimit(userId, ip, endpoint);
23
+ * if (!result.allowed) {
24
+ * return res.status(429).json({ error: 'Rate limit exceeded' });
25
+ * }
26
+ *
27
+ * // Get statistics
28
+ * const stats = limiter.getStats();
29
+ *
30
+ * // Reset counters
31
+ * limiter.reset();
32
+ *
33
+ * Decorator usage:
34
+ * @RateLimit({ perMinute: 100, perHour: 1000 })
35
+ * class User extends Model { ... }
36
+ */
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.RateLimiter = void 0;
39
+ exports.RateLimit = RateLimit;
40
+ exports.getRateLimitOptions = getRateLimitOptions;
41
+ exports.getGlobalLimiter = getGlobalLimiter;
42
+ exports.initRateLimiter = initRateLimiter;
43
+ exports.checkRateLimit = checkRateLimit;
44
+ exports.satisfiesCompliance = satisfiesCompliance;
45
+ exports.getRecommendedLimits = getRecommendedLimits;
46
+ // In-memory storage (default)
47
+ class RateLimitStore {
48
+ constructor() {
49
+ this.entries = new Map();
50
+ }
51
+ getKey(identifier, identifierType, endpoint) {
52
+ return `${identifierType}:${identifier}${endpoint ? `:${endpoint}` : ''}`;
53
+ }
54
+ get(identifier, identifierType, endpoint) {
55
+ return this.entries.get(this.getKey(identifier, identifierType, endpoint));
56
+ }
57
+ set(entry) {
58
+ const key = this.getKey(entry.identifier, entry.identifierType, entry.endpoint);
59
+ this.entries.set(key, entry);
60
+ }
61
+ delete(key) {
62
+ this.entries.delete(key);
63
+ }
64
+ clear() {
65
+ this.entries.clear();
66
+ }
67
+ getAll() {
68
+ return Array.from(this.entries.values());
69
+ }
70
+ getSize() {
71
+ return this.entries.size;
72
+ }
73
+ }
74
+ // ==================== Rate Limiter Class ====================
75
+ class RateLimiter {
76
+ constructor(prorm, options = {}) {
77
+ this.dbModel = null;
78
+ this.totalRequests = 0;
79
+ this.blockedRequests = 0;
80
+ this.throttledRequests = 0;
81
+ this.prorm = prorm;
82
+ this.store = new RateLimitStore();
83
+ this.options = {
84
+ user: options.user ?? { perMinute: 100, perHour: 1000, perDay: 10000 },
85
+ anonymous: options.anonymous ?? { perMinute: 20, perHour: 200, perDay: 1000 },
86
+ ip: options.ip ?? { perMinute: 50, perHour: 500, perDay: 5000 },
87
+ endpoint: options.endpoint ?? { perMinute: 200, perHour: 2000, perDay: 20000 },
88
+ role: options.role ?? {},
89
+ action: options.action ?? 'block',
90
+ throttleDelay: options.throttleDelay ?? 1000,
91
+ logEvents: options.logEvents ?? true,
92
+ onLimitExceeded: options.onLimitExceeded ?? (() => { }),
93
+ storeInDb: options.storeInDb ?? false,
94
+ rateLimitModelName: options.rateLimitModelName ?? 'RateLimitEntry',
95
+ };
96
+ }
97
+ /**
98
+ * Factory method to create a RateLimiter instance
99
+ */
100
+ static create(prorm, options = {}) {
101
+ return new RateLimiter(prorm, options);
102
+ }
103
+ /**
104
+ * Initialize database storage (optional)
105
+ */
106
+ async initDatabase() {
107
+ if (!this.options.storeInDb)
108
+ return;
109
+ const modelName = this.options.rateLimitModelName;
110
+ // Check if model already exists
111
+ const existingModel = this.prorm.models[modelName];
112
+ if (existingModel) {
113
+ this.dbModel = existingModel;
114
+ return;
115
+ }
116
+ // Create the model
117
+ this.dbModel = this.prorm.define(modelName, {
118
+ id: {
119
+ type: this.prorm.Prorm.INTEGER,
120
+ primaryKey: true,
121
+ autoIncrement: true,
122
+ },
123
+ identifier: {
124
+ type: this.prorm.Prorm.STRING,
125
+ allowNull: false,
126
+ },
127
+ identifierType: {
128
+ type: this.prorm.Prorm.STRING,
129
+ allowNull: false,
130
+ },
131
+ endpoint: {
132
+ type: this.prorm.Prorm.STRING,
133
+ allowNull: true,
134
+ },
135
+ countMinute: {
136
+ type: this.prorm.Prorm.INTEGER,
137
+ defaultValue: 0,
138
+ },
139
+ countHour: {
140
+ type: this.prorm.Prorm.INTEGER,
141
+ defaultValue: 0,
142
+ },
143
+ countDay: {
144
+ type: this.prorm.Prorm.INTEGER,
145
+ defaultValue: 0,
146
+ },
147
+ windowStartMinute: {
148
+ type: this.prorm.Prorm.BIGINT,
149
+ defaultValue: 0,
150
+ },
151
+ windowStartHour: {
152
+ type: this.prorm.Prorm.BIGINT,
153
+ defaultValue: 0,
154
+ },
155
+ windowStartDay: {
156
+ type: this.prorm.Prorm.BIGINT,
157
+ defaultValue: 0,
158
+ },
159
+ blocked: {
160
+ type: this.prorm.Prorm.BOOLEAN,
161
+ defaultValue: false,
162
+ },
163
+ blockUntil: {
164
+ type: this.prorm.Prorm.DATE,
165
+ allowNull: true,
166
+ },
167
+ });
168
+ await this.dbModel.sync({ alter: true });
169
+ }
170
+ /**
171
+ * Check if a request is within rate limits
172
+ */
173
+ checkLimit(userId, ip, endpoint, role) {
174
+ this.totalRequests++;
175
+ const now = Date.now();
176
+ const currentMinute = Math.floor(now / 60000);
177
+ const currentHour = Math.floor(now / 3600000);
178
+ const currentDay = Math.floor(now / 86400000);
179
+ // Determine which limits apply
180
+ const limits = [];
181
+ // User limit
182
+ if (userId) {
183
+ limits.push({ type: 'user', config: this.options.user, identifier: userId });
184
+ }
185
+ else {
186
+ // Anonymous user limit
187
+ limits.push({ type: 'user', config: this.options.anonymous, identifier: `anon:${ip}` });
188
+ }
189
+ // Role limit
190
+ if (role && this.options.role[role]) {
191
+ limits.push({ type: 'role', config: this.options.role[role], identifier: `role:${role}` });
192
+ }
193
+ // IP limit
194
+ limits.push({ type: 'ip', config: this.options.ip, identifier: ip });
195
+ // Endpoint limit
196
+ limits.push({ type: 'endpoint', config: this.options.endpoint, identifier: endpoint });
197
+ // Check all limits
198
+ let overallAllowed = true;
199
+ let minRemaining = Infinity;
200
+ let minResetIn = Infinity;
201
+ for (const limit of limits) {
202
+ const entry = this.store.get(limit.identifier, limit.type, endpoint);
203
+ let countMinute = 0;
204
+ let countHour = 0;
205
+ let countDay = 0;
206
+ if (entry) {
207
+ // Check if we need to reset windows
208
+ if (entry.windowStartMinute !== currentMinute) {
209
+ entry.countMinute = 0;
210
+ entry.windowStartMinute = currentMinute;
211
+ }
212
+ if (entry.windowStartHour !== currentHour) {
213
+ entry.countHour = 0;
214
+ entry.windowStartHour = currentHour;
215
+ }
216
+ if (entry.windowStartDay !== currentDay) {
217
+ entry.countDay = 0;
218
+ entry.windowStartDay = currentDay;
219
+ }
220
+ // Check if blocked
221
+ if (entry.blocked && entry.blockUntil && entry.blockUntil.getTime() > now) {
222
+ overallAllowed = false;
223
+ minRemaining = 0;
224
+ minResetIn = Math.min(minResetIn, entry.blockUntil.getTime() - now);
225
+ this.blockedRequests++;
226
+ this.options.onLimitExceeded({
227
+ identifier: limit.identifier,
228
+ identifierType: limit.type,
229
+ limit: limit.config.perMinute,
230
+ current: entry.countMinute + 1,
231
+ window: 'minute',
232
+ action: this.options.action,
233
+ timestamp: new Date(),
234
+ });
235
+ continue;
236
+ }
237
+ countMinute = entry.countMinute;
238
+ countHour = entry.countHour;
239
+ countDay = entry.countDay;
240
+ }
241
+ // Check per-minute, per-hour, and per-day limits. Each window is
242
+ // tracked and enforced independently (a request can be within the
243
+ // per-minute budget but still exceed the per-hour or per-day
244
+ // budget), matching what RateLimitConfig documents but the original
245
+ // implementation never actually enforced for hour/day.
246
+ const windows = [
247
+ { window: 'minute', limitVal: limit.config.perMinute, current: countMinute, resetIn: 60000 - (now % 60000) },
248
+ { window: 'hour', limitVal: limit.config.perHour, current: countHour, resetIn: 3600000 - (now % 3600000) },
249
+ { window: 'day', limitVal: limit.config.perDay, current: countDay, resetIn: 86400000 - (now % 86400000) },
250
+ ];
251
+ const breached = windows.find((w) => w.limitVal !== undefined && w.current + 1 > w.limitVal);
252
+ if (breached) {
253
+ if (this.options.action === 'block') {
254
+ overallAllowed = false;
255
+ minRemaining = 0;
256
+ minResetIn = Math.min(minResetIn, breached.resetIn);
257
+ // Block the identifier until the breached window resets
258
+ this.blockEntry(limit.identifier, limit.type, endpoint, now, breached.resetIn);
259
+ this.options.onLimitExceeded({
260
+ identifier: limit.identifier,
261
+ identifierType: limit.type,
262
+ limit: breached.limitVal,
263
+ current: breached.current + 1,
264
+ window: breached.window,
265
+ action: 'block',
266
+ timestamp: new Date(),
267
+ });
268
+ this.blockedRequests++;
269
+ }
270
+ else {
271
+ // Throttle
272
+ minRemaining = 0;
273
+ minResetIn = Math.min(minResetIn, breached.resetIn);
274
+ this.throttledRequests++;
275
+ this.options.onLimitExceeded({
276
+ identifier: limit.identifier,
277
+ identifierType: limit.type,
278
+ limit: breached.limitVal,
279
+ current: breached.current + 1,
280
+ window: breached.window,
281
+ action: 'throttle',
282
+ timestamp: new Date(),
283
+ });
284
+ }
285
+ }
286
+ else {
287
+ for (const w of windows) {
288
+ if (w.limitVal !== undefined) {
289
+ minRemaining = Math.min(minRemaining, w.limitVal - w.current);
290
+ }
291
+ }
292
+ }
293
+ // Update entry - increments minute/hour/day counters independently
294
+ this.updateEntry(limit.identifier, limit.type, endpoint, now);
295
+ }
296
+ return {
297
+ allowed: overallAllowed,
298
+ remaining: minRemaining === Infinity ? Math.min(this.options.user.perMinute, this.options.ip.perMinute, this.options.endpoint.perMinute) : minRemaining,
299
+ resetIn: minResetIn === Infinity ? 60000 - (now % 60000) : minResetIn,
300
+ action: overallAllowed ? undefined : this.options.action,
301
+ throttleDelay: this.options.action === 'throttle' && !overallAllowed ? this.options.throttleDelay : undefined,
302
+ };
303
+ }
304
+ /**
305
+ * Block an identifier until `now + blockDurationMs` (defaults to the
306
+ * standard 1 minute block window when no specific breached window was
307
+ * given, e.g. for callers outside checkLimit).
308
+ */
309
+ blockEntry(identifier, type, endpoint, now, blockDurationMs = 60000) {
310
+ let entry = this.store.get(identifier, type, endpoint);
311
+ if (!entry) {
312
+ entry = {
313
+ identifier,
314
+ identifierType: type,
315
+ endpoint,
316
+ countMinute: 0,
317
+ countHour: 0,
318
+ countDay: 0,
319
+ windowStartMinute: Math.floor(now / 60000),
320
+ windowStartHour: Math.floor(now / 3600000),
321
+ windowStartDay: Math.floor(now / 86400000),
322
+ lastRequest: new Date(),
323
+ blocked: false,
324
+ };
325
+ }
326
+ entry.blocked = true;
327
+ const newBlockUntil = now + Math.max(blockDurationMs, 0);
328
+ // Never shorten an existing block (e.g. a daily breach shouldn't be
329
+ // clobbered by a shorter minute-level block computed afterwards).
330
+ const existingUntil = entry.blockUntil?.getTime() ?? 0;
331
+ entry.blockUntil = new Date(Math.max(newBlockUntil, existingUntil));
332
+ this.store.set(entry);
333
+ if (this.options.logEvents) {
334
+ console.log(`[RateLimiter] Blocked: ${identifier} (${type}) until ${entry.blockUntil}`);
335
+ }
336
+ }
337
+ /**
338
+ * Update or create an entry, independently incrementing the minute,
339
+ * hour, and day counters for the current request. Each window is reset
340
+ * to 0 when its bucket boundary rolls over.
341
+ */
342
+ updateEntry(identifier, type, endpoint, now) {
343
+ const currentMinute = Math.floor(now / 60000);
344
+ const currentHour = Math.floor(now / 3600000);
345
+ const currentDay = Math.floor(now / 86400000);
346
+ let entry = this.store.get(identifier, type, endpoint);
347
+ if (!entry) {
348
+ entry = {
349
+ identifier,
350
+ identifierType: type,
351
+ endpoint,
352
+ countMinute: 0,
353
+ countHour: 0,
354
+ countDay: 0,
355
+ windowStartMinute: currentMinute,
356
+ windowStartHour: currentHour,
357
+ windowStartDay: currentDay,
358
+ lastRequest: new Date(),
359
+ blocked: false,
360
+ };
361
+ }
362
+ // Reset counters whose window has rolled over
363
+ if (entry.windowStartMinute !== currentMinute) {
364
+ entry.countMinute = 0;
365
+ entry.windowStartMinute = currentMinute;
366
+ }
367
+ if (entry.windowStartHour !== currentHour) {
368
+ entry.countHour = 0;
369
+ entry.windowStartHour = currentHour;
370
+ }
371
+ if (entry.windowStartDay !== currentDay) {
372
+ entry.countDay = 0;
373
+ entry.windowStartDay = currentDay;
374
+ }
375
+ // Each window tracks its own independent count of requests seen within
376
+ // it, rather than all three being forced to the same value.
377
+ entry.countMinute += 1;
378
+ entry.countHour += 1;
379
+ entry.countDay += 1;
380
+ entry.lastRequest = new Date();
381
+ // Unblock if needed
382
+ if (entry.blocked && (!entry.blockUntil || entry.blockUntil.getTime() <= now)) {
383
+ entry.blocked = false;
384
+ entry.blockUntil = undefined;
385
+ }
386
+ this.store.set(entry);
387
+ }
388
+ /**
389
+ * Get rate limit statistics
390
+ */
391
+ getStats() {
392
+ const entries = this.store.getAll();
393
+ const byUser = {};
394
+ const byIP = {};
395
+ const byEndpoint = {};
396
+ const uniqueUsers = new Set();
397
+ const uniqueIPs = new Set();
398
+ const uniqueEndpoints = new Set();
399
+ for (const entry of entries) {
400
+ if (entry.identifierType === 'user') {
401
+ byUser[entry.identifier] = {
402
+ minute: entry.countMinute,
403
+ hour: entry.countHour,
404
+ day: entry.countDay,
405
+ };
406
+ uniqueUsers.add(entry.identifier);
407
+ }
408
+ else if (entry.identifierType === 'ip') {
409
+ byIP[entry.identifier] = {
410
+ minute: entry.countMinute,
411
+ hour: entry.countHour,
412
+ day: entry.countDay,
413
+ };
414
+ uniqueIPs.add(entry.identifier);
415
+ }
416
+ else if (entry.identifierType === 'endpoint') {
417
+ byEndpoint[entry.identifier] = {
418
+ minute: entry.countMinute,
419
+ hour: entry.countHour,
420
+ day: entry.countDay,
421
+ };
422
+ uniqueEndpoints.add(entry.identifier);
423
+ }
424
+ }
425
+ return {
426
+ totalRequests: this.totalRequests,
427
+ blockedRequests: this.blockedRequests,
428
+ throttledRequests: this.throttledRequests,
429
+ uniqueUsers: uniqueUsers.size,
430
+ uniqueIPs: uniqueIPs.size,
431
+ uniqueEndpoints: uniqueEndpoints.size,
432
+ byUser,
433
+ byIP,
434
+ byEndpoint,
435
+ };
436
+ }
437
+ /**
438
+ * Reset all rate limit counters
439
+ */
440
+ reset() {
441
+ this.store.clear();
442
+ this.totalRequests = 0;
443
+ this.blockedRequests = 0;
444
+ this.throttledRequests = 0;
445
+ if (this.options.logEvents) {
446
+ console.log('[RateLimiter] Counters reset');
447
+ }
448
+ }
449
+ /**
450
+ * Reset specific user/IP
451
+ */
452
+ resetIdentifier(identifier, type, endpoint) {
453
+ const key = `${type}:${identifier}${endpoint ? `:${endpoint}` : ''}`;
454
+ this.store.delete(key);
455
+ if (this.options.logEvents) {
456
+ console.log(`[RateLimiter] Reset: ${key}`);
457
+ }
458
+ }
459
+ /**
460
+ * Get current window reset time
461
+ */
462
+ getResetTime() {
463
+ const now = Date.now();
464
+ return 60000 - (now % 60000); // Reset in X seconds
465
+ }
466
+ }
467
+ exports.RateLimiter = RateLimiter;
468
+ /**
469
+ * Rate limit decorator for models
470
+ *
471
+ * Usage:
472
+ * @RateLimit({ perMinute: 100, perHour: 1000 })
473
+ * class User extends Model { ... }
474
+ */
475
+ function RateLimit(options) {
476
+ return function (target) {
477
+ // Store rate limit metadata on the model
478
+ target.rateLimitOptions = {
479
+ perMinute: options.perMinute ?? 100,
480
+ perHour: options.perHour ?? 1000,
481
+ perDay: options.perDay ?? 10000,
482
+ endpoint: options.endpoint,
483
+ roles: options.roles,
484
+ };
485
+ return target;
486
+ };
487
+ }
488
+ /**
489
+ * Get rate limit options from a model
490
+ */
491
+ function getRateLimitOptions(model) {
492
+ return model.rateLimitOptions ?? null;
493
+ }
494
+ // ==================== Utility Functions ====================
495
+ let globalLimiter = null;
496
+ /**
497
+ * Get the global rate limiter instance
498
+ */
499
+ function getGlobalLimiter() {
500
+ return globalLimiter;
501
+ }
502
+ /**
503
+ * Initialize the global rate limiter
504
+ */
505
+ function initRateLimiter(prorm, options) {
506
+ globalLimiter = new RateLimiter(prorm, options);
507
+ return globalLimiter;
508
+ }
509
+ /**
510
+ * Quick rate limit check using global limiter
511
+ */
512
+ function checkRateLimit(userId, ip, endpoint, role) {
513
+ if (!globalLimiter) {
514
+ return { allowed: true, remaining: 100, resetIn: 60000 };
515
+ }
516
+ return globalLimiter.checkLimit(userId, ip, endpoint, role);
517
+ }
518
+ // ==================== Compliance Helpers ====================
519
+ /**
520
+ * Check if rate limiting satisfies a compliance framework
521
+ */
522
+ function satisfiesCompliance(framework) {
523
+ const frameworks = [
524
+ 'OWASP_Top_10',
525
+ 'NIST_800_53',
526
+ 'NIST_CSF',
527
+ 'ISO_27001',
528
+ 'SOC_2',
529
+ 'DORA',
530
+ 'NIS2',
531
+ 'PCI_DSS',
532
+ ];
533
+ return frameworks.includes(framework);
534
+ }
535
+ /**
536
+ * Get recommended rate limits for a framework
537
+ */
538
+ function getRecommendedLimits(framework) {
539
+ switch (framework) {
540
+ case 'OWASP_Top_10':
541
+ // Prevent enumeration attacks
542
+ return {
543
+ user: { perMinute: 100, perHour: 1000 },
544
+ ip: { perMinute: 50, perHour: 500 },
545
+ endpoint: { perMinute: 200, perHour: 2000 },
546
+ action: 'block',
547
+ };
548
+ case 'DORA':
549
+ // Digital Operational Resilience Act
550
+ return {
551
+ user: { perMinute: 200, perHour: 2000, perDay: 10000 },
552
+ ip: { perMinute: 100, perHour: 1000, perDay: 5000 },
553
+ endpoint: { perMinute: 300, perHour: 3000, perDay: 30000 },
554
+ action: 'throttle',
555
+ throttleDelay: 500,
556
+ };
557
+ case 'NIS2':
558
+ // Network and Information Security Directive
559
+ return {
560
+ user: { perMinute: 150, perHour: 1500, perDay: 10000 },
561
+ ip: { perMinute: 75, perHour: 750, perDay: 5000 },
562
+ endpoint: { perMinute: 250, perHour: 2500, perDay: 25000 },
563
+ action: 'block',
564
+ logEvents: true,
565
+ };
566
+ case 'PCI_DSS':
567
+ return {
568
+ user: { perMinute: 50, perHour: 500, perDay: 2000 },
569
+ ip: { perMinute: 25, perHour: 250, perDay: 1000 },
570
+ endpoint: { perMinute: 100, perHour: 1000, perDay: 10000 },
571
+ action: 'block',
572
+ };
573
+ default:
574
+ return {
575
+ user: { perMinute: 100, perHour: 1000 },
576
+ ip: { perMinute: 50, perHour: 500 },
577
+ endpoint: { perMinute: 200, perHour: 2000 },
578
+ };
579
+ }
580
+ }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ /**
3
+ * RightToErasure
4
+ *
5
+ * Handles Data Subject Access Requests (DSARs) for the right to be forgotten.
6
+ * Supports hard deletion or field-level anonymization (GDPR-preferred),
7
+ * and cascades through associated models.
8
+ *
9
+ * Covers ~57 compliance frameworks including:
10
+ * GDPR Art 17, CCPA, CPRA, LGPD, APPI, PIPA (South Korea),
11
+ * PDPA (Thailand, Singapore, Philippines, Malaysia), PoPI, DPDP India,
12
+ * and all US state privacy laws with deletion rights.
13
+ *
14
+ * Usage:
15
+ * // Hard delete all records for a user:
16
+ * const result = await RightToErasure.erase(User, { where: { id: userId } });
17
+ *
18
+ * // Anonymize instead of delete (preserves referential integrity):
19
+ * const result = await RightToErasure.erase(User, { where: { id: userId } }, {
20
+ * mode: 'anonymize',
21
+ * piiFields: ['email', 'name', 'phoneNumber', 'address'],
22
+ * });
23
+ *
24
+ * // Cascade to associated models:
25
+ * const result = await RightToErasure.erase(User, { where: { id: userId } }, {
26
+ * mode: 'anonymize',
27
+ * cascade: [
28
+ * { model: Order, foreignKey: 'userId', piiFields: ['shippingAddress'] },
29
+ * { model: Payment, foreignKey: 'userId', piiFields: ['cardLastFour'] },
30
+ * ],
31
+ * });
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.RightToErasure = void 0;
35
+ function generateRequestId() {
36
+ return `dsar-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
37
+ }
38
+ async function anonymizeRecords(records, piiFields, redactedValue) {
39
+ let count = 0;
40
+ for (const record of records) {
41
+ const patch = {};
42
+ for (const field of piiFields) {
43
+ patch[field] = redactedValue;
44
+ }
45
+ if (Object.keys(patch).length > 0) {
46
+ await record.update(patch);
47
+ count++;
48
+ }
49
+ }
50
+ return count;
51
+ }
52
+ async function processErasure(model, where, options, requestId) {
53
+ const modelName = model.name ?? model.tableName ?? 'Unknown';
54
+ let affected = 0;
55
+ if (options.mode === 'delete') {
56
+ affected = (await model.destroy({ where, force: options.force })) ?? 0;
57
+ }
58
+ else {
59
+ const records = await model.findAll({ where });
60
+ const pii = options.piiFields.length > 0 ? options.piiFields : Object.keys(records[0]?.dataValues ?? {}).filter((k) => k !== 'id' && k !== 'createdAt' && k !== 'updatedAt');
61
+ affected = await anonymizeRecords(records, pii, options.redactedValue);
62
+ }
63
+ const cascadeResults = [];
64
+ for (const target of options.cascade) {
65
+ // Find the primary key values of the erased subject to cascade
66
+ const subjectIds = await model.findAll({ where, attributes: ['id'] });
67
+ const ids = subjectIds.map((r) => r.id ?? r.dataValues?.id);
68
+ if (ids.length === 0)
69
+ continue;
70
+ const cascadeWhere = { [target.foreignKey]: ids };
71
+ const cascadeResult = await processErasure(target.model, cascadeWhere, {
72
+ mode: options.mode,
73
+ piiFields: target.piiFields ?? [],
74
+ redactedValue: options.redactedValue,
75
+ force: options.force,
76
+ cascade: target.cascade ?? [],
77
+ }, requestId);
78
+ cascadeResults.push(cascadeResult);
79
+ }
80
+ return {
81
+ model: modelName,
82
+ mode: options.mode,
83
+ affected,
84
+ cascadeResults: cascadeResults.length > 0 ? cascadeResults : undefined,
85
+ timestamp: new Date(),
86
+ requestId,
87
+ };
88
+ }
89
+ class RightToErasure {
90
+ /**
91
+ * Erase personal data for one or more data subjects.
92
+ *
93
+ * @param model - The primary model to erase from
94
+ * @param query - Where clause identifying the data subject(s)
95
+ * @param options - Erasure mode, fields, and cascade configuration
96
+ * @returns An ErasureResult with a unique request ID for DSAR audit purposes
97
+ */
98
+ static async erase(model, query, options = {}) {
99
+ const resolved = {
100
+ mode: options.mode ?? 'delete',
101
+ piiFields: options.piiFields ?? [],
102
+ redactedValue: options.redactedValue ?? '[REDACTED]',
103
+ force: options.force !== false,
104
+ cascade: options.cascade ?? [],
105
+ };
106
+ const requestId = generateRequestId();
107
+ return processErasure(model, query.where, resolved, requestId);
108
+ }
109
+ /**
110
+ * Check if records exist for a given subject (for verification before erasure).
111
+ */
112
+ static async exists(model, query) {
113
+ const count = await model.count({ where: query.where });
114
+ return count > 0;
115
+ }
116
+ }
117
+ exports.RightToErasure = RightToErasure;