@hartl-services/medusa-base 0.1.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 (34) hide show
  1. package/.medusa/server/src/admin/index.js +267 -0
  2. package/.medusa/server/src/admin/index.mjs +266 -0
  3. package/.medusa/server/src/api/admin/plugin-features/route.js +20 -0
  4. package/.medusa/server/src/api/middlewares.js +25 -0
  5. package/.medusa/server/src/api/plugin-features-http.js +20 -0
  6. package/.medusa/server/src/api/store/plugin-features/[plugin_key]/route.js +13 -0
  7. package/.medusa/server/src/api/store/plugin-features/route.js +13 -0
  8. package/.medusa/server/src/api/wire-response.js +10 -0
  9. package/.medusa/server/src/modules/plugin-features/index.js +14 -0
  10. package/.medusa/server/src/modules/plugin-features/migrations/Migration20260928155256.js +16 -0
  11. package/.medusa/server/src/modules/plugin-features/models/plugin-feature-state.js +29 -0
  12. package/.medusa/server/src/modules/plugin-features/service.js +141 -0
  13. package/.medusa/server/src/public/constants.js +6 -0
  14. package/.medusa/server/src/public/descriptor.js +54 -0
  15. package/.medusa/server/src/public/effective-features.js +74 -0
  16. package/.medusa/server/src/public/enforcement.js +51 -0
  17. package/.medusa/server/src/public/index.js +13 -0
  18. package/.medusa/server/src/public/types.js +3 -0
  19. package/.medusa/server/src/utils/discover-plugin-features.js +51 -0
  20. package/LICENSE +21 -0
  21. package/README.md +166 -0
  22. package/dist/public/constants.d.ts +2 -0
  23. package/dist/public/constants.js +5 -0
  24. package/dist/public/descriptor.d.ts +11 -0
  25. package/dist/public/descriptor.js +53 -0
  26. package/dist/public/effective-features.d.ts +28 -0
  27. package/dist/public/effective-features.js +73 -0
  28. package/dist/public/enforcement.d.ts +13 -0
  29. package/dist/public/enforcement.js +50 -0
  30. package/dist/public/index.d.ts +5 -0
  31. package/dist/public/index.js +12 -0
  32. package/dist/public/types.d.ts +29 -0
  33. package/dist/public/types.js +2 -0
  34. package/package.json +82 -0
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # Medusa Base
2
+
3
+ Medusa v2.19 base plugin for Hartl Services shops. Every shop installs it. It
4
+ provides per-plugin feature toggles: a generic Admin page under
5
+ **Settings → Hartl Services Plugins**, a Store endpoint for storefronts and
6
+ helpers that plugins use to enforce their toggles.
7
+
8
+ ## What it does
9
+
10
+ - Discovers every registered module whose service declares `pluginFeatures`
11
+ (see below). No plugin has to register itself anywhere else.
12
+ - Stores one row per plugin in `plugin_feature_state` (module
13
+ `plugin_features`): the plugin switch, the configured feature values and a
14
+ `version` for optimistic concurrency.
15
+ - Serves the effective values to storefronts and the configured plus effective
16
+ values to the Admin.
17
+ - Exports `definePluginFeatures`, `requirePluginFeature` and
18
+ `isPluginFeatureEnabled` from `@hartl-services/medusa-base` for plugins.
19
+
20
+ ## Installation in a shop
21
+
22
+ ```bash
23
+ pnpm add @hartl-services/medusa-base @hartl-services/medusa-food-supplements-contracts
24
+ ```
25
+
26
+ ```ts
27
+ // medusa-config.ts
28
+ plugins: [
29
+ // First in the list: other plugins read their toggles through it.
30
+ {
31
+ resolve: "@hartl-services/medusa-base",
32
+ options: {},
33
+ },
34
+ // … all other plugins
35
+ ]
36
+ ```
37
+
38
+ The plugin has no options. Run `medusa db:migrate` after installing it.
39
+
40
+ Peer dependencies are the host's Medusa 2.19 packages (`@medusajs/framework`,
41
+ `@medusajs/medusa`, `@medusajs/admin-sdk`, `@medusajs/js-sdk`, `@medusajs/ui`)
42
+ plus React, React Router and TanStack Query in the versions the Medusa 2.19
43
+ dashboard uses.
44
+
45
+ ## HTTP endpoints
46
+
47
+ | Method | Path | Auth | Response |
48
+ |---|---|---|---|
49
+ | `GET` | `/store/plugin-features` | publishable key | `StorePluginFeaturesListResponse` |
50
+ | `GET` | `/store/plugin-features/:plugin_key` | publishable key | `StorePluginFeaturesResponse`, 404 for an unknown plugin |
51
+ | `GET` | `/admin/plugin-features` | Admin user | `AdminPluginFeaturesListResponse` |
52
+ | `POST` | `/admin/plugin-features` | Admin user | `AdminPluginFeaturesResponse` |
53
+
54
+ Every authenticated Admin user may change the toggles (no RBAC by design).
55
+ `POST` takes `{ plugin_key, expected_version, enabled, features }`; `features`
56
+ holds configured values for declared keys, missing keys keep their default and
57
+ an unknown key is 400. An unknown `plugin_key` is 404, a stale
58
+ `expected_version` is 409.
59
+
60
+ Store values are effective values only; a storefront uses them to hide
61
+ features. DTOs, schemas and paths come from the Contracts package:
62
+
63
+ ```ts
64
+ import type { StorePluginFeaturesListResponse } from "@hartl-services/medusa-food-supplements-contracts/types/plugin-features-store"
65
+ import { pluginFeaturesStorePaths } from "@hartl-services/medusa-food-supplements-contracts/paths/plugin-features"
66
+
67
+ const { plugins } = await sdk.client.fetch<StorePluginFeaturesListResponse>(
68
+ pluginFeaturesStorePaths.get.list,
69
+ { method: "GET" }
70
+ )
71
+ ```
72
+
73
+ ## Declaring features in a plugin
74
+
75
+ A plugin depends on `@hartl-services/medusa-base` (peer dependency), declares
76
+ its features once and exposes the declaration as `pluginFeatures` on its
77
+ module service:
78
+
79
+ ```ts
80
+ // src/plugin-features.ts
81
+ import { definePluginFeatures } from "@hartl-services/medusa-base"
82
+
83
+ export const demoPluginFeatures = definePluginFeatures({
84
+ plugin_key: "demo",
85
+ label: "Demo-Plugin",
86
+ // default_enabled: true, // plugin switch default
87
+ features: [
88
+ { key: "coupons", label: "Gutscheine", default_enabled: true },
89
+ {
90
+ key: "coupons_self_service",
91
+ label: "Gutscheine selbst verwalten",
92
+ default_enabled: false,
93
+ parent_key: "coupons",
94
+ },
95
+ ],
96
+ })
97
+
98
+ // src/modules/demo/service.ts
99
+ class DemoModuleService extends MedusaService({ /* models */ }) {
100
+ readonly pluginFeatures = demoPluginFeatures
101
+ }
102
+ ```
103
+
104
+ Keys match `^[a-z][a-z0-9_]{0,62}$`, feature keys are unique, a `parent_key`
105
+ must exist and only one nesting level is allowed. `definePluginFeatures`
106
+ throws at import time otherwise.
107
+
108
+ ### Gating routes
109
+
110
+ Put gate entries at the start of the plugin's `routes` array. An entry without
111
+ `methods` is a prefix match and runs before every method-specific middleware:
112
+
113
+ ```ts
114
+ // src/api/middlewares.ts
115
+ import { requirePluginFeature } from "@hartl-services/medusa-base"
116
+
117
+ export default defineMiddlewares({
118
+ routes: [
119
+ { matcher: "/store/demo", middlewares: [requirePluginFeature(demoPluginFeatures)] },
120
+ {
121
+ matcher: "/store/demo/coupons",
122
+ middlewares: [requirePluginFeature(demoPluginFeatures, "coupons")],
123
+ },
124
+ // … existing entries unchanged
125
+ ],
126
+ })
127
+ ```
128
+
129
+ A disabled plugin or feature answers 404. With several keys, all must be
130
+ enabled. The toggles are read once per request.
131
+
132
+ ### Gating subscribers and jobs
133
+
134
+ Return early:
135
+
136
+ ```ts
137
+ import { isPluginFeatureEnabled } from "@hartl-services/medusa-base"
138
+
139
+ if (!(await isPluginFeatureEnabled(container, demoPluginFeatures, "coupons"))) {
140
+ return
141
+ }
142
+ ```
143
+
144
+ A read failure throws, so the subscriber or job is retried. A missing base
145
+ plugin fails with `UNEXPECTED_STATE` ("@hartl-services/medusa-base ist nicht in
146
+ medusa-config registriert.").
147
+
148
+ ### Never on the cart path
149
+
150
+ Code on the cart and checkout hot path (cart middlewares, cart workflow hooks,
151
+ `orderCreated` handling, cart subscribers) must never read toggles. It stays
152
+ fail-safe and independent of the database state of this plugin. Toggles
153
+ control visibility and reachability of features, not the integrity of
154
+ existing data.
155
+
156
+ ## Semantics
157
+
158
+ - No stored row: every value is its declared default, `version` is 0.
159
+ - `effective(feature) = plugin enabled && configured(feature) && effective(parent)`.
160
+ Turning a parent off turns its children off; their configured values stay.
161
+ Turning the plugin off turns every feature off.
162
+ - Stored values for keys a plugin no longer declares are ignored; a feature
163
+ added later uses its default until it is saved.
164
+ - Updates are optimistic: `expected_version` 0 creates the row, any other
165
+ value updates exactly that version. A concurrent change answers 409; the
166
+ Admin page reloads and shows a notice.
@@ -0,0 +1,2 @@
1
+ /** Container key of the base plugin's module (`Module("plugin_features", …)`). */
2
+ export declare const PLUGIN_FEATURES_MODULE: "plugin_features";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PLUGIN_FEATURES_MODULE = void 0;
4
+ /** Container key of the base plugin's module (`Module("plugin_features", …)`). */
5
+ exports.PLUGIN_FEATURES_MODULE = "plugin_features";
@@ -0,0 +1,11 @@
1
+ import type { PluginFeatureDescriptor } from "./types";
2
+ /**
3
+ * Checks key format, unique feature keys, existing parents and the single
4
+ * allowed nesting level. Throws `INVALID_DATA`.
5
+ */
6
+ export declare function validatePluginFeatureDescriptor(descriptor: PluginFeatureDescriptor): void;
7
+ /**
8
+ * Declares a plugin's feature switches. Validates at import time so a broken
9
+ * declaration fails the boot instead of a request.
10
+ */
11
+ export declare function definePluginFeatures<const K extends string>(descriptor: PluginFeatureDescriptor<K>): PluginFeatureDescriptor<K>;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validatePluginFeatureDescriptor = validatePluginFeatureDescriptor;
4
+ exports.definePluginFeatures = definePluginFeatures;
5
+ const utils_1 = require("@medusajs/framework/utils");
6
+ const KEY_PATTERN = /^[a-z][a-z0-9_]{0,62}$/;
7
+ const invalid = (pluginKey, message) => new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Ungültige Funktionsschalter für Plugin "${pluginKey}": ${message}`);
8
+ /**
9
+ * Checks key format, unique feature keys, existing parents and the single
10
+ * allowed nesting level. Throws `INVALID_DATA`.
11
+ */
12
+ function validatePluginFeatureDescriptor(descriptor) {
13
+ const pluginKey = String(descriptor.plugin_key);
14
+ if (!KEY_PATTERN.test(pluginKey)) {
15
+ throw invalid(pluginKey, "plugin_key hat ein ungültiges Format.");
16
+ }
17
+ if (typeof descriptor.label !== "string" || !descriptor.label.trim()) {
18
+ throw invalid(pluginKey, "label fehlt.");
19
+ }
20
+ const byKey = new Map();
21
+ for (const feature of descriptor.features) {
22
+ if (!KEY_PATTERN.test(feature.key)) {
23
+ throw invalid(pluginKey, `Feature-Key "${feature.key}" hat ein ungültiges Format.`);
24
+ }
25
+ if (byKey.has(feature.key)) {
26
+ throw invalid(pluginKey, `Feature-Key "${feature.key}" ist doppelt.`);
27
+ }
28
+ if (typeof feature.label !== "string" || !feature.label.trim()) {
29
+ throw invalid(pluginKey, `Feature "${feature.key}" hat kein label.`);
30
+ }
31
+ byKey.set(feature.key, feature);
32
+ }
33
+ for (const feature of descriptor.features) {
34
+ if (feature.parent_key === undefined) {
35
+ continue;
36
+ }
37
+ const parent = byKey.get(feature.parent_key);
38
+ if (!parent || parent.key === feature.key) {
39
+ throw invalid(pluginKey, `Parent "${feature.parent_key}" von Feature "${feature.key}" existiert nicht.`);
40
+ }
41
+ if (parent.parent_key !== undefined) {
42
+ throw invalid(pluginKey, `Feature "${feature.key}" ist zu tief verschachtelt; erlaubt ist eine Ebene.`);
43
+ }
44
+ }
45
+ }
46
+ /**
47
+ * Declares a plugin's feature switches. Validates at import time so a broken
48
+ * declaration fails the boot instead of a request.
49
+ */
50
+ function definePluginFeatures(descriptor) {
51
+ validatePluginFeatureDescriptor(descriptor);
52
+ return descriptor;
53
+ }
@@ -0,0 +1,28 @@
1
+ import type { EffectivePluginFeatures, PluginFeatureDefinition, PluginFeatureDescriptor, StoredPluginFeatures } from "./types";
2
+ export type PluginFeatureState = {
3
+ definition: PluginFeatureDefinition;
4
+ /** Configured value: stored or default. */
5
+ enabled: boolean;
6
+ /** After the plugin switch and the parent rule. */
7
+ effective_enabled: boolean;
8
+ };
9
+ export type PluginFeatureStates = {
10
+ /** Configured plugin switch: stored or default. */
11
+ enabled: boolean;
12
+ features: PluginFeatureState[];
13
+ };
14
+ /**
15
+ * Configured and effective values of every declared feature.
16
+ * `effective(f) = pluginEnabled && configured(f) && effective(parent(f))`.
17
+ * Stored keys the descriptor no longer declares are ignored; declared keys
18
+ * without a stored value fall back to their default.
19
+ */
20
+ export declare function resolvePluginFeatureStates(descriptor: PluginFeatureDescriptor, stored: StoredPluginFeatures | null): PluginFeatureStates;
21
+ /** Pure rule shared by service and tests. `stored === null` means no row. */
22
+ export declare function resolveEffectivePluginFeatures(descriptor: PluginFeatureDescriptor, stored: StoredPluginFeatures | null): EffectivePluginFeatures;
23
+ /**
24
+ * Rejects configured values for keys the descriptor does not declare and
25
+ * returns the values to persist (declared keys only; missing keys keep their
26
+ * default).
27
+ */
28
+ export declare function validateFeatureUpdate(descriptor: PluginFeatureDescriptor, features: Record<string, boolean>): Record<string, boolean>;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolvePluginFeatureStates = resolvePluginFeatureStates;
4
+ exports.resolveEffectivePluginFeatures = resolveEffectivePluginFeatures;
5
+ exports.validateFeatureUpdate = validateFeatureUpdate;
6
+ const utils_1 = require("@medusajs/framework/utils");
7
+ /**
8
+ * Configured and effective values of every declared feature.
9
+ * `effective(f) = pluginEnabled && configured(f) && effective(parent(f))`.
10
+ * Stored keys the descriptor no longer declares are ignored; declared keys
11
+ * without a stored value fall back to their default.
12
+ */
13
+ function resolvePluginFeatureStates(descriptor, stored) {
14
+ const pluginEnabled = stored
15
+ ? stored.enabled
16
+ : descriptor.default_enabled ?? true;
17
+ const configured = new Map();
18
+ for (const feature of descriptor.features) {
19
+ const value = stored?.features[feature.key];
20
+ configured.set(feature.key, typeof value === "boolean" ? value : feature.default_enabled);
21
+ }
22
+ const effective = (feature) => {
23
+ if (!pluginEnabled || configured.get(feature.key) !== true) {
24
+ return false;
25
+ }
26
+ if (feature.parent_key === undefined) {
27
+ return true;
28
+ }
29
+ // Validated descriptors allow one nesting level, so the parent is a root.
30
+ return configured.get(feature.parent_key) === true;
31
+ };
32
+ return {
33
+ enabled: pluginEnabled,
34
+ features: descriptor.features.map((definition) => ({
35
+ definition,
36
+ enabled: configured.get(definition.key) === true,
37
+ effective_enabled: effective(definition),
38
+ })),
39
+ };
40
+ }
41
+ /** Pure rule shared by service and tests. `stored === null` means no row. */
42
+ function resolveEffectivePluginFeatures(descriptor, stored) {
43
+ const states = resolvePluginFeatureStates(descriptor, stored);
44
+ const features = {};
45
+ for (const state of states.features) {
46
+ features[state.definition.key] = state.effective_enabled;
47
+ }
48
+ return {
49
+ plugin_key: descriptor.plugin_key,
50
+ enabled: states.enabled,
51
+ features,
52
+ };
53
+ }
54
+ /**
55
+ * Rejects configured values for keys the descriptor does not declare and
56
+ * returns the values to persist (declared keys only; missing keys keep their
57
+ * default).
58
+ */
59
+ function validateFeatureUpdate(descriptor, features) {
60
+ const declared = new Set(descriptor.features.map((feature) => feature.key));
61
+ const unknown = Object.keys(features).filter((key) => !declared.has(key));
62
+ if (unknown.length) {
63
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Unbekannte Funktionsschalter für Plugin "${descriptor.plugin_key}": ${unknown.join(", ")}.`);
64
+ }
65
+ const result = {};
66
+ for (const feature of descriptor.features) {
67
+ const value = features[feature.key];
68
+ if (typeof value === "boolean") {
69
+ result[feature.key] = value;
70
+ }
71
+ }
72
+ return result;
73
+ }
@@ -0,0 +1,13 @@
1
+ import type { MedusaRequestHandler } from "@medusajs/framework/http";
2
+ import type { MedusaContainer } from "@medusajs/framework/types";
3
+ import type { PluginFeatureDescriptor } from "./types";
4
+ /**
5
+ * For subscribers, jobs and workflow steps. No keys = plugin switch only;
6
+ * several keys = all must be enabled. Read failures propagate.
7
+ */
8
+ export declare function isPluginFeatureEnabled<K extends string>(container: MedusaContainer, descriptor: PluginFeatureDescriptor<K>, ...keys: K[]): Promise<boolean>;
9
+ /**
10
+ * Middleware for `defineMiddlewares`. A disabled plugin or feature answers
11
+ * with `NOT_FOUND` (404); all given keys must be enabled.
12
+ */
13
+ export declare function requirePluginFeature<K extends string>(descriptor: PluginFeatureDescriptor<K>, ...keys: K[]): MedusaRequestHandler;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPluginFeatureEnabled = isPluginFeatureEnabled;
4
+ exports.requirePluginFeature = requirePluginFeature;
5
+ const utils_1 = require("@medusajs/framework/utils");
6
+ const constants_1 = require("./constants");
7
+ const resolveReader = (container) => {
8
+ if (!container.hasRegistration(constants_1.PLUGIN_FEATURES_MODULE)) {
9
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, "@hartl-services/medusa-base ist nicht in medusa-config registriert.");
10
+ }
11
+ return container.resolve(constants_1.PLUGIN_FEATURES_MODULE);
12
+ };
13
+ const allEnabled = (effective, keys) => effective.enabled && keys.every((key) => effective.features[key] === true);
14
+ /**
15
+ * For subscribers, jobs and workflow steps. No keys = plugin switch only;
16
+ * several keys = all must be enabled. Read failures propagate.
17
+ */
18
+ async function isPluginFeatureEnabled(container, descriptor, ...keys) {
19
+ const effective = await resolveReader(container).retrieveEffectiveFeatures(descriptor);
20
+ return allEnabled(effective, keys);
21
+ }
22
+ // Keyed by the request object, so one request reads each plugin's toggles once
23
+ // even when several gate middlewares match it.
24
+ const requestMemo = new WeakMap();
25
+ const effectiveForRequest = (req, descriptor) => {
26
+ let perRequest = requestMemo.get(req);
27
+ if (!perRequest) {
28
+ perRequest = new Map();
29
+ requestMemo.set(req, perRequest);
30
+ }
31
+ let pending = perRequest.get(descriptor.plugin_key);
32
+ if (!pending) {
33
+ pending = resolveReader(req.scope).retrieveEffectiveFeatures(descriptor);
34
+ perRequest.set(descriptor.plugin_key, pending);
35
+ }
36
+ return pending;
37
+ };
38
+ /**
39
+ * Middleware for `defineMiddlewares`. A disabled plugin or feature answers
40
+ * with `NOT_FOUND` (404); all given keys must be enabled.
41
+ */
42
+ function requirePluginFeature(descriptor, ...keys) {
43
+ return async (req, _res, next) => {
44
+ const effective = await effectiveForRequest(req, descriptor);
45
+ if (!allEnabled(effective, keys)) {
46
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, "Diese Funktion ist in diesem Shop nicht aktiviert.");
47
+ }
48
+ next();
49
+ };
50
+ }
@@ -0,0 +1,5 @@
1
+ export { PLUGIN_FEATURES_MODULE } from "./constants";
2
+ export { definePluginFeatures } from "./descriptor";
3
+ export { resolveEffectivePluginFeatures } from "./effective-features";
4
+ export { isPluginFeatureEnabled, requirePluginFeature } from "./enforcement";
5
+ export type { EffectivePluginFeatures, PluginFeatureDefinition, PluginFeatureDescriptor, PluginFeatureProvider, } from "./types";
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requirePluginFeature = exports.isPluginFeatureEnabled = exports.resolveEffectivePluginFeatures = exports.definePluginFeatures = exports.PLUGIN_FEATURES_MODULE = void 0;
4
+ var constants_1 = require("./constants");
5
+ Object.defineProperty(exports, "PLUGIN_FEATURES_MODULE", { enumerable: true, get: function () { return constants_1.PLUGIN_FEATURES_MODULE; } });
6
+ var descriptor_1 = require("./descriptor");
7
+ Object.defineProperty(exports, "definePluginFeatures", { enumerable: true, get: function () { return descriptor_1.definePluginFeatures; } });
8
+ var effective_features_1 = require("./effective-features");
9
+ Object.defineProperty(exports, "resolveEffectivePluginFeatures", { enumerable: true, get: function () { return effective_features_1.resolveEffectivePluginFeatures; } });
10
+ var enforcement_1 = require("./enforcement");
11
+ Object.defineProperty(exports, "isPluginFeatureEnabled", { enumerable: true, get: function () { return enforcement_1.isPluginFeatureEnabled; } });
12
+ Object.defineProperty(exports, "requirePluginFeature", { enumerable: true, get: function () { return enforcement_1.requirePluginFeature; } });
@@ -0,0 +1,29 @@
1
+ export type PluginFeatureDefinition<K extends string = string> = {
2
+ readonly key: K;
3
+ readonly label: string;
4
+ readonly description?: string;
5
+ readonly default_enabled: boolean;
6
+ readonly parent_key?: K;
7
+ };
8
+ export type PluginFeatureDescriptor<K extends string = string> = {
9
+ readonly plugin_key: string;
10
+ readonly label: string;
11
+ readonly description?: string;
12
+ /** Plugin switch default, `true` when omitted. */
13
+ readonly default_enabled?: boolean;
14
+ readonly features: readonly PluginFeatureDefinition<K>[];
15
+ };
16
+ /** Implemented by a plugin's module service so the base can discover it. */
17
+ export interface PluginFeatureProvider {
18
+ readonly pluginFeatures: PluginFeatureDescriptor;
19
+ }
20
+ export type EffectivePluginFeatures = {
21
+ plugin_key: string;
22
+ enabled: boolean;
23
+ features: Record<string, boolean>;
24
+ };
25
+ /** Configured values as persisted by the base module; `null` = no row. */
26
+ export type StoredPluginFeatures = {
27
+ enabled: boolean;
28
+ features: Record<string, boolean>;
29
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@hartl-services/medusa-base",
3
+ "version": "0.1.0",
4
+ "description": "Base plugin for Hartl Services Medusa shops: per-plugin feature toggles with a generic Admin settings page, a Store endpoint for storefronts, and enforcement helpers for plugin routes, subscribers and jobs.",
5
+ "license": "MIT",
6
+ "files": [
7
+ ".medusa/server",
8
+ "dist",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://gitlab.com/hartl-services-gmbh/services/food-supplements-e-commerce.git",
15
+ "directory": "plugins/base"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public",
19
+ "registry": "https://registry.npmjs.org/"
20
+ },
21
+ "sideEffects": [
22
+ "./.medusa/**"
23
+ ],
24
+ "exports": {
25
+ "./package.json": "./package.json",
26
+ ".": {
27
+ "types": "./dist/public/index.d.ts",
28
+ "import": "./dist/public/index.js",
29
+ "require": "./dist/public/index.js"
30
+ },
31
+ "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
32
+ "./.medusa/server/src/*": "./.medusa/server/src/*",
33
+ "./admin": {
34
+ "import": "./.medusa/server/src/admin/index.mjs",
35
+ "require": "./.medusa/server/src/admin/index.js"
36
+ }
37
+ },
38
+ "peerDependencies": {
39
+ "@medusajs/admin-sdk": "^2.19.0",
40
+ "@medusajs/framework": "^2.19.0",
41
+ "@medusajs/js-sdk": "^2.19.0",
42
+ "@medusajs/medusa": "^2.19.0",
43
+ "@medusajs/ui": "^4.2.1",
44
+ "@tanstack/react-query": "^5.64.2",
45
+ "react": "^18.3.1",
46
+ "react-router-dom": "^7.18.2"
47
+ },
48
+ "dependencies": {
49
+ "@hartl-services/medusa-food-supplements-contracts": "^0.10.0"
50
+ },
51
+ "devDependencies": {
52
+ "@medusajs/admin-sdk": "2.19.0",
53
+ "@medusajs/cli": "2.19.0",
54
+ "@medusajs/framework": "2.19.0",
55
+ "@medusajs/js-sdk": "2.19.0",
56
+ "@medusajs/medusa": "2.19.0",
57
+ "@medusajs/ui": "4.2.1",
58
+ "@swc/core": "^1.7.28",
59
+ "@swc/jest": "^0.2.39",
60
+ "@tanstack/react-query": "5.64.2",
61
+ "@types/jest": "^29.5.14",
62
+ "@types/node": "^20.12.11",
63
+ "@types/react": "^18.3.2",
64
+ "@types/react-dom": "^18.2.25",
65
+ "jest": "^29.7.0",
66
+ "react": "^18.3.1",
67
+ "react-router-dom": "7.18.2",
68
+ "typescript": "^5.6.2",
69
+ "vite": "7.3.6",
70
+ "yalc": "^1.0.0-pre.53"
71
+ },
72
+ "engines": {
73
+ "node": ">=20"
74
+ },
75
+ "scripts": {
76
+ "build": "medusa plugin:build && tsc -p tsconfig.public.json",
77
+ "dev": "medusa plugin:develop",
78
+ "typecheck": "tsc --noEmit",
79
+ "pretest:unit": "pnpm --filter @hartl-services/medusa-food-supplements-contracts build",
80
+ "test:unit": "jest"
81
+ }
82
+ }