@happyvertical/smrt-features 0.51.19 → 0.51.21

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 (40) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +152 -0
  3. package/dist/__smrt-register__.d.ts +2 -0
  4. package/dist/__smrt-register__.d.ts.map +1 -0
  5. package/dist/feature-definition.d.ts +18 -0
  6. package/dist/feature-definition.d.ts.map +1 -0
  7. package/dist/feature-definitions.d.ts +13 -0
  8. package/dist/feature-definitions.d.ts.map +1 -0
  9. package/dist/feature-override-service.d.ts +41 -0
  10. package/dist/feature-override-service.d.ts.map +1 -0
  11. package/dist/feature-override.d.ts +13 -0
  12. package/dist/feature-override.d.ts.map +1 -0
  13. package/dist/feature-overrides.d.ts +16 -0
  14. package/dist/feature-overrides.d.ts.map +1 -0
  15. package/dist/feature-resolver.d.ts +18 -0
  16. package/dist/feature-resolver.d.ts.map +1 -0
  17. package/dist/feature-settings.d.ts +209 -0
  18. package/dist/feature-settings.d.ts.map +1 -0
  19. package/dist/feature-sync.d.ts +17 -0
  20. package/dist/feature-sync.d.ts.map +1 -0
  21. package/dist/index.d.ts +11 -263
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +155 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/manifest.json +1 -1
  26. package/dist/smrt-knowledge.json +14 -6
  27. package/dist/svelte/components/FeatureSettingsPanel.svelte +338 -0
  28. package/dist/svelte/components/FeatureSettingsPanel.svelte.d.ts +52 -0
  29. package/dist/svelte/components/FeatureSettingsPanel.svelte.d.ts.map +1 -0
  30. package/dist/svelte/index.d.ts +17 -0
  31. package/dist/svelte/index.d.ts.map +1 -0
  32. package/dist/svelte/index.js +14 -0
  33. package/dist/svelte/types.d.ts +76 -0
  34. package/dist/svelte/types.d.ts.map +1 -0
  35. package/dist/svelte/types.js +37 -0
  36. package/dist/types.d.ts +107 -131
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/utils.d.ts +28 -0
  39. package/dist/utils.d.ts.map +1 -0
  40. package/package.json +26 -7
package/AGENTS.md CHANGED
@@ -8,6 +8,9 @@ Code-first feature flag system with global, app-level, and tenant-hierarchy reso
8
8
  - `FeatureOverride` (`_smrt_feature_overrides` table) — runtime override state, scoped by `(featureKey, scopeType, scopeId)`
9
9
  - `FeatureResolver` — resolves the effective state of a feature in a given context, walking the scope chain
10
10
  - `FeatureSyncService` — keeps `_smrt_feature_definitions` in sync with code-registered defaults at boot
11
+ - `FeatureOverrideService` — the only write path; asks a host-supplied authorizer before every write and fails closed (#3013)
12
+ - `FeatureSettingsService` — read/write helper for a management screen: `listFeatureSettings()` returns one row per definition (definition fields, `effectiveEnabled`, `globalEffect`, `tenantEffect`), `setFeatureOverride()` refuses unknown keys and scopes the resolver never reads back, then delegates to `FeatureOverrideService` (#3052)
13
+ - `./svelte` subpath — `FeatureSettingsPanel`, the presentational management panel (#3052)
11
14
 
12
15
  ## Resolution chain (priority high → low)
13
16
 
@@ -24,6 +27,20 @@ Code-first feature flag system with global, app-level, and tenant-hierarchy reso
24
27
  - Overrides are write-time validated against the matching definition (effect must be a known `FeatureOverrideEffect`, scope must be valid for the definition's `allowedScopes`)
25
28
  - Tenant-hierarchy resolution is an optional integration wired by dependency injection (`FeatureTenantHierarchyProvider`), not a static dependency — `smrt-features` never imports `@happyvertical/smrt-users`. Without a configured provider, tenant-hierarchy resolution falls back to a flat tenant scope. (The consumer that wants hierarchy installs `smrt-users` itself and supplies the provider; `smrt-users` is a dev-only dependency here for tests.)
26
29
 
30
+ ## Management UI (`./svelte`)
31
+
32
+ - Built like every other Svelte-shipping package here (`smrt-tenancy`/`smrt-personas` are the closest models): `vite build --mode library && svelte-package -i src/svelte -o dist/svelte`, an `./svelte` export with a `svelte` condition, `svelte` as an **optional** peer dependency — plus `scripts/prune-svelte-package-artifacts.js` (as `smrt-content` does), because `svelte-package` otherwise copies `__tests__/` and `*.test.*` into the published `dist/svelte`. The root export stays Svelte-free so server-only consumers are unaffected.
33
+ - `FeatureSettingsPanel` is presentational: rows and callbacks in, no fetching, no authorization. `src/svelte/types.ts` deliberately imports nothing from `smrt-core` or the `@smrt()` models, so the browser bundle stays thin; `FeatureSettingsRow` is structurally assignable to `FeatureSettingsView`.
34
+ - Each row is its own `<form>`, so `formAction` gives a working screen with no client JS; `onSave` intercepts the submit instead and reads the row's values straight off the form. The global column is opt-in (`showGlobal` / `globalEditable`) because most apps have no platform-admin tier.
35
+ - Two invariants the selects have to keep, both regression-tested. (1) The tenant column's *Default* option must never name the code default: the tenant scope inherits the global override and, with a `FeatureTenantHierarchyProvider`, ancestor tenants' overrides too, so `defaultEnabled` would tell an operator the opposite of what choosing it does. `inheritedOptionLabel()` names `FeatureSettingsView.inheritedEnabled` when the row carries it and says "Default (inherited)" otherwise; `FeatureSettingsService` sets it only where it is exactly knowable (no tenant override → inherited is the effective state), never derived from `globalEffect`. The global column does name the code default — nothing sits above global. (2) The rows remount on `contextKey ?? features`, so an unsaved choice cannot survive a reload or a tenant switch and be submitted against the new rows.
36
+ - Writes validate the **scope shape** before the authorizer runs: `scopeType` must be `global` or `tenant`, a global write must use `GLOBAL_FEATURE_SCOPE_ID`, and a tenant id must be non-blank and already trimmed. A host authorizer cannot catch this — `scopeType: 'global'` with someone else's tenant id reads as an ordinary global write to an authorizer that only gates the global scope — and the resolver looks rows up by the exact `(featureKey, scopeType, scopeId)` triple, so a row written elsewhere is invisible state, not a setting.
37
+ - **Authorization stays with the consumer.** `FeatureSettingsService` reads are unfiltered and every write goes through `FeatureOverrideService`; constructing the settings service without an `authorize` option makes it read-only (writes fail closed). Never add an authorization decision to this package — hosts differ, and the generated surfaces are closed precisely so that decision has to be theirs.
38
+ - `vitest.config.ts` runs two projects because the halves need incompatible resolution: `server` (node, `smrtVitestPlugin`) and `svelte` (jsdom, `@sveltejs/vite-plugin-svelte`, `conditions: ['browser']`). The browser condition would send `smrt-core` to its browser build, so it must not be global.
39
+
40
+ ## Feature keys are persisted identifiers
41
+
42
+ Overrides are stored keyed by the exact `featureKey` string and the resolver looks them up by that string. **Renaming a key orphans every existing override and silently reverts all tenants to the definition default.** Treat a shipped key as immutable; a rename needs a data migration over `_smrt_feature_overrides.feature_key`, not an edit. Renaming the owning package is the usual trigger — keep the old key namespace and let `packageName` track the new package.
43
+
27
44
  ## Integration with `@happyvertical/smrt-users`
28
45
 
29
46
  When `smrt-users` is present, `FeatureResolver` uses it to walk the tenant hierarchy (parent → grandparent → root). Configure via `FeatureTenantHierarchyProvider`. Without it, tenant lookup is single-level.
package/README.md CHANGED
@@ -70,6 +70,158 @@ const service = new FeatureOverrideService(overrides, (request) =>
70
70
  await service.setOverride(key, 'tenant', session.tenantId, FeatureOverrideEffect.ENABLE);
71
71
  ```
72
72
 
73
+ ## Management UI
74
+
75
+ The package ships the settings screen so consuming apps do not each rebuild
76
+ it: `FeatureSettingsService` (root export) produces the rows, and
77
+ `FeatureSettingsPanel` (the `./svelte` subpath) renders them. The root export
78
+ stays Svelte-free, so a server-only consumer is unaffected; `svelte` is an
79
+ optional peer dependency.
80
+
81
+ ### Consumer recipe
82
+
83
+ **1. Declare the definitions in code.** Feature toggles are declared on the
84
+ `@smrt()` decorator of the class they belong to:
85
+
86
+ ```typescript
87
+ @smrt({
88
+ features: {
89
+ drafts: { defaultEnabled: false, label: 'Invoice drafts', description: 'Save invoices as drafts before sending.' },
90
+ },
91
+ })
92
+ export class Invoice extends SmrtObject {}
93
+ ```
94
+
95
+ **2. Sync them at startup**, so `_smrt_feature_definitions` matches code:
96
+
97
+ ```typescript
98
+ await new FeatureSyncService(dbOptions).syncDefinitions();
99
+ ```
100
+
101
+ **3. Load the rows in your server load.** The load is where *your*
102
+ permission check lives — the package makes no authorization decision:
103
+
104
+ ```typescript
105
+ // src/routes/settings/features/+page.server.ts
106
+ export async function load(event) {
107
+ const auth = requirePermission(event, APP_PERMISSIONS.tenantAdmin);
108
+ const settings = await FeatureSettingsService.create(dbOptions);
109
+ return {
110
+ tenantId: auth.tenantId,
111
+ features: await settings.listFeatureSettings({
112
+ tenantId: auth.tenantId,
113
+ packageName: '@your/app',
114
+ }),
115
+ };
116
+ }
117
+ ```
118
+
119
+ `listFeatureSettings()` returns one `FeatureSettingsRow` per definition —
120
+ the definition fields, `effectiveEnabled` as `FeatureResolver` resolves it
121
+ for that tenant, and `globalEffect` / `tenantEffect` for the raw override row
122
+ at each level. The row is structurally assignable to the panel's
123
+ `FeatureSettingsView`, so no mapping step is needed.
124
+
125
+ **4. Render the panel:**
126
+
127
+ ```svelte
128
+ <script lang="ts">
129
+ import { FeatureSettingsPanel } from '@happyvertical/smrt-features/svelte';
130
+ let { data, form } = $props();
131
+ </script>
132
+
133
+ <FeatureSettingsPanel
134
+ features={data.features}
135
+ tenantLabel="Shop"
136
+ contextKey={data.tenantId}
137
+ formAction="?/saveFeature"
138
+ message={form?.message}
139
+ error={form?.error}
140
+ />
141
+ ```
142
+
143
+ The panel is presentational: it takes rows and callbacks, fetches nothing and
144
+ authorizes nothing. Each row renders label, description, key, effective state
145
+ and a **Default / Enable / Disable** control, where *Default* removes the
146
+ override row and returns the feature to whatever the level above resolves to.
147
+ Pass `showGlobal` (and `globalEditable`) only if your app has a platform-admin
148
+ tier; apps without one omit the column entirely. Supply `onSave` instead of
149
+ `formAction` to drive it from a client handler.
150
+
151
+ Two details worth knowing:
152
+
153
+ - The tenant column's *Default* option never names the code default, because
154
+ the tenant scope inherits — from the global override, and, when a
155
+ `FeatureTenantHierarchyProvider` is configured, from ancestor tenants too. It
156
+ names the inherited state when the row carries `inheritedEnabled` (the helper
157
+ sets it whenever it is exactly knowable, i.e. when the tenant has no override
158
+ of its own) and otherwise reads "Default (inherited)". The global column's
159
+ *Default* does name the code default, because the global scope inherits
160
+ nothing.
161
+ - Unsaved edits are discarded whenever `contextKey` changes, or, when you omit
162
+ it, whenever `features` is replaced with a new array. Pass the tenant id as
163
+ `contextKey` so a half-made choice for one tenant can never be submitted
164
+ against another's rows.
165
+
166
+ **5. Wire the write with your own permission check.** `setFeatureOverride()`
167
+ refuses any key without a definition and any scope the resolver would never
168
+ read back — an unknown `scopeType`, a global write under anything but
169
+ `GLOBAL_FEATURE_SCOPE_ID`, or a blank/untrimmed tenant id — then delegates to
170
+ `FeatureOverrideService`, which asks your authorizer and fails closed:
171
+
172
+ ```typescript
173
+ export const actions = {
174
+ saveFeature: async (event) => {
175
+ const auth = requirePermission(event, APP_PERMISSIONS.tenantAdmin);
176
+ const form = await event.request.formData();
177
+ const settings = await FeatureSettingsService.create(dbOptions, {
178
+ // Your decision, bound to this caller. Returning anything but `true` denies.
179
+ authorize: (request) =>
180
+ request.scopeType === 'tenant' && request.scopeId === auth.tenantId,
181
+ });
182
+
183
+ try {
184
+ await settings.setTenantFeatureOverride(
185
+ String(form.get('featureKey') ?? ''),
186
+ auth.tenantId,
187
+ featureOverrideEffectFromValue(form.get('tenantEffect')),
188
+ );
189
+ } catch (error) {
190
+ if (error instanceof UnknownFeatureKeyError) return fail(400, { error: 'Unknown feature.' });
191
+ if (error instanceof InvalidFeatureScopeError) return fail(400, { error: 'Invalid scope.' });
192
+ if (error instanceof FeatureOverrideAuthorizationError) return fail(403, { error: 'Not permitted.' });
193
+ throw error;
194
+ }
195
+ return { message: 'Feature overrides saved.' };
196
+ },
197
+ };
198
+ ```
199
+
200
+ Constructing the service **without** an `authorize` option makes it
201
+ read-only: every write then fails closed with
202
+ `FeatureOverrideAuthorizationError`. That is the right shape for a load
203
+ function.
204
+
205
+ ### What the consumer still owns
206
+
207
+ - the route, its navigation entry, and the page shell;
208
+ - the permission check on both the load and the action;
209
+ - the `authorize` callback (this package never decides who may write);
210
+ - resolving the session's `tenantId` — a tenant id from the URL or a form
211
+ field is a selector, not authorization;
212
+ - calling `FeatureSyncService` at startup.
213
+
214
+ ### Feature keys are persisted identifiers
215
+
216
+ Overrides are stored keyed by the exact `featureKey` string and resolved by
217
+ that string at runtime. **Renaming a key orphans every existing override**,
218
+ silently reverting all tenants to the definition default — the change looks
219
+ harmless in a diff and shows up as flags mysteriously resetting. Treat a key
220
+ as immutable once shipped. A rename needs a data migration that rewrites
221
+ `_smrt_feature_overrides.feature_key`, not an edit. (Renaming the owning
222
+ package is the usual way this happens: keep the old key namespace and let
223
+ `packageName` track the new package.)
224
+
73
225
  ## Documentation
74
226
 
75
227
  - See [`AGENTS.md`](./AGENTS.md) for package-internal patterns
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=__smrt-register__.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__smrt-register__.d.ts","sourceRoot":"","sources":["../src/__smrt-register__.ts"],"names":[],"mappings":""}
@@ -0,0 +1,18 @@
1
+ import { SmrtObject } from '@happyvertical/smrt-core';
2
+ import { FeatureDefinitionOptions, FeatureMetadata } from './types.js';
3
+ export declare class FeatureDefinition extends SmrtObject {
4
+ featureKey: string;
5
+ packageName: string;
6
+ qualifiedClassName: string;
7
+ className: string;
8
+ localId: string;
9
+ defaultEnabled: boolean;
10
+ label: string;
11
+ description: string;
12
+ metadata: string;
13
+ visibility: string;
14
+ constructor(options?: FeatureDefinitionOptions);
15
+ getMetadata(): FeatureMetadata;
16
+ setMetadata(metadata: FeatureMetadata | null): void;
17
+ }
18
+ //# sourceMappingURL=feature-definition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-definition.d.ts","sourceRoot":"","sources":["../src/feature-definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG5E,qBASa,iBAAkB,SAAQ,UAAU;IAC/C,UAAU,EAAE,MAAM,CAAM;IACxB,WAAW,EAAE,MAAM,CAAM;IACzB,kBAAkB,EAAE,MAAM,CAAM;IAChC,SAAS,EAAE,MAAM,CAAM;IACvB,OAAO,EAAE,MAAM,CAAM;IACrB,cAAc,EAAE,OAAO,CAAS;IAChC,KAAK,EAAE,MAAM,CAAM;IACnB,WAAW,EAAE,MAAM,CAAM;IACzB,QAAQ,EAAE,MAAM,CAAM;IACtB,UAAU,EAAE,MAAM,CAAY;gBAElB,OAAO,GAAE,wBAA6B;IAuBlD,WAAW,IAAI,eAAe;IAI9B,WAAW,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,GAAG,IAAI;CAGpD"}
@@ -0,0 +1,13 @@
1
+ import { SmrtCollection } from '@happyvertical/smrt-core';
2
+ import { FeatureDefinition } from './feature-definition.js';
3
+ import { FeatureDefinitionSeed } from './types.js';
4
+ export declare class FeatureDefinitionCollection extends SmrtCollection<FeatureDefinition> {
5
+ static readonly _itemClass: typeof FeatureDefinition;
6
+ findByFeatureKey(featureKey: string): Promise<FeatureDefinition | null>;
7
+ findByPackageName(packageName: string): Promise<FeatureDefinition[]>;
8
+ upsertDefinition(seed: FeatureDefinitionSeed): Promise<{
9
+ definition: FeatureDefinition;
10
+ status: 'created' | 'updated' | 'unchanged';
11
+ }>;
12
+ }
13
+ //# sourceMappingURL=feature-definitions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-definitions.d.ts","sourceRoot":"","sources":["../src/feature-definitions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGxD,qBAAa,2BAA4B,SAAQ,cAAc,CAAC,iBAAiB,CAAC;IAChF,MAAM,CAAC,QAAQ,CAAC,UAAU,2BAAqB;IAEzC,gBAAgB,CACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAQ9B,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAMpE,gBAAgB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC;QAC3D,UAAU,EAAE,iBAAiB,CAAC;QAC9B,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;KAC7C,CAAC;CAyCH"}
@@ -0,0 +1,41 @@
1
+ import { FeatureOverride } from './feature-override.js';
2
+ import { FeatureOverrideCollection } from './feature-overrides.js';
3
+ import { FeatureOverrideEffect, FeatureScopeType } from './types.js';
4
+ /** One override write the host must authorize before it runs (#3013). */
5
+ export interface FeatureOverrideWriteRequest {
6
+ operation: 'set' | 'remove';
7
+ featureKey: string;
8
+ scopeType: FeatureScopeType;
9
+ scopeId: string;
10
+ /** Present for `set`. */
11
+ effect?: FeatureOverrideEffect;
12
+ }
13
+ /**
14
+ * The host's explicit authorization decision for one override write. Return
15
+ * `true` only when the current caller may write that exact scope; anything
16
+ * else denies.
17
+ */
18
+ export type FeatureOverrideAuthorizer = (request: FeatureOverrideWriteRequest) => boolean | Promise<boolean>;
19
+ /** Thrown when the authorizer does not return `true`. Nothing is written. */
20
+ export declare class FeatureOverrideAuthorizationError extends Error {
21
+ readonly request: FeatureOverrideWriteRequest;
22
+ readonly status = 403;
23
+ constructor(request: FeatureOverrideWriteRequest);
24
+ }
25
+ /**
26
+ * Authorized write path for feature overrides (#3013). Override rows are
27
+ * authorization state for every scope and `FeatureOverride` is not
28
+ * tenant-scoped, so generated surfaces are closed. Hosts that let callers
29
+ * change flags construct this service with an authorizer bound to the
30
+ * current caller and write through it; every write asks the authorizer
31
+ * first and fails closed.
32
+ */
33
+ export declare class FeatureOverrideService {
34
+ private readonly overrides;
35
+ private readonly authorize;
36
+ constructor(overrides: FeatureOverrideCollection, authorize: FeatureOverrideAuthorizer);
37
+ setOverride(featureKey: string, scopeType: FeatureScopeType, scopeId: string, effect: FeatureOverrideEffect): Promise<FeatureOverride>;
38
+ removeOverride(featureKey: string, scopeType: FeatureScopeType, scopeId: string): Promise<boolean>;
39
+ private require;
40
+ }
41
+ //# sourceMappingURL=feature-override-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-override-service.d.ts","sourceRoot":"","sources":["../src/feature-override-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE1E,yEAAyE;AACzE,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,gBAAgB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB;IACzB,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG,CACtC,OAAO,EAAE,2BAA2B,KACjC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,6EAA6E;AAC7E,qBAAa,iCAAkC,SAAQ,KAAK;IAE9C,QAAQ,CAAC,OAAO,EAAE,2BAA2B;IADzD,QAAQ,CAAC,MAAM,OAAO;gBACD,OAAO,EAAE,2BAA2B;CAM1D;AAED;;;;;;;GAOG;AACH,qBAAa,sBAAsB;IAE/B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS;gBADT,SAAS,EAAE,yBAAyB,EACpC,SAAS,EAAE,yBAAyB;IAGjD,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,eAAe,CAAC;IAWrB,cAAc,CAClB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC;YAKL,OAAO;CAStB"}
@@ -0,0 +1,13 @@
1
+ import { SmrtObject } from '@happyvertical/smrt-core';
2
+ import { FeatureOverrideEffect, FeatureOverrideOptions, FeatureScopeType } from './types.js';
3
+ export declare class FeatureOverride extends SmrtObject {
4
+ featureKey: string;
5
+ scopeType: FeatureScopeType;
6
+ scopeId: string;
7
+ effect: FeatureOverrideEffect;
8
+ constructor(options?: FeatureOverrideOptions);
9
+ isInherit(): boolean;
10
+ isEnabled(): boolean;
11
+ isDisabled(): boolean;
12
+ }
13
+ //# sourceMappingURL=feature-override.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-override.d.ts","sourceRoot":"","sources":["../src/feature-override.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EACL,qBAAqB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EAEtB,MAAM,YAAY,CAAC;AAUpB,qBAOa,eAAgB,SAAQ,UAAU;IAC7C,UAAU,EAAE,MAAM,CAAM;IACxB,SAAS,EAAE,gBAAgB,CAAY;IACvC,OAAO,EAAE,MAAM,CAA2B;IAC1C,MAAM,EAAE,qBAAqB,CAAiC;gBAElD,OAAO,GAAE,sBAA2B;IAShD,SAAS,IAAI,OAAO;IAIpB,SAAS,IAAI,OAAO;IAIpB,UAAU,IAAI,OAAO;CAGtB"}
@@ -0,0 +1,16 @@
1
+ import { SmrtCollection } from '@happyvertical/smrt-core';
2
+ import { FeatureOverride } from './feature-override.js';
3
+ import { FeatureOverrideEffect, FeatureScopeType } from './types.js';
4
+ export declare class FeatureOverrideCollection extends SmrtCollection<FeatureOverride> {
5
+ static readonly _itemClass: typeof FeatureOverride;
6
+ findByFeatureKey(featureKey: string): Promise<FeatureOverride[]>;
7
+ findByFeatureAndScope(featureKey: string, scopeType: FeatureScopeType, scopeId: string): Promise<FeatureOverride | null>;
8
+ getGlobalOverride(featureKey: string): Promise<FeatureOverride | null>;
9
+ getTenantOverride(featureKey: string, tenantId: string): Promise<FeatureOverride | null>;
10
+ getOverrideMap(featureKey: string, scopeType: FeatureScopeType, scopeIds: string[]): Promise<Map<string, FeatureOverride>>;
11
+ setOverride(featureKey: string, scopeType: FeatureScopeType, scopeId: string, effect: FeatureOverrideEffect): Promise<FeatureOverride>;
12
+ setGlobalOverride(featureKey: string, effect: FeatureOverrideEffect): Promise<FeatureOverride>;
13
+ setTenantOverride(featureKey: string, tenantId: string, effect: FeatureOverrideEffect): Promise<FeatureOverride>;
14
+ removeOverride(featureKey: string, scopeType: FeatureScopeType, scopeId: string): Promise<boolean>;
15
+ }
16
+ //# sourceMappingURL=feature-overrides.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-overrides.d.ts","sourceRoot":"","sources":["../src/feature-overrides.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EAEtB,MAAM,YAAY,CAAC;AAEpB,qBAAa,yBAA0B,SAAQ,cAAc,CAAC,eAAe,CAAC;IAC5E,MAAM,CAAC,QAAQ,CAAC,UAAU,yBAAmB;IAEvC,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAMhE,qBAAqB,CACzB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAQ5B,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAQtE,iBAAiB,CACrB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAI5B,cAAc,CAClB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,gBAAgB,EAC3B,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAiBlC,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,eAAe,CAAC;IAuBrB,iBAAiB,CACrB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,eAAe,CAAC;IASrB,iBAAiB,CACrB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,eAAe,CAAC;IAIrB,cAAc,CAClB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC;CAcpB"}
@@ -0,0 +1,18 @@
1
+ import { SmrtClassOptions } from '@happyvertical/smrt-core';
2
+ import { FeatureResolutionContext, FeatureResolverOptions, FeatureTargetConstructor } from './types.js';
3
+ export declare class FeatureResolver {
4
+ private readonly options;
5
+ private readonly resolverOptions;
6
+ private featureDefinitions;
7
+ private featureOverrides;
8
+ private initializationPromise;
9
+ private tenantHierarchyPromise;
10
+ constructor(options?: SmrtClassOptions, resolverOptions?: FeatureResolverOptions);
11
+ isEnabled(featureKey: string, context?: FeatureResolutionContext): Promise<boolean>;
12
+ isEnabledFor(classOrInstance: object | FeatureTargetConstructor, localId: string, context?: FeatureResolutionContext): Promise<boolean>;
13
+ private ensureInitialized;
14
+ private resolveBaseState;
15
+ private applyOverride;
16
+ private getTenantHierarchy;
17
+ }
18
+ //# sourceMappingURL=feature-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-resolver.d.ts","sourceRoot":"","sources":["../src/feature-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAIjE,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAI9B,MAAM,YAAY,CAAC;AAMpB,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyB;IACzD,OAAO,CAAC,kBAAkB,CAA+B;IACzD,OAAO,CAAC,gBAAgB,CAA6B;IACrD,OAAO,CAAC,qBAAqB,CAA8B;IAC3D,OAAO,CAAC,sBAAsB,CACvB;gBAGL,OAAO,GAAE,gBAAqB,EAC9B,eAAe,GAAE,sBAA2B;IAMxC,SAAS,CACb,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,OAAO,CAAC;IAqDb,YAAY,CAChB,eAAe,EAAE,MAAM,GAAG,wBAAwB,EAClD,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,OAAO,CAAC;YAKL,iBAAiB;YAejB,gBAAgB;IAiB9B,OAAO,CAAC,aAAa;YAcP,kBAAkB;CAUjC"}
@@ -0,0 +1,209 @@
1
+ import { SmrtClassOptions } from '@happyvertical/smrt-core';
2
+ import { FeatureDefinitionCollection } from './feature-definitions.js';
3
+ import { FeatureOverride } from './feature-override.js';
4
+ import { FeatureOverrideAuthorizer } from './feature-override-service.js';
5
+ import { FeatureOverrideCollection } from './feature-overrides.js';
6
+ import { FeatureResolver } from './feature-resolver.js';
7
+ import { FeatureMetadata, FeatureOverrideEffect, FeatureResolverOptions, FeatureScopeType } from './types.js';
8
+ /**
9
+ * One feature, rendered for a management screen: the code-owned definition,
10
+ * the value that is actually in effect for the requested tenant, and the raw
11
+ * override row at each scope level so the UI can show *why* it is in effect.
12
+ *
13
+ * Structurally assignable to the presentational `FeatureSettingsView` consumed
14
+ * by `@happyvertical/smrt-features/svelte`, so a server load can hand its rows
15
+ * straight to `FeatureSettingsPanel` without a mapping step.
16
+ */
17
+ export interface FeatureSettingsRow {
18
+ /**
19
+ * The persisted identifier. Renaming it orphans every existing override —
20
+ * see the warning on {@link FeatureSettingsService}.
21
+ */
22
+ featureKey: string;
23
+ /** Human label from the definition; may be empty. */
24
+ label: string;
25
+ /** Human description from the definition; may be empty. */
26
+ description: string;
27
+ /** Owning package, as synced from the manifest. */
28
+ packageName: string;
29
+ /** Qualified `package:Class` name of the feature's target. */
30
+ qualifiedClassName: string;
31
+ /** Unqualified class name of the feature's target. */
32
+ className: string;
33
+ /** Class-local feature id. */
34
+ localId: string;
35
+ /** Definition visibility marker (`public` unless the seed says otherwise). */
36
+ visibility: string;
37
+ /** Parsed definition metadata. */
38
+ metadata: FeatureMetadata;
39
+ /** The code-owned default, used when no override applies. */
40
+ defaultEnabled: boolean;
41
+ /**
42
+ * What {@link FeatureResolver} resolves for the requested context — the
43
+ * value the application actually behaves by.
44
+ */
45
+ effectiveEnabled: boolean;
46
+ /** Effect of the global override row, or `null` when there is none. */
47
+ globalEffect: FeatureOverrideEffect | null;
48
+ /**
49
+ * Effect of the override row for the requested tenant, or `null` when there
50
+ * is none (or when no `tenantId` was requested).
51
+ */
52
+ tenantEffect: FeatureOverrideEffect | null;
53
+ /**
54
+ * What this feature resolves to for the requested tenant with **no override
55
+ * of its own** — the state that removing the tenant override would produce,
56
+ * which is what a management UI must name on its "back to Default" control.
57
+ *
58
+ * `null` when no `tenantId` was requested — nothing tenant-inherited was
59
+ * computed, and the global state is not an answer to a question about a
60
+ * tenant — and `null` when the tenant holds an override of its own, because
61
+ * then the inherited state depends on the levels above this tenant, which a
62
+ * configured `FeatureTenantHierarchyProvider` extends to ancestor tenants'
63
+ * overrides. It is reported only for a requested tenant with no override of
64
+ * its own, where the inherited state *is* the effective state. Rows that
65
+ * leave it `null` get the neutral "Default (inherited)" label in the panel,
66
+ * never a claim about a state nothing computed.
67
+ */
68
+ inheritedEnabled: boolean | null;
69
+ }
70
+ /** Selection for {@link FeatureSettingsService.listFeatureSettings}. */
71
+ export interface ListFeatureSettingsOptions {
72
+ /**
73
+ * Tenant whose overrides and effective values to report. Omit for a
74
+ * global-only view.
75
+ */
76
+ tenantId?: string;
77
+ /** Restrict to one owning package (the usual case for an app's own screen). */
78
+ packageName?: string;
79
+ /**
80
+ * Restrict to an explicit allowlist of keys. Keys with no definition are
81
+ * simply absent from the result — this filters, it does not validate.
82
+ */
83
+ featureKeys?: string[];
84
+ }
85
+ /** One override write requested through {@link FeatureSettingsService}. */
86
+ export interface SetFeatureOverrideRequest {
87
+ featureKey: string;
88
+ scopeType: FeatureScopeType;
89
+ scopeId: string;
90
+ /**
91
+ * `INHERIT` removes the override row at that scope, returning the feature to
92
+ * whatever the next level up resolves to.
93
+ */
94
+ effect: FeatureOverrideEffect;
95
+ }
96
+ /**
97
+ * Thrown when a write names a `featureKey` that has no {@link FeatureDefinition}
98
+ * row. Nothing is written.
99
+ *
100
+ * This is the generalized form of the per-app "is this one of my keys?" guard
101
+ * every consumer would otherwise hand-roll to keep a form post from creating an
102
+ * override for an arbitrary string.
103
+ */
104
+ export declare class UnknownFeatureKeyError extends Error {
105
+ readonly featureKey: string;
106
+ readonly status = 400;
107
+ constructor(featureKey: string);
108
+ }
109
+ /**
110
+ * Thrown when a write names a scope the resolver would never read back: an
111
+ * unknown `scopeType`, a global write under anything but
112
+ * {@link GLOBAL_FEATURE_SCOPE_ID}, or a tenant write with a blank or untrimmed
113
+ * `scopeId`. Nothing is written.
114
+ *
115
+ * `FeatureOverride` rows are looked up by the exact `(featureKey, scopeType,
116
+ * scopeId)` triple, so a row stored under a scope nothing queries is invisible
117
+ * state: it never takes effect, and it silently reappears if the scope naming
118
+ * is ever corrected.
119
+ */
120
+ export declare class InvalidFeatureScopeError extends Error {
121
+ readonly scopeType: string;
122
+ readonly scopeId: string;
123
+ readonly status = 400;
124
+ constructor(scopeType: string, scopeId: string, reason: string);
125
+ }
126
+ /** Options for {@link FeatureSettingsService.create}. */
127
+ export interface FeatureSettingsServiceOptions {
128
+ /**
129
+ * The host's authorization decision for each override write, bound to the
130
+ * current caller. **Omitting it makes the service read-only**: every write
131
+ * then fails closed with `FeatureOverrideAuthorizationError`. This
132
+ * package never decides who may write.
133
+ */
134
+ authorize?: FeatureOverrideAuthorizer;
135
+ /** Forwarded to {@link FeatureResolver} (e.g. the tenant-hierarchy loader). */
136
+ resolver?: FeatureResolverOptions;
137
+ }
138
+ /**
139
+ * Coerce an untrusted value (a form field, a query parameter, a JSON body) to a
140
+ * {@link FeatureOverrideEffect}. Anything unrecognized — including `null` and
141
+ * `undefined` — becomes `INHERIT`, i.e. "back to the default", which is the
142
+ * safe reading of a value the host could not parse.
143
+ */
144
+ export declare function featureOverrideEffectFromValue(value: unknown): FeatureOverrideEffect;
145
+ /**
146
+ * Read and write side of a feature-flag management screen, so consumers do not
147
+ * re-derive the row shape or the unknown-key guard.
148
+ *
149
+ * **Authorization stays with the consumer.** This service makes no
150
+ * authorization decision of its own: reads are unfiltered, and every write is
151
+ * delegated to {@link FeatureOverrideService}, which asks the host-supplied
152
+ * {@link FeatureOverrideAuthorizer} first and fails closed. Call it only from
153
+ * server code that has already established who the caller is. See
154
+ * `packages/features/README.md` for the consumer recipe.
155
+ *
156
+ * **Feature keys are persisted identifiers.** Overrides are stored keyed by the
157
+ * exact `featureKey` string and resolved by that string at runtime. Renaming a
158
+ * key orphans every existing override, silently reverting all tenants to the
159
+ * definition default. Treat a key as immutable once shipped; a rename needs a
160
+ * data migration, not an edit.
161
+ */
162
+ export declare class FeatureSettingsService {
163
+ private readonly definitions;
164
+ private readonly overrides;
165
+ private readonly resolver;
166
+ private readonly writes;
167
+ constructor(definitions: FeatureDefinitionCollection, overrides: FeatureOverrideCollection, resolver: FeatureResolver, authorize?: FeatureOverrideAuthorizer);
168
+ /**
169
+ * Build a service over the given database options.
170
+ *
171
+ * @param options - SMRT database options, as passed to any collection.
172
+ * @param serviceOptions - Host authorizer and resolver wiring. Omit
173
+ * `authorize` for a read-only (load-function) service.
174
+ */
175
+ static create(options?: SmrtClassOptions, serviceOptions?: FeatureSettingsServiceOptions): Promise<FeatureSettingsService>;
176
+ /**
177
+ * One {@link FeatureSettingsRow} per matching definition, ordered by owning
178
+ * package, then label, then key, so a rendered list is stable across loads.
179
+ *
180
+ * Makes no authorization decision — the caller has already decided that this
181
+ * principal may see this tenant's flags.
182
+ */
183
+ listFeatureSettings(options?: ListFeatureSettingsOptions): Promise<FeatureSettingsRow[]>;
184
+ /** The single row for one key, or `null` when no definition owns that key. */
185
+ getFeatureSetting(featureKey: string, tenantId?: string): Promise<FeatureSettingsRow | null>;
186
+ /**
187
+ * Apply one override change.
188
+ *
189
+ * Refuses any `featureKey` without a {@link FeatureDefinition} row and any
190
+ * scope the resolver would never read back, then delegates to
191
+ * {@link FeatureOverrideService} so the host authorizer decides whether this
192
+ * caller may write this scope. `INHERIT` removes the row.
193
+ *
194
+ * @returns the stored override, or `null` when the row was removed (or was
195
+ * already absent).
196
+ * @throws {UnknownFeatureKeyError} when the key has no definition.
197
+ * @throws {InvalidFeatureScopeError} when the scope is not one the resolver reads.
198
+ * @throws `FeatureOverrideAuthorizationError` when the authorizer declines.
199
+ */
200
+ setFeatureOverride(request: SetFeatureOverrideRequest): Promise<FeatureOverride | null>;
201
+ /** {@link setFeatureOverride} for the global scope. */
202
+ setGlobalFeatureOverride(featureKey: string, effect: FeatureOverrideEffect): Promise<FeatureOverride | null>;
203
+ /** {@link setFeatureOverride} for one tenant. */
204
+ setTenantFeatureOverride(featureKey: string, tenantId: string, effect: FeatureOverrideEffect): Promise<FeatureOverride | null>;
205
+ private assertKnownFeatureKey;
206
+ private loadDefinitions;
207
+ private toRow;
208
+ }
209
+ //# sourceMappingURL=feature-settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-settings.d.ts","sourceRoot":"","sources":["../src/feature-settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { SmrtClassOptions } from '@happyvertical/smrt-core';
2
+ import { SmartObjectManifest } from '@happyvertical/smrt-core/manifest';
3
+ import { FeatureSyncResult, SyncDefinitionsOptions, SyncManifestOptions } from './types.js';
4
+ export declare class FeatureSyncService {
5
+ private readonly options;
6
+ private featureDefinitions;
7
+ private initializationPromise;
8
+ constructor(options?: SmrtClassOptions);
9
+ syncDefinitions(options?: SyncDefinitionsOptions): Promise<FeatureSyncResult>;
10
+ syncManifest(manifest: SmartObjectManifest, options?: SyncManifestOptions): Promise<FeatureSyncResult>;
11
+ private ensureInitialized;
12
+ private collectDefinitionsFromRegistrations;
13
+ private collectTouchedPackagesFromRegistrations;
14
+ private collectRegistrations;
15
+ private applyDefinitions;
16
+ }
17
+ //# sourceMappingURL=feature-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-sync.d.ts","sourceRoot":"","sources":["../src/feature-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAE7E,OAAO,KAAK,EAEV,iBAAiB,EACjB,sBAAsB,EACtB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AASpB,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,kBAAkB,CAA+B;IACzD,OAAO,CAAC,qBAAqB,CAA8B;gBAE/C,OAAO,GAAE,gBAAqB;IAIpC,eAAe,CACnB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,iBAAiB,CAAC;IAcvB,YAAY,CAChB,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,iBAAiB,CAAC;YAWf,iBAAiB;IAY/B,OAAO,CAAC,mCAAmC;IAY3C,OAAO,CAAC,uCAAuC;IAmB/C,OAAO,CAAC,oBAAoB;YA6Bd,gBAAgB;CA0D/B"}