@endora-commerce/mod-admin-actions 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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/dist/backend/entities/module-action.entity.d.ts +30 -0
  4. package/dist/backend/entities/module-action.entity.d.ts.map +1 -0
  5. package/dist/backend/entities/module-action.entity.js +100 -0
  6. package/dist/backend/entities/module-action.entity.js.map +1 -0
  7. package/dist/backend/index.d.ts +85 -0
  8. package/dist/backend/index.d.ts.map +1 -0
  9. package/dist/backend/index.js +57 -0
  10. package/dist/backend/index.js.map +1 -0
  11. package/dist/backend/plugin.d.ts +110 -0
  12. package/dist/backend/plugin.d.ts.map +1 -0
  13. package/dist/backend/plugin.js +77 -0
  14. package/dist/backend/plugin.js.map +1 -0
  15. package/dist/backend/routes.admin.d.ts +19 -0
  16. package/dist/backend/routes.admin.d.ts.map +1 -0
  17. package/dist/backend/routes.admin.js +32 -0
  18. package/dist/backend/routes.admin.js.map +1 -0
  19. package/dist/backend/services/admin-actions-reconciler.d.ts +49 -0
  20. package/dist/backend/services/admin-actions-reconciler.d.ts.map +1 -0
  21. package/dist/backend/services/admin-actions-reconciler.js +78 -0
  22. package/dist/backend/services/admin-actions-reconciler.js.map +1 -0
  23. package/dist/backend/services/admin-actions-service.d.ts +136 -0
  24. package/dist/backend/services/admin-actions-service.d.ts.map +1 -0
  25. package/dist/backend/services/admin-actions-service.js +168 -0
  26. package/dist/backend/services/admin-actions-service.js.map +1 -0
  27. package/dist/manifest.d.ts +196 -0
  28. package/dist/manifest.d.ts.map +1 -0
  29. package/dist/manifest.js +85 -0
  30. package/dist/manifest.js.map +1 -0
  31. package/dist/migrations/20260507T142354_admin_actions_init.d.ts +31 -0
  32. package/dist/migrations/20260507T142354_admin_actions_init.d.ts.map +1 -0
  33. package/dist/migrations/20260507T142354_admin_actions_init.js +55 -0
  34. package/dist/migrations/20260507T142354_admin_actions_init.js.map +1 -0
  35. package/dist/migrations/index.d.ts +27 -0
  36. package/dist/migrations/index.d.ts.map +1 -0
  37. package/dist/migrations/index.js +29 -0
  38. package/dist/migrations/index.js.map +1 -0
  39. package/docs/admin-actions.md +164 -0
  40. package/i18n/en.json +5 -0
  41. package/i18n/pl.json +5 -0
  42. package/package.json +68 -0
@@ -0,0 +1,49 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { ModuleAction as ModuleActionDecl } from '@endora-commerce/contracts';
3
+ /**
4
+ * Admin Actions Reconciler — feature 020.
5
+ *
6
+ * Idempotent UPSERT-and-prune of `module_actions` rows from a module's
7
+ * declared `manifest.actions` array. Mirrors feature 019's i18n-service
8
+ * UPSERT pattern: a single SQL statement with `INSERT … ON CONFLICT
9
+ * (module_id, action_id) DO UPDATE` so every successful install
10
+ * advances the per-row `version` sequence whether the row existed or
11
+ * not. After UPSERT, any rows owned by `moduleId` whose `action_id` is
12
+ * no longer in the new declaration are deleted (prune).
13
+ *
14
+ * Wraps work in the caller's transaction (the lifecycle orchestrator
15
+ * supplies its own `EntityManager`); idempotent — re-running install
16
+ * with the same payload is a no-op aside from bumping `version`.
17
+ */
18
+ export interface AdminActionsReconcilerDeps {
19
+ em: () => EntityManager;
20
+ }
21
+ export declare class AdminActionsReconciler {
22
+ private readonly deps;
23
+ constructor(deps: AdminActionsReconcilerDeps);
24
+ /**
25
+ * Install (or update) every declared action for `moduleId`. After
26
+ * inserting/updating the declared rows, prune any rows for the same
27
+ * module whose action_id is not present in the new declaration.
28
+ */
29
+ installForModule(args: {
30
+ moduleId: string;
31
+ actions: readonly ModuleActionDecl[];
32
+ em?: EntityManager;
33
+ }): Promise<{
34
+ upserted: number;
35
+ pruned: number;
36
+ }>;
37
+ /**
38
+ * Hard-uninstall — drop every action row for the module. Soft-
39
+ * uninstall (state → 'disabled') intentionally leaves rows in place;
40
+ * visibility is gated by the registry-state join at query time.
41
+ */
42
+ removeForModule(args: {
43
+ moduleId: string;
44
+ em?: EntityManager;
45
+ }): Promise<{
46
+ removed: number;
47
+ }>;
48
+ }
49
+ //# sourceMappingURL=admin-actions-reconciler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-actions-reconciler.d.ts","sourceRoot":"","sources":["../../../src/backend/services/admin-actions-reconciler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,IAAI,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAGnF;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,aAAa,CAAC;CACzB;AAED,qBAAa,sBAAsB;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,0BAA0B;IAE7D;;;;OAIG;IACG,gBAAgB,CAAC,IAAI,EAAE;QAC3B,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;QACrC,EAAE,CAAC,EAAE,aAAa,CAAC;KACpB,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IA2DjD;;;;OAIG;IACG,eAAe,CAAC,IAAI,EAAE;QAC1B,QAAQ,EAAE,MAAM,CAAC;QACjB,EAAE,CAAC,EAAE,aAAa,CAAC;KACpB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAOjC"}
@@ -0,0 +1,78 @@
1
+ import { ModuleAction } from '../entities/module-action.entity.js';
2
+ export class AdminActionsReconciler {
3
+ deps;
4
+ constructor(deps) {
5
+ this.deps = deps;
6
+ }
7
+ /**
8
+ * Install (or update) every declared action for `moduleId`. After
9
+ * inserting/updating the declared rows, prune any rows for the same
10
+ * module whose action_id is not present in the new declaration.
11
+ */
12
+ async installForModule(args) {
13
+ // command-coverage-ignore: idempotent lifecycle reconciler/seed — a system-
14
+ // invariant repair, not an operator-initiated audited write.
15
+ const targetEm = args.em ?? this.deps.em();
16
+ const declaredIds = new Set();
17
+ for (const action of args.actions) {
18
+ declaredIds.add(action.id);
19
+ // `targetEm.execute`, not `targetEm.getKnex().raw` — the knex instance is
20
+ // connection-level and carries no transaction context, so the UPSERT
21
+ // committed on its own connection while the prune below it (an
22
+ // `em.nativeDelete`) stayed inside the caller's transaction. One method,
23
+ // two answers to a rollback (issue #200).
24
+ await targetEm.execute(`insert into "module_actions" (` +
25
+ `"module_id", "action_id", "label_key", "description_key", "icon", ` +
26
+ `"target_route", "required_permission", "keywords", "weight", ` +
27
+ `"installed_at", "updated_at"` +
28
+ `) values (?, ?, ?, ?, ?, ?, ?, ?::jsonb, ?, now(), now()) ` +
29
+ `on conflict ("module_id", "action_id") do update set ` +
30
+ `"label_key" = excluded."label_key", ` +
31
+ `"description_key" = excluded."description_key", ` +
32
+ `"icon" = excluded."icon", ` +
33
+ `"target_route" = excluded."target_route", ` +
34
+ `"required_permission" = excluded."required_permission", ` +
35
+ `"keywords" = excluded."keywords", ` +
36
+ `"weight" = excluded."weight", ` +
37
+ `"version" = nextval('module_actions_version_seq'), ` +
38
+ `"updated_at" = now()`, [
39
+ args.moduleId,
40
+ action.id,
41
+ action.labelKey,
42
+ action.descriptionKey ?? null,
43
+ action.icon,
44
+ action.targetRoute,
45
+ action.requiredPermission ?? null,
46
+ JSON.stringify(action.keywords ?? []),
47
+ action.weight ?? 100,
48
+ ]);
49
+ }
50
+ // Prune: delete any rows for this module whose action_id is not in
51
+ // the newly-declared set. The empty-set case is safe — the
52
+ // whereNotIn(...) becomes a delete-all-for-module.
53
+ let pruned = 0;
54
+ if (declaredIds.size > 0) {
55
+ pruned = await targetEm.nativeDelete(ModuleAction, {
56
+ moduleId: args.moduleId,
57
+ actionId: { $nin: [...declaredIds] },
58
+ });
59
+ }
60
+ else {
61
+ pruned = await targetEm.nativeDelete(ModuleAction, { moduleId: args.moduleId });
62
+ }
63
+ return { upserted: args.actions.length, pruned };
64
+ }
65
+ /**
66
+ * Hard-uninstall — drop every action row for the module. Soft-
67
+ * uninstall (state → 'disabled') intentionally leaves rows in place;
68
+ * visibility is gated by the registry-state join at query time.
69
+ */
70
+ async removeForModule(args) {
71
+ // command-coverage-ignore: idempotent lifecycle reconciler/seed — a system-
72
+ // invariant repair, not an operator-initiated audited write.
73
+ const targetEm = args.em ?? this.deps.em();
74
+ const removed = await targetEm.nativeDelete(ModuleAction, { moduleId: args.moduleId });
75
+ return { removed };
76
+ }
77
+ }
78
+ //# sourceMappingURL=admin-actions-reconciler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-actions-reconciler.js","sourceRoot":"","sources":["../../../src/backend/services/admin-actions-reconciler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAqBnE,MAAM,OAAO,sBAAsB;IACJ;IAA7B,YAA6B,IAAgC;QAAhC,SAAI,GAAJ,IAAI,CAA4B;IAAG,CAAC;IAEjE;;;;OAIG;IACH,KAAK,CAAC,gBAAgB,CAAC,IAItB;QACC,4EAA4E;QAC5E,6DAA6D;QAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;QAEtC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC3B,0EAA0E;YAC1E,qEAAqE;YACrE,+DAA+D;YAC/D,yEAAyE;YACzE,0CAA0C;YAC1C,MAAM,QAAQ,CAAC,OAAO,CACpB,gCAAgC;gBAC9B,oEAAoE;gBACpE,+DAA+D;gBAC/D,8BAA8B;gBAC9B,4DAA4D;gBAC5D,uDAAuD;gBACvD,sCAAsC;gBACtC,kDAAkD;gBAClD,4BAA4B;gBAC5B,4CAA4C;gBAC5C,0DAA0D;gBAC1D,oCAAoC;gBACpC,gCAAgC;gBAChC,qDAAqD;gBACrD,sBAAsB,EACxB;gBACE,IAAI,CAAC,QAAQ;gBACb,MAAM,CAAC,EAAE;gBACT,MAAM,CAAC,QAAQ;gBACf,MAAM,CAAC,cAAc,IAAI,IAAI;gBAC7B,MAAM,CAAC,IAAI;gBACX,MAAM,CAAC,WAAW;gBAClB,MAAM,CAAC,kBAAkB,IAAI,IAAI;gBACjC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;gBACrC,MAAM,CAAC,MAAM,IAAI,GAAG;aACrB,CACF,CAAC;QACJ,CAAC;QAED,mEAAmE;QACnE,2DAA2D;QAC3D,mDAAmD;QACnD,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,IAAI,WAAW,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,YAAY,EAAE;gBACjD,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,WAAW,CAAC,EAAE;aACrC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClF,CAAC;QAED,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IACnD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,eAAe,CAAC,IAGrB;QACC,4EAA4E;QAC5E,6DAA6D;QAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACvF,OAAO,EAAE,OAAO,EAAE,CAAC;IACrB,CAAC;CACF"}
@@ -0,0 +1,136 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { AdminActionView, AdminI18nTranslatePort, PermissionReadPort, SupportedAdminLanguage } from '@endora-commerce/contracts';
3
+ export interface AdminActionsServiceDeps {
4
+ em: () => EntityManager;
5
+ /**
6
+ * `_i18n`'s resolver and `admin_roles`' permission read, both as the contract
7
+ * types those modules publish rather than their service classes (feature 075,
8
+ * Phase C). What this service asks for is two methods — resolve a string,
9
+ * list an operator's permissions — where the classes brought bundle
10
+ * installation, coverage snapshots and role administration across the
11
+ * boundary with them.
12
+ */
13
+ i18nService: AdminI18nTranslatePort;
14
+ permissionService: PermissionReadPort;
15
+ log?: {
16
+ info(msg: string): void;
17
+ warn(msg: string): void;
18
+ };
19
+ /**
20
+ * Feature 073 / issue #225 / issue #187 — **both** presence axes and the
21
+ * generation the readings came from, injected as one value.
22
+ *
23
+ * The platform axis joined this module's rows to `module_registrations` until
24
+ * feature 080's ledger sweep. It reads through the probe now, from the same
25
+ * `effectiveState` projection the operator axis has always come from, so the
26
+ * palette cannot disagree with the route gates about a module it advertises.
27
+ *
28
+ * Injected rather than read from the lifecycle singleton so this service
29
+ * stays constructible without a warmed process cache — a test that drives
30
+ * presence must be able to hand over the presence it is driving rather than
31
+ * reach for a process-wide seam.
32
+ *
33
+ * The readings and the version travel together, in one object, because a
34
+ * composition that supplied the first without the second would cache an
35
+ * answer it can never drop — and there are two composition roots to keep in
36
+ * step. Omitted ⇒ every module reads as present and the version never moves,
37
+ * which is the behaviour of an unwired composition, not a fall-open: there is
38
+ * no presence state to resolve at all.
39
+ */
40
+ presence?: ModulePresenceProbe;
41
+ }
42
+ /**
43
+ * Module presence, as this service consumes it: both axes and the generation
44
+ * the two readings came from.
45
+ *
46
+ * Both composition roots build it off `effectiveState`, which is where all
47
+ * three are already one object; stating them together here is what stops a
48
+ * root from wiring one reading without the others.
49
+ *
50
+ * The two axes stay **separate methods** rather than collapsing into
51
+ * `effectiveState.isPresent`, and the reason is the tri-state the combiner
52
+ * itself keeps: a module the deployment does not ship and a module the operator
53
+ * switched off are one answer to the palette and two different sentences on
54
+ * `/platform/modules`, so a probe that could only say "absent" would be a
55
+ * narrower value than the object both roots already hold. The conjunction is
56
+ * applied here, at the row filter, and nowhere else.
57
+ */
58
+ export interface ModulePresenceProbe {
59
+ /**
60
+ * Is this module installed and enabled on this deployment?
61
+ *
62
+ * The composition roots answer it `false` for a module id the registry and
63
+ * the manifests both know nothing about, which is what keeps an orphan
64
+ * `module_actions` row — a module uninstalled without its rows being reaped —
65
+ * out of the palette. That used to fall out of the `module_registrations`
66
+ * join having no row to match.
67
+ */
68
+ isPlatformAvailable(moduleId: string): boolean;
69
+ /** Is this module activated by the operator? */
70
+ isActivated(moduleId: string): boolean;
71
+ /**
72
+ * Generation of the presence the two readings above answer from. Changes when
73
+ * a refresh installs presence that differs from the presence before it.
74
+ */
75
+ version(): number;
76
+ }
77
+ export interface ListVisibleResult {
78
+ actions: AdminActionView[];
79
+ registryVersion: number;
80
+ }
81
+ /**
82
+ * Namespace this module's palette snapshot is registered under.
83
+ *
84
+ * Deliberately **not** in `CACHE_NAMESPACES` (the `settings` module's operator
85
+ * "clear cache" surface): this layer owns no Redis keys, and the palette is
86
+ * rebuilt from PostgreSQL on the next read anyway. The registration exists for
87
+ * the module-state notification alone.
88
+ */
89
+ export declare const ADMIN_ACTIONS_CACHE_NAMESPACE = "admin_actions";
90
+ export declare class AdminActionsService {
91
+ private readonly em;
92
+ private readonly i18nService;
93
+ private readonly permissionService;
94
+ private readonly presence;
95
+ private readonly cache;
96
+ /** Presence generation every snapshot currently in {@link cache} was built under. */
97
+ private cachedPresenceVersion;
98
+ /** Counts service work so tests can assert cache hits / misses. */
99
+ stats: {
100
+ dbHits: number;
101
+ cacheHits: number;
102
+ invalidations: number;
103
+ };
104
+ private readonly unregisterCacheLayer;
105
+ constructor(deps: AdminActionsServiceDeps);
106
+ /**
107
+ * Withdraw this service's cache layer from the process registry. Production
108
+ * holds it until process exit; a test that builds a service per case releases
109
+ * it so the registry names a live cache rather than a discarded one.
110
+ */
111
+ dispose(): void;
112
+ /**
113
+ * Drop every cached snapshot. Called by the registered cache layer above and
114
+ * by the presence-version comparison in {@link listVisibleForOperator}.
115
+ */
116
+ invalidate(): void;
117
+ /**
118
+ * List the actions an operator may see in the command palette.
119
+ *
120
+ * Permissions are loaded via the admin_roles `PermissionService`. The
121
+ * action filter is the same as the existing `hasPermission()` check:
122
+ * `*` in the operator's role grants everything; otherwise the action's
123
+ * `requiredPermission` (if any) must be present in the role's array.
124
+ */
125
+ listVisibleForOperator(args: {
126
+ language: SupportedAdminLanguage;
127
+ adminUserId: string;
128
+ }): Promise<ListVisibleResult>;
129
+ }
130
+ /**
131
+ * Stable hash of an operator's permission set. Sorting first makes the
132
+ * fingerprint independent of insertion order; sha1 keeps the cache key
133
+ * compact while making collisions astronomically unlikely.
134
+ */
135
+ export declare function fingerprintPermissions(permissions: readonly string[]): string;
136
+ //# sourceMappingURL=admin-actions-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-actions-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/admin-actions-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EACV,eAAe,EACf,sBAAsB,EAEtB,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,4BAA4B,CAAC;AAuEpC,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,aAAa,CAAC;IACxB;;;;;;;OAOG;IACH,WAAW,EAAE,sBAAsB,CAAC;IACpC,iBAAiB,EAAE,kBAAkB,CAAC;IACtC,GAAG,CAAC,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC3D;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;OAQG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/C,gDAAgD;IAChD,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC;;;OAGG;IACH,OAAO,IAAI,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,kBAAkB,CAAC;AAE7D,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAsB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IACrD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IACvD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwC;IAC9D,qFAAqF;IACrF,OAAO,CAAC,qBAAqB,CAAS;IACtC,mEAAmE;IAC5D,KAAK;;;;MAAiD;IAC7D,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAa;gBAEtC,IAAI,EAAE,uBAAuB;IA8BzC;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAIf;;;OAGG;IACH,UAAU,IAAI,IAAI;IAKlB;;;;;;;OAOG;IACG,sBAAsB,CAAC,IAAI,EAAE;QACjC,QAAQ,EAAE,sBAAsB,CAAC;QACjC,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA+G/B;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAG7E"}
@@ -0,0 +1,168 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { inProcessCaches } from '@endora-commerce/platform/kernel';
3
+ /**
4
+ * Namespace this module's palette snapshot is registered under.
5
+ *
6
+ * Deliberately **not** in `CACHE_NAMESPACES` (the `settings` module's operator
7
+ * "clear cache" surface): this layer owns no Redis keys, and the palette is
8
+ * rebuilt from PostgreSQL on the next read anyway. The registration exists for
9
+ * the module-state notification alone.
10
+ */
11
+ export const ADMIN_ACTIONS_CACHE_NAMESPACE = 'admin_actions';
12
+ export class AdminActionsService {
13
+ em;
14
+ i18nService;
15
+ permissionService;
16
+ presence;
17
+ cache = new Map();
18
+ /** Presence generation every snapshot currently in {@link cache} was built under. */
19
+ cachedPresenceVersion;
20
+ /** Counts service work so tests can assert cache hits / misses. */
21
+ stats = { dbHits: 0, cacheHits: 0, invalidations: 0 };
22
+ unregisterCacheLayer;
23
+ constructor(deps) {
24
+ this.em = deps.em;
25
+ this.i18nService = deps.i18nService;
26
+ this.permissionService = deps.permissionService;
27
+ this.presence = deps.presence ?? {
28
+ isPlatformAvailable: () => true,
29
+ isActivated: () => true,
30
+ version: () => 0,
31
+ };
32
+ this.cachedPresenceVersion = this.presence.version();
33
+ // D-174 — this process's palette snapshot, announced to the platform so its
34
+ // module-state subscriber can drop it. Unconditional: the registry is
35
+ // process-local and a drop costs a `Map.clear()`, so there is nothing for a
36
+ // composition to get wrong by omission. A second composition in the same
37
+ // process (the harness builds one per test file) replaces the entry.
38
+ this.unregisterCacheLayer = inProcessCaches.register(ADMIN_ACTIONS_CACHE_NAMESPACE, {
39
+ invalidateAll: async () => {
40
+ this.invalidate();
41
+ // No shared layer: this cache is a `Map`, so no Redis key was deleted
42
+ // and the operator-facing count this return value feeds is zero.
43
+ return 0;
44
+ },
45
+ }, { invalidateOnModuleStateChange: true });
46
+ }
47
+ /**
48
+ * Withdraw this service's cache layer from the process registry. Production
49
+ * holds it until process exit; a test that builds a service per case releases
50
+ * it so the registry names a live cache rather than a discarded one.
51
+ */
52
+ dispose() {
53
+ this.unregisterCacheLayer();
54
+ }
55
+ /**
56
+ * Drop every cached snapshot. Called by the registered cache layer above and
57
+ * by the presence-version comparison in {@link listVisibleForOperator}.
58
+ */
59
+ invalidate() {
60
+ this.cache.clear();
61
+ this.stats.invalidations += 1;
62
+ }
63
+ /**
64
+ * List the actions an operator may see in the command palette.
65
+ *
66
+ * Permissions are loaded via the admin_roles `PermissionService`. The
67
+ * action filter is the same as the existing `hasPermission()` check:
68
+ * `*` in the operator's role grants everything; otherwise the action's
69
+ * `requiredPermission` (if any) must be present in the role's array.
70
+ */
71
+ async listVisibleForOperator(args) {
72
+ // Issue #225 — before anything is served from the snapshot, ask whether the
73
+ // presence it was built from is still the presence being answered. A read
74
+ // that landed while `registryCache.refreshFromDb` was in flight built its
75
+ // snapshot from the pre-refresh maps; the version moves when that refresh
76
+ // lands, and this is where the snapshot goes with it. The number is recorded
77
+ // *before* the rebuild below, so a refresh completing while that rebuild is
78
+ // running is caught by the next read rather than becoming permanent.
79
+ const presenceVersion = this.presence.version();
80
+ if (presenceVersion !== this.cachedPresenceVersion) {
81
+ this.cachedPresenceVersion = presenceVersion;
82
+ this.invalidate();
83
+ }
84
+ const permissions = await this.permissionService.listPermissions(args.adminUserId);
85
+ const fingerprint = fingerprintPermissions(permissions);
86
+ const cacheKey = `${args.language}::${fingerprint}`;
87
+ const cached = this.cache.get(cacheKey);
88
+ if (cached) {
89
+ this.stats.cacheHits += 1;
90
+ return cached;
91
+ }
92
+ this.stats.dbHits += 1;
93
+ const knex = this.em().getKnex();
94
+ // This module's own table and nothing else. Presence is decided below,
95
+ // against the probe, for both axes at once.
96
+ const rows = (await knex('module_actions as a')
97
+ .select('a.module_id', 'a.action_id', 'a.label_key', 'a.description_key', 'a.icon', 'a.target_route', 'a.required_permission', 'a.keywords', 'a.weight', 'a.version'));
98
+ const visible = [];
99
+ for (const row of rows) {
100
+ // Constitution XVII — effective presence is the conjunction of the two
101
+ // axes, and both are asked here so neither can be forgotten by one of
102
+ // them moving. A palette that advertises an absent module's action leads
103
+ // an operator straight to a 503, which is the same defect as an
104
+ // unfiltered sidebar; one that hides a present module's action is the
105
+ // same defect wearing the other sign.
106
+ if (!this.presence.isPlatformAvailable(row.module_id))
107
+ continue;
108
+ if (!this.presence.isActivated(row.module_id))
109
+ continue;
110
+ if (row.required_permission == null ||
111
+ permissions.includes('*') ||
112
+ permissions.includes(row.required_permission)) {
113
+ visible.push(row);
114
+ }
115
+ }
116
+ let registryVersion = 0;
117
+ const collator = new Intl.Collator(args.language, {
118
+ sensitivity: 'base',
119
+ numeric: true,
120
+ });
121
+ const resolved = [];
122
+ for (const row of visible) {
123
+ const v = Number(row.version);
124
+ if (v > registryVersion)
125
+ registryVersion = v;
126
+ const label = await this.i18nService.translate(row.module_id, row.label_key, args.language);
127
+ let description = null;
128
+ if (row.description_key) {
129
+ const resolvedDescription = await this.i18nService.translate(row.module_id, row.description_key, args.language);
130
+ // The resolver returns `${moduleId}.${key}` as a placeholder when
131
+ // the key is missing — surface that as `null` rather than a
132
+ // raw-key string in the description slot.
133
+ description =
134
+ resolvedDescription === `${row.module_id}.${row.description_key}`
135
+ ? null
136
+ : resolvedDescription;
137
+ }
138
+ resolved.push({
139
+ moduleId: row.module_id,
140
+ actionId: row.action_id,
141
+ label,
142
+ description,
143
+ icon: row.icon,
144
+ targetRoute: row.target_route,
145
+ keywords: Array.isArray(row.keywords) ? row.keywords : [],
146
+ weight: row.weight,
147
+ });
148
+ }
149
+ resolved.sort((a, b) => {
150
+ if (a.weight !== b.weight)
151
+ return a.weight - b.weight;
152
+ return collator.compare(a.label, b.label);
153
+ });
154
+ const result = { actions: resolved, registryVersion };
155
+ this.cache.set(cacheKey, result);
156
+ return result;
157
+ }
158
+ }
159
+ /**
160
+ * Stable hash of an operator's permission set. Sorting first makes the
161
+ * fingerprint independent of insertion order; sha1 keeps the cache key
162
+ * compact while making collisions astronomically unlikely.
163
+ */
164
+ export function fingerprintPermissions(permissions) {
165
+ const sorted = [...permissions].sort();
166
+ return createHash('sha1').update(sorted.join('\0')).digest('hex').slice(0, 16);
167
+ }
168
+ //# sourceMappingURL=admin-actions-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-actions-service.js","sourceRoot":"","sources":["../../../src/backend/services/admin-actions-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,196 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type ModuleLifecycleParticipant } from '@endora-commerce/contracts';
3
+ /**
4
+ * Admin Command Palette Actions Registry — feature 020.
5
+ *
6
+ * Owns the `module_actions` table, the install-time reconciler that
7
+ * ingests every module's manifest `actions` array, the in-process
8
+ * cache, and the read API consumed by the admin SPA's command palette.
9
+ * Itself declares no actions — this module is platform plumbing, not
10
+ * a place to surface UI features.
11
+ *
12
+ * Depends on `_lifecycle` because every module's action install / hard-
13
+ * uninstall is driven by the lifecycle orchestrator's hook surface, and
14
+ * on `_i18n` because action labels and descriptions are resolved
15
+ * through the existing translation-bundle resolver.
16
+ */
17
+ export declare const manifest: {
18
+ id: string;
19
+ name: string;
20
+ version: string;
21
+ dependencies: string[];
22
+ description?: string | undefined;
23
+ acknowledgedDependencies?: {
24
+ moduleId: string;
25
+ port: string;
26
+ reason: string;
27
+ }[] | undefined;
28
+ nonBindingDependencies?: {
29
+ moduleId: string;
30
+ name: string;
31
+ kind: "contributes-to" | "degrades-without" | "refuses-without";
32
+ reason: string;
33
+ whenAbsent?: string | undefined;
34
+ }[] | undefined;
35
+ activation?: {
36
+ settingCode: string;
37
+ default: boolean;
38
+ } | {
39
+ nonDeactivatable: true;
40
+ reason: string;
41
+ } | undefined;
42
+ settings?: {
43
+ moduleCode: string;
44
+ groups: {
45
+ code: string;
46
+ name: string;
47
+ salesChannelCodes?: string[] | undefined;
48
+ isSystemProtected?: boolean | undefined;
49
+ }[];
50
+ settings: {
51
+ code: string;
52
+ name: string;
53
+ valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
54
+ defaultValue: unknown;
55
+ description?: string | undefined;
56
+ groupCode?: string | undefined;
57
+ previousDefaultValues?: unknown[] | undefined;
58
+ salesChannelCodes?: string[] | undefined;
59
+ enumOptions?: string[] | undefined;
60
+ configurationType?: string | undefined;
61
+ hidden?: boolean | undefined;
62
+ }[];
63
+ } | undefined;
64
+ i18n?: {
65
+ bundlesDir: string;
66
+ } | undefined;
67
+ docs?: false | {
68
+ dir: string;
69
+ } | undefined;
70
+ demo?: false | {
71
+ summary: string;
72
+ seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
73
+ reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
74
+ after?: readonly string[] | undefined;
75
+ package?: string | undefined;
76
+ } | undefined;
77
+ actions?: {
78
+ id: string;
79
+ labelKey: string;
80
+ 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";
81
+ targetRoute: string;
82
+ keywords: string[];
83
+ weight: number;
84
+ descriptionKey?: string | undefined;
85
+ requiredPermission?: string | undefined;
86
+ }[] | undefined;
87
+ permissions?: {
88
+ code: string;
89
+ label: string;
90
+ module?: string | undefined;
91
+ description?: string | undefined;
92
+ requires?: string[] | undefined;
93
+ }[] | undefined;
94
+ transactionalEmails?: {
95
+ code: string;
96
+ name: string;
97
+ variables: {
98
+ key: string;
99
+ label: string;
100
+ sampleValue?: string | undefined;
101
+ description?: string | undefined;
102
+ }[];
103
+ description?: string | undefined;
104
+ group?: string | undefined;
105
+ }[] | undefined;
106
+ capabilities?: string[] | undefined;
107
+ exclusiveCapabilities?: {
108
+ key: string;
109
+ errorCode: string;
110
+ }[] | undefined;
111
+ errorCodes?: {
112
+ code: string;
113
+ tokens?: string[] | undefined;
114
+ }[] | undefined;
115
+ blocks?: {
116
+ name: string;
117
+ labelKey: string;
118
+ category: string;
119
+ contexts: ("invoice" | "email" | "cms" | "newsletter")[];
120
+ fields: Record<string, {
121
+ type: "number" | "object" | "uuid" | "array" | "text" | "textarea" | "select" | "radio" | "external" | "richtext";
122
+ label?: string | undefined;
123
+ required?: boolean | undefined;
124
+ options?: {
125
+ label: string;
126
+ value: string | number;
127
+ }[] | undefined;
128
+ refKind?: string | undefined;
129
+ }>;
130
+ descriptionKey?: string | undefined;
131
+ defaultProps?: Record<string, unknown> | undefined;
132
+ responsiveFields?: string[] | undefined;
133
+ previewIcon?: string | undefined;
134
+ weight?: number | undefined;
135
+ }[] | undefined;
136
+ blockCategories?: {
137
+ key: string;
138
+ titleKey: string;
139
+ contexts: ("invoice" | "email" | "cms" | "newsletter")[];
140
+ weight?: number | undefined;
141
+ visible?: boolean | undefined;
142
+ }[] | undefined;
143
+ env?: {
144
+ name: string;
145
+ describes: {
146
+ en: string;
147
+ pl: string;
148
+ };
149
+ requirement: {
150
+ kind: "required";
151
+ } | {
152
+ kind: "requiredWhen";
153
+ input: string;
154
+ equals: string;
155
+ } | {
156
+ kind: "optional";
157
+ without: {
158
+ en: string;
159
+ pl: string;
160
+ };
161
+ };
162
+ secret: boolean;
163
+ generable: boolean;
164
+ owner: {
165
+ kind: "platform";
166
+ } | {
167
+ kind: "application";
168
+ application: "admin" | "backend" | "storefront";
169
+ } | {
170
+ kind: "module";
171
+ moduleId: string;
172
+ };
173
+ consumers: ("admin" | "backend" | "storefront")[];
174
+ addressOf: "admin" | "backend" | "storefront" | null;
175
+ }[] | undefined;
176
+ };
177
+ /**
178
+ * `module_actions` follows the manifest set — feature 080, T036a / D-159.
179
+ *
180
+ * `_i18n/manifest.ts` states the shape's reasoning in full; this module is the
181
+ * second instance of it, projecting every other module's `manifest.actions`
182
+ * array instead of its `i18n` block.
183
+ *
184
+ * **It is not gated on this module's own activation, and that is a ruling**
185
+ * (D-159 §9, the owner, 2026-08-22). `composition.ts` used to forward the
186
+ * reconciler as a lazily-resolved port, so an operator who had switched the
187
+ * command palette off could not install an *unrelated* module — the install
188
+ * aborted on a `MODULE_DISABLED` from a discovery surface that has nothing to
189
+ * do with it. That was the better of the only two options then on the table,
190
+ * the other being a backend that would not start; the third, which this takes,
191
+ * is that a projection of manifest data is written whether or not anything is
192
+ * serving it. The rows are inert while the palette is off and the palette
193
+ * answers from them, unchanged, the moment it is switched back on.
194
+ */
195
+ export declare const lifecycleParticipant: ModuleLifecycleParticipant<EntityManager>;
196
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAwB,KAAK,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAEnG;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCnB,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,EAAE,0BAA0B,CAAC,aAAa,CAoB1E,CAAC"}