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,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DataRetentionPolicy
|
|
4
|
+
*
|
|
5
|
+
* Attach a mandatory data retention period to any model.
|
|
6
|
+
* Records older than the policy window are purged (deleted or anonymized)
|
|
7
|
+
* when DataRetentionPolicy.purge() is called.
|
|
8
|
+
*
|
|
9
|
+
* Covers ~80 compliance frameworks including:
|
|
10
|
+
* GDPR (storage limitation), SOX (7yr), MiFID II (5yr), SEC 17a-4,
|
|
11
|
+
* FINRA, HIPAA, PIPEDA, LGPD, APPI, PDPA variants, and more.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* // Set a 7-year retention on financial records:
|
|
15
|
+
* DataRetentionPolicy.set(FinancialRecord, {
|
|
16
|
+
* maxAge: { years: 7 },
|
|
17
|
+
* field: 'createdAt',
|
|
18
|
+
* onExpiry: 'delete',
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Purge expired records (call from a scheduled job):
|
|
22
|
+
* const { deleted } = await DataRetentionPolicy.purge(FinancialRecord, prorm);
|
|
23
|
+
*
|
|
24
|
+
* // Or purge all registered models:
|
|
25
|
+
* const report = await DataRetentionPolicy.purgeAll(prorm);
|
|
26
|
+
*/
|
|
27
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
30
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
31
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
32
|
+
}
|
|
33
|
+
Object.defineProperty(o, k2, desc);
|
|
34
|
+
}) : (function(o, m, k, k2) {
|
|
35
|
+
if (k2 === undefined) k2 = k;
|
|
36
|
+
o[k2] = m[k];
|
|
37
|
+
}));
|
|
38
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
39
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
40
|
+
}) : function(o, v) {
|
|
41
|
+
o["default"] = v;
|
|
42
|
+
});
|
|
43
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
44
|
+
var ownKeys = function(o) {
|
|
45
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
46
|
+
var ar = [];
|
|
47
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
48
|
+
return ar;
|
|
49
|
+
};
|
|
50
|
+
return ownKeys(o);
|
|
51
|
+
};
|
|
52
|
+
return function (mod) {
|
|
53
|
+
if (mod && mod.__esModule) return mod;
|
|
54
|
+
var result = {};
|
|
55
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
56
|
+
__setModuleDefault(result, mod);
|
|
57
|
+
return result;
|
|
58
|
+
};
|
|
59
|
+
})();
|
|
60
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
61
|
+
exports.DataRetentionPolicy = void 0;
|
|
62
|
+
// ==================== Duration Math ====================
|
|
63
|
+
function durationToMs(d) {
|
|
64
|
+
if (d.ms != null)
|
|
65
|
+
return d.ms;
|
|
66
|
+
let ms = 0;
|
|
67
|
+
if (d.hours)
|
|
68
|
+
ms += d.hours * 60 * 60 * 1000;
|
|
69
|
+
if (d.days)
|
|
70
|
+
ms += d.days * 24 * 60 * 60 * 1000;
|
|
71
|
+
if (d.months)
|
|
72
|
+
ms += d.months * 30 * 24 * 60 * 60 * 1000;
|
|
73
|
+
if (d.years)
|
|
74
|
+
ms += d.years * 365 * 24 * 60 * 60 * 1000;
|
|
75
|
+
return ms;
|
|
76
|
+
}
|
|
77
|
+
const registry = new Map();
|
|
78
|
+
// ==================== DataRetentionPolicy ====================
|
|
79
|
+
class DataRetentionPolicy {
|
|
80
|
+
/**
|
|
81
|
+
* Register a retention policy on a model.
|
|
82
|
+
* This does not start any background process — call purge() on a schedule.
|
|
83
|
+
*
|
|
84
|
+
* @param model - The model class
|
|
85
|
+
* @param options - Retention configuration
|
|
86
|
+
*/
|
|
87
|
+
static set(model, options) {
|
|
88
|
+
registry.set(model, {
|
|
89
|
+
model,
|
|
90
|
+
options: {
|
|
91
|
+
field: options.field ?? 'createdAt',
|
|
92
|
+
onExpiry: options.onExpiry ?? 'delete',
|
|
93
|
+
preserveFields: options.preserveFields ?? [],
|
|
94
|
+
maxAge: options.maxAge,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Remove the retention policy from a model.
|
|
100
|
+
*/
|
|
101
|
+
static unset(model) {
|
|
102
|
+
registry.delete(model);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get the registered policy for a model, or undefined.
|
|
106
|
+
*/
|
|
107
|
+
static get(model) {
|
|
108
|
+
return registry.get(model)?.options;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Purge expired records for a specific model.
|
|
112
|
+
*
|
|
113
|
+
* @param model - The model class
|
|
114
|
+
* @param prorm - The Prorm instance (for Op support)
|
|
115
|
+
* @returns Number of records deleted or anonymized
|
|
116
|
+
*/
|
|
117
|
+
static async purge(model, prorm) {
|
|
118
|
+
const entry = registry.get(model);
|
|
119
|
+
if (!entry) {
|
|
120
|
+
throw new Error(`[DataRetentionPolicy] No retention policy registered for model "${model.name ?? model}". ` +
|
|
121
|
+
`Call DataRetentionPolicy.set(Model, options) first.`);
|
|
122
|
+
}
|
|
123
|
+
const { options } = entry;
|
|
124
|
+
const cutoff = new Date(Date.now() - durationToMs(options.maxAge));
|
|
125
|
+
const field = options.field;
|
|
126
|
+
// Build a where clause: field < cutoff
|
|
127
|
+
// Use prorm Op if available, otherwise fall back to raw object key
|
|
128
|
+
let where;
|
|
129
|
+
try {
|
|
130
|
+
const Op = prorm?.Op ?? (await Promise.resolve().then(() => __importStar(require('../models/operators'))).then((m) => m.Op));
|
|
131
|
+
where = { [field]: { [Op.lt]: cutoff } };
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Fallback: use Symbol-based lt operator directly
|
|
135
|
+
where = { [field]: { [Symbol.for('lt')]: cutoff } };
|
|
136
|
+
}
|
|
137
|
+
if (options.onExpiry === 'delete') {
|
|
138
|
+
const count = await model.destroy({ where, force: true });
|
|
139
|
+
return { affected: count ?? 0, mode: 'delete' };
|
|
140
|
+
}
|
|
141
|
+
// Anonymize mode: find and null out non-preserved fields
|
|
142
|
+
const records = await model.findAll({ where });
|
|
143
|
+
let affected = 0;
|
|
144
|
+
for (const record of records) {
|
|
145
|
+
const data = record.dataValues ?? record;
|
|
146
|
+
const patch = {};
|
|
147
|
+
for (const key of Object.keys(data)) {
|
|
148
|
+
if (key === 'id' || key === field || options.preserveFields.includes(key))
|
|
149
|
+
continue;
|
|
150
|
+
patch[key] = null;
|
|
151
|
+
}
|
|
152
|
+
if (Object.keys(patch).length > 0) {
|
|
153
|
+
await record.update(patch);
|
|
154
|
+
affected++;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { affected, mode: 'anonymize' };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Purge all models that have a registered retention policy.
|
|
161
|
+
*
|
|
162
|
+
* @param prorm - The Prorm instance
|
|
163
|
+
* @returns A map of model name → purge result
|
|
164
|
+
*/
|
|
165
|
+
static async purgeAll(prorm) {
|
|
166
|
+
const results = new Map();
|
|
167
|
+
for (const [modelClass, entry] of registry) {
|
|
168
|
+
try {
|
|
169
|
+
const result = await DataRetentionPolicy.purge(entry.model, prorm);
|
|
170
|
+
results.set(modelClass.name ?? 'Unknown', result);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
results.set(modelClass.name ?? 'Unknown', { affected: -1, mode: `error: ${err?.message}` });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return results;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* List all models with registered retention policies.
|
|
180
|
+
*/
|
|
181
|
+
static listPolicies() {
|
|
182
|
+
return Array.from(registry.values()).map(({ model, options }) => ({
|
|
183
|
+
modelName: model.name ?? String(model),
|
|
184
|
+
options,
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Calculate the cutoff date for a given retention duration.
|
|
189
|
+
* Useful for displaying "records older than X will be deleted on Y".
|
|
190
|
+
*/
|
|
191
|
+
static cutoffDate(maxAge) {
|
|
192
|
+
return new Date(Date.now() - durationToMs(maxAge));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
exports.DataRetentionPolicy = DataRetentionPolicy;
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DSARWorkflow
|
|
4
|
+
*
|
|
5
|
+
* Manages the complete lifecycle of Data Subject Access Requests (DSARs).
|
|
6
|
+
* Tracks request status from creation through fulfillment and completion.
|
|
7
|
+
* Generates data exports for data subjects under GDPR, CCPA, HIPAA, and PIPEDA.
|
|
8
|
+
*
|
|
9
|
+
* Covers ~65 compliance frameworks including:
|
|
10
|
+
* GDPR Art 15-22, CCPA, CPRA, LGPD, APPI, PIPA, PDPA, PoPI, DPDP India,
|
|
11
|
+
* HIPAA Right to Access, PIPEDA, and all major data privacy regulations.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* // Create a new access request
|
|
15
|
+
* const request = await DSARWorkflow.createRequest('user-123', 'access');
|
|
16
|
+
*
|
|
17
|
+
* // Check request status
|
|
18
|
+
* const status = await DSARWorkflow.getRequestStatus(request.id);
|
|
19
|
+
*
|
|
20
|
+
* // Fulfill the request (generate data export)
|
|
21
|
+
* const fulfilled = await DSARWorkflow.fulfillRequest(request.id);
|
|
22
|
+
*
|
|
23
|
+
* // Mark as complete
|
|
24
|
+
* await DSARWorkflow.completeRequest(request.id);
|
|
25
|
+
*
|
|
26
|
+
* // List all requests for a data subject
|
|
27
|
+
* const allRequests = await DSARWorkflow.getRequestsForDataSubject('user-123');
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.DSARWorkflow = void 0;
|
|
31
|
+
const data_portability_1 = require("./data-portability");
|
|
32
|
+
const right_to_erasure_1 = require("./right-to-erasure");
|
|
33
|
+
/**
|
|
34
|
+
* In-memory store for DSAR requests.
|
|
35
|
+
* In production, this would be persisted to a database.
|
|
36
|
+
*/
|
|
37
|
+
const dsarRequestStore = new Map();
|
|
38
|
+
/**
|
|
39
|
+
* Generate a unique DSAR request ID.
|
|
40
|
+
*/
|
|
41
|
+
function generateRequestId() {
|
|
42
|
+
return `dsar-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Map request type to default compliance frameworks.
|
|
46
|
+
*/
|
|
47
|
+
function getDefaultFrameworks(requestType) {
|
|
48
|
+
const frameworkMap = {
|
|
49
|
+
access: ['GDPR', 'CCPA', 'HIPAA', 'PIPEDA'],
|
|
50
|
+
rectification: ['GDPR', 'CCPA', 'PIPEDA'],
|
|
51
|
+
erasure: ['GDPR', 'CCPA', 'LGPD'],
|
|
52
|
+
portability: ['GDPR', 'CCPA', 'PIPEDA'],
|
|
53
|
+
restriction: ['GDPR', 'CCPA'],
|
|
54
|
+
objection: ['GDPR', 'CCPA'],
|
|
55
|
+
withdrawal: ['GDPR', 'CCPA', 'HIPAA'],
|
|
56
|
+
};
|
|
57
|
+
return frameworkMap[requestType];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Calculate deadline based on request type and jurisdiction.
|
|
61
|
+
* Default: 30 days (GDPR), extended to 45 for CCPA, 60 for HIPAA.
|
|
62
|
+
*/
|
|
63
|
+
function calculateDeadline(requestType) {
|
|
64
|
+
const now = new Date();
|
|
65
|
+
// HIPAA allows 60 days, CCPA allows 45 days, GDPR allows 30 days
|
|
66
|
+
// Use the longest deadline for safety
|
|
67
|
+
const days = 60;
|
|
68
|
+
now.setDate(now.getDate() + days);
|
|
69
|
+
return now;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validate request type and status transitions.
|
|
73
|
+
*/
|
|
74
|
+
function canTransition(currentStatus, newStatus) {
|
|
75
|
+
const validTransitions = {
|
|
76
|
+
pending: ['in_progress', 'rejected', 'expired'],
|
|
77
|
+
in_progress: ['pending_fulfillment', 'rejected', 'expired'],
|
|
78
|
+
pending_fulfillment: ['fulfilled', 'rejected', 'expired'],
|
|
79
|
+
fulfilled: ['delivered', 'rejected', 'expired'],
|
|
80
|
+
delivered: ['completed', 'expired'],
|
|
81
|
+
completed: [],
|
|
82
|
+
rejected: [],
|
|
83
|
+
expired: [],
|
|
84
|
+
};
|
|
85
|
+
return validTransitions[currentStatus]?.includes(newStatus) ?? false;
|
|
86
|
+
}
|
|
87
|
+
class DSARWorkflow {
|
|
88
|
+
/**
|
|
89
|
+
* Create a new DSAR request.
|
|
90
|
+
*
|
|
91
|
+
* @param dataSubjectId - The unique identifier for the data subject
|
|
92
|
+
* @param requestType - The type of DSAR request
|
|
93
|
+
* @returns The created DSAR request
|
|
94
|
+
*/
|
|
95
|
+
static createRequest(dataSubjectId, requestType) {
|
|
96
|
+
const id = generateRequestId();
|
|
97
|
+
const now = new Date();
|
|
98
|
+
const request = {
|
|
99
|
+
id,
|
|
100
|
+
dataSubjectId,
|
|
101
|
+
requestType,
|
|
102
|
+
status: 'pending',
|
|
103
|
+
createdAt: now,
|
|
104
|
+
updatedAt: now,
|
|
105
|
+
deadline: calculateDeadline(requestType),
|
|
106
|
+
frameworks: getDefaultFrameworks(requestType),
|
|
107
|
+
};
|
|
108
|
+
dsarRequestStore.set(id, request);
|
|
109
|
+
return request;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get the status of a DSAR request.
|
|
113
|
+
*
|
|
114
|
+
* @param requestId - The request ID
|
|
115
|
+
* @returns The request if found, undefined otherwise
|
|
116
|
+
*/
|
|
117
|
+
static getRequestStatus(requestId) {
|
|
118
|
+
return dsarRequestStore.get(requestId);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Fulfill a DSAR request by generating the data export.
|
|
122
|
+
* For 'access' and 'portability' requests, generates data export.
|
|
123
|
+
* For 'erasure' requests, performs the erasure operation.
|
|
124
|
+
*
|
|
125
|
+
* @param requestId - The request ID
|
|
126
|
+
* @param options - Fulfillment options
|
|
127
|
+
* @returns The fulfilled request
|
|
128
|
+
*/
|
|
129
|
+
static async fulfillRequest(requestId, options = {}) {
|
|
130
|
+
const request = dsarRequestStore.get(requestId);
|
|
131
|
+
if (!request) {
|
|
132
|
+
throw new Error(`[DSARWorkflow] Request not found: ${requestId}`);
|
|
133
|
+
}
|
|
134
|
+
if (!canTransition(request.status, 'pending_fulfillment')) {
|
|
135
|
+
throw new Error(`[DSARWorkflow] Cannot fulfill request in status: ${request.status}`);
|
|
136
|
+
}
|
|
137
|
+
const now = new Date();
|
|
138
|
+
const format = options.format ?? 'json';
|
|
139
|
+
// Update status to in_progress first
|
|
140
|
+
request.status = 'in_progress';
|
|
141
|
+
request.updatedAt = now;
|
|
142
|
+
dsarRequestStore.set(requestId, request);
|
|
143
|
+
try {
|
|
144
|
+
if (request.requestType === 'erasure' && options.models && options.models.length > 0) {
|
|
145
|
+
// Handle erasure requests
|
|
146
|
+
for (const { model, where } of options.models) {
|
|
147
|
+
await right_to_erasure_1.RightToErasure.erase(model, { where: where ?? {} }, options.erasureOptions);
|
|
148
|
+
}
|
|
149
|
+
request.honored = true;
|
|
150
|
+
}
|
|
151
|
+
else if ((request.requestType === 'access' || request.requestType === 'portability') &&
|
|
152
|
+
options.models &&
|
|
153
|
+
options.models.length > 0) {
|
|
154
|
+
// Handle access/portability requests - generate data export
|
|
155
|
+
const exports = [];
|
|
156
|
+
for (const { model, where } of options.models) {
|
|
157
|
+
const result = await (0, data_portability_1.exportData)(model, {
|
|
158
|
+
where,
|
|
159
|
+
format,
|
|
160
|
+
include: options.include,
|
|
161
|
+
limit: options.limit,
|
|
162
|
+
...options.exportOptions,
|
|
163
|
+
});
|
|
164
|
+
exports.push({
|
|
165
|
+
content: result.content,
|
|
166
|
+
format: result.format,
|
|
167
|
+
recordCount: result.recordCount,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
// Combine all exports into single content
|
|
171
|
+
const combinedContent = exports
|
|
172
|
+
.map((exp) => exp.content)
|
|
173
|
+
.join('\n---\n');
|
|
174
|
+
const totalRecords = exports.reduce((sum, exp) => sum + exp.recordCount, 0);
|
|
175
|
+
request.exportData = {
|
|
176
|
+
content: combinedContent,
|
|
177
|
+
format,
|
|
178
|
+
recordCount: totalRecords,
|
|
179
|
+
};
|
|
180
|
+
request.honored = true;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
// No models provided - mark as fulfilled with empty data
|
|
184
|
+
request.honored = false;
|
|
185
|
+
}
|
|
186
|
+
// Transition to fulfilled status
|
|
187
|
+
request.status = 'fulfilled';
|
|
188
|
+
request.fulfilledAt = now;
|
|
189
|
+
request.updatedAt = now;
|
|
190
|
+
if (options.notes) {
|
|
191
|
+
request.notes = options.notes;
|
|
192
|
+
}
|
|
193
|
+
dsarRequestStore.set(requestId, request);
|
|
194
|
+
return request;
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
request.status = 'rejected';
|
|
198
|
+
request.rejectionReason = error instanceof Error ? error.message : 'Unknown error';
|
|
199
|
+
request.updatedAt = new Date();
|
|
200
|
+
dsarRequestStore.set(requestId, request);
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Mark a DSAR request as complete.
|
|
206
|
+
* Valid transitions: fulfilled -> delivered -> completed
|
|
207
|
+
*
|
|
208
|
+
* @param requestId - The request ID
|
|
209
|
+
* @returns The completed request
|
|
210
|
+
*/
|
|
211
|
+
static completeRequest(requestId) {
|
|
212
|
+
const request = dsarRequestStore.get(requestId);
|
|
213
|
+
if (!request) {
|
|
214
|
+
throw new Error(`[DSARWorkflow] Request not found: ${requestId}`);
|
|
215
|
+
}
|
|
216
|
+
if (!canTransition(request.status, 'completed')) {
|
|
217
|
+
// Try to transition through delivered
|
|
218
|
+
if (request.status === 'fulfilled') {
|
|
219
|
+
request.status = 'delivered';
|
|
220
|
+
request.deliveredAt = new Date();
|
|
221
|
+
}
|
|
222
|
+
else if (request.status !== 'delivered') {
|
|
223
|
+
throw new Error(`[DSARWorkflow] Cannot complete request in status: ${request.status}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const now = new Date();
|
|
227
|
+
request.status = 'completed';
|
|
228
|
+
request.completedAt = now;
|
|
229
|
+
request.updatedAt = now;
|
|
230
|
+
dsarRequestStore.set(requestId, request);
|
|
231
|
+
return request;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get all requests for a specific data subject.
|
|
235
|
+
*
|
|
236
|
+
* @param dataSubjectId - The data subject ID
|
|
237
|
+
* @returns Array of DSAR requests for the data subject
|
|
238
|
+
*/
|
|
239
|
+
static getRequestsForDataSubject(dataSubjectId) {
|
|
240
|
+
const requests = [];
|
|
241
|
+
for (const request of dsarRequestStore.values()) {
|
|
242
|
+
if (request.dataSubjectId === dataSubjectId) {
|
|
243
|
+
requests.push(request);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Sort by creation date, newest first
|
|
247
|
+
return requests.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get all requests of a specific status.
|
|
251
|
+
*
|
|
252
|
+
* @param status - The status to filter by
|
|
253
|
+
* @returns Array of DSAR requests with the specified status
|
|
254
|
+
*/
|
|
255
|
+
static getRequestsByStatus(status) {
|
|
256
|
+
const requests = [];
|
|
257
|
+
for (const request of dsarRequestStore.values()) {
|
|
258
|
+
if (request.status === status) {
|
|
259
|
+
requests.push(request);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return requests.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get all pending requests that have exceeded their deadline.
|
|
266
|
+
*
|
|
267
|
+
* @returns Array of expired DSAR requests
|
|
268
|
+
*/
|
|
269
|
+
static getExpiredRequests() {
|
|
270
|
+
const now = new Date();
|
|
271
|
+
const requests = [];
|
|
272
|
+
for (const request of dsarRequestStore.values()) {
|
|
273
|
+
if (request.deadline && request.deadline < now &&
|
|
274
|
+
!['completed', 'rejected', 'expired'].includes(request.status)) {
|
|
275
|
+
requests.push(request);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return requests.sort((a, b) => (a.deadline?.getTime() ?? 0) - (b.deadline?.getTime() ?? 0));
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Reject a DSAR request with a reason.
|
|
282
|
+
*
|
|
283
|
+
* @param requestId - The request ID
|
|
284
|
+
* @param reason - The rejection reason
|
|
285
|
+
* @returns The rejected request
|
|
286
|
+
*/
|
|
287
|
+
static rejectRequest(requestId, reason) {
|
|
288
|
+
const request = dsarRequestStore.get(requestId);
|
|
289
|
+
if (!request) {
|
|
290
|
+
throw new Error(`[DSARWorkflow] Request not found: ${requestId}`);
|
|
291
|
+
}
|
|
292
|
+
if (!canTransition(request.status, 'rejected')) {
|
|
293
|
+
throw new Error(`[DSARWorkflow] Cannot reject request in status: ${request.status}`);
|
|
294
|
+
}
|
|
295
|
+
request.status = 'rejected';
|
|
296
|
+
request.rejectionReason = reason;
|
|
297
|
+
request.updatedAt = new Date();
|
|
298
|
+
request.honored = false;
|
|
299
|
+
dsarRequestStore.set(requestId, request);
|
|
300
|
+
return request;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Start processing a request (transition from pending to in_progress).
|
|
304
|
+
*
|
|
305
|
+
* @param requestId - The request ID
|
|
306
|
+
* @returns The request in progress
|
|
307
|
+
*/
|
|
308
|
+
static startProcessing(requestId) {
|
|
309
|
+
const request = dsarRequestStore.get(requestId);
|
|
310
|
+
if (!request) {
|
|
311
|
+
throw new Error(`[DSARWorkflow] Request not found: ${requestId}`);
|
|
312
|
+
}
|
|
313
|
+
if (!canTransition(request.status, 'in_progress')) {
|
|
314
|
+
throw new Error(`[DSARWorkflow] Cannot start processing request in status: ${request.status}`);
|
|
315
|
+
}
|
|
316
|
+
request.status = 'in_progress';
|
|
317
|
+
request.updatedAt = new Date();
|
|
318
|
+
dsarRequestStore.set(requestId, request);
|
|
319
|
+
return request;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Mark a request as delivered to the data subject.
|
|
323
|
+
*
|
|
324
|
+
* @param requestId - The request ID
|
|
325
|
+
* @returns The delivered request
|
|
326
|
+
*/
|
|
327
|
+
static markDelivered(requestId) {
|
|
328
|
+
const request = dsarRequestStore.get(requestId);
|
|
329
|
+
if (!request) {
|
|
330
|
+
throw new Error(`[DSARWorkflow] Request not found: ${requestId}`);
|
|
331
|
+
}
|
|
332
|
+
if (!canTransition(request.status, 'delivered')) {
|
|
333
|
+
throw new Error(`[DSARWorkflow] Cannot mark request as delivered in status: ${request.status}`);
|
|
334
|
+
}
|
|
335
|
+
request.status = 'delivered';
|
|
336
|
+
request.deliveredAt = new Date();
|
|
337
|
+
request.updatedAt = new Date();
|
|
338
|
+
dsarRequestStore.set(requestId, request);
|
|
339
|
+
return request;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
exports.DSARWorkflow = DSARWorkflow;
|