@endora-commerce/mod-settings 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.
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/admin/api/settings-client.d.ts +47 -0
- package/dist/admin/api/settings-client.d.ts.map +1 -0
- package/dist/admin/api/settings-client.js +41 -0
- package/dist/admin/api/settings-client.js.map +1 -0
- package/dist/admin/components/ActivationPointerRow.d.ts +21 -0
- package/dist/admin/components/ActivationPointerRow.d.ts.map +1 -0
- package/dist/admin/components/ActivationPointerRow.js +28 -0
- package/dist/admin/components/ActivationPointerRow.js.map +1 -0
- package/dist/admin/components/AssetIdSettingInput.d.ts +12 -0
- package/dist/admin/components/AssetIdSettingInput.d.ts.map +1 -0
- package/dist/admin/components/AssetIdSettingInput.js +10 -0
- package/dist/admin/components/AssetIdSettingInput.js.map +1 -0
- package/dist/admin/components/ConfigurationReferenceInput.d.ts +39 -0
- package/dist/admin/components/ConfigurationReferenceInput.d.ts.map +1 -0
- package/dist/admin/components/ConfigurationReferenceInput.js +74 -0
- package/dist/admin/components/ConfigurationReferenceInput.js.map +1 -0
- package/dist/admin/components/ConflictBanner.d.ts +13 -0
- package/dist/admin/components/ConflictBanner.d.ts.map +1 -0
- package/dist/admin/components/ConflictBanner.js +12 -0
- package/dist/admin/components/ConflictBanner.js.map +1 -0
- package/dist/admin/components/ImageSettingInput.d.ts +34 -0
- package/dist/admin/components/ImageSettingInput.d.ts.map +1 -0
- package/dist/admin/components/ImageSettingInput.js +36 -0
- package/dist/admin/components/ImageSettingInput.js.map +1 -0
- package/dist/admin/components/QuoteRequestsSettingsTab.d.ts +3 -0
- package/dist/admin/components/QuoteRequestsSettingsTab.d.ts.map +1 -0
- package/dist/admin/components/QuoteRequestsSettingsTab.js +69 -0
- package/dist/admin/components/QuoteRequestsSettingsTab.js.map +1 -0
- package/dist/admin/components/SellerCompanyDataInput.d.ts +22 -0
- package/dist/admin/components/SellerCompanyDataInput.d.ts.map +1 -0
- package/dist/admin/components/SellerCompanyDataInput.js +53 -0
- package/dist/admin/components/SellerCompanyDataInput.js.map +1 -0
- package/dist/admin/components/SettingRowEditor.d.ts +55 -0
- package/dist/admin/components/SettingRowEditor.d.ts.map +1 -0
- package/dist/admin/components/SettingRowEditor.js +160 -0
- package/dist/admin/components/SettingRowEditor.js.map +1 -0
- package/dist/admin/index.d.ts +39 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +87 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/CachePage.d.ts +14 -0
- package/dist/admin/pages/CachePage.d.ts.map +1 -0
- package/dist/admin/pages/CachePage.js +124 -0
- package/dist/admin/pages/CachePage.js.map +1 -0
- package/dist/admin/pages/GroupsPage.d.ts +17 -0
- package/dist/admin/pages/GroupsPage.d.ts.map +1 -0
- package/dist/admin/pages/GroupsPage.js +84 -0
- package/dist/admin/pages/GroupsPage.js.map +1 -0
- package/dist/admin/pages/SettingsPage.d.ts +20 -0
- package/dist/admin/pages/SettingsPage.d.ts.map +1 -0
- package/dist/admin/pages/SettingsPage.js +453 -0
- package/dist/admin/pages/SettingsPage.js.map +1 -0
- package/dist/backend/cli/cache-clear.d.ts +23 -0
- package/dist/backend/cli/cache-clear.d.ts.map +1 -0
- package/dist/backend/cli/cache-clear.js +37 -0
- package/dist/backend/cli/cache-clear.js.map +1 -0
- package/dist/backend/index.d.ts +100 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +125 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +308 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +188 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.cache.d.ts +20 -0
- package/dist/backend/routes.cache.d.ts.map +1 -0
- package/dist/backend/routes.cache.js +22 -0
- package/dist/backend/routes.cache.js.map +1 -0
- package/dist/backend/routes.homepage.d.ts +15 -0
- package/dist/backend/routes.homepage.d.ts.map +1 -0
- package/dist/backend/routes.homepage.js +20 -0
- package/dist/backend/routes.homepage.js.map +1 -0
- package/dist/backend/routes.product-card-buttons.d.ts +15 -0
- package/dist/backend/routes.product-card-buttons.d.ts.map +1 -0
- package/dist/backend/routes.product-card-buttons.js +21 -0
- package/dist/backend/routes.product-card-buttons.js.map +1 -0
- package/dist/backend/routes.speculation-rules.d.ts +16 -0
- package/dist/backend/routes.speculation-rules.d.ts.map +1 -0
- package/dist/backend/routes.speculation-rules.js +21 -0
- package/dist/backend/routes.speculation-rules.js.map +1 -0
- package/dist/backend/routes.storefront.d.ts +15 -0
- package/dist/backend/routes.storefront.d.ts.map +1 -0
- package/dist/backend/routes.storefront.js +20 -0
- package/dist/backend/routes.storefront.js.map +1 -0
- package/dist/backend/services/cache-admin.service.d.ts +83 -0
- package/dist/backend/services/cache-admin.service.d.ts.map +1 -0
- package/dist/backend/services/cache-admin.service.js +148 -0
- package/dist/backend/services/cache-admin.service.js.map +1 -0
- package/dist/backend/services/homepage-resolver.d.ts +25 -0
- package/dist/backend/services/homepage-resolver.d.ts.map +1 -0
- package/dist/backend/services/homepage-resolver.js +31 -0
- package/dist/backend/services/homepage-resolver.js.map +1 -0
- package/dist/backend/services/product-card-buttons-resolver.d.ts +25 -0
- package/dist/backend/services/product-card-buttons-resolver.d.ts.map +1 -0
- package/dist/backend/services/product-card-buttons-resolver.js +37 -0
- package/dist/backend/services/product-card-buttons-resolver.js.map +1 -0
- package/dist/backend/services/registered-settings-manifests.d.ts +45 -0
- package/dist/backend/services/registered-settings-manifests.d.ts.map +1 -0
- package/dist/backend/services/registered-settings-manifests.js +55 -0
- package/dist/backend/services/registered-settings-manifests.js.map +1 -0
- package/dist/backend/services/setting-write-validators.d.ts +43 -0
- package/dist/backend/services/setting-write-validators.d.ts.map +1 -0
- package/dist/backend/services/setting-write-validators.js +43 -0
- package/dist/backend/services/setting-write-validators.js.map +1 -0
- package/dist/backend/services/settings-admin.service.d.ts +206 -0
- package/dist/backend/services/settings-admin.service.d.ts.map +1 -0
- package/dist/backend/services/settings-admin.service.js +642 -0
- package/dist/backend/services/settings-admin.service.js.map +1 -0
- package/dist/backend/services/shop-info-resolver.d.ts +15 -0
- package/dist/backend/services/shop-info-resolver.d.ts.map +1 -0
- package/dist/backend/services/shop-info-resolver.js +44 -0
- package/dist/backend/services/shop-info-resolver.js.map +1 -0
- package/dist/backend/services/speculation-rules-resolver.d.ts +28 -0
- package/dist/backend/services/speculation-rules-resolver.d.ts.map +1 -0
- package/dist/backend/services/speculation-rules-resolver.js +39 -0
- package/dist/backend/services/speculation-rules-resolver.js.map +1 -0
- package/dist/manifest.d.ts +194 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +379 -0
- package/dist/manifest.js.map +1 -0
- package/docs/settings/index.md +171 -0
- package/i18n/en.json +167 -0
- package/i18n/pl.json +167 -0
- package/package.json +96 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { inProcessCaches, } from '@endora-commerce/platform/kernel';
|
|
2
|
+
import { SETTINGS_CACHE_KEY_PREFIX, SETTINGS_CACHE_NAMESPACE, } from '@endora-commerce/platform/kernel';
|
|
3
|
+
import { SALES_CHANNELS_CACHE_KEY_PREFIX, SALES_CHANNELS_CACHE_NAMESPACE, } from '@endora-commerce/platform/kernel';
|
|
4
|
+
export const CACHE_NAMESPACES = [
|
|
5
|
+
{
|
|
6
|
+
key: SETTINGS_CACHE_NAMESPACE,
|
|
7
|
+
label: 'Settings',
|
|
8
|
+
description: 'Resolved platform / per-channel setting values.',
|
|
9
|
+
patterns: [`${SETTINGS_CACHE_KEY_PREFIX}*`],
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
key: SALES_CHANNELS_CACHE_NAMESPACE,
|
|
13
|
+
label: 'Sales channels',
|
|
14
|
+
description: 'Resolved sales-channel configuration.',
|
|
15
|
+
patterns: [`${SALES_CHANNELS_CACHE_KEY_PREFIX}*`],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
key: 'blog',
|
|
19
|
+
label: 'Blog',
|
|
20
|
+
description: 'Storefront blog index, posts, categories and tag pages.',
|
|
21
|
+
patterns: ['blog:v1:*'],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
key: 'cms',
|
|
25
|
+
label: 'CMS',
|
|
26
|
+
description: 'Storefront CMS pages, blocks and hook content.',
|
|
27
|
+
patterns: ['cms:v1:*'],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: 'megamenu',
|
|
31
|
+
label: 'Mega menu',
|
|
32
|
+
description: 'Resolved storefront mega-menu trees.',
|
|
33
|
+
patterns: ['megamenu:v1:*'],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: 'dictionaries',
|
|
37
|
+
label: 'Dictionaries',
|
|
38
|
+
description: 'Resolved dictionary registries and entries (countries, etc.).',
|
|
39
|
+
patterns: ['dictionary:*'],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: 'carts',
|
|
43
|
+
label: 'Cart pricing',
|
|
44
|
+
description: 'Cached cart re-pricing snapshots.',
|
|
45
|
+
patterns: ['b2b:cart:recompute:*'],
|
|
46
|
+
},
|
|
47
|
+
// No `modules` entry. It cleared `b2b:module:enabled-set`, which no code path
|
|
48
|
+
// ever read: module presence is held in memory and refreshed from PostgreSQL
|
|
49
|
+
// on a `b2b:module:state-changed` notification. The mirror is gone (feature
|
|
50
|
+
// 073, T140), and offering an operator a button that clears a key nobody
|
|
51
|
+
// reads is worse than offering nothing — it is where they look first when
|
|
52
|
+
// presence is wrong.
|
|
53
|
+
];
|
|
54
|
+
/**
|
|
55
|
+
* Admin-facing cache maintenance. Lets an operator flush selected cache
|
|
56
|
+
* namespaces so freshly-published content / changed settings show up without
|
|
57
|
+
* waiting for TTL expiry.
|
|
58
|
+
*
|
|
59
|
+
* Six of the eight namespaces live only in Redis, so SCAN+DEL over their key
|
|
60
|
+
* patterns is a complete clear. Two — `settings` and `sales_channels` — keep a
|
|
61
|
+
* per-process layer in front of Redis, and for those the clear goes through
|
|
62
|
+
* the cache that owns both layers (see {@link InProcessCacheRegistry}), which
|
|
63
|
+
* is also the only way to get the ordering right: shared layer first, prefix
|
|
64
|
+
* marked for the whole operation.
|
|
65
|
+
*
|
|
66
|
+
* The cost of that ordering is worth naming. Immediately after a clear both
|
|
67
|
+
* layers are cold and the marked prefix sends reads through to PostgreSQL, so
|
|
68
|
+
* a platform-wide burst of setting reads lands on the database. The queries are
|
|
69
|
+
* trivial (small table, unique index on `code`), but the connection pool is
|
|
70
|
+
* not: it is the scarce resource. That is acceptable for a rare,
|
|
71
|
+
* operator-initiated action and unacceptable in a loop, which is why the route
|
|
72
|
+
* carries its own rate limit (`routes.cache.ts`).
|
|
73
|
+
*/
|
|
74
|
+
export class CacheAdminService {
|
|
75
|
+
redis;
|
|
76
|
+
caches;
|
|
77
|
+
constructor(redis,
|
|
78
|
+
/** Injectable so a test does not clear this process's real caches. */
|
|
79
|
+
caches = inProcessCaches) {
|
|
80
|
+
this.redis = redis;
|
|
81
|
+
this.caches = caches;
|
|
82
|
+
}
|
|
83
|
+
get enabled() {
|
|
84
|
+
return this.redis !== undefined;
|
|
85
|
+
}
|
|
86
|
+
listNamespaces() {
|
|
87
|
+
return CACHE_NAMESPACES.map((n) => ({
|
|
88
|
+
key: n.key,
|
|
89
|
+
label: n.label,
|
|
90
|
+
description: n.description,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Clear the given namespaces (or every namespace when passed `'all'`).
|
|
95
|
+
* Unknown keys are ignored. Returns the per-namespace deleted-key counts.
|
|
96
|
+
*/
|
|
97
|
+
async clear(namespaces) {
|
|
98
|
+
const wanted = namespaces === 'all'
|
|
99
|
+
? CACHE_NAMESPACES
|
|
100
|
+
: CACHE_NAMESPACES.filter((n) => namespaces.includes(n.key));
|
|
101
|
+
const cleared = [];
|
|
102
|
+
let totalDeletedKeys = 0;
|
|
103
|
+
for (const ns of wanted) {
|
|
104
|
+
const deleted = await this.clearNamespace(ns);
|
|
105
|
+
cleared.push({ key: ns.key, deletedKeysCount: deleted });
|
|
106
|
+
totalDeletedKeys += deleted;
|
|
107
|
+
}
|
|
108
|
+
return { cleared, totalDeletedKeys };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Prefer the owning cache when this process has one: it drops the shared
|
|
112
|
+
* layer and its own in-memory layer as one marked operation. A process that
|
|
113
|
+
* registered none — the `cache:clear` CLI, a worker without the module —
|
|
114
|
+
* has no in-memory layer to be stale, so clearing the Redis keys is the
|
|
115
|
+
* whole job.
|
|
116
|
+
*/
|
|
117
|
+
async clearNamespace(ns) {
|
|
118
|
+
if (this.caches.has(ns.key)) {
|
|
119
|
+
return (await this.caches.clear(ns.key)) ?? 0;
|
|
120
|
+
}
|
|
121
|
+
if (!this.redis)
|
|
122
|
+
return 0;
|
|
123
|
+
let deleted = 0;
|
|
124
|
+
for (const pattern of ns.patterns) {
|
|
125
|
+
deleted += await this.scanDelete(pattern);
|
|
126
|
+
}
|
|
127
|
+
return deleted;
|
|
128
|
+
}
|
|
129
|
+
async scanDelete(pattern) {
|
|
130
|
+
const redis = this.redis;
|
|
131
|
+
if (!redis)
|
|
132
|
+
return 0;
|
|
133
|
+
// A glob-free pattern is a single literal key — delete it directly.
|
|
134
|
+
if (!/[*?[\]]/.test(pattern)) {
|
|
135
|
+
return redis.del(pattern);
|
|
136
|
+
}
|
|
137
|
+
let cursor = '0';
|
|
138
|
+
let deleted = 0;
|
|
139
|
+
do {
|
|
140
|
+
const [next, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 200);
|
|
141
|
+
cursor = next;
|
|
142
|
+
if (keys.length > 0)
|
|
143
|
+
deleted += await redis.del(...keys);
|
|
144
|
+
} while (cursor !== '0');
|
|
145
|
+
return deleted;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=cache-admin.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-admin.service.js","sourceRoot":"","sources":["../../../src/backend/services/cache-admin.service.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,eAAe,GAEhB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,kCAAkC,CAAC;AA6B1C,MAAM,CAAC,MAAM,gBAAgB,GAA8B;IACzD;QACE,GAAG,EAAE,wBAAwB;QAC7B,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,iDAAiD;QAC9D,QAAQ,EAAE,CAAC,GAAG,yBAAyB,GAAG,CAAC;KAC5C;IACD;QACE,GAAG,EAAE,8BAA8B;QACnC,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,uCAAuC;QACpD,QAAQ,EAAE,CAAC,GAAG,+BAA+B,GAAG,CAAC;KAClD;IACD;QACE,GAAG,EAAE,MAAM;QACX,KAAK,EAAE,MAAM;QACb,WAAW,EAAE,yDAAyD;QACtE,QAAQ,EAAE,CAAC,WAAW,CAAC;KACxB;IACD;QACE,GAAG,EAAE,KAAK;QACV,KAAK,EAAE,KAAK;QACZ,WAAW,EAAE,gDAAgD;QAC7D,QAAQ,EAAE,CAAC,UAAU,CAAC;KACvB;IACD;QACE,GAAG,EAAE,UAAU;QACf,KAAK,EAAE,WAAW;QAClB,WAAW,EAAE,sCAAsC;QACnD,QAAQ,EAAE,CAAC,eAAe,CAAC;KAC5B;IACD;QACE,GAAG,EAAE,cAAc;QACnB,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,+DAA+D;QAC5E,QAAQ,EAAE,CAAC,cAAc,CAAC;KAC3B;IACD;QACE,GAAG,EAAE,OAAO;QACZ,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,mCAAmC;QAChD,QAAQ,EAAE,CAAC,sBAAsB,CAAC;KACnC;IACD,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,yEAAyE;IACzE,0EAA0E;IAC1E,qBAAqB;CACb,CAAC;AAOX;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,iBAAiB;IAET;IAEA;IAHnB,YACmB,KAAa;IAC9B,sEAAsE;IACrD,SAAiC,eAAe;QAFhD,UAAK,GAAL,KAAK,CAAQ;QAEb,WAAM,GAAN,MAAM,CAA0C;IAChE,CAAC;IAEJ,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC;IAClC,CAAC;IAED,cAAc;QACZ,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAClC,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,WAAW,EAAE,CAAC,CAAC,WAAW;SAC3B,CAAC,CAAC,CAAC;IACN,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK,CACT,UAA4B;QAE5B,MAAM,MAAM,GACV,UAAU,KAAK,KAAK;YAClB,CAAC,CAAC,gBAAgB;YAClB,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAEjE,MAAM,OAAO,GAAuB,EAAE,CAAC;QACvC,IAAI,gBAAgB,GAAG,CAAC,CAAC;QACzB,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;YAC9C,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC,CAAC;YACzD,gBAAgB,IAAI,OAAO,CAAC;QAC9B,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC;IACvC,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,cAAc,CAAC,EAAkB;QAC7C,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QAChD,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,CAAC;QAC1B,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,KAAK,MAAM,OAAO,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC;YAClC,OAAO,IAAI,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,OAAe;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,CAAC;QACrB,oEAAoE;QACpE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,OAAO,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,MAAM,GAAG,GAAG,CAAC;QACjB,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,GAAG,CAAC;YACF,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;YAC9E,MAAM,GAAG,IAAI,CAAC;YACd,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,IAAI,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QAC3D,CAAC,QAAQ,MAAM,KAAK,GAAG,EAAE;QACzB,OAAO,OAAO,CAAC;IACjB,CAAC;CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HomepageConfig } from '@endora-commerce/contracts';
|
|
2
|
+
import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the storefront home-page configuration for a sales channel from the
|
|
5
|
+
* `homepage_cms_page_slug` setting. An unset / not-registered / out-of-scope
|
|
6
|
+
* setting resolves to `{ cmsPageSlug: null }` so the storefront falls back to
|
|
7
|
+
* its built-in landing page and a missing setting never 500s a public page.
|
|
8
|
+
*
|
|
9
|
+
* There is no "missing channel" arm any more (feature 075 / D-87): the channel
|
|
10
|
+
* arrives resolved, and the resolver middleware has already refused an unknown
|
|
11
|
+
* or inactive one before a handler runs.
|
|
12
|
+
*/
|
|
13
|
+
export declare class HomepageResolver {
|
|
14
|
+
private readonly settingsService;
|
|
15
|
+
constructor(settingsService: SettingsReadPort);
|
|
16
|
+
/**
|
|
17
|
+
* Feature 075 / D-87 — the resolved request channel's id, passed in by the
|
|
18
|
+
* route. It used to be the channel *code*, looked back up here with a raw
|
|
19
|
+
* `select id from sales_channels`: a re-resolution of a channel the resolver
|
|
20
|
+
* middleware had already resolved (feature 053, FR-011), across a boundary
|
|
21
|
+
* no import specifier named.
|
|
22
|
+
*/
|
|
23
|
+
resolve(salesChannelId: string): Promise<HomepageConfig>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=homepage-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"homepage-resolver.d.ts","sourceRoot":"","sources":["../../../src/backend/services/homepage-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAIzE;;;;;;;;;GASG;AACH,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,eAAe;gBAAf,eAAe,EAAE,gBAAgB;IAE9D;;;;;;OAMG;IACG,OAAO,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;CAM/D"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const HOMEPAGE_SETTING_CODE = 'homepage_cms_page_slug';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the storefront home-page configuration for a sales channel from the
|
|
4
|
+
* `homepage_cms_page_slug` setting. An unset / not-registered / out-of-scope
|
|
5
|
+
* setting resolves to `{ cmsPageSlug: null }` so the storefront falls back to
|
|
6
|
+
* its built-in landing page and a missing setting never 500s a public page.
|
|
7
|
+
*
|
|
8
|
+
* There is no "missing channel" arm any more (feature 075 / D-87): the channel
|
|
9
|
+
* arrives resolved, and the resolver middleware has already refused an unknown
|
|
10
|
+
* or inactive one before a handler runs.
|
|
11
|
+
*/
|
|
12
|
+
export class HomepageResolver {
|
|
13
|
+
settingsService;
|
|
14
|
+
constructor(settingsService) {
|
|
15
|
+
this.settingsService = settingsService;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Feature 075 / D-87 — the resolved request channel's id, passed in by the
|
|
19
|
+
* route. It used to be the channel *code*, looked back up here with a raw
|
|
20
|
+
* `select id from sales_channels`: a re-resolution of a channel the resolver
|
|
21
|
+
* middleware had already resolved (feature 053, FR-011), across a boundary
|
|
22
|
+
* no import specifier named.
|
|
23
|
+
*/
|
|
24
|
+
async resolve(salesChannelId) {
|
|
25
|
+
const resolved = await this.settingsService.getMany([HOMEPAGE_SETTING_CODE], salesChannelId);
|
|
26
|
+
const r = resolved.get(HOMEPAGE_SETTING_CODE);
|
|
27
|
+
const slug = r && r.ok && typeof r.value === 'string' ? r.value.trim() : '';
|
|
28
|
+
return { cmsPageSlug: slug === '' ? null : slug };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=homepage-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"homepage-resolver.js","sourceRoot":"","sources":["../../../src/backend/services/homepage-resolver.ts"],"names":[],"mappings":"AAGA,MAAM,qBAAqB,GAAG,wBAAwB,CAAC;AAEvD;;;;;;;;;GASG;AACH,MAAM,OAAO,gBAAgB;IACE;IAA7B,YAA6B,eAAiC;QAAjC,oBAAe,GAAf,eAAe,CAAkB;IAAG,CAAC;IAElE;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CAAC,cAAsB;QAClC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,qBAAqB,CAAC,EAAE,cAAc,CAAC,CAAC;QAC7F,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;QAC9C,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,OAAO,EAAE,WAAW,EAAE,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,CAAC;CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
|
|
2
|
+
export interface ProductCardButtonsConfig {
|
|
3
|
+
showAddToCart: boolean;
|
|
4
|
+
showAddToShoppingList: boolean;
|
|
5
|
+
showAddToQuote: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the storefront product-card button visibility toggles for a sales
|
|
9
|
+
* channel. Each flag defaults to `true` (button shown) when its setting is
|
|
10
|
+
* unset / not-registered / out-of-scope, so a settings hiccup never hides a
|
|
11
|
+
* storefront affordance and never 500s a public page.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ProductCardButtonsResolver {
|
|
14
|
+
private readonly settingsService;
|
|
15
|
+
constructor(settingsService: SettingsReadPort);
|
|
16
|
+
/**
|
|
17
|
+
* Feature 075 / D-87 — the resolved request channel's id, passed in by the
|
|
18
|
+
* route. It used to be the channel *code*, looked back up here with a raw
|
|
19
|
+
* `select id from sales_channels`: a re-resolution of a channel the resolver
|
|
20
|
+
* middleware had already resolved (feature 053, FR-011), across a boundary
|
|
21
|
+
* no import specifier named.
|
|
22
|
+
*/
|
|
23
|
+
resolve(salesChannelId: string): Promise<ProductCardButtonsConfig>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=product-card-buttons-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"product-card-buttons-resolver.d.ts","sourceRoot":"","sources":["../../../src/backend/services/product-card-buttons-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAQzE,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,OAAO,CAAC;IACvB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;;GAKG;AACH,qBAAa,0BAA0B;IACzB,OAAO,CAAC,QAAQ,CAAC,eAAe;gBAAf,eAAe,EAAE,gBAAgB;IAE9D;;;;;;OAMG;IACG,OAAO,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC;CAezE"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const ADD_TO_CART_CODE = 'storefront.product_card.show_add_to_cart';
|
|
2
|
+
const ADD_TO_SHOPPING_LIST_CODE = 'storefront.product_card.show_add_to_shopping_list';
|
|
3
|
+
// Owned by the quote_requests module's manifest; read here by code so the
|
|
4
|
+
// three product-card button toggles resolve through one endpoint.
|
|
5
|
+
const ADD_TO_QUOTE_CODE = 'quote_requests.show_add_to_quote_on_card';
|
|
6
|
+
/**
|
|
7
|
+
* Resolves the storefront product-card button visibility toggles for a sales
|
|
8
|
+
* channel. Each flag defaults to `true` (button shown) when its setting is
|
|
9
|
+
* unset / not-registered / out-of-scope, so a settings hiccup never hides a
|
|
10
|
+
* storefront affordance and never 500s a public page.
|
|
11
|
+
*/
|
|
12
|
+
export class ProductCardButtonsResolver {
|
|
13
|
+
settingsService;
|
|
14
|
+
constructor(settingsService) {
|
|
15
|
+
this.settingsService = settingsService;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Feature 075 / D-87 — the resolved request channel's id, passed in by the
|
|
19
|
+
* route. It used to be the channel *code*, looked back up here with a raw
|
|
20
|
+
* `select id from sales_channels`: a re-resolution of a channel the resolver
|
|
21
|
+
* middleware had already resolved (feature 053, FR-011), across a boundary
|
|
22
|
+
* no import specifier named.
|
|
23
|
+
*/
|
|
24
|
+
async resolve(salesChannelId) {
|
|
25
|
+
const resolved = await this.settingsService.getMany([ADD_TO_CART_CODE, ADD_TO_SHOPPING_LIST_CODE, ADD_TO_QUOTE_CODE], salesChannelId);
|
|
26
|
+
const flag = (code) => {
|
|
27
|
+
const r = resolved.get(code);
|
|
28
|
+
return r && r.ok && typeof r.value === 'boolean' ? r.value : true;
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
showAddToCart: flag(ADD_TO_CART_CODE),
|
|
32
|
+
showAddToShoppingList: flag(ADD_TO_SHOPPING_LIST_CODE),
|
|
33
|
+
showAddToQuote: flag(ADD_TO_QUOTE_CODE),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=product-card-buttons-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"product-card-buttons-resolver.js","sourceRoot":"","sources":["../../../src/backend/services/product-card-buttons-resolver.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GAAG,0CAA0C,CAAC;AACpE,MAAM,yBAAyB,GAAG,mDAAmD,CAAC;AACtF,0EAA0E;AAC1E,kEAAkE;AAClE,MAAM,iBAAiB,GAAG,0CAA0C,CAAC;AAQrE;;;;;GAKG;AACH,MAAM,OAAO,0BAA0B;IACR;IAA7B,YAA6B,eAAiC;QAAjC,oBAAe,GAAf,eAAe,CAAkB;IAAG,CAAC;IAElE;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CAAC,cAAsB;QAClC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CACjD,CAAC,gBAAgB,EAAE,yBAAyB,EAAE,iBAAiB,CAAC,EAChE,cAAc,CACf,CAAC;QACF,MAAM,IAAI,GAAG,CAAC,IAAY,EAAW,EAAE;YACrC,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC7B,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QACpE,CAAC,CAAC;QACF,OAAO;YACL,aAAa,EAAE,IAAI,CAAC,gBAAgB,CAAC;YACrC,qBAAqB,EAAE,IAAI,CAAC,yBAAyB,CAAC;YACtD,cAAc,EAAE,IAAI,CAAC,iBAAiB,CAAC;SACxC,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ModuleSettingsManifest, SettingsManifestCollectionPort, SettingsManifestSource } from '@endora-commerce/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Every settings manifest the boot-time {@link ManifestReconciler} must walk,
|
|
4
|
+
* derived from the module registry rather than hand-listed.
|
|
5
|
+
*
|
|
6
|
+
* This used to be a literal array in `composition.ts`, and a module could
|
|
7
|
+
* declare `settings: defineModuleSettingsManifest(...)` in its manifest yet be
|
|
8
|
+
* forgotten there. When that happened the module's `setting_groups` /
|
|
9
|
+
* `settings` rows were never created, so `/settings` silently omitted the whole
|
|
10
|
+
* group and `GET /api/v1/admin/settings/<code>` answered 404
|
|
11
|
+
* `SETTING_NOT_REGISTERED`. Eight modules had drifted that way — `ksef`, `mfa`,
|
|
12
|
+
* `carts`, `customers`, `orders`, `organizations`, `quick_order`, `returns` —
|
|
13
|
+
* and nothing caught it, not least because the test harness kept its own
|
|
14
|
+
* (differently stale) copy of the list.
|
|
15
|
+
*
|
|
16
|
+
* Deriving from the registry makes registration the single act that wires a
|
|
17
|
+
* module in, matching how permissions, i18n bundles, and command-palette
|
|
18
|
+
* actions already work.
|
|
19
|
+
*
|
|
20
|
+
* **The registry arrives as an argument (feature 075, Phase C).** It used to be
|
|
21
|
+
* imported from `_lifecycle`, which made `settings` name another module's file
|
|
22
|
+
* for a fact it does not own: which modules a deployment ships — core plus that
|
|
23
|
+
* deployment's overlay modules — is a composition-root input by nature, and it
|
|
24
|
+
* is why `resolvedModuleRegistry` is a platform-owned container name rather
|
|
25
|
+
* than a port. Both roots call this function and both already hold the
|
|
26
|
+
* registry, so the caller passes what it already has. Nothing is gated here on
|
|
27
|
+
* purpose: a module that is switched off keeps its settings rows and keeps its
|
|
28
|
+
* group on `/settings`, because a deactivation is not an uninstall
|
|
29
|
+
* (Constitution XVII) and the operator has to be able to switch it back on.
|
|
30
|
+
*/
|
|
31
|
+
export declare function collectRegisteredSettingsManifests(registry: ReadonlyArray<SettingsManifestSource>): ModuleSettingsManifest[];
|
|
32
|
+
/**
|
|
33
|
+
* The published face of the derivation above — feature 080 (T040b).
|
|
34
|
+
*
|
|
35
|
+
* Both composition roots called the function by importing this file, which is
|
|
36
|
+
* a root value import of a module's source and stops having a spelling the day
|
|
37
|
+
* `settings` becomes a package (D-160.6.1). The rule is unchanged and stays
|
|
38
|
+
* here, where the settings module's own manifest is: the roots keep supplying
|
|
39
|
+
* the registry, because which modules a deployment ships is theirs to say, and
|
|
40
|
+
* resolve the assembly from the container they already composed.
|
|
41
|
+
*/
|
|
42
|
+
export declare class SettingsManifestCollectionService implements SettingsManifestCollectionPort {
|
|
43
|
+
collect(registry: ReadonlyArray<SettingsManifestSource>): ModuleSettingsManifest[];
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=registered-settings-manifests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registered-settings-manifests.d.ts","sourceRoot":"","sources":["../../../src/backend/services/registered-settings-manifests.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EACtB,8BAA8B,EAC9B,sBAAsB,EACvB,MAAM,4BAA4B,CAAC;AAGpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,aAAa,CAAC,sBAAsB,CAAC,GAC9C,sBAAsB,EAAE,CAU1B;AAED;;;;;;;;;GASG;AACH,qBAAa,iCAAkC,YAAW,8BAA8B;IACtF,OAAO,CAAC,QAAQ,EAAE,aAAa,CAAC,sBAAsB,CAAC,GAAG,sBAAsB,EAAE;CAGnF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { settingsManifestWithRecentActivity } from '@endora-commerce/contracts';
|
|
2
|
+
import { settingsManifest as settingsModuleManifest } from '../../manifest.js';
|
|
3
|
+
/**
|
|
4
|
+
* Every settings manifest the boot-time {@link ManifestReconciler} must walk,
|
|
5
|
+
* derived from the module registry rather than hand-listed.
|
|
6
|
+
*
|
|
7
|
+
* This used to be a literal array in `composition.ts`, and a module could
|
|
8
|
+
* declare `settings: defineModuleSettingsManifest(...)` in its manifest yet be
|
|
9
|
+
* forgotten there. When that happened the module's `setting_groups` /
|
|
10
|
+
* `settings` rows were never created, so `/settings` silently omitted the whole
|
|
11
|
+
* group and `GET /api/v1/admin/settings/<code>` answered 404
|
|
12
|
+
* `SETTING_NOT_REGISTERED`. Eight modules had drifted that way — `ksef`, `mfa`,
|
|
13
|
+
* `carts`, `customers`, `orders`, `organizations`, `quick_order`, `returns` —
|
|
14
|
+
* and nothing caught it, not least because the test harness kept its own
|
|
15
|
+
* (differently stale) copy of the list.
|
|
16
|
+
*
|
|
17
|
+
* Deriving from the registry makes registration the single act that wires a
|
|
18
|
+
* module in, matching how permissions, i18n bundles, and command-palette
|
|
19
|
+
* actions already work.
|
|
20
|
+
*
|
|
21
|
+
* **The registry arrives as an argument (feature 075, Phase C).** It used to be
|
|
22
|
+
* imported from `_lifecycle`, which made `settings` name another module's file
|
|
23
|
+
* for a fact it does not own: which modules a deployment ships — core plus that
|
|
24
|
+
* deployment's overlay modules — is a composition-root input by nature, and it
|
|
25
|
+
* is why `resolvedModuleRegistry` is a platform-owned container name rather
|
|
26
|
+
* than a port. Both roots call this function and both already hold the
|
|
27
|
+
* registry, so the caller passes what it already has. Nothing is gated here on
|
|
28
|
+
* purpose: a module that is switched off keeps its settings rows and keeps its
|
|
29
|
+
* group on `/settings`, because a deactivation is not an uninstall
|
|
30
|
+
* (Constitution XVII) and the operator has to be able to switch it back on.
|
|
31
|
+
*/
|
|
32
|
+
export function collectRegisteredSettingsManifests(registry) {
|
|
33
|
+
// The settings module goes first: every other manifest's settings fall back
|
|
34
|
+
// to its `general` group, which must exist before they are inserted.
|
|
35
|
+
const rest = registry
|
|
36
|
+
.map((entry) => settingsManifestWithRecentActivity(entry.manifest, entry.recentActivity))
|
|
37
|
+
.filter((manifest) => manifest !== undefined && manifest.moduleCode !== settingsModuleManifest.moduleCode);
|
|
38
|
+
return [settingsModuleManifest, ...rest];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The published face of the derivation above — feature 080 (T040b).
|
|
42
|
+
*
|
|
43
|
+
* Both composition roots called the function by importing this file, which is
|
|
44
|
+
* a root value import of a module's source and stops having a spelling the day
|
|
45
|
+
* `settings` becomes a package (D-160.6.1). The rule is unchanged and stays
|
|
46
|
+
* here, where the settings module's own manifest is: the roots keep supplying
|
|
47
|
+
* the registry, because which modules a deployment ships is theirs to say, and
|
|
48
|
+
* resolve the assembly from the container they already composed.
|
|
49
|
+
*/
|
|
50
|
+
export class SettingsManifestCollectionService {
|
|
51
|
+
collect(registry) {
|
|
52
|
+
return collectRegisteredSettingsManifests(registry);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=registered-settings-manifests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registered-settings-manifests.js","sourceRoot":"","sources":["../../../src/backend/services/registered-settings-manifests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kCAAkC,EAAE,MAAM,4BAA4B,CAAC;AAMhF,OAAO,EAAE,gBAAgB,IAAI,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,kCAAkC,CAChD,QAA+C;IAE/C,4EAA4E;IAC5E,qEAAqE;IACrE,MAAM,IAAI,GAAG,QAAQ;SAClB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,kCAAkC,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;SACxF,MAAM,CACL,CAAC,QAAQ,EAAsC,EAAE,CAC/C,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,UAAU,KAAK,sBAAsB,CAAC,UAAU,CACtF,CAAC;IACJ,OAAO,CAAC,sBAAsB,EAAE,GAAG,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,iCAAiC;IAC5C,OAAO,CAAC,QAA+C;QACrD,OAAO,kCAAkC,CAAC,QAAQ,CAAC,CAAC;IACtD,CAAC;CACF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { SettingWriteValidator, SettingWriteValidatorRegistryPort } from '@endora-commerce/contracts';
|
|
2
|
+
import type { ModulePresencePort } from './settings-admin.service.js';
|
|
3
|
+
/**
|
|
4
|
+
* Where a module says whether the value another module is about to store is
|
|
5
|
+
* legal — feature 078, D-95.2.
|
|
6
|
+
*
|
|
7
|
+
* The division of labour is the whole point of the seam:
|
|
8
|
+
*
|
|
9
|
+
* > `settings` answers "what would every channel's value be after this write".
|
|
10
|
+
* > The module that declared the setting answers "is that legal".
|
|
11
|
+
*
|
|
12
|
+
* `settings` owns three-tier resolution and keeps owning it; it never learns
|
|
13
|
+
* what an invoice number is. `invoices` never re-derives the resolution.
|
|
14
|
+
*
|
|
15
|
+
* **Absent-contributor policy: `skip`.** A validator whose contributing module
|
|
16
|
+
* is not effectively present is not enumerated, so its refusal does not answer
|
|
17
|
+
* for a module an operator switched off.
|
|
18
|
+
*
|
|
19
|
+
* That filter is redundant today, and it is written anyway. `assertWritable`
|
|
20
|
+
* already refuses every write to a switched-off module's settings before
|
|
21
|
+
* validation runs — but that refusal has one exception, a module's own
|
|
22
|
+
* activation control, and a second exception added later must not silently let
|
|
23
|
+
* an absent module's validator answer for a code.
|
|
24
|
+
*/
|
|
25
|
+
export declare class SettingWriteValidatorRegistry implements SettingWriteValidatorRegistryPort {
|
|
26
|
+
/**
|
|
27
|
+
* Absent in a composition with no lifecycle (unit tests): every contributor
|
|
28
|
+
* then counts as present, which is the pre-073 behaviour rather than a
|
|
29
|
+
* fall-open — there is no activation axis to resolve at all.
|
|
30
|
+
*/
|
|
31
|
+
private readonly presence?;
|
|
32
|
+
private readonly validators;
|
|
33
|
+
constructor(
|
|
34
|
+
/**
|
|
35
|
+
* Absent in a composition with no lifecycle (unit tests): every contributor
|
|
36
|
+
* then counts as present, which is the pre-073 behaviour rather than a
|
|
37
|
+
* fall-open — there is no activation axis to resolve at all.
|
|
38
|
+
*/
|
|
39
|
+
presence?: ModulePresencePort | undefined);
|
|
40
|
+
register(validator: SettingWriteValidator): void;
|
|
41
|
+
forCode(code: string): readonly SettingWriteValidator[];
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=setting-write-validators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setting-write-validators.d.ts","sourceRoot":"","sources":["../../../src/backend/services/setting-write-validators.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,iCAAiC,EAClC,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,6BAA8B,YAAW,iCAAiC;IAInF;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAR5B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA+B;;IAGxD;;;;OAIG;IACc,QAAQ,CAAC,EAAE,kBAAkB,YAAA;IAGhD,QAAQ,CAAC,SAAS,EAAE,qBAAqB,GAAG,IAAI;IAIhD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,qBAAqB,EAAE;CAOxD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a module says whether the value another module is about to store is
|
|
3
|
+
* legal — feature 078, D-95.2.
|
|
4
|
+
*
|
|
5
|
+
* The division of labour is the whole point of the seam:
|
|
6
|
+
*
|
|
7
|
+
* > `settings` answers "what would every channel's value be after this write".
|
|
8
|
+
* > The module that declared the setting answers "is that legal".
|
|
9
|
+
*
|
|
10
|
+
* `settings` owns three-tier resolution and keeps owning it; it never learns
|
|
11
|
+
* what an invoice number is. `invoices` never re-derives the resolution.
|
|
12
|
+
*
|
|
13
|
+
* **Absent-contributor policy: `skip`.** A validator whose contributing module
|
|
14
|
+
* is not effectively present is not enumerated, so its refusal does not answer
|
|
15
|
+
* for a module an operator switched off.
|
|
16
|
+
*
|
|
17
|
+
* That filter is redundant today, and it is written anyway. `assertWritable`
|
|
18
|
+
* already refuses every write to a switched-off module's settings before
|
|
19
|
+
* validation runs — but that refusal has one exception, a module's own
|
|
20
|
+
* activation control, and a second exception added later must not silently let
|
|
21
|
+
* an absent module's validator answer for a code.
|
|
22
|
+
*/
|
|
23
|
+
export class SettingWriteValidatorRegistry {
|
|
24
|
+
presence;
|
|
25
|
+
validators = [];
|
|
26
|
+
constructor(
|
|
27
|
+
/**
|
|
28
|
+
* Absent in a composition with no lifecycle (unit tests): every contributor
|
|
29
|
+
* then counts as present, which is the pre-073 behaviour rather than a
|
|
30
|
+
* fall-open — there is no activation axis to resolve at all.
|
|
31
|
+
*/
|
|
32
|
+
presence) {
|
|
33
|
+
this.presence = presence;
|
|
34
|
+
}
|
|
35
|
+
register(validator) {
|
|
36
|
+
this.validators.push(validator);
|
|
37
|
+
}
|
|
38
|
+
forCode(code) {
|
|
39
|
+
return this.validators.filter((validator) => validator.codes.includes(code) &&
|
|
40
|
+
(this.presence?.presenceOf(validator.contributorModuleId) ?? true));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=setting-write-validators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setting-write-validators.js","sourceRoot":"","sources":["../../../src/backend/services/setting-write-validators.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,6BAA6B;IASrB;IARF,UAAU,GAA4B,EAAE,CAAC;IAE1D;IACE;;;;OAIG;IACc,QAA6B;QAA7B,aAAQ,GAAR,QAAQ,CAAqB;IAC7C,CAAC;IAEJ,QAAQ,CAAC,SAAgC;QACvC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAC3B,CAAC,SAAS,EAAE,EAAE,CACZ,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;YAC9B,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,SAAS,CAAC,mBAAmB,CAAC,IAAI,IAAI,CAAC,CACrE,CAAC;IACJ,CAAC;CACF"}
|