metal-orm 1.0.118 → 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/dist/index.js CHANGED
@@ -1268,7 +1268,7 @@ var Dialect = class _Dialect {
1268
1268
  params: [...ctx.params]
1269
1269
  };
1270
1270
  }
1271
- supportsReturning() {
1271
+ supportsDmlReturningClause() {
1272
1272
  return false;
1273
1273
  }
1274
1274
  /**
@@ -2293,7 +2293,7 @@ var PostgresDialect = class extends SqlDialectBase {
2293
2293
  const columns = this.formatReturningColumns(returning);
2294
2294
  return ` RETURNING ${columns}`;
2295
2295
  }
2296
- supportsReturning() {
2296
+ supportsDmlReturningClause() {
2297
2297
  return true;
2298
2298
  }
2299
2299
  /**
@@ -2608,7 +2608,7 @@ var SqliteDialect = class extends SqlDialectBase {
2608
2608
  return `${this.quoteIdentifier(column.name)}${alias}`;
2609
2609
  }).join(", ");
2610
2610
  }
2611
- supportsReturning() {
2611
+ supportsDmlReturningClause() {
2612
2612
  return true;
2613
2613
  }
2614
2614
  };
@@ -2799,8 +2799,21 @@ var SqlServerDialect = class extends SqlDialectBase {
2799
2799
  this.compileExpression.bind(this)
2800
2800
  );
2801
2801
  const whereClause = this.compileWhere(ast.where, ctx);
2802
- const returning = this.compileReturning(ast.returning, ctx);
2803
- return `DELETE ${this.quoteIdentifier(alias)} FROM ${target}${joins}${whereClause}${returning}`;
2802
+ const returning = this.compileOutputClause(ast.returning, "deleted");
2803
+ return `DELETE ${this.quoteIdentifier(alias)}${returning} FROM ${target}${joins}${whereClause}`;
2804
+ }
2805
+ compileUpdateAst(ast, ctx) {
2806
+ const target = this.compileTableReference(ast.table);
2807
+ const assignments = this.compileUpdateAssignments(ast.set, ast.table, ctx);
2808
+ const output = this.compileReturning(ast.returning, ctx);
2809
+ const fromClause = ast.from ? ` FROM ${this.compileFrom(ast.from, ctx)}` : "";
2810
+ const joins = ast.joins ? ast.joins.map((j) => {
2811
+ const table = this.compileFrom(j.table, ctx);
2812
+ const cond = this.compileExpression(j.condition, ctx);
2813
+ return ` ${j.kind} JOIN ${table} ON ${cond}`;
2814
+ }).join("") : "";
2815
+ const whereClause = this.compileWhere(ast.where, ctx);
2816
+ return `UPDATE ${target} SET ${assignments}${output}${fromClause}${joins}${whereClause}`;
2804
2817
  }
2805
2818
  compileSelectCoreForMssql(ast, ctx) {
2806
2819
  const columns = ast.columns.map((c) => {
@@ -2851,6 +2864,44 @@ var SqlServerDialect = class extends SqlDialectBase {
2851
2864
  }
2852
2865
  return pagination;
2853
2866
  }
2867
+ supportsDmlReturningClause() {
2868
+ return true;
2869
+ }
2870
+ compileReturning(returning, _ctx) {
2871
+ void _ctx;
2872
+ return this.compileOutputClause(returning, "inserted");
2873
+ }
2874
+ compileOutputClause(returning, prefix) {
2875
+ if (!returning || returning.length === 0) return "";
2876
+ const columns = returning.map((column) => {
2877
+ const colName = this.quoteIdentifier(column.name);
2878
+ const alias = column.alias ? ` AS ${this.quoteIdentifier(column.alias)}` : "";
2879
+ return `${prefix}.${colName}${alias}`;
2880
+ }).join(", ");
2881
+ return ` OUTPUT ${columns}`;
2882
+ }
2883
+ compileInsertAst(ast, ctx) {
2884
+ if (!ast.columns.length) {
2885
+ throw new Error("INSERT queries must specify columns.");
2886
+ }
2887
+ const table = this.compileTableName(ast.into);
2888
+ const columnList = ast.columns.map((column) => this.quoteIdentifier(column.name)).join(", ");
2889
+ const output = this.compileReturning(ast.returning, ctx);
2890
+ const source = this.compileInsertValues(ast, ctx);
2891
+ return `INSERT INTO ${table} (${columnList})${output} ${source}`;
2892
+ }
2893
+ compileInsertValues(ast, ctx) {
2894
+ const source = ast.source;
2895
+ if (source.type === "InsertValues") {
2896
+ if (!source.rows.length) {
2897
+ throw new Error("INSERT ... VALUES requires at least one row.");
2898
+ }
2899
+ const values = source.rows.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
2900
+ return `VALUES ${values}`;
2901
+ }
2902
+ const normalized = this.normalizeSelectAst(source.query);
2903
+ return this.compileSelectAst(normalized, ctx).trim();
2904
+ }
2854
2905
  compileCtes(ast, ctx) {
2855
2906
  if (!ast.ctes || ast.ctes.length === 0) return "";
2856
2907
  const defs = ast.ctes.map((cte) => {
@@ -7266,6 +7317,53 @@ var SelectRelationFacet = class {
7266
7317
  }
7267
7318
  };
7268
7319
 
7320
+ // src/query-builder/select/cache-facet.ts
7321
+ var CacheFacet = class {
7322
+ /**
7323
+ * Configura opções de cache no contexto
7324
+ */
7325
+ cache(context, options) {
7326
+ return {
7327
+ state: {
7328
+ ...context.state,
7329
+ options
7330
+ }
7331
+ };
7332
+ }
7333
+ /**
7334
+ * Obtém as opções de cache do contexto
7335
+ */
7336
+ getOptions(context) {
7337
+ return context.state.options;
7338
+ }
7339
+ /**
7340
+ * Verifica se há configuração de cache
7341
+ */
7342
+ hasCache(context) {
7343
+ return context.state.options !== void 0;
7344
+ }
7345
+ /**
7346
+ * Cria opções de cache a partir de parâmetros variados
7347
+ * API flexível para diferentes casos de uso
7348
+ */
7349
+ static createOptions(key, ttl, tagsOrConfig) {
7350
+ let tags;
7351
+ let autoInvalidate;
7352
+ if (Array.isArray(tagsOrConfig)) {
7353
+ tags = tagsOrConfig;
7354
+ } else if (tagsOrConfig) {
7355
+ tags = tagsOrConfig.tags;
7356
+ autoInvalidate = tagsOrConfig.autoInvalidate;
7357
+ }
7358
+ return {
7359
+ key,
7360
+ ttl,
7361
+ tags,
7362
+ autoInvalidate
7363
+ };
7364
+ }
7365
+ };
7366
+
7269
7367
  // src/query-builder/select.ts
7270
7368
  var SelectQueryBuilder = class _SelectQueryBuilder {
7271
7369
  env;
@@ -7282,6 +7380,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7282
7380
  lazyRelationOptions;
7283
7381
  entityConstructor;
7284
7382
  includeTree;
7383
+ cacheFacet;
7384
+ cacheContext;
7285
7385
  /**
7286
7386
  * Creates a new SelectQueryBuilder instance
7287
7387
  * @param table - Table definition to query
@@ -7289,7 +7389,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7289
7389
  * @param hydration - Optional hydration manager
7290
7390
  * @param dependencies - Optional query builder dependencies
7291
7391
  */
7292
- constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions, entityConstructor, includeTree) {
7392
+ constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions, entityConstructor, includeTree, cacheContext) {
7293
7393
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
7294
7394
  this.env = { table, deps };
7295
7395
  const createAstService = (nextState) => deps.createQueryAstService(table, nextState);
@@ -7303,6 +7403,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7303
7403
  this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
7304
7404
  this.entityConstructor = entityConstructor;
7305
7405
  this.includeTree = includeTree ?? {};
7406
+ this.cacheFacet = new CacheFacet();
7407
+ this.cacheContext = cacheContext ?? { state: {} };
7306
7408
  this.columnSelector = deps.createColumnSelector(this.env);
7307
7409
  const relationManager = deps.createRelationManager(this.env);
7308
7410
  this.fromFacet = new SelectFromFacet(this.env, createAstService);
@@ -7319,7 +7421,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7319
7421
  * @param lazyRelations - Updated lazy relations set
7320
7422
  * @returns New SelectQueryBuilder instance
7321
7423
  */
7322
- clone(context = this.context, lazyRelations = new Set(this.lazyRelations), lazyRelationOptions = new Map(this.lazyRelationOptions), includeTree = this.includeTree) {
7424
+ clone(context = this.context, lazyRelations = new Set(this.lazyRelations), lazyRelationOptions = new Map(this.lazyRelationOptions), includeTree = this.includeTree, cacheContext = this.cacheContext) {
7323
7425
  return new _SelectQueryBuilder(
7324
7426
  this.env.table,
7325
7427
  context.state,
@@ -7328,7 +7430,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7328
7430
  lazyRelations,
7329
7431
  lazyRelationOptions,
7330
7432
  this.entityConstructor,
7331
- includeTree
7433
+ includeTree,
7434
+ cacheContext
7332
7435
  );
7333
7436
  }
7334
7437
  /**
@@ -7750,10 +7853,43 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7750
7853
  }
7751
7854
  return this;
7752
7855
  }
7856
+ /**
7857
+ * Configures caching for this query.
7858
+ * @param key - Unique cache key
7859
+ * @param ttl - Time-to-live (e.g., '1h', '30m', '1d') or milliseconds
7860
+ * @param tagsOrConfig - Optional tags for invalidation or configuration object
7861
+ * @returns New query builder instance with cache configuration
7862
+ * @example
7863
+ * // Simple cache with TTL
7864
+ * await selectFrom(User).cache('active_users', '1h').execute(session);
7865
+ *
7866
+ * // Cache with tags for invalidation
7867
+ * await selectFrom(User)
7868
+ * .cache('users_list', '30m', ['users', 'dashboard'])
7869
+ * .execute(session);
7870
+ *
7871
+ * // Cache with auto-invalidation
7872
+ * await selectFrom(User)
7873
+ * .cache('users_list', '1h', { autoInvalidate: true })
7874
+ * .execute(session);
7875
+ */
7876
+ cache(key, ttl, tagsOrConfig) {
7877
+ const options = CacheFacet.createOptions(key, ttl, tagsOrConfig);
7878
+ const nextCacheContext = this.cacheFacet.cache(this.cacheContext, options);
7879
+ const builder = this.clone(
7880
+ this.context,
7881
+ new Set(this.lazyRelations),
7882
+ new Map(this.lazyRelationOptions),
7883
+ this.includeTree,
7884
+ nextCacheContext
7885
+ );
7886
+ return builder;
7887
+ }
7753
7888
  /**
7754
7889
  * Executes the query and returns hydrated results.
7755
7890
  * If the builder was created with an entity constructor (e.g. via selectFromEntity),
7756
7891
  * this will automatically return fully materialized entity instances.
7892
+ * If caching is configured, results will be cached/retrieved from cache.
7757
7893
  *
7758
7894
  * @param ctx - ORM session context
7759
7895
  * @returns Promise of entity instances (or objects if generic T is not an entity)
@@ -7763,6 +7899,20 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7763
7899
  * users[0] instanceof User; // true
7764
7900
  */
7765
7901
  async execute(ctx) {
7902
+ const cacheOptions = this.cacheFacet.getOptions(this.cacheContext);
7903
+ if (!cacheOptions || !ctx.cacheManager) {
7904
+ return this.executeWithoutCache(ctx);
7905
+ }
7906
+ return ctx.cacheManager.getOrExecute(
7907
+ cacheOptions,
7908
+ () => this.executeWithoutCache(ctx),
7909
+ ctx.tenantId
7910
+ );
7911
+ }
7912
+ /**
7913
+ * Executa a query sem cache (método interno)
7914
+ */
7915
+ async executeWithoutCache(ctx) {
7766
7916
  if (this.entityConstructor) {
7767
7917
  return this.executeAs(this.entityConstructor, ctx);
7768
7918
  }
@@ -12084,12 +12234,13 @@ var UnitOfWork = class {
12084
12234
  await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
12085
12235
  const payload = this.extractColumns(tracked.table, tracked.entity);
12086
12236
  let builder = new InsertQueryBuilder(tracked.table).values(payload);
12087
- if (this.dialect.supportsReturning()) {
12237
+ if (this.dialect.supportsDmlReturningClause()) {
12088
12238
  builder = builder.returning(...this.getReturningColumns(tracked.table));
12089
12239
  }
12090
12240
  const compiled = builder.compile(this.dialect);
12091
12241
  const results = await this.executeCompiled(compiled);
12092
12242
  this.applyReturningResults(tracked, results);
12243
+ this.applyInsertedIdIfAbsent(tracked, results);
12093
12244
  tracked.status = "managed" /* Managed */;
12094
12245
  tracked.original = this.createSnapshot(tracked.table, tracked.entity);
12095
12246
  tracked.pk = this.getPrimaryKeyValue(tracked);
@@ -12111,7 +12262,7 @@ var UnitOfWork = class {
12111
12262
  const pkColumn = tracked.table.columns[findPrimaryKey(tracked.table)];
12112
12263
  if (!pkColumn) return;
12113
12264
  let builder = new UpdateQueryBuilder(tracked.table).set(changes).where(eq(pkColumn, tracked.pk));
12114
- if (this.dialect.supportsReturning()) {
12265
+ if (this.dialect.supportsDmlReturningClause()) {
12115
12266
  builder = builder.returning(...this.getReturningColumns(tracked.table));
12116
12267
  }
12117
12268
  const compiled = builder.compile(this.dialect);
@@ -12205,9 +12356,8 @@ var UnitOfWork = class {
12205
12356
  * @param results - Query results
12206
12357
  */
12207
12358
  applyReturningResults(tracked, results) {
12208
- if (!this.dialect.supportsReturning()) return;
12209
12359
  const first = results[0];
12210
- if (!first || first.values.length === 0) return;
12360
+ if (!first || first.columns.length === 0 || first.values.length === 0) return;
12211
12361
  const row = first.values[0];
12212
12362
  for (let i = 0; i < first.columns.length; i++) {
12213
12363
  const columnName = this.normalizeColumnName(first.columns[i]);
@@ -12215,6 +12365,21 @@ var UnitOfWork = class {
12215
12365
  tracked.entity[columnName] = row[i];
12216
12366
  }
12217
12367
  }
12368
+ /**
12369
+ * Applies the driver-provided insertId when no RETURNING clause was used.
12370
+ * Only sets the PK if it is currently absent on the entity.
12371
+ * @param tracked - The tracked entity
12372
+ * @param results - Query results (may contain meta.insertId)
12373
+ */
12374
+ applyInsertedIdIfAbsent(tracked, results) {
12375
+ const pkName = findPrimaryKey(tracked.table);
12376
+ const current = tracked.entity[pkName];
12377
+ if (current != null) return;
12378
+ const first = results[0];
12379
+ const insertId = first?.meta?.insertId;
12380
+ if (insertId == null) return;
12381
+ tracked.entity[pkName] = insertId;
12382
+ }
12218
12383
  /**
12219
12384
  * Normalizes a column name by removing quotes and table prefixes.
12220
12385
  * @param column - The column name to normalize
@@ -12852,6 +13017,10 @@ var OrmSession = class {
12852
13017
  domainEvents;
12853
13018
  /** The relation change processor */
12854
13019
  relationChanges;
13020
+ /** The cache manager for query caching */
13021
+ cacheManager;
13022
+ /** The tenant ID for multi-tenancy support */
13023
+ tenantId;
12855
13024
  interceptors;
12856
13025
  saveGraphDefaults;
12857
13026
  /**
@@ -12866,6 +13035,8 @@ var OrmSession = class {
12866
13035
  this.unitOfWork = new UnitOfWork(this.orm.dialect, this.executor, this.identityMap, () => this);
12867
13036
  this.relationChanges = new RelationChangeProcessor(this.unitOfWork, this.orm.dialect, this.executor);
12868
13037
  this.domainEvents = new DomainEventBus(opts.domainEventHandlers);
13038
+ this.cacheManager = opts.cacheManager;
13039
+ this.tenantId = opts.tenantId;
12869
13040
  }
12870
13041
  /**
12871
13042
  * Releases resources associated with this session (executor/pool leases) and resets tracking.
@@ -13240,6 +13411,45 @@ var OrmSession = class {
13240
13411
  entityContext: this
13241
13412
  };
13242
13413
  }
13414
+ /**
13415
+ * Invalidates cache by specific tags.
13416
+ * @param tags - Tags to invalidate
13417
+ * @throws Error if no cache manager is configured
13418
+ * @example
13419
+ * await session.invalidateCacheTags(['users', 'dashboard']);
13420
+ */
13421
+ async invalidateCacheTags(tags) {
13422
+ if (!this.cacheManager) {
13423
+ throw new Error("No cache manager configured. Please provide cacheManager when creating the session.");
13424
+ }
13425
+ await this.cacheManager.invalidateTags(tags);
13426
+ }
13427
+ /**
13428
+ * Invalidates cache by key prefix (useful for multi-tenancy).
13429
+ * @param prefix - Prefix to match cache keys
13430
+ * @throws Error if no cache manager is configured
13431
+ * @example
13432
+ * await session.invalidateCachePrefix('tenant:123:');
13433
+ */
13434
+ async invalidateCachePrefix(prefix) {
13435
+ if (!this.cacheManager) {
13436
+ throw new Error("No cache manager configured. Please provide cacheManager when creating the session.");
13437
+ }
13438
+ await this.cacheManager.invalidatePrefix(prefix);
13439
+ }
13440
+ /**
13441
+ * Invalidates a specific cache key.
13442
+ * @param key - Cache key to invalidate
13443
+ * @throws Error if no cache manager is configured
13444
+ * @example
13445
+ * await session.invalidateCacheKey('active_users');
13446
+ */
13447
+ async invalidateCacheKey(key) {
13448
+ if (!this.cacheManager) {
13449
+ throw new Error("No cache manager configured. Please provide cacheManager when creating the session.");
13450
+ }
13451
+ await this.cacheManager.invalidateKey(key, this.tenantId);
13452
+ }
13243
13453
  /**
13244
13454
  * Merges session defaults with per-call saveGraph options.
13245
13455
  * @param options - Per-call saveGraph options
@@ -13277,6 +13487,309 @@ var InterceptorPipeline = class {
13277
13487
  }
13278
13488
  };
13279
13489
 
13490
+ // src/cache/strategies/default-cache-strategy.ts
13491
+ var DefaultCacheStrategy = class {
13492
+ name = "default";
13493
+ /**
13494
+ * Gera chave de cache com prefixo de tenant se houver
13495
+ */
13496
+ generateKey(queryKey, tenantId) {
13497
+ if (tenantId !== void 0) {
13498
+ return `tenant:${tenantId}:${queryKey}`;
13499
+ }
13500
+ return queryKey;
13501
+ }
13502
+ /**
13503
+ * Verifica se deve cachear baseado na condição configurada
13504
+ */
13505
+ shouldCache(result, options) {
13506
+ if (options.condition) {
13507
+ return options.condition(result);
13508
+ }
13509
+ return true;
13510
+ }
13511
+ /**
13512
+ * Serializa com suporte a tipos especiais
13513
+ */
13514
+ serialize(data) {
13515
+ return JSON.stringify(data, (key, value) => {
13516
+ if (value instanceof Date) {
13517
+ return { __type: "Date", value: value.toISOString() };
13518
+ }
13519
+ if (typeof value === "bigint") {
13520
+ return { __type: "BigInt", value: value.toString() };
13521
+ }
13522
+ if (value instanceof Map) {
13523
+ return { __type: "Map", value: Array.from(value.entries()) };
13524
+ }
13525
+ if (value instanceof Set) {
13526
+ return { __type: "Set", value: Array.from(value) };
13527
+ }
13528
+ return value;
13529
+ });
13530
+ }
13531
+ /**
13532
+ * Desserializa restaurando tipos especiais
13533
+ */
13534
+ deserialize(data) {
13535
+ if (typeof data !== "string") {
13536
+ return data;
13537
+ }
13538
+ return JSON.parse(data, (key, value) => {
13539
+ if (!value || typeof value !== "object") {
13540
+ return value;
13541
+ }
13542
+ if (value.__type === "Date") {
13543
+ return new Date(value.value);
13544
+ }
13545
+ if (value.__type === "BigInt") {
13546
+ return BigInt(value.value);
13547
+ }
13548
+ if (value.__type === "Map") {
13549
+ return new Map(value.value);
13550
+ }
13551
+ if (value.__type === "Set") {
13552
+ return new Set(value.value);
13553
+ }
13554
+ return value;
13555
+ });
13556
+ }
13557
+ };
13558
+
13559
+ // src/cache/duration-utils.ts
13560
+ var DURATION_MULTIPLIERS = {
13561
+ s: 1e3,
13562
+ // segundos
13563
+ m: 6e4,
13564
+ // minutos
13565
+ h: 36e5,
13566
+ // horas
13567
+ d: 864e5,
13568
+ // dias
13569
+ w: 6048e5
13570
+ // semanas
13571
+ };
13572
+ function parseDuration(duration) {
13573
+ if (typeof duration === "number") {
13574
+ return duration;
13575
+ }
13576
+ const match = duration.match(/^(\d+)([smhdw])$/);
13577
+ if (!match) {
13578
+ throw new Error(
13579
+ `Invalid duration format: "${duration}". Use formats like '30s', '10m', '2h', '1d', '1w' or a number in milliseconds.`
13580
+ );
13581
+ }
13582
+ const value = parseInt(match[1], 10);
13583
+ const unit = match[2];
13584
+ return value * DURATION_MULTIPLIERS[unit];
13585
+ }
13586
+ function formatDuration(ms) {
13587
+ if (ms < 1e3) return `${ms}ms`;
13588
+ if (ms < 6e4) return `${Math.floor(ms / 1e3)}s`;
13589
+ if (ms < 36e5) return `${Math.floor(ms / 6e4)}m`;
13590
+ if (ms < 864e5) return `${Math.floor(ms / 36e5)}h`;
13591
+ if (ms < 6048e5) return `${Math.floor(ms / 864e5)}d`;
13592
+ return `${Math.floor(ms / 6048e5)}w`;
13593
+ }
13594
+ function isValidDuration(value) {
13595
+ if (typeof value === "number") {
13596
+ return value >= 0;
13597
+ }
13598
+ if (typeof value === "string") {
13599
+ return /^\d+[smhdw]$/.test(value);
13600
+ }
13601
+ return false;
13602
+ }
13603
+
13604
+ // src/cache/adapters/memory-cache-adapter.ts
13605
+ var MemoryCacheAdapter = class {
13606
+ name = "memory";
13607
+ storage = /* @__PURE__ */ new Map();
13608
+ tagIndex = /* @__PURE__ */ new Map();
13609
+ async get(key) {
13610
+ const entry = this.storage.get(key);
13611
+ if (!entry) {
13612
+ return void 0;
13613
+ }
13614
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
13615
+ await this.delete(key);
13616
+ return void 0;
13617
+ }
13618
+ return entry.value;
13619
+ }
13620
+ async has(key) {
13621
+ const value = await this.get(key);
13622
+ return value !== void 0;
13623
+ }
13624
+ async set(key, value, ttlMs) {
13625
+ const entry = {
13626
+ value,
13627
+ expiresAt: ttlMs ? Date.now() + ttlMs : void 0
13628
+ };
13629
+ this.storage.set(key, entry);
13630
+ }
13631
+ async delete(key) {
13632
+ this.storage.delete(key);
13633
+ for (const [tag, keys] of this.tagIndex) {
13634
+ keys.delete(key);
13635
+ if (keys.size === 0) {
13636
+ this.tagIndex.delete(tag);
13637
+ }
13638
+ }
13639
+ }
13640
+ async invalidate(key) {
13641
+ await this.delete(key);
13642
+ }
13643
+ async invalidateTags(tags) {
13644
+ const keysToDelete = /* @__PURE__ */ new Set();
13645
+ for (const tag of tags) {
13646
+ const keys = this.tagIndex.get(tag);
13647
+ if (keys) {
13648
+ for (const key of keys) {
13649
+ keysToDelete.add(key);
13650
+ }
13651
+ this.tagIndex.delete(tag);
13652
+ }
13653
+ }
13654
+ for (const key of keysToDelete) {
13655
+ this.storage.delete(key);
13656
+ }
13657
+ }
13658
+ async invalidatePrefix(prefix) {
13659
+ const keysToDelete = [];
13660
+ for (const key of this.storage.keys()) {
13661
+ if (key.startsWith(prefix)) {
13662
+ keysToDelete.push(key);
13663
+ }
13664
+ }
13665
+ for (const key of keysToDelete) {
13666
+ await this.delete(key);
13667
+ }
13668
+ }
13669
+ /**
13670
+ * Registra uma chave com tags (para invalidação)
13671
+ */
13672
+ registerTags(key, tags) {
13673
+ for (const tag of tags) {
13674
+ if (!this.tagIndex.has(tag)) {
13675
+ this.tagIndex.set(tag, /* @__PURE__ */ new Set());
13676
+ }
13677
+ this.tagIndex.get(tag).add(key);
13678
+ }
13679
+ }
13680
+ /**
13681
+ * Limpa todo o cache
13682
+ */
13683
+ clear() {
13684
+ this.storage.clear();
13685
+ this.tagIndex.clear();
13686
+ }
13687
+ /**
13688
+ * Retorna estatísticas do cache
13689
+ */
13690
+ getStats() {
13691
+ return {
13692
+ size: this.storage.size,
13693
+ tags: this.tagIndex.size
13694
+ };
13695
+ }
13696
+ async dispose() {
13697
+ this.clear();
13698
+ }
13699
+ };
13700
+
13701
+ // src/cache/query-cache-manager.ts
13702
+ var QueryCacheManager = class {
13703
+ constructor(provider = new MemoryCacheAdapter(), strategy = new DefaultCacheStrategy(), defaultTtl = "1h") {
13704
+ this.provider = provider;
13705
+ this.strategy = strategy;
13706
+ this.defaultTtl = defaultTtl;
13707
+ }
13708
+ /**
13709
+ * Executa com cache - padrão execute-around
13710
+ * @returns Resultado da execução (do cache ou da função)
13711
+ */
13712
+ async getOrExecute(options, executor, tenantId) {
13713
+ const key = this.strategy.generateKey(options.key, tenantId);
13714
+ const ttlMs = this.parseDuration(options.ttl ?? this.defaultTtl);
13715
+ const cached = await this.provider.get(key);
13716
+ if (cached !== void 0) {
13717
+ return this.strategy.deserialize(cached);
13718
+ }
13719
+ const result = await executor();
13720
+ if (!this.strategy.shouldCache(result, options)) {
13721
+ return result;
13722
+ }
13723
+ const serialized = this.strategy.serialize(result);
13724
+ await this.provider.set(key, serialized, ttlMs);
13725
+ if (options.tags) {
13726
+ await this.registerTags(key, options.tags);
13727
+ }
13728
+ return result;
13729
+ }
13730
+ /**
13731
+ * Invalida uma chave específica
13732
+ */
13733
+ async invalidateKey(key, tenantId) {
13734
+ const fullKey = this.strategy.generateKey(key, tenantId);
13735
+ await this.provider.invalidate(fullKey);
13736
+ }
13737
+ /**
13738
+ * Invalida por tags
13739
+ */
13740
+ async invalidateTags(tags) {
13741
+ await this.provider.invalidateTags(tags);
13742
+ }
13743
+ /**
13744
+ * Invalida por prefixo (útil para multi-tenancy)
13745
+ */
13746
+ async invalidatePrefix(prefix) {
13747
+ await this.provider.invalidatePrefix(prefix);
13748
+ }
13749
+ /**
13750
+ * Limpa todo o cache (cuidado!)
13751
+ */
13752
+ async clear() {
13753
+ const provider = this.provider;
13754
+ if (typeof provider.clear === "function") {
13755
+ provider.clear();
13756
+ } else {
13757
+ throw new Error("Cache provider does not support clear operation");
13758
+ }
13759
+ }
13760
+ /**
13761
+ * Retorna estatísticas do cache (se disponível)
13762
+ */
13763
+ getStats() {
13764
+ const provider = this.provider;
13765
+ if (typeof provider.getStats === "function") {
13766
+ return provider.getStats();
13767
+ }
13768
+ return void 0;
13769
+ }
13770
+ /**
13771
+ * Libera recursos do cache
13772
+ */
13773
+ async dispose() {
13774
+ await this.provider.dispose?.();
13775
+ }
13776
+ /**
13777
+ * Registra tags para uma chave
13778
+ */
13779
+ async registerTags(key, tags) {
13780
+ const provider = this.provider;
13781
+ if (typeof provider.registerTags === "function") {
13782
+ provider.registerTags(key, tags);
13783
+ }
13784
+ }
13785
+ /**
13786
+ * Converte duração para milissegundos
13787
+ */
13788
+ parseDuration(d) {
13789
+ return parseDuration(d);
13790
+ }
13791
+ };
13792
+
13280
13793
  // src/orm/orm.ts
13281
13794
  var Orm = class {
13282
13795
  /** The database dialect */
@@ -13285,6 +13798,8 @@ var Orm = class {
13285
13798
  interceptors;
13286
13799
  /** The naming strategy */
13287
13800
  namingStrategy;
13801
+ /** The cache manager (if configured) */
13802
+ cacheManager;
13288
13803
  executorFactory;
13289
13804
  /**
13290
13805
  * Creates a new ORM instance.
@@ -13295,15 +13810,27 @@ var Orm = class {
13295
13810
  this.interceptors = opts.interceptors ?? new InterceptorPipeline();
13296
13811
  this.namingStrategy = opts.namingStrategy ?? new DefaultNamingStrategy();
13297
13812
  this.executorFactory = opts.executorFactory;
13813
+ if (opts.cache) {
13814
+ this.cacheManager = new QueryCacheManager(
13815
+ opts.cache.provider,
13816
+ opts.cache.strategy ?? new DefaultCacheStrategy(),
13817
+ opts.cache.defaultTtl ?? "1h"
13818
+ );
13819
+ }
13298
13820
  }
13299
13821
  /**
13300
13822
  * Creates a new ORM session.
13301
- * @param options - Optional session options
13823
+ * @param options - Optional session options (e.g., tenantId for multi-tenancy)
13302
13824
  * @returns The ORM session
13303
13825
  */
13304
- createSession() {
13826
+ createSession(options) {
13305
13827
  const executor = this.executorFactory.createExecutor();
13306
- return new OrmSession({ orm: this, executor });
13828
+ return new OrmSession({
13829
+ orm: this,
13830
+ executor,
13831
+ cacheManager: this.cacheManager,
13832
+ tenantId: options?.tenantId
13833
+ });
13307
13834
  }
13308
13835
  /**
13309
13836
  * Executes a function within a transaction.
@@ -13314,7 +13841,11 @@ var Orm = class {
13314
13841
  */
13315
13842
  async transaction(fn8) {
13316
13843
  const executor = this.executorFactory.createTransactionalExecutor();
13317
- const session = new OrmSession({ orm: this, executor });
13844
+ const session = new OrmSession({
13845
+ orm: this,
13846
+ executor,
13847
+ cacheManager: this.cacheManager
13848
+ });
13318
13849
  try {
13319
13850
  return await session.transaction(() => fn8(session));
13320
13851
  } finally {
@@ -14479,7 +15010,15 @@ function createMysqlExecutor(client) {
14479
15010
  async executeSql(sql, params) {
14480
15011
  const [rows] = await client.query(sql, params);
14481
15012
  if (!Array.isArray(rows)) {
14482
- return [{ columns: [], values: [] }];
15013
+ const header = rows;
15014
+ return [{
15015
+ columns: [],
15016
+ values: [],
15017
+ meta: {
15018
+ insertId: header.insertId,
15019
+ rowsAffected: header.affectedRows
15020
+ }
15021
+ }];
14483
15022
  }
14484
15023
  const result = rowsToQueryResult(
14485
15024
  rows
@@ -17788,6 +18327,162 @@ function queryResultsToRows(results) {
17788
18327
  }
17789
18328
  return rows;
17790
18329
  }
18330
+
18331
+ // src/cache/adapters/keyv-cache-adapter.ts
18332
+ var KeyvCacheAdapter = class {
18333
+ constructor(keyv) {
18334
+ this.keyv = keyv;
18335
+ }
18336
+ name = "keyv";
18337
+ async get(key) {
18338
+ return this.keyv.get(key);
18339
+ }
18340
+ async has(key) {
18341
+ const value = await this.keyv.get(key);
18342
+ return value !== void 0;
18343
+ }
18344
+ async set(key, value, ttlMs) {
18345
+ await this.keyv.set(key, value, ttlMs);
18346
+ }
18347
+ async delete(key) {
18348
+ await this.keyv.delete(key);
18349
+ }
18350
+ async invalidate(key) {
18351
+ await this.delete(key);
18352
+ }
18353
+ async invalidateTags(_tags) {
18354
+ throw new Error(
18355
+ "Keyv adapter does not support tag invalidation. Use MemoryCacheAdapter for testing or implement a custom Redis provider."
18356
+ );
18357
+ }
18358
+ async invalidatePrefix(prefix) {
18359
+ if (typeof this.keyv.iterator === "function") {
18360
+ const keys = [];
18361
+ for await (const [key] of this.keyv.iterator()) {
18362
+ if (key.startsWith(prefix)) {
18363
+ keys.push(key);
18364
+ }
18365
+ }
18366
+ if (keys.length > 0) {
18367
+ await Promise.all(keys.map((k) => this.keyv.delete(k)));
18368
+ }
18369
+ return;
18370
+ }
18371
+ throw new Error(
18372
+ "Keyv adapter does not support prefix invalidation in this store. Consider using a store with iterator support."
18373
+ );
18374
+ }
18375
+ async dispose() {
18376
+ await this.keyv.disconnect?.();
18377
+ }
18378
+ };
18379
+
18380
+ // src/cache/tag-index.ts
18381
+ var TagIndex = class {
18382
+ tagToKeys = /* @__PURE__ */ new Map();
18383
+ keyToTags = /* @__PURE__ */ new Map();
18384
+ /**
18385
+ * Registra que uma chave pertence a determinadas tags
18386
+ */
18387
+ register(key, tags) {
18388
+ for (const tag of tags) {
18389
+ if (!this.tagToKeys.has(tag)) {
18390
+ this.tagToKeys.set(tag, /* @__PURE__ */ new Set());
18391
+ }
18392
+ this.tagToKeys.get(tag).add(key);
18393
+ }
18394
+ const existingTags = this.keyToTags.get(key) ?? /* @__PURE__ */ new Set();
18395
+ tags.forEach((tag) => existingTags.add(tag));
18396
+ this.keyToTags.set(key, existingTags);
18397
+ }
18398
+ /**
18399
+ * Remove uma chave do índice
18400
+ */
18401
+ unregister(key) {
18402
+ const tags = this.keyToTags.get(key);
18403
+ if (tags) {
18404
+ for (const tag of tags) {
18405
+ this.tagToKeys.get(tag)?.delete(key);
18406
+ if (this.tagToKeys.get(tag)?.size === 0) {
18407
+ this.tagToKeys.delete(tag);
18408
+ }
18409
+ }
18410
+ this.keyToTags.delete(key);
18411
+ }
18412
+ }
18413
+ /**
18414
+ * Obtém todas as chaves de uma tag
18415
+ */
18416
+ getKeysByTag(tag) {
18417
+ return Array.from(this.tagToKeys.get(tag) ?? []);
18418
+ }
18419
+ /**
18420
+ * Obtém todas as tags de uma chave
18421
+ */
18422
+ getTagsByKey(key) {
18423
+ return Array.from(this.keyToTags.get(key) ?? []);
18424
+ }
18425
+ /**
18426
+ * Invalida todas as chaves de um conjunto de tags
18427
+ * Retorna as chaves afetadas
18428
+ */
18429
+ invalidateTags(tags) {
18430
+ const keysToInvalidate = /* @__PURE__ */ new Set();
18431
+ for (const tag of tags) {
18432
+ const keys = this.tagToKeys.get(tag);
18433
+ if (keys) {
18434
+ for (const key of keys) {
18435
+ keysToInvalidate.add(key);
18436
+ this.unregister(key);
18437
+ }
18438
+ this.tagToKeys.delete(tag);
18439
+ }
18440
+ }
18441
+ return Array.from(keysToInvalidate);
18442
+ }
18443
+ /**
18444
+ * Invalida por prefixo (útil para multi-tenancy)
18445
+ * Retorna as chaves afetadas
18446
+ */
18447
+ invalidatePrefix(prefix) {
18448
+ const keysToInvalidate = [];
18449
+ for (const key of this.keyToTags.keys()) {
18450
+ if (key.startsWith(prefix)) {
18451
+ keysToInvalidate.push(key);
18452
+ this.unregister(key);
18453
+ }
18454
+ }
18455
+ return keysToInvalidate;
18456
+ }
18457
+ /**
18458
+ * Retorna todas as tags registradas
18459
+ */
18460
+ getAllTags() {
18461
+ return Array.from(this.tagToKeys.keys());
18462
+ }
18463
+ /**
18464
+ * Retorna todas as chaves registradas
18465
+ */
18466
+ getAllKeys() {
18467
+ return Array.from(this.keyToTags.keys());
18468
+ }
18469
+ /**
18470
+ * Limpa todo o índice
18471
+ */
18472
+ clear() {
18473
+ this.tagToKeys.clear();
18474
+ this.keyToTags.clear();
18475
+ }
18476
+ /**
18477
+ * Retorna estatísticas do índice
18478
+ */
18479
+ getStats() {
18480
+ return {
18481
+ tags: this.tagToKeys.size,
18482
+ keys: this.keyToTags.size
18483
+ };
18484
+ }
18485
+ };
17791
18486
  export {
17792
18487
  Alphanumeric,
17793
18488
  AsyncLocalStorage,
@@ -17806,6 +18501,7 @@ export {
17806
18501
  DateTimeTypeStrategy,
17807
18502
  DecimalTypeStrategy,
17808
18503
  DefaultBelongsToReference,
18504
+ DefaultCacheStrategy,
17809
18505
  DefaultEntityMaterializer,
17810
18506
  DefaultHasManyCollection,
17811
18507
  DefaultManyToManyCollection,
@@ -17820,8 +18516,10 @@ export {
17820
18516
  InsertQueryBuilder,
17821
18517
  IntegerTypeStrategy,
17822
18518
  InterceptorPipeline,
18519
+ KeyvCacheAdapter,
17823
18520
  Length,
17824
18521
  Lower,
18522
+ MemoryCacheAdapter,
17825
18523
  MySqlDialect,
17826
18524
  NestedSetStrategy,
17827
18525
  Orm,
@@ -17831,12 +18529,14 @@ export {
17831
18529
  PostgresDialect,
17832
18530
  PrimaryKey,
17833
18531
  PrototypeMaterializationStrategy,
18532
+ QueryCacheManager,
17834
18533
  RelationKinds,
17835
18534
  STANDARD_COLUMN_TYPES,
17836
18535
  SelectQueryBuilder,
17837
18536
  SqlServerDialect,
17838
18537
  SqliteDialect,
17839
18538
  StringTypeStrategy,
18539
+ TagIndex,
17840
18540
  Title,
17841
18541
  Tree,
17842
18542
  TreeChildren,
@@ -17956,6 +18656,7 @@ export {
17956
18656
  extractScopeValues,
17957
18657
  firstValue,
17958
18658
  floor,
18659
+ formatDuration,
17959
18660
  formatTreeList,
17960
18661
  fromUnixTime,
17961
18662
  generateComponentSchemas,
@@ -18011,6 +18712,7 @@ export {
18011
18712
  isOperandNode,
18012
18713
  isTableDef2 as isTableDef,
18013
18714
  isTreeConfig,
18715
+ isValidDuration,
18014
18716
  isValueOperandInput,
18015
18717
  isWindowFunctionNode,
18016
18718
  jsonArrayAgg,
@@ -18071,6 +18773,7 @@ export {
18071
18773
  pagedResponseToOpenApiSchema,
18072
18774
  paginationParamsSchema,
18073
18775
  parameterToRef,
18776
+ parseDuration,
18074
18777
  pi,
18075
18778
  pick,
18076
18779
  position,