@endora-commerce/mod-comparisons 0.0.0-stage → 0.100.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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -2
  3. package/dist/admin/api/comparisons-client.d.ts +24 -0
  4. package/dist/admin/api/comparisons-client.d.ts.map +1 -0
  5. package/dist/admin/api/comparisons-client.js +15 -0
  6. package/dist/admin/api/comparisons-client.js.map +1 -0
  7. package/dist/admin/index.d.ts +37 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +49 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/ComparisonDetailPage.d.ts +19 -0
  12. package/dist/admin/pages/ComparisonDetailPage.d.ts.map +1 -0
  13. package/dist/admin/pages/ComparisonDetailPage.js +62 -0
  14. package/dist/admin/pages/ComparisonDetailPage.js.map +1 -0
  15. package/dist/admin/pages/ComparisonsListPage.d.ts +10 -0
  16. package/dist/admin/pages/ComparisonsListPage.d.ts.map +1 -0
  17. package/dist/admin/pages/ComparisonsListPage.js +71 -0
  18. package/dist/admin/pages/ComparisonsListPage.js.map +1 -0
  19. package/dist/backend/entities/comparison-product.entity.d.ts +22 -0
  20. package/dist/backend/entities/comparison-product.entity.d.ts.map +1 -0
  21. package/dist/backend/entities/comparison-product.entity.js +54 -0
  22. package/dist/backend/entities/comparison-product.entity.js.map +1 -0
  23. package/dist/backend/entities/comparison.entity.d.ts +61 -0
  24. package/dist/backend/entities/comparison.entity.d.ts.map +1 -0
  25. package/dist/backend/entities/comparison.entity.js +115 -0
  26. package/dist/backend/entities/comparison.entity.js.map +1 -0
  27. package/dist/backend/index.d.ts +61 -0
  28. package/dist/backend/index.d.ts.map +1 -0
  29. package/dist/backend/index.js +117 -0
  30. package/dist/backend/index.js.map +1 -0
  31. package/dist/backend/request-actor.d.ts +22 -0
  32. package/dist/backend/request-actor.d.ts.map +1 -0
  33. package/dist/backend/request-actor.js +24 -0
  34. package/dist/backend/request-actor.js.map +1 -0
  35. package/dist/backend/routes.admin.d.ts +22 -0
  36. package/dist/backend/routes.admin.d.ts.map +1 -0
  37. package/dist/backend/routes.admin.js +41 -0
  38. package/dist/backend/routes.admin.js.map +1 -0
  39. package/dist/backend/routes.public.d.ts +33 -0
  40. package/dist/backend/routes.public.d.ts.map +1 -0
  41. package/dist/backend/routes.public.js +215 -0
  42. package/dist/backend/routes.public.js.map +1 -0
  43. package/dist/backend/routes.share.d.ts +27 -0
  44. package/dist/backend/routes.share.d.ts.map +1 -0
  45. package/dist/backend/routes.share.js +49 -0
  46. package/dist/backend/routes.share.js.map +1 -0
  47. package/dist/backend/services/anonymous-token-cookie.d.ts +23 -0
  48. package/dist/backend/services/anonymous-token-cookie.d.ts.map +1 -0
  49. package/dist/backend/services/anonymous-token-cookie.js +33 -0
  50. package/dist/backend/services/anonymous-token-cookie.js.map +1 -0
  51. package/dist/backend/services/asset-byte-fetcher.d.ts +37 -0
  52. package/dist/backend/services/asset-byte-fetcher.d.ts.map +1 -0
  53. package/dist/backend/services/asset-byte-fetcher.js +70 -0
  54. package/dist/backend/services/asset-byte-fetcher.js.map +1 -0
  55. package/dist/backend/services/comparable-attribute-projection.d.ts +57 -0
  56. package/dist/backend/services/comparable-attribute-projection.d.ts.map +1 -0
  57. package/dist/backend/services/comparable-attribute-projection.js +136 -0
  58. package/dist/backend/services/comparable-attribute-projection.js.map +1 -0
  59. package/dist/backend/services/comparison-admin.service.d.ts +31 -0
  60. package/dist/backend/services/comparison-admin.service.d.ts.map +1 -0
  61. package/dist/backend/services/comparison-admin.service.js +184 -0
  62. package/dist/backend/services/comparison-admin.service.js.map +1 -0
  63. package/dist/backend/services/comparison-pdf-renderer.d.ts +57 -0
  64. package/dist/backend/services/comparison-pdf-renderer.d.ts.map +1 -0
  65. package/dist/backend/services/comparison-pdf-renderer.js +211 -0
  66. package/dist/backend/services/comparison-pdf-renderer.js.map +1 -0
  67. package/dist/backend/services/comparison-service.d.ts +339 -0
  68. package/dist/backend/services/comparison-service.d.ts.map +1 -0
  69. package/dist/backend/services/comparison-service.js +588 -0
  70. package/dist/backend/services/comparison-service.js.map +1 -0
  71. package/dist/backend/services/share-token-generator.d.ts +20 -0
  72. package/dist/backend/services/share-token-generator.d.ts.map +1 -0
  73. package/dist/backend/services/share-token-generator.js +23 -0
  74. package/dist/backend/services/share-token-generator.js.map +1 -0
  75. package/dist/manifest.d.ts +199 -0
  76. package/dist/manifest.d.ts.map +1 -0
  77. package/dist/manifest.js +179 -0
  78. package/dist/manifest.js.map +1 -0
  79. package/dist/migrations/20260501T185834_comparisons_init.d.ts +32 -0
  80. package/dist/migrations/20260501T185834_comparisons_init.d.ts.map +1 -0
  81. package/dist/migrations/20260501T185834_comparisons_init.js +102 -0
  82. package/dist/migrations/20260501T185834_comparisons_init.js.map +1 -0
  83. package/dist/migrations/20260830T163143_comparisons_organization_attribution.d.ts +84 -0
  84. package/dist/migrations/20260830T163143_comparisons_organization_attribution.d.ts.map +1 -0
  85. package/dist/migrations/20260830T163143_comparisons_organization_attribution.js +147 -0
  86. package/dist/migrations/20260830T163143_comparisons_organization_attribution.js.map +1 -0
  87. package/dist/migrations/index.d.ts +28 -0
  88. package/dist/migrations/index.d.ts.map +1 -0
  89. package/dist/migrations/index.js +31 -0
  90. package/dist/migrations/index.js.map +1 -0
  91. package/docs/comparisons.md +278 -0
  92. package/i18n/en.json +51 -0
  93. package/i18n/pl.json +51 -0
  94. package/package.json +102 -3
  95. package/tailwind.css +14 -0
@@ -0,0 +1,115 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Entity, Index, OptionalProps, PrimaryKey, Property, Unique } from '@mikro-orm/core';
11
+ import { CustomerScoped } from '@endora-commerce/platform/tenancy';
12
+ import { randomUUID } from 'crypto';
13
+ /**
14
+ * Comparison — feature 007 / T011 / data-model.md §1.1.
15
+ *
16
+ * A customer-owned set of product references with a chosen display mode,
17
+ * an owner (customer account or anonymous browser session), an associated
18
+ * sales channel, and a stable shareable URL token. Owner is *exclusively*
19
+ * one of the two — the DB CHECK constraint
20
+ * `comparisons_owner_xor_chk` enforces it; the service layer also
21
+ * validates before insert.
22
+ *
23
+ * `displayMode` persists across re-opens so the customer's chosen mode is
24
+ * remembered. The default `'all'` matches spec FR-008.
25
+ *
26
+ * State transitions: live → deleted (hard delete; `comparison_products`
27
+ * cascades). No soft-delete: spec FR-014 requires the share-token endpoint
28
+ * to resolve to a clear "no longer exists" state, which a missing row
29
+ * already satisfies.
30
+ */
31
+ let Comparison = class Comparison {
32
+ [OptionalProps];
33
+ id = randomUUID();
34
+ shareToken;
35
+ /**
36
+ * Set when the owner is authenticated. Mutually exclusive with
37
+ * `anonymousToken` (DB CHECK + service-side guard).
38
+ */
39
+ customerAccountId;
40
+ /**
41
+ * The organisation that owns this comparison — feature 087 Group B, ruling
42
+ * D-187.
43
+ *
44
+ * Derived from {@link customerAccountId}'s account and stamped at the write,
45
+ * never resolved on read: `customerOrganizationColumn` asks the ORM's own
46
+ * metadata whether this property exists, so its presence is what makes
47
+ * `customerFilterCond`'s `allowed-set` arm **grant** on this table rather
48
+ * than refuse it whole. A sales representative assigned to the buyer's
49
+ * organisation sees this row because this column is here.
50
+ *
51
+ * Nullable, and the constraint is an implication rather than an equivalence:
52
+ * `comparisons_organization_attribution_chk` requires an organisation of a
53
+ * row that names an account, and says nothing about a row that names none.
54
+ * An **anonymous** comparison is a representable state (FR-011) — this table
55
+ * enforces the owner XOR, so half of it is anonymous by construction — and
56
+ * who such a row belongs to is R-6's open question, which a constraint must
57
+ * not answer.
58
+ */
59
+ organizationId;
60
+ /**
61
+ * Set when the owner is an anonymous browser session. Same encoding as
62
+ * `shareToken` (16 random bytes, base64url) but a separate value so a
63
+ * leaked anonymous-cookie token does not double as a public share link.
64
+ */
65
+ anonymousToken;
66
+ salesChannelId;
67
+ displayMode = 'all';
68
+ createdAt = new Date();
69
+ updatedAt = new Date();
70
+ };
71
+ __decorate([
72
+ PrimaryKey({ type: 'uuid' }),
73
+ __metadata("design:type", String)
74
+ ], Comparison.prototype, "id", void 0);
75
+ __decorate([
76
+ Property({ type: 'string', length: 32 }),
77
+ Unique(),
78
+ __metadata("design:type", String)
79
+ ], Comparison.prototype, "shareToken", void 0);
80
+ __decorate([
81
+ Property({ type: 'uuid', nullable: true }),
82
+ __metadata("design:type", Object)
83
+ ], Comparison.prototype, "customerAccountId", void 0);
84
+ __decorate([
85
+ Property({ type: 'uuid', nullable: true }),
86
+ Index(),
87
+ __metadata("design:type", Object)
88
+ ], Comparison.prototype, "organizationId", void 0);
89
+ __decorate([
90
+ Property({ type: 'string', length: 32, nullable: true }),
91
+ __metadata("design:type", Object)
92
+ ], Comparison.prototype, "anonymousToken", void 0);
93
+ __decorate([
94
+ Property({ type: 'uuid' }),
95
+ Index(),
96
+ __metadata("design:type", String)
97
+ ], Comparison.prototype, "salesChannelId", void 0);
98
+ __decorate([
99
+ Property({ type: 'string', length: 16 }),
100
+ __metadata("design:type", String)
101
+ ], Comparison.prototype, "displayMode", void 0);
102
+ __decorate([
103
+ Property({ type: 'datetime', onCreate: () => new Date() }),
104
+ __metadata("design:type", Date)
105
+ ], Comparison.prototype, "createdAt", void 0);
106
+ __decorate([
107
+ Property({ type: 'datetime', onUpdate: () => new Date() }),
108
+ __metadata("design:type", Date)
109
+ ], Comparison.prototype, "updatedAt", void 0);
110
+ Comparison = __decorate([
111
+ CustomerScoped(),
112
+ Entity({ tableName: 'comparisons' })
113
+ ], Comparison);
114
+ export { Comparison };
115
+ //# sourceMappingURL=comparison.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comparison.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/comparison.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAIpC;;;;;;;;;;;;;;;;;GAiBG;AAGI,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,CAAC,aAAa,CAAC,CAOM;IAGrB,EAAE,GAAW,UAAU,EAAE,CAAC;IAI1B,UAAU,CAAU;IAEpB;;;OAGG;IAEH,iBAAiB,CAAiB;IAElC;;;;;;;;;;;;;;;;;;OAkBG;IAGH,cAAc,CAAiB;IAE/B;;;;OAIG;IAEH,cAAc,CAAiB;IAI/B,cAAc,CAAU;IAGxB,WAAW,GAA0B,KAAK,CAAC;IAG3C,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;CAC9B,CAAA;AAxDC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;sCACH;AAI1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxC,MAAM,EAAE;;8CACW;AAOpB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qDACT;AAuBlC;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1C,KAAK,EAAE;;kDACuB;AAQ/B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;kDAC1B;AAI/B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE;;kDACgB;AAGxB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;+CACE;AAG3C;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;6CAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;6CAAc;AAlElB,UAAU;IAFtB,cAAc,EAAE;IAChB,MAAM,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC;GACxB,UAAU,CAmEtB"}
@@ -0,0 +1,61 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
3
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
4
+ import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
5
+ import { ComparisonService } from './services/comparison-service.js';
6
+ import { ComparisonAdminService } from './services/comparison-admin.service.js';
7
+ import { ShareTokenGenerator } from './services/share-token-generator.js';
8
+ import { ComparisonProduct } from './entities/comparison-product.entity.js';
9
+ import { Comparison } from './entities/comparison.entity.js';
10
+ /**
11
+ * `comparisons` — the module that made a whole service to throw it away
12
+ * (feature 072, wave 2, T111).
13
+ *
14
+ * `ComparisonService`'s second constructor parameter was `_catalogQuery:
15
+ * CatalogQueryService` — underscore-prefixed, unused, kept with the comment
16
+ * "constructor contract kept; attribute reads moved to `catalogAttributes`".
17
+ * To feed it, each composition root built an entire `CatalogQueryService`,
18
+ * one of four per composition, and handed it to a parameter that discards it.
19
+ * The parameter is gone and so is the construction; `catalogAttributeRead` is
20
+ * what this module actually reads attributes through, and it stays.
21
+ *
22
+ * **`requireAdmin` becomes required, and this one failed *safe* before.** Where
23
+ * `cms` and `assets_library` defaulted an absent gate to a permissive no-op,
24
+ * this module's plugin read
25
+ *
26
+ * if (options.requireAdmin) { await registerComparisonsAdminRoutes(…) }
27
+ *
28
+ * so an omitted gate removed the admin surface rather than opening it. That is
29
+ * the right direction to fail in, and it is still worth removing: whether an
30
+ * API exists at all should not be a side effect of an argument being passed.
31
+ * Both roots passed one, so the surface is unchanged.
32
+ *
33
+ * `settingsService` stops being optional too. Absent, `compare.max_products`
34
+ * silently fell back to a constant on every call, which is the quiet form —
35
+ * an operator's configured limit ignored with no indication.
36
+ *
37
+ * `comparisonService` is a **port**: the login flow adopts an anonymous
38
+ * comparison through it, across a module boundary.
39
+ */
40
+ export interface ComparisonsCradle {
41
+ readonly emFactory: () => EntityManager;
42
+ readonly requireAdmin: RequireAdminFactory;
43
+ readonly settingsReadPort: SettingsReadPort;
44
+ readonly comparisonService: ComparisonService;
45
+ readonly comparisonAdminService: ComparisonAdminService;
46
+ readonly comparisonShareTokens: ShareTokenGenerator;
47
+ }
48
+ export declare function registerModule(ctx: ModuleContext): void;
49
+ /**
50
+ * The module's persisted entity classes, on the `./backend` subpath, as one
51
+ * array and **no named class export** (D-168).
52
+ *
53
+ * This is the shape the platform reads when the package is *installed*: the
54
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
55
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
56
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
57
+ * the package pass of `check-entity-tenant-classification`. A missing array is
58
+ * answered with `[]` — zero entities registered, no error anywhere.
59
+ */
60
+ export declare const entities: (typeof Comparison | typeof ComparisonProduct)[];
61
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAS3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAEhF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAK1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;IAC9C,QAAQ,CAAC,sBAAsB,EAAE,sBAAsB,CAAC;IACxD,QAAQ,CAAC,qBAAqB,EAAE,mBAAmB,CAAC;CACrD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CA8GvD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,kDAGpB,CAAC"}
@@ -0,0 +1,117 @@
1
+ import { lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { ComparisonService } from './services/comparison-service.js';
3
+ import { ComparisonAdminService } from './services/comparison-admin.service.js';
4
+ import { ComparableAttributeProjection } from './services/comparable-attribute-projection.js';
5
+ import { ShareTokenGenerator } from './services/share-token-generator.js';
6
+ import { ComparisonPdfRenderer } from './services/comparison-pdf-renderer.js';
7
+ import { registerComparisonsPublicRoutes } from './routes.public.js';
8
+ import { registerComparisonsShareRoutes } from './routes.share.js';
9
+ import { registerComparisonsAdminRoutes } from './routes.admin.js';
10
+ import { ComparisonProduct } from './entities/comparison-product.entity.js';
11
+ import { Comparison } from './entities/comparison.entity.js';
12
+ export function registerModule(ctx) {
13
+ ctx.di.providePort('comparisonService', ctx
14
+ .asFunction(({ emFactory }) => new ComparisonService(emFactory, new ComparableAttributeProjection(), ctx.cradle().comparisonShareTokens,
15
+ // Feature 075, Phase C — the products a comparison holds are
16
+ // `catalog`'s rows. They used to be read with `em.find(Product, …)`
17
+ // from inside this module, which no gate can see; over the port the
18
+ // same read answers 503 when `catalog` is off, which is the
19
+ // binding dependency this manifest declares.
20
+ lazyPort(ctx, 'catalogProductReadPort'),
21
+ // Issue #259 — the channel assortment gate on the add seam. A
22
+ // kernel registration, so there is no module edge to declare and
23
+ // nothing to switch off underneath it.
24
+ lazyPort(ctx, 'salesChannelMembershipPort'),
25
+ // Feature 075 / D-87 — the base image of a column, over `catalog`'s
26
+ // batch gallery read. It used to be a three-table join written
27
+ // here, reaching two of `catalog`'s tables and one of
28
+ // `assets_library`'s; a raw statement names no import specifier, so
29
+ // nothing in the tree could see it and no gate could stop it. The
30
+ // edge binds — `catalog` is already a dependency of this manifest,
31
+ // and a comparison whose products are unreadable has no column to
32
+ // put an image in.
33
+ lazyPort(ctx, 'galleryService'), lazyPort(ctx, 'settingsReadPort'),
34
+ // The attribute read model, now `catalog`'s published port rather
35
+ // than a hand-made adapter over a name a root registered.
36
+ lazyPort(ctx, 'catalogAttributeReadPort'),
37
+ // Issue #132 — a comparison column is a listing and prices through
38
+ // the engine, not off the catalogue's legacy attribute.
39
+ lazyPort(ctx, 'pricingService'),
40
+ // The viewer's organisation, for the customer group the pricing
41
+ // engine selects a group-targeted list by. The edge binds: a
42
+ // comparison that cannot read the buyer's organisation would quote
43
+ // the channel price to a buyer who has negotiated one, which is
44
+ // silently wrong rather than visibly refused.
45
+ lazyPort(ctx, 'organizationDetailsPort'),
46
+ // Feature 087 Group B / D-187 — the organisation an owned
47
+ // comparison is stamped with, derived from the owning account
48
+ // rather than from the ambient request context. Both write paths
49
+ // need it and only one of them has a request:
50
+ // `adoptAnonymousComparison` runs inside the post-login hook. The
51
+ // edge binds — a comparison this module cannot attribute is a row
52
+ // `comparisons_organization_attribution_chk` refuses, so failing
53
+ // closed at the port is the same answer arriving earlier and with
54
+ // the account named. Nothing to declare: the same port is already
55
+ // resolved for `comparisonAdminService` below, and
56
+ // `check:port-dependencies` reads the manifest's transitive
57
+ // closure.
58
+ lazyPort(ctx, 'customerAccountReadPort')))
59
+ .singleton());
60
+ ctx.di.register({
61
+ // One generator, registered rather than constructed twice. It happens to be
62
+ // stateless today — `randomBytes(16)` and nothing else — so two instances
63
+ // would agree; registering it means that stays true by construction rather
64
+ // than by the class never gaining a key.
65
+ comparisonShareTokens: ctx.asFunction(() => new ShareTokenGenerator()).singleton(),
66
+ comparisonAdminService: ctx
67
+ .asFunction(({ emFactory }) => new ComparisonAdminService(emFactory,
68
+ // Resolved per call, not captured. `comparisonService` is a
69
+ // transient port gate and Awilix's strict mode refuses a singleton
70
+ // holding one — rightly, since a captured port keeps answering
71
+ // after its module is switched off. `buildOwnerView` is the only
72
+ // method this service reaches.
73
+ {
74
+ buildOwnerView: (...args) => ctx.cradle().comparisonService.buildOwnerView(...args),
75
+ },
76
+ // Feature 075, Phase C — the owner column's e-mail address is
77
+ // `customer_accounts`' row, read over its port instead of out of
78
+ // its table.
79
+ lazyPort(ctx, 'customerAccountReadPort')))
80
+ .singleton(),
81
+ });
82
+ ctx.routes(async (app) => {
83
+ const { requireAdmin, comparisonShareTokens } = ctx.cradle();
84
+ // The module's own two ports, read lazily. Route *registration* runs
85
+ // whatever the module's effective state is — `defineModuleRoutes` gates
86
+ // requests, not the wiring — so destructuring a gated port here asked the
87
+ // gate inside `buildServer`, and an operator who had switched `comparisons`
88
+ // off stopped the backend from starting rather than stopping its routes.
89
+ const comparisonService = lazyPort(ctx, 'comparisonService');
90
+ await registerComparisonsPublicRoutes(app, {
91
+ comparisonService,
92
+ tokens: comparisonShareTokens,
93
+ pdfRenderer: new ComparisonPdfRenderer(),
94
+ });
95
+ await registerComparisonsShareRoutes(app, { comparisonService });
96
+ await registerComparisonsAdminRoutes(app, {
97
+ adminService: lazyPort(ctx, 'comparisonAdminService'),
98
+ requireAdmin,
99
+ });
100
+ });
101
+ }
102
+ /**
103
+ * The module's persisted entity classes, on the `./backend` subpath, as one
104
+ * array and **no named class export** (D-168).
105
+ *
106
+ * This is the shape the platform reads when the package is *installed*: the
107
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
108
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
109
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
110
+ * the package pass of `check-entity-tenant-classification`. A missing array is
111
+ * answered with `[]` — zero entities registered, no error anywhere.
112
+ */
113
+ export const entities = [
114
+ ComparisonProduct,
115
+ Comparison,
116
+ ];
117
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { FastifyRequest } from 'fastify';
2
+ /**
3
+ * The signed-in customer behind a request, or `null`.
4
+ *
5
+ * `request.actor` is not on `FastifyRequest`: it is a declaration-merging
6
+ * augmentation the `auth` module contributes (`modules/auth/plugin.ts`). Inside
7
+ * `backend/src` that augmentation arrived ambiently through the host's own
8
+ * `types` graph, so nothing in this module ever named it. A package compiles
9
+ * against its own manifest and `auth` publishes nothing a package can name — it
10
+ * is the one module still holding an unpublished platform reach — so the
11
+ * property is simply not there (TS2339).
12
+ *
13
+ * So the shape is read structurally, in **one** place rather than at each of the
14
+ * three call sites, and it is the same door `admin_notifications` already uses
15
+ * for the admin half of the same augmentation. It narrows rather than asserts:
16
+ * a request carrying no actor, or an actor of another kind, answers `null`, so
17
+ * the anonymous `compare_token` path is reached exactly as before.
18
+ */
19
+ export declare function customerActor(request: FastifyRequest): {
20
+ customerAccountId: string;
21
+ } | null;
22
+ //# sourceMappingURL=request-actor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-actor.d.ts","sourceRoot":"","sources":["../../src/backend/request-actor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG;IAAE,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAM3F"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The signed-in customer behind a request, or `null`.
3
+ *
4
+ * `request.actor` is not on `FastifyRequest`: it is a declaration-merging
5
+ * augmentation the `auth` module contributes (`modules/auth/plugin.ts`). Inside
6
+ * `backend/src` that augmentation arrived ambiently through the host's own
7
+ * `types` graph, so nothing in this module ever named it. A package compiles
8
+ * against its own manifest and `auth` publishes nothing a package can name — it
9
+ * is the one module still holding an unpublished platform reach — so the
10
+ * property is simply not there (TS2339).
11
+ *
12
+ * So the shape is read structurally, in **one** place rather than at each of the
13
+ * three call sites, and it is the same door `admin_notifications` already uses
14
+ * for the admin half of the same augmentation. It narrows rather than asserts:
15
+ * a request carrying no actor, or an actor of another kind, answers `null`, so
16
+ * the anonymous `compare_token` path is reached exactly as before.
17
+ */
18
+ export function customerActor(request) {
19
+ const { actor } = request;
20
+ if (actor?.kind !== 'customer' || typeof actor.customerAccountId !== 'string')
21
+ return null;
22
+ return { customerAccountId: actor.customerAccountId };
23
+ }
24
+ //# sourceMappingURL=request-actor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-actor.js","sourceRoot":"","sources":["../../src/backend/request-actor.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,aAAa,CAAC,OAAuB;IACnD,MAAM,EAAE,KAAK,EAAE,GAAG,OAEjB,CAAC;IACF,IAAI,KAAK,EAAE,IAAI,KAAK,UAAU,IAAI,OAAO,KAAK,CAAC,iBAAiB,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3F,OAAO,EAAE,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,EAAE,CAAC;AACxD,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { ComparisonAdminService } from './services/comparison-admin.service.js';
3
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
4
+ /**
5
+ * Admin HTTP surface — feature 007 / US5 / T064.
6
+ *
7
+ * Mounts under `/api/v1/admin/comparisons/*` and is GET-only:
8
+ *
9
+ * - GET /api/v1/admin/comparisons — list with filters
10
+ * - GET /api/v1/admin/comparisons/:id — single-comparison detail
11
+ *
12
+ * Spec FR-022: "MUST allow an administrator to open any single
13
+ * Comparison and inspect ... without offering any controls that mutate
14
+ * the Comparison." Translated literally — no PATCH, no POST, no
15
+ * DELETE under this prefix.
16
+ */
17
+ export interface ComparisonsAdminDeps {
18
+ adminService: ComparisonAdminService;
19
+ requireAdmin: RequireAdminFactory;
20
+ }
21
+ export declare function registerComparisonsAdminRoutes(app: FastifyInstance, deps: ComparisonsAdminDeps): Promise<void>;
22
+ //# sourceMappingURL=routes.admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAO/C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,sBAAsB,CAAC;IACrC,YAAY,EAAE,mBAAmB,CAAC;CACnC;AAED,wBAAsB,8BAA8B,CAClD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
@@ -0,0 +1,41 @@
1
+ import { ERROR_CODES, comparisonAdminListQuerySchema, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { ComparisonNotFoundError } from './services/comparison-service.js';
4
+ export async function registerComparisonsAdminRoutes(app, deps) {
5
+ const { adminService, requireAdmin } = deps;
6
+ app.get('/api/v1/admin/comparisons', { preHandler: requireAdmin('comparisons:read') }, async (request) => {
7
+ const query = parseListQuery(request.query);
8
+ const { data, nextCursor, limit } = await adminService.list(query);
9
+ return { data, meta: { limit, nextCursor } };
10
+ });
11
+ app.get('/api/v1/admin/comparisons/:id', { preHandler: requireAdmin('comparisons:read') }, async (request) => {
12
+ try {
13
+ return { data: await adminService.detail(request.params.id) };
14
+ }
15
+ catch (err) {
16
+ if (err instanceof ComparisonNotFoundError) {
17
+ throw new HttpError(404, ERROR_CODES.COMPARISON_NOT_FOUND, 'Comparison not found.');
18
+ }
19
+ throw err;
20
+ }
21
+ });
22
+ }
23
+ function parseListQuery(input) {
24
+ // Coerce numeric strings (Fastify hands query params as strings) before Zod.
25
+ const raw = (input ?? {});
26
+ const coerced = {
27
+ ...raw,
28
+ ...(typeof raw['limit'] === 'string'
29
+ ? { limit: Number(raw['limit']) }
30
+ : {}),
31
+ };
32
+ const parsed = comparisonAdminListQuerySchema.safeParse(coerced);
33
+ if (!parsed.success) {
34
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'Validation failed.', parsed.error.issues.map((i) => ({
35
+ path: i.path.map(String).join('.'),
36
+ issue: i.message,
37
+ })));
38
+ }
39
+ return parsed.data;
40
+ }
41
+ //# sourceMappingURL=routes.admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.js","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,8BAA8B,GAC/B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAuB3E,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,GAAoB,EACpB,IAA0B;IAE1B,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAE5C,GAAG,CAAC,GAAG,CACL,2BAA2B,EAC3B,EAAE,UAAU,EAAE,YAAY,CAAC,kBAAkB,CAAC,EAAE,EAChD,KAAK,EAAE,OAAO,EAAE,EAAE;QAChB,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC;IAC/C,CAAC,CACF,CAAC;IAEF,GAAG,CAAC,GAAG,CACL,+BAA+B,EAC/B,EAAE,UAAU,EAAE,YAAY,CAAC,kBAAkB,CAAC,EAAE,EAChD,KAAK,EAAE,OAAO,EAAE,EAAE;QAChB,IAAI,CAAC;YACH,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,uBAAuB,EAAE,CAAC;gBAC3C,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,WAAW,CAAC,oBAAoB,EAChC,uBAAuB,CACxB,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,6EAA6E;IAC7E,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAA4B,CAAC;IACrD,MAAM,OAAO,GAAG;QACd,GAAG,GAAG;QACN,GAAG,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,QAAQ;YAClC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE;YACjC,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;IACF,MAAM,MAAM,GAAG,8BAA8B,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,WAAW,CAAC,iBAAiB,EAC7B,oBAAoB,EACpB,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9B,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YAClC,KAAK,EAAE,CAAC,CAAC,OAAO;SACjB,CAAC,CAAC,CACJ,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import { type ComparisonService } from './services/comparison-service.js';
3
+ import type { ComparisonPdfRenderer } from './services/comparison-pdf-renderer.js';
4
+ import { COMPARE_TOKEN_COOKIE } from './services/anonymous-token-cookie.js';
5
+ import type { ShareTokenGenerator } from './services/share-token-generator.js';
6
+ /**
7
+ * Public HTTP surface — feature 007 / US1 (T027).
8
+ *
9
+ * Routes mounted under `/api/v1/comparisons/me*` (project convention is
10
+ * `/api/v1/<module>/<resource>`; the contract README's `/public/`
11
+ * prefix follows spec wording but the codebase strips it — same as
12
+ * `catalog`, `search`, `sales_channels`).
13
+ *
14
+ * - GET /me — read; 204 when none exists
15
+ * - POST /me/products — add (creates on first call,
16
+ * mints `compare_token` cookie)
17
+ * - DELETE /me/products/:productId — remove
18
+ * - PATCH /me — update display mode
19
+ * - DELETE /me — hard-delete the comparison
20
+ *
21
+ * Owner resolution: authenticated customer wins over anonymous cookie.
22
+ * On a write where neither is present, a fresh anonymous token is
23
+ * minted and the `compare_token` cookie is set on the response (R-2).
24
+ */
25
+ export interface ComparisonsPublicDeps {
26
+ comparisonService: ComparisonService;
27
+ tokens: ShareTokenGenerator;
28
+ /** Optional — when omitted, `GET /me/pdf` is not mounted (foundation tests). */
29
+ pdfRenderer?: ComparisonPdfRenderer;
30
+ }
31
+ export declare function registerComparisonsPublicRoutes(app: FastifyInstance, deps: ComparisonsPublicDeps): Promise<void>;
32
+ export { COMPARE_TOKEN_COOKIE };
33
+ //# sourceMappingURL=routes.public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.public.d.ts","sourceRoot":"","sources":["../../src/backend/routes.public.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAU7E,OAAO,EAML,KAAK,iBAAiB,EACvB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AACnF,OAAO,EACL,oBAAoB,EAGrB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAI/E;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,gFAAgF;IAChF,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAED,wBAAsB,+BAA+B,CACnD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA6Kf;AAoGD,OAAO,EAAE,oBAAoB,EAAE,CAAC"}