@spfn/core 0.2.0-beta.30 → 0.2.0-beta.32

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.
@@ -854,6 +854,10 @@ declare function getSchemaInfo(packageName: string): {
854
854
  * Uses Record<string, unknown> to accept any schema shape
855
855
  */
856
856
  type TransactionDB = PostgresJsDatabase<Record<string, unknown>>;
857
+ /**
858
+ * afterCommit callback type
859
+ */
860
+ type AfterCommitCallback = () => void | Promise<void>;
857
861
  /**
858
862
  * Transaction context stored in AsyncLocalStorage
859
863
  */
@@ -863,6 +867,8 @@ type TransactionContext = {
863
867
  /** Unique transaction ID for logging and tracing */
864
868
  txId: string;
865
869
  level: number;
870
+ /** Callbacks to execute after root transaction commits */
871
+ afterCommitCallbacks: AfterCommitCallback[];
866
872
  };
867
873
  /**
868
874
  * Get current transaction from AsyncLocalStorage
@@ -883,6 +889,31 @@ declare function getTransaction(): TransactionDB | null;
883
889
  */
884
890
  declare function runWithTransaction<T>(tx: TransactionDB, txId: string, // Add txId parameter
885
891
  callback: () => Promise<T>): Promise<T>;
892
+ /**
893
+ * Register a callback to run after the current transaction commits
894
+ *
895
+ * - Inside a transaction: queued and executed after root transaction commits
896
+ * - Outside a transaction: executed immediately (already "committed")
897
+ * - Nested transactions: callbacks bubble up to root transaction
898
+ * - Callbacks run outside transaction context (new connection for DB access)
899
+ * - Errors are logged but never thrown (commit already succeeded)
900
+ *
901
+ * @example
902
+ * ```typescript
903
+ * import { onAfterCommit } from '@spfn/core/db/transaction';
904
+ *
905
+ * async function submit(spaceId: string, chatId: string)
906
+ * {
907
+ * const publication = await publicationRepo.create({...});
908
+ * await requestRepo.updateStatusAtomically(...);
909
+ *
910
+ * onAfterCommit(() => generateArticle(spaceId, chatId, publication.id));
911
+ *
912
+ * return publication;
913
+ * }
914
+ * ```
915
+ */
916
+ declare function onAfterCommit(callback: AfterCommitCallback): void;
886
917
 
887
918
  /**
888
919
  * Transaction middleware options
@@ -1571,4 +1602,4 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1571
1602
  protected _count<T extends PgTable>(table: T, where?: Record<string, any> | SQL | undefined): Promise<number>;
1572
1603
  }
1573
1604
 
1574
- export { BaseRepository, type DatabaseClients, type DrizzleConfigOptions, type PoolConfig, RepositoryError, type RetryConfig, type TransactionContext, type TransactionDB, Transactional, type TransactionalOptions, auditFields, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, enumText, findMany, findOne, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, id, initDatabase, optionalForeignKey, packageNameToSchema, publishingFields, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
1605
+ export { type AfterCommitCallback, BaseRepository, type DatabaseClients, type DrizzleConfigOptions, type PoolConfig, RepositoryError, type RetryConfig, type TransactionContext, type TransactionDB, Transactional, type TransactionalOptions, auditFields, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, enumText, findMany, findOne, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, id, initDatabase, onAfterCommit, optionalForeignKey, packageNameToSchema, publishingFields, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
package/dist/db/index.js CHANGED
@@ -1207,7 +1207,20 @@ function runWithTransaction(tx, txId, callback) {
1207
1207
  } else {
1208
1208
  txLogger.debug("Root transaction context set", { txId, level: newLevel });
1209
1209
  }
1210
- return asyncContext.run({ tx, txId, level: newLevel }, callback);
1210
+ const afterCommitCallbacks = existingContext ? existingContext.afterCommitCallbacks : [];
1211
+ return asyncContext.run({ tx, txId, level: newLevel, afterCommitCallbacks }, callback);
1212
+ }
1213
+ function onAfterCommit(callback) {
1214
+ const context = getTransactionContext();
1215
+ if (!context) {
1216
+ Promise.resolve().then(callback).catch((err) => {
1217
+ txLogger.error("afterCommit callback failed (no transaction)", {
1218
+ error: err instanceof Error ? err.message : String(err)
1219
+ });
1220
+ });
1221
+ return;
1222
+ }
1223
+ context.afterCommitCallbacks.push(callback);
1211
1224
  }
1212
1225
  var MAX_TIMEOUT_MS = 2147483647;
1213
1226
  var txLogger2 = logger.child("@spfn/core:transaction");
@@ -1290,13 +1303,21 @@ async function runInTransaction(callback, options = {}) {
1290
1303
  txLogger2.debug("Transaction started", { txId, context });
1291
1304
  }
1292
1305
  const startTime = Date.now();
1306
+ let afterCommitCallbacks = [];
1293
1307
  try {
1294
1308
  const result = await writeDb.transaction(async (tx) => {
1295
1309
  if (timeout > 0 && !isNested) {
1296
1310
  await tx.execute(sql.raw(`SET LOCAL statement_timeout = ${timeout}`));
1297
1311
  }
1298
1312
  return await runWithTransaction(tx, txId, async () => {
1299
- return await callback(tx);
1313
+ const innerResult = await callback(tx);
1314
+ if (!isNested) {
1315
+ const ctx = getTransactionContext();
1316
+ if (ctx) {
1317
+ afterCommitCallbacks = [...ctx.afterCommitCallbacks];
1318
+ }
1319
+ }
1320
+ return innerResult;
1300
1321
  });
1301
1322
  });
1302
1323
  const duration = Date.now() - startTime;
@@ -1316,6 +1337,24 @@ async function runInTransaction(callback, options = {}) {
1316
1337
  });
1317
1338
  }
1318
1339
  }
1340
+ if (!isNested && afterCommitCallbacks.length > 0) {
1341
+ if (enableLogging) {
1342
+ txLogger2.debug("Executing afterCommit callbacks", {
1343
+ txId,
1344
+ context,
1345
+ count: afterCommitCallbacks.length
1346
+ });
1347
+ }
1348
+ for (const cb of afterCommitCallbacks) {
1349
+ Promise.resolve().then(cb).catch((err) => {
1350
+ txLogger2.error("afterCommit callback failed", {
1351
+ txId,
1352
+ context,
1353
+ error: err instanceof Error ? err.message : String(err)
1354
+ });
1355
+ });
1356
+ }
1357
+ }
1319
1358
  return result;
1320
1359
  } catch (error) {
1321
1360
  const duration = Date.now() - startTime;
@@ -1861,6 +1900,6 @@ var BaseRepository = class {
1861
1900
  }
1862
1901
  };
1863
1902
 
1864
- export { BaseRepository, RepositoryError, Transactional, auditFields, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, enumText, findMany, findOne, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, id, initDatabase, optionalForeignKey, packageNameToSchema, publishingFields, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
1903
+ export { BaseRepository, RepositoryError, Transactional, auditFields, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, enumText, findMany, findOne, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, id, initDatabase, onAfterCommit, optionalForeignKey, packageNameToSchema, publishingFields, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
1865
1904
  //# sourceMappingURL=index.js.map
1866
1905
  //# sourceMappingURL=index.js.map