@happyvertical/smrt-features 0.37.2 → 0.37.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +515 -681
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/smrt-knowledge.json +7 -7
- package/dist/types.js +10 -10
- package/dist/types.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,725 +1,559 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { FeatureOverrideEffect, GLOBAL_FEATURE_SCOPE_ID } from "./types.js";
|
|
2
|
+
import { ObjectRegistry, SmrtCollection, SmrtObject, createQualifiedName, isQualifiedName, parseQualifiedName, smrt } from "@happyvertical/smrt-core";
|
|
3
3
|
import { importWorkspaceModule } from "@happyvertical/smrt-core/utils/import-workspace-module";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
//#region src/__smrt-register__.ts
|
|
5
|
+
ObjectRegistry.registerPackageManifest(new URL("./manifest.json", "" + import.meta.url));
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/utils.ts
|
|
7
8
|
function createFeatureKey(qualifiedClassName, localId) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
if (!localId) {
|
|
14
|
-
throw new Error("Feature localId is required.");
|
|
15
|
-
}
|
|
16
|
-
if (localId.includes("#")) {
|
|
17
|
-
throw new Error(
|
|
18
|
-
`Feature localId "${localId}" cannot contain "#". This character is reserved for canonical feature keys.`
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
return `${qualifiedClassName}#${localId}`;
|
|
9
|
+
if (!isQualifiedName(qualifiedClassName)) throw new Error(`Feature keys require a qualified class name. Received "${qualifiedClassName}".`);
|
|
10
|
+
if (!localId) throw new Error("Feature localId is required.");
|
|
11
|
+
if (localId.includes("#")) throw new Error(`Feature localId "${localId}" cannot contain "#". This character is reserved for canonical feature keys.`);
|
|
12
|
+
return `${qualifiedClassName}#${localId}`;
|
|
22
13
|
}
|
|
23
14
|
function parseFeatureKey(featureKey) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
throw new Error(
|
|
34
|
-
`Invalid feature key "${featureKey}". "${qualifiedClassName}" is not a qualified class name.`
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
return { qualifiedClassName, localId };
|
|
15
|
+
const separatorIndex = featureKey.lastIndexOf("#");
|
|
16
|
+
if (separatorIndex <= 0 || separatorIndex === featureKey.length - 1) throw new Error(`Invalid feature key "${featureKey}". Expected format "<qualifiedClassName>#<localId>".`);
|
|
17
|
+
const qualifiedClassName = featureKey.slice(0, separatorIndex);
|
|
18
|
+
const localId = featureKey.slice(separatorIndex + 1);
|
|
19
|
+
if (!isQualifiedName(qualifiedClassName)) throw new Error(`Invalid feature key "${featureKey}". "${qualifiedClassName}" is not a qualified class name.`);
|
|
20
|
+
return {
|
|
21
|
+
qualifiedClassName,
|
|
22
|
+
localId
|
|
23
|
+
};
|
|
38
24
|
}
|
|
39
25
|
function serializeFeatureMetadata(metadata) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (!isFeatureMetadataRecord(metadata)) {
|
|
48
|
-
throw new Error(
|
|
49
|
-
"Feature metadata must be a plain object or a JSON string representing a plain object."
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
return JSON.stringify(metadata);
|
|
26
|
+
if (!metadata) return "";
|
|
27
|
+
if (typeof metadata === "string") {
|
|
28
|
+
const parsed = parseFeatureMetadataString(metadata);
|
|
29
|
+
return JSON.stringify(parsed);
|
|
30
|
+
}
|
|
31
|
+
if (!isFeatureMetadataRecord(metadata)) throw new Error("Feature metadata must be a plain object or a JSON string representing a plain object.");
|
|
32
|
+
return JSON.stringify(metadata);
|
|
53
33
|
}
|
|
54
34
|
function parseFeatureMetadata(metadata) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return {};
|
|
62
|
-
}
|
|
35
|
+
if (!metadata) return {};
|
|
36
|
+
try {
|
|
37
|
+
return parseFeatureMetadataString(metadata);
|
|
38
|
+
} catch {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
63
41
|
}
|
|
64
42
|
function parseFeatureMetadataString(metadata) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
if (!isFeatureMetadataRecord(parsed)) {
|
|
75
|
-
throw new Error(
|
|
76
|
-
"Feature metadata must be a plain object or a JSON string representing a plain object."
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
return parsed;
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(metadata);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new Error("Feature metadata must be a plain object or a JSON string representing a plain object.", { cause: error });
|
|
48
|
+
}
|
|
49
|
+
if (!isFeatureMetadataRecord(parsed)) throw new Error("Feature metadata must be a plain object or a JSON string representing a plain object.");
|
|
50
|
+
return parsed;
|
|
80
51
|
}
|
|
81
52
|
function isFeatureMetadataRecord(value) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const prototype = Object.getPrototypeOf(value);
|
|
86
|
-
return prototype === Object.prototype || prototype === null;
|
|
53
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
54
|
+
const prototype = Object.getPrototypeOf(value);
|
|
55
|
+
return prototype === Object.prototype || prototype === null;
|
|
87
56
|
}
|
|
88
57
|
function getQualifiedClassNameFromRegistry(registration) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (registration.packageName) {
|
|
93
|
-
return createQualifiedName(registration.packageName, registration.name);
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
58
|
+
if (registration.qualifiedName && isQualifiedName(registration.qualifiedName)) return registration.qualifiedName;
|
|
59
|
+
if (registration.packageName) return createQualifiedName(registration.packageName, registration.name);
|
|
60
|
+
return null;
|
|
96
61
|
}
|
|
97
62
|
function getPackageNameFromRegistry(registration) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (!qualifiedClassName) {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
return parseQualifiedName(qualifiedClassName).packageName;
|
|
63
|
+
if (registration.packageName) return registration.packageName;
|
|
64
|
+
const qualifiedClassName = getQualifiedClassNameFromRegistry(registration);
|
|
65
|
+
if (!qualifiedClassName) return null;
|
|
66
|
+
return parseQualifiedName(qualifiedClassName).packageName;
|
|
106
67
|
}
|
|
107
68
|
function extractFeatureSeedsFromRegistryEntry(registration) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
defaultEnabled: feature.defaultEnabled,
|
|
127
|
-
label: feature.label,
|
|
128
|
-
description: feature.description,
|
|
129
|
-
metadata: feature.metadata,
|
|
130
|
-
visibility: registration.visibility
|
|
131
|
-
}));
|
|
69
|
+
if (registration.visibility === "test") return [];
|
|
70
|
+
const qualifiedClassName = getQualifiedClassNameFromRegistry(registration);
|
|
71
|
+
if (!qualifiedClassName) return [];
|
|
72
|
+
const featureConfig = registration.config.features;
|
|
73
|
+
if (!featureConfig) return [];
|
|
74
|
+
const packageName = registration.packageName || parseQualifiedName(qualifiedClassName).packageName;
|
|
75
|
+
return Object.entries(featureConfig).map(([localId, feature]) => ({
|
|
76
|
+
featureKey: createFeatureKey(qualifiedClassName, localId),
|
|
77
|
+
packageName,
|
|
78
|
+
qualifiedClassName,
|
|
79
|
+
className: registration.name,
|
|
80
|
+
localId,
|
|
81
|
+
defaultEnabled: feature.defaultEnabled,
|
|
82
|
+
label: feature.label,
|
|
83
|
+
description: feature.description,
|
|
84
|
+
metadata: feature.metadata,
|
|
85
|
+
visibility: registration.visibility
|
|
86
|
+
}));
|
|
132
87
|
}
|
|
133
88
|
function extractFeatureSeedsFromManifest(manifest) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
return seeds;
|
|
89
|
+
const seeds = [];
|
|
90
|
+
for (const [objectKey, objectDef] of Object.entries(manifest.objects)) seeds.push(...extractFeatureSeedsFromManifestObject(objectKey, objectDef));
|
|
91
|
+
return seeds;
|
|
139
92
|
}
|
|
140
93
|
function extractTouchedPackagesFromManifest(manifest) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const qualifiedClassName = resolveManifestQualifiedClassName(
|
|
154
|
-
objectKey,
|
|
155
|
-
objectDef
|
|
156
|
-
);
|
|
157
|
-
if (qualifiedClassName) {
|
|
158
|
-
packages.add(parseQualifiedName(qualifiedClassName).packageName);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return packages;
|
|
94
|
+
const packages = /* @__PURE__ */ new Set();
|
|
95
|
+
if (manifest.packageName) packages.add(manifest.packageName);
|
|
96
|
+
for (const [objectKey, objectDef] of Object.entries(manifest.objects)) {
|
|
97
|
+
if (objectDef.visibility === "test") continue;
|
|
98
|
+
if (objectDef.packageName) {
|
|
99
|
+
packages.add(objectDef.packageName);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const qualifiedClassName = resolveManifestQualifiedClassName(objectKey, objectDef);
|
|
103
|
+
if (qualifiedClassName) packages.add(parseQualifiedName(qualifiedClassName).packageName);
|
|
104
|
+
}
|
|
105
|
+
return packages;
|
|
162
106
|
}
|
|
163
107
|
function extractFeatureSeedsFromManifestObject(objectKey, objectDef) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
qualifiedClassName,
|
|
183
|
-
className: objectDef.className,
|
|
184
|
-
localId,
|
|
185
|
-
defaultEnabled: feature.defaultEnabled,
|
|
186
|
-
label: feature.label,
|
|
187
|
-
description: feature.description,
|
|
188
|
-
metadata: feature.metadata && typeof feature.metadata === "object" ? feature.metadata : void 0,
|
|
189
|
-
visibility: objectDef.visibility
|
|
190
|
-
}));
|
|
108
|
+
if (objectDef.visibility === "test") return [];
|
|
109
|
+
const features = objectDef.decoratorConfig.features;
|
|
110
|
+
if (!features) return [];
|
|
111
|
+
const qualifiedClassName = resolveManifestQualifiedClassName(objectKey, objectDef);
|
|
112
|
+
if (!qualifiedClassName) return [];
|
|
113
|
+
const packageName = objectDef.packageName || parseQualifiedName(qualifiedClassName).packageName;
|
|
114
|
+
return Object.entries(features).map(([localId, feature]) => ({
|
|
115
|
+
featureKey: createFeatureKey(qualifiedClassName, localId),
|
|
116
|
+
packageName,
|
|
117
|
+
qualifiedClassName,
|
|
118
|
+
className: objectDef.className,
|
|
119
|
+
localId,
|
|
120
|
+
defaultEnabled: feature.defaultEnabled,
|
|
121
|
+
label: feature.label,
|
|
122
|
+
description: feature.description,
|
|
123
|
+
metadata: feature.metadata && typeof feature.metadata === "object" ? feature.metadata : void 0,
|
|
124
|
+
visibility: objectDef.visibility
|
|
125
|
+
}));
|
|
191
126
|
}
|
|
192
127
|
function resolveManifestQualifiedClassName(objectKey, objectDef) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return objectKey;
|
|
198
|
-
}
|
|
199
|
-
if (objectDef.packageName) {
|
|
200
|
-
return createQualifiedName(objectDef.packageName, objectDef.className);
|
|
201
|
-
}
|
|
202
|
-
return null;
|
|
128
|
+
if (objectDef.qualifiedName && isQualifiedName(objectDef.qualifiedName)) return objectDef.qualifiedName;
|
|
129
|
+
if (isQualifiedName(objectKey)) return objectKey;
|
|
130
|
+
if (objectDef.packageName) return createQualifiedName(objectDef.packageName, objectDef.className);
|
|
131
|
+
return null;
|
|
203
132
|
}
|
|
204
133
|
function resolveFeatureKeyForTarget(classOrInstance, localId) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
const feature = registration.config.features?.[localId];
|
|
219
|
-
if (!feature) {
|
|
220
|
-
throw new Error(
|
|
221
|
-
`Feature "${localId}" is not declared on ${qualifiedClassName}.`
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
return {
|
|
225
|
-
featureKey: createFeatureKey(qualifiedClassName, localId),
|
|
226
|
-
defaultEnabled: feature.defaultEnabled
|
|
227
|
-
};
|
|
134
|
+
const ctor = typeof classOrInstance === "function" ? classOrInstance : classOrInstance.constructor;
|
|
135
|
+
const registration = ObjectRegistry.getClassByConstructor(ctor);
|
|
136
|
+
if (!registration) throw new Error(`Cannot resolve feature "${localId}" because ${ctor?.name || "the target class"} is not registered with @smrt().`);
|
|
137
|
+
const qualifiedClassName = getQualifiedClassNameFromRegistry(registration);
|
|
138
|
+
if (!qualifiedClassName) throw new Error(`Cannot resolve feature "${localId}" for ${registration.name} because the class has no qualified package identity.`);
|
|
139
|
+
const feature = registration.config.features?.[localId];
|
|
140
|
+
if (!feature) throw new Error(`Feature "${localId}" is not declared on ${qualifiedClassName}.`);
|
|
141
|
+
return {
|
|
142
|
+
featureKey: createFeatureKey(qualifiedClassName, localId),
|
|
143
|
+
defaultEnabled: feature.defaultEnabled
|
|
144
|
+
};
|
|
228
145
|
}
|
|
229
146
|
function findFeatureDefaultInRegistry(featureKey) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const feature = registration?.config.features?.[localId];
|
|
233
|
-
return feature?.defaultEnabled;
|
|
147
|
+
const { qualifiedClassName, localId } = parseFeatureKey(featureKey);
|
|
148
|
+
return ((ObjectRegistry.getClassByQualifiedName(qualifiedClassName) || ObjectRegistry.getClass(qualifiedClassName))?.config.features?.[localId])?.defaultEnabled;
|
|
234
149
|
}
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/feature-definition.ts
|
|
152
|
+
var __defProp$1 = Object.defineProperty;
|
|
235
153
|
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
236
154
|
var __decorateClass$1 = (decorators, target, key, kind) => {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return result;
|
|
155
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
|
|
156
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
157
|
+
if (kind && result) __defProp$1(target, key, result);
|
|
158
|
+
return result;
|
|
242
159
|
};
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
getMetadata() {
|
|
276
|
-
return parseFeatureMetadata(this.metadata);
|
|
277
|
-
}
|
|
278
|
-
setMetadata(metadata) {
|
|
279
|
-
this.metadata = serializeFeatureMetadata(metadata);
|
|
280
|
-
}
|
|
160
|
+
var FeatureDefinition = class extends SmrtObject {
|
|
161
|
+
featureKey = "";
|
|
162
|
+
packageName = "";
|
|
163
|
+
qualifiedClassName = "";
|
|
164
|
+
className = "";
|
|
165
|
+
localId = "";
|
|
166
|
+
defaultEnabled = false;
|
|
167
|
+
label = "";
|
|
168
|
+
description = "";
|
|
169
|
+
metadata = "";
|
|
170
|
+
visibility = "public";
|
|
171
|
+
constructor(options = {}) {
|
|
172
|
+
super(options);
|
|
173
|
+
if (options.featureKey !== void 0) this.featureKey = options.featureKey;
|
|
174
|
+
if (options.packageName !== void 0) this.packageName = options.packageName;
|
|
175
|
+
if (options.qualifiedClassName !== void 0) this.qualifiedClassName = options.qualifiedClassName;
|
|
176
|
+
if (options.className !== void 0) this.className = options.className;
|
|
177
|
+
if (options.localId !== void 0) this.localId = options.localId;
|
|
178
|
+
if (options.defaultEnabled !== void 0) this.defaultEnabled = options.defaultEnabled;
|
|
179
|
+
if (options.label !== void 0) this.label = options.label;
|
|
180
|
+
if (options.description !== void 0) this.description = options.description;
|
|
181
|
+
if (options.visibility !== void 0) this.visibility = options.visibility;
|
|
182
|
+
if (options.metadata !== void 0) this.metadata = serializeFeatureMetadata(options.metadata);
|
|
183
|
+
}
|
|
184
|
+
getMetadata() {
|
|
185
|
+
return parseFeatureMetadata(this.metadata);
|
|
186
|
+
}
|
|
187
|
+
setMetadata(metadata) {
|
|
188
|
+
this.metadata = serializeFeatureMetadata(metadata);
|
|
189
|
+
}
|
|
281
190
|
};
|
|
282
|
-
FeatureDefinition = __decorateClass$1([
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
],
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
191
|
+
FeatureDefinition = __decorateClass$1([smrt({
|
|
192
|
+
tableName: "_smrt_feature_definitions",
|
|
193
|
+
api: { include: ["list", "get"] },
|
|
194
|
+
cli: {
|
|
195
|
+
include: ["list", "get"],
|
|
196
|
+
exclude: ["getMetadata", "setMetadata"]
|
|
197
|
+
},
|
|
198
|
+
mcp: {
|
|
199
|
+
include: ["list", "get"],
|
|
200
|
+
exclude: ["getMetadata", "setMetadata"]
|
|
201
|
+
},
|
|
202
|
+
conflictColumns: ["feature_key"]
|
|
203
|
+
})], FeatureDefinition);
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/feature-definitions.ts
|
|
206
|
+
var FeatureDefinitionCollection = class extends SmrtCollection {
|
|
207
|
+
static _itemClass = FeatureDefinition;
|
|
208
|
+
async findByFeatureKey(featureKey) {
|
|
209
|
+
return (await this.list({
|
|
210
|
+
where: { featureKey },
|
|
211
|
+
limit: 1
|
|
212
|
+
}))[0] ?? null;
|
|
213
|
+
}
|
|
214
|
+
async findByPackageName(packageName) {
|
|
215
|
+
return this.list({ where: { packageName } });
|
|
216
|
+
}
|
|
217
|
+
async upsertDefinition(seed) {
|
|
218
|
+
const existing = await this.findByFeatureKey(seed.featureKey);
|
|
219
|
+
if (!existing) {
|
|
220
|
+
const created = await this.create({
|
|
221
|
+
...seed,
|
|
222
|
+
metadata: serializeFeatureMetadata(seed.metadata)
|
|
223
|
+
});
|
|
224
|
+
await created.save();
|
|
225
|
+
return {
|
|
226
|
+
definition: created,
|
|
227
|
+
status: "created"
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const nextMetadata = serializeFeatureMetadata(seed.metadata);
|
|
231
|
+
if (!(existing.packageName !== seed.packageName || existing.qualifiedClassName !== seed.qualifiedClassName || existing.className !== seed.className || existing.localId !== seed.localId || existing.defaultEnabled !== seed.defaultEnabled || existing.label !== (seed.label ?? "") || existing.description !== (seed.description ?? "") || existing.metadata !== nextMetadata || existing.visibility !== (seed.visibility ?? "public"))) return {
|
|
232
|
+
definition: existing,
|
|
233
|
+
status: "unchanged"
|
|
234
|
+
};
|
|
235
|
+
existing.packageName = seed.packageName;
|
|
236
|
+
existing.qualifiedClassName = seed.qualifiedClassName;
|
|
237
|
+
existing.className = seed.className;
|
|
238
|
+
existing.localId = seed.localId;
|
|
239
|
+
existing.defaultEnabled = seed.defaultEnabled;
|
|
240
|
+
existing.label = seed.label ?? "";
|
|
241
|
+
existing.description = seed.description ?? "";
|
|
242
|
+
existing.metadata = nextMetadata;
|
|
243
|
+
existing.visibility = seed.visibility ?? "public";
|
|
244
|
+
await existing.save();
|
|
245
|
+
return {
|
|
246
|
+
definition: existing,
|
|
247
|
+
status: "updated"
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/feature-override.ts
|
|
253
|
+
var __defProp = Object.defineProperty;
|
|
333
254
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
334
255
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
return result;
|
|
256
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
257
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
258
|
+
if (kind && result) __defProp(target, key, result);
|
|
259
|
+
return result;
|
|
340
260
|
};
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
261
|
+
var FeatureOverride = class extends SmrtObject {
|
|
262
|
+
featureKey = "";
|
|
263
|
+
scopeType = "global";
|
|
264
|
+
scopeId = "*";
|
|
265
|
+
effect = FeatureOverrideEffect.INHERIT;
|
|
266
|
+
constructor(options = {}) {
|
|
267
|
+
super(options);
|
|
268
|
+
if (options.featureKey !== void 0) this.featureKey = options.featureKey;
|
|
269
|
+
if (options.scopeType !== void 0) this.scopeType = options.scopeType;
|
|
270
|
+
if (options.scopeId !== void 0) this.scopeId = options.scopeId;
|
|
271
|
+
if (options.effect !== void 0) this.effect = options.effect;
|
|
272
|
+
}
|
|
273
|
+
isInherit() {
|
|
274
|
+
return this.effect === FeatureOverrideEffect.INHERIT;
|
|
275
|
+
}
|
|
276
|
+
isEnabled() {
|
|
277
|
+
return this.effect === FeatureOverrideEffect.ENABLE;
|
|
278
|
+
}
|
|
279
|
+
isDisabled() {
|
|
280
|
+
return this.effect === FeatureOverrideEffect.DISABLE;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
FeatureOverride = __decorateClass([smrt({
|
|
284
|
+
tableName: "_smrt_feature_overrides",
|
|
285
|
+
api: { include: [
|
|
286
|
+
"list",
|
|
287
|
+
"get",
|
|
288
|
+
"create",
|
|
289
|
+
"update",
|
|
290
|
+
"delete"
|
|
291
|
+
] },
|
|
292
|
+
cli: { exclude: [
|
|
293
|
+
"isInherit",
|
|
294
|
+
"isEnabled",
|
|
295
|
+
"isDisabled"
|
|
296
|
+
] },
|
|
297
|
+
mcp: { exclude: [
|
|
298
|
+
"isInherit",
|
|
299
|
+
"isEnabled",
|
|
300
|
+
"isDisabled"
|
|
301
|
+
] },
|
|
302
|
+
conflictColumns: [
|
|
303
|
+
"feature_key",
|
|
304
|
+
"scope_type",
|
|
305
|
+
"scope_id"
|
|
306
|
+
]
|
|
307
|
+
})], FeatureOverride);
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/feature-overrides.ts
|
|
310
|
+
var FeatureOverrideCollection = class extends SmrtCollection {
|
|
311
|
+
static _itemClass = FeatureOverride;
|
|
312
|
+
async findByFeatureKey(featureKey) {
|
|
313
|
+
return this.list({ where: { featureKey } });
|
|
314
|
+
}
|
|
315
|
+
async findByFeatureAndScope(featureKey, scopeType, scopeId) {
|
|
316
|
+
return (await this.list({
|
|
317
|
+
where: {
|
|
318
|
+
featureKey,
|
|
319
|
+
scopeType,
|
|
320
|
+
scopeId
|
|
321
|
+
},
|
|
322
|
+
limit: 1
|
|
323
|
+
}))[0] ?? null;
|
|
324
|
+
}
|
|
325
|
+
async getGlobalOverride(featureKey) {
|
|
326
|
+
return this.findByFeatureAndScope(featureKey, "global", "*");
|
|
327
|
+
}
|
|
328
|
+
async getTenantOverride(featureKey, tenantId) {
|
|
329
|
+
return this.findByFeatureAndScope(featureKey, "tenant", tenantId);
|
|
330
|
+
}
|
|
331
|
+
async getOverrideMap(featureKey, scopeType, scopeIds) {
|
|
332
|
+
const result = /* @__PURE__ */ new Map();
|
|
333
|
+
if (scopeIds.length === 0) return result;
|
|
334
|
+
const overrides = await this.list({ where: {
|
|
335
|
+
featureKey,
|
|
336
|
+
scopeType,
|
|
337
|
+
scopeId: scopeIds
|
|
338
|
+
} });
|
|
339
|
+
for (const override of overrides) result.set(override.scopeId, override);
|
|
340
|
+
return result;
|
|
341
|
+
}
|
|
342
|
+
async setOverride(featureKey, scopeType, scopeId, effect) {
|
|
343
|
+
const existing = await this.findByFeatureAndScope(featureKey, scopeType, scopeId);
|
|
344
|
+
if (existing) {
|
|
345
|
+
existing.effect = effect;
|
|
346
|
+
await existing.save();
|
|
347
|
+
return existing;
|
|
348
|
+
}
|
|
349
|
+
const created = await this.create({
|
|
350
|
+
featureKey,
|
|
351
|
+
scopeType,
|
|
352
|
+
scopeId,
|
|
353
|
+
effect
|
|
354
|
+
});
|
|
355
|
+
await created.save();
|
|
356
|
+
return created;
|
|
357
|
+
}
|
|
358
|
+
async setGlobalOverride(featureKey, effect) {
|
|
359
|
+
return this.setOverride(featureKey, "global", "*", effect);
|
|
360
|
+
}
|
|
361
|
+
async setTenantOverride(featureKey, tenantId, effect) {
|
|
362
|
+
return this.setOverride(featureKey, "tenant", tenantId, effect);
|
|
363
|
+
}
|
|
364
|
+
async removeOverride(featureKey, scopeType, scopeId) {
|
|
365
|
+
const existing = await this.findByFeatureAndScope(featureKey, scopeType, scopeId);
|
|
366
|
+
if (!existing) return false;
|
|
367
|
+
await existing.delete();
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
//#endregion
|
|
372
|
+
//#region src/feature-resolver.ts
|
|
373
|
+
var FeatureResolver = class {
|
|
374
|
+
options;
|
|
375
|
+
resolverOptions;
|
|
376
|
+
featureDefinitions;
|
|
377
|
+
featureOverrides;
|
|
378
|
+
initializationPromise = null;
|
|
379
|
+
tenantHierarchyPromise = null;
|
|
380
|
+
constructor(options = {}, resolverOptions = {}) {
|
|
381
|
+
this.options = options;
|
|
382
|
+
this.resolverOptions = resolverOptions;
|
|
383
|
+
}
|
|
384
|
+
async isEnabled(featureKey, context = {}) {
|
|
385
|
+
await this.ensureInitialized();
|
|
386
|
+
const baseState = await this.resolveBaseState(featureKey);
|
|
387
|
+
const globalOverride = await this.featureOverrides.getGlobalOverride(featureKey);
|
|
388
|
+
const globalState = this.applyOverride(baseState, globalOverride?.effect);
|
|
389
|
+
if (!context.tenantId) return globalState;
|
|
390
|
+
const tenantHierarchy = await this.getTenantHierarchy();
|
|
391
|
+
if (!tenantHierarchy) {
|
|
392
|
+
const directOverride = await this.featureOverrides.getTenantOverride(featureKey, context.tenantId);
|
|
393
|
+
return this.applyOverride(globalState, directOverride?.effect);
|
|
394
|
+
}
|
|
395
|
+
const chain = await tenantHierarchy.getChain(context.tenantId);
|
|
396
|
+
if (chain.length === 0) {
|
|
397
|
+
const directOverride = await this.featureOverrides.getTenantOverride(featureKey, context.tenantId);
|
|
398
|
+
return this.applyOverride(globalState, directOverride?.effect);
|
|
399
|
+
}
|
|
400
|
+
const overrides = await this.featureOverrides.getOverrideMap(featureKey, "tenant", chain.map((node) => node.id));
|
|
401
|
+
let inheritedState = globalState;
|
|
402
|
+
for (let index = 0; index < chain.length; index++) {
|
|
403
|
+
const current = chain[index];
|
|
404
|
+
const previous = index > 0 ? chain[index - 1] : null;
|
|
405
|
+
const baseline = index === 0 || !!previous?.cascadePermissions && current.inheritPermissions ? inheritedState : globalState;
|
|
406
|
+
const override = overrides.get(current.id);
|
|
407
|
+
inheritedState = this.applyOverride(baseline, override?.effect);
|
|
408
|
+
}
|
|
409
|
+
return inheritedState;
|
|
410
|
+
}
|
|
411
|
+
async isEnabledFor(classOrInstance, localId, context = {}) {
|
|
412
|
+
const { featureKey } = resolveFeatureKeyForTarget(classOrInstance, localId);
|
|
413
|
+
return this.isEnabled(featureKey, context);
|
|
414
|
+
}
|
|
415
|
+
async ensureInitialized() {
|
|
416
|
+
if (!this.initializationPromise) this.initializationPromise = (async () => {
|
|
417
|
+
this.featureDefinitions = await FeatureDefinitionCollection.create(this.options);
|
|
418
|
+
this.featureOverrides = await FeatureOverrideCollection.create(this.options);
|
|
419
|
+
})();
|
|
420
|
+
await this.initializationPromise;
|
|
421
|
+
}
|
|
422
|
+
async resolveBaseState(featureKey) {
|
|
423
|
+
const registryDefault = findFeatureDefaultInRegistry(featureKey);
|
|
424
|
+
if (registryDefault !== void 0) return registryDefault;
|
|
425
|
+
const definition = await this.featureDefinitions.findByFeatureKey(featureKey);
|
|
426
|
+
if (!definition) throw new Error(`Feature "${featureKey}" is not declared in the registry or synced feature definitions.`);
|
|
427
|
+
return definition.defaultEnabled;
|
|
428
|
+
}
|
|
429
|
+
applyOverride(currentState, effect) {
|
|
430
|
+
switch (effect) {
|
|
431
|
+
case FeatureOverrideEffect.ENABLE: return true;
|
|
432
|
+
case FeatureOverrideEffect.DISABLE: return false;
|
|
433
|
+
default: return currentState;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async getTenantHierarchy() {
|
|
437
|
+
if (!this.tenantHierarchyPromise) {
|
|
438
|
+
const loader = this.resolverOptions.tenantHierarchyLoader || defaultTenantHierarchyLoader;
|
|
439
|
+
this.tenantHierarchyPromise = loader(this.options);
|
|
440
|
+
}
|
|
441
|
+
return this.tenantHierarchyPromise;
|
|
442
|
+
}
|
|
362
443
|
};
|
|
363
|
-
FeatureOverride = __decorateClass([
|
|
364
|
-
smrt({
|
|
365
|
-
tableName: "_smrt_feature_overrides",
|
|
366
|
-
api: { include: ["list", "get", "create", "update", "delete"] },
|
|
367
|
-
cli: {
|
|
368
|
-
exclude: ["isInherit", "isEnabled", "isDisabled"]
|
|
369
|
-
},
|
|
370
|
-
mcp: {
|
|
371
|
-
exclude: ["isInherit", "isEnabled", "isDisabled"]
|
|
372
|
-
},
|
|
373
|
-
conflictColumns: ["feature_key", "scope_type", "scope_id"]
|
|
374
|
-
})
|
|
375
|
-
], FeatureOverride);
|
|
376
|
-
class FeatureOverrideCollection extends SmrtCollection {
|
|
377
|
-
static _itemClass = FeatureOverride;
|
|
378
|
-
async findByFeatureKey(featureKey) {
|
|
379
|
-
return this.list({
|
|
380
|
-
where: { featureKey }
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
async findByFeatureAndScope(featureKey, scopeType, scopeId) {
|
|
384
|
-
const results = await this.list({
|
|
385
|
-
where: { featureKey, scopeType, scopeId },
|
|
386
|
-
limit: 1
|
|
387
|
-
});
|
|
388
|
-
return results[0] ?? null;
|
|
389
|
-
}
|
|
390
|
-
async getGlobalOverride(featureKey) {
|
|
391
|
-
return this.findByFeatureAndScope(
|
|
392
|
-
featureKey,
|
|
393
|
-
"global",
|
|
394
|
-
GLOBAL_FEATURE_SCOPE_ID
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
async getTenantOverride(featureKey, tenantId) {
|
|
398
|
-
return this.findByFeatureAndScope(featureKey, "tenant", tenantId);
|
|
399
|
-
}
|
|
400
|
-
async getOverrideMap(featureKey, scopeType, scopeIds) {
|
|
401
|
-
const result = /* @__PURE__ */ new Map();
|
|
402
|
-
if (scopeIds.length === 0) {
|
|
403
|
-
return result;
|
|
404
|
-
}
|
|
405
|
-
const overrides = await this.list({
|
|
406
|
-
where: { featureKey, scopeType, scopeId: scopeIds }
|
|
407
|
-
});
|
|
408
|
-
for (const override of overrides) {
|
|
409
|
-
result.set(override.scopeId, override);
|
|
410
|
-
}
|
|
411
|
-
return result;
|
|
412
|
-
}
|
|
413
|
-
async setOverride(featureKey, scopeType, scopeId, effect) {
|
|
414
|
-
const existing = await this.findByFeatureAndScope(
|
|
415
|
-
featureKey,
|
|
416
|
-
scopeType,
|
|
417
|
-
scopeId
|
|
418
|
-
);
|
|
419
|
-
if (existing) {
|
|
420
|
-
existing.effect = effect;
|
|
421
|
-
await existing.save();
|
|
422
|
-
return existing;
|
|
423
|
-
}
|
|
424
|
-
const created = await this.create({
|
|
425
|
-
featureKey,
|
|
426
|
-
scopeType,
|
|
427
|
-
scopeId,
|
|
428
|
-
effect
|
|
429
|
-
});
|
|
430
|
-
await created.save();
|
|
431
|
-
return created;
|
|
432
|
-
}
|
|
433
|
-
async setGlobalOverride(featureKey, effect) {
|
|
434
|
-
return this.setOverride(
|
|
435
|
-
featureKey,
|
|
436
|
-
"global",
|
|
437
|
-
GLOBAL_FEATURE_SCOPE_ID,
|
|
438
|
-
effect
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
async setTenantOverride(featureKey, tenantId, effect) {
|
|
442
|
-
return this.setOverride(featureKey, "tenant", tenantId, effect);
|
|
443
|
-
}
|
|
444
|
-
async removeOverride(featureKey, scopeType, scopeId) {
|
|
445
|
-
const existing = await this.findByFeatureAndScope(
|
|
446
|
-
featureKey,
|
|
447
|
-
scopeType,
|
|
448
|
-
scopeId
|
|
449
|
-
);
|
|
450
|
-
if (!existing) {
|
|
451
|
-
return false;
|
|
452
|
-
}
|
|
453
|
-
await existing.delete();
|
|
454
|
-
return true;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
class FeatureResolver {
|
|
458
|
-
options;
|
|
459
|
-
resolverOptions;
|
|
460
|
-
featureDefinitions;
|
|
461
|
-
featureOverrides;
|
|
462
|
-
initializationPromise = null;
|
|
463
|
-
tenantHierarchyPromise = null;
|
|
464
|
-
constructor(options = {}, resolverOptions = {}) {
|
|
465
|
-
this.options = options;
|
|
466
|
-
this.resolverOptions = resolverOptions;
|
|
467
|
-
}
|
|
468
|
-
async isEnabled(featureKey, context = {}) {
|
|
469
|
-
await this.ensureInitialized();
|
|
470
|
-
const baseState = await this.resolveBaseState(featureKey);
|
|
471
|
-
const globalOverride = await this.featureOverrides.getGlobalOverride(featureKey);
|
|
472
|
-
const globalState = this.applyOverride(baseState, globalOverride?.effect);
|
|
473
|
-
if (!context.tenantId) {
|
|
474
|
-
return globalState;
|
|
475
|
-
}
|
|
476
|
-
const tenantHierarchy = await this.getTenantHierarchy();
|
|
477
|
-
if (!tenantHierarchy) {
|
|
478
|
-
const directOverride = await this.featureOverrides.getTenantOverride(
|
|
479
|
-
featureKey,
|
|
480
|
-
context.tenantId
|
|
481
|
-
);
|
|
482
|
-
return this.applyOverride(globalState, directOverride?.effect);
|
|
483
|
-
}
|
|
484
|
-
const chain = await tenantHierarchy.getChain(context.tenantId);
|
|
485
|
-
if (chain.length === 0) {
|
|
486
|
-
const directOverride = await this.featureOverrides.getTenantOverride(
|
|
487
|
-
featureKey,
|
|
488
|
-
context.tenantId
|
|
489
|
-
);
|
|
490
|
-
return this.applyOverride(globalState, directOverride?.effect);
|
|
491
|
-
}
|
|
492
|
-
const overrides = await this.featureOverrides.getOverrideMap(
|
|
493
|
-
featureKey,
|
|
494
|
-
"tenant",
|
|
495
|
-
chain.map((node) => node.id)
|
|
496
|
-
);
|
|
497
|
-
let inheritedState = globalState;
|
|
498
|
-
for (let index = 0; index < chain.length; index++) {
|
|
499
|
-
const current = chain[index];
|
|
500
|
-
const previous = index > 0 ? chain[index - 1] : null;
|
|
501
|
-
const shouldInherit = index === 0 || !!previous?.cascadePermissions && current.inheritPermissions;
|
|
502
|
-
const baseline = shouldInherit ? inheritedState : globalState;
|
|
503
|
-
const override = overrides.get(current.id);
|
|
504
|
-
inheritedState = this.applyOverride(baseline, override?.effect);
|
|
505
|
-
}
|
|
506
|
-
return inheritedState;
|
|
507
|
-
}
|
|
508
|
-
async isEnabledFor(classOrInstance, localId, context = {}) {
|
|
509
|
-
const { featureKey } = resolveFeatureKeyForTarget(classOrInstance, localId);
|
|
510
|
-
return this.isEnabled(featureKey, context);
|
|
511
|
-
}
|
|
512
|
-
async ensureInitialized() {
|
|
513
|
-
if (!this.initializationPromise) {
|
|
514
|
-
this.initializationPromise = (async () => {
|
|
515
|
-
this.featureDefinitions = await FeatureDefinitionCollection.create(this.options);
|
|
516
|
-
this.featureOverrides = await FeatureOverrideCollection.create(this.options);
|
|
517
|
-
})();
|
|
518
|
-
}
|
|
519
|
-
await this.initializationPromise;
|
|
520
|
-
}
|
|
521
|
-
async resolveBaseState(featureKey) {
|
|
522
|
-
const registryDefault = findFeatureDefaultInRegistry(featureKey);
|
|
523
|
-
if (registryDefault !== void 0) {
|
|
524
|
-
return registryDefault;
|
|
525
|
-
}
|
|
526
|
-
const definition = await this.featureDefinitions.findByFeatureKey(featureKey);
|
|
527
|
-
if (!definition) {
|
|
528
|
-
throw new Error(
|
|
529
|
-
`Feature "${featureKey}" is not declared in the registry or synced feature definitions.`
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
return definition.defaultEnabled;
|
|
533
|
-
}
|
|
534
|
-
applyOverride(currentState, effect) {
|
|
535
|
-
switch (effect) {
|
|
536
|
-
case FeatureOverrideEffect.ENABLE:
|
|
537
|
-
return true;
|
|
538
|
-
case FeatureOverrideEffect.DISABLE:
|
|
539
|
-
return false;
|
|
540
|
-
default:
|
|
541
|
-
return currentState;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
async getTenantHierarchy() {
|
|
545
|
-
if (!this.tenantHierarchyPromise) {
|
|
546
|
-
const loader = this.resolverOptions.tenantHierarchyLoader || defaultTenantHierarchyLoader;
|
|
547
|
-
this.tenantHierarchyPromise = loader(this.options);
|
|
548
|
-
}
|
|
549
|
-
return this.tenantHierarchyPromise;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
444
|
async function defaultTenantHierarchyLoader(options) {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
} catch (error) {
|
|
575
|
-
if (isMissingUsersDependency(error)) {
|
|
576
|
-
return null;
|
|
577
|
-
}
|
|
578
|
-
throw error;
|
|
579
|
-
}
|
|
445
|
+
try {
|
|
446
|
+
const tenantCollection = await (await importWorkspaceModule({
|
|
447
|
+
packageName: "@happyvertical/smrt-users",
|
|
448
|
+
sourceEntry: "packages/users/src/collections/index.ts",
|
|
449
|
+
purpose: "tenant-aware feature flag resolution"
|
|
450
|
+
})).TenantCollection.create(options);
|
|
451
|
+
return { async getChain(tenantId) {
|
|
452
|
+
const tenant = await tenantCollection.get({ id: tenantId });
|
|
453
|
+
if (!tenant) return [];
|
|
454
|
+
return [...await tenantCollection.getAncestorsFromRoot(tenantId), tenant].map((node) => ({
|
|
455
|
+
id: String(node.id),
|
|
456
|
+
inheritPermissions: Boolean(node.inheritPermissions),
|
|
457
|
+
cascadePermissions: Boolean(node.cascadePermissions)
|
|
458
|
+
}));
|
|
459
|
+
} };
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (isMissingUsersDependency(error)) return null;
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
580
464
|
}
|
|
581
465
|
function isMissingUsersDependency(error) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
const text = `${error.message}${causeMessage}`;
|
|
587
|
-
return text.includes("@happyvertical/smrt-users") && (text.includes("Cannot find module") || text.includes("Failed to load") || text.includes("could not find"));
|
|
588
|
-
}
|
|
589
|
-
class FeatureSyncService {
|
|
590
|
-
options;
|
|
591
|
-
featureDefinitions;
|
|
592
|
-
initializationPromise = null;
|
|
593
|
-
constructor(options = {}) {
|
|
594
|
-
this.options = options;
|
|
595
|
-
}
|
|
596
|
-
async syncDefinitions(options = {}) {
|
|
597
|
-
await this.ensureInitialized();
|
|
598
|
-
const registrations = this.collectRegistrations(options);
|
|
599
|
-
const definitions = this.collectDefinitionsFromRegistrations(registrations);
|
|
600
|
-
const touchedPackages = this.collectTouchedPackagesFromRegistrations(registrations);
|
|
601
|
-
const isFilteredSync = Boolean(options.classNames?.length) || Boolean(options.constructors?.length);
|
|
602
|
-
const pruneStale = isFilteredSync ? false : options.pruneStale ?? true;
|
|
603
|
-
return this.applyDefinitions(definitions, touchedPackages, pruneStale);
|
|
604
|
-
}
|
|
605
|
-
async syncManifest(manifest, options = {}) {
|
|
606
|
-
await this.ensureInitialized();
|
|
607
|
-
const definitions = extractFeatureSeedsFromManifest(manifest);
|
|
608
|
-
const touchedPackages = extractTouchedPackagesFromManifest(manifest);
|
|
609
|
-
return this.applyDefinitions(
|
|
610
|
-
definitions,
|
|
611
|
-
touchedPackages,
|
|
612
|
-
options.pruneStale ?? true
|
|
613
|
-
);
|
|
614
|
-
}
|
|
615
|
-
async ensureInitialized() {
|
|
616
|
-
if (!this.initializationPromise) {
|
|
617
|
-
this.initializationPromise = (async () => {
|
|
618
|
-
this.featureDefinitions = await FeatureDefinitionCollection.create(this.options);
|
|
619
|
-
})();
|
|
620
|
-
}
|
|
621
|
-
await this.initializationPromise;
|
|
622
|
-
}
|
|
623
|
-
collectDefinitionsFromRegistrations(registrations) {
|
|
624
|
-
const definitions = [];
|
|
625
|
-
for (const registration of registrations) {
|
|
626
|
-
definitions.push(...extractFeatureSeedsFromRegistryEntry(registration));
|
|
627
|
-
}
|
|
628
|
-
return definitions;
|
|
629
|
-
}
|
|
630
|
-
collectTouchedPackagesFromRegistrations(registrations) {
|
|
631
|
-
const packages = /* @__PURE__ */ new Set();
|
|
632
|
-
for (const registration of registrations) {
|
|
633
|
-
if (registration.visibility === "test") {
|
|
634
|
-
continue;
|
|
635
|
-
}
|
|
636
|
-
const packageName = getPackageNameFromRegistry(registration);
|
|
637
|
-
if (packageName) {
|
|
638
|
-
packages.add(packageName);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
return packages;
|
|
642
|
-
}
|
|
643
|
-
collectRegistrations(options) {
|
|
644
|
-
if (options.classNames?.length) {
|
|
645
|
-
return options.classNames.map((name) => ObjectRegistry.getClass(name)).filter((value) => !!value);
|
|
646
|
-
}
|
|
647
|
-
if (options.constructors?.length) {
|
|
648
|
-
return options.constructors.map((ctor) => ObjectRegistry.getClassByConstructor(ctor)).filter((value) => !!value);
|
|
649
|
-
}
|
|
650
|
-
const registrations = [];
|
|
651
|
-
const seen = /* @__PURE__ */ new Set();
|
|
652
|
-
for (const registration of ObjectRegistry.getAllClasses().values()) {
|
|
653
|
-
if (seen.has(registration)) {
|
|
654
|
-
continue;
|
|
655
|
-
}
|
|
656
|
-
seen.add(registration);
|
|
657
|
-
registrations.push(registration);
|
|
658
|
-
}
|
|
659
|
-
return registrations;
|
|
660
|
-
}
|
|
661
|
-
async applyDefinitions(definitions, touchedPackages, pruneStale) {
|
|
662
|
-
const deduped = /* @__PURE__ */ new Map();
|
|
663
|
-
for (const definition of definitions) {
|
|
664
|
-
deduped.set(definition.featureKey, definition);
|
|
665
|
-
}
|
|
666
|
-
let created = 0;
|
|
667
|
-
let updated = 0;
|
|
668
|
-
let unchanged = 0;
|
|
669
|
-
let deleted = 0;
|
|
670
|
-
const dedupedDefinitions = Array.from(deduped.values());
|
|
671
|
-
const currentKeys = new Set(
|
|
672
|
-
dedupedDefinitions.map((definition) => definition.featureKey)
|
|
673
|
-
);
|
|
674
|
-
const upsertResults = await Promise.all(
|
|
675
|
-
dedupedDefinitions.map(
|
|
676
|
-
(definition) => this.featureDefinitions.upsertDefinition(definition)
|
|
677
|
-
)
|
|
678
|
-
);
|
|
679
|
-
for (const result of upsertResults) {
|
|
680
|
-
if (result.status === "created") created++;
|
|
681
|
-
if (result.status === "updated") updated++;
|
|
682
|
-
if (result.status === "unchanged") unchanged++;
|
|
683
|
-
}
|
|
684
|
-
if (pruneStale) {
|
|
685
|
-
const existingByPackage = await Promise.all(
|
|
686
|
-
Array.from(
|
|
687
|
-
touchedPackages,
|
|
688
|
-
(packageName) => this.featureDefinitions.findByPackageName(packageName)
|
|
689
|
-
)
|
|
690
|
-
);
|
|
691
|
-
const staleDefinitions = existingByPackage.flatMap(
|
|
692
|
-
(existing) => existing.filter(
|
|
693
|
-
(definition) => !currentKeys.has(definition.featureKey)
|
|
694
|
-
)
|
|
695
|
-
);
|
|
696
|
-
await Promise.all(
|
|
697
|
-
staleDefinitions.map((definition) => definition.delete())
|
|
698
|
-
);
|
|
699
|
-
deleted = staleDefinitions.length;
|
|
700
|
-
}
|
|
701
|
-
return {
|
|
702
|
-
total: deduped.size,
|
|
703
|
-
created,
|
|
704
|
-
updated,
|
|
705
|
-
unchanged,
|
|
706
|
-
deleted,
|
|
707
|
-
featureKeys: Array.from(currentKeys).sort()
|
|
708
|
-
};
|
|
709
|
-
}
|
|
466
|
+
if (!(error instanceof Error)) return false;
|
|
467
|
+
const causeMessage = error.cause instanceof Error ? ` ${error.cause.message}` : "";
|
|
468
|
+
const text = `${error.message}${causeMessage}`;
|
|
469
|
+
return text.includes("@happyvertical/smrt-users") && (text.includes("Cannot find module") || text.includes("Failed to load") || text.includes("could not find"));
|
|
710
470
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
471
|
+
//#endregion
|
|
472
|
+
//#region src/feature-sync.ts
|
|
473
|
+
var FeatureSyncService = class {
|
|
474
|
+
options;
|
|
475
|
+
featureDefinitions;
|
|
476
|
+
initializationPromise = null;
|
|
477
|
+
constructor(options = {}) {
|
|
478
|
+
this.options = options;
|
|
479
|
+
}
|
|
480
|
+
async syncDefinitions(options = {}) {
|
|
481
|
+
await this.ensureInitialized();
|
|
482
|
+
const registrations = this.collectRegistrations(options);
|
|
483
|
+
const definitions = this.collectDefinitionsFromRegistrations(registrations);
|
|
484
|
+
const touchedPackages = this.collectTouchedPackagesFromRegistrations(registrations);
|
|
485
|
+
const pruneStale = Boolean(options.classNames?.length) || Boolean(options.constructors?.length) ? false : options.pruneStale ?? true;
|
|
486
|
+
return this.applyDefinitions(definitions, touchedPackages, pruneStale);
|
|
487
|
+
}
|
|
488
|
+
async syncManifest(manifest, options = {}) {
|
|
489
|
+
await this.ensureInitialized();
|
|
490
|
+
const definitions = extractFeatureSeedsFromManifest(manifest);
|
|
491
|
+
const touchedPackages = extractTouchedPackagesFromManifest(manifest);
|
|
492
|
+
return this.applyDefinitions(definitions, touchedPackages, options.pruneStale ?? true);
|
|
493
|
+
}
|
|
494
|
+
async ensureInitialized() {
|
|
495
|
+
if (!this.initializationPromise) this.initializationPromise = (async () => {
|
|
496
|
+
this.featureDefinitions = await FeatureDefinitionCollection.create(this.options);
|
|
497
|
+
})();
|
|
498
|
+
await this.initializationPromise;
|
|
499
|
+
}
|
|
500
|
+
collectDefinitionsFromRegistrations(registrations) {
|
|
501
|
+
const definitions = [];
|
|
502
|
+
for (const registration of registrations) definitions.push(...extractFeatureSeedsFromRegistryEntry(registration));
|
|
503
|
+
return definitions;
|
|
504
|
+
}
|
|
505
|
+
collectTouchedPackagesFromRegistrations(registrations) {
|
|
506
|
+
const packages = /* @__PURE__ */ new Set();
|
|
507
|
+
for (const registration of registrations) {
|
|
508
|
+
if (registration.visibility === "test") continue;
|
|
509
|
+
const packageName = getPackageNameFromRegistry(registration);
|
|
510
|
+
if (packageName) packages.add(packageName);
|
|
511
|
+
}
|
|
512
|
+
return packages;
|
|
513
|
+
}
|
|
514
|
+
collectRegistrations(options) {
|
|
515
|
+
if (options.classNames?.length) return options.classNames.map((name) => ObjectRegistry.getClass(name)).filter((value) => !!value);
|
|
516
|
+
if (options.constructors?.length) return options.constructors.map((ctor) => ObjectRegistry.getClassByConstructor(ctor)).filter((value) => !!value);
|
|
517
|
+
const registrations = [];
|
|
518
|
+
const seen = /* @__PURE__ */ new Set();
|
|
519
|
+
for (const registration of ObjectRegistry.getAllClasses().values()) {
|
|
520
|
+
if (seen.has(registration)) continue;
|
|
521
|
+
seen.add(registration);
|
|
522
|
+
registrations.push(registration);
|
|
523
|
+
}
|
|
524
|
+
return registrations;
|
|
525
|
+
}
|
|
526
|
+
async applyDefinitions(definitions, touchedPackages, pruneStale) {
|
|
527
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
528
|
+
for (const definition of definitions) deduped.set(definition.featureKey, definition);
|
|
529
|
+
let created = 0;
|
|
530
|
+
let updated = 0;
|
|
531
|
+
let unchanged = 0;
|
|
532
|
+
let deleted = 0;
|
|
533
|
+
const dedupedDefinitions = Array.from(deduped.values());
|
|
534
|
+
const currentKeys = new Set(dedupedDefinitions.map((definition) => definition.featureKey));
|
|
535
|
+
const upsertResults = await Promise.all(dedupedDefinitions.map((definition) => this.featureDefinitions.upsertDefinition(definition)));
|
|
536
|
+
for (const result of upsertResults) {
|
|
537
|
+
if (result.status === "created") created++;
|
|
538
|
+
if (result.status === "updated") updated++;
|
|
539
|
+
if (result.status === "unchanged") unchanged++;
|
|
540
|
+
}
|
|
541
|
+
if (pruneStale) {
|
|
542
|
+
const staleDefinitions = (await Promise.all(Array.from(touchedPackages, (packageName) => this.featureDefinitions.findByPackageName(packageName)))).flatMap((existing) => existing.filter((definition) => !currentKeys.has(definition.featureKey)));
|
|
543
|
+
await Promise.all(staleDefinitions.map((definition) => definition.delete()));
|
|
544
|
+
deleted = staleDefinitions.length;
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
total: deduped.size,
|
|
548
|
+
created,
|
|
549
|
+
updated,
|
|
550
|
+
unchanged,
|
|
551
|
+
deleted,
|
|
552
|
+
featureKeys: Array.from(currentKeys).sort()
|
|
553
|
+
};
|
|
554
|
+
}
|
|
724
555
|
};
|
|
725
|
-
//#
|
|
556
|
+
//#endregion
|
|
557
|
+
export { FeatureDefinition, FeatureDefinitionCollection, FeatureOverride, FeatureOverrideCollection, FeatureOverrideEffect, FeatureResolver, FeatureSyncService, GLOBAL_FEATURE_SCOPE_ID, createFeatureKey, findFeatureDefaultInRegistry, parseFeatureKey, resolveFeatureKeyForTarget };
|
|
558
|
+
|
|
559
|
+
//# sourceMappingURL=index.js.map
|