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,303 @@
1
+ "use strict";
2
+ /**
3
+ * DataMasker
4
+ *
5
+ * Masks sensitive fields in query output. Supports built-in masking patterns
6
+ * (PAN, SSN, email, phone) and custom patterns. Works with the permission
7
+ * system to provide role-aware masking.
8
+ *
9
+ * Covers ~14 compliance frameworks including:
10
+ * PCI DSS (cardholder data), GLBA (financial), SOX, HIPAA,
11
+ * NYDFS 23 NYCRR 500, GDPR Art 25/32, NIST SP 800-122, and more.
12
+ *
13
+ * Usage:
14
+ * // Register a masking rule:
15
+ * DataMasker.maskField(User, 'ssn', { pattern: 'ssn' });
16
+ * DataMasker.maskField(Payment, 'cardNumber', { pattern: 'card-last4', showLast: 4 });
17
+ * DataMasker.maskField(Customer, 'email', { pattern: 'email' });
18
+ *
19
+ * // Or use the decorator:
20
+ * @Mask('ssn', { pattern: 'ssn' })
21
+ * class User extends Model { ... }
22
+ *
23
+ * // At query time, mask the output:
24
+ * const user = await User.findByPk(1);
25
+ * const masked = DataMasker.mask(user, 'admin'); // respects role
26
+ *
27
+ * // Or use the automatic hook:
28
+ * DataMasker.applyHooks(User);
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.DataMasker = exports.MaskPatternEnum = void 0;
32
+ exports.maskField = maskField;
33
+ exports.getMaskRules = getMaskRules;
34
+ exports.Mask = Mask;
35
+ exports.maskValue = maskValue;
36
+ exports.mask = mask;
37
+ exports.maskMany = maskMany;
38
+ exports.applyHooks = applyHooks;
39
+ const permissions_1 = require("../decorators/permissions");
40
+ /**
41
+ * Companion enum for {@link MaskPattern}. Provided for callers who prefer
42
+ * enum-based ergonomics (autocomplete, refactor-safety) over raw string
43
+ * literals. The underlying values are identical to the `MaskPattern`
44
+ * string literals, so either form may be used interchangeably.
45
+ */
46
+ var MaskPatternEnum;
47
+ (function (MaskPatternEnum) {
48
+ MaskPatternEnum["SSN"] = "ssn";
49
+ MaskPatternEnum["CardLast4"] = "card-last4";
50
+ MaskPatternEnum["CardFirst6"] = "card-first6";
51
+ MaskPatternEnum["Email"] = "email";
52
+ MaskPatternEnum["Phone"] = "phone";
53
+ MaskPatternEnum["Full"] = "full";
54
+ MaskPatternEnum["None"] = "none";
55
+ MaskPatternEnum["Custom"] = "custom";
56
+ })(MaskPatternEnum || (exports.MaskPatternEnum = MaskPatternEnum = {}));
57
+ const maskedFields = new Map();
58
+ // ==================== Built-in Maskers ====================
59
+ /** Masks a 9-digit SSN as `***-**-1234`; falls back to a length-preserving mask (last 4 chars shown) for non-standard formats. */
60
+ function maskSSN(value, opts) {
61
+ if (!value)
62
+ return '';
63
+ const digits = value.replace(/\D/g, '');
64
+ if (digits.length === 9) {
65
+ return `***-**-${digits.slice(-4)}`;
66
+ }
67
+ // Fallback for non-standard format
68
+ const char = opts.maskChar ?? '*';
69
+ return char.repeat(value.length - 4) + value.slice(-4);
70
+ }
71
+ /**
72
+ * Masks a card number, revealing only the last N digits (PCI DSS truncation
73
+ * pattern). Defaults to 4 digits; pass `opts.showLast` to reveal a different count.
74
+ */
75
+ function maskCardLast4(value, opts) {
76
+ if (!value)
77
+ return '';
78
+ const clean = value.replace(/\s/g, '');
79
+ const showLast = opts.showLast ?? 4;
80
+ const maskChar = opts.maskChar ?? '*';
81
+ if (clean.length >= showLast) {
82
+ return maskChar.repeat(clean.length - showLast) + clean.slice(clean.length - showLast);
83
+ }
84
+ return maskChar.repeat(value.length);
85
+ }
86
+ /**
87
+ * Masks a card number, revealing only the first N digits (the BIN/IIN).
88
+ * Defaults to 6 digits; pass `opts.showFirst` to reveal a different count.
89
+ */
90
+ function maskCardFirst6(value, opts) {
91
+ if (!value)
92
+ return '';
93
+ const clean = value.replace(/\s/g, '');
94
+ const showFirst = opts.showFirst ?? 6;
95
+ const maskChar = opts.maskChar ?? '*';
96
+ if (clean.length >= showFirst) {
97
+ return clean.slice(0, showFirst) + maskChar.repeat(clean.length - showFirst);
98
+ }
99
+ return maskChar.repeat(value.length);
100
+ }
101
+ /** Masks the local part of an email address, leaving the domain visible. */
102
+ function maskEmail(value) {
103
+ if (!value || !value.includes('@'))
104
+ return value;
105
+ const [local, domain] = value.split('@');
106
+ // Always use a fixed-width mask ('***') so the masked output never leaks
107
+ // the original local-part length.
108
+ const maskedLocal = local.length > 2 ? `${local[0]}***${local.slice(-1)}` : '***';
109
+ return `${maskedLocal}@${domain}`;
110
+ }
111
+ /** Masks a phone number, showing only the last 4 digits; recognizes 10-digit and 11-digit (+1) US formats. */
112
+ function maskPhone(value) {
113
+ if (!value)
114
+ return '';
115
+ const digits = value.replace(/\D/g, '');
116
+ if (digits.length === 10) {
117
+ return `(***) ***-${digits.slice(-4)}`;
118
+ }
119
+ if (digits.length === 11 && digits[0] === '1') {
120
+ return `+1 (***) ***-${digits.slice(-4)}`;
121
+ }
122
+ return '*'.repeat(value.length - 4) + value.slice(-4);
123
+ }
124
+ /** Fully masks a value, replacing every character with the mask char. */
125
+ function maskFull(value, opts) {
126
+ if (!value)
127
+ return '';
128
+ return (opts.maskChar ?? '*').repeat(value.length);
129
+ }
130
+ /**
131
+ * Dispatches to the appropriate masker for `opts.pattern`. Non-string
132
+ * values are stringified before masking; `null`/`undefined` short-circuit
133
+ * to `''` and unknown/`none` patterns pass the value through unchanged.
134
+ */
135
+ function applyMask(value, opts) {
136
+ if (value == null)
137
+ return '';
138
+ if (typeof value !== 'string')
139
+ value = String(value);
140
+ const pattern = opts.pattern ?? 'none';
141
+ const maskChar = opts.maskChar ?? '*';
142
+ switch (pattern) {
143
+ case 'ssn':
144
+ return maskSSN(value, { ...opts, maskChar });
145
+ case 'card-last4':
146
+ return maskCardLast4(value, { ...opts, maskChar });
147
+ case 'card-first6':
148
+ return maskCardFirst6(value, { ...opts, maskChar });
149
+ case 'email':
150
+ return maskEmail(value);
151
+ case 'phone':
152
+ return maskPhone(value);
153
+ case 'full':
154
+ return maskFull(value, { ...opts, maskChar });
155
+ case 'none':
156
+ return value;
157
+ case 'custom':
158
+ return opts.customMasker ? opts.customMasker(value) : value;
159
+ default:
160
+ return value;
161
+ }
162
+ }
163
+ // ==================== Registry ====================
164
+ /**
165
+ * Register a masking rule for a field on a model.
166
+ */
167
+ function maskField(Model, field, options = {}) {
168
+ if (!maskedFields.has(Model)) {
169
+ maskedFields.set(Model, []);
170
+ }
171
+ const rules = maskedFields.get(Model);
172
+ const existing = rules.findIndex((r) => r.field === field);
173
+ const rule = {
174
+ field,
175
+ options: {
176
+ pattern: options.pattern ?? 'none',
177
+ customMasker: options.customMasker,
178
+ showLast: options.showLast ?? 4,
179
+ showFirst: options.showFirst ?? 0,
180
+ maskChar: options.maskChar ?? '*',
181
+ },
182
+ };
183
+ if (existing >= 0) {
184
+ rules[existing] = rule;
185
+ }
186
+ else {
187
+ rules.push(rule);
188
+ }
189
+ }
190
+ /**
191
+ * Get all masking rules for a model.
192
+ */
193
+ function getMaskRules(Model) {
194
+ return maskedFields.get(Model) ?? [];
195
+ }
196
+ // ==================== Decorator ====================
197
+ /**
198
+ * Decorator to mark a field for masking.
199
+ *
200
+ * @Mask('ssn', { pattern: 'ssn' })
201
+ * class User extends Model { ... }
202
+ */
203
+ function Mask(field, options = {}) {
204
+ return function (target) {
205
+ maskField(target, field, options);
206
+ };
207
+ }
208
+ // ==================== Masking Logic ====================
209
+ /**
210
+ * Apply masking to a single value.
211
+ */
212
+ function maskValue(value, options) {
213
+ return applyMask(value, {
214
+ pattern: options.pattern ?? 'none',
215
+ customMasker: options.customMasker,
216
+ showLast: options.showLast ?? 4,
217
+ showFirst: options.showFirst ?? 0,
218
+ maskChar: options.maskChar ?? '*',
219
+ });
220
+ }
221
+ /**
222
+ * Apply masking to a model instance, respecting both permission rules
223
+ * and masking rules.
224
+ *
225
+ * @param instance - The model instance or plain object
226
+ * @param role - The current user's role (for permission filtering)
227
+ * @param Model - The model class (optional, inferred from instance.constructor)
228
+ */
229
+ function mask(instance, role, Model) {
230
+ const modelClass = Model ?? instance.constructor;
231
+ const rules = getMaskRules(modelClass);
232
+ // First filter by permissions
233
+ const permitted = (0, permissions_1.filterByPermissions)(instance, role, modelClass);
234
+ // Then apply masking
235
+ const result = {};
236
+ const data = permitted;
237
+ for (const [key, value] of Object.entries(data)) {
238
+ const rule = rules.find((r) => r.field === key);
239
+ if (rule) {
240
+ result[key] = applyMask(value, rule.options);
241
+ }
242
+ else {
243
+ result[key] = value;
244
+ }
245
+ }
246
+ return result;
247
+ }
248
+ /**
249
+ * Apply masking to an array of instances.
250
+ */
251
+ function maskMany(instances, role, Model) {
252
+ return instances.map((inst) => mask(inst, role, Model));
253
+ }
254
+ // ==================== Hook Wiring ====================
255
+ /**
256
+ * Wire afterFind hooks onto a model to automatically mask results.
257
+ *
258
+ * DataMasker.applyHooks(User);
259
+ */
260
+ function applyHooks(Model) {
261
+ const rules = getMaskRules(Model);
262
+ if (rules.length === 0)
263
+ return;
264
+ const hookName = 'orm:dataMasker';
265
+ if (typeof Model.addHook === 'function') {
266
+ Model.addHook('afterFind', hookName, (result) => {
267
+ const maskOne = (instance) => {
268
+ if (!instance)
269
+ return;
270
+ const data = instance.toJSON ? instance.toJSON() : instance;
271
+ for (const rule of rules) {
272
+ const value = data[rule.field];
273
+ if (value != null) {
274
+ const masked = applyMask(value, rule.options);
275
+ if (instance.setDataValue) {
276
+ instance.setDataValue(rule.field, masked);
277
+ }
278
+ else {
279
+ instance[rule.field] = masked;
280
+ }
281
+ }
282
+ }
283
+ };
284
+ if (Array.isArray(result)) {
285
+ result.forEach(maskOne);
286
+ }
287
+ else {
288
+ maskOne(result);
289
+ }
290
+ });
291
+ }
292
+ }
293
+ // ==================== Class Export ====================
294
+ /** Static facade bundling the masking API (mirrors the module-level functions). */
295
+ class DataMasker {
296
+ }
297
+ exports.DataMasker = DataMasker;
298
+ DataMasker.maskField = maskField;
299
+ DataMasker.getRules = getMaskRules;
300
+ DataMasker.mask = mask;
301
+ DataMasker.maskMany = maskMany;
302
+ DataMasker.maskValue = maskValue;
303
+ DataMasker.applyHooks = applyHooks;
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+ /**
3
+ * DataPortabilityExporter
4
+ *
5
+ * Exports data in machine-readable formats (JSON, CSV, NDJSON) for
6
+ * GDPR Art 20 / CCPA / CPRA data subject access requests (DSARs).
7
+ * Respects field-level permissions — strips restricted fields from export.
8
+ *
9
+ * Covers ~25 compliance frameworks including:
10
+ * GDPR Art 20, CCPA, CPRA, LGPD, APPI, PDPA (Singapore, Philippines),
11
+ * PoPI (South Africa), PIPEDA, and other data portability rights.
12
+ *
13
+ * Usage:
14
+ * // Export all data for a user (JSON):
15
+ * const export = await DataPortabilityExporter.export(User, {
16
+ * where: { id: userId },
17
+ * format: 'json',
18
+ * });
19
+ *
20
+ * // Export in CSV format:
21
+ * const csv = await DataPortabilityExporter.export(User, {
22
+ * where: { id: userId },
23
+ * format: 'csv',
24
+ * include: [Order, Address], // also export associated models
25
+ * });
26
+ *
27
+ * // Export as NDJSON (newline-delimited JSON, better for large datasets):
28
+ * const ndjson = await DataPortabilityExporter.export(User, {
29
+ * where: { id: userId },
30
+ * format: 'ndjson',
31
+ * });
32
+ *
33
+ * // Stream export (for very large datasets):
34
+ * await DataPortabilityExporter.exportStream(User, {
35
+ * where: { userId },
36
+ * format: 'ndjson',
37
+ * onChunk: (chunk) => stream.write(chunk),
38
+ * });
39
+ */
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.DataPortabilityExporter = void 0;
42
+ exports.exportData = exportData;
43
+ exports.exportStream = exportStream;
44
+ exports.createDSARResponse = createDSARResponse;
45
+ const permissions_1 = require("../decorators/permissions");
46
+ /**
47
+ * Get the list of attribute names defined on a model.
48
+ * Used as the default attribute universe for permission filtering when
49
+ * no explicit per-field rules have been registered for the model.
50
+ */
51
+ function getModelAttributeNames(Model) {
52
+ if (Model?.rawAttributes) {
53
+ return Object.keys(Model.rawAttributes);
54
+ }
55
+ if (typeof Model?.getAttributes === 'function') {
56
+ return Object.keys(Model.getAttributes());
57
+ }
58
+ return [];
59
+ }
60
+ /**
61
+ * Convert a plain object to CSV row.
62
+ */
63
+ function toCSVRow(obj, headers) {
64
+ return headers
65
+ .map((h) => {
66
+ const value = obj[h];
67
+ if (value == null)
68
+ return '';
69
+ const str = String(value);
70
+ // Escape quotes and wrap in quotes if contains comma/quote/newline
71
+ if (str.includes(',') || str.includes('"') || str.includes('\n')) {
72
+ return `"${str.replace(/"/g, '""')}"`;
73
+ }
74
+ return str;
75
+ })
76
+ .join(',');
77
+ }
78
+ /**
79
+ * Export associated data recursively.
80
+ */
81
+ async function exportAssociations(instance, include, role) {
82
+ const result = {};
83
+ for (const assoc of include) {
84
+ const model = assoc.model ?? assoc;
85
+ const as = assoc.as ?? model.name?.toLowerCase() ?? 'association';
86
+ // Get the associated records
87
+ const associated = await instance[as]?.();
88
+ if (!associated)
89
+ continue;
90
+ if (Array.isArray(associated)) {
91
+ // Filter by permissions
92
+ const filtered = await Promise.all(associated.map(async (item) => {
93
+ const data = item.toJSON ? item.toJSON() : item;
94
+ const allowed = (0, permissions_1.getAllowedAttributes)(model, role);
95
+ const filtered = {};
96
+ for (const key of allowed) {
97
+ filtered[key] = data[key];
98
+ }
99
+ return filtered;
100
+ }));
101
+ result[as] = filtered;
102
+ }
103
+ else {
104
+ const data = associated.toJSON ? associated.toJSON() : associated;
105
+ const allowed = (0, permissions_1.getAllowedAttributes)(model, role);
106
+ const filtered = {};
107
+ for (const key of allowed) {
108
+ filtered[key] = data[key];
109
+ }
110
+ result[as] = filtered;
111
+ }
112
+ }
113
+ return result;
114
+ }
115
+ /**
116
+ * Main export function.
117
+ */
118
+ async function exportData(Model, options) {
119
+ const { where = {}, include = [], format = 'json', excludeForRole, limit } = options;
120
+ const role = excludeForRole ?? 'export';
121
+ const allowedAttrs = (0, permissions_1.getAllowedAttributes)(Model, role, getModelAttributeNames(Model));
122
+ // Fetch the main records
123
+ const records = await Model.findAll({
124
+ where,
125
+ include,
126
+ limit,
127
+ order: options.order,
128
+ });
129
+ if (records.length === 0) {
130
+ return {
131
+ format,
132
+ content: format === 'csv' ? '' : '[]',
133
+ recordCount: 0,
134
+ exportedAt: new Date(),
135
+ includedModels: [Model.name, ...include.map((m) => m.model?.name ?? m.name ?? 'assoc')],
136
+ };
137
+ }
138
+ // Process each record
139
+ const processed = [];
140
+ for (const record of records) {
141
+ const base = record.toJSON ? record.toJSON() : record;
142
+ const filtered = {};
143
+ // Only include allowed attributes
144
+ for (const key of allowedAttrs) {
145
+ if (base[key] !== undefined) {
146
+ filtered[key] = base[key];
147
+ }
148
+ }
149
+ // Include associations if requested
150
+ if (include.length > 0) {
151
+ const assocData = await exportAssociations(record, include, role);
152
+ Object.assign(filtered, assocData);
153
+ }
154
+ processed.push(filtered);
155
+ }
156
+ // Format the output
157
+ let content;
158
+ if (format === 'json') {
159
+ content = JSON.stringify(processed, null, 2);
160
+ }
161
+ else if (format === 'csv') {
162
+ const headers = Object.keys(processed[0]);
163
+ const headerRow = headers.join(',');
164
+ const dataRows = processed.map((row) => toCSVRow(row, headers));
165
+ content = [headerRow, ...dataRows].join('\n');
166
+ }
167
+ else if (format === 'ndjson') {
168
+ // NDJSON convention: every record (including the last) is terminated by '\n'.
169
+ content = processed.map((row) => `${JSON.stringify(row)}\n`).join('');
170
+ }
171
+ else {
172
+ throw new Error(`[DataPortabilityExporter] Unsupported format: ${format}`);
173
+ }
174
+ return {
175
+ format,
176
+ content,
177
+ recordCount: processed.length,
178
+ exportedAt: new Date(),
179
+ includedModels: [Model.name, ...include.map((m) => m.model?.name ?? m.name ?? 'assoc')],
180
+ };
181
+ }
182
+ /**
183
+ * Stream export for large datasets.
184
+ * Writes directly to a callback to avoid loading everything in memory.
185
+ */
186
+ async function exportStream(Model, options) {
187
+ const { where = {}, include = [], format = 'ndjson', onChunk, batchSize = 100 } = options;
188
+ const role = options.excludeForRole ?? 'export';
189
+ const allowedAttrs = (0, permissions_1.getAllowedAttributes)(Model, role, getModelAttributeNames(Model));
190
+ let offset = 0;
191
+ let recordCount = 0;
192
+ let batch = [];
193
+ // eslint-disable-next-line no-constant-condition
194
+ while (true) {
195
+ const records = await Model.findAll({
196
+ where,
197
+ include,
198
+ limit: batchSize,
199
+ offset,
200
+ order: options.order,
201
+ });
202
+ if (records.length === 0)
203
+ break;
204
+ for (const record of records) {
205
+ const base = record.toJSON ? record.toJSON() : record;
206
+ const filtered = {};
207
+ for (const key of allowedAttrs) {
208
+ if (base[key] !== undefined) {
209
+ filtered[key] = base[key];
210
+ }
211
+ }
212
+ if (include.length > 0) {
213
+ const assocData = await exportAssociations(record, include, role);
214
+ Object.assign(filtered, assocData);
215
+ }
216
+ if (format === 'csv') {
217
+ // CSV streaming is complex — fall back to NDJSON for streaming
218
+ batch.push(filtered);
219
+ }
220
+ else {
221
+ // NDJSON
222
+ await onChunk(JSON.stringify(filtered) + '\n');
223
+ }
224
+ recordCount++;
225
+ }
226
+ offset += batchSize;
227
+ // For CSV, we need to collect headers first
228
+ if (format === 'csv' && batch.length > 0) {
229
+ const headers = Object.keys(batch[0]);
230
+ const headerRow = headers.join(',');
231
+ await onChunk(headerRow + '\n');
232
+ for (const row of batch) {
233
+ await onChunk(toCSVRow(row, headers) + '\n');
234
+ }
235
+ batch = [];
236
+ }
237
+ }
238
+ return { recordCount, exportedAt: new Date() };
239
+ }
240
+ /**
241
+ * Generate a DSAR response object ready for API return.
242
+ */
243
+ async function createDSARResponse(subjectId, models, options = {}) {
244
+ const exports = [];
245
+ for (const { model, where } of models) {
246
+ const exp = await exportData(model, {
247
+ where: { ...where, id: subjectId },
248
+ format: options.format ?? 'json',
249
+ include: options.include,
250
+ });
251
+ exports.push(exp);
252
+ }
253
+ return {
254
+ subjectId,
255
+ requestedAt: new Date(),
256
+ exports,
257
+ };
258
+ }
259
+ // ==================== Class Export ====================
260
+ class DataPortabilityExporter {
261
+ }
262
+ exports.DataPortabilityExporter = DataPortabilityExporter;
263
+ DataPortabilityExporter.export = exportData;
264
+ DataPortabilityExporter.exportStream = exportStream;
265
+ DataPortabilityExporter.createDSARResponse = createDSARResponse;