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,364 @@
1
+ "use strict";
2
+ /**
3
+ * PrivacyImpactAssessment
4
+ *
5
+ * A ready-made model and service for conducting Privacy Impact Assessments (PIA)
6
+ * and Data Protection Impact Assessments (DPIA) for high-risk data processing.
7
+ *
8
+ * Covers compliance frameworks including:
9
+ * GDPR Art. 35 (DPIA), ISO 27701, UK GDPR, CCPA, PIPEDA,
10
+ * LGPD (Brazil), APPI (Japan), PDPA (Thailand, Singapore), and more.
11
+ *
12
+ * Usage:
13
+ * // Initialize once (creates the pia_assessments table):
14
+ * await PrivacyImpactAssessment.initialize(prorm);
15
+ *
16
+ * // Create a new PIA:
17
+ * const assessment = await PrivacyImpactAssessment.createAssessment(
18
+ * 'User Analytics Processing',
19
+ * ['email', 'ip_address', 'behavioral_data'],
20
+ * 'analyze_user_behavior_for_product_improvement'
21
+ * );
22
+ *
23
+ * // Record identified risks:
24
+ * await PrivacyImpactAssessment.recordRisk(
25
+ * assessment.id,
26
+ * 'Potential data breach exposing user profiles',
27
+ * 'high',
28
+ * * 'Implement AES-256 encryption at rest and TLS 1.3 in transit'
29
+ * );
30
+ *
31
+ * // Approve the assessment:
32
+ * await PrivacyImpactAssessment.approveAssessment(assessment.id, 'DPO John Smith');
33
+ *
34
+ * // Link to a model:
35
+ * await PrivacyImpactAssessment.linkToModel(assessment.id, UserAnalytics);
36
+ *
37
+ * // Get assessment details:
38
+ * const details = await PrivacyImpactAssessment.getAssessment(assessment.id);
39
+ */
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.PrivacyImpactAssessment = void 0;
42
+ const data_types_1 = require("../models/data-types");
43
+ const operators_1 = require("../models/operators");
44
+ let PIAModel = null;
45
+ let RiskModel = null;
46
+ class PrivacyImpactAssessment {
47
+ /**
48
+ * Initialize the PrivacyImpactAssessment model and sync the tables.
49
+ * Call once at application startup.
50
+ *
51
+ * @param prorm - The Prorm instance
52
+ * @param tableName - Override the default table name 'pia_assessments'
53
+ */
54
+ static async initialize(prorm, tableName = 'pia_assessments') {
55
+ PIAModel = prorm.define('PIAContainer', {
56
+ id: {
57
+ type: data_types_1.DataTypes.INTEGER(),
58
+ primaryKey: true,
59
+ autoIncrement: true,
60
+ },
61
+ name: {
62
+ type: data_types_1.DataTypes.STRING(255),
63
+ allowNull: false,
64
+ comment: 'Name/title of the privacy impact assessment',
65
+ },
66
+ dataTypes: {
67
+ type: data_types_1.DataTypes.JSON(),
68
+ allowNull: false,
69
+ comment: 'List of data types being processed',
70
+ },
71
+ processingPurpose: {
72
+ type: data_types_1.DataTypes.TEXT(),
73
+ allowNull: false,
74
+ comment: 'Purpose of the data processing',
75
+ },
76
+ status: {
77
+ type: data_types_1.DataTypes.ENUM('draft', 'in_review', 'approved', 'rejected', 'archived'),
78
+ allowNull: false,
79
+ defaultValue: 'draft',
80
+ comment: 'Current status of the assessment',
81
+ },
82
+ approvedBy: {
83
+ type: data_types_1.DataTypes.STRING(255),
84
+ allowNull: true,
85
+ comment: 'Name/ID of the approver (DPO, compliance officer)',
86
+ },
87
+ approvedAt: {
88
+ type: data_types_1.DataTypes.DATE(),
89
+ allowNull: true,
90
+ comment: 'Timestamp when the assessment was approved',
91
+ },
92
+ linkedModels: {
93
+ type: data_types_1.DataTypes.JSON(),
94
+ allowNull: true,
95
+ comment: 'List of model names linked to this assessment',
96
+ },
97
+ metadata: {
98
+ type: data_types_1.DataTypes.JSON(),
99
+ allowNull: true,
100
+ comment: 'Additional structured metadata',
101
+ },
102
+ }, {
103
+ tableName,
104
+ timestamps: true,
105
+ underscored: true,
106
+ indexes: [
107
+ { fields: ['status'] },
108
+ { fields: ['name'] },
109
+ ],
110
+ });
111
+ RiskModel = prorm.define('PIARisk', {
112
+ id: {
113
+ type: data_types_1.DataTypes.INTEGER(),
114
+ primaryKey: true,
115
+ autoIncrement: true,
116
+ },
117
+ assessmentId: {
118
+ type: data_types_1.DataTypes.INTEGER(),
119
+ allowNull: false,
120
+ references: {
121
+ model: tableName,
122
+ key: 'id',
123
+ },
124
+ comment: 'Foreign key to the parent PIA assessment',
125
+ },
126
+ risk: {
127
+ type: data_types_1.DataTypes.TEXT(),
128
+ allowNull: false,
129
+ comment: 'Description of the identified risk',
130
+ },
131
+ severity: {
132
+ type: data_types_1.DataTypes.ENUM('low', 'medium', 'high', 'critical'),
133
+ allowNull: false,
134
+ comment: 'Severity level of the risk',
135
+ },
136
+ mitigation: {
137
+ type: data_types_1.DataTypes.TEXT(),
138
+ allowNull: true,
139
+ comment: 'Mitigation measures for the risk',
140
+ },
141
+ }, {
142
+ tableName: `${tableName}_risks`,
143
+ timestamps: true,
144
+ underscored: true,
145
+ indexes: [
146
+ { fields: ['assessment_id'] },
147
+ { fields: ['severity'] },
148
+ ],
149
+ });
150
+ await PIAModel.sync({ alter: false });
151
+ await RiskModel.sync({ alter: false });
152
+ }
153
+ static requireModels() {
154
+ if (!PIAModel || !RiskModel) {
155
+ throw new Error('[PrivacyImpactAssessment] Not initialized. Call PrivacyImpactAssessment.initialize(prorm) first.');
156
+ }
157
+ return { pia: PIAModel, risk: RiskModel };
158
+ }
159
+ /**
160
+ * Create a new Privacy Impact Assessment.
161
+ *
162
+ * @param name - Name/title of the assessment
163
+ * @param dataTypes - List of data types being processed
164
+ * @param processingPurpose - Purpose of the processing operation
165
+ * @returns The created PIA record
166
+ */
167
+ static async createAssessment(name, dataTypes, processingPurpose) {
168
+ const { pia } = PrivacyImpactAssessment.requireModels();
169
+ return pia.create({
170
+ name,
171
+ dataTypes,
172
+ processingPurpose,
173
+ status: 'draft',
174
+ });
175
+ }
176
+ /**
177
+ * Record a risk associated with a PIA assessment.
178
+ *
179
+ * @param assessmentId - The ID of the assessment to add the risk to
180
+ * @param risk - Description of the identified risk
181
+ * @param severity - Severity level of the risk
182
+ * @param mitigation - Mitigation measures for the risk
183
+ * @returns The created risk record
184
+ */
185
+ static async recordRisk(assessmentId, risk, severity, mitigation) {
186
+ const { risk: RiskModelClass } = PrivacyImpactAssessment.requireModels();
187
+ return RiskModelClass.create({
188
+ assessmentId,
189
+ risk,
190
+ severity,
191
+ mitigation,
192
+ });
193
+ }
194
+ /**
195
+ * Approve a Privacy Impact Assessment.
196
+ * Sets status to 'approved' and records approver information.
197
+ *
198
+ * @param assessmentId - The ID of the assessment to approve
199
+ * @param approver - Name/ID of the approver (e.g., DPO name)
200
+ * @returns The updated PIA record
201
+ */
202
+ static async approveAssessment(assessmentId, approver) {
203
+ const { pia } = PrivacyImpactAssessment.requireModels();
204
+ const assessment = await pia.findByPk(assessmentId);
205
+ if (!assessment) {
206
+ throw new Error(`[PrivacyImpactAssessment] Assessment not found: ${assessmentId}`);
207
+ }
208
+ await assessment.update({
209
+ status: 'approved',
210
+ approvedBy: approver,
211
+ approvedAt: new Date(),
212
+ });
213
+ return assessment;
214
+ }
215
+ /**
216
+ * Get a Privacy Impact Assessment by ID.
217
+ * Includes associated risks.
218
+ *
219
+ * @param assessmentId - The ID of the assessment to retrieve
220
+ * @returns The PIA record with associated risks
221
+ */
222
+ static async getAssessment(assessmentId) {
223
+ const { pia, risk } = PrivacyImpactAssessment.requireModels();
224
+ return pia.findByPk(assessmentId, {
225
+ include: [
226
+ {
227
+ model: risk,
228
+ as: 'risks',
229
+ },
230
+ ],
231
+ });
232
+ }
233
+ /**
234
+ * Link a model to a PIA assessment.
235
+ * Useful for tracking which models are covered by the assessment.
236
+ *
237
+ * @param assessmentId - The ID of the assessment
238
+ * @param model - The model class to link (can be a Prorm model or string name)
239
+ * @returns The updated PIA record
240
+ */
241
+ static async linkToModel(assessmentId, model) {
242
+ const { pia } = PrivacyImpactAssessment.requireModels();
243
+ const assessment = await pia.findByPk(assessmentId);
244
+ if (!assessment) {
245
+ throw new Error(`[PrivacyImpactAssessment] Assessment not found: ${assessmentId}`);
246
+ }
247
+ const modelName = typeof model === 'string' ? model : (model.tableName ?? model.name ?? 'Unknown');
248
+ const currentLinkedModels = assessment.linkedModels ?? [];
249
+ if (!currentLinkedModels.includes(modelName)) {
250
+ await assessment.update({
251
+ linkedModels: [...currentLinkedModels, modelName],
252
+ });
253
+ }
254
+ return assessment;
255
+ }
256
+ /**
257
+ * Get all assessments with a specific status.
258
+ *
259
+ * @param status - The status to filter by
260
+ * @returns Array of PIA records matching the status
261
+ */
262
+ static async getByStatus(status) {
263
+ const { pia } = PrivacyImpactAssessment.requireModels();
264
+ return pia.findAll({
265
+ where: { status },
266
+ order: [['createdAt', 'DESC']],
267
+ });
268
+ }
269
+ /**
270
+ * Reject a Privacy Impact Assessment.
271
+ *
272
+ * @param assessmentId - The ID of the assessment to reject
273
+ * @param reason - Reason for rejection
274
+ * @returns The updated PIA record
275
+ */
276
+ static async rejectAssessment(assessmentId, reason) {
277
+ const { pia } = PrivacyImpactAssessment.requireModels();
278
+ const assessment = await pia.findByPk(assessmentId);
279
+ if (!assessment) {
280
+ throw new Error(`[PrivacyImpactAssessment] Assessment not found: ${assessmentId}`);
281
+ }
282
+ await assessment.update({
283
+ status: 'rejected',
284
+ metadata: {
285
+ ...(assessment.metadata ?? {}),
286
+ rejectionReason: reason,
287
+ },
288
+ });
289
+ return assessment;
290
+ }
291
+ /**
292
+ * Submit an assessment for review.
293
+ * Changes status from 'draft' to 'in_review'.
294
+ *
295
+ * @param assessmentId - The ID of the assessment to submit
296
+ * @returns The updated PIA record
297
+ */
298
+ static async submitForReview(assessmentId) {
299
+ const { pia } = PrivacyImpactAssessment.requireModels();
300
+ const assessment = await pia.findByPk(assessmentId);
301
+ if (!assessment) {
302
+ throw new Error(`[PrivacyImpactAssessment] Assessment not found: ${assessmentId}`);
303
+ }
304
+ if (assessment.status !== 'draft') {
305
+ throw new Error(`[PrivacyImpactAssessment] Only draft assessments can be submitted for review. Current status: ${assessment.status}`);
306
+ }
307
+ await assessment.update({
308
+ status: 'in_review',
309
+ });
310
+ return assessment;
311
+ }
312
+ /**
313
+ * Get all risks for an assessment grouped by severity.
314
+ *
315
+ * @param assessmentId - The ID of the assessment
316
+ * @returns Object with risks grouped by severity level
317
+ */
318
+ static async getRisksBySeverity(assessmentId) {
319
+ const { risk } = PrivacyImpactAssessment.requireModels();
320
+ const risks = await risk.findAll({
321
+ where: { assessmentId },
322
+ order: [['severity', 'DESC']],
323
+ });
324
+ return {
325
+ critical: risks.filter((r) => r.severity === 'critical'),
326
+ high: risks.filter((r) => r.severity === 'high'),
327
+ medium: risks.filter((r) => r.severity === 'medium'),
328
+ low: risks.filter((r) => r.severity === 'low'),
329
+ };
330
+ }
331
+ /**
332
+ * Check if an assessment has any high or critical risks without mitigations.
333
+ *
334
+ * @param assessmentId - The ID of the assessment
335
+ * @returns True if there are unresolved high-risk items
336
+ */
337
+ static async hasUnresolvedHighRisks(assessmentId) {
338
+ const { risk } = PrivacyImpactAssessment.requireModels();
339
+ const count = await risk.count({
340
+ where: {
341
+ [operators_1.Op.and]: [
342
+ { assessmentId, severity: { [operators_1.Op.in]: ['high', 'critical'] } },
343
+ { [operators_1.Op.or]: [{ mitigation: { [operators_1.Op.isNull]: true } }, { mitigation: '' }] },
344
+ ],
345
+ },
346
+ });
347
+ return count > 0;
348
+ }
349
+ /**
350
+ * List all models linked to a PIA assessment.
351
+ *
352
+ * @param assessmentId - The ID of the assessment
353
+ * @returns Array of linked model names
354
+ */
355
+ static async getLinkedModels(assessmentId) {
356
+ const { pia } = PrivacyImpactAssessment.requireModels();
357
+ const assessment = await pia.findByPk(assessmentId);
358
+ if (!assessment) {
359
+ throw new Error(`[PrivacyImpactAssessment] Assessment not found: ${assessmentId}`);
360
+ }
361
+ return assessment.linkedModels ?? [];
362
+ }
363
+ }
364
+ exports.PrivacyImpactAssessment = PrivacyImpactAssessment;
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ /**
3
+ * PseudonymizationService
4
+ *
5
+ * Replaces identifiers with non-reversible tokens for analytics, logging,
6
+ * and cross-system sharing while preserving referential integrity.
7
+ * Supports deterministic tokenization (same input → same token) using HMAC-SHA256.
8
+ *
9
+ * Covers ~34 compliance frameworks including:
10
+ * GDPR Art 25 (Privacy by Design), ISO 27701, ISO 29151, HIPAA Safe Harbor,
11
+ * PIPL (China), KVKK (Turkey), NIST SP 800-122, and more.
12
+ *
13
+ * Usage:
14
+ * // Configure once at startup:
15
+ * await PseudonymizationService.configure({
16
+ * secretKey: process.env.PSEUDO_SECRET_KEY,
17
+ * store: 'database', // or 'memory'
18
+ * });
19
+ *
20
+ * // Tokenize a value:
21
+ * const token = await PseudonymizationService.tokenize('user-123');
22
+ * // token: 'a1b2c3d4e5f6...' (deterministic, same input = same output)
23
+ *
24
+ * // Detokenize (reverse):
25
+ * const original = await PseudonymizationService.detokenize(token);
26
+ * // original: 'user-123'
27
+ *
28
+ * // Or use the decorator on a model field:
29
+ * @Pseudonymize('nationalId')
30
+ * class Patient extends Model { ... }
31
+ *
32
+ * // Apply hooks to auto-tokenize on write:
33
+ * PseudonymizationService.applyHooks(Patient, 'nationalId');
34
+ */
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.PseudonymizationService = void 0;
37
+ exports.configure = configure;
38
+ exports.tokenize = tokenize;
39
+ exports.detokenize = detokenize;
40
+ exports.exists = exists;
41
+ exports.removeToken = removeToken;
42
+ exports.removeOriginal = removeOriginal;
43
+ exports.getPseudonymizedFields = getPseudonymizedFields;
44
+ exports.Pseudonymize = Pseudonymize;
45
+ exports.applyHooks = applyHooks;
46
+ const crypto_1 = require("crypto");
47
+ // In-memory storage for tokens
48
+ const tokenStore = new Map();
49
+ const reverseStore = new Map();
50
+ let config = null;
51
+ let tokenModel = null;
52
+ /**
53
+ * Configure the pseudonymization service.
54
+ *
55
+ * @param cfg - Configuration options
56
+ */
57
+ async function configure(cfg) {
58
+ if (!cfg.secretKey || cfg.secretKey.length < 32) {
59
+ throw new Error('[PseudonymizationService] secretKey must be at least 32 characters. ' +
60
+ 'Use a strong, randomly generated key and store it securely.');
61
+ }
62
+ config = {
63
+ store: cfg.store ?? 'memory',
64
+ algorithm: cfg.algorithm ?? 'sha256',
65
+ ...cfg,
66
+ };
67
+ if (cfg.store === 'database' && cfg.model) {
68
+ tokenModel = cfg.model;
69
+ await tokenModel.sync?.({ alter: true });
70
+ }
71
+ }
72
+ function getConfig() {
73
+ if (!config) {
74
+ throw new Error('[PseudonymizationService] Not configured. Call await PseudonymizationService.configure({ secretKey }) first.');
75
+ }
76
+ return config;
77
+ }
78
+ /**
79
+ * Generate a deterministic HMAC token from a value.
80
+ */
81
+ function generateToken(value) {
82
+ const cfg = getConfig();
83
+ const hmac = (0, crypto_1.createHmac)(cfg.algorithm, cfg.secretKey);
84
+ hmac.update(value);
85
+ return hmac.digest('hex');
86
+ }
87
+ /**
88
+ * Tokenize a value (replace with a pseudonym/token).
89
+ * Returns the same token for the same input (deterministic).
90
+ */
91
+ async function tokenize(value) {
92
+ if (!value)
93
+ return '';
94
+ const cfg = getConfig();
95
+ // Check if we already have a token for this value
96
+ if (cfg.store === 'memory') {
97
+ const existing = reverseStore.get(value);
98
+ if (existing)
99
+ return existing;
100
+ }
101
+ else if (cfg.store === 'database' && tokenModel) {
102
+ const record = await tokenModel.findOne({ where: { original: value } });
103
+ if (record)
104
+ return record.token;
105
+ }
106
+ // Generate new token
107
+ const token = generateToken(value);
108
+ // Store the mapping
109
+ if (cfg.store === 'memory') {
110
+ tokenStore.set(token, { token, original: value, createdAt: new Date() });
111
+ reverseStore.set(value, token);
112
+ }
113
+ else if (cfg.store === 'database' && tokenModel) {
114
+ await tokenModel.findOrCreate({
115
+ where: { token },
116
+ defaults: { token, original: value, createdAt: new Date() },
117
+ });
118
+ }
119
+ return token;
120
+ }
121
+ /**
122
+ * Detokenize (reverse) a token to get the original value.
123
+ */
124
+ async function detokenize(token) {
125
+ if (!token)
126
+ return null;
127
+ const cfg = getConfig();
128
+ if (cfg.store === 'memory') {
129
+ const record = tokenStore.get(token);
130
+ return record?.original ?? null;
131
+ }
132
+ else if (cfg.store === 'database' && tokenModel) {
133
+ const record = await tokenModel.findOne({ where: { token } });
134
+ return record?.original ?? null;
135
+ }
136
+ return null;
137
+ }
138
+ /**
139
+ * Check if a token exists.
140
+ */
141
+ async function exists(token) {
142
+ if (!token)
143
+ return false;
144
+ const cfg = getConfig();
145
+ if (cfg.store === 'memory') {
146
+ return tokenStore.has(token);
147
+ }
148
+ else if (cfg.store === 'database' && tokenModel) {
149
+ const count = await tokenModel.count({ where: { token } });
150
+ return count > 0;
151
+ }
152
+ return false;
153
+ }
154
+ /**
155
+ * Delete a token (irreversible — useful for right to erasure).
156
+ */
157
+ async function removeToken(token) {
158
+ if (!token)
159
+ return false;
160
+ const cfg = getConfig();
161
+ if (cfg.store === 'memory') {
162
+ const record = tokenStore.get(token);
163
+ if (record) {
164
+ reverseStore.delete(record.original);
165
+ tokenStore.delete(token);
166
+ return true;
167
+ }
168
+ }
169
+ else if (cfg.store === 'database' && tokenModel) {
170
+ const record = await tokenModel.findOne({ where: { token } });
171
+ if (record) {
172
+ await record.destroy();
173
+ return true;
174
+ }
175
+ }
176
+ return false;
177
+ }
178
+ /**
179
+ * Delete tokens for a specific original value.
180
+ */
181
+ async function removeOriginal(original) {
182
+ if (!original)
183
+ return false;
184
+ const cfg = getConfig();
185
+ if (cfg.store === 'memory') {
186
+ const token = reverseStore.get(original);
187
+ if (token) {
188
+ tokenStore.delete(token);
189
+ reverseStore.delete(original);
190
+ return true;
191
+ }
192
+ }
193
+ else if (cfg.store === 'database' && tokenModel) {
194
+ const record = await tokenModel.findOne({ where: { original } });
195
+ if (record) {
196
+ await record.destroy();
197
+ return true;
198
+ }
199
+ }
200
+ return false;
201
+ }
202
+ // ==================== Decorator ====================
203
+ const PSEUDO_FIELDS_KEY = '__orm_pseudo_fields__';
204
+ function getPseudonymizedFields(target) {
205
+ return target[PSEUDO_FIELDS_KEY] ?? [];
206
+ }
207
+ /**
208
+ * Mark a field for automatic pseudonymization.
209
+ *
210
+ * @Pseudonymize('nationalId')
211
+ * class Patient extends Model { ... }
212
+ */
213
+ function Pseudonymize(field) {
214
+ return function (target) {
215
+ if (!target[PSEUDO_FIELDS_KEY]) {
216
+ target[PSEUDO_FIELDS_KEY] = [];
217
+ }
218
+ target[PSEUDO_FIELDS_KEY].push(field);
219
+ };
220
+ }
221
+ // ==================== Hook Wiring ====================
222
+ /**
223
+ * Wire hooks onto a model to auto-tokenize the specified field on write
224
+ * and auto-detokenize on read.
225
+ *
226
+ * @param Model - The model class
227
+ * @param field - The field to pseudonymize
228
+ */
229
+ function applyHooks(Model, field) {
230
+ const hookName = 'orm:pseudonymize';
231
+ if (typeof Model.addHook === 'function') {
232
+ // Tokenize before create/update
233
+ Model.addHook('beforeCreate', hookName, async (instance) => {
234
+ const value = instance.getDataValue ? instance.getDataValue(field) : instance[field];
235
+ if (value && typeof value === 'string' && !value.startsWith('pseudo:')) {
236
+ const token = await tokenize(value);
237
+ if (instance.setDataValue) {
238
+ instance.setDataValue(field, token);
239
+ }
240
+ else {
241
+ instance[field] = token;
242
+ }
243
+ }
244
+ });
245
+ Model.addHook('beforeUpdate', hookName, async (instance) => {
246
+ const value = instance.getDataValue ? instance.getDataValue(field) : instance[field];
247
+ if (instance.changed(field) && value && typeof value === 'string' && !value.startsWith('pseudo:')) {
248
+ const token = await tokenize(value);
249
+ if (instance.setDataValue) {
250
+ instance.setDataValue(field, token);
251
+ }
252
+ else {
253
+ instance[field] = token;
254
+ }
255
+ }
256
+ });
257
+ }
258
+ }
259
+ // ==================== Class Export ====================
260
+ class PseudonymizationService {
261
+ }
262
+ exports.PseudonymizationService = PseudonymizationService;
263
+ PseudonymizationService.configure = configure;
264
+ PseudonymizationService.tokenize = tokenize;
265
+ PseudonymizationService.detokenize = detokenize;
266
+ PseudonymizationService.exists = exists;
267
+ PseudonymizationService.removeToken = removeToken;
268
+ PseudonymizationService.removeOriginal = removeOriginal;
269
+ PseudonymizationService.applyHooks = applyHooks;