@fonderie/auth 7.11.1 → 7.12.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.d.cts CHANGED
@@ -230,8 +230,11 @@ declare function importUser(store: IStoreAdapter, user: IImportUser): Promise<{
230
230
 
231
231
  interface IPurgeOptions {
232
232
  olderThanDays: number;
233
+ bus?: {
234
+ emit(type: string, payload: unknown): Promise<void>;
235
+ } | undefined;
233
236
  }
234
- declare function purgeSoftDeletedUsers(store: IStoreAdapter, { olderThanDays }: IPurgeOptions): Promise<number>;
237
+ declare function purgeSoftDeletedUsers(store: IStoreAdapter, { olderThanDays, bus }: IPurgeOptions): Promise<number>;
235
238
  interface IUserRetentionScheduleOptions extends IPurgeOptions {
236
239
  intervalMs?: number;
237
240
  onPurge?: (deleted: number) => void;
package/dist/index.d.ts CHANGED
@@ -230,8 +230,11 @@ declare function importUser(store: IStoreAdapter, user: IImportUser): Promise<{
230
230
 
231
231
  interface IPurgeOptions {
232
232
  olderThanDays: number;
233
+ bus?: {
234
+ emit(type: string, payload: unknown): Promise<void>;
235
+ } | undefined;
233
236
  }
234
- declare function purgeSoftDeletedUsers(store: IStoreAdapter, { olderThanDays }: IPurgeOptions): Promise<number>;
237
+ declare function purgeSoftDeletedUsers(store: IStoreAdapter, { olderThanDays, bus }: IPurgeOptions): Promise<number>;
235
238
  interface IUserRetentionScheduleOptions extends IPurgeOptions {
236
239
  intervalMs?: number;
237
240
  onPurge?: (deleted: number) => void;
package/dist/index.js CHANGED
@@ -275,6 +275,8 @@ var MESSAGE_KEYS = {
275
275
  var EVENT_KEYS = {
276
276
  userRegistered: "fonderie.user.registered",
277
277
  userDeleted: "fonderie.user.deleted",
278
+ // Emitted by purgeSoftDeletedUsers (given a bus) once the row is hard-deleted.
279
+ userPurged: "fonderie.user.purged",
278
280
  emailVerified: "fonderie.user.email_verified",
279
281
  passwordChanged: "fonderie.user.password_changed"
280
282
  };
@@ -548,7 +550,7 @@ var UserModel = class {
548
550
  async list(query = {}) {
549
551
  const limit = Math.min(query.limit ?? 50, MAX_LIST_LIMIT);
550
552
  const params = [];
551
- const where = ["deleted_at IS NULL"];
553
+ const where = [query.deleted ? "deleted_at IS NOT NULL" : "deleted_at IS NULL"];
552
554
  if (query.cursor) {
553
555
  params.push(query.cursor.createdAt, query.cursor.id);
554
556
  where.push(`(created_at, id) < ($${params.length - 1}::timestamptz, $${params.length}::uuid)`);
@@ -564,6 +566,16 @@ var UserModel = class {
564
566
  );
565
567
  return { users: rows.slice(0, limit), hasMore: rows.length > limit };
566
568
  }
569
+ // For operator views only: a soft-deleted account still resolves, with
570
+ // deletedAt set, so the console can say "deleted on …" instead of "no such
571
+ // user". Nothing that authenticates or acts on an account may use this.
572
+ async findByIdIncludingDeleted(id) {
573
+ const [row] = await this.store.query(
574
+ `SELECT ${USER_COLUMNS} FROM fonderie_users WHERE id = $1`,
575
+ [id]
576
+ );
577
+ return row ?? null;
578
+ }
567
579
  async findById(id) {
568
580
  const [row] = await this.store.query(
569
581
  `SELECT ${USER_COLUMNS} FROM fonderie_users WHERE id = $1 AND deleted_at IS NULL`,
@@ -3381,7 +3393,12 @@ function adminRouteTable(store) {
3381
3393
  const cursor = cursorParam ? decodeLoginCursor(cursorParam) : null;
3382
3394
  if (cursorParam && !cursor)
3383
3395
  return setApiResponse7(HTTP7.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid cursor");
3384
- const page = await users.list({ limit, ...cursor ? { cursor } : {} });
3396
+ const deleted = params.get("deleted") === "1" || params.get("deleted") === "true";
3397
+ const page = await users.list({
3398
+ limit,
3399
+ ...cursor ? { cursor } : {},
3400
+ ...deleted ? { deleted } : {}
3401
+ });
3385
3402
  return setApiResponse7(HTTP7.OK, "USERS", "Users", toAdminUserPageDTO(page));
3386
3403
  }
3387
3404
  ],
@@ -3389,7 +3406,7 @@ function adminRouteTable(store) {
3389
3406
  "GET",
3390
3407
  "/_admin/users/:id",
3391
3408
  async (ctx) => {
3392
- const user = await users.findById(idOf(ctx));
3409
+ const user = await users.findByIdIncludingDeleted(idOf(ctx));
3393
3410
  return user ? setApiResponse7(HTTP7.OK, "USER", "User", toAdminUserDTO(user)) : NOT_FOUND();
3394
3411
  }
3395
3412
  ],
@@ -3468,7 +3485,7 @@ var AuthModule = class {
3468
3485
  name = "@fonderie/auth";
3469
3486
  // Baked in at build time by tsup.base, so the operator's Modules page can
3470
3487
  // say what is actually deployed rather than 'not reported'.
3471
- version = "7.11.1";
3488
+ version = "7.12.0";
3472
3489
  // Users, sessions, login history, suspend — only through @fonderie/admin.
3473
3490
  describeAdmin() {
3474
3491
  return { routes: describeAuthAdminRoutes(this.store) };
@@ -3699,7 +3716,7 @@ async function importUser(store, user) {
3699
3716
  }
3700
3717
 
3701
3718
  // src/services/retention.ts
3702
- async function purgeSoftDeletedUsers(store, { olderThanDays }) {
3719
+ async function purgeSoftDeletedUsers(store, { olderThanDays, bus }) {
3703
3720
  if (!Number.isFinite(olderThanDays) || olderThanDays < 0) {
3704
3721
  throw new Error("[auth] purgeSoftDeletedUsers: olderThanDays must be a non-negative number");
3705
3722
  }
@@ -3710,6 +3727,9 @@ async function purgeSoftDeletedUsers(store, { olderThanDays }) {
3710
3727
  RETURNING id`,
3711
3728
  [olderThanDays]
3712
3729
  );
3730
+ if (bus) {
3731
+ for (const { id } of rows) await bus.emit("fonderie.user.purged", { userId: id });
3732
+ }
3713
3733
  return rows.length;
3714
3734
  }
3715
3735
  function startUserRetention(store, options) {
@@ -3718,7 +3738,10 @@ function startUserRetention(store, options) {
3718
3738
  const run = async () => {
3719
3739
  if (stopped) return;
3720
3740
  try {
3721
- const deleted = await purgeSoftDeletedUsers(store, { olderThanDays: options.olderThanDays });
3741
+ const deleted = await purgeSoftDeletedUsers(store, {
3742
+ olderThanDays: options.olderThanDays,
3743
+ ...options.bus ? { bus: options.bus } : {}
3744
+ });
3722
3745
  options.onPurge?.(deleted);
3723
3746
  } catch (err) {
3724
3747
  console.error("[auth] scheduled user retention purge failed:", err);