metal-orm 1.1.2 → 1.1.3
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 +13 -4
- package/dist/index.cjs +158 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -1
- package/dist/index.d.ts +108 -1
- package/dist/index.js +163 -0
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/src/cache/adapters/keyv-cache-adapter.ts +5 -0
- package/src/cache/adapters/memory-cache-adapter.ts +5 -0
- package/src/cache/adapters/redis-cache-adapter.ts +233 -0
- package/src/cache/cache-interfaces.ts +11 -0
- package/src/cache/index.ts +2 -0
package/README.md
CHANGED
|
@@ -131,7 +131,7 @@ On top of the query builder, MetalORM ships a focused runtime managed by `Orm` a
|
|
|
131
131
|
- **Lazy, batched relations**: `user.posts.load()`, `user.roles.syncByIds([...])`, etc.
|
|
132
132
|
- **Scoped transactions**: `session.transaction(async s => { ... })` wraps `begin/commit/rollback` on the existing executor; `Orm.transaction` remains available when you want a fresh transactional executor per call.
|
|
133
133
|
- **Identity map**: the same row becomes the same entity instance within a session (see the [Identity map pattern](https://en.wikipedia.org/wiki/Identity_map_pattern)).
|
|
134
|
-
- **Caching**: Flexible caching with `MemoryCacheAdapter` (dev)
|
|
134
|
+
- **Caching**: Flexible caching with `MemoryCacheAdapter` (dev), `KeyvCacheAdapter` (simple production), or `RedisCacheAdapter` (full-featured with tag support). Features human-readable TTL (`'30m'`, `'2h'`), tag-based invalidation, and multi-tenant cache isolation.
|
|
135
135
|
- **Tree Behavior (Nested Set/MPTT)**: hierarchical data with `TreeManager`, `treeQuery()`, and `@Tree` decorators. Efficient O(log n) operations for moves, inserts, and deletes. Supports multi-tree scoping, recovery, and validation.
|
|
136
136
|
- **DTO/OpenAPI helpers**: the `metal-orm/dto` module generates DTOs and OpenAPI schemas, including tree schemas (`TreeNode`, `TreeNodeResult`, threaded trees).
|
|
137
137
|
- **Unit of Work (`OrmSession`)** tracking New/Dirty/Removed entities and relation changes, inspired by the classic [Unit of Work pattern](https://en.wikipedia.org/wiki/wiki/Unit_of_work).
|
|
@@ -195,15 +195,24 @@ Pick the matching dialect (`MySqlDialect`, `SQLiteDialect`, `PostgresDialect`, `
|
|
|
195
195
|
|
|
196
196
|
> Drivers are declared as optional peer dependencies. Install only the ones you actually use in your project.
|
|
197
197
|
|
|
198
|
-
**Optional: Caching
|
|
198
|
+
**Optional: Caching Backends**
|
|
199
199
|
|
|
200
|
-
For production caching
|
|
200
|
+
For production caching, choose based on your needs:
|
|
201
|
+
|
|
202
|
+
| Adapter | Tags | Install | Use Case |
|
|
203
|
+
|---------|------|---------|----------|
|
|
204
|
+
| `RedisCacheAdapter` | ✅ Full support | `npm install ioredis` | Production with tag invalidation |
|
|
205
|
+
| `KeyvCacheAdapter` | ❌ Not supported | `npm install keyv @keyv/redis` | Simple production setups |
|
|
201
206
|
|
|
202
207
|
```bash
|
|
208
|
+
# For full-featured Redis (recommended)
|
|
209
|
+
npm install ioredis
|
|
210
|
+
|
|
211
|
+
# For simple Keyv-based caching
|
|
203
212
|
npm install keyv @keyv/redis
|
|
204
213
|
```
|
|
205
214
|
|
|
206
|
-
>
|
|
215
|
+
> Caching packages are optional peer dependencies. MetalORM includes `MemoryCacheAdapter` for development without external dependencies.
|
|
207
216
|
|
|
208
217
|
### Playground (optional) 🧪
|
|
209
218
|
|
package/dist/index.cjs
CHANGED
|
@@ -89,6 +89,7 @@ __export(index_exports, {
|
|
|
89
89
|
PrimaryKey: () => PrimaryKey,
|
|
90
90
|
PrototypeMaterializationStrategy: () => PrototypeMaterializationStrategy,
|
|
91
91
|
QueryCacheManager: () => QueryCacheManager,
|
|
92
|
+
RedisCacheAdapter: () => RedisCacheAdapter,
|
|
92
93
|
RelationKinds: () => RelationKinds,
|
|
93
94
|
STANDARD_COLUMN_TYPES: () => STANDARD_COLUMN_TYPES,
|
|
94
95
|
SelectQueryBuilder: () => SelectQueryBuilder,
|
|
@@ -14009,6 +14010,11 @@ function isValidDuration(value) {
|
|
|
14009
14010
|
// src/cache/adapters/memory-cache-adapter.ts
|
|
14010
14011
|
var MemoryCacheAdapter = class {
|
|
14011
14012
|
name = "memory";
|
|
14013
|
+
capabilities = {
|
|
14014
|
+
tags: true,
|
|
14015
|
+
prefix: true,
|
|
14016
|
+
ttl: true
|
|
14017
|
+
};
|
|
14012
14018
|
storage = /* @__PURE__ */ new Map();
|
|
14013
14019
|
tagIndex = /* @__PURE__ */ new Map();
|
|
14014
14020
|
async get(key) {
|
|
@@ -18862,6 +18868,11 @@ var KeyvCacheAdapter = class {
|
|
|
18862
18868
|
this.keyv = keyv;
|
|
18863
18869
|
}
|
|
18864
18870
|
name = "keyv";
|
|
18871
|
+
capabilities = {
|
|
18872
|
+
tags: false,
|
|
18873
|
+
prefix: true,
|
|
18874
|
+
ttl: true
|
|
18875
|
+
};
|
|
18865
18876
|
async get(key) {
|
|
18866
18877
|
return this.keyv.get(key);
|
|
18867
18878
|
}
|
|
@@ -18905,6 +18916,152 @@ var KeyvCacheAdapter = class {
|
|
|
18905
18916
|
}
|
|
18906
18917
|
};
|
|
18907
18918
|
|
|
18919
|
+
// src/cache/adapters/redis-cache-adapter.ts
|
|
18920
|
+
var RedisCacheAdapter = class {
|
|
18921
|
+
name = "redis";
|
|
18922
|
+
capabilities = {
|
|
18923
|
+
tags: true,
|
|
18924
|
+
prefix: true,
|
|
18925
|
+
ttl: true
|
|
18926
|
+
};
|
|
18927
|
+
redis;
|
|
18928
|
+
ownsConnection;
|
|
18929
|
+
tagPrefix;
|
|
18930
|
+
/**
|
|
18931
|
+
* Cria um adapter Redis
|
|
18932
|
+
*
|
|
18933
|
+
* @param redis - Instância do ioredis OU opções de conexão
|
|
18934
|
+
* @param options - Opções adicionais
|
|
18935
|
+
* @param options.tagPrefix - Prefixo para chaves de tag (default: 'tag:')
|
|
18936
|
+
*
|
|
18937
|
+
* Exemplos:
|
|
18938
|
+
*
|
|
18939
|
+
* // Com instância existente (recomendado para connection pooling):
|
|
18940
|
+
* const redis = new Redis({ host: 'localhost', port: 6379 });
|
|
18941
|
+
* const adapter = new RedisCacheAdapter(redis);
|
|
18942
|
+
*
|
|
18943
|
+
* // Com opções (adapter gerencia conexão):
|
|
18944
|
+
* const adapter = new RedisCacheAdapter({ host: 'localhost', port: 6379 });
|
|
18945
|
+
*
|
|
18946
|
+
* // Para testes com ioredis-mock:
|
|
18947
|
+
* import Redis from 'ioredis-mock';
|
|
18948
|
+
* const adapter = new RedisCacheAdapter(new Redis());
|
|
18949
|
+
*/
|
|
18950
|
+
constructor(redis, options) {
|
|
18951
|
+
this.tagPrefix = options?.tagPrefix ?? "tag:";
|
|
18952
|
+
if (this.isRedisInstance(redis)) {
|
|
18953
|
+
this.redis = redis;
|
|
18954
|
+
this.ownsConnection = false;
|
|
18955
|
+
} else {
|
|
18956
|
+
this.redis = this.createRedis(redis);
|
|
18957
|
+
this.ownsConnection = true;
|
|
18958
|
+
}
|
|
18959
|
+
}
|
|
18960
|
+
isRedisInstance(obj) {
|
|
18961
|
+
return typeof obj === "object" && obj !== null && "get" in obj && "set" in obj && "del" in obj && typeof obj.get === "function";
|
|
18962
|
+
}
|
|
18963
|
+
createRedis(options) {
|
|
18964
|
+
try {
|
|
18965
|
+
const Redis = require("ioredis");
|
|
18966
|
+
return new Redis(options);
|
|
18967
|
+
} catch {
|
|
18968
|
+
throw new Error(
|
|
18969
|
+
"ioredis is required for RedisCacheAdapter. Install it with: npm install ioredis"
|
|
18970
|
+
);
|
|
18971
|
+
}
|
|
18972
|
+
}
|
|
18973
|
+
async get(key) {
|
|
18974
|
+
const value = await this.redis.get(key);
|
|
18975
|
+
if (value === null) {
|
|
18976
|
+
return void 0;
|
|
18977
|
+
}
|
|
18978
|
+
try {
|
|
18979
|
+
return JSON.parse(value);
|
|
18980
|
+
} catch {
|
|
18981
|
+
return void 0;
|
|
18982
|
+
}
|
|
18983
|
+
}
|
|
18984
|
+
async has(key) {
|
|
18985
|
+
const value = await this.redis.get(key);
|
|
18986
|
+
return value !== null;
|
|
18987
|
+
}
|
|
18988
|
+
async set(key, value, ttlMs, tags) {
|
|
18989
|
+
const serialized = JSON.stringify(value);
|
|
18990
|
+
if (ttlMs) {
|
|
18991
|
+
await this.redis.set(key, serialized, "PX", ttlMs);
|
|
18992
|
+
} else {
|
|
18993
|
+
await this.redis.set(key, serialized);
|
|
18994
|
+
}
|
|
18995
|
+
if (tags && tags.length > 0) {
|
|
18996
|
+
await this.registerTags(key, tags);
|
|
18997
|
+
}
|
|
18998
|
+
}
|
|
18999
|
+
async delete(key) {
|
|
19000
|
+
await this.redis.del(key);
|
|
19001
|
+
}
|
|
19002
|
+
async invalidate(key) {
|
|
19003
|
+
await this.delete(key);
|
|
19004
|
+
}
|
|
19005
|
+
async invalidateTags(tags) {
|
|
19006
|
+
const keysToDelete = /* @__PURE__ */ new Set();
|
|
19007
|
+
for (const tag of tags) {
|
|
19008
|
+
const tagKey = `${this.tagPrefix}${tag}`;
|
|
19009
|
+
const keys = await this.redis.smembers(tagKey);
|
|
19010
|
+
for (const key of keys) {
|
|
19011
|
+
keysToDelete.add(key);
|
|
19012
|
+
}
|
|
19013
|
+
await this.redis.del(tagKey);
|
|
19014
|
+
}
|
|
19015
|
+
if (keysToDelete.size > 0) {
|
|
19016
|
+
await this.redis.del(...Array.from(keysToDelete));
|
|
19017
|
+
}
|
|
19018
|
+
}
|
|
19019
|
+
async invalidatePrefix(prefix) {
|
|
19020
|
+
const keysToDelete = [];
|
|
19021
|
+
let cursor = "0";
|
|
19022
|
+
do {
|
|
19023
|
+
const [nextCursor, keys] = await this.redis.scan(
|
|
19024
|
+
cursor,
|
|
19025
|
+
"MATCH",
|
|
19026
|
+
`${prefix}*`,
|
|
19027
|
+
"COUNT",
|
|
19028
|
+
100
|
|
19029
|
+
);
|
|
19030
|
+
cursor = nextCursor;
|
|
19031
|
+
keysToDelete.push(...keys);
|
|
19032
|
+
} while (cursor !== "0");
|
|
19033
|
+
if (keysToDelete.length > 0) {
|
|
19034
|
+
const batchSize = 1e3;
|
|
19035
|
+
for (let i = 0; i < keysToDelete.length; i += batchSize) {
|
|
19036
|
+
const batch = keysToDelete.slice(i, i + batchSize);
|
|
19037
|
+
await this.redis.del(...batch);
|
|
19038
|
+
}
|
|
19039
|
+
}
|
|
19040
|
+
}
|
|
19041
|
+
async registerTags(key, tags) {
|
|
19042
|
+
for (const tag of tags) {
|
|
19043
|
+
const tagKey = `${this.tagPrefix}${tag}`;
|
|
19044
|
+
await this.redis.sadd(tagKey, key);
|
|
19045
|
+
}
|
|
19046
|
+
}
|
|
19047
|
+
async dispose() {
|
|
19048
|
+
if (this.ownsConnection) {
|
|
19049
|
+
try {
|
|
19050
|
+
await this.redis.quit();
|
|
19051
|
+
} catch {
|
|
19052
|
+
this.redis.disconnect?.();
|
|
19053
|
+
}
|
|
19054
|
+
}
|
|
19055
|
+
}
|
|
19056
|
+
/**
|
|
19057
|
+
* Retorna a instância Redis subjacente
|
|
19058
|
+
* Útil para operações avançadas ou health checks
|
|
19059
|
+
*/
|
|
19060
|
+
getRedis() {
|
|
19061
|
+
return this.redis;
|
|
19062
|
+
}
|
|
19063
|
+
};
|
|
19064
|
+
|
|
18908
19065
|
// src/cache/tag-index.ts
|
|
18909
19066
|
var TagIndex = class {
|
|
18910
19067
|
tagToKeys = /* @__PURE__ */ new Map();
|
|
@@ -19059,6 +19216,7 @@ var TagIndex = class {
|
|
|
19059
19216
|
PrimaryKey,
|
|
19060
19217
|
PrototypeMaterializationStrategy,
|
|
19061
19218
|
QueryCacheManager,
|
|
19219
|
+
RedisCacheAdapter,
|
|
19062
19220
|
RelationKinds,
|
|
19063
19221
|
STANDARD_COLUMN_TYPES,
|
|
19064
19222
|
SelectQueryBuilder,
|