@endora-commerce/mod-google-analytics 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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/admin/api/google-analytics-client.d.ts +17 -0
  4. package/dist/admin/api/google-analytics-client.d.ts.map +1 -0
  5. package/dist/admin/api/google-analytics-client.js +21 -0
  6. package/dist/admin/api/google-analytics-client.js.map +1 -0
  7. package/dist/admin/index.d.ts +29 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +63 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/CustomEventEditPage.d.ts +3 -0
  12. package/dist/admin/pages/CustomEventEditPage.d.ts.map +1 -0
  13. package/dist/admin/pages/CustomEventEditPage.js +179 -0
  14. package/dist/admin/pages/CustomEventEditPage.js.map +1 -0
  15. package/dist/admin/pages/CustomEventsListPage.d.ts +8 -0
  16. package/dist/admin/pages/CustomEventsListPage.d.ts.map +1 -0
  17. package/dist/admin/pages/CustomEventsListPage.js +36 -0
  18. package/dist/admin/pages/CustomEventsListPage.js.map +1 -0
  19. package/dist/backend/entities/ga-custom-event.entity.d.ts +26 -0
  20. package/dist/backend/entities/ga-custom-event.entity.d.ts.map +1 -0
  21. package/dist/backend/entities/ga-custom-event.entity.js +82 -0
  22. package/dist/backend/entities/ga-custom-event.entity.js.map +1 -0
  23. package/dist/backend/index.d.ts +85 -0
  24. package/dist/backend/index.d.ts.map +1 -0
  25. package/dist/backend/index.js +102 -0
  26. package/dist/backend/index.js.map +1 -0
  27. package/dist/backend/routes.admin.d.ts +15 -0
  28. package/dist/backend/routes.admin.d.ts.map +1 -0
  29. package/dist/backend/routes.admin.js +35 -0
  30. package/dist/backend/routes.admin.js.map +1 -0
  31. package/dist/backend/routes.storefront.d.ts +17 -0
  32. package/dist/backend/routes.storefront.d.ts.map +1 -0
  33. package/dist/backend/routes.storefront.js +30 -0
  34. package/dist/backend/routes.storefront.js.map +1 -0
  35. package/dist/backend/services/cookie-consent-block-seeder.d.ts +29 -0
  36. package/dist/backend/services/cookie-consent-block-seeder.d.ts.map +1 -0
  37. package/dist/backend/services/cookie-consent-block-seeder.js +51 -0
  38. package/dist/backend/services/cookie-consent-block-seeder.js.map +1 -0
  39. package/dist/backend/services/custom-events.service.d.ts +54 -0
  40. package/dist/backend/services/custom-events.service.d.ts.map +1 -0
  41. package/dist/backend/services/custom-events.service.js +158 -0
  42. package/dist/backend/services/custom-events.service.js.map +1 -0
  43. package/dist/backend/services/ga-config.service.d.ts +27 -0
  44. package/dist/backend/services/ga-config.service.d.ts.map +1 -0
  45. package/dist/backend/services/ga-config.service.js +66 -0
  46. package/dist/backend/services/ga-config.service.js.map +1 -0
  47. package/dist/backend/services/ga4-mp-client.d.ts +32 -0
  48. package/dist/backend/services/ga4-mp-client.d.ts.map +1 -0
  49. package/dist/backend/services/ga4-mp-client.js +46 -0
  50. package/dist/backend/services/ga4-mp-client.js.map +1 -0
  51. package/dist/backend/services/ss-delivery-queue.d.ts +32 -0
  52. package/dist/backend/services/ss-delivery-queue.d.ts.map +1 -0
  53. package/dist/backend/services/ss-delivery-queue.js +29 -0
  54. package/dist/backend/services/ss-delivery-queue.js.map +1 -0
  55. package/dist/backend/services/ss-delivery.service.d.ts +24 -0
  56. package/dist/backend/services/ss-delivery.service.d.ts.map +1 -0
  57. package/dist/backend/services/ss-delivery.service.js +52 -0
  58. package/dist/backend/services/ss-delivery.service.js.map +1 -0
  59. package/dist/manifest.d.ts +194 -0
  60. package/dist/manifest.d.ts.map +1 -0
  61. package/dist/manifest.js +134 -0
  62. package/dist/manifest.js.map +1 -0
  63. package/dist/migrations/20260715T171116_google_analytics_init.d.ts +14 -0
  64. package/dist/migrations/20260715T171116_google_analytics_init.d.ts.map +1 -0
  65. package/dist/migrations/20260715T171116_google_analytics_init.js +35 -0
  66. package/dist/migrations/20260715T171116_google_analytics_init.js.map +1 -0
  67. package/dist/migrations/index.d.ts +28 -0
  68. package/dist/migrations/index.d.ts.map +1 -0
  69. package/dist/migrations/index.js +28 -0
  70. package/dist/migrations/index.js.map +1 -0
  71. package/docs/google-analytics.md +106 -0
  72. package/i18n/en.json +33 -0
  73. package/i18n/pl.json +33 -0
  74. package/package.json +98 -0
  75. package/tailwind.css +14 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-events.service.js","sourceRoot":"","sources":["../../../src/backend/services/custom-events.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { type GaStorefrontConfig, type GaStorefrontCustomEvent } from '@endora-commerce/contracts';
2
+ import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
3
+ /**
4
+ * Loader for a channel's enabled custom events, resolved into the storefront
5
+ * shape. Injected so the config service stays decoupled from the custom-events
6
+ * persistence (which lands in feature 049 US3). Defaults to an empty list until
7
+ * wired.
8
+ */
9
+ export type CustomEventsLoader = (salesChannelId: string) => Promise<GaStorefrontCustomEvent[]>;
10
+ /**
11
+ * Resolves the per-sales-channel Google Analytics configuration exposed to the
12
+ * storefront. Reads the module's Settings values (three-tier resolution) and
13
+ * folds in the channel's enabled custom events.
14
+ *
15
+ * A blank/absent Measurement ID means the channel is untracked (FR-004): the
16
+ * returned config has `enabled: false` and `measurementId: null` regardless of
17
+ * the master switch, so the storefront loads nothing for that channel.
18
+ */
19
+ export declare class GaConfigService {
20
+ private readonly settings;
21
+ private readonly loadCustomEvents;
22
+ constructor(settings: SettingsReadPort, loadCustomEvents?: CustomEventsLoader);
23
+ getConfig(salesChannelId: string): Promise<GaStorefrontConfig>;
24
+ private readBool;
25
+ private readString;
26
+ }
27
+ //# sourceMappingURL=ga-config.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ga-config.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/ga-config.service.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC7B,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAC/B,cAAc,EAAE,MAAM,KACnB,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAAC;AAExC;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBADhB,QAAQ,EAAE,gBAAgB,EAC1B,gBAAgB,GAAE,kBAAmC;IAGlE,SAAS,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;YAkDtD,QAAQ;YAYR,UAAU;CAWzB"}
@@ -0,0 +1,66 @@
1
+ import { z } from 'zod';
2
+ import { GOOGLE_ANALYTICS_SETTING_CODES, } from '@endora-commerce/contracts';
3
+ /**
4
+ * Resolves the per-sales-channel Google Analytics configuration exposed to the
5
+ * storefront. Reads the module's Settings values (three-tier resolution) and
6
+ * folds in the channel's enabled custom events.
7
+ *
8
+ * A blank/absent Measurement ID means the channel is untracked (FR-004): the
9
+ * returned config has `enabled: false` and `measurementId: null` regardless of
10
+ * the master switch, so the storefront loads nothing for that channel.
11
+ */
12
+ export class GaConfigService {
13
+ settings;
14
+ loadCustomEvents;
15
+ constructor(settings, loadCustomEvents = async () => []) {
16
+ this.settings = settings;
17
+ this.loadCustomEvents = loadCustomEvents;
18
+ }
19
+ async getConfig(salesChannelId) {
20
+ const enabled = await this.readBool(GOOGLE_ANALYTICS_SETTING_CODES.ENABLED, salesChannelId, false);
21
+ const measurementIdRaw = await this.readString(GOOGLE_ANALYTICS_SETTING_CODES.MEASUREMENT_ID, salesChannelId, '');
22
+ const measurementId = measurementIdRaw.trim() || null;
23
+ const requireConsent = await this.readBool(GOOGLE_ANALYTICS_SETTING_CODES.REQUIRE_CONSENT, salesChannelId, true);
24
+ // Untracked channel: master switch off or no measurement id (FR-004).
25
+ if (!enabled || measurementId === null) {
26
+ return {
27
+ enabled: false,
28
+ measurementId: null,
29
+ enhancedEcommerce: false,
30
+ serverSide: false,
31
+ requireConsent,
32
+ customEvents: [],
33
+ };
34
+ }
35
+ const [enhancedEcommerce, serverSide, customEvents] = await Promise.all([
36
+ this.readBool(GOOGLE_ANALYTICS_SETTING_CODES.ENHANCED_ECOMMERCE_ENABLED, salesChannelId, false),
37
+ this.readBool(GOOGLE_ANALYTICS_SETTING_CODES.SERVER_SIDE_ENABLED, salesChannelId, false),
38
+ this.loadCustomEvents(salesChannelId),
39
+ ]);
40
+ return {
41
+ enabled: true,
42
+ measurementId,
43
+ enhancedEcommerce,
44
+ serverSide,
45
+ requireConsent,
46
+ customEvents,
47
+ };
48
+ }
49
+ async readBool(code, salesChannelId, fallback) {
50
+ try {
51
+ return await this.settings.get(code, salesChannelId, z.boolean());
52
+ }
53
+ catch {
54
+ return fallback;
55
+ }
56
+ }
57
+ async readString(code, salesChannelId, fallback) {
58
+ try {
59
+ return await this.settings.get(code, salesChannelId, z.string());
60
+ }
61
+ catch {
62
+ return fallback;
63
+ }
64
+ }
65
+ }
66
+ //# sourceMappingURL=ga-config.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ga-config.service.js","sourceRoot":"","sources":["../../../src/backend/services/ga-config.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,8BAA8B,GAG/B,MAAM,4BAA4B,CAAC;AAapC;;;;;;;;GAQG;AACH,MAAM,OAAO,eAAe;IAEP;IACA;IAFnB,YACmB,QAA0B,EAC1B,mBAAuC,KAAK,IAAI,EAAE,CAAC,EAAE;QADrD,aAAQ,GAAR,QAAQ,CAAkB;QAC1B,qBAAgB,GAAhB,gBAAgB,CAAqC;IACrE,CAAC;IAEJ,KAAK,CAAC,SAAS,CAAC,cAAsB;QACpC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,QAAQ,CACjC,8BAA8B,CAAC,OAAO,EACtC,cAAc,EACd,KAAK,CACN,CAAC;QACF,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,UAAU,CAC5C,8BAA8B,CAAC,cAAc,EAC7C,cAAc,EACd,EAAE,CACH,CAAC;QACF,MAAM,aAAa,GAAG,gBAAgB,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;QACtD,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,QAAQ,CACxC,8BAA8B,CAAC,eAAe,EAC9C,cAAc,EACd,IAAI,CACL,CAAC;QAEF,sEAAsE;QACtE,IAAI,CAAC,OAAO,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;YACvC,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,aAAa,EAAE,IAAI;gBACnB,iBAAiB,EAAE,KAAK;gBACxB,UAAU,EAAE,KAAK;gBACjB,cAAc;gBACd,YAAY,EAAE,EAAE;aACjB,CAAC;QACJ,CAAC;QAED,MAAM,CAAC,iBAAiB,EAAE,UAAU,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACtE,IAAI,CAAC,QAAQ,CACX,8BAA8B,CAAC,0BAA0B,EACzD,cAAc,EACd,KAAK,CACN;YACD,IAAI,CAAC,QAAQ,CAAC,8BAA8B,CAAC,mBAAmB,EAAE,cAAc,EAAE,KAAK,CAAC;YACxF,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC;SACtC,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE,IAAI;YACb,aAAa;YACb,iBAAiB;YACjB,UAAU;YACV,cAAc;YACd,YAAY;SACb,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,QAAQ,CACpB,IAAY,EACZ,cAAsB,EACtB,QAAiB;QAEjB,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACpE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,UAAU,CACtB,IAAY,EACZ,cAAsB,EACtB,QAAgB;QAEhB,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACnE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * GA4 Measurement Protocol / server-side GTM client (feature 049, US4).
3
+ *
4
+ * Forwards a single analytics event server-side. Unlike the legacy
5
+ * `analytics/ga4-forwarder.ts` (fire-and-forget), this client THROWS on a
6
+ * non-2xx response or transport error so the BullMQ worker retries
7
+ * (Principle X / FR-024). `fetchFn` is injectable for tests.
8
+ */
9
+ export interface Ga4MpDestination {
10
+ /** Server-side GTM container URL; blank ⇒ GA4 MP default endpoint. */
11
+ endpoint: string;
12
+ measurementId: string;
13
+ apiSecret: string;
14
+ }
15
+ export interface Ga4MpEvent {
16
+ clientId: string;
17
+ name: string;
18
+ params: Record<string, string | number | boolean>;
19
+ consent: {
20
+ analyticsStorage: 'granted' | 'denied';
21
+ };
22
+ }
23
+ export interface Ga4MpClientOptions {
24
+ fetchFn?: typeof fetch;
25
+ timeoutMs?: number;
26
+ }
27
+ export declare class Ga4MpClient {
28
+ private readonly options;
29
+ constructor(options?: Ga4MpClientOptions);
30
+ send(dest: Ga4MpDestination, event: Ga4MpEvent): Promise<void>;
31
+ }
32
+ //# sourceMappingURL=ga4-mp-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ga4-mp-client.d.ts","sourceRoot":"","sources":["../../../src/backend/services/ga4-mp-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAClD,OAAO,EAAE;QAAE,gBAAgB,EAAE,SAAS,GAAG,QAAQ,CAAA;KAAE,CAAC;CACrD;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,qBAAa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,GAAE,kBAAuB;IAEvD,IAAI,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CA+BrE"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * GA4 Measurement Protocol / server-side GTM client (feature 049, US4).
3
+ *
4
+ * Forwards a single analytics event server-side. Unlike the legacy
5
+ * `analytics/ga4-forwarder.ts` (fire-and-forget), this client THROWS on a
6
+ * non-2xx response or transport error so the BullMQ worker retries
7
+ * (Principle X / FR-024). `fetchFn` is injectable for tests.
8
+ */
9
+ const MP_DEFAULT_BASE = 'https://www.google-analytics.com/mp/collect';
10
+ export class Ga4MpClient {
11
+ options;
12
+ constructor(options = {}) {
13
+ this.options = options;
14
+ }
15
+ async send(dest, event) {
16
+ const fetchFn = this.options.fetchFn ?? globalThis.fetch;
17
+ const base = dest.endpoint.trim() || MP_DEFAULT_BASE;
18
+ const url = `${base}?measurement_id=${encodeURIComponent(dest.measurementId)}` +
19
+ `&api_secret=${encodeURIComponent(dest.apiSecret)}`;
20
+ const controller = new AbortController();
21
+ const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs ?? 10_000);
22
+ try {
23
+ const res = await fetchFn(url, {
24
+ method: 'POST',
25
+ headers: { 'Content-Type': 'application/json' },
26
+ signal: controller.signal,
27
+ body: JSON.stringify({
28
+ client_id: event.clientId,
29
+ consent: {
30
+ ad_user_data: event.consent.analyticsStorage === 'granted' ? 'GRANTED' : 'DENIED',
31
+ ad_personalization: event.consent.analyticsStorage === 'granted' ? 'GRANTED' : 'DENIED',
32
+ },
33
+ events: [{ name: event.name, params: event.params }],
34
+ }),
35
+ });
36
+ // GA4 MP returns 204 on success; treat any non-2xx as retryable.
37
+ if (!res.ok) {
38
+ throw new Error(`GA4 MP delivery failed with status ${res.status}`);
39
+ }
40
+ }
41
+ finally {
42
+ clearTimeout(timeout);
43
+ }
44
+ }
45
+ }
46
+ //# sourceMappingURL=ga4-mp-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ga4-mp-client.js","sourceRoot":"","sources":["../../../src/backend/services/ga4-mp-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAqBH,MAAM,eAAe,GAAG,6CAA6C,CAAC;AAEtE,MAAM,OAAO,WAAW;IACO;IAA7B,YAA6B,UAA8B,EAAE;QAAhC,YAAO,GAAP,OAAO,CAAyB;IAAG,CAAC;IAEjE,KAAK,CAAC,IAAI,CAAC,IAAsB,EAAE,KAAiB;QAClD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC;QACzD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,eAAe,CAAC;QACrD,MAAM,GAAG,GACP,GAAG,IAAI,mBAAmB,kBAAkB,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE;YAClE,eAAe,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAEtD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC,CAAC;QACvF,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE;gBAC7B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,MAAM,EAAE,UAAU,CAAC,MAAM;gBACzB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,SAAS,EAAE,KAAK,CAAC,QAAQ;oBACzB,OAAO,EAAE;wBACP,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;wBACjF,kBAAkB,EAAE,KAAK,CAAC,OAAO,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;qBACxF;oBACD,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;iBACrD,CAAC;aACH,CAAC,CAAC;YACH,iEAAiE;YACjE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,sCAAsC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,32 @@
1
+ import { Queue, Worker, type Processor, type QueueOptions, type WorkerOptions } from 'bullmq';
2
+ import type { Redis } from 'ioredis';
3
+ import type { GaCollectRequest } from '@endora-commerce/contracts';
4
+ /**
5
+ * BullMQ queue for server-side Google Analytics event delivery (feature 049,
6
+ * US4 / Principle X). The storefront `/collect` route is a pure producer: it
7
+ * validates and enqueues one job per event; this worker forwards each event to
8
+ * the channel's GA4 destination (Measurement Protocol or server-side GTM),
9
+ * throwing on failure so BullMQ retries. Idempotency key: `eventId`.
10
+ */
11
+ export interface GaDeliveryJobData {
12
+ eventId: string;
13
+ salesChannelId: string;
14
+ clientId: string;
15
+ event: {
16
+ name: string;
17
+ params: Record<string, string | number | boolean>;
18
+ };
19
+ consent: {
20
+ analyticsStorage: 'granted' | 'denied';
21
+ };
22
+ occurredAt: string;
23
+ }
24
+ /**
25
+ * Producer callback exposed to the storefront route. Assigns an `eventId` per
26
+ * event, enqueues a delivery job for each, and returns how many were accepted.
27
+ */
28
+ export type GaCollectEnqueuer = (salesChannelId: string, request: GaCollectRequest) => Promise<number>;
29
+ export declare const GA_DELIVERY_QUEUE_NAME = "google_analytics.ss.deliver";
30
+ export declare function createGaDeliveryQueue(redis: Redis, overrides?: Partial<QueueOptions>): Queue<GaDeliveryJobData>;
31
+ export declare function createGaDeliveryWorker(redis: Redis, processor: Processor<GaDeliveryJobData>, overrides?: Partial<WorkerOptions>): Worker<GaDeliveryJobData>;
32
+ //# sourceMappingURL=ss-delivery-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ss-delivery-queue.d.ts","sourceRoot":"","sources":["../../../src/backend/services/ss-delivery-queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAE9F,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAA;KAAE,CAAC;IAC3E,OAAO,EAAE;QAAE,gBAAgB,EAAE,SAAS,GAAG,QAAQ,CAAA;KAAE,CAAC;IACpD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC9B,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,gBAAgB,KACtB,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB,eAAO,MAAM,sBAAsB,gCAAgC,CAAC;AAKpE,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,KAAK,EACZ,SAAS,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAChC,KAAK,CAAC,iBAAiB,CAAC,CAY1B;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,SAAS,CAAC,iBAAiB,CAAC,EACvC,SAAS,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GACjC,MAAM,CAAC,iBAAiB,CAAC,CAa3B"}
@@ -0,0 +1,29 @@
1
+ import { Queue, Worker } from 'bullmq';
2
+ import { enterSystemScope } from '@endora-commerce/platform/kernel';
3
+ export const GA_DELIVERY_QUEUE_NAME = 'google_analytics.ss.deliver';
4
+ const DEFAULT_ATTEMPTS = 8;
5
+ const DEFAULT_BACKOFF = { type: 'exponential', delay: 1_000 };
6
+ export function createGaDeliveryQueue(redis, overrides) {
7
+ const options = {
8
+ connection: redis,
9
+ defaultJobOptions: {
10
+ attempts: DEFAULT_ATTEMPTS,
11
+ backoff: DEFAULT_BACKOFF,
12
+ removeOnComplete: { count: 500 },
13
+ removeOnFail: { count: 5_000 },
14
+ },
15
+ ...overrides,
16
+ };
17
+ return new Queue(GA_DELIVERY_QUEUE_NAME, options);
18
+ }
19
+ export function createGaDeliveryWorker(redis, processor, overrides) {
20
+ const options = {
21
+ connection: redis,
22
+ concurrency: 8,
23
+ ...overrides,
24
+ };
25
+ // Feature 072 (T033) — the job establishes its own scope; it established
26
+ // nothing before.
27
+ return new Worker(GA_DELIVERY_QUEUE_NAME, (job) => enterSystemScope('google_analytics: server-side delivery', () => processor(job)), options);
28
+ }
29
+ //# sourceMappingURL=ss-delivery-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ss-delivery-queue.js","sourceRoot":"","sources":["../../../src/backend/services/ss-delivery-queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAyD,MAAM,QAAQ,CAAC;AAC9F,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AA6BpE,MAAM,CAAC,MAAM,sBAAsB,GAAG,6BAA6B,CAAC;AAEpE,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAC3B,MAAM,eAAe,GAAG,EAAE,IAAI,EAAE,aAAsB,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAEvE,MAAM,UAAU,qBAAqB,CACnC,KAAY,EACZ,SAAiC;IAEjC,MAAM,OAAO,GAAiB;QAC5B,UAAU,EAAE,KAAK;QACjB,iBAAiB,EAAE;YACjB,QAAQ,EAAE,gBAAgB;YAC1B,OAAO,EAAE,eAAe;YACxB,gBAAgB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;YAChC,YAAY,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;SAC/B;QACD,GAAG,SAAS;KACb,CAAC;IACF,OAAO,IAAI,KAAK,CAAoB,sBAAsB,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,KAAY,EACZ,SAAuC,EACvC,SAAkC;IAElC,MAAM,OAAO,GAAkB;QAC7B,UAAU,EAAE,KAAK;QACjB,WAAW,EAAE,CAAC;QACd,GAAG,SAAS;KACb,CAAC;IACF,yEAAyE;IACzE,kBAAkB;IAClB,OAAO,IAAI,MAAM,CACf,sBAAsB,EACtB,CAAC,GAAG,EAAE,EAAE,CAAC,gBAAgB,CAAC,wCAAwC,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,EACzF,OAAO,CACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { Processor } from 'bullmq';
2
+ import type { Queue } from 'bullmq';
3
+ import { type GaCollectRequest } from '@endora-commerce/contracts';
4
+ import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
5
+ import type { GaDeliveryJobData } from './ss-delivery-queue.js';
6
+ import type { Ga4MpClient } from './ga4-mp-client.js';
7
+ /**
8
+ * Server-side delivery orchestration (feature 049, US4).
9
+ *
10
+ * - `makeEnqueuer` returns the producer used by the `/collect` route: it assigns
11
+ * an `eventId` per event (idempotency key) and enqueues one delivery job each.
12
+ * Pure producer — no forwarding here (Principle X).
13
+ * - `makeProcessor` returns the BullMQ processor: it resolves the channel's
14
+ * Measurement ID + endpoint + decrypted API secret from Settings and forwards
15
+ * via the MP client, throwing on failure so BullMQ retries. Idempotent w.r.t.
16
+ * retry (the destination call carries the stable `eventId` in params).
17
+ */
18
+ export declare function makeEnqueuer(queue: Queue<GaDeliveryJobData>): (salesChannelId: string, request: GaCollectRequest) => Promise<number>;
19
+ export interface ProcessorDeps {
20
+ settings: SettingsReadPort;
21
+ client: Ga4MpClient;
22
+ }
23
+ export declare function makeProcessor(deps: ProcessorDeps): Processor<GaDeliveryJobData>;
24
+ //# sourceMappingURL=ss-delivery.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ss-delivery.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/ss-delivery.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAkC,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,iBAAiB,CAAC,IAC5C,gBAAgB,MAAM,EAAE,SAAS,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAAC,CAqBlF;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,CAAC,iBAAiB,CAAC,CAiC/E"}
@@ -0,0 +1,52 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { z } from 'zod';
3
+ import { GOOGLE_ANALYTICS_SETTING_CODES } from '@endora-commerce/contracts';
4
+ /**
5
+ * Server-side delivery orchestration (feature 049, US4).
6
+ *
7
+ * - `makeEnqueuer` returns the producer used by the `/collect` route: it assigns
8
+ * an `eventId` per event (idempotency key) and enqueues one delivery job each.
9
+ * Pure producer — no forwarding here (Principle X).
10
+ * - `makeProcessor` returns the BullMQ processor: it resolves the channel's
11
+ * Measurement ID + endpoint + decrypted API secret from Settings and forwards
12
+ * via the MP client, throwing on failure so BullMQ retries. Idempotent w.r.t.
13
+ * retry (the destination call carries the stable `eventId` in params).
14
+ */
15
+ export function makeEnqueuer(queue) {
16
+ return async (salesChannelId, request) => {
17
+ const occurredAt = new Date().toISOString();
18
+ let accepted = 0;
19
+ for (const event of request.events) {
20
+ const eventId = randomUUID();
21
+ await queue.add('deliver', {
22
+ eventId,
23
+ salesChannelId,
24
+ clientId: request.clientId,
25
+ event: { name: event.name, params: { ...event.params, ga_event_id: eventId } },
26
+ consent: request.consent,
27
+ occurredAt,
28
+ }, { jobId: eventId });
29
+ accepted += 1;
30
+ }
31
+ return accepted;
32
+ };
33
+ }
34
+ export function makeProcessor(deps) {
35
+ return async (job) => {
36
+ const { salesChannelId } = job.data;
37
+ const measurementId = await deps.settings.get(GOOGLE_ANALYTICS_SETTING_CODES.MEASUREMENT_ID, salesChannelId, z.string());
38
+ if (!measurementId.trim()) {
39
+ // Channel untracked — drop silently (do not retry a permanent no-op).
40
+ return;
41
+ }
42
+ const apiSecret = await deps.settings.get(GOOGLE_ANALYTICS_SETTING_CODES.SERVER_SIDE_API_SECRET, salesChannelId, z.string());
43
+ const endpoint = await deps.settings.get(GOOGLE_ANALYTICS_SETTING_CODES.SERVER_SIDE_ENDPOINT, salesChannelId, z.string());
44
+ await deps.client.send({ endpoint, measurementId, apiSecret }, {
45
+ clientId: job.data.clientId,
46
+ name: job.data.event.name,
47
+ params: job.data.event.params,
48
+ consent: job.data.consent,
49
+ });
50
+ };
51
+ }
52
+ //# sourceMappingURL=ss-delivery.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ss-delivery.service.js","sourceRoot":"","sources":["../../../src/backend/services/ss-delivery.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,8BAA8B,EAAyB,MAAM,4BAA4B,CAAC;AAKnG;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,KAA+B;IAC1D,OAAO,KAAK,EAAE,cAAsB,EAAE,OAAyB,EAAmB,EAAE;QAClF,MAAM,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC5C,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;YAC7B,MAAM,KAAK,CAAC,GAAG,CACb,SAAS,EACT;gBACE,OAAO;gBACP,cAAc;gBACd,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE;gBAC9E,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,UAAU;aACX,EACD,EAAE,KAAK,EAAE,OAAO,EAAE,CACnB,CAAC;YACF,QAAQ,IAAI,CAAC,CAAC;QAChB,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC;AAOD,MAAM,UAAU,aAAa,CAAC,IAAmB;IAC/C,OAAO,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;QACpC,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAC3C,8BAA8B,CAAC,cAAc,EAC7C,cAAc,EACd,CAAC,CAAC,MAAM,EAAE,CACX,CAAC;QACF,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC;YAC1B,sEAAsE;YACtE,OAAO;QACT,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CACvC,8BAA8B,CAAC,sBAAsB,EACrD,cAAc,EACd,CAAC,CAAC,MAAM,EAAE,CACX,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CACtC,8BAA8B,CAAC,oBAAoB,EACnD,cAAc,EACd,CAAC,CAAC,MAAM,EAAE,CACX,CAAC;QAEF,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CACpB,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,EACtC;YACE,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ;YAC3B,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;YACzB,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM;YAC7B,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO;SAC1B,CACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Google Analytics module (feature 049). Integrates the storefront with
3
+ * Google Analytics 4: per-sales-channel activation + Measurement ID, Enhanced
4
+ * Ecommerce, an optional server-side tagging delivery path (durable BullMQ
5
+ * worker — Principle X), and an admin custom-events builder. Per-channel
6
+ * configuration is stored through the Settings module; the Measurement Protocol
7
+ * API secret uses the `secret` value type (feature 043, AES-256-GCM at rest).
8
+ *
9
+ * Distinct from the legacy `analytics` module (internal first-party event log +
10
+ * env-gated forwarder), which this module supersedes for GA forwarding.
11
+ */
12
+ export declare const googleAnalyticsSettingsManifest: {
13
+ moduleCode: string;
14
+ groups: {
15
+ code: string;
16
+ name: string;
17
+ salesChannelCodes?: string[] | undefined;
18
+ isSystemProtected?: boolean | undefined;
19
+ }[];
20
+ settings: {
21
+ code: string;
22
+ name: string;
23
+ valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
24
+ defaultValue: unknown;
25
+ description?: string | undefined;
26
+ groupCode?: string | undefined;
27
+ previousDefaultValues?: unknown[] | undefined;
28
+ salesChannelCodes?: string[] | undefined;
29
+ enumOptions?: string[] | undefined;
30
+ configurationType?: string | undefined;
31
+ hidden?: boolean | undefined;
32
+ }[];
33
+ };
34
+ export declare const manifest: {
35
+ id: string;
36
+ name: string;
37
+ version: string;
38
+ dependencies: string[];
39
+ description?: string | undefined;
40
+ acknowledgedDependencies?: {
41
+ moduleId: string;
42
+ port: string;
43
+ reason: string;
44
+ }[] | undefined;
45
+ nonBindingDependencies?: {
46
+ moduleId: string;
47
+ name: string;
48
+ kind: "contributes-to" | "degrades-without" | "refuses-without";
49
+ reason: string;
50
+ whenAbsent?: string | undefined;
51
+ }[] | undefined;
52
+ activation?: {
53
+ settingCode: string;
54
+ default: boolean;
55
+ } | {
56
+ nonDeactivatable: true;
57
+ reason: string;
58
+ } | undefined;
59
+ settings?: {
60
+ moduleCode: string;
61
+ groups: {
62
+ code: string;
63
+ name: string;
64
+ salesChannelCodes?: string[] | undefined;
65
+ isSystemProtected?: boolean | undefined;
66
+ }[];
67
+ settings: {
68
+ code: string;
69
+ name: string;
70
+ valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
71
+ defaultValue: unknown;
72
+ description?: string | undefined;
73
+ groupCode?: string | undefined;
74
+ previousDefaultValues?: unknown[] | undefined;
75
+ salesChannelCodes?: string[] | undefined;
76
+ enumOptions?: string[] | undefined;
77
+ configurationType?: string | undefined;
78
+ hidden?: boolean | undefined;
79
+ }[];
80
+ } | undefined;
81
+ i18n?: {
82
+ bundlesDir: string;
83
+ } | undefined;
84
+ docs?: false | {
85
+ dir: string;
86
+ } | undefined;
87
+ demo?: false | {
88
+ summary: string;
89
+ seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
90
+ reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
91
+ after?: readonly string[] | undefined;
92
+ package?: string | undefined;
93
+ } | undefined;
94
+ actions?: {
95
+ id: string;
96
+ labelKey: string;
97
+ icon: "Plus" | "Sparkles" | "Settings" | "Search" | "Boxes" | "Layers" | "Menu" | "PlusCircle" | "PlusSquare" | "FilePlus" | "FolderPlus" | "Upload" | "FileUp" | "CloudUpload" | "Download" | "FileDown" | "FileText" | "BookOpen" | "Rss" | "Package" | "Tag" | "ShoppingCart" | "Receipt" | "CreditCard" | "Users" | "UserPlus" | "Inbox" | "ListChecks" | "ClipboardList" | "Image" | "Video" | "LayoutDashboard" | "PanelLeft" | "KeyRound" | "ShieldCheck" | "Edit" | "Archive" | "Box" | "Truck" | "CircleDollarSign" | "Activity" | "LineChart" | "Smartphone" | "Webhook" | "Scale" | "PlugZap" | "PercentDiamond" | "Newspaper" | "Languages" | "Eraser" | "Warehouse" | "TrendingDown" | "Bell" | "PackageOpen" | "Building2" | "Store" | "ClipboardCheck";
98
+ targetRoute: string;
99
+ keywords: string[];
100
+ weight: number;
101
+ descriptionKey?: string | undefined;
102
+ requiredPermission?: string | undefined;
103
+ }[] | undefined;
104
+ permissions?: {
105
+ code: string;
106
+ label: string;
107
+ module?: string | undefined;
108
+ description?: string | undefined;
109
+ requires?: string[] | undefined;
110
+ }[] | undefined;
111
+ transactionalEmails?: {
112
+ code: string;
113
+ name: string;
114
+ variables: {
115
+ key: string;
116
+ label: string;
117
+ sampleValue?: string | undefined;
118
+ description?: string | undefined;
119
+ }[];
120
+ description?: string | undefined;
121
+ group?: string | undefined;
122
+ }[] | undefined;
123
+ capabilities?: string[] | undefined;
124
+ exclusiveCapabilities?: {
125
+ key: string;
126
+ errorCode: string;
127
+ }[] | undefined;
128
+ errorCodes?: {
129
+ code: string;
130
+ tokens?: string[] | undefined;
131
+ }[] | undefined;
132
+ blocks?: {
133
+ name: string;
134
+ labelKey: string;
135
+ category: string;
136
+ contexts: ("cms" | "invoice" | "email" | "newsletter")[];
137
+ fields: Record<string, {
138
+ type: "number" | "object" | "array" | "text" | "textarea" | "select" | "radio" | "external" | "uuid" | "richtext";
139
+ label?: string | undefined;
140
+ required?: boolean | undefined;
141
+ options?: {
142
+ label: string;
143
+ value: string | number;
144
+ }[] | undefined;
145
+ refKind?: string | undefined;
146
+ }>;
147
+ descriptionKey?: string | undefined;
148
+ defaultProps?: Record<string, unknown> | undefined;
149
+ responsiveFields?: string[] | undefined;
150
+ previewIcon?: string | undefined;
151
+ weight?: number | undefined;
152
+ }[] | undefined;
153
+ blockCategories?: {
154
+ key: string;
155
+ titleKey: string;
156
+ contexts: ("cms" | "invoice" | "email" | "newsletter")[];
157
+ weight?: number | undefined;
158
+ visible?: boolean | undefined;
159
+ }[] | undefined;
160
+ env?: {
161
+ name: string;
162
+ describes: {
163
+ en: string;
164
+ pl: string;
165
+ };
166
+ requirement: {
167
+ kind: "required";
168
+ } | {
169
+ kind: "requiredWhen";
170
+ input: string;
171
+ equals: string;
172
+ } | {
173
+ kind: "optional";
174
+ without: {
175
+ en: string;
176
+ pl: string;
177
+ };
178
+ };
179
+ secret: boolean;
180
+ generable: boolean;
181
+ owner: {
182
+ kind: "platform";
183
+ } | {
184
+ kind: "application";
185
+ application: "admin" | "backend" | "storefront";
186
+ } | {
187
+ kind: "module";
188
+ moduleId: string;
189
+ };
190
+ consumers: ("admin" | "backend" | "storefront")[];
191
+ addressOf: "admin" | "backend" | "storefront" | null;
192
+ }[] | undefined;
193
+ };
194
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;CA2E1C,CAAC;AAEH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8CnB,CAAC"}