@porulle/core 0.50.3 → 0.52.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/core",
3
- "version": "0.50.3",
3
+ "version": "0.52.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -62,8 +62,8 @@
62
62
  "eslint": "^9.39.1",
63
63
  "typescript": "5.9.2",
64
64
  "vitest": "^3.2.4",
65
- "@porulle/eslint-config": "0.1.0",
66
- "@porulle/typescript-config": "0.1.0"
65
+ "@porulle/typescript-config": "0.1.0",
66
+ "@porulle/eslint-config": "0.1.0"
67
67
  },
68
68
  "publishConfig": {
69
69
  "access": "public"
@@ -1,4 +1,4 @@
1
- import { eq, and, asc, count, desc, gt, inArray, isNull, lt, max, ne, or, type SQL } from "drizzle-orm";
1
+ import { eq, and, asc, count, desc, gt, inArray, isNull, lt, max, ne, or, sql, type SQL } from "drizzle-orm";
2
2
  import type { TxContext } from "../../../kernel/database/tx-context.js";
3
3
  import { CommerceNotFoundError } from "../../../kernel/errors.js";
4
4
  import type {
@@ -1290,12 +1290,29 @@ export class CatalogRepository {
1290
1290
  const db = this.getDb(ctx);
1291
1291
  const rows = await db
1292
1292
  .update(variants)
1293
- .set(data)
1293
+ .set({ ...data, updatedAt: sql`now()` })
1294
1294
  .where(eq(variants.id, id))
1295
1295
  .returning();
1296
1296
  return rows[0];
1297
1297
  }
1298
1298
 
1299
+ /**
1300
+ * Run a write to variants' option values and bump those variants' `updated_at` in ONE
1301
+ * transaction (a savepoint when the caller already holds one). A bump that could commit without
1302
+ * its write, or a write without its bump, is exactly the version drift the column exists to end.
1303
+ */
1304
+ private async touchingVariants(
1305
+ variantIds: readonly string[],
1306
+ ctx: TxContext | undefined,
1307
+ write: (db: DbOrTx) => Promise<void>,
1308
+ ): Promise<void> {
1309
+ if (variantIds.length === 0) return;
1310
+ await this.getDb(ctx).transaction(async (tx) => {
1311
+ await write(tx);
1312
+ await tx.update(variants).set({ updatedAt: sql`now()` }).where(inArray(variants.id, [...new Set(variantIds)]));
1313
+ });
1314
+ }
1315
+
1299
1316
  async deleteVariantsByEntityId(
1300
1317
  entityId: string,
1301
1318
  ctx?: TxContext,
@@ -1324,32 +1341,33 @@ export class CatalogRepository {
1324
1341
  ctx?: TxContext,
1325
1342
  ): Promise<void> {
1326
1343
  if (data.length === 0) return;
1327
- const db = this.getDb(ctx);
1328
- await db.insert(variantOptionValues).values(data).onConflictDoNothing();
1344
+ await this.touchingVariants(data.map((row) => row.variantId), ctx, async (db) => {
1345
+ await db.insert(variantOptionValues).values(data).onConflictDoNothing();
1346
+ });
1329
1347
  }
1330
1348
 
1331
1349
  async deleteVariantOptionValuesByVariantId(
1332
1350
  variantId: string,
1333
1351
  ctx?: TxContext,
1334
1352
  ): Promise<void> {
1335
- const db = this.getDb(ctx);
1336
- await db
1337
- .delete(variantOptionValues)
1338
- .where(eq(variantOptionValues.variantId, variantId));
1353
+ await this.touchingVariants([variantId], ctx, async (db) => {
1354
+ await db
1355
+ .delete(variantOptionValues)
1356
+ .where(eq(variantOptionValues.variantId, variantId));
1357
+ });
1339
1358
  }
1340
1359
 
1341
1360
  async deleteVariantOptionValuesByEntityId(
1342
1361
  entityId: string,
1343
1362
  ctx?: TxContext,
1344
1363
  ): Promise<void> {
1345
- const db = this.getDb(ctx);
1346
1364
  // Get all variant IDs for this entity first
1347
1365
  const entityVariants = await this.findVariantsByEntityId(entityId, ctx);
1348
1366
  const variantIds = entityVariants.map((v) => v.id);
1349
- if (variantIds.length > 0) {
1367
+ await this.touchingVariants(variantIds, ctx, async (db) => {
1350
1368
  await db
1351
1369
  .delete(variantOptionValues)
1352
1370
  .where(inArray(variantOptionValues.variantId, variantIds));
1353
- }
1371
+ });
1354
1372
  }
1355
1373
  }
@@ -337,6 +337,10 @@ export const variants = pgTable(
337
337
  .default("active"),
338
338
  sortOrder: integer("sort_order").notNull().default(0),
339
339
  metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
340
+ // Bumped on EVERY write to the variant or its option values, so a variant-only change is
341
+ // visible to anything that versions a product from its timestamps (the entity row is not
342
+ // touched by a variant write).
343
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
340
344
  },
341
345
  (table) => ({
342
346
  barcodeIdx: index("idx_variants_barcode").on(table.barcode),