@cqrs-ddd/core 0.2.0 → 0.2.1
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/README.md +14 -3
- package/dist/application/tenant-resolver.d.ts +20 -2
- package/dist/application/tenant-resolver.d.ts.map +1 -1
- package/dist/application/tenant-resolver.js +24 -3
- package/dist/application/tenant-resolver.js.map +1 -1
- package/dist/persistence/helpers/cache-key.helper.js +2 -2
- package/dist/persistence/helpers/cache-key.helper.js.map +1 -1
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ needs.
|
|
|
50
50
|
| Entry | Holds |
|
|
51
51
|
| --- | --- |
|
|
52
52
|
| `@cqrs-ddd/core/domain` | `AggregateRoot`, `RootEntity`, `RootEntitySnapshot`, `@Mutable`, `getMutableFields`, `@ApplyMutation`, `IEvent`, `DomainEvent`, `RootDomainEvent`, `deepCloneAndFreeze`, `textRule`, `numberRule`, `ValueViolation`, and the errors `DomainException`, `InvalidValueException`, `EntityNotFoundException`, `ConcurrencyConflictError`, `TransientOperationError` (with `isTransientOperationError`), `MissingTenantContextError`, `UnknownMutableFieldError` |
|
|
53
|
-
| `@cqrs-ddd/core/application` | `BaseCommand`, `BaseQuery`, `IQueryOptions`, `CommandBaseHandler`, the ports (`IDomainEventPublisher`, `ICommandRepository`, `IQueryRepository`, `IWriteSideAggregateRepository`, `ICache`, `IVersionedCache`, `isVersionedCache`), `
|
|
53
|
+
| `@cqrs-ddd/core/application` | `BaseCommand`, `BaseQuery`, `IQueryOptions`, `CommandBaseHandler`, the ports (`IDomainEventPublisher`, `ICommandRepository`, `IQueryRepository`, `IWriteSideAggregateRepository`, `ICache`, `IVersionedCache`, `isVersionedCache`), `requireTenant`, `setTenantResolver`, and the deprecated `requireTenantId` |
|
|
54
54
|
| `@cqrs-ddd/core/persistence` | the lifecycle decorators (`@PersistedWrite`, `@Cache`, `@AcknowledgePersisted`, `@MapPersistenceErrors`, `@FromCache`), `QueryRepository`, `CommandRepository`, `MemoryCache` and its injection token `CACHE_TOKEN`, `cacheKey`, `cacheKeyTemplate`, `isCacheNewer`, `toCacheSnapshot`, the mutation-barrier helpers, `consoleCacheLogger`, `safeWarn`, and the persistence dialect contract (`IPersistenceDialect`, `setPersistenceDialect`, `persistenceDialect`) |
|
|
55
55
|
| `@cqrs-ddd/core/http` | `domainErrorHttpStatus` |
|
|
56
56
|
| `@cqrs-ddd/core` | all of the above, plus the `Method` type |
|
|
@@ -585,8 +585,19 @@ requestTenant.run(tenantId, () => handle(request));
|
|
|
585
585
|
There is no shared namespace: a missing or empty tenant, or a resolver that returns
|
|
586
586
|
nothing, throws. A single-tenant deployment passes a fixed id or registers
|
|
587
587
|
`setTenantResolver(() => 'single')`. The same resolution is public as
|
|
588
|
-
`
|
|
589
|
-
tenant, such as an idempotency or rate-limit key
|
|
588
|
+
`requireTenant(purpose, source?)`, for any other key that must be partitioned by
|
|
589
|
+
tenant, such as an idempotency or rate-limit key, and for any code that needs the
|
|
590
|
+
current tenant and must fail when there is none:
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
import { requireTenant } from '@cqrs-ddd/core/application';
|
|
594
|
+
|
|
595
|
+
requireTenant('access token issuance'); // the resolver's tenant
|
|
596
|
+
requireTenant('users.create idempotency key', ctx); // ctx.tenantId, else the resolver's
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
`requireTenantId(source, purpose)` is deprecated: it takes the same arguments in the
|
|
600
|
+
reverse order and calls `requireTenant`.
|
|
590
601
|
|
|
591
602
|
`cacheKey` keys have the form `${tenant}:${resource}:v1:${sha256}`, hashed over a
|
|
592
603
|
key-sorted serialization of `[tenant, resource, conditions]`. The format is frozen, so
|
|
@@ -41,15 +41,33 @@ export declare function setTenantResolver(resolver: TenantResolver | undefined):
|
|
|
41
41
|
* idempotency and rate limits must never collapse several tenants into one
|
|
42
42
|
* shared namespace.
|
|
43
43
|
*
|
|
44
|
-
* @param source - The explicit tenant, or `undefined` to use the resolver.
|
|
45
44
|
* @param purpose - What the tenant is for, named in the error.
|
|
45
|
+
* @param source - The explicit tenant; omitted, the resolver's tenant.
|
|
46
46
|
* @returns The non-empty tenant id.
|
|
47
47
|
* @throws {MissingTenantContextError} When no tenant can be resolved.
|
|
48
48
|
*
|
|
49
49
|
* @example
|
|
50
50
|
* ```ts
|
|
51
|
+
* // The tenant of the running unit of work, through the resolver:
|
|
52
|
+
* const tenantId = requireTenant('access token issuance');
|
|
53
|
+
*
|
|
51
54
|
* // A key factory that receives a request or job context:
|
|
52
|
-
* const
|
|
55
|
+
* const keyTenant = requireTenant('users.create idempotency key', ctx);
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function requireTenant(purpose: string, source?: TenantSource): string;
|
|
59
|
+
/**
|
|
60
|
+
* Returns the tenant for a security-sensitive operation, or fails closed; see
|
|
61
|
+
* {@link requireTenant}, which it calls.
|
|
62
|
+
*
|
|
63
|
+
* @deprecated Use {@link requireTenant}, which takes the required `purpose`
|
|
64
|
+
* first and the optional `source` last.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* requireTenantId(ctx, 'users.create idempotency key');
|
|
69
|
+
* // becomes
|
|
70
|
+
* requireTenant('users.create idempotency key', ctx);
|
|
53
71
|
* ```
|
|
54
72
|
*/
|
|
55
73
|
export declare function requireTenantId(source: TenantSource | undefined, purpose: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-resolver.d.ts","sourceRoot":"","sources":["../../application/tenant-resolver.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAE/E;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,MAAM,GAAG,SAAS,CAAC;AAItD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,CAE5E;AAED
|
|
1
|
+
{"version":3,"file":"tenant-resolver.d.ts","sourceRoot":"","sources":["../../application/tenant-resolver.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAE/E;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,MAAM,GAAG,SAAS,CAAC;AAItD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,CAE5E;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,CAO5E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,YAAY,GAAG,SAAS,EAChC,OAAO,EAAE,MAAM,GACd,MAAM,CAER"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.setTenantResolver = setTenantResolver;
|
|
5
|
+
exports.requireTenant = requireTenant;
|
|
5
6
|
exports.requireTenantId = requireTenantId;
|
|
6
7
|
const missing_tenant_context_exception_1 = require("../domain/exceptions/missing-tenant-context.exception");
|
|
7
8
|
let tenantResolver;
|
|
@@ -38,18 +39,21 @@ function setTenantResolver(resolver) {
|
|
|
38
39
|
* idempotency and rate limits must never collapse several tenants into one
|
|
39
40
|
* shared namespace.
|
|
40
41
|
*
|
|
41
|
-
* @param source - The explicit tenant, or `undefined` to use the resolver.
|
|
42
42
|
* @param purpose - What the tenant is for, named in the error.
|
|
43
|
+
* @param source - The explicit tenant; omitted, the resolver's tenant.
|
|
43
44
|
* @returns The non-empty tenant id.
|
|
44
45
|
* @throws {MissingTenantContextError} When no tenant can be resolved.
|
|
45
46
|
*
|
|
46
47
|
* @example
|
|
47
48
|
* ```ts
|
|
49
|
+
* // The tenant of the running unit of work, through the resolver:
|
|
50
|
+
* const tenantId = requireTenant('access token issuance');
|
|
51
|
+
*
|
|
48
52
|
* // A key factory that receives a request or job context:
|
|
49
|
-
* const
|
|
53
|
+
* const keyTenant = requireTenant('users.create idempotency key', ctx);
|
|
50
54
|
* ```
|
|
51
55
|
*/
|
|
52
|
-
function
|
|
56
|
+
function requireTenant(purpose, source) {
|
|
53
57
|
const tenantId = typeof source === 'string'
|
|
54
58
|
? source
|
|
55
59
|
: (source?.tenantId ?? tenantResolver?.());
|
|
@@ -57,4 +61,21 @@ function requireTenantId(source, purpose) {
|
|
|
57
61
|
throw new missing_tenant_context_exception_1.MissingTenantContextError(purpose);
|
|
58
62
|
return tenantId;
|
|
59
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Returns the tenant for a security-sensitive operation, or fails closed; see
|
|
66
|
+
* {@link requireTenant}, which it calls.
|
|
67
|
+
*
|
|
68
|
+
* @deprecated Use {@link requireTenant}, which takes the required `purpose`
|
|
69
|
+
* first and the optional `source` last.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* requireTenantId(ctx, 'users.create idempotency key');
|
|
74
|
+
* // becomes
|
|
75
|
+
* requireTenant('users.create idempotency key', ctx);
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
function requireTenantId(source, purpose) {
|
|
79
|
+
return requireTenant(purpose, source);
|
|
80
|
+
}
|
|
60
81
|
//# sourceMappingURL=tenant-resolver.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-resolver.js","sourceRoot":"","sources":["../../application/tenant-resolver.ts"],"names":[],"mappings":";AAAA,sEAAsE;;AAsCtE,8CAEC;
|
|
1
|
+
{"version":3,"file":"tenant-resolver.js","sourceRoot":"","sources":["../../application/tenant-resolver.ts"],"names":[],"mappings":";AAAA,sEAAsE;;AAsCtE,8CAEC;AA0BD,sCAOC;AAgBD,0CAKC;AA5FD,4GAAkG;AAclG,IAAI,cAA0C,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAgB,iBAAiB,CAAC,QAAoC;IACpE,cAAc,GAAG,QAAQ,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAgB,aAAa,CAAC,OAAe,EAAE,MAAqB;IAClE,MAAM,QAAQ,GACZ,OAAO,MAAM,KAAK,QAAQ;QACxB,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,IAAI,cAAc,EAAE,EAAE,CAAC,CAAC;IAC/C,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,4DAAyB,CAAC,OAAO,CAAC,CAAC;IAC5D,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAgB,eAAe,CAC7B,MAAgC,EAChC,OAAe;IAEf,OAAO,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -51,7 +51,7 @@ function normalizeFilterConditions(conditions) {
|
|
|
51
51
|
* @throws {TypeError} When conditions contain non-serializable values.
|
|
52
52
|
*/
|
|
53
53
|
function cacheKey(resourceOrEntity, conditions, tenantOrContext) {
|
|
54
|
-
const schema = (0, tenant_resolver_1.
|
|
54
|
+
const schema = (0, tenant_resolver_1.requireTenant)('cache key derivation', tenantOrContext);
|
|
55
55
|
let resource;
|
|
56
56
|
if (typeof resourceOrEntity === 'string') {
|
|
57
57
|
resource = resourceOrEntity;
|
|
@@ -115,7 +115,7 @@ function cacheKeyTemplate(template, tenantOrContext) {
|
|
|
115
115
|
? source
|
|
116
116
|
: undefined;
|
|
117
117
|
const data = (ctx ? ctx.request : source);
|
|
118
|
-
const schema = (0, tenant_resolver_1.
|
|
118
|
+
const schema = (0, tenant_resolver_1.requireTenant)('cache key derivation', tenantOrContext ?? ctx);
|
|
119
119
|
const resolved = template.replace(/\{(\w+)(\?)?\}/g, (_, prop, optional) => {
|
|
120
120
|
const val = data?.[prop];
|
|
121
121
|
if (val !== undefined && val !== null) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-key.helper.js","sourceRoot":"","sources":["../../../persistence/helpers/cache-key.helper.ts"],"names":[],"mappings":";AAAA,sEAAsE;;AAwFtE,4BA8BC;AAyCD,4CAiCC;AA9LD,6CAAyC;AACzC,6DAA6E;AAC7E,uEAG2C;AA4B3C;;;;;;GAMG;AACH,SAAS,yBAAyB,CAChC,UAAmC;IAEnC,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;IAClD,MAAM,UAAU,GAA4B,EAAE,CAAC;IAE/C,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,SAAS;QACX,CAAC;QACD,IACE,OAAO,GAAG,KAAK,UAAU;YACzB,OAAO,GAAG,KAAK,QAAQ;YACvB,OAAO,GAAG,KAAK,QAAQ,EACvB,CAAC;YACD,MAAM,IAAI,SAAS,CACjB,4CAA4C,OAAO,GAAG,GAAG,CAC1D,CAAC;QACJ,CAAC;QACD,UAAU,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACxB,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAgB,QAAQ,CACtB,gBAAwC,EACxC,UAAmC,EACnC,eAAsC;IAEtC,MAAM,MAAM,GAAG,IAAA,
|
|
1
|
+
{"version":3,"file":"cache-key.helper.js","sourceRoot":"","sources":["../../../persistence/helpers/cache-key.helper.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cqrs-ddd/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Framework- and ORM-neutral DDD building blocks for TypeScript: aggregates and domain events, CQRS base classes and repository ports, and a revision-fenced repository cache, as separate entry points.",
|
|
5
5
|
"author": "Aristotelis",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -63,9 +63,7 @@
|
|
|
63
63
|
"@cqrs-ddd/uuidv7": "^0.2.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@biomejs/biome": "^2.5.14",
|
|
67
66
|
"@vitest/coverage-v8": "^4.1.11",
|
|
68
|
-
"tsx": "^4.23.15",
|
|
69
67
|
"typescript": "^6.0.3",
|
|
70
68
|
"vitest": "^4.1.11"
|
|
71
69
|
},
|