@porulle/core 0.53.1 → 0.54.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.
|
|
3
|
+
"version": "0.54.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/
|
|
66
|
-
"@porulle/
|
|
65
|
+
"@porulle/eslint-config": "0.1.0",
|
|
66
|
+
"@porulle/typescript-config": "0.1.0"
|
|
67
67
|
},
|
|
68
68
|
"publishConfig": {
|
|
69
69
|
"access": "public"
|
|
@@ -24,7 +24,7 @@ import { paginate } from "../../utils/pagination.js";
|
|
|
24
24
|
import type { JobsAdapter } from "../../kernel/jobs/adapter.js";
|
|
25
25
|
import type { PluginDb } from "../../kernel/database/plugin-types.js";
|
|
26
26
|
import type { CatalogWriteContext, TxContext } from "../../kernel/database/tx-context.js";
|
|
27
|
-
import { isWriteContextTransactional } from "../../kernel/database/tx-context.js";
|
|
27
|
+
import { createTxContext, isWriteContextTransactional } from "../../kernel/database/tx-context.js";
|
|
28
28
|
import { canReadUnpublishedCatalog, isCatalogEntityVisible } from "./read-policy.js";
|
|
29
29
|
import { isValidFieldPath } from "./ownership.js";
|
|
30
30
|
import type {
|
|
@@ -519,21 +519,36 @@ export class EntityService {
|
|
|
519
519
|
try { this.assertSameOrg(entity, actor); } catch (error) { return Err(toCommerceError(error)); }
|
|
520
520
|
const beforeAttr = await this.repo.findAttributeByLocale(entityId, locale, txCtx);
|
|
521
521
|
const changedFieldPaths: string[] = [];
|
|
522
|
+
let changed = false;
|
|
522
523
|
for (const field of attributeFields) {
|
|
523
524
|
if (attrs[field] === undefined) continue;
|
|
524
525
|
const before = beforeAttr?.[field] ?? null;
|
|
525
526
|
const after = attrs[field] ?? null;
|
|
526
527
|
if (before !== after) {
|
|
528
|
+
changed = true;
|
|
527
529
|
const path = `attributes.${locale}.${field}`;
|
|
528
530
|
if (isValidFieldPath(path)) changedFieldPaths.push(path);
|
|
529
531
|
}
|
|
530
532
|
}
|
|
531
|
-
|
|
533
|
+
// An identical re-set writes nothing and moves nothing: an idempotent save must not look like an
|
|
534
|
+
// edit to anything that versions the product.
|
|
535
|
+
if (!changed && beforeAttr) return Ok(undefined);
|
|
536
|
+
|
|
537
|
+
// The attribute write and the entity's `updated_at` bump commit together. Consumers version a
|
|
538
|
+
// product from `sellable_entities.updated_at`; an attribute change that left it unmoved was
|
|
539
|
+
// invisible to them — a changed title never re-indexed (sim, 2026-09-24).
|
|
540
|
+
const write = async (writeCtx: TxContext): Promise<SellableEntity | undefined> => {
|
|
541
|
+
await this.repo.upsertAttribute(entityId, locale, { title: attrs.title, subtitle: attrs.subtitle, description: attrs.description, richDescription: attrs.richDescription, seoTitle: attrs.seoTitle, seoDescription: attrs.seoDescription }, writeCtx);
|
|
542
|
+
return changed ? this.repo.updateEntity(entityId, {}, writeCtx) : undefined;
|
|
543
|
+
};
|
|
544
|
+
const updated = txCtx
|
|
545
|
+
? await write(txCtx)
|
|
546
|
+
: await this.deps.database.transaction((tx) => write(createTxContext(tx, { actor })));
|
|
532
547
|
if (changedFieldPaths.length > 0) {
|
|
533
548
|
const afterHooks = this.deps.hooks.resolve("catalog.afterUpdate") as CatalogUpdateAfterHook[];
|
|
534
549
|
const context = catalogHookContext(this.deps, actor, ctx, "update");
|
|
535
550
|
context.context.changedFieldPaths = changedFieldPaths;
|
|
536
|
-
await runAfterHooks(afterHooks, entity, entity, "update", context, (hook) => this.deps.hooks.runsInTransaction(hook));
|
|
551
|
+
await runAfterHooks(afterHooks, entity, updated ?? entity, "update", context, (hook) => this.deps.hooks.runsInTransaction(hook));
|
|
537
552
|
}
|
|
538
553
|
return Ok(undefined);
|
|
539
554
|
}
|
|
@@ -563,10 +578,13 @@ export class EntityService {
|
|
|
563
578
|
const txCtx = isWriteContextTransactional(ctx) ? ctx : undefined;
|
|
564
579
|
const entity = await this.repo.findEntityById(entityId, txCtx);
|
|
565
580
|
if (!entity) return;
|
|
581
|
+
// The related rows changed, so the entity's `updated_at` moves with them (in the caller's
|
|
582
|
+
// transaction when it has one) — the same versioning contract as `setAttributes`.
|
|
583
|
+
const updated = await this.repo.updateEntity(entityId, {}, txCtx);
|
|
566
584
|
const afterHooks = this.deps.hooks.resolve("catalog.afterUpdate") as CatalogUpdateAfterHook[];
|
|
567
585
|
const context = catalogHookContext(this.deps, actor, ctx, "update");
|
|
568
586
|
context.context.changedFieldPaths = [...changedFieldPaths];
|
|
569
|
-
await runAfterHooks(afterHooks, entity, entity, "update", context, (hook) => this.deps.hooks.runsInTransaction(hook));
|
|
587
|
+
await runAfterHooks(afterHooks, entity, updated ?? entity, "update", context, (hook) => this.deps.hooks.runsInTransaction(hook));
|
|
570
588
|
}
|
|
571
589
|
|
|
572
590
|
async getAttributes(entityId: string, locale: string, actor: Actor | null, ctx?: TxContext): Promise<Result<SellableAttribute>> {
|