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.
- package/CHANGELOG.md +1111 -0
- package/LICENSE +21 -0
- package/README.md +573 -0
- package/dist/audit/history-query.js +180 -0
- package/dist/audit/index.js +23 -0
- package/dist/audit/logger.js +236 -0
- package/dist/cache/cache-manager.js +84 -0
- package/dist/cache/index.js +16 -0
- package/dist/cache/redis-cluster-cache.js +557 -0
- package/dist/cli.js +2200 -0
- package/dist/compliance/audit-trail.js +68 -0
- package/dist/compliance/backup-verification.js +685 -0
- package/dist/compliance/breach-detector.js +505 -0
- package/dist/compliance/consent-record.js +227 -0
- package/dist/compliance/consent-versioning.js +331 -0
- package/dist/compliance/cross-border-log.js +530 -0
- package/dist/compliance/data-classifier.js +258 -0
- package/dist/compliance/data-lineage.js +565 -0
- package/dist/compliance/data-masker.js +303 -0
- package/dist/compliance/data-portability.js +265 -0
- package/dist/compliance/data-retention.js +195 -0
- package/dist/compliance/dsar-workflow.js +342 -0
- package/dist/compliance/field-encryption.js +223 -0
- package/dist/compliance/immutable-record.js +148 -0
- package/dist/compliance/index.js +260 -0
- package/dist/compliance/privacy-impact-assessment.js +364 -0
- package/dist/compliance/pseudonymization.js +269 -0
- package/dist/compliance/query-firewall.js +1182 -0
- package/dist/compliance/rate-limiter.js +580 -0
- package/dist/compliance/right-to-erasure.js +117 -0
- package/dist/compliance/row-level-security.js +246 -0
- package/dist/compliance/security-decorator.js +574 -0
- package/dist/compliance/security-monitor.js +525 -0
- package/dist/compliance/sensitive-data-discovery.js +476 -0
- package/dist/compliance/session-isolation.js +472 -0
- package/dist/compliance/tls-enforcer.js +108 -0
- package/dist/compliance/worm-storage.js +712 -0
- package/dist/connection-manager.js +198 -0
- package/dist/connection-pool.js +519 -0
- package/dist/decorators/audit.js +136 -0
- package/dist/decorators/belongs-to-many.js +115 -0
- package/dist/decorators/belongs-to.js +115 -0
- package/dist/decorators/check.js +435 -0
- package/dist/decorators/collate.js +329 -0
- package/dist/decorators/comment.js +205 -0
- package/dist/decorators/database-settings.js +236 -0
- package/dist/decorators/default.js +244 -0
- package/dist/decorators/encryption.js +235 -0
- package/dist/decorators/engine.js +97 -0
- package/dist/decorators/fk-constraints.js +594 -0
- package/dist/decorators/foreign-table.js +136 -0
- package/dist/decorators/generated.js +274 -0
- package/dist/decorators/has-many.js +127 -0
- package/dist/decorators/has-one.js +116 -0
- package/dist/decorators/hstore.js +129 -0
- package/dist/decorators/index.js +355 -0
- package/dist/decorators/json-column.js +83 -0
- package/dist/decorators/jsonb.js +101 -0
- package/dist/decorators/orm-decorators.js +425 -0
- package/dist/decorators/permissions.js +294 -0
- package/dist/decorators/procedure.js +210 -0
- package/dist/decorators/query-options.js +556 -0
- package/dist/decorators/range.js +167 -0
- package/dist/decorators/set-column.js +83 -0
- package/dist/decorators/spatial.js +114 -0
- package/dist/decorators/storage.js +580 -0
- package/dist/decorators/timezone.js +512 -0
- package/dist/decorators/trigger.js +90 -0
- package/dist/decorators/uuid.js +135 -0
- package/dist/decorators/view.js +258 -0
- package/dist/diagrams/chen-diagram.js +354 -0
- package/dist/diagrams/class-diagram.js +384 -0
- package/dist/diagrams/dependency-diagram.js +432 -0
- package/dist/diagrams/er-diagram.js +605 -0
- package/dist/diagrams/flow-diagram.js +394 -0
- package/dist/diagrams/gantt-diagram.js +411 -0
- package/dist/diagrams/index-diagram.js +353 -0
- package/dist/diagrams/index.js +184 -0
- package/dist/diagrams/migration-diagram.js +316 -0
- package/dist/diagrams/model-diagram.js +616 -0
- package/dist/diagrams/package-diagram.js +376 -0
- package/dist/diagrams/palette.js +85 -0
- package/dist/diagrams/relational-diagram.js +455 -0
- package/dist/diagrams/schemadoc-diagram.js +309 -0
- package/dist/diagrams/sequence-diagram.js +307 -0
- package/dist/diagrams/state-diagram.js +344 -0
- package/dist/diagrams/svg-dom.js +111 -0
- package/dist/diagrams/tree-diagram.js +250 -0
- package/dist/dialects/clickhouse/index.js +1742 -0
- package/dist/dialects/cockroachdb/index.js +4677 -0
- package/dist/dialects/cratedb/index.js +441 -0
- package/dist/dialects/databricks/index.js +517 -0
- package/dist/dialects/db2/index.js +2225 -0
- package/dist/dialects/dialect.js +720 -0
- package/dist/dialects/duckdb/index.js +2014 -0
- package/dist/dialects/exasol/index.js +357 -0
- package/dist/dialects/firebird/index.js +495 -0
- package/dist/dialects/greenplum/index.js +457 -0
- package/dist/dialects/hana/index.js +1741 -0
- package/dist/dialects/mariadb/index.js +3590 -0
- package/dist/dialects/mssql/index.js +2296 -0
- package/dist/dialects/mysql/index.js +4058 -0
- package/dist/dialects/oracle/index.js +2999 -0
- package/dist/dialects/postgres/index.js +5319 -0
- package/dist/dialects/query-stream-helper.js +152 -0
- package/dist/dialects/questdb/index.js +433 -0
- package/dist/dialects/redshift/index.js +2277 -0
- package/dist/dialects/singlestore/index.js +354 -0
- package/dist/dialects/snowflake/index.js +2082 -0
- package/dist/dialects/spanner/index.js +1768 -0
- package/dist/dialects/sqlite/index.js +3382 -0
- package/dist/dialects/tidb/index.js +377 -0
- package/dist/dialects/timescaledb/index.js +164 -0
- package/dist/dialects/trino/index.js +420 -0
- package/dist/dialects/turso/index.js +227 -0
- package/dist/dialects/vertica/index.js +317 -0
- package/dist/dialects/yugabytedb/index.js +424 -0
- package/dist/errors/index.js +472 -0
- package/dist/errors/utils.js +340 -0
- package/dist/errors.js +21 -0
- package/dist/extensions/catalog/cloud-warehouse-features.js +207 -0
- package/dist/extensions/catalog/mssql-features.js +147 -0
- package/dist/extensions/catalog/mysql-mariadb-plugins.js +229 -0
- package/dist/extensions/catalog/oracle-db2-features.js +196 -0
- package/dist/extensions/catalog/postgres-extensions.js +516 -0
- package/dist/extensions/index.js +71 -0
- package/dist/extensions/types.js +13 -0
- package/dist/foreign-data.js +173 -0
- package/dist/hooks/hooks-manager.js +350 -0
- package/dist/hooks/index.js +37 -0
- package/dist/index.js +463 -0
- package/dist/logging.js +335 -0
- package/dist/migrations/index.js +40 -0
- package/dist/migrations/migration.js +196 -0
- package/dist/migrations/migrator.js +411 -0
- package/dist/migrations/prormmigration.js +275 -0
- package/dist/migrations/query-interface.js +435 -0
- package/dist/migrations/seeder.js +353 -0
- package/dist/models/associations.js +852 -0
- package/dist/models/constraints.js +288 -0
- package/dist/models/data-types.js +2518 -0
- package/dist/models/decorators.js +445 -0
- package/dist/models/index.js +33 -0
- package/dist/models/indexes.js +531 -0
- package/dist/models/methods.js +382 -0
- package/dist/models/model-manager.js +103 -0
- package/dist/models/model.js +5349 -0
- package/dist/models/operators.js +67 -0
- package/dist/models/scopes.js +189 -0
- package/dist/models/typescript-types.js +26 -0
- package/dist/nosql/aerospike/index.js +205 -0
- package/dist/nosql/allegrograph/index.js +169 -0
- package/dist/nosql/arangodb/index.js +364 -0
- package/dist/nosql/azure-blob/index.js +206 -0
- package/dist/nosql/beanstalkd/index.js +231 -0
- package/dist/nosql/beequeue/index.js +210 -0
- package/dist/nosql/bigchaindb/index.js +195 -0
- package/dist/nosql/bigtable/index.js +224 -0
- package/dist/nosql/blazegraph/index.js +173 -0
- package/dist/nosql/bullmq/index.js +191 -0
- package/dist/nosql/cassandra/index.js +174 -0
- package/dist/nosql/chroma/index.js +190 -0
- package/dist/nosql/cloudflare-kv/index.js +220 -0
- package/dist/nosql/coherence/index.js +200 -0
- package/dist/nosql/cosmosdb/index.js +157 -0
- package/dist/nosql/couchbase/index.js +213 -0
- package/dist/nosql/dax/index.js +212 -0
- package/dist/nosql/deno-kv/index.js +206 -0
- package/dist/nosql/dgraph/index.js +171 -0
- package/dist/nosql/doris/index.js +169 -0
- package/dist/nosql/druid/index.js +162 -0
- package/dist/nosql/dynamodb/index.js +937 -0
- package/dist/nosql/elasticsearch/index.js +377 -0
- package/dist/nosql/etcd/index.js +502 -0
- package/dist/nosql/eventhubs/index.js +213 -0
- package/dist/nosql/eventstore/index.js +254 -0
- package/dist/nosql/faunadb/index.js +188 -0
- package/dist/nosql/firestore/index.js +177 -0
- package/dist/nosql/fluree/index.js +148 -0
- package/dist/nosql/fuseki/index.js +170 -0
- package/dist/nosql/gcs/index.js +172 -0
- package/dist/nosql/gearman/index.js +160 -0
- package/dist/nosql/geode/index.js +196 -0
- package/dist/nosql/graphdb/index.js +169 -0
- package/dist/nosql/graylog/index.js +188 -0
- package/dist/nosql/gridgain/index.js +171 -0
- package/dist/nosql/hazelcast/index.js +162 -0
- package/dist/nosql/hbase/index.js +230 -0
- package/dist/nosql/ignite/index.js +173 -0
- package/dist/nosql/immudb/index.js +184 -0
- package/dist/nosql/index.js +232 -0
- package/dist/nosql/infinispan/index.js +200 -0
- package/dist/nosql/influxdb/index.js +0 -0
- package/dist/nosql/kafka/index.js +234 -0
- package/dist/nosql/keyspaces/index.js +189 -0
- package/dist/nosql/kinesis/index.js +253 -0
- package/dist/nosql/leveldb/index.js +153 -0
- package/dist/nosql/lmdb/index.js +160 -0
- package/dist/nosql/loki/index.js +201 -0
- package/dist/nosql/marklogic/index.js +204 -0
- package/dist/nosql/materialize/index.js +145 -0
- package/dist/nosql/meilisearch/index.js +154 -0
- package/dist/nosql/memcached/index.js +223 -0
- package/dist/nosql/milvus/index.js +410 -0
- package/dist/nosql/minio/index.js +265 -0
- package/dist/nosql/momento/index.js +179 -0
- package/dist/nosql/mongodb/index.js +461 -0
- package/dist/nosql/nats/index.js +247 -0
- package/dist/nosql/nedb/index.js +164 -0
- package/dist/nosql/neo4j/index.js +450 -0
- package/dist/nosql/neptune/index.js +470 -0
- package/dist/nosql/nsq/index.js +200 -0
- package/dist/nosql/opensearch/index.js +186 -0
- package/dist/nosql/orientdb/index.js +175 -0
- package/dist/nosql/papertrail/index.js +200 -0
- package/dist/nosql/pinecone/index.js +0 -0
- package/dist/nosql/pinot/index.js +133 -0
- package/dist/nosql/pouchdb/index.js +172 -0
- package/dist/nosql/prometheus/index.js +174 -0
- package/dist/nosql/provendb/index.js +147 -0
- package/dist/nosql/pubsub/index.js +187 -0
- package/dist/nosql/pulsar/index.js +232 -0
- package/dist/nosql/qdrant/index.js +295 -0
- package/dist/nosql/qldb/index.js +192 -0
- package/dist/nosql/r2/index.js +281 -0
- package/dist/nosql/rabbitmq/index.js +237 -0
- package/dist/nosql/ravendb/index.js +175 -0
- package/dist/nosql/redis/index.js +607 -0
- package/dist/nosql/redpanda/index.js +237 -0
- package/dist/nosql/resque/index.js +203 -0
- package/dist/nosql/rethinkdb/index.js +232 -0
- package/dist/nosql/rocksdb/index.js +152 -0
- package/dist/nosql/rockset/index.js +126 -0
- package/dist/nosql/s3/index.js +298 -0
- package/dist/nosql/scylladb/index.js +178 -0
- package/dist/nosql/signoz/index.js +227 -0
- package/dist/nosql/sns/index.js +201 -0
- package/dist/nosql/solr/index.js +231 -0
- package/dist/nosql/splunk/index.js +227 -0
- package/dist/nosql/sqs/index.js +246 -0
- package/dist/nosql/stardog/index.js +169 -0
- package/dist/nosql/starrocks/index.js +170 -0
- package/dist/nosql/store.js +2 -0
- package/dist/nosql/sumologic/index.js +213 -0
- package/dist/nosql/surrealdb/index.js +179 -0
- package/dist/nosql/terminusdb/index.js +151 -0
- package/dist/nosql/tigergraph/index.js +469 -0
- package/dist/nosql/typesense/index.js +148 -0
- package/dist/nosql/unqlite/index.js +157 -0
- package/dist/nosql/upstash/index.js +167 -0
- package/dist/nosql/vercel-kv/index.js +166 -0
- package/dist/nosql/victoriametrics/index.js +213 -0
- package/dist/nosql/virtuoso/index.js +169 -0
- package/dist/nosql/weaviate/index.js +276 -0
- package/dist/operators/index.js +108 -0
- package/dist/operators.js +2690 -0
- package/dist/prisma-migrate/index.js +38 -0
- package/dist/prisma-migrate/migration-generator.js +125 -0
- package/dist/prisma-migrate/model-generator.js +172 -0
- package/dist/prisma-migrate/relations.js +100 -0
- package/dist/prisma-migrate/schema-parser.js +167 -0
- package/dist/prisma-migrate/type-mapper.js +40 -0
- package/dist/prorm.js +6832 -0
- package/dist/query-builders/cte-builder.js +80 -0
- package/dist/query-builders/functions/aggregate.js +390 -0
- package/dist/query-builders/functions/conditional.js +503 -0
- package/dist/query-builders/functions/datetime.js +695 -0
- package/dist/query-builders/functions/fulltext.js +439 -0
- package/dist/query-builders/functions/index.js +93 -0
- package/dist/query-builders/functions/json.js +427 -0
- package/dist/query-builders/functions/math.js +399 -0
- package/dist/query-builders/functions/string.js +518 -0
- package/dist/query-builders/functions/window.js +328 -0
- package/dist/query-builders/include-builder.js +323 -0
- package/dist/query-builders/index-expression-builder.js +242 -0
- package/dist/query-builders/index.js +161 -0
- package/dist/query-builders/insert-builder.js +164 -0
- package/dist/query-builders/model-helpers.js +38 -0
- package/dist/query-builders/order-limit-builder.js +239 -0
- package/dist/query-builders/sql-compiler.js +2040 -0
- package/dist/query-builders/subquery-builder.js +152 -0
- package/dist/query-builders/update-builder.js +182 -0
- package/dist/query-builders/view-builder.js +218 -0
- package/dist/query-builders/where-builder.js +1532 -0
- package/dist/query-interface.js +622 -0
- package/dist/query-optimizers/batch-optimizer.js +426 -0
- package/dist/query-optimizers/explain-plans.js +415 -0
- package/dist/query-optimizers/index.js +51 -0
- package/dist/query-optimizers/prepared-statement-cache.js +423 -0
- package/dist/query-optimizers/query-hints.js +438 -0
- package/dist/query-optimizers/query-optimizer.js +278 -0
- package/dist/query-optimizers/slow-query-logger.js +271 -0
- package/dist/replica-manager.js +507 -0
- package/dist/schema/index.js +15 -0
- package/dist/schema/migration-generator.js +374 -0
- package/dist/schema/schema-differ.js +549 -0
- package/dist/schema/types.js +6 -0
- package/dist/sql-constants.js +300 -0
- package/dist/sqlite-advanced.js +1047 -0
- package/dist/streams/index.js +12 -0
- package/dist/streams/transforms.js +178 -0
- package/dist/transaction.js +456 -0
- package/dist/types/index.js +180 -0
- package/dist/user-management.js +127 -0
- package/dist/utils/date.js +300 -0
- package/dist/utils/index.js +1079 -0
- package/dist/utils/string.js +161 -0
- package/dist/validators/index.js +19 -0
- package/dist/validators/validator.js +911 -0
- package/package.json +190 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ConsentRecord
|
|
4
|
+
*
|
|
5
|
+
* A ready-made model and service for storing, managing, and querying
|
|
6
|
+
* data subject consent — including purpose, legal basis, version, and withdrawal.
|
|
7
|
+
*
|
|
8
|
+
* Covers ~62 compliance frameworks including:
|
|
9
|
+
* GDPR Art 6/7, CCPA, CPRA, LGPD, APPI, ePrivacy Directive, CASL,
|
|
10
|
+
* PDPA (Thailand, Singapore, Philippines, Malaysia, Indonesia, Vietnam),
|
|
11
|
+
* DPDP India, PIPA South Korea, PIPL, KVKK, PoPI, and more.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* // Initialize once (creates the consent_records table):
|
|
15
|
+
* await ConsentRecord.initialize(prorm);
|
|
16
|
+
*
|
|
17
|
+
* // Record consent:
|
|
18
|
+
* await ConsentRecord.grant({
|
|
19
|
+
* subjectId: 'user-123',
|
|
20
|
+
* purpose: 'marketing_emails',
|
|
21
|
+
* legalBasis: 'consent',
|
|
22
|
+
* version: '2.1',
|
|
23
|
+
* ipAddress: req.ip,
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Check active consent:
|
|
27
|
+
* const active = await ConsentRecord.isActive('user-123', 'marketing_emails');
|
|
28
|
+
*
|
|
29
|
+
* // Withdraw consent (GDPR Art 7(3)):
|
|
30
|
+
* await ConsentRecord.withdraw('user-123', 'marketing_emails');
|
|
31
|
+
*
|
|
32
|
+
* // Get full consent history for a subject:
|
|
33
|
+
* const history = await ConsentRecord.getHistory('user-123');
|
|
34
|
+
*/
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ConsentRecord = exports.LegalBasisEnum = void 0;
|
|
37
|
+
const data_types_1 = require("../models/data-types");
|
|
38
|
+
/**
|
|
39
|
+
* Companion enum for {@link LegalBasis}. Provided for callers who prefer
|
|
40
|
+
* enum-based ergonomics (autocomplete, refactor-safety) over raw string
|
|
41
|
+
* literals. The underlying values are identical to the `LegalBasis`
|
|
42
|
+
* string literals, so either form may be used interchangeably.
|
|
43
|
+
*/
|
|
44
|
+
var LegalBasisEnum;
|
|
45
|
+
(function (LegalBasisEnum) {
|
|
46
|
+
LegalBasisEnum["Consent"] = "consent";
|
|
47
|
+
LegalBasisEnum["Contract"] = "contract";
|
|
48
|
+
LegalBasisEnum["LegalObligation"] = "legal_obligation";
|
|
49
|
+
LegalBasisEnum["VitalInterests"] = "vital_interests";
|
|
50
|
+
LegalBasisEnum["PublicTask"] = "public_task";
|
|
51
|
+
LegalBasisEnum["LegitimateInterests"] = "legitimate_interests";
|
|
52
|
+
})(LegalBasisEnum || (exports.LegalBasisEnum = LegalBasisEnum = {}));
|
|
53
|
+
let ConsentModel = null;
|
|
54
|
+
class ConsentRecord {
|
|
55
|
+
/**
|
|
56
|
+
* Initialize the ConsentRecord model and sync the table.
|
|
57
|
+
* Call once at application startup.
|
|
58
|
+
*
|
|
59
|
+
* @param prorm - The Prorm instance
|
|
60
|
+
* @param tableName - Override the default table name 'consent_records'
|
|
61
|
+
*/
|
|
62
|
+
static async initialize(prorm, tableName = 'consent_records') {
|
|
63
|
+
ConsentModel = prorm.define('ConsentRecord', {
|
|
64
|
+
id: {
|
|
65
|
+
type: data_types_1.DataTypes.INTEGER(),
|
|
66
|
+
primaryKey: true,
|
|
67
|
+
autoIncrement: true,
|
|
68
|
+
},
|
|
69
|
+
subjectId: {
|
|
70
|
+
type: data_types_1.DataTypes.STRING(255),
|
|
71
|
+
allowNull: false,
|
|
72
|
+
comment: 'Unique identifier of the data subject (user ID, email, etc.)',
|
|
73
|
+
},
|
|
74
|
+
purpose: {
|
|
75
|
+
type: data_types_1.DataTypes.STRING(255),
|
|
76
|
+
allowNull: false,
|
|
77
|
+
comment: 'Processing purpose (e.g. marketing_emails, analytics)',
|
|
78
|
+
},
|
|
79
|
+
legalBasis: {
|
|
80
|
+
type: data_types_1.DataTypes.STRING(64),
|
|
81
|
+
allowNull: true,
|
|
82
|
+
comment: 'GDPR Article 6 legal basis for processing',
|
|
83
|
+
},
|
|
84
|
+
consentedAt: {
|
|
85
|
+
type: data_types_1.DataTypes.DATE(),
|
|
86
|
+
allowNull: true,
|
|
87
|
+
comment: 'Timestamp when consent was granted',
|
|
88
|
+
},
|
|
89
|
+
withdrawnAt: {
|
|
90
|
+
type: data_types_1.DataTypes.DATE(),
|
|
91
|
+
allowNull: true,
|
|
92
|
+
comment: 'Timestamp when consent was withdrawn',
|
|
93
|
+
},
|
|
94
|
+
version: {
|
|
95
|
+
type: data_types_1.DataTypes.STRING(64),
|
|
96
|
+
allowNull: true,
|
|
97
|
+
comment: 'Privacy notice / consent form version',
|
|
98
|
+
},
|
|
99
|
+
ipAddress: {
|
|
100
|
+
type: data_types_1.DataTypes.STRING(45),
|
|
101
|
+
allowNull: true,
|
|
102
|
+
comment: 'IP address of the consenting user',
|
|
103
|
+
},
|
|
104
|
+
metadata: {
|
|
105
|
+
type: data_types_1.DataTypes.JSON(),
|
|
106
|
+
allowNull: true,
|
|
107
|
+
comment: 'Additional structured metadata',
|
|
108
|
+
},
|
|
109
|
+
}, {
|
|
110
|
+
tableName,
|
|
111
|
+
timestamps: true,
|
|
112
|
+
underscored: true,
|
|
113
|
+
indexes: [
|
|
114
|
+
{ fields: ['subject_id', 'purpose'] },
|
|
115
|
+
{ fields: ['subject_id'] },
|
|
116
|
+
],
|
|
117
|
+
});
|
|
118
|
+
await ConsentModel.sync({ alter: false });
|
|
119
|
+
}
|
|
120
|
+
static requireModel() {
|
|
121
|
+
if (!ConsentModel) {
|
|
122
|
+
throw new Error('[ConsentRecord] Not initialized. Call ConsentRecord.initialize(prorm) first.');
|
|
123
|
+
}
|
|
124
|
+
return ConsentModel;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Record a new consent grant.
|
|
128
|
+
* If an active consent already exists for the same subject + purpose,
|
|
129
|
+
* a new record is created (audit trail is preserved).
|
|
130
|
+
*/
|
|
131
|
+
static async grant(options) {
|
|
132
|
+
const model = ConsentRecord.requireModel();
|
|
133
|
+
return model.create({
|
|
134
|
+
subjectId: options.subjectId,
|
|
135
|
+
purpose: options.purpose,
|
|
136
|
+
legalBasis: options.legalBasis ?? 'consent',
|
|
137
|
+
version: options.version ?? null,
|
|
138
|
+
ipAddress: options.ipAddress ?? null,
|
|
139
|
+
metadata: options.metadata ?? null,
|
|
140
|
+
consentedAt: new Date(),
|
|
141
|
+
withdrawnAt: null,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Withdraw consent for a subject + purpose.
|
|
146
|
+
* Sets withdrawnAt on the most recent active consent record.
|
|
147
|
+
*
|
|
148
|
+
* @returns true if a consent record was found and updated, false if none existed.
|
|
149
|
+
*/
|
|
150
|
+
static async withdraw(subjectId, purpose, options = {}) {
|
|
151
|
+
const model = ConsentRecord.requireModel();
|
|
152
|
+
const record = await model.findOne({
|
|
153
|
+
where: {
|
|
154
|
+
subjectId,
|
|
155
|
+
purpose,
|
|
156
|
+
withdrawnAt: { $isNull: true },
|
|
157
|
+
consentedAt: { $isNotNull: true },
|
|
158
|
+
},
|
|
159
|
+
order: [['consentedAt', 'DESC']],
|
|
160
|
+
});
|
|
161
|
+
if (!record)
|
|
162
|
+
return false;
|
|
163
|
+
await record.update({
|
|
164
|
+
withdrawnAt: new Date(),
|
|
165
|
+
metadata: {
|
|
166
|
+
...(record.metadata ?? {}),
|
|
167
|
+
withdrawalReason: options.reason ?? null,
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Check whether an active (non-withdrawn) consent exists.
|
|
174
|
+
*/
|
|
175
|
+
static async isActive(subjectId, purpose) {
|
|
176
|
+
const model = ConsentRecord.requireModel();
|
|
177
|
+
const count = await model.count({
|
|
178
|
+
where: {
|
|
179
|
+
subjectId,
|
|
180
|
+
purpose,
|
|
181
|
+
consentedAt: { $isNotNull: true },
|
|
182
|
+
withdrawnAt: { $isNull: true },
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
return count > 0;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get the full consent history for a subject (all purposes).
|
|
189
|
+
*/
|
|
190
|
+
static async getHistory(subjectId) {
|
|
191
|
+
const model = ConsentRecord.requireModel();
|
|
192
|
+
return model.findAll({
|
|
193
|
+
where: { subjectId },
|
|
194
|
+
order: [['consentedAt', 'DESC']],
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get active consents for a subject grouped by purpose.
|
|
199
|
+
*/
|
|
200
|
+
static async getActiveConsents(subjectId) {
|
|
201
|
+
const model = ConsentRecord.requireModel();
|
|
202
|
+
return model.findAll({
|
|
203
|
+
where: {
|
|
204
|
+
subjectId,
|
|
205
|
+
consentedAt: { $isNotNull: true },
|
|
206
|
+
withdrawnAt: { $isNull: true },
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* List all subjects who have active consent for a specific purpose.
|
|
212
|
+
* Useful for marketing campaign validation.
|
|
213
|
+
*/
|
|
214
|
+
static async getConsented(purpose) {
|
|
215
|
+
const model = ConsentRecord.requireModel();
|
|
216
|
+
const records = await model.findAll({
|
|
217
|
+
where: {
|
|
218
|
+
purpose,
|
|
219
|
+
consentedAt: { $isNotNull: true },
|
|
220
|
+
withdrawnAt: { $isNull: true },
|
|
221
|
+
},
|
|
222
|
+
attributes: ['subjectId'],
|
|
223
|
+
});
|
|
224
|
+
return records.map((r) => r.subjectId);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
exports.ConsentRecord = ConsentRecord;
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ConsentVersioning
|
|
4
|
+
*
|
|
5
|
+
* Tracks consent history with timestamps for legal audit compliance.
|
|
6
|
+
* Provides version control for consent records with full history tracking.
|
|
7
|
+
*
|
|
8
|
+
* Supports compliance frameworks:
|
|
9
|
+
* GDPR (Art 6, 7, 9), CCPA/CPRA, LGPD (Brazil), PDPA (Thailand),
|
|
10
|
+
* PDPA (Singapore, Philippines, Malaysia, Indonesia, Vietnam),
|
|
11
|
+
* PIPL (China), PIPA (South Korea), and 60+ others.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* // Initialize (creates consent_versions table):
|
|
15
|
+
* await ConsentVersioning.initialize(prorm);
|
|
16
|
+
*
|
|
17
|
+
* // Record consent with version tracking:
|
|
18
|
+
* await ConsentVersioning.recordConsent(
|
|
19
|
+
* 'user-123',
|
|
20
|
+
* 'marketing_emails',
|
|
21
|
+
* true,
|
|
22
|
+
* '2.1'
|
|
23
|
+
* );
|
|
24
|
+
*
|
|
25
|
+
* // Get full history for audit:
|
|
26
|
+
* const history = await ConsentVersioning.getConsentHistory('user-123', 'marketing_emails');
|
|
27
|
+
*
|
|
28
|
+
* // Get current consent state:
|
|
29
|
+
* const current = await ConsentVersioning.getCurrentConsent('user-123', 'marketing_emails');
|
|
30
|
+
*
|
|
31
|
+
* // Revoke consent:
|
|
32
|
+
* await ConsentVersioning.revokeConsent('user-123', 'marketing_emails');
|
|
33
|
+
*
|
|
34
|
+
* // Validate consent is still valid:
|
|
35
|
+
* const isValid = await ConsentVersioning.validateConsent('user-123', 'marketing_emails');
|
|
36
|
+
*/
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.ConsentVersioning = void 0;
|
|
39
|
+
const data_types_1 = require("../models/data-types");
|
|
40
|
+
const operators_1 = require("../models/operators");
|
|
41
|
+
let ConsentVersionModel = null;
|
|
42
|
+
/**
|
|
43
|
+
* ConsentVersioning class for tracking consent history with version control.
|
|
44
|
+
* Provides methods for recording, retrieving, and validating consent records.
|
|
45
|
+
*/
|
|
46
|
+
class ConsentVersioning {
|
|
47
|
+
/**
|
|
48
|
+
* Initialize the ConsentVersioning model and sync the table.
|
|
49
|
+
* Call once at application startup.
|
|
50
|
+
*
|
|
51
|
+
* @param prorm - The Prorm instance
|
|
52
|
+
* @param tableName - Override the default table name 'consent_versions'
|
|
53
|
+
*/
|
|
54
|
+
static async initialize(prorm, tableName = 'consent_versions') {
|
|
55
|
+
ConsentVersionModel = prorm.define('ConsentVersion', {
|
|
56
|
+
id: {
|
|
57
|
+
type: data_types_1.DataTypes.INTEGER(),
|
|
58
|
+
primaryKey: true,
|
|
59
|
+
autoIncrement: true,
|
|
60
|
+
},
|
|
61
|
+
userId: {
|
|
62
|
+
type: data_types_1.DataTypes.STRING(255),
|
|
63
|
+
allowNull: false,
|
|
64
|
+
comment: 'Unique identifier of the data subject',
|
|
65
|
+
},
|
|
66
|
+
consentType: {
|
|
67
|
+
type: data_types_1.DataTypes.STRING(255),
|
|
68
|
+
allowNull: false,
|
|
69
|
+
comment: 'Type of consent (e.g. marketing_emails, analytics, cookies)',
|
|
70
|
+
},
|
|
71
|
+
granted: {
|
|
72
|
+
type: data_types_1.DataTypes.BOOLEAN(),
|
|
73
|
+
allowNull: false,
|
|
74
|
+
comment: 'Whether consent was granted (true) or revoked (false)',
|
|
75
|
+
},
|
|
76
|
+
version: {
|
|
77
|
+
type: data_types_1.DataTypes.STRING(64),
|
|
78
|
+
allowNull: false,
|
|
79
|
+
comment: 'Privacy notice / consent form version at time of recording',
|
|
80
|
+
},
|
|
81
|
+
grantedAt: {
|
|
82
|
+
type: data_types_1.DataTypes.DATE(),
|
|
83
|
+
allowNull: false,
|
|
84
|
+
comment: 'Timestamp when consent was granted',
|
|
85
|
+
},
|
|
86
|
+
revokedAt: {
|
|
87
|
+
type: data_types_1.DataTypes.DATE(),
|
|
88
|
+
allowNull: true,
|
|
89
|
+
comment: 'Timestamp when consent was revoked',
|
|
90
|
+
},
|
|
91
|
+
expiresAt: {
|
|
92
|
+
type: data_types_1.DataTypes.DATE(),
|
|
93
|
+
allowNull: true,
|
|
94
|
+
comment: 'Timestamp when consent expires (for time-limited consent)',
|
|
95
|
+
},
|
|
96
|
+
metadata: {
|
|
97
|
+
type: data_types_1.DataTypes.JSON(),
|
|
98
|
+
allowNull: true,
|
|
99
|
+
comment: 'Additional structured metadata',
|
|
100
|
+
},
|
|
101
|
+
ipAddress: {
|
|
102
|
+
type: data_types_1.DataTypes.STRING(45),
|
|
103
|
+
allowNull: true,
|
|
104
|
+
comment: 'IP address of the consenting user',
|
|
105
|
+
},
|
|
106
|
+
userAgent: {
|
|
107
|
+
type: data_types_1.DataTypes.STRING(512),
|
|
108
|
+
allowNull: true,
|
|
109
|
+
comment: 'User agent string of the consenting user',
|
|
110
|
+
},
|
|
111
|
+
}, {
|
|
112
|
+
tableName,
|
|
113
|
+
timestamps: true,
|
|
114
|
+
underscored: true,
|
|
115
|
+
indexes: [
|
|
116
|
+
{ fields: ['user_id', 'consent_type', 'granted_at'] },
|
|
117
|
+
{ fields: ['user_id', 'consent_type'] },
|
|
118
|
+
{ fields: ['consent_type', 'granted'] },
|
|
119
|
+
{ fields: ['expires_at'] },
|
|
120
|
+
],
|
|
121
|
+
});
|
|
122
|
+
await ConsentVersionModel.sync({ alter: false });
|
|
123
|
+
}
|
|
124
|
+
static requireModel() {
|
|
125
|
+
if (!ConsentVersionModel) {
|
|
126
|
+
throw new Error('[ConsentVersioning] Not initialized. Call ConsentVersioning.initialize(prorm) first.');
|
|
127
|
+
}
|
|
128
|
+
return ConsentVersionModel;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Record a new consent event with version tracking.
|
|
132
|
+
* Creates a new version record for each consent change.
|
|
133
|
+
*
|
|
134
|
+
* @param userId - Unique identifier of the data subject
|
|
135
|
+
* @param consentType - Type of consent (e.g. 'marketing_emails')
|
|
136
|
+
* @param granted - Whether consent is granted (true) or revoked (false)
|
|
137
|
+
* @param version - Privacy notice version at time of recording
|
|
138
|
+
* @param options - Additional options
|
|
139
|
+
* @returns The created consent version record
|
|
140
|
+
*/
|
|
141
|
+
static async recordConsent(userId, consentType, granted, version, options = {}) {
|
|
142
|
+
const model = ConsentVersioning.requireModel();
|
|
143
|
+
// If granting consent, first revoke any existing active consent
|
|
144
|
+
if (granted) {
|
|
145
|
+
const activeRecord = await model.findOne({
|
|
146
|
+
where: {
|
|
147
|
+
userId,
|
|
148
|
+
consentType,
|
|
149
|
+
granted: true,
|
|
150
|
+
revokedAt: null,
|
|
151
|
+
},
|
|
152
|
+
order: [['grantedAt', 'DESC']],
|
|
153
|
+
});
|
|
154
|
+
if (activeRecord) {
|
|
155
|
+
await activeRecord.update({
|
|
156
|
+
revokedAt: new Date(),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const record = await model.create({
|
|
161
|
+
userId,
|
|
162
|
+
consentType,
|
|
163
|
+
granted,
|
|
164
|
+
version,
|
|
165
|
+
grantedAt: new Date(),
|
|
166
|
+
revokedAt: granted ? null : new Date(),
|
|
167
|
+
expiresAt: options.expiresAt ?? null,
|
|
168
|
+
metadata: options.metadata ?? null,
|
|
169
|
+
ipAddress: options.ipAddress ?? null,
|
|
170
|
+
userAgent: options.userAgent ?? null,
|
|
171
|
+
});
|
|
172
|
+
return record;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get the full consent history for a user and consent type.
|
|
176
|
+
* Returns all version records in chronological order.
|
|
177
|
+
*
|
|
178
|
+
* @param userId - Unique identifier of the data subject
|
|
179
|
+
* @param consentType - Type of consent to query
|
|
180
|
+
* @param options - History options (order, limit, date filters)
|
|
181
|
+
* @returns Array of consent version records
|
|
182
|
+
*/
|
|
183
|
+
static async getConsentHistory(userId, consentType, options = {}) {
|
|
184
|
+
const model = ConsentVersioning.requireModel();
|
|
185
|
+
const where = {
|
|
186
|
+
userId,
|
|
187
|
+
consentType,
|
|
188
|
+
};
|
|
189
|
+
if (options.since) {
|
|
190
|
+
where.grantedAt = { [operators_1.Op.gte]: options.since };
|
|
191
|
+
}
|
|
192
|
+
if (options.until) {
|
|
193
|
+
where.grantedAt = {
|
|
194
|
+
...(where.grantedAt ?? {}),
|
|
195
|
+
[operators_1.Op.lte]: options.until,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return model.findAll({
|
|
199
|
+
where,
|
|
200
|
+
order: [['grantedAt', options.order ?? 'DESC']],
|
|
201
|
+
limit: options.limit ?? undefined,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get the current consent state for a user and consent type.
|
|
206
|
+
* Returns the most recent consent record (granted or revoked).
|
|
207
|
+
*
|
|
208
|
+
* @param userId - Unique identifier of the data subject
|
|
209
|
+
* @param consentType - Type of consent to query
|
|
210
|
+
* @returns The current consent version record or null if none exists
|
|
211
|
+
*/
|
|
212
|
+
static async getCurrentConsent(userId, consentType) {
|
|
213
|
+
const model = ConsentVersioning.requireModel();
|
|
214
|
+
return model.findOne({
|
|
215
|
+
where: {
|
|
216
|
+
userId,
|
|
217
|
+
consentType,
|
|
218
|
+
},
|
|
219
|
+
order: [['grantedAt', 'DESC']],
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Revoke consent for a user and consent type.
|
|
224
|
+
* Creates a new version record with granted = false.
|
|
225
|
+
*
|
|
226
|
+
* @param userId - Unique identifier of the data subject
|
|
227
|
+
* @param consentType - Type of consent to revoke
|
|
228
|
+
* @param options - Additional options (metadata, ipAddress, etc.)
|
|
229
|
+
* @returns The created revocation record
|
|
230
|
+
*/
|
|
231
|
+
static async revokeConsent(userId, consentType, options = {}) {
|
|
232
|
+
return ConsentVersioning.recordConsent(userId, consentType, false, options.metadata?.version ?? 'unknown', options);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Validate whether consent is currently valid (granted and not expired).
|
|
236
|
+
*
|
|
237
|
+
* @param userId - Unique identifier of the data subject
|
|
238
|
+
* @param consentType - Type of consent to validate
|
|
239
|
+
* @returns true if consent is currently valid, false otherwise
|
|
240
|
+
*/
|
|
241
|
+
static async validateConsent(userId, consentType) {
|
|
242
|
+
const current = await ConsentVersioning.getCurrentConsent(userId, consentType);
|
|
243
|
+
if (!current) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
// Check if consent is granted
|
|
247
|
+
if (!current.granted) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
// Check if consent has been revoked
|
|
251
|
+
if (current.revokedAt) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
// Check if consent has expired
|
|
255
|
+
if (current.expiresAt && new Date() > current.expiresAt) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get all active (granted and not expired) consents for a user.
|
|
262
|
+
*
|
|
263
|
+
* @param userId - Unique identifier of the data subject
|
|
264
|
+
* @returns Array of active consent version records
|
|
265
|
+
*/
|
|
266
|
+
static async getActiveConsents(userId) {
|
|
267
|
+
const model = ConsentVersioning.requireModel();
|
|
268
|
+
const now = new Date();
|
|
269
|
+
return model.findAll({
|
|
270
|
+
where: {
|
|
271
|
+
[operators_1.Op.and]: [
|
|
272
|
+
{ userId, granted: true, revokedAt: { [operators_1.Op.isNull]: true } },
|
|
273
|
+
{ [operators_1.Op.or]: [{ expiresAt: { [operators_1.Op.isNull]: true } }, { expiresAt: { [operators_1.Op.gte]: now } }] },
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Get all users who have granted consent for a specific consent type.
|
|
280
|
+
* Useful for compliance audits and marketing campaigns.
|
|
281
|
+
*
|
|
282
|
+
* @param consentType - Type of consent to query
|
|
283
|
+
* @returns Array of user IDs with active consent
|
|
284
|
+
*/
|
|
285
|
+
static async getConsentedUsers(consentType) {
|
|
286
|
+
const model = ConsentVersioning.requireModel();
|
|
287
|
+
const now = new Date();
|
|
288
|
+
const records = await model.findAll({
|
|
289
|
+
where: {
|
|
290
|
+
[operators_1.Op.and]: [
|
|
291
|
+
{ consentType, granted: true, revokedAt: { [operators_1.Op.isNull]: true } },
|
|
292
|
+
{ [operators_1.Op.or]: [{ expiresAt: { [operators_1.Op.isNull]: true } }, { expiresAt: { [operators_1.Op.gte]: now } }] },
|
|
293
|
+
],
|
|
294
|
+
},
|
|
295
|
+
attributes: ['userId'],
|
|
296
|
+
});
|
|
297
|
+
return Array.from(new Set(records.map((r) => r.userId)));
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Get consent by specific version.
|
|
301
|
+
*
|
|
302
|
+
* @param userId - Unique identifier of the data subject
|
|
303
|
+
* @param consentType - Type of consent to query
|
|
304
|
+
* @param version - Specific version to retrieve
|
|
305
|
+
* @returns The consent version record or null if not found
|
|
306
|
+
*/
|
|
307
|
+
static async getConsentByVersion(userId, consentType, version) {
|
|
308
|
+
const model = ConsentVersioning.requireModel();
|
|
309
|
+
return model.findOne({
|
|
310
|
+
where: {
|
|
311
|
+
userId,
|
|
312
|
+
consentType,
|
|
313
|
+
version,
|
|
314
|
+
},
|
|
315
|
+
order: [['grantedAt', 'DESC']],
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Re-grant consent (creates new version after revocation).
|
|
320
|
+
*
|
|
321
|
+
* @param userId - Unique identifier of the data subject
|
|
322
|
+
* @param consentType - Type of consent to re-grant
|
|
323
|
+
* @param version - Current privacy notice version
|
|
324
|
+
* @param options - Additional options
|
|
325
|
+
* @returns The created consent record
|
|
326
|
+
*/
|
|
327
|
+
static async reGrantConsent(userId, consentType, version, options = {}) {
|
|
328
|
+
return ConsentVersioning.recordConsent(userId, consentType, true, version, options);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
exports.ConsentVersioning = ConsentVersioning;
|