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,294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Permission / access-control decorators for the ORM.
|
|
4
|
+
*
|
|
5
|
+
* These decorators let you annotate model classes and individual fields with
|
|
6
|
+
* access rules — which roles can read, write, or are completely blocked from
|
|
7
|
+
* a field or an entire model. At query time you call filterByPermissions()
|
|
8
|
+
* to strip out any fields the current user is not allowed to see.
|
|
9
|
+
*
|
|
10
|
+
* ──────────────────────────────────────────────────────────────────────────
|
|
11
|
+
* Quick Reference
|
|
12
|
+
* ──────────────────────────────────────────────────────────────────────────
|
|
13
|
+
*
|
|
14
|
+
* @CanView('admin', 'manager') — only these roles can read the field/model
|
|
15
|
+
* @CannotView('guest') — these roles are DENIED read access
|
|
16
|
+
* @CanWrite('admin') — only these roles can write the field
|
|
17
|
+
* @CannotWrite('guest') — these roles are DENIED write access
|
|
18
|
+
* @Permission({ ... }) — full control with explicit allow/deny lists
|
|
19
|
+
*
|
|
20
|
+
* ──────────────────────────────────────────────────────────────────────────
|
|
21
|
+
* Example
|
|
22
|
+
* ──────────────────────────────────────────────────────────────────────────
|
|
23
|
+
*
|
|
24
|
+
* // Field-level permissions (applied via static registration):
|
|
25
|
+
* PermissionRegistry.addFieldRule(User, 'ssn', { canView: ['admin'] });
|
|
26
|
+
* PermissionRegistry.addFieldRule(User, 'salary', { canView: ['admin', 'hr'] });
|
|
27
|
+
* PermissionRegistry.addFieldRule(User, 'password', { canView: [] }); // nobody
|
|
28
|
+
*
|
|
29
|
+
* // Class-level permission (applied via decorator):
|
|
30
|
+
* @CanView('admin', 'manager')
|
|
31
|
+
* class AuditLog extends Model { ... }
|
|
32
|
+
*
|
|
33
|
+
* // At request time:
|
|
34
|
+
* const currentRole = 'guest';
|
|
35
|
+
* const user = await User.findOne({ where: { id: 1 } });
|
|
36
|
+
* const safeUser = filterByPermissions(user, currentRole);
|
|
37
|
+
* // safeUser.ssn === undefined, safeUser.salary === undefined
|
|
38
|
+
*
|
|
39
|
+
* // Or get the allowed attribute list for a findAll() call:
|
|
40
|
+
* const attrs = getAllowedAttributes(User, currentRole);
|
|
41
|
+
* const users = await User.findAll({ attributes: attrs });
|
|
42
|
+
*/
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.PermissionRegistry = void 0;
|
|
45
|
+
exports.canViewField = canViewField;
|
|
46
|
+
exports.canWriteField = canWriteField;
|
|
47
|
+
exports.getAllowedAttributes = getAllowedAttributes;
|
|
48
|
+
exports.filterByPermissions = filterByPermissions;
|
|
49
|
+
exports.filterWriteByPermissions = filterWriteByPermissions;
|
|
50
|
+
exports.CanView = CanView;
|
|
51
|
+
exports.CannotView = CannotView;
|
|
52
|
+
exports.CanWrite = CanWrite;
|
|
53
|
+
exports.CannotWrite = CannotWrite;
|
|
54
|
+
exports.Permission = Permission;
|
|
55
|
+
exports.withPermissions = withPermissions;
|
|
56
|
+
// ==================== Registry ====================
|
|
57
|
+
// model constructor → field name → rule
|
|
58
|
+
const fieldRules = new Map();
|
|
59
|
+
// model constructor → model-level rule
|
|
60
|
+
const modelRules = new Map();
|
|
61
|
+
function getFieldMap(target) {
|
|
62
|
+
if (!fieldRules.has(target))
|
|
63
|
+
fieldRules.set(target, new Map());
|
|
64
|
+
return fieldRules.get(target);
|
|
65
|
+
}
|
|
66
|
+
exports.PermissionRegistry = {
|
|
67
|
+
// ── Field-level rules ──────────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Register or update a permission rule for a specific field on a model.
|
|
70
|
+
*
|
|
71
|
+
* @param target - The model class constructor
|
|
72
|
+
* @param fieldName - The attribute/field name
|
|
73
|
+
* @param rule - The access rule
|
|
74
|
+
*/
|
|
75
|
+
addFieldRule(target, fieldName, rule) {
|
|
76
|
+
const map = getFieldMap(target);
|
|
77
|
+
const existing = map.get(fieldName) ?? {};
|
|
78
|
+
map.set(fieldName, { ...existing, ...rule });
|
|
79
|
+
},
|
|
80
|
+
/**
|
|
81
|
+
* Get the rule for a specific field (or undefined if none).
|
|
82
|
+
*/
|
|
83
|
+
getFieldRule(target, fieldName) {
|
|
84
|
+
return getFieldMap(target).get(fieldName);
|
|
85
|
+
},
|
|
86
|
+
/**
|
|
87
|
+
* Get all field rules registered on a model.
|
|
88
|
+
*/
|
|
89
|
+
getAllFieldRules(target) {
|
|
90
|
+
return getFieldMap(target);
|
|
91
|
+
},
|
|
92
|
+
// ── Model-level rules ──────────────────────────────────────────────────
|
|
93
|
+
/**
|
|
94
|
+
* Register or update a model-level permission rule (applies to ALL fields).
|
|
95
|
+
*/
|
|
96
|
+
addModelRule(target, rule) {
|
|
97
|
+
const existing = modelRules.get(target) ?? {};
|
|
98
|
+
modelRules.set(target, { ...existing, ...rule });
|
|
99
|
+
},
|
|
100
|
+
/**
|
|
101
|
+
* Get the model-level rule (or undefined if none).
|
|
102
|
+
*/
|
|
103
|
+
getModelRule(target) {
|
|
104
|
+
return modelRules.get(target);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
// ==================== Access-Check Helpers ====================
|
|
108
|
+
/**
|
|
109
|
+
* Check whether a given role can VIEW a field on a model.
|
|
110
|
+
*
|
|
111
|
+
* Resolution order:
|
|
112
|
+
* 1. Field-level cannotView → deny if role is listed
|
|
113
|
+
* 2. Field-level canView → allow only if role is listed (or '*' present)
|
|
114
|
+
* 3. Model-level cannotView → deny if role is listed
|
|
115
|
+
* 4. Model-level canView → allow only if role is listed (or '*' present)
|
|
116
|
+
* 5. Default: allow
|
|
117
|
+
*
|
|
118
|
+
* @param ModelClass - The model constructor
|
|
119
|
+
* @param fieldName - The attribute name to check
|
|
120
|
+
* @param role - The current user's role
|
|
121
|
+
*/
|
|
122
|
+
function canViewField(ModelClass, fieldName, role) {
|
|
123
|
+
const fieldRule = exports.PermissionRegistry.getFieldRule(ModelClass, fieldName);
|
|
124
|
+
const modelRule = exports.PermissionRegistry.getModelRule(ModelClass);
|
|
125
|
+
// Field-level cannotView
|
|
126
|
+
if (fieldRule?.cannotView?.includes(role))
|
|
127
|
+
return false;
|
|
128
|
+
// Field-level canView
|
|
129
|
+
if (fieldRule?.canView !== undefined) {
|
|
130
|
+
return fieldRule.canView.includes('*') || fieldRule.canView.includes(role);
|
|
131
|
+
}
|
|
132
|
+
// Model-level cannotView
|
|
133
|
+
if (modelRule?.cannotView?.includes(role))
|
|
134
|
+
return false;
|
|
135
|
+
// Model-level canView
|
|
136
|
+
if (modelRule?.canView !== undefined) {
|
|
137
|
+
return modelRule.canView.includes('*') || modelRule.canView.includes(role);
|
|
138
|
+
}
|
|
139
|
+
return true; // default: allow
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Check whether a given role can WRITE a field on a model.
|
|
143
|
+
*/
|
|
144
|
+
function canWriteField(ModelClass, fieldName, role) {
|
|
145
|
+
const fieldRule = exports.PermissionRegistry.getFieldRule(ModelClass, fieldName);
|
|
146
|
+
const modelRule = exports.PermissionRegistry.getModelRule(ModelClass);
|
|
147
|
+
if (fieldRule?.cannotWrite?.includes(role))
|
|
148
|
+
return false;
|
|
149
|
+
if (fieldRule?.canWrite !== undefined) {
|
|
150
|
+
return fieldRule.canWrite.includes('*') || fieldRule.canWrite.includes(role);
|
|
151
|
+
}
|
|
152
|
+
if (modelRule?.cannotWrite?.includes(role))
|
|
153
|
+
return false;
|
|
154
|
+
if (modelRule?.canWrite !== undefined) {
|
|
155
|
+
return modelRule.canWrite.includes('*') || modelRule.canWrite.includes(role);
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
// ==================== Attribute Filtering ====================
|
|
160
|
+
/**
|
|
161
|
+
* Return the list of field names on a model that `role` is allowed to VIEW.
|
|
162
|
+
*
|
|
163
|
+
* Useful for passing to `findAll({ attributes: ... })` to prevent loading
|
|
164
|
+
* restricted data at the database level.
|
|
165
|
+
*
|
|
166
|
+
* @param ModelClass - The model constructor
|
|
167
|
+
* @param role - The current user's role
|
|
168
|
+
* @param allAttributes - The full list of attribute names. If omitted,
|
|
169
|
+
* the registry's field rules are used as the universe
|
|
170
|
+
* (only fields with explicit rules are considered).
|
|
171
|
+
*/
|
|
172
|
+
function getAllowedAttributes(ModelClass, role, allAttributes) {
|
|
173
|
+
const fields = allAttributes ?? Array.from(exports.PermissionRegistry.getAllFieldRules(ModelClass).keys());
|
|
174
|
+
return fields.filter((f) => canViewField(ModelClass, f, role));
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Strip restricted fields from a plain object or model instance.
|
|
178
|
+
*
|
|
179
|
+
* Returns a new plain object with only the fields `role` is allowed to see.
|
|
180
|
+
* Does not mutate the original.
|
|
181
|
+
*
|
|
182
|
+
* @param instance - A model instance or plain record object
|
|
183
|
+
* @param role - The current user's role
|
|
184
|
+
* @param ModelClass - The model class (defaults to instance.constructor)
|
|
185
|
+
*/
|
|
186
|
+
function filterByPermissions(instance, role, ModelClass) {
|
|
187
|
+
const Model = ModelClass ?? instance.constructor;
|
|
188
|
+
// Get the plain data object
|
|
189
|
+
const data = typeof instance.toJSON === 'function' ? instance.toJSON() : { ...instance };
|
|
190
|
+
const result = {};
|
|
191
|
+
for (const [key, value] of Object.entries(data)) {
|
|
192
|
+
if (canViewField(Model, key, role)) {
|
|
193
|
+
result[key] = value;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Strip restricted fields from a plain object for WRITE operations.
|
|
200
|
+
* Removes any fields the role is not allowed to write.
|
|
201
|
+
*/
|
|
202
|
+
function filterWriteByPermissions(data, role, ModelClass) {
|
|
203
|
+
const result = {};
|
|
204
|
+
for (const [key, value] of Object.entries(data)) {
|
|
205
|
+
if (canWriteField(ModelClass, key, role)) {
|
|
206
|
+
result[key] = value;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
// ==================== Decorators ====================
|
|
212
|
+
/**
|
|
213
|
+
* Allow only the listed roles to VIEW the decorated class (all fields) or
|
|
214
|
+
* use PermissionRegistry.addFieldRule() for per-field control.
|
|
215
|
+
*
|
|
216
|
+
* @CanView('admin', 'manager')
|
|
217
|
+
* class AuditLog extends Model { ... }
|
|
218
|
+
*/
|
|
219
|
+
function CanView(...roles) {
|
|
220
|
+
return function (target) {
|
|
221
|
+
exports.PermissionRegistry.addModelRule(target, { canView: roles });
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Deny the listed roles from viewing the decorated class.
|
|
226
|
+
*
|
|
227
|
+
* @CannotView('guest', 'public')
|
|
228
|
+
* class SensitiveReport extends Model { ... }
|
|
229
|
+
*/
|
|
230
|
+
function CannotView(...roles) {
|
|
231
|
+
return function (target) {
|
|
232
|
+
exports.PermissionRegistry.addModelRule(target, { cannotView: roles });
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Allow only the listed roles to WRITE to the decorated class.
|
|
237
|
+
*
|
|
238
|
+
* @CanWrite('admin')
|
|
239
|
+
* class Settings extends Model { ... }
|
|
240
|
+
*/
|
|
241
|
+
function CanWrite(...roles) {
|
|
242
|
+
return function (target) {
|
|
243
|
+
exports.PermissionRegistry.addModelRule(target, { canWrite: roles });
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Deny the listed roles from writing to the decorated class.
|
|
248
|
+
*/
|
|
249
|
+
function CannotWrite(...roles) {
|
|
250
|
+
return function (target) {
|
|
251
|
+
exports.PermissionRegistry.addModelRule(target, { cannotWrite: roles });
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Full permission decorator — applies read and write rules at the model level.
|
|
256
|
+
*
|
|
257
|
+
* @Permission({ canView: ['admin', 'manager'], canWrite: ['admin'] })
|
|
258
|
+
* class Invoice extends Model { ... }
|
|
259
|
+
*/
|
|
260
|
+
function Permission(options) {
|
|
261
|
+
return function (target) {
|
|
262
|
+
exports.PermissionRegistry.addModelRule(target, options);
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
// ==================== Controller-Style Helpers ====================
|
|
266
|
+
/**
|
|
267
|
+
* A higher-order helper that wraps a controller function and automatically
|
|
268
|
+
* applies permission filtering on the result.
|
|
269
|
+
*
|
|
270
|
+
* Example:
|
|
271
|
+
*
|
|
272
|
+
* const getUser = withPermissions(User, async (id: number) => {
|
|
273
|
+
* return User.findByPk(id);
|
|
274
|
+
* });
|
|
275
|
+
*
|
|
276
|
+
* const safeUser = await getUser(1, 'guest');
|
|
277
|
+
* // safeUser has only the fields 'guest' is allowed to see
|
|
278
|
+
*
|
|
279
|
+
* @param ModelClass - The model class whose permissions to apply
|
|
280
|
+
* @param fn - The async controller function (last arg must be role string)
|
|
281
|
+
*/
|
|
282
|
+
function withPermissions(ModelClass, fn) {
|
|
283
|
+
return async (...args) => {
|
|
284
|
+
const role = args[args.length - 1];
|
|
285
|
+
const fnArgs = args.slice(0, -1);
|
|
286
|
+
const result = await fn(...fnArgs);
|
|
287
|
+
if (!result)
|
|
288
|
+
return null;
|
|
289
|
+
if (Array.isArray(result)) {
|
|
290
|
+
return result.map((item) => filterByPermissions(item, role, ModelClass));
|
|
291
|
+
}
|
|
292
|
+
return filterByPermissions(result, role, ModelClass);
|
|
293
|
+
};
|
|
294
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TypeScript decorators for SQL Stored Procedures and SQL Functions.
|
|
4
|
+
*
|
|
5
|
+
* @Procedure — annotate a class method (or standalone function wrapper) so
|
|
6
|
+
* it is understood by the ORM as a stored procedure.
|
|
7
|
+
* The ORM can generate the CREATE PROCEDURE SQL and register
|
|
8
|
+
* a callable wrapper on your Prorm instance.
|
|
9
|
+
*
|
|
10
|
+
* @SqlFunction — like @Procedure but for database functions that RETURN a
|
|
11
|
+
* value and can be called inside SELECT / WHERE clauses.
|
|
12
|
+
*
|
|
13
|
+
* Both decorators store metadata in a registry. Call
|
|
14
|
+
* ProcedureRegistry.getSQL(target, method) to retrieve the generated DDL,
|
|
15
|
+
* and ProcedureRegistry.call(prorm, target, method, args) to execute.
|
|
16
|
+
*
|
|
17
|
+
* Example:
|
|
18
|
+
*
|
|
19
|
+
* class UserProcedures {
|
|
20
|
+
* @Procedure({
|
|
21
|
+
* name: 'get_user_by_email',
|
|
22
|
+
* params: [{ name: 'p_email', type: 'VARCHAR(255)', mode: 'IN' }],
|
|
23
|
+
* dialect: 'mysql',
|
|
24
|
+
* body: `BEGIN SELECT * FROM users WHERE email = p_email; END`,
|
|
25
|
+
* })
|
|
26
|
+
* getUserByEmail(email: string) {}
|
|
27
|
+
*
|
|
28
|
+
* @SqlFunction({
|
|
29
|
+
* name: 'full_name',
|
|
30
|
+
* params: [
|
|
31
|
+
* { name: 'first', type: 'TEXT' },
|
|
32
|
+
* { name: 'last', type: 'TEXT' },
|
|
33
|
+
* ],
|
|
34
|
+
* returns: 'TEXT',
|
|
35
|
+
* dialect: 'postgres',
|
|
36
|
+
* body: `SELECT first || ' ' || last`,
|
|
37
|
+
* language: 'SQL',
|
|
38
|
+
* })
|
|
39
|
+
* fullName(first: string, last: string): string { return ''; }
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* // Generate and run the DDL
|
|
43
|
+
* const sql = ProcedureRegistry.getSQL(UserProcedures, 'getUserByEmail');
|
|
44
|
+
* await prorm.query(sql);
|
|
45
|
+
*
|
|
46
|
+
* // Or use the auto-call helper
|
|
47
|
+
* const rows = await ProcedureRegistry.call(prorm, UserProcedures, 'getUserByEmail', ['john@example.com']);
|
|
48
|
+
*/
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.ProcedureRegistry = void 0;
|
|
51
|
+
exports.Procedure = Procedure;
|
|
52
|
+
exports.SqlFunction = SqlFunction;
|
|
53
|
+
const registry = new Map();
|
|
54
|
+
function getOrCreateMap(target) {
|
|
55
|
+
if (!registry.has(target))
|
|
56
|
+
registry.set(target, new Map());
|
|
57
|
+
return registry.get(target);
|
|
58
|
+
}
|
|
59
|
+
// ==================== Decorators ====================
|
|
60
|
+
/**
|
|
61
|
+
* Mark a method as a SQL stored procedure.
|
|
62
|
+
*
|
|
63
|
+
* The decorated method is a no-op at runtime — use ProcedureRegistry.call()
|
|
64
|
+
* to execute the actual database procedure.
|
|
65
|
+
*/
|
|
66
|
+
function Procedure(options = {}) {
|
|
67
|
+
return function (target, propertyKey, _descriptor) {
|
|
68
|
+
const map = getOrCreateMap(target.constructor ?? target);
|
|
69
|
+
map.set(propertyKey, {
|
|
70
|
+
kind: 'procedure',
|
|
71
|
+
methodKey: propertyKey,
|
|
72
|
+
options: { name: propertyKey, ...options },
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Mark a method as a SQL function (returns a value, usable in SELECT/WHERE).
|
|
78
|
+
*
|
|
79
|
+
* The decorated method is a no-op at runtime — use ProcedureRegistry.call()
|
|
80
|
+
* to execute the actual database function.
|
|
81
|
+
*/
|
|
82
|
+
function SqlFunction(options) {
|
|
83
|
+
return function (target, propertyKey, _descriptor) {
|
|
84
|
+
const map = getOrCreateMap(target.constructor ?? target);
|
|
85
|
+
map.set(propertyKey, {
|
|
86
|
+
kind: 'function',
|
|
87
|
+
methodKey: propertyKey,
|
|
88
|
+
options: { name: propertyKey, ...options },
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// ==================== SQL Generation ====================
|
|
93
|
+
function buildMysqlProcedure(meta) {
|
|
94
|
+
const opts = meta.options;
|
|
95
|
+
const name = opts.schema ? `\`${opts.schema}\`.\`${opts.name}\`` : `\`${opts.name}\``;
|
|
96
|
+
const params = (opts.params ?? []).map((p) => `${p.mode ?? 'IN'} ${p.name} ${p.type}`).join(', ');
|
|
97
|
+
const drop = opts.replace ? `DROP PROCEDURE IF EXISTS ${name};\n` : '';
|
|
98
|
+
const body = opts.body ?? '-- TODO: add procedure body here';
|
|
99
|
+
return `${drop}CREATE PROCEDURE ${name}(${params})\nBEGIN\n ${body}\nEND`;
|
|
100
|
+
}
|
|
101
|
+
function buildMysqlFunction(meta) {
|
|
102
|
+
const opts = meta.options;
|
|
103
|
+
const name = opts.schema ? `\`${opts.schema}\`.\`${opts.name}\`` : `\`${opts.name}\``;
|
|
104
|
+
const params = (opts.params ?? []).map((p) => `${p.name} ${p.type}`).join(', ');
|
|
105
|
+
const determinism = opts.volatility ?? 'DETERMINISTIC';
|
|
106
|
+
const drop = opts.replace ? `DROP FUNCTION IF EXISTS ${name};\n` : '';
|
|
107
|
+
const body = opts.body ?? '-- TODO: add function body here';
|
|
108
|
+
return `${drop}CREATE FUNCTION ${name}(${params})\nRETURNS ${opts.returns}\n${determinism}\nBEGIN\n ${body}\nEND`;
|
|
109
|
+
}
|
|
110
|
+
function buildPostgresProcedure(meta) {
|
|
111
|
+
const opts = meta.options;
|
|
112
|
+
const name = opts.schema ? `"${opts.schema}"."${opts.name}"` : `"${opts.name}"`;
|
|
113
|
+
const params = (opts.params ?? []).map((p) => `${p.mode ?? 'IN'} ${p.name} ${p.type}`).join(', ');
|
|
114
|
+
const orReplace = opts.replace ? 'OR REPLACE ' : '';
|
|
115
|
+
const body = opts.body ?? '-- TODO: add procedure body here';
|
|
116
|
+
return `CREATE ${orReplace}PROCEDURE ${name}(${params})\nLANGUAGE plpgsql\nAS $$\nBEGIN\n ${body}\nEND;\n$$`;
|
|
117
|
+
}
|
|
118
|
+
function buildPostgresFunction(meta) {
|
|
119
|
+
const opts = meta.options;
|
|
120
|
+
const name = opts.schema ? `"${opts.schema}"."${opts.name}"` : `"${opts.name}"`;
|
|
121
|
+
const params = (opts.params ?? []).map((p) => `${p.name} ${p.type}`).join(', ');
|
|
122
|
+
const language = opts.language ?? 'plpgsql';
|
|
123
|
+
const volatility = opts.volatility ? `\n${opts.volatility}` : '';
|
|
124
|
+
const orReplace = opts.replace ? 'OR REPLACE ' : '';
|
|
125
|
+
const body = opts.body ?? '-- TODO: add function body here';
|
|
126
|
+
const bodyWrapper = language === 'SQL' ? `$$ ${body} $$` : `$$\nBEGIN\n ${body}\nEND;\n$$`;
|
|
127
|
+
return `CREATE ${orReplace}FUNCTION ${name}(${params})\nRETURNS ${opts.returns}${volatility}\nLANGUAGE ${language}\nAS ${bodyWrapper}`;
|
|
128
|
+
}
|
|
129
|
+
// ==================== Public Registry API ====================
|
|
130
|
+
exports.ProcedureRegistry = {
|
|
131
|
+
/**
|
|
132
|
+
* Get all registered procedures/functions on a class.
|
|
133
|
+
*/
|
|
134
|
+
getAll(target) {
|
|
135
|
+
return Array.from(getOrCreateMap(target).values());
|
|
136
|
+
},
|
|
137
|
+
/**
|
|
138
|
+
* Get a single procedure/function meta by method name.
|
|
139
|
+
*/
|
|
140
|
+
get(target, methodKey) {
|
|
141
|
+
return getOrCreateMap(target).get(methodKey);
|
|
142
|
+
},
|
|
143
|
+
/**
|
|
144
|
+
* Generate the CREATE PROCEDURE / CREATE FUNCTION SQL for a registered method.
|
|
145
|
+
*
|
|
146
|
+
* @param target - The class constructor
|
|
147
|
+
* @param methodKey - The method name
|
|
148
|
+
* @param dialect - Override the dialect stored in the decorator options
|
|
149
|
+
*/
|
|
150
|
+
getSQL(target, methodKey, dialect) {
|
|
151
|
+
const meta = getOrCreateMap(target).get(methodKey);
|
|
152
|
+
if (!meta)
|
|
153
|
+
throw new Error(`[ORM] No @Procedure/@SqlFunction found for method "${methodKey}"`);
|
|
154
|
+
const effectiveDialect = dialect ?? meta.options.dialect ?? 'mysql';
|
|
155
|
+
if (meta.kind === 'procedure') {
|
|
156
|
+
if (effectiveDialect === 'postgres')
|
|
157
|
+
return buildPostgresProcedure(meta);
|
|
158
|
+
return buildMysqlProcedure(meta); // mysql / mariadb
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
if (effectiveDialect === 'postgres')
|
|
162
|
+
return buildPostgresFunction(meta);
|
|
163
|
+
return buildMysqlFunction(meta);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
/**
|
|
167
|
+
* Generate DDL for every procedure/function on a class and execute them.
|
|
168
|
+
*
|
|
169
|
+
* @param prorm - Prorm instance
|
|
170
|
+
* @param target - The class constructor
|
|
171
|
+
* @param dialect - Override dialect
|
|
172
|
+
*/
|
|
173
|
+
async createAll(prorm, target, dialect) {
|
|
174
|
+
for (const [methodKey] of getOrCreateMap(target)) {
|
|
175
|
+
const sql = exports.ProcedureRegistry.getSQL(target, methodKey, dialect);
|
|
176
|
+
await prorm.query(sql);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
/**
|
|
180
|
+
* Call a stored procedure.
|
|
181
|
+
*
|
|
182
|
+
* @param prorm - Prorm instance
|
|
183
|
+
* @param target - The class constructor that has the @Procedure decorator
|
|
184
|
+
* @param methodKey - The method name
|
|
185
|
+
* @param args - Arguments to pass (positional, matched to params order)
|
|
186
|
+
*/
|
|
187
|
+
async call(prorm, target, methodKey, args = []) {
|
|
188
|
+
const meta = getOrCreateMap(target).get(methodKey);
|
|
189
|
+
if (!meta)
|
|
190
|
+
throw new Error(`[ORM] No @Procedure found for method "${methodKey}"`);
|
|
191
|
+
const opts = meta.options;
|
|
192
|
+
const name = opts.name ?? methodKey;
|
|
193
|
+
const dialect = opts.dialect ?? 'mysql';
|
|
194
|
+
const placeholders = args.map(() => (dialect === 'postgres' ? '$?' : '?')).join(', ');
|
|
195
|
+
let sql;
|
|
196
|
+
if (meta.kind === 'procedure') {
|
|
197
|
+
sql =
|
|
198
|
+
dialect === 'mysql' || dialect === 'mariadb'
|
|
199
|
+
? `CALL \`${name}\`(${placeholders})`
|
|
200
|
+
: `CALL "${name}"(${placeholders})`;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
sql =
|
|
204
|
+
dialect === 'postgres'
|
|
205
|
+
? `SELECT * FROM "${name}"(${placeholders})`
|
|
206
|
+
: `SELECT ${name}(${placeholders})`;
|
|
207
|
+
}
|
|
208
|
+
return prorm.query(sql, { replacements: args });
|
|
209
|
+
},
|
|
210
|
+
};
|