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.cjs CHANGED
@@ -60,6 +60,7 @@ __export(index_exports, {
60
60
  DateTimeTypeStrategy: () => DateTimeTypeStrategy,
61
61
  DecimalTypeStrategy: () => DecimalTypeStrategy,
62
62
  DefaultBelongsToReference: () => DefaultBelongsToReference,
63
+ DefaultCacheStrategy: () => DefaultCacheStrategy,
63
64
  DefaultEntityMaterializer: () => DefaultEntityMaterializer,
64
65
  DefaultHasManyCollection: () => DefaultHasManyCollection,
65
66
  DefaultManyToManyCollection: () => DefaultManyToManyCollection,
@@ -74,8 +75,10 @@ __export(index_exports, {
74
75
  InsertQueryBuilder: () => InsertQueryBuilder,
75
76
  IntegerTypeStrategy: () => IntegerTypeStrategy,
76
77
  InterceptorPipeline: () => InterceptorPipeline,
78
+ KeyvCacheAdapter: () => KeyvCacheAdapter,
77
79
  Length: () => Length,
78
80
  Lower: () => Lower,
81
+ MemoryCacheAdapter: () => MemoryCacheAdapter,
79
82
  MySqlDialect: () => MySqlDialect,
80
83
  NestedSetStrategy: () => NestedSetStrategy,
81
84
  Orm: () => Orm,
@@ -85,12 +88,14 @@ __export(index_exports, {
85
88
  PostgresDialect: () => PostgresDialect,
86
89
  PrimaryKey: () => PrimaryKey,
87
90
  PrototypeMaterializationStrategy: () => PrototypeMaterializationStrategy,
91
+ QueryCacheManager: () => QueryCacheManager,
88
92
  RelationKinds: () => RelationKinds,
89
93
  STANDARD_COLUMN_TYPES: () => STANDARD_COLUMN_TYPES,
90
94
  SelectQueryBuilder: () => SelectQueryBuilder,
91
95
  SqlServerDialect: () => SqlServerDialect,
92
96
  SqliteDialect: () => SqliteDialect,
93
97
  StringTypeStrategy: () => StringTypeStrategy,
98
+ TagIndex: () => TagIndex,
94
99
  Title: () => Title,
95
100
  Tree: () => Tree,
96
101
  TreeChildren: () => TreeChildren,
@@ -210,6 +215,7 @@ __export(index_exports, {
210
215
  extractScopeValues: () => extractScopeValues,
211
216
  firstValue: () => firstValue,
212
217
  floor: () => floor,
218
+ formatDuration: () => formatDuration,
213
219
  formatTreeList: () => formatTreeList,
214
220
  fromUnixTime: () => fromUnixTime,
215
221
  generateComponentSchemas: () => generateComponentSchemas,
@@ -265,6 +271,7 @@ __export(index_exports, {
265
271
  isOperandNode: () => isOperandNode,
266
272
  isTableDef: () => isTableDef2,
267
273
  isTreeConfig: () => isTreeConfig,
274
+ isValidDuration: () => isValidDuration,
268
275
  isValueOperandInput: () => isValueOperandInput,
269
276
  isWindowFunctionNode: () => isWindowFunctionNode,
270
277
  jsonArrayAgg: () => jsonArrayAgg,
@@ -325,6 +332,7 @@ __export(index_exports, {
325
332
  pagedResponseToOpenApiSchema: () => pagedResponseToOpenApiSchema,
326
333
  paginationParamsSchema: () => paginationParamsSchema,
327
334
  parameterToRef: () => parameterToRef,
335
+ parseDuration: () => parseDuration,
328
336
  pi: () => pi,
329
337
  pick: () => pick,
330
338
  position: () => position,
@@ -1659,7 +1667,7 @@ var Dialect = class _Dialect {
1659
1667
  params: [...ctx.params]
1660
1668
  };
1661
1669
  }
1662
- supportsReturning() {
1670
+ supportsDmlReturningClause() {
1663
1671
  return false;
1664
1672
  }
1665
1673
  /**
@@ -2684,7 +2692,7 @@ var PostgresDialect = class extends SqlDialectBase {
2684
2692
  const columns = this.formatReturningColumns(returning);
2685
2693
  return ` RETURNING ${columns}`;
2686
2694
  }
2687
- supportsReturning() {
2695
+ supportsDmlReturningClause() {
2688
2696
  return true;
2689
2697
  }
2690
2698
  /**
@@ -2999,7 +3007,7 @@ var SqliteDialect = class extends SqlDialectBase {
2999
3007
  return `${this.quoteIdentifier(column.name)}${alias}`;
3000
3008
  }).join(", ");
3001
3009
  }
3002
- supportsReturning() {
3010
+ supportsDmlReturningClause() {
3003
3011
  return true;
3004
3012
  }
3005
3013
  };
@@ -3190,8 +3198,21 @@ var SqlServerDialect = class extends SqlDialectBase {
3190
3198
  this.compileExpression.bind(this)
3191
3199
  );
3192
3200
  const whereClause = this.compileWhere(ast.where, ctx);
3193
- const returning = this.compileReturning(ast.returning, ctx);
3194
- return `DELETE ${this.quoteIdentifier(alias)} FROM ${target}${joins}${whereClause}${returning}`;
3201
+ const returning = this.compileOutputClause(ast.returning, "deleted");
3202
+ return `DELETE ${this.quoteIdentifier(alias)}${returning} FROM ${target}${joins}${whereClause}`;
3203
+ }
3204
+ compileUpdateAst(ast, ctx) {
3205
+ const target = this.compileTableReference(ast.table);
3206
+ const assignments = this.compileUpdateAssignments(ast.set, ast.table, ctx);
3207
+ const output = this.compileReturning(ast.returning, ctx);
3208
+ const fromClause = ast.from ? ` FROM ${this.compileFrom(ast.from, ctx)}` : "";
3209
+ const joins = ast.joins ? ast.joins.map((j) => {
3210
+ const table = this.compileFrom(j.table, ctx);
3211
+ const cond = this.compileExpression(j.condition, ctx);
3212
+ return ` ${j.kind} JOIN ${table} ON ${cond}`;
3213
+ }).join("") : "";
3214
+ const whereClause = this.compileWhere(ast.where, ctx);
3215
+ return `UPDATE ${target} SET ${assignments}${output}${fromClause}${joins}${whereClause}`;
3195
3216
  }
3196
3217
  compileSelectCoreForMssql(ast, ctx) {
3197
3218
  const columns = ast.columns.map((c) => {
@@ -3242,6 +3263,44 @@ var SqlServerDialect = class extends SqlDialectBase {
3242
3263
  }
3243
3264
  return pagination;
3244
3265
  }
3266
+ supportsDmlReturningClause() {
3267
+ return true;
3268
+ }
3269
+ compileReturning(returning, _ctx) {
3270
+ void _ctx;
3271
+ return this.compileOutputClause(returning, "inserted");
3272
+ }
3273
+ compileOutputClause(returning, prefix) {
3274
+ if (!returning || returning.length === 0) return "";
3275
+ const columns = returning.map((column) => {
3276
+ const colName = this.quoteIdentifier(column.name);
3277
+ const alias = column.alias ? ` AS ${this.quoteIdentifier(column.alias)}` : "";
3278
+ return `${prefix}.${colName}${alias}`;
3279
+ }).join(", ");
3280
+ return ` OUTPUT ${columns}`;
3281
+ }
3282
+ compileInsertAst(ast, ctx) {
3283
+ if (!ast.columns.length) {
3284
+ throw new Error("INSERT queries must specify columns.");
3285
+ }
3286
+ const table = this.compileTableName(ast.into);
3287
+ const columnList = ast.columns.map((column) => this.quoteIdentifier(column.name)).join(", ");
3288
+ const output = this.compileReturning(ast.returning, ctx);
3289
+ const source = this.compileInsertValues(ast, ctx);
3290
+ return `INSERT INTO ${table} (${columnList})${output} ${source}`;
3291
+ }
3292
+ compileInsertValues(ast, ctx) {
3293
+ const source = ast.source;
3294
+ if (source.type === "InsertValues") {
3295
+ if (!source.rows.length) {
3296
+ throw new Error("INSERT ... VALUES requires at least one row.");
3297
+ }
3298
+ const values = source.rows.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
3299
+ return `VALUES ${values}`;
3300
+ }
3301
+ const normalized = this.normalizeSelectAst(source.query);
3302
+ return this.compileSelectAst(normalized, ctx).trim();
3303
+ }
3245
3304
  compileCtes(ast, ctx) {
3246
3305
  if (!ast.ctes || ast.ctes.length === 0) return "";
3247
3306
  const defs = ast.ctes.map((cte) => {
@@ -6915,10 +6974,28 @@ var setLazyOptionsIfEmpty = (entity, relationName, options) => {
6915
6974
  if (!meta || meta.lazyRelationOptions.has(relationName)) return;
6916
6975
  meta.lazyRelationOptions.set(relationName, options);
6917
6976
  };
6977
+ var batchByTable = (pending) => {
6978
+ if (pending.length <= 1) return pending;
6979
+ const byTable = /* @__PURE__ */ new Map();
6980
+ let ungroupedIndex = 0;
6981
+ for (const { entities, include } of pending) {
6982
+ const meta = entities.length ? getEntityMeta(entities[0]) : void 0;
6983
+ const tableKey2 = meta?.table?.name ?? `__ungrouped_${ungroupedIndex++}`;
6984
+ const existing = byTable.get(tableKey2);
6985
+ if (existing) {
6986
+ existing.entities.push(...entities);
6987
+ existing.include = mergeRelationIncludeTrees(existing.include, include);
6988
+ } else {
6989
+ byTable.set(tableKey2, { entities: [...entities], include: { ...include } });
6990
+ }
6991
+ }
6992
+ return Array.from(byTable.values());
6993
+ };
6918
6994
  var preloadRelationIncludes = async (entities, includeTree, depth = 0) => {
6919
6995
  if (!entities.length) return;
6920
6996
  const entries = Object.entries(includeTree);
6921
6997
  if (!entries.length) return;
6998
+ const pending = [];
6922
6999
  for (const [relationName, node] of entries) {
6923
7000
  const shouldLoad = depth > 0 || Boolean(node.include);
6924
7001
  if (!shouldLoad) continue;
@@ -6930,9 +7007,13 @@ var preloadRelationIncludes = async (entities, includeTree, depth = 0) => {
6930
7007
  );
6931
7008
  const relatedEntities = loaded.flat();
6932
7009
  if (node.include && relatedEntities.length) {
6933
- await preloadRelationIncludes(relatedEntities, node.include, depth + 1);
7010
+ pending.push({ entities: relatedEntities, include: node.include });
6934
7011
  }
6935
7012
  }
7013
+ const batched = batchByTable(pending);
7014
+ for (const { entities: batchEntities, include } of batched) {
7015
+ await preloadRelationIncludes(batchEntities, include, depth + 1);
7016
+ }
6936
7017
  };
6937
7018
 
6938
7019
  // src/orm/execute.ts
@@ -7635,6 +7716,53 @@ var SelectRelationFacet = class {
7635
7716
  }
7636
7717
  };
7637
7718
 
7719
+ // src/query-builder/select/cache-facet.ts
7720
+ var CacheFacet = class {
7721
+ /**
7722
+ * Configura opções de cache no contexto
7723
+ */
7724
+ cache(context, options) {
7725
+ return {
7726
+ state: {
7727
+ ...context.state,
7728
+ options
7729
+ }
7730
+ };
7731
+ }
7732
+ /**
7733
+ * Obtém as opções de cache do contexto
7734
+ */
7735
+ getOptions(context) {
7736
+ return context.state.options;
7737
+ }
7738
+ /**
7739
+ * Verifica se há configuração de cache
7740
+ */
7741
+ hasCache(context) {
7742
+ return context.state.options !== void 0;
7743
+ }
7744
+ /**
7745
+ * Cria opções de cache a partir de parâmetros variados
7746
+ * API flexível para diferentes casos de uso
7747
+ */
7748
+ static createOptions(key, ttl, tagsOrConfig) {
7749
+ let tags;
7750
+ let autoInvalidate;
7751
+ if (Array.isArray(tagsOrConfig)) {
7752
+ tags = tagsOrConfig;
7753
+ } else if (tagsOrConfig) {
7754
+ tags = tagsOrConfig.tags;
7755
+ autoInvalidate = tagsOrConfig.autoInvalidate;
7756
+ }
7757
+ return {
7758
+ key,
7759
+ ttl,
7760
+ tags,
7761
+ autoInvalidate
7762
+ };
7763
+ }
7764
+ };
7765
+
7638
7766
  // src/query-builder/select.ts
7639
7767
  var SelectQueryBuilder = class _SelectQueryBuilder {
7640
7768
  env;
@@ -7651,6 +7779,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7651
7779
  lazyRelationOptions;
7652
7780
  entityConstructor;
7653
7781
  includeTree;
7782
+ cacheFacet;
7783
+ cacheContext;
7654
7784
  /**
7655
7785
  * Creates a new SelectQueryBuilder instance
7656
7786
  * @param table - Table definition to query
@@ -7658,7 +7788,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7658
7788
  * @param hydration - Optional hydration manager
7659
7789
  * @param dependencies - Optional query builder dependencies
7660
7790
  */
7661
- constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions, entityConstructor, includeTree) {
7791
+ constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions, entityConstructor, includeTree, cacheContext) {
7662
7792
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
7663
7793
  this.env = { table, deps };
7664
7794
  const createAstService = (nextState) => deps.createQueryAstService(table, nextState);
@@ -7672,6 +7802,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7672
7802
  this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
7673
7803
  this.entityConstructor = entityConstructor;
7674
7804
  this.includeTree = includeTree ?? {};
7805
+ this.cacheFacet = new CacheFacet();
7806
+ this.cacheContext = cacheContext ?? { state: {} };
7675
7807
  this.columnSelector = deps.createColumnSelector(this.env);
7676
7808
  const relationManager = deps.createRelationManager(this.env);
7677
7809
  this.fromFacet = new SelectFromFacet(this.env, createAstService);
@@ -7688,7 +7820,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7688
7820
  * @param lazyRelations - Updated lazy relations set
7689
7821
  * @returns New SelectQueryBuilder instance
7690
7822
  */
7691
- clone(context = this.context, lazyRelations = new Set(this.lazyRelations), lazyRelationOptions = new Map(this.lazyRelationOptions), includeTree = this.includeTree) {
7823
+ clone(context = this.context, lazyRelations = new Set(this.lazyRelations), lazyRelationOptions = new Map(this.lazyRelationOptions), includeTree = this.includeTree, cacheContext = this.cacheContext) {
7692
7824
  return new _SelectQueryBuilder(
7693
7825
  this.env.table,
7694
7826
  context.state,
@@ -7697,7 +7829,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7697
7829
  lazyRelations,
7698
7830
  lazyRelationOptions,
7699
7831
  this.entityConstructor,
7700
- includeTree
7832
+ includeTree,
7833
+ cacheContext
7701
7834
  );
7702
7835
  }
7703
7836
  /**
@@ -8119,10 +8252,43 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
8119
8252
  }
8120
8253
  return this;
8121
8254
  }
8255
+ /**
8256
+ * Configures caching for this query.
8257
+ * @param key - Unique cache key
8258
+ * @param ttl - Time-to-live (e.g., '1h', '30m', '1d') or milliseconds
8259
+ * @param tagsOrConfig - Optional tags for invalidation or configuration object
8260
+ * @returns New query builder instance with cache configuration
8261
+ * @example
8262
+ * // Simple cache with TTL
8263
+ * await selectFrom(User).cache('active_users', '1h').execute(session);
8264
+ *
8265
+ * // Cache with tags for invalidation
8266
+ * await selectFrom(User)
8267
+ * .cache('users_list', '30m', ['users', 'dashboard'])
8268
+ * .execute(session);
8269
+ *
8270
+ * // Cache with auto-invalidation
8271
+ * await selectFrom(User)
8272
+ * .cache('users_list', '1h', { autoInvalidate: true })
8273
+ * .execute(session);
8274
+ */
8275
+ cache(key, ttl, tagsOrConfig) {
8276
+ const options = CacheFacet.createOptions(key, ttl, tagsOrConfig);
8277
+ const nextCacheContext = this.cacheFacet.cache(this.cacheContext, options);
8278
+ const builder = this.clone(
8279
+ this.context,
8280
+ new Set(this.lazyRelations),
8281
+ new Map(this.lazyRelationOptions),
8282
+ this.includeTree,
8283
+ nextCacheContext
8284
+ );
8285
+ return builder;
8286
+ }
8122
8287
  /**
8123
8288
  * Executes the query and returns hydrated results.
8124
8289
  * If the builder was created with an entity constructor (e.g. via selectFromEntity),
8125
8290
  * this will automatically return fully materialized entity instances.
8291
+ * If caching is configured, results will be cached/retrieved from cache.
8126
8292
  *
8127
8293
  * @param ctx - ORM session context
8128
8294
  * @returns Promise of entity instances (or objects if generic T is not an entity)
@@ -8132,6 +8298,20 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
8132
8298
  * users[0] instanceof User; // true
8133
8299
  */
8134
8300
  async execute(ctx) {
8301
+ const cacheOptions = this.cacheFacet.getOptions(this.cacheContext);
8302
+ if (!cacheOptions || !ctx.cacheManager) {
8303
+ return this.executeWithoutCache(ctx);
8304
+ }
8305
+ return ctx.cacheManager.getOrExecute(
8306
+ cacheOptions,
8307
+ () => this.executeWithoutCache(ctx),
8308
+ ctx.tenantId
8309
+ );
8310
+ }
8311
+ /**
8312
+ * Executa a query sem cache (método interno)
8313
+ */
8314
+ async executeWithoutCache(ctx) {
8135
8315
  if (this.entityConstructor) {
8136
8316
  return this.executeAs(this.entityConstructor, ctx);
8137
8317
  }
@@ -12453,12 +12633,13 @@ var UnitOfWork = class {
12453
12633
  await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
12454
12634
  const payload = this.extractColumns(tracked.table, tracked.entity);
12455
12635
  let builder = new InsertQueryBuilder(tracked.table).values(payload);
12456
- if (this.dialect.supportsReturning()) {
12636
+ if (this.dialect.supportsDmlReturningClause()) {
12457
12637
  builder = builder.returning(...this.getReturningColumns(tracked.table));
12458
12638
  }
12459
12639
  const compiled = builder.compile(this.dialect);
12460
12640
  const results = await this.executeCompiled(compiled);
12461
12641
  this.applyReturningResults(tracked, results);
12642
+ this.applyInsertedIdIfAbsent(tracked, results);
12462
12643
  tracked.status = "managed" /* Managed */;
12463
12644
  tracked.original = this.createSnapshot(tracked.table, tracked.entity);
12464
12645
  tracked.pk = this.getPrimaryKeyValue(tracked);
@@ -12480,7 +12661,7 @@ var UnitOfWork = class {
12480
12661
  const pkColumn = tracked.table.columns[findPrimaryKey(tracked.table)];
12481
12662
  if (!pkColumn) return;
12482
12663
  let builder = new UpdateQueryBuilder(tracked.table).set(changes).where(eq(pkColumn, tracked.pk));
12483
- if (this.dialect.supportsReturning()) {
12664
+ if (this.dialect.supportsDmlReturningClause()) {
12484
12665
  builder = builder.returning(...this.getReturningColumns(tracked.table));
12485
12666
  }
12486
12667
  const compiled = builder.compile(this.dialect);
@@ -12574,9 +12755,8 @@ var UnitOfWork = class {
12574
12755
  * @param results - Query results
12575
12756
  */
12576
12757
  applyReturningResults(tracked, results) {
12577
- if (!this.dialect.supportsReturning()) return;
12578
12758
  const first = results[0];
12579
- if (!first || first.values.length === 0) return;
12759
+ if (!first || first.columns.length === 0 || first.values.length === 0) return;
12580
12760
  const row = first.values[0];
12581
12761
  for (let i = 0; i < first.columns.length; i++) {
12582
12762
  const columnName = this.normalizeColumnName(first.columns[i]);
@@ -12584,6 +12764,21 @@ var UnitOfWork = class {
12584
12764
  tracked.entity[columnName] = row[i];
12585
12765
  }
12586
12766
  }
12767
+ /**
12768
+ * Applies the driver-provided insertId when no RETURNING clause was used.
12769
+ * Only sets the PK if it is currently absent on the entity.
12770
+ * @param tracked - The tracked entity
12771
+ * @param results - Query results (may contain meta.insertId)
12772
+ */
12773
+ applyInsertedIdIfAbsent(tracked, results) {
12774
+ const pkName = findPrimaryKey(tracked.table);
12775
+ const current = tracked.entity[pkName];
12776
+ if (current != null) return;
12777
+ const first = results[0];
12778
+ const insertId = first?.meta?.insertId;
12779
+ if (insertId == null) return;
12780
+ tracked.entity[pkName] = insertId;
12781
+ }
12587
12782
  /**
12588
12783
  * Normalizes a column name by removing quotes and table prefixes.
12589
12784
  * @param column - The column name to normalize
@@ -13221,6 +13416,10 @@ var OrmSession = class {
13221
13416
  domainEvents;
13222
13417
  /** The relation change processor */
13223
13418
  relationChanges;
13419
+ /** The cache manager for query caching */
13420
+ cacheManager;
13421
+ /** The tenant ID for multi-tenancy support */
13422
+ tenantId;
13224
13423
  interceptors;
13225
13424
  saveGraphDefaults;
13226
13425
  /**
@@ -13235,6 +13434,8 @@ var OrmSession = class {
13235
13434
  this.unitOfWork = new UnitOfWork(this.orm.dialect, this.executor, this.identityMap, () => this);
13236
13435
  this.relationChanges = new RelationChangeProcessor(this.unitOfWork, this.orm.dialect, this.executor);
13237
13436
  this.domainEvents = new DomainEventBus(opts.domainEventHandlers);
13437
+ this.cacheManager = opts.cacheManager;
13438
+ this.tenantId = opts.tenantId;
13238
13439
  }
13239
13440
  /**
13240
13441
  * Releases resources associated with this session (executor/pool leases) and resets tracking.
@@ -13609,6 +13810,45 @@ var OrmSession = class {
13609
13810
  entityContext: this
13610
13811
  };
13611
13812
  }
13813
+ /**
13814
+ * Invalidates cache by specific tags.
13815
+ * @param tags - Tags to invalidate
13816
+ * @throws Error if no cache manager is configured
13817
+ * @example
13818
+ * await session.invalidateCacheTags(['users', 'dashboard']);
13819
+ */
13820
+ async invalidateCacheTags(tags) {
13821
+ if (!this.cacheManager) {
13822
+ throw new Error("No cache manager configured. Please provide cacheManager when creating the session.");
13823
+ }
13824
+ await this.cacheManager.invalidateTags(tags);
13825
+ }
13826
+ /**
13827
+ * Invalidates cache by key prefix (useful for multi-tenancy).
13828
+ * @param prefix - Prefix to match cache keys
13829
+ * @throws Error if no cache manager is configured
13830
+ * @example
13831
+ * await session.invalidateCachePrefix('tenant:123:');
13832
+ */
13833
+ async invalidateCachePrefix(prefix) {
13834
+ if (!this.cacheManager) {
13835
+ throw new Error("No cache manager configured. Please provide cacheManager when creating the session.");
13836
+ }
13837
+ await this.cacheManager.invalidatePrefix(prefix);
13838
+ }
13839
+ /**
13840
+ * Invalidates a specific cache key.
13841
+ * @param key - Cache key to invalidate
13842
+ * @throws Error if no cache manager is configured
13843
+ * @example
13844
+ * await session.invalidateCacheKey('active_users');
13845
+ */
13846
+ async invalidateCacheKey(key) {
13847
+ if (!this.cacheManager) {
13848
+ throw new Error("No cache manager configured. Please provide cacheManager when creating the session.");
13849
+ }
13850
+ await this.cacheManager.invalidateKey(key, this.tenantId);
13851
+ }
13612
13852
  /**
13613
13853
  * Merges session defaults with per-call saveGraph options.
13614
13854
  * @param options - Per-call saveGraph options
@@ -13646,6 +13886,309 @@ var InterceptorPipeline = class {
13646
13886
  }
13647
13887
  };
13648
13888
 
13889
+ // src/cache/strategies/default-cache-strategy.ts
13890
+ var DefaultCacheStrategy = class {
13891
+ name = "default";
13892
+ /**
13893
+ * Gera chave de cache com prefixo de tenant se houver
13894
+ */
13895
+ generateKey(queryKey, tenantId) {
13896
+ if (tenantId !== void 0) {
13897
+ return `tenant:${tenantId}:${queryKey}`;
13898
+ }
13899
+ return queryKey;
13900
+ }
13901
+ /**
13902
+ * Verifica se deve cachear baseado na condição configurada
13903
+ */
13904
+ shouldCache(result, options) {
13905
+ if (options.condition) {
13906
+ return options.condition(result);
13907
+ }
13908
+ return true;
13909
+ }
13910
+ /**
13911
+ * Serializa com suporte a tipos especiais
13912
+ */
13913
+ serialize(data) {
13914
+ return JSON.stringify(data, (key, value) => {
13915
+ if (value instanceof Date) {
13916
+ return { __type: "Date", value: value.toISOString() };
13917
+ }
13918
+ if (typeof value === "bigint") {
13919
+ return { __type: "BigInt", value: value.toString() };
13920
+ }
13921
+ if (value instanceof Map) {
13922
+ return { __type: "Map", value: Array.from(value.entries()) };
13923
+ }
13924
+ if (value instanceof Set) {
13925
+ return { __type: "Set", value: Array.from(value) };
13926
+ }
13927
+ return value;
13928
+ });
13929
+ }
13930
+ /**
13931
+ * Desserializa restaurando tipos especiais
13932
+ */
13933
+ deserialize(data) {
13934
+ if (typeof data !== "string") {
13935
+ return data;
13936
+ }
13937
+ return JSON.parse(data, (key, value) => {
13938
+ if (!value || typeof value !== "object") {
13939
+ return value;
13940
+ }
13941
+ if (value.__type === "Date") {
13942
+ return new Date(value.value);
13943
+ }
13944
+ if (value.__type === "BigInt") {
13945
+ return BigInt(value.value);
13946
+ }
13947
+ if (value.__type === "Map") {
13948
+ return new Map(value.value);
13949
+ }
13950
+ if (value.__type === "Set") {
13951
+ return new Set(value.value);
13952
+ }
13953
+ return value;
13954
+ });
13955
+ }
13956
+ };
13957
+
13958
+ // src/cache/duration-utils.ts
13959
+ var DURATION_MULTIPLIERS = {
13960
+ s: 1e3,
13961
+ // segundos
13962
+ m: 6e4,
13963
+ // minutos
13964
+ h: 36e5,
13965
+ // horas
13966
+ d: 864e5,
13967
+ // dias
13968
+ w: 6048e5
13969
+ // semanas
13970
+ };
13971
+ function parseDuration(duration) {
13972
+ if (typeof duration === "number") {
13973
+ return duration;
13974
+ }
13975
+ const match = duration.match(/^(\d+)([smhdw])$/);
13976
+ if (!match) {
13977
+ throw new Error(
13978
+ `Invalid duration format: "${duration}". Use formats like '30s', '10m', '2h', '1d', '1w' or a number in milliseconds.`
13979
+ );
13980
+ }
13981
+ const value = parseInt(match[1], 10);
13982
+ const unit = match[2];
13983
+ return value * DURATION_MULTIPLIERS[unit];
13984
+ }
13985
+ function formatDuration(ms) {
13986
+ if (ms < 1e3) return `${ms}ms`;
13987
+ if (ms < 6e4) return `${Math.floor(ms / 1e3)}s`;
13988
+ if (ms < 36e5) return `${Math.floor(ms / 6e4)}m`;
13989
+ if (ms < 864e5) return `${Math.floor(ms / 36e5)}h`;
13990
+ if (ms < 6048e5) return `${Math.floor(ms / 864e5)}d`;
13991
+ return `${Math.floor(ms / 6048e5)}w`;
13992
+ }
13993
+ function isValidDuration(value) {
13994
+ if (typeof value === "number") {
13995
+ return value >= 0;
13996
+ }
13997
+ if (typeof value === "string") {
13998
+ return /^\d+[smhdw]$/.test(value);
13999
+ }
14000
+ return false;
14001
+ }
14002
+
14003
+ // src/cache/adapters/memory-cache-adapter.ts
14004
+ var MemoryCacheAdapter = class {
14005
+ name = "memory";
14006
+ storage = /* @__PURE__ */ new Map();
14007
+ tagIndex = /* @__PURE__ */ new Map();
14008
+ async get(key) {
14009
+ const entry = this.storage.get(key);
14010
+ if (!entry) {
14011
+ return void 0;
14012
+ }
14013
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
14014
+ await this.delete(key);
14015
+ return void 0;
14016
+ }
14017
+ return entry.value;
14018
+ }
14019
+ async has(key) {
14020
+ const value = await this.get(key);
14021
+ return value !== void 0;
14022
+ }
14023
+ async set(key, value, ttlMs) {
14024
+ const entry = {
14025
+ value,
14026
+ expiresAt: ttlMs ? Date.now() + ttlMs : void 0
14027
+ };
14028
+ this.storage.set(key, entry);
14029
+ }
14030
+ async delete(key) {
14031
+ this.storage.delete(key);
14032
+ for (const [tag, keys] of this.tagIndex) {
14033
+ keys.delete(key);
14034
+ if (keys.size === 0) {
14035
+ this.tagIndex.delete(tag);
14036
+ }
14037
+ }
14038
+ }
14039
+ async invalidate(key) {
14040
+ await this.delete(key);
14041
+ }
14042
+ async invalidateTags(tags) {
14043
+ const keysToDelete = /* @__PURE__ */ new Set();
14044
+ for (const tag of tags) {
14045
+ const keys = this.tagIndex.get(tag);
14046
+ if (keys) {
14047
+ for (const key of keys) {
14048
+ keysToDelete.add(key);
14049
+ }
14050
+ this.tagIndex.delete(tag);
14051
+ }
14052
+ }
14053
+ for (const key of keysToDelete) {
14054
+ this.storage.delete(key);
14055
+ }
14056
+ }
14057
+ async invalidatePrefix(prefix) {
14058
+ const keysToDelete = [];
14059
+ for (const key of this.storage.keys()) {
14060
+ if (key.startsWith(prefix)) {
14061
+ keysToDelete.push(key);
14062
+ }
14063
+ }
14064
+ for (const key of keysToDelete) {
14065
+ await this.delete(key);
14066
+ }
14067
+ }
14068
+ /**
14069
+ * Registra uma chave com tags (para invalidação)
14070
+ */
14071
+ registerTags(key, tags) {
14072
+ for (const tag of tags) {
14073
+ if (!this.tagIndex.has(tag)) {
14074
+ this.tagIndex.set(tag, /* @__PURE__ */ new Set());
14075
+ }
14076
+ this.tagIndex.get(tag).add(key);
14077
+ }
14078
+ }
14079
+ /**
14080
+ * Limpa todo o cache
14081
+ */
14082
+ clear() {
14083
+ this.storage.clear();
14084
+ this.tagIndex.clear();
14085
+ }
14086
+ /**
14087
+ * Retorna estatísticas do cache
14088
+ */
14089
+ getStats() {
14090
+ return {
14091
+ size: this.storage.size,
14092
+ tags: this.tagIndex.size
14093
+ };
14094
+ }
14095
+ async dispose() {
14096
+ this.clear();
14097
+ }
14098
+ };
14099
+
14100
+ // src/cache/query-cache-manager.ts
14101
+ var QueryCacheManager = class {
14102
+ constructor(provider = new MemoryCacheAdapter(), strategy = new DefaultCacheStrategy(), defaultTtl = "1h") {
14103
+ this.provider = provider;
14104
+ this.strategy = strategy;
14105
+ this.defaultTtl = defaultTtl;
14106
+ }
14107
+ /**
14108
+ * Executa com cache - padrão execute-around
14109
+ * @returns Resultado da execução (do cache ou da função)
14110
+ */
14111
+ async getOrExecute(options, executor, tenantId) {
14112
+ const key = this.strategy.generateKey(options.key, tenantId);
14113
+ const ttlMs = this.parseDuration(options.ttl ?? this.defaultTtl);
14114
+ const cached = await this.provider.get(key);
14115
+ if (cached !== void 0) {
14116
+ return this.strategy.deserialize(cached);
14117
+ }
14118
+ const result = await executor();
14119
+ if (!this.strategy.shouldCache(result, options)) {
14120
+ return result;
14121
+ }
14122
+ const serialized = this.strategy.serialize(result);
14123
+ await this.provider.set(key, serialized, ttlMs);
14124
+ if (options.tags) {
14125
+ await this.registerTags(key, options.tags);
14126
+ }
14127
+ return result;
14128
+ }
14129
+ /**
14130
+ * Invalida uma chave específica
14131
+ */
14132
+ async invalidateKey(key, tenantId) {
14133
+ const fullKey = this.strategy.generateKey(key, tenantId);
14134
+ await this.provider.invalidate(fullKey);
14135
+ }
14136
+ /**
14137
+ * Invalida por tags
14138
+ */
14139
+ async invalidateTags(tags) {
14140
+ await this.provider.invalidateTags(tags);
14141
+ }
14142
+ /**
14143
+ * Invalida por prefixo (útil para multi-tenancy)
14144
+ */
14145
+ async invalidatePrefix(prefix) {
14146
+ await this.provider.invalidatePrefix(prefix);
14147
+ }
14148
+ /**
14149
+ * Limpa todo o cache (cuidado!)
14150
+ */
14151
+ async clear() {
14152
+ const provider = this.provider;
14153
+ if (typeof provider.clear === "function") {
14154
+ provider.clear();
14155
+ } else {
14156
+ throw new Error("Cache provider does not support clear operation");
14157
+ }
14158
+ }
14159
+ /**
14160
+ * Retorna estatísticas do cache (se disponível)
14161
+ */
14162
+ getStats() {
14163
+ const provider = this.provider;
14164
+ if (typeof provider.getStats === "function") {
14165
+ return provider.getStats();
14166
+ }
14167
+ return void 0;
14168
+ }
14169
+ /**
14170
+ * Libera recursos do cache
14171
+ */
14172
+ async dispose() {
14173
+ await this.provider.dispose?.();
14174
+ }
14175
+ /**
14176
+ * Registra tags para uma chave
14177
+ */
14178
+ async registerTags(key, tags) {
14179
+ const provider = this.provider;
14180
+ if (typeof provider.registerTags === "function") {
14181
+ provider.registerTags(key, tags);
14182
+ }
14183
+ }
14184
+ /**
14185
+ * Converte duração para milissegundos
14186
+ */
14187
+ parseDuration(d) {
14188
+ return parseDuration(d);
14189
+ }
14190
+ };
14191
+
13649
14192
  // src/orm/orm.ts
13650
14193
  var Orm = class {
13651
14194
  /** The database dialect */
@@ -13654,6 +14197,8 @@ var Orm = class {
13654
14197
  interceptors;
13655
14198
  /** The naming strategy */
13656
14199
  namingStrategy;
14200
+ /** The cache manager (if configured) */
14201
+ cacheManager;
13657
14202
  executorFactory;
13658
14203
  /**
13659
14204
  * Creates a new ORM instance.
@@ -13664,15 +14209,27 @@ var Orm = class {
13664
14209
  this.interceptors = opts.interceptors ?? new InterceptorPipeline();
13665
14210
  this.namingStrategy = opts.namingStrategy ?? new DefaultNamingStrategy();
13666
14211
  this.executorFactory = opts.executorFactory;
14212
+ if (opts.cache) {
14213
+ this.cacheManager = new QueryCacheManager(
14214
+ opts.cache.provider,
14215
+ opts.cache.strategy ?? new DefaultCacheStrategy(),
14216
+ opts.cache.defaultTtl ?? "1h"
14217
+ );
14218
+ }
13667
14219
  }
13668
14220
  /**
13669
14221
  * Creates a new ORM session.
13670
- * @param options - Optional session options
14222
+ * @param options - Optional session options (e.g., tenantId for multi-tenancy)
13671
14223
  * @returns The ORM session
13672
14224
  */
13673
- createSession() {
14225
+ createSession(options) {
13674
14226
  const executor = this.executorFactory.createExecutor();
13675
- return new OrmSession({ orm: this, executor });
14227
+ return new OrmSession({
14228
+ orm: this,
14229
+ executor,
14230
+ cacheManager: this.cacheManager,
14231
+ tenantId: options?.tenantId
14232
+ });
13676
14233
  }
13677
14234
  /**
13678
14235
  * Executes a function within a transaction.
@@ -13683,7 +14240,11 @@ var Orm = class {
13683
14240
  */
13684
14241
  async transaction(fn8) {
13685
14242
  const executor = this.executorFactory.createTransactionalExecutor();
13686
- const session = new OrmSession({ orm: this, executor });
14243
+ const session = new OrmSession({
14244
+ orm: this,
14245
+ executor,
14246
+ cacheManager: this.cacheManager
14247
+ });
13687
14248
  try {
13688
14249
  return await session.transaction(() => fn8(session));
13689
14250
  } finally {
@@ -14848,7 +15409,15 @@ function createMysqlExecutor(client) {
14848
15409
  async executeSql(sql, params) {
14849
15410
  const [rows] = await client.query(sql, params);
14850
15411
  if (!Array.isArray(rows)) {
14851
- return [{ columns: [], values: [] }];
15412
+ const header = rows;
15413
+ return [{
15414
+ columns: [],
15415
+ values: [],
15416
+ meta: {
15417
+ insertId: header.insertId,
15418
+ rowsAffected: header.affectedRows
15419
+ }
15420
+ }];
14852
15421
  }
14853
15422
  const result = rowsToQueryResult(
14854
15423
  rows
@@ -18157,6 +18726,162 @@ function queryResultsToRows(results) {
18157
18726
  }
18158
18727
  return rows;
18159
18728
  }
18729
+
18730
+ // src/cache/adapters/keyv-cache-adapter.ts
18731
+ var KeyvCacheAdapter = class {
18732
+ constructor(keyv) {
18733
+ this.keyv = keyv;
18734
+ }
18735
+ name = "keyv";
18736
+ async get(key) {
18737
+ return this.keyv.get(key);
18738
+ }
18739
+ async has(key) {
18740
+ const value = await this.keyv.get(key);
18741
+ return value !== void 0;
18742
+ }
18743
+ async set(key, value, ttlMs) {
18744
+ await this.keyv.set(key, value, ttlMs);
18745
+ }
18746
+ async delete(key) {
18747
+ await this.keyv.delete(key);
18748
+ }
18749
+ async invalidate(key) {
18750
+ await this.delete(key);
18751
+ }
18752
+ async invalidateTags(_tags) {
18753
+ throw new Error(
18754
+ "Keyv adapter does not support tag invalidation. Use MemoryCacheAdapter for testing or implement a custom Redis provider."
18755
+ );
18756
+ }
18757
+ async invalidatePrefix(prefix) {
18758
+ if (typeof this.keyv.iterator === "function") {
18759
+ const keys = [];
18760
+ for await (const [key] of this.keyv.iterator()) {
18761
+ if (key.startsWith(prefix)) {
18762
+ keys.push(key);
18763
+ }
18764
+ }
18765
+ if (keys.length > 0) {
18766
+ await Promise.all(keys.map((k) => this.keyv.delete(k)));
18767
+ }
18768
+ return;
18769
+ }
18770
+ throw new Error(
18771
+ "Keyv adapter does not support prefix invalidation in this store. Consider using a store with iterator support."
18772
+ );
18773
+ }
18774
+ async dispose() {
18775
+ await this.keyv.disconnect?.();
18776
+ }
18777
+ };
18778
+
18779
+ // src/cache/tag-index.ts
18780
+ var TagIndex = class {
18781
+ tagToKeys = /* @__PURE__ */ new Map();
18782
+ keyToTags = /* @__PURE__ */ new Map();
18783
+ /**
18784
+ * Registra que uma chave pertence a determinadas tags
18785
+ */
18786
+ register(key, tags) {
18787
+ for (const tag of tags) {
18788
+ if (!this.tagToKeys.has(tag)) {
18789
+ this.tagToKeys.set(tag, /* @__PURE__ */ new Set());
18790
+ }
18791
+ this.tagToKeys.get(tag).add(key);
18792
+ }
18793
+ const existingTags = this.keyToTags.get(key) ?? /* @__PURE__ */ new Set();
18794
+ tags.forEach((tag) => existingTags.add(tag));
18795
+ this.keyToTags.set(key, existingTags);
18796
+ }
18797
+ /**
18798
+ * Remove uma chave do índice
18799
+ */
18800
+ unregister(key) {
18801
+ const tags = this.keyToTags.get(key);
18802
+ if (tags) {
18803
+ for (const tag of tags) {
18804
+ this.tagToKeys.get(tag)?.delete(key);
18805
+ if (this.tagToKeys.get(tag)?.size === 0) {
18806
+ this.tagToKeys.delete(tag);
18807
+ }
18808
+ }
18809
+ this.keyToTags.delete(key);
18810
+ }
18811
+ }
18812
+ /**
18813
+ * Obtém todas as chaves de uma tag
18814
+ */
18815
+ getKeysByTag(tag) {
18816
+ return Array.from(this.tagToKeys.get(tag) ?? []);
18817
+ }
18818
+ /**
18819
+ * Obtém todas as tags de uma chave
18820
+ */
18821
+ getTagsByKey(key) {
18822
+ return Array.from(this.keyToTags.get(key) ?? []);
18823
+ }
18824
+ /**
18825
+ * Invalida todas as chaves de um conjunto de tags
18826
+ * Retorna as chaves afetadas
18827
+ */
18828
+ invalidateTags(tags) {
18829
+ const keysToInvalidate = /* @__PURE__ */ new Set();
18830
+ for (const tag of tags) {
18831
+ const keys = this.tagToKeys.get(tag);
18832
+ if (keys) {
18833
+ for (const key of keys) {
18834
+ keysToInvalidate.add(key);
18835
+ this.unregister(key);
18836
+ }
18837
+ this.tagToKeys.delete(tag);
18838
+ }
18839
+ }
18840
+ return Array.from(keysToInvalidate);
18841
+ }
18842
+ /**
18843
+ * Invalida por prefixo (útil para multi-tenancy)
18844
+ * Retorna as chaves afetadas
18845
+ */
18846
+ invalidatePrefix(prefix) {
18847
+ const keysToInvalidate = [];
18848
+ for (const key of this.keyToTags.keys()) {
18849
+ if (key.startsWith(prefix)) {
18850
+ keysToInvalidate.push(key);
18851
+ this.unregister(key);
18852
+ }
18853
+ }
18854
+ return keysToInvalidate;
18855
+ }
18856
+ /**
18857
+ * Retorna todas as tags registradas
18858
+ */
18859
+ getAllTags() {
18860
+ return Array.from(this.tagToKeys.keys());
18861
+ }
18862
+ /**
18863
+ * Retorna todas as chaves registradas
18864
+ */
18865
+ getAllKeys() {
18866
+ return Array.from(this.keyToTags.keys());
18867
+ }
18868
+ /**
18869
+ * Limpa todo o índice
18870
+ */
18871
+ clear() {
18872
+ this.tagToKeys.clear();
18873
+ this.keyToTags.clear();
18874
+ }
18875
+ /**
18876
+ * Retorna estatísticas do índice
18877
+ */
18878
+ getStats() {
18879
+ return {
18880
+ tags: this.tagToKeys.size,
18881
+ keys: this.keyToTags.size
18882
+ };
18883
+ }
18884
+ };
18160
18885
  // Annotate the CommonJS export names for ESM import in node:
18161
18886
  0 && (module.exports = {
18162
18887
  Alphanumeric,
@@ -18176,6 +18901,7 @@ function queryResultsToRows(results) {
18176
18901
  DateTimeTypeStrategy,
18177
18902
  DecimalTypeStrategy,
18178
18903
  DefaultBelongsToReference,
18904
+ DefaultCacheStrategy,
18179
18905
  DefaultEntityMaterializer,
18180
18906
  DefaultHasManyCollection,
18181
18907
  DefaultManyToManyCollection,
@@ -18190,8 +18916,10 @@ function queryResultsToRows(results) {
18190
18916
  InsertQueryBuilder,
18191
18917
  IntegerTypeStrategy,
18192
18918
  InterceptorPipeline,
18919
+ KeyvCacheAdapter,
18193
18920
  Length,
18194
18921
  Lower,
18922
+ MemoryCacheAdapter,
18195
18923
  MySqlDialect,
18196
18924
  NestedSetStrategy,
18197
18925
  Orm,
@@ -18201,12 +18929,14 @@ function queryResultsToRows(results) {
18201
18929
  PostgresDialect,
18202
18930
  PrimaryKey,
18203
18931
  PrototypeMaterializationStrategy,
18932
+ QueryCacheManager,
18204
18933
  RelationKinds,
18205
18934
  STANDARD_COLUMN_TYPES,
18206
18935
  SelectQueryBuilder,
18207
18936
  SqlServerDialect,
18208
18937
  SqliteDialect,
18209
18938
  StringTypeStrategy,
18939
+ TagIndex,
18210
18940
  Title,
18211
18941
  Tree,
18212
18942
  TreeChildren,
@@ -18326,6 +19056,7 @@ function queryResultsToRows(results) {
18326
19056
  extractScopeValues,
18327
19057
  firstValue,
18328
19058
  floor,
19059
+ formatDuration,
18329
19060
  formatTreeList,
18330
19061
  fromUnixTime,
18331
19062
  generateComponentSchemas,
@@ -18381,6 +19112,7 @@ function queryResultsToRows(results) {
18381
19112
  isOperandNode,
18382
19113
  isTableDef,
18383
19114
  isTreeConfig,
19115
+ isValidDuration,
18384
19116
  isValueOperandInput,
18385
19117
  isWindowFunctionNode,
18386
19118
  jsonArrayAgg,
@@ -18441,6 +19173,7 @@ function queryResultsToRows(results) {
18441
19173
  pagedResponseToOpenApiSchema,
18442
19174
  paginationParamsSchema,
18443
19175
  parameterToRef,
19176
+ parseDuration,
18444
19177
  pi,
18445
19178
  pick,
18446
19179
  position,