metal-orm 1.0.117 → 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) => {
@@ -6524,10 +6575,28 @@ var setLazyOptionsIfEmpty = (entity, relationName, options) => {
6524
6575
  if (!meta || meta.lazyRelationOptions.has(relationName)) return;
6525
6576
  meta.lazyRelationOptions.set(relationName, options);
6526
6577
  };
6578
+ var batchByTable = (pending) => {
6579
+ if (pending.length <= 1) return pending;
6580
+ const byTable = /* @__PURE__ */ new Map();
6581
+ let ungroupedIndex = 0;
6582
+ for (const { entities, include } of pending) {
6583
+ const meta = entities.length ? getEntityMeta(entities[0]) : void 0;
6584
+ const tableKey2 = meta?.table?.name ?? `__ungrouped_${ungroupedIndex++}`;
6585
+ const existing = byTable.get(tableKey2);
6586
+ if (existing) {
6587
+ existing.entities.push(...entities);
6588
+ existing.include = mergeRelationIncludeTrees(existing.include, include);
6589
+ } else {
6590
+ byTable.set(tableKey2, { entities: [...entities], include: { ...include } });
6591
+ }
6592
+ }
6593
+ return Array.from(byTable.values());
6594
+ };
6527
6595
  var preloadRelationIncludes = async (entities, includeTree, depth = 0) => {
6528
6596
  if (!entities.length) return;
6529
6597
  const entries = Object.entries(includeTree);
6530
6598
  if (!entries.length) return;
6599
+ const pending = [];
6531
6600
  for (const [relationName, node] of entries) {
6532
6601
  const shouldLoad = depth > 0 || Boolean(node.include);
6533
6602
  if (!shouldLoad) continue;
@@ -6539,9 +6608,13 @@ var preloadRelationIncludes = async (entities, includeTree, depth = 0) => {
6539
6608
  );
6540
6609
  const relatedEntities = loaded.flat();
6541
6610
  if (node.include && relatedEntities.length) {
6542
- await preloadRelationIncludes(relatedEntities, node.include, depth + 1);
6611
+ pending.push({ entities: relatedEntities, include: node.include });
6543
6612
  }
6544
6613
  }
6614
+ const batched = batchByTable(pending);
6615
+ for (const { entities: batchEntities, include } of batched) {
6616
+ await preloadRelationIncludes(batchEntities, include, depth + 1);
6617
+ }
6545
6618
  };
6546
6619
 
6547
6620
  // src/orm/execute.ts
@@ -7244,6 +7317,53 @@ var SelectRelationFacet = class {
7244
7317
  }
7245
7318
  };
7246
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
+
7247
7367
  // src/query-builder/select.ts
7248
7368
  var SelectQueryBuilder = class _SelectQueryBuilder {
7249
7369
  env;
@@ -7260,6 +7380,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7260
7380
  lazyRelationOptions;
7261
7381
  entityConstructor;
7262
7382
  includeTree;
7383
+ cacheFacet;
7384
+ cacheContext;
7263
7385
  /**
7264
7386
  * Creates a new SelectQueryBuilder instance
7265
7387
  * @param table - Table definition to query
@@ -7267,7 +7389,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7267
7389
  * @param hydration - Optional hydration manager
7268
7390
  * @param dependencies - Optional query builder dependencies
7269
7391
  */
7270
- constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions, entityConstructor, includeTree) {
7392
+ constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions, entityConstructor, includeTree, cacheContext) {
7271
7393
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
7272
7394
  this.env = { table, deps };
7273
7395
  const createAstService = (nextState) => deps.createQueryAstService(table, nextState);
@@ -7281,6 +7403,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7281
7403
  this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
7282
7404
  this.entityConstructor = entityConstructor;
7283
7405
  this.includeTree = includeTree ?? {};
7406
+ this.cacheFacet = new CacheFacet();
7407
+ this.cacheContext = cacheContext ?? { state: {} };
7284
7408
  this.columnSelector = deps.createColumnSelector(this.env);
7285
7409
  const relationManager = deps.createRelationManager(this.env);
7286
7410
  this.fromFacet = new SelectFromFacet(this.env, createAstService);
@@ -7297,7 +7421,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7297
7421
  * @param lazyRelations - Updated lazy relations set
7298
7422
  * @returns New SelectQueryBuilder instance
7299
7423
  */
7300
- 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) {
7301
7425
  return new _SelectQueryBuilder(
7302
7426
  this.env.table,
7303
7427
  context.state,
@@ -7306,7 +7430,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7306
7430
  lazyRelations,
7307
7431
  lazyRelationOptions,
7308
7432
  this.entityConstructor,
7309
- includeTree
7433
+ includeTree,
7434
+ cacheContext
7310
7435
  );
7311
7436
  }
7312
7437
  /**
@@ -7728,10 +7853,43 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7728
7853
  }
7729
7854
  return this;
7730
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
+ }
7731
7888
  /**
7732
7889
  * Executes the query and returns hydrated results.
7733
7890
  * If the builder was created with an entity constructor (e.g. via selectFromEntity),
7734
7891
  * this will automatically return fully materialized entity instances.
7892
+ * If caching is configured, results will be cached/retrieved from cache.
7735
7893
  *
7736
7894
  * @param ctx - ORM session context
7737
7895
  * @returns Promise of entity instances (or objects if generic T is not an entity)
@@ -7741,6 +7899,20 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7741
7899
  * users[0] instanceof User; // true
7742
7900
  */
7743
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) {
7744
7916
  if (this.entityConstructor) {
7745
7917
  return this.executeAs(this.entityConstructor, ctx);
7746
7918
  }
@@ -12062,12 +12234,13 @@ var UnitOfWork = class {
12062
12234
  await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
12063
12235
  const payload = this.extractColumns(tracked.table, tracked.entity);
12064
12236
  let builder = new InsertQueryBuilder(tracked.table).values(payload);
12065
- if (this.dialect.supportsReturning()) {
12237
+ if (this.dialect.supportsDmlReturningClause()) {
12066
12238
  builder = builder.returning(...this.getReturningColumns(tracked.table));
12067
12239
  }
12068
12240
  const compiled = builder.compile(this.dialect);
12069
12241
  const results = await this.executeCompiled(compiled);
12070
12242
  this.applyReturningResults(tracked, results);
12243
+ this.applyInsertedIdIfAbsent(tracked, results);
12071
12244
  tracked.status = "managed" /* Managed */;
12072
12245
  tracked.original = this.createSnapshot(tracked.table, tracked.entity);
12073
12246
  tracked.pk = this.getPrimaryKeyValue(tracked);
@@ -12089,7 +12262,7 @@ var UnitOfWork = class {
12089
12262
  const pkColumn = tracked.table.columns[findPrimaryKey(tracked.table)];
12090
12263
  if (!pkColumn) return;
12091
12264
  let builder = new UpdateQueryBuilder(tracked.table).set(changes).where(eq(pkColumn, tracked.pk));
12092
- if (this.dialect.supportsReturning()) {
12265
+ if (this.dialect.supportsDmlReturningClause()) {
12093
12266
  builder = builder.returning(...this.getReturningColumns(tracked.table));
12094
12267
  }
12095
12268
  const compiled = builder.compile(this.dialect);
@@ -12183,9 +12356,8 @@ var UnitOfWork = class {
12183
12356
  * @param results - Query results
12184
12357
  */
12185
12358
  applyReturningResults(tracked, results) {
12186
- if (!this.dialect.supportsReturning()) return;
12187
12359
  const first = results[0];
12188
- if (!first || first.values.length === 0) return;
12360
+ if (!first || first.columns.length === 0 || first.values.length === 0) return;
12189
12361
  const row = first.values[0];
12190
12362
  for (let i = 0; i < first.columns.length; i++) {
12191
12363
  const columnName = this.normalizeColumnName(first.columns[i]);
@@ -12193,6 +12365,21 @@ var UnitOfWork = class {
12193
12365
  tracked.entity[columnName] = row[i];
12194
12366
  }
12195
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
+ }
12196
12383
  /**
12197
12384
  * Normalizes a column name by removing quotes and table prefixes.
12198
12385
  * @param column - The column name to normalize
@@ -12830,6 +13017,10 @@ var OrmSession = class {
12830
13017
  domainEvents;
12831
13018
  /** The relation change processor */
12832
13019
  relationChanges;
13020
+ /** The cache manager for query caching */
13021
+ cacheManager;
13022
+ /** The tenant ID for multi-tenancy support */
13023
+ tenantId;
12833
13024
  interceptors;
12834
13025
  saveGraphDefaults;
12835
13026
  /**
@@ -12844,6 +13035,8 @@ var OrmSession = class {
12844
13035
  this.unitOfWork = new UnitOfWork(this.orm.dialect, this.executor, this.identityMap, () => this);
12845
13036
  this.relationChanges = new RelationChangeProcessor(this.unitOfWork, this.orm.dialect, this.executor);
12846
13037
  this.domainEvents = new DomainEventBus(opts.domainEventHandlers);
13038
+ this.cacheManager = opts.cacheManager;
13039
+ this.tenantId = opts.tenantId;
12847
13040
  }
12848
13041
  /**
12849
13042
  * Releases resources associated with this session (executor/pool leases) and resets tracking.
@@ -13218,6 +13411,45 @@ var OrmSession = class {
13218
13411
  entityContext: this
13219
13412
  };
13220
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
+ }
13221
13453
  /**
13222
13454
  * Merges session defaults with per-call saveGraph options.
13223
13455
  * @param options - Per-call saveGraph options
@@ -13255,6 +13487,309 @@ var InterceptorPipeline = class {
13255
13487
  }
13256
13488
  };
13257
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
+
13258
13793
  // src/orm/orm.ts
13259
13794
  var Orm = class {
13260
13795
  /** The database dialect */
@@ -13263,6 +13798,8 @@ var Orm = class {
13263
13798
  interceptors;
13264
13799
  /** The naming strategy */
13265
13800
  namingStrategy;
13801
+ /** The cache manager (if configured) */
13802
+ cacheManager;
13266
13803
  executorFactory;
13267
13804
  /**
13268
13805
  * Creates a new ORM instance.
@@ -13273,15 +13810,27 @@ var Orm = class {
13273
13810
  this.interceptors = opts.interceptors ?? new InterceptorPipeline();
13274
13811
  this.namingStrategy = opts.namingStrategy ?? new DefaultNamingStrategy();
13275
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
+ }
13276
13820
  }
13277
13821
  /**
13278
13822
  * Creates a new ORM session.
13279
- * @param options - Optional session options
13823
+ * @param options - Optional session options (e.g., tenantId for multi-tenancy)
13280
13824
  * @returns The ORM session
13281
13825
  */
13282
- createSession() {
13826
+ createSession(options) {
13283
13827
  const executor = this.executorFactory.createExecutor();
13284
- return new OrmSession({ orm: this, executor });
13828
+ return new OrmSession({
13829
+ orm: this,
13830
+ executor,
13831
+ cacheManager: this.cacheManager,
13832
+ tenantId: options?.tenantId
13833
+ });
13285
13834
  }
13286
13835
  /**
13287
13836
  * Executes a function within a transaction.
@@ -13292,7 +13841,11 @@ var Orm = class {
13292
13841
  */
13293
13842
  async transaction(fn8) {
13294
13843
  const executor = this.executorFactory.createTransactionalExecutor();
13295
- const session = new OrmSession({ orm: this, executor });
13844
+ const session = new OrmSession({
13845
+ orm: this,
13846
+ executor,
13847
+ cacheManager: this.cacheManager
13848
+ });
13296
13849
  try {
13297
13850
  return await session.transaction(() => fn8(session));
13298
13851
  } finally {
@@ -14457,7 +15010,15 @@ function createMysqlExecutor(client) {
14457
15010
  async executeSql(sql, params) {
14458
15011
  const [rows] = await client.query(sql, params);
14459
15012
  if (!Array.isArray(rows)) {
14460
- 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
+ }];
14461
15022
  }
14462
15023
  const result = rowsToQueryResult(
14463
15024
  rows
@@ -17766,6 +18327,162 @@ function queryResultsToRows(results) {
17766
18327
  }
17767
18328
  return rows;
17768
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
+ };
17769
18486
  export {
17770
18487
  Alphanumeric,
17771
18488
  AsyncLocalStorage,
@@ -17784,6 +18501,7 @@ export {
17784
18501
  DateTimeTypeStrategy,
17785
18502
  DecimalTypeStrategy,
17786
18503
  DefaultBelongsToReference,
18504
+ DefaultCacheStrategy,
17787
18505
  DefaultEntityMaterializer,
17788
18506
  DefaultHasManyCollection,
17789
18507
  DefaultManyToManyCollection,
@@ -17798,8 +18516,10 @@ export {
17798
18516
  InsertQueryBuilder,
17799
18517
  IntegerTypeStrategy,
17800
18518
  InterceptorPipeline,
18519
+ KeyvCacheAdapter,
17801
18520
  Length,
17802
18521
  Lower,
18522
+ MemoryCacheAdapter,
17803
18523
  MySqlDialect,
17804
18524
  NestedSetStrategy,
17805
18525
  Orm,
@@ -17809,12 +18529,14 @@ export {
17809
18529
  PostgresDialect,
17810
18530
  PrimaryKey,
17811
18531
  PrototypeMaterializationStrategy,
18532
+ QueryCacheManager,
17812
18533
  RelationKinds,
17813
18534
  STANDARD_COLUMN_TYPES,
17814
18535
  SelectQueryBuilder,
17815
18536
  SqlServerDialect,
17816
18537
  SqliteDialect,
17817
18538
  StringTypeStrategy,
18539
+ TagIndex,
17818
18540
  Title,
17819
18541
  Tree,
17820
18542
  TreeChildren,
@@ -17934,6 +18656,7 @@ export {
17934
18656
  extractScopeValues,
17935
18657
  firstValue,
17936
18658
  floor,
18659
+ formatDuration,
17937
18660
  formatTreeList,
17938
18661
  fromUnixTime,
17939
18662
  generateComponentSchemas,
@@ -17989,6 +18712,7 @@ export {
17989
18712
  isOperandNode,
17990
18713
  isTableDef2 as isTableDef,
17991
18714
  isTreeConfig,
18715
+ isValidDuration,
17992
18716
  isValueOperandInput,
17993
18717
  isWindowFunctionNode,
17994
18718
  jsonArrayAgg,
@@ -18049,6 +18773,7 @@ export {
18049
18773
  pagedResponseToOpenApiSchema,
18050
18774
  paginationParamsSchema,
18051
18775
  parameterToRef,
18776
+ parseDuration,
18052
18777
  pi,
18053
18778
  pick,
18054
18779
  position,