@astram/engine 0.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/LICENSE +21 -0
- package/dist/adapters/mariadb-audit-sink.d.ts +24 -0
- package/dist/adapters/mariadb-audit-sink.d.ts.map +1 -0
- package/dist/adapters/mariadb-audit-sink.js +40 -0
- package/dist/adapters/mariadb-audit-sink.js.map +1 -0
- package/dist/adapters/mariadb-unit-of-work.d.ts +27 -0
- package/dist/adapters/mariadb-unit-of-work.d.ts.map +1 -0
- package/dist/adapters/mariadb-unit-of-work.js +58 -0
- package/dist/adapters/mariadb-unit-of-work.js.map +1 -0
- package/dist/decorators/access-bundle.decorator.d.ts +66 -0
- package/dist/decorators/access-bundle.decorator.d.ts.map +1 -0
- package/dist/decorators/access-bundle.decorator.js +76 -0
- package/dist/decorators/access-bundle.decorator.js.map +1 -0
- package/dist/decorators/object-type.decorator.d.ts +116 -0
- package/dist/decorators/object-type.decorator.d.ts.map +1 -0
- package/dist/decorators/object-type.decorator.js +146 -0
- package/dist/decorators/object-type.decorator.js.map +1 -0
- package/dist/errors.d.ts +46 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +67 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/module/engine.module.d.ts +90 -0
- package/dist/module/engine.module.d.ts.map +1 -0
- package/dist/module/engine.module.js +218 -0
- package/dist/module/engine.module.js.map +1 -0
- package/dist/module/tokens.d.ts +12 -0
- package/dist/module/tokens.d.ts.map +1 -0
- package/dist/module/tokens.js +14 -0
- package/dist/pipe/context.d.ts +55 -0
- package/dist/pipe/context.d.ts.map +1 -0
- package/dist/pipe/context.js +18 -0
- package/dist/pipe/context.js.map +1 -0
- package/dist/pipe/record-engine.d.ts +72 -0
- package/dist/pipe/record-engine.d.ts.map +1 -0
- package/dist/pipe/record-engine.js +279 -0
- package/dist/pipe/record-engine.js.map +1 -0
- package/dist/pipe/records-service.d.ts +47 -0
- package/dist/pipe/records-service.d.ts.map +1 -0
- package/dist/pipe/records-service.js +108 -0
- package/dist/pipe/stages/audit.d.ts +15 -0
- package/dist/pipe/stages/audit.d.ts.map +1 -0
- package/dist/pipe/stages/audit.js +42 -0
- package/dist/pipe/stages/audit.js.map +1 -0
- package/dist/pipe/stages/derive-write-context.d.ts +22 -0
- package/dist/pipe/stages/derive-write-context.d.ts.map +1 -0
- package/dist/pipe/stages/derive-write-context.js +61 -0
- package/dist/pipe/stages/derive-write-context.js.map +1 -0
- package/dist/pipe/stages/fetch-owner.d.ts +28 -0
- package/dist/pipe/stages/fetch-owner.d.ts.map +1 -0
- package/dist/pipe/stages/fetch-owner.js +90 -0
- package/dist/pipe/stages/fetch-owner.js.map +1 -0
- package/dist/pipe/stages/junction-sync.d.ts +14 -0
- package/dist/pipe/stages/junction-sync.d.ts.map +1 -0
- package/dist/pipe/stages/junction-sync.js +63 -0
- package/dist/pipe/stages/junction-sync.js.map +1 -0
- package/dist/pipe/stages/permission-gate.d.ts +11 -0
- package/dist/pipe/stages/permission-gate.d.ts.map +1 -0
- package/dist/pipe/stages/permission-gate.js +53 -0
- package/dist/pipe/stages/permission-gate.js.map +1 -0
- package/dist/pipe/stages/validate.d.ts +11 -0
- package/dist/pipe/stages/validate.d.ts.map +1 -0
- package/dist/pipe/stages/validate.js +89 -0
- package/dist/pipe/stages/validate.js.map +1 -0
- package/dist/pipe/stages/write.d.ts +12 -0
- package/dist/pipe/stages/write.d.ts.map +1 -0
- package/dist/pipe/stages/write.js +109 -0
- package/dist/pipe/stages/write.js.map +1 -0
- package/dist/ports.d.ts +37 -0
- package/dist/ports.d.ts.map +1 -0
- package/dist/ports.js +11 -0
- package/dist/ports.js.map +1 -0
- package/dist/registry.d.ts +42 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +184 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +207 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Astram
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The append-only audit sink adapter — writes one row per event into the
|
|
3
|
+
* `audit_log` table (shield_audit schema), on the pipe's transaction. Append-only:
|
|
4
|
+
* this adapter exposes ONLY `record()` — no update/delete path (structural, matching
|
|
5
|
+
* core's audit invariant). The table is created by an @astram/migrate migration
|
|
6
|
+
* (P-D); the adapter assumes it exists.
|
|
7
|
+
*
|
|
8
|
+
* Actor/tenant/action/resource are stored by stable id/key; the presentation layer
|
|
9
|
+
* resolves ids to labels (no raw ids to end users). Sensitive values are never put
|
|
10
|
+
* in the event by the pipe (the audit stage carries only op + typeKey attributes).
|
|
11
|
+
*/
|
|
12
|
+
import type { AuditEvent, AuditPort, QueryFn } from '../ports.js';
|
|
13
|
+
export interface AuditSinkConfig {
|
|
14
|
+
/** Schema holding the audit_log table (default shield_audit). */
|
|
15
|
+
readonly schema?: string;
|
|
16
|
+
/** Table name (default audit_log). */
|
|
17
|
+
readonly table?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class MariaDbAuditSink implements AuditPort {
|
|
20
|
+
private readonly ref;
|
|
21
|
+
constructor(config?: AuditSinkConfig);
|
|
22
|
+
record(event: AuditEvent, query: QueryFn): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=mariadb-audit-sink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mariadb-audit-sink.d.ts","sourceRoot":"","sources":["../../src/adapters/mariadb-audit-sink.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAElE,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,gBAAiB,YAAW,SAAS;IAChD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,MAAM,GAAE,eAAoB;IAMlC,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAmB/D"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The append-only audit sink adapter — writes one row per event into the
|
|
4
|
+
* `audit_log` table (shield_audit schema), on the pipe's transaction. Append-only:
|
|
5
|
+
* this adapter exposes ONLY `record()` — no update/delete path (structural, matching
|
|
6
|
+
* core's audit invariant). The table is created by an @astram/migrate migration
|
|
7
|
+
* (P-D); the adapter assumes it exists.
|
|
8
|
+
*
|
|
9
|
+
* Actor/tenant/action/resource are stored by stable id/key; the presentation layer
|
|
10
|
+
* resolves ids to labels (no raw ids to end users). Sensitive values are never put
|
|
11
|
+
* in the event by the pipe (the audit stage carries only op + typeKey attributes).
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.MariaDbAuditSink = void 0;
|
|
15
|
+
const node_crypto_1 = require("node:crypto");
|
|
16
|
+
class MariaDbAuditSink {
|
|
17
|
+
ref;
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
const schema = config.schema ?? 'shield_audit';
|
|
20
|
+
const table = config.table ?? 'audit_log';
|
|
21
|
+
this.ref = `\`${schema}\`.\`${table}\``;
|
|
22
|
+
}
|
|
23
|
+
async record(event, query) {
|
|
24
|
+
await query(`INSERT INTO ${this.ref}
|
|
25
|
+
(al_uuid, al_type, al_occurred_at_ms, al_actor_id, al_tenant_id,
|
|
26
|
+
al_action_key, al_resource_json, al_decision_json, al_attributes_json)
|
|
27
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
28
|
+
(0, node_crypto_1.randomUUID)(),
|
|
29
|
+
event.type,
|
|
30
|
+
event.occurredAtMs,
|
|
31
|
+
event.actorId,
|
|
32
|
+
event.tenantId,
|
|
33
|
+
event.action?.key ?? null,
|
|
34
|
+
event.resource ? JSON.stringify(event.resource) : null,
|
|
35
|
+
event.decision ? JSON.stringify(event.decision) : null,
|
|
36
|
+
event.attributes ? JSON.stringify(event.attributes) : null,
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.MariaDbAuditSink = MariaDbAuditSink;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mariadb-audit-sink.js","sourceRoot":"","sources":["../../src/adapters/mariadb-audit-sink.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;AAEH,6CAAyC;AAUzC,MAAa,gBAAgB;IACV,GAAG,CAAS;IAE7B,YAAY,SAA0B,EAAE;QACtC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,cAAc,CAAC;QAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,WAAW,CAAC;QAC1C,IAAI,CAAC,GAAG,GAAG,KAAK,MAAM,QAAQ,KAAK,IAAI,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAiB,EAAE,KAAc;QAC5C,MAAM,KAAK,CACT,eAAe,IAAI,CAAC,GAAG;;;0CAGa,EACpC;YACE,IAAA,wBAAU,GAAE;YACZ,KAAK,CAAC,IAAI;YACV,KAAK,CAAC,YAAY;YAClB,KAAK,CAAC,OAAO;YACb,KAAK,CAAC,QAAQ;YACd,KAAK,CAAC,MAAM,EAAE,GAAG,IAAI,IAAI;YACzB,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;YACtD,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;YACtD,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;SAC3D,CACF,CAAC;IACJ,CAAC;CACF;AA5BD,4CA4BC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SEALED mariadb {@link UnitOfWork} — the engine's own runtime pool (shield_core
|
|
3
|
+
* account: DML on shield_app, NO DDL). This pool is created INSIDE the engine module
|
|
4
|
+
* and is NEVER provided to feature scope — that absence IS the physical wall. A stage
|
|
5
|
+
* gets only the bound {@link QueryFn} for the duration of one transaction.
|
|
6
|
+
*
|
|
7
|
+
* ⛔ SECURITY — never logs the password. ADR-0018: the connection limit is a budgeted
|
|
8
|
+
* value from the engine config, not an ad-hoc knob.
|
|
9
|
+
*/
|
|
10
|
+
import type { QueryFn, UnitOfWork } from '../ports.js';
|
|
11
|
+
export interface EngineDbConfig {
|
|
12
|
+
readonly host: string;
|
|
13
|
+
readonly port: number;
|
|
14
|
+
/** Runtime DML account (shield_core) — NOT a DDL account. */
|
|
15
|
+
readonly user: string;
|
|
16
|
+
readonly password: string;
|
|
17
|
+
readonly database: string;
|
|
18
|
+
/** ADR-0018 budgeted connection cap for this pool. */
|
|
19
|
+
readonly connectionLimit: number;
|
|
20
|
+
}
|
|
21
|
+
export declare class MariaDbUnitOfWork implements UnitOfWork {
|
|
22
|
+
private readonly pool;
|
|
23
|
+
constructor(config: EngineDbConfig);
|
|
24
|
+
run<T>(body: (query: QueryFn) => Promise<T>): Promise<T>;
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=mariadb-unit-of-work.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mariadb-unit-of-work.d.ts","sourceRoot":"","sources":["../../src/adapters/mariadb-unit-of-work.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEvD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,sDAAsD;IACtD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,qBAAa,iBAAkB,YAAW,UAAU;IAClD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;gBAEhB,MAAM,EAAE,cAAc;IAc5B,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAqBxD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The SEALED mariadb {@link UnitOfWork} — the engine's own runtime pool (shield_core
|
|
4
|
+
* account: DML on shield_app, NO DDL). This pool is created INSIDE the engine module
|
|
5
|
+
* and is NEVER provided to feature scope — that absence IS the physical wall. A stage
|
|
6
|
+
* gets only the bound {@link QueryFn} for the duration of one transaction.
|
|
7
|
+
*
|
|
8
|
+
* ⛔ SECURITY — never logs the password. ADR-0018: the connection limit is a budgeted
|
|
9
|
+
* value from the engine config, not an ad-hoc knob.
|
|
10
|
+
*/
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.MariaDbUnitOfWork = void 0;
|
|
16
|
+
const mariadb_1 = __importDefault(require("mariadb"));
|
|
17
|
+
class MariaDbUnitOfWork {
|
|
18
|
+
pool;
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.pool = mariadb_1.default.createPool({
|
|
21
|
+
host: config.host,
|
|
22
|
+
port: config.port,
|
|
23
|
+
user: config.user,
|
|
24
|
+
password: config.password,
|
|
25
|
+
database: config.database,
|
|
26
|
+
connectionLimit: config.connectionLimit,
|
|
27
|
+
connectTimeout: 10_000,
|
|
28
|
+
timezone: 'Z',
|
|
29
|
+
multipleStatements: false,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async run(body) {
|
|
33
|
+
const conn = await this.pool.getConnection();
|
|
34
|
+
try {
|
|
35
|
+
await conn.beginTransaction();
|
|
36
|
+
const query = (sql, params) => params === undefined ? conn.query(sql) : conn.query(sql, params);
|
|
37
|
+
const result = await body(query);
|
|
38
|
+
await conn.commit();
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
try {
|
|
43
|
+
await conn.rollback();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// subordinate to the original error
|
|
47
|
+
}
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
await conn.release();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async close() {
|
|
55
|
+
await this.pool.end();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.MariaDbUnitOfWork = MariaDbUnitOfWork;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mariadb-unit-of-work.js","sourceRoot":"","sources":["../../src/adapters/mariadb-unit-of-work.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;;;;AAEH,sDAA6C;AAc7C,MAAa,iBAAiB;IACX,IAAI,CAAO;IAE5B,YAAY,MAAsB;QAChC,IAAI,CAAC,IAAI,GAAG,iBAAO,CAAC,UAAU,CAAC;YAC7B,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,eAAe,EAAE,MAAM,CAAC,eAAe;YACvC,cAAc,EAAE,MAAM;YACtB,QAAQ,EAAE,GAAG;YACb,kBAAkB,EAAE,KAAK;SAC1B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,GAAG,CAAI,IAAoC;QAC/C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC9B,MAAM,KAAK,GAAY,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CACrC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,MAAmB,CAAC,CAAC;YAChF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;YACjC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YACpB,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;YACxB,CAAC;YAAC,MAAM,CAAC;gBACP,oCAAoC;YACtC,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IACxB,CAAC;CACF;AAzCD,8CAyCC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@AccessBundle` — a metadata-only class decorator that lets a feature declare a
|
|
3
|
+
* NAMED, admin-grantable permission bundle (a friendly label over a set of atomic
|
|
4
|
+
* permission keys), and {@link reflectAccessBundle} that reads it back into a flat
|
|
5
|
+
* {@link AccessBundleSpec}.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors `@ObjectType` in `./object-type.decorator.ts` EXACTLY: a private `Symbol`
|
|
8
|
+
* metadata key, `reflect-metadata` used DIRECTLY (bare `Reflect.defineMetadata` — no
|
|
9
|
+
* Nest dependency), and the SAME omit-when-absent assembly discipline (`description`
|
|
10
|
+
* is set on the result ONLY when present, never as an `undefined` key).
|
|
11
|
+
*
|
|
12
|
+
* IMPORTANT — the decorated class is a METADATA CARRIER ONLY. It is NEVER
|
|
13
|
+
* instantiated: {@link reflectAccessBundle} takes the CONSTRUCTOR (`Function`), reads
|
|
14
|
+
* one `Reflect` key off it, and never calls `new cls()`.
|
|
15
|
+
*
|
|
16
|
+
* The bundle expands to atomic permission keys at reflect time:
|
|
17
|
+
* `permissionKeys = opts.permissions.flatMap(p => p.actions.map(a =>
|
|
18
|
+
* `${opts.module}:${p.typeKey}:${a}`))`, de-duped through a `Set` so the first-seen
|
|
19
|
+
* order is preserved (a repeated `module:typeKey:action` collapses to one entry).
|
|
20
|
+
*/
|
|
21
|
+
import 'reflect-metadata';
|
|
22
|
+
/**
|
|
23
|
+
* One permission line inside a bundle: a `typeKey` (the object/resource type) and the
|
|
24
|
+
* `actions` on it. Each `(typeKey, action)` pair expands to a `module:typeKey:action`
|
|
25
|
+
* atomic key at reflect time.
|
|
26
|
+
*/
|
|
27
|
+
export interface AccessBundlePermission {
|
|
28
|
+
readonly typeKey: string;
|
|
29
|
+
readonly actions: readonly string[];
|
|
30
|
+
}
|
|
31
|
+
/** Class-level options for `@AccessBundle`. */
|
|
32
|
+
export interface AccessBundleOptions {
|
|
33
|
+
readonly key: string;
|
|
34
|
+
readonly module: string;
|
|
35
|
+
readonly label: string;
|
|
36
|
+
readonly description?: string;
|
|
37
|
+
readonly permissions: readonly AccessBundlePermission[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The reflected result: the bundle's identity + its flat, de-duped, declaration-ordered
|
|
41
|
+
* atomic key set. `description` is OMITTED when absent (never an `undefined` key).
|
|
42
|
+
*/
|
|
43
|
+
export interface AccessBundleSpec {
|
|
44
|
+
readonly key: string;
|
|
45
|
+
readonly module: string;
|
|
46
|
+
readonly label: string;
|
|
47
|
+
readonly description?: string;
|
|
48
|
+
readonly permissionKeys: readonly string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `@AccessBundle({ key, module, label, description?, permissions })` — declare a named
|
|
52
|
+
* permission bundle on a class. Stores the options verbatim under
|
|
53
|
+
* {@link ACCESS_BUNDLE_META} on the constructor.
|
|
54
|
+
*/
|
|
55
|
+
export declare function AccessBundle(opts: AccessBundleOptions): ClassDecorator;
|
|
56
|
+
/**
|
|
57
|
+
* Read an {@link AccessBundleSpec} back out of a class decorated with `@AccessBundle`.
|
|
58
|
+
* Takes the CONSTRUCTOR (`Function`); never instantiates it.
|
|
59
|
+
*
|
|
60
|
+
* Throws {@link RegistrationError} when the class is undecorated or the options are
|
|
61
|
+
* empty (missing `key` / `module` / `label` / `permissions`). Assembles the result
|
|
62
|
+
* field-by-field with an explicit omit guard on `description` (omit-when-absent, mirror
|
|
63
|
+
* `reflectObjectType`).
|
|
64
|
+
*/
|
|
65
|
+
export declare function reflectAccessBundle(cls: Function): AccessBundleSpec;
|
|
66
|
+
//# sourceMappingURL=access-bundle.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-bundle.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/access-bundle.decorator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,kBAAkB,CAAC;AAW1B;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,+CAA+C;AAC/C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,SAAS,sBAAsB,EAAE,CAAC;CACzD;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C;AAID;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,cAAc,CAItE;AAID;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,QAAQ,GAAG,gBAAgB,CAsCnE"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@AccessBundle` — a metadata-only class decorator that lets a feature declare a
|
|
4
|
+
* NAMED, admin-grantable permission bundle (a friendly label over a set of atomic
|
|
5
|
+
* permission keys), and {@link reflectAccessBundle} that reads it back into a flat
|
|
6
|
+
* {@link AccessBundleSpec}.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors `@ObjectType` in `./object-type.decorator.ts` EXACTLY: a private `Symbol`
|
|
9
|
+
* metadata key, `reflect-metadata` used DIRECTLY (bare `Reflect.defineMetadata` — no
|
|
10
|
+
* Nest dependency), and the SAME omit-when-absent assembly discipline (`description`
|
|
11
|
+
* is set on the result ONLY when present, never as an `undefined` key).
|
|
12
|
+
*
|
|
13
|
+
* IMPORTANT — the decorated class is a METADATA CARRIER ONLY. It is NEVER
|
|
14
|
+
* instantiated: {@link reflectAccessBundle} takes the CONSTRUCTOR (`Function`), reads
|
|
15
|
+
* one `Reflect` key off it, and never calls `new cls()`.
|
|
16
|
+
*
|
|
17
|
+
* The bundle expands to atomic permission keys at reflect time:
|
|
18
|
+
* `permissionKeys = opts.permissions.flatMap(p => p.actions.map(a =>
|
|
19
|
+
* `${opts.module}:${p.typeKey}:${a}`))`, de-duped through a `Set` so the first-seen
|
|
20
|
+
* order is preserved (a repeated `module:typeKey:action` collapses to one entry).
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.AccessBundle = AccessBundle;
|
|
24
|
+
exports.reflectAccessBundle = reflectAccessBundle;
|
|
25
|
+
require("reflect-metadata");
|
|
26
|
+
const errors_js_1 = require("../errors.js");
|
|
27
|
+
// --- Private metadata key (Symbol) — mirror OBJECT_TYPE_META -------------------
|
|
28
|
+
/** Class-level: the {@link AccessBundleOptions} passed to `@AccessBundle`. */
|
|
29
|
+
const ACCESS_BUNDLE_META = Symbol('astram:engine:accessBundle');
|
|
30
|
+
// --- Decorator ---------------------------------------------------------------
|
|
31
|
+
/**
|
|
32
|
+
* `@AccessBundle({ key, module, label, description?, permissions })` — declare a named
|
|
33
|
+
* permission bundle on a class. Stores the options verbatim under
|
|
34
|
+
* {@link ACCESS_BUNDLE_META} on the constructor.
|
|
35
|
+
*/
|
|
36
|
+
function AccessBundle(opts) {
|
|
37
|
+
return (target) => {
|
|
38
|
+
Reflect.defineMetadata(ACCESS_BUNDLE_META, opts, target);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// --- Reflector ---------------------------------------------------------------
|
|
42
|
+
/**
|
|
43
|
+
* Read an {@link AccessBundleSpec} back out of a class decorated with `@AccessBundle`.
|
|
44
|
+
* Takes the CONSTRUCTOR (`Function`); never instantiates it.
|
|
45
|
+
*
|
|
46
|
+
* Throws {@link RegistrationError} when the class is undecorated or the options are
|
|
47
|
+
* empty (missing `key` / `module` / `label` / `permissions`). Assembles the result
|
|
48
|
+
* field-by-field with an explicit omit guard on `description` (omit-when-absent, mirror
|
|
49
|
+
* `reflectObjectType`).
|
|
50
|
+
*/
|
|
51
|
+
function reflectAccessBundle(cls) {
|
|
52
|
+
const opts = Reflect.getMetadata(ACCESS_BUNDLE_META, cls);
|
|
53
|
+
if (!opts) {
|
|
54
|
+
throw new errors_js_1.RegistrationError(`${cls.name} is not decorated with @AccessBundle`);
|
|
55
|
+
}
|
|
56
|
+
if (!opts.key || !opts.module || !opts.label) {
|
|
57
|
+
throw new errors_js_1.RegistrationError(`${cls.name} @AccessBundle is missing a required 'key', 'module', or 'label'`);
|
|
58
|
+
}
|
|
59
|
+
if (!opts.permissions || opts.permissions.length === 0) {
|
|
60
|
+
throw new errors_js_1.RegistrationError(`${cls.name} @AccessBundle declares no permissions`);
|
|
61
|
+
}
|
|
62
|
+
// Expand to flat atomic keys, de-duped preserving first-seen order (Set keeps
|
|
63
|
+
// insertion order, and re-inserting an existing key is a no-op).
|
|
64
|
+
const permissionKeys = [
|
|
65
|
+
...new Set(opts.permissions.flatMap((p) => p.actions.map((a) => `${opts.module}:${p.typeKey}:${a}`))),
|
|
66
|
+
];
|
|
67
|
+
const out = {
|
|
68
|
+
key: opts.key,
|
|
69
|
+
module: opts.module,
|
|
70
|
+
label: opts.label,
|
|
71
|
+
};
|
|
72
|
+
if (opts.description !== undefined)
|
|
73
|
+
out.description = opts.description;
|
|
74
|
+
out.permissionKeys = permissionKeys;
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-bundle.decorator.js","sourceRoot":"","sources":["../../src/decorators/access-bundle.decorator.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;AAmDH,oCAIC;AAaD,kDAsCC;AAxGD,4BAA0B;AAE1B,4CAAiD;AAEjD,kFAAkF;AAElF,8EAA8E;AAC9E,MAAM,kBAAkB,GAAG,MAAM,CAAC,4BAA4B,CAAC,CAAC;AAmChE,gFAAgF;AAEhF;;;;GAIG;AACH,SAAgB,YAAY,CAAC,IAAyB;IACpD,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,OAAO,CAAC,cAAc,CAAC,kBAAkB,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAC3D,CAAC,CAAC;AACJ,CAAC;AAED,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,SAAgB,mBAAmB,CAAC,GAAa;IAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,kBAAkB,EAAE,GAAG,CAE3C,CAAC;IACd,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,6BAAiB,CACzB,GAAG,GAAG,CAAC,IAAI,sCAAsC,CAClD,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAC7C,MAAM,IAAI,6BAAiB,CACzB,GAAG,GAAG,CAAC,IAAI,kEAAkE,CAC9E,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,6BAAiB,CACzB,GAAG,GAAG,CAAC,IAAI,wCAAwC,CACpD,CAAC;IACJ,CAAC;IAED,8EAA8E;IAC9E,iEAAiE;IACjE,MAAM,cAAc,GAAG;QACrB,GAAG,IAAI,GAAG,CACR,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAC7B,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,EAAE,CAAC,CACzD,CACF;KACF,CAAC;IAEF,MAAM,GAAG,GAA4B;QACnC,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC;IACF,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;QAAE,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;IACvE,GAAG,CAAC,cAAc,GAAG,cAAc,CAAC;IACpC,OAAO,GAAkC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ObjectType` / `@Field` / `@PrimaryKey` — the decorator layer that lets a
|
|
3
|
+
* feature author an {@link ObjectType} contract as a decorated (metadata-only)
|
|
4
|
+
* class instead of a plain literal, and {@link reflectObjectType} that reads it
|
|
5
|
+
* back into the exact same {@link ObjectType} shape.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the `@Classified` pattern in @astram/guard (SetMetadata-style
|
|
8
|
+
* `Reflect.defineMetadata` + a private metadata-key), but uses `reflect-metadata`
|
|
9
|
+
* DIRECTLY (bare `Reflect.defineMetadata` — no Nest dependency needed). The engine's
|
|
10
|
+
* tsconfig already has `experimentalDecorators` + `emitDecoratorMetadata` ON and
|
|
11
|
+
* `reflect-metadata` present, so the polyfill is active.
|
|
12
|
+
*
|
|
13
|
+
* IMPORTANT — the decorated class is a METADATA CARRIER ONLY. It is NEVER
|
|
14
|
+
* instantiated: {@link reflectObjectType} takes the CONSTRUCTOR (`Function`), reads
|
|
15
|
+
* three `Reflect` keys off it, and never calls `new cls()`. No instance is created,
|
|
16
|
+
* no field initializers run.
|
|
17
|
+
*
|
|
18
|
+
* GOLDEN INVARIANT — the reflected output must `deepStrictEqual` the equivalent
|
|
19
|
+
* literal INCLUDING the omit-when-absent convention (the literals OMIT
|
|
20
|
+
* `junctions` / `extensible` / `required:false` / absent `trusted` / absent
|
|
21
|
+
* `column` rather than setting `undefined` / `false`). The reflector therefore
|
|
22
|
+
* assembles the result field-by-field with explicit omit guards — NOT by spreading
|
|
23
|
+
* the whole options blob (a spread would carry `junctions` / `extensible` as
|
|
24
|
+
* `undefined` keys and break `deepStrictEqual`).
|
|
25
|
+
*/
|
|
26
|
+
import 'reflect-metadata';
|
|
27
|
+
import type { AccessDeclaration, FieldType, IdentityRole, JunctionSpec, ObjectType as ObjectTypeContract, PkStrategy, TrustedSource } from '../types.js';
|
|
28
|
+
/**
|
|
29
|
+
* Class-level options for `@ObjectType`. Mirrors the top-level {@link ObjectType}
|
|
30
|
+
* shape EXCEPT `primaryKey` (declared separately via `@PrimaryKey`) and `fields`
|
|
31
|
+
* (declared per-member via `@Field`).
|
|
32
|
+
*
|
|
33
|
+
* `junctions` is a CLASS-LEVEL option — a junction is an object-owned side-table
|
|
34
|
+
* (keyed by name), not a per-field concept, so it has no home member.
|
|
35
|
+
*/
|
|
36
|
+
export interface ObjectTypeOptions {
|
|
37
|
+
readonly typeKey: string;
|
|
38
|
+
readonly table: string;
|
|
39
|
+
readonly schema?: string;
|
|
40
|
+
readonly extensible?: boolean;
|
|
41
|
+
readonly junctions?: readonly JunctionSpec[];
|
|
42
|
+
/**
|
|
43
|
+
* REQUIRED access declaration (tenantScoped/owned/system). Reflected AS-IS onto the
|
|
44
|
+
* {@link ObjectType} contract; the REGISTRY's `validate` (chokepoint 1) rejects an
|
|
45
|
+
* absent/contradictory declaration at boot. See {@link AccessDeclaration}.
|
|
46
|
+
*/
|
|
47
|
+
readonly access: AccessDeclaration;
|
|
48
|
+
}
|
|
49
|
+
/** Per-member options for `@Field` (the field's `key` is read from the member name). */
|
|
50
|
+
export interface FieldOptions {
|
|
51
|
+
readonly column?: string;
|
|
52
|
+
readonly type: FieldType;
|
|
53
|
+
readonly required?: boolean;
|
|
54
|
+
readonly trusted?: TrustedSource;
|
|
55
|
+
readonly role?: IdentityRole;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* `@PrimaryKey` options — the {@link PkStrategy} discriminated union verbatim, so it
|
|
59
|
+
* is stored + assigned AS-IS (no reconstruction), keeping the `column`-vs-`columns`
|
|
60
|
+
* key set byte-identical to the literal's union member.
|
|
61
|
+
*/
|
|
62
|
+
export type PrimaryKeyOptions = PkStrategy;
|
|
63
|
+
/**
|
|
64
|
+
* `@ObjectType({ typeKey, table, access, schema?, junctions?, extensible? })` —
|
|
65
|
+
* declare the class-level object-type metadata. Stores the options verbatim under
|
|
66
|
+
* {@link OBJECT_TYPE_META} on the constructor. `access` is REQUIRED (the registry's
|
|
67
|
+
* `validate` — chokepoint 1 — rejects an absent/contradictory declaration at boot).
|
|
68
|
+
*
|
|
69
|
+
* The generic, framework-neutral decorator name. (The `ObjectType` result data type is
|
|
70
|
+
* referenced internally as `ObjectTypeContract` so the decorator can own this name.)
|
|
71
|
+
*/
|
|
72
|
+
export declare function ObjectType(opts: ObjectTypeOptions): ClassDecorator;
|
|
73
|
+
/**
|
|
74
|
+
* DECLARATION MERGING — makes `ObjectType` carry BOTH meanings under one name:
|
|
75
|
+
* - the `function ObjectType(...)` above is the VALUE (the class decorator), and
|
|
76
|
+
* - this `interface ObjectType` (extending the data-shape contract) is the TYPE.
|
|
77
|
+
* A `function` + `interface` of the same name declaration-merge in TypeScript, so
|
|
78
|
+
* `import { ObjectType }` (decorator/value) AND `import type { ObjectType }` (the
|
|
79
|
+
* data shape) both resolve to this single, merged export. This is the mechanism that
|
|
80
|
+
* lets the barrel export ONE `ObjectType` name that is simultaneously the feature-
|
|
81
|
+
* facing decorator and the metadata contract type. `reflectObjectType` keeps
|
|
82
|
+
* returning `ObjectTypeContract`, which is structurally identical to this merged type.
|
|
83
|
+
*/
|
|
84
|
+
export interface ObjectType extends ObjectTypeContract {
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* `@Field({ type, column?, required?, trusted?, role? })` — declare one core field.
|
|
88
|
+
* The field's `key` is the decorated member's property name. Each invocation APPENDS a
|
|
89
|
+
* {@link FieldSpec} to the {@link FIELDS_META} array on the CONSTRUCTOR
|
|
90
|
+
* (`target.constructor` — `target` is the prototype for instance members).
|
|
91
|
+
*
|
|
92
|
+
* ORDER GUARANTEE: property decorators execute in SOURCE (top-to-bottom) member
|
|
93
|
+
* order at class-definition time, and each call read-existing → push → redefine, so
|
|
94
|
+
* the array accumulates in declaration order === the literal's `fields[]` order.
|
|
95
|
+
*/
|
|
96
|
+
export declare function Field(opts: FieldOptions): PropertyDecorator;
|
|
97
|
+
/**
|
|
98
|
+
* `@PrimaryKey({ kind:'uuid', column } | { kind:'composite', columns:[...] })` — a
|
|
99
|
+
* SINGLE class-level decorator (never per-member). Stores the options object
|
|
100
|
+
* verbatim under {@link PK_META}; {@link reflectObjectType} assigns it directly to
|
|
101
|
+
* `primaryKey` (no reconstruction), so the result is byte-identical to the literal's
|
|
102
|
+
* {@link PkStrategy} union member. Authoring `columns` as an explicit literal array
|
|
103
|
+
* removes any member-ordering ambiguity for the composite case.
|
|
104
|
+
*/
|
|
105
|
+
export declare function PrimaryKey(opts: PrimaryKeyOptions): ClassDecorator;
|
|
106
|
+
/**
|
|
107
|
+
* Read an {@link ObjectType} back out of a class decorated with `@ObjectType` +
|
|
108
|
+
* `@PrimaryKey` + one-or-more `@Field`. Takes the CONSTRUCTOR (`Function`); never
|
|
109
|
+
* instantiates it.
|
|
110
|
+
*
|
|
111
|
+
* The result is assembled field-by-field with explicit omit guards so it
|
|
112
|
+
* `deepStrictEqual`s the equivalent literal (omit-when-absent convention). Key order
|
|
113
|
+
* mirrors the literals for readability/JSON parity (irrelevant to `deepStrictEqual`).
|
|
114
|
+
*/
|
|
115
|
+
export declare function reflectObjectType(cls: Function): ObjectTypeContract;
|
|
116
|
+
//# sourceMappingURL=object-type.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-type.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/object-type.decorator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EACV,iBAAiB,EAEjB,SAAS,EACT,YAAY,EACZ,YAAY,EAKZ,UAAU,IAAI,kBAAkB,EAChC,UAAU,EACV,aAAa,EACd,MAAM,aAAa,CAAC;AAarB;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;CACpC;AAED,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,CAAC;AAI3C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,cAAc,CAIlE;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAW,SAAQ,kBAAkB;CAAG;AAEzD;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,iBAAiB,CAQ3D;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,cAAc,CAIlE;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,QAAQ,GAAG,kBAAkB,CAqCnE"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@ObjectType` / `@Field` / `@PrimaryKey` — the decorator layer that lets a
|
|
4
|
+
* feature author an {@link ObjectType} contract as a decorated (metadata-only)
|
|
5
|
+
* class instead of a plain literal, and {@link reflectObjectType} that reads it
|
|
6
|
+
* back into the exact same {@link ObjectType} shape.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors the `@Classified` pattern in @astram/guard (SetMetadata-style
|
|
9
|
+
* `Reflect.defineMetadata` + a private metadata-key), but uses `reflect-metadata`
|
|
10
|
+
* DIRECTLY (bare `Reflect.defineMetadata` — no Nest dependency needed). The engine's
|
|
11
|
+
* tsconfig already has `experimentalDecorators` + `emitDecoratorMetadata` ON and
|
|
12
|
+
* `reflect-metadata` present, so the polyfill is active.
|
|
13
|
+
*
|
|
14
|
+
* IMPORTANT — the decorated class is a METADATA CARRIER ONLY. It is NEVER
|
|
15
|
+
* instantiated: {@link reflectObjectType} takes the CONSTRUCTOR (`Function`), reads
|
|
16
|
+
* three `Reflect` keys off it, and never calls `new cls()`. No instance is created,
|
|
17
|
+
* no field initializers run.
|
|
18
|
+
*
|
|
19
|
+
* GOLDEN INVARIANT — the reflected output must `deepStrictEqual` the equivalent
|
|
20
|
+
* literal INCLUDING the omit-when-absent convention (the literals OMIT
|
|
21
|
+
* `junctions` / `extensible` / `required:false` / absent `trusted` / absent
|
|
22
|
+
* `column` rather than setting `undefined` / `false`). The reflector therefore
|
|
23
|
+
* assembles the result field-by-field with explicit omit guards — NOT by spreading
|
|
24
|
+
* the whole options blob (a spread would carry `junctions` / `extensible` as
|
|
25
|
+
* `undefined` keys and break `deepStrictEqual`).
|
|
26
|
+
*/
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.ObjectType = ObjectType;
|
|
29
|
+
exports.Field = Field;
|
|
30
|
+
exports.PrimaryKey = PrimaryKey;
|
|
31
|
+
exports.reflectObjectType = reflectObjectType;
|
|
32
|
+
require("reflect-metadata");
|
|
33
|
+
const errors_js_1 = require("../errors.js");
|
|
34
|
+
// --- Private metadata keys (Symbols) — mirror CLASSIFICATION_METADATA ---------
|
|
35
|
+
/** Class-level: the {@link ObjectTypeOptions} passed to `@ObjectType`. */
|
|
36
|
+
const OBJECT_TYPE_META = Symbol('astram:engine:objectType');
|
|
37
|
+
/** Class-level: the {@link PrimaryKeyOptions} passed to `@PrimaryKey`. */
|
|
38
|
+
const PK_META = Symbol('astram:engine:primaryKey');
|
|
39
|
+
/** Constructor-level: the accumulated array of {@link FieldSpec} from `@Field`. */
|
|
40
|
+
const FIELDS_META = Symbol('astram:engine:fields');
|
|
41
|
+
// --- Decorators --------------------------------------------------------------
|
|
42
|
+
/**
|
|
43
|
+
* `@ObjectType({ typeKey, table, access, schema?, junctions?, extensible? })` —
|
|
44
|
+
* declare the class-level object-type metadata. Stores the options verbatim under
|
|
45
|
+
* {@link OBJECT_TYPE_META} on the constructor. `access` is REQUIRED (the registry's
|
|
46
|
+
* `validate` — chokepoint 1 — rejects an absent/contradictory declaration at boot).
|
|
47
|
+
*
|
|
48
|
+
* The generic, framework-neutral decorator name. (The `ObjectType` result data type is
|
|
49
|
+
* referenced internally as `ObjectTypeContract` so the decorator can own this name.)
|
|
50
|
+
*/
|
|
51
|
+
function ObjectType(opts) {
|
|
52
|
+
return (target) => {
|
|
53
|
+
Reflect.defineMetadata(OBJECT_TYPE_META, opts, target);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* `@Field({ type, column?, required?, trusted?, role? })` — declare one core field.
|
|
58
|
+
* The field's `key` is the decorated member's property name. Each invocation APPENDS a
|
|
59
|
+
* {@link FieldSpec} to the {@link FIELDS_META} array on the CONSTRUCTOR
|
|
60
|
+
* (`target.constructor` — `target` is the prototype for instance members).
|
|
61
|
+
*
|
|
62
|
+
* ORDER GUARANTEE: property decorators execute in SOURCE (top-to-bottom) member
|
|
63
|
+
* order at class-definition time, and each call read-existing → push → redefine, so
|
|
64
|
+
* the array accumulates in declaration order === the literal's `fields[]` order.
|
|
65
|
+
*/
|
|
66
|
+
function Field(opts) {
|
|
67
|
+
return (target, propertyKey) => {
|
|
68
|
+
const ctor = target.constructor;
|
|
69
|
+
const existing = Reflect.getMetadata(FIELDS_META, ctor) ?? [];
|
|
70
|
+
const spec = { key: String(propertyKey), ...opts };
|
|
71
|
+
Reflect.defineMetadata(FIELDS_META, [...existing, spec], ctor);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* `@PrimaryKey({ kind:'uuid', column } | { kind:'composite', columns:[...] })` — a
|
|
76
|
+
* SINGLE class-level decorator (never per-member). Stores the options object
|
|
77
|
+
* verbatim under {@link PK_META}; {@link reflectObjectType} assigns it directly to
|
|
78
|
+
* `primaryKey` (no reconstruction), so the result is byte-identical to the literal's
|
|
79
|
+
* {@link PkStrategy} union member. Authoring `columns` as an explicit literal array
|
|
80
|
+
* removes any member-ordering ambiguity for the composite case.
|
|
81
|
+
*/
|
|
82
|
+
function PrimaryKey(opts) {
|
|
83
|
+
return (target) => {
|
|
84
|
+
Reflect.defineMetadata(PK_META, opts, target);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// --- Reflector ---------------------------------------------------------------
|
|
88
|
+
/**
|
|
89
|
+
* Rebuild one {@link FieldSpec} from its stored spec, applying the per-field
|
|
90
|
+
* omit-when-absent rule so `required:false` / absent `trusted` / absent `role` /
|
|
91
|
+
* absent `column` never appear as keys (they must be OMITTED to `deepStrictEqual`
|
|
92
|
+
* the literals). Key order kept `{ key, column, type, required?, trusted?, role? }`
|
|
93
|
+
* to mirror the literals.
|
|
94
|
+
*/
|
|
95
|
+
function reflectField(spec) {
|
|
96
|
+
const f = { key: spec.key };
|
|
97
|
+
if (spec.column !== undefined)
|
|
98
|
+
f.column = spec.column;
|
|
99
|
+
f.type = spec.type;
|
|
100
|
+
if (spec.required === true)
|
|
101
|
+
f.required = true;
|
|
102
|
+
if (spec.trusted !== undefined)
|
|
103
|
+
f.trusted = spec.trusted;
|
|
104
|
+
if (spec.role !== undefined)
|
|
105
|
+
f.role = spec.role;
|
|
106
|
+
return f;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Read an {@link ObjectType} back out of a class decorated with `@ObjectType` +
|
|
110
|
+
* `@PrimaryKey` + one-or-more `@Field`. Takes the CONSTRUCTOR (`Function`); never
|
|
111
|
+
* instantiates it.
|
|
112
|
+
*
|
|
113
|
+
* The result is assembled field-by-field with explicit omit guards so it
|
|
114
|
+
* `deepStrictEqual`s the equivalent literal (omit-when-absent convention). Key order
|
|
115
|
+
* mirrors the literals for readability/JSON parity (irrelevant to `deepStrictEqual`).
|
|
116
|
+
*/
|
|
117
|
+
function reflectObjectType(cls) {
|
|
118
|
+
const opts = Reflect.getMetadata(OBJECT_TYPE_META, cls);
|
|
119
|
+
if (!opts) {
|
|
120
|
+
throw new errors_js_1.RegistrationError(`${cls.name} is not decorated with @ObjectType`);
|
|
121
|
+
}
|
|
122
|
+
const pk = Reflect.getMetadata(PK_META, cls);
|
|
123
|
+
if (!pk) {
|
|
124
|
+
throw new errors_js_1.RegistrationError(`${cls.name} is not decorated with @PrimaryKey`);
|
|
125
|
+
}
|
|
126
|
+
const fields = Reflect.getMetadata(FIELDS_META, cls) ?? [];
|
|
127
|
+
if (fields.length === 0) {
|
|
128
|
+
throw new errors_js_1.RegistrationError(`${cls.name} declares no @Field members`);
|
|
129
|
+
}
|
|
130
|
+
const out = { typeKey: opts.typeKey, table: opts.table };
|
|
131
|
+
if (opts.schema !== undefined)
|
|
132
|
+
out.schema = opts.schema;
|
|
133
|
+
out.primaryKey = pk; // assigned AS-IS — no reconstruction (union key set preserved)
|
|
134
|
+
out.fields = fields.map(reflectField);
|
|
135
|
+
if (opts.junctions !== undefined)
|
|
136
|
+
out.junctions = opts.junctions;
|
|
137
|
+
if (opts.extensible !== undefined)
|
|
138
|
+
out.extensible = opts.extensible;
|
|
139
|
+
// `access` is REQUIRED on the contract, but reflected with the SAME
|
|
140
|
+
// omit-when-absent discipline as junctions/extensible (assigned AS-IS). If a
|
|
141
|
+
// decorator omits it, the reflected literal likewise omits the key — and the
|
|
142
|
+
// registry's `validate` (chokepoint 1) is what rejects the absence at boot.
|
|
143
|
+
if (opts.access !== undefined)
|
|
144
|
+
out.access = opts.access;
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-type.decorator.js","sourceRoot":"","sources":["../../src/decorators/object-type.decorator.ts"],"names":[],"mappings":"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"}
|