@happyvertical/smrt-fields 0.40.60 → 0.40.62
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/AGENTS.md +58 -12
- package/README.md +143 -42
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/cache.d.ts +18 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/chunks/FieldPolicyCollection--RxukfCX.js +1218 -0
- package/dist/chunks/FieldPolicyCollection--RxukfCX.js.map +1 -0
- package/dist/collections/FieldPolicyCollection.d.ts +95 -0
- package/dist/collections/FieldPolicyCollection.d.ts.map +1 -0
- package/dist/collections/FieldPolicySuggestionCollection.d.ts +160 -0
- package/dist/collections/FieldPolicySuggestionCollection.d.ts.map +1 -0
- package/dist/collections/FieldUsageCounterCollection.d.ts +143 -0
- package/dist/collections/FieldUsageCounterCollection.d.ts.map +1 -0
- package/dist/deterministic-id.d.ts +18 -0
- package/dist/deterministic-id.d.ts.map +1 -0
- package/dist/field-definitions.d.ts +85 -0
- package/dist/field-definitions.d.ts.map +1 -0
- package/dist/field-policy-resolver.d.ts +46 -0
- package/dist/field-policy-resolver.d.ts.map +1 -0
- package/dist/index.d.ts +15 -532
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1813 -628
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1487 -191
- package/dist/models/FieldPolicy.d.ts +163 -0
- package/dist/models/FieldPolicy.d.ts.map +1 -0
- package/dist/models/FieldPolicySuggestion.d.ts +132 -0
- package/dist/models/FieldPolicySuggestion.d.ts.map +1 -0
- package/dist/models/FieldUsageCounter.d.ts +178 -0
- package/dist/models/FieldUsageCounter.d.ts.map +1 -0
- package/dist/models/FieldUsageReportReceipt.d.ts +25 -0
- package/dist/models/FieldUsageReportReceipt.d.ts.map +1 -0
- package/dist/permissions.d.ts +9 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/playground.d.ts +2 -0
- package/dist/playground.d.ts.map +1 -0
- package/dist/playground.js +39 -0
- package/dist/playground.js.map +1 -0
- package/dist/settings-catalog.d.ts +73 -0
- package/dist/settings-catalog.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +449 -9
- package/dist/svelte/__tests__/FieldPolicyControlPanel.test.js +277 -0
- package/dist/svelte/__tests__/FieldPolicyGear.test.js +503 -0
- package/dist/svelte/__tests__/FormHelp.test.js +138 -0
- package/dist/svelte/__tests__/ModeUX.test.js +109 -0
- package/dist/svelte/__tests__/ObjectForm.test.js +687 -0
- package/dist/svelte/__tests__/ObjectFormGeneratedApi.integration.test.js +436 -0
- package/dist/svelte/__tests__/PolicyField.test.js +373 -0
- package/dist/svelte/__tests__/UsageLearning.test.js +140 -0
- package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte +30 -0
- package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte.d.ts +9 -0
- package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte +33 -0
- package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte.d.ts +10 -0
- package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte +19 -0
- package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte.d.ts +10 -0
- package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte +18 -0
- package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte.d.ts +11 -0
- package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte +11 -0
- package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte.d.ts +19 -0
- package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte +18 -0
- package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte.d.ts +8 -0
- package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte +24 -0
- package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte.d.ts +13 -0
- package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte +37 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts +15 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte +17 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte.d.ts +10 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte +25 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte.d.ts +10 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte +34 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte.d.ts +10 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte +29 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte.d.ts +11 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte +33 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte.d.ts +9 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte +40 -0
- package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte.d.ts +8 -0
- package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte +26 -0
- package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte.d.ts +13 -0
- package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte +39 -0
- package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte.d.ts +17 -0
- package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/RegistryInput.svelte +15 -0
- package/dist/svelte/__tests__/fixtures/RegistryInput.svelte.d.ts +5 -0
- package/dist/svelte/__tests__/fixtures/RegistryInput.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte +29 -0
- package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte.d.ts +14 -0
- package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte +24 -0
- package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte.d.ts +14 -0
- package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte +28 -0
- package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte.d.ts +13 -0
- package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte +23 -0
- package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte.d.ts +10 -0
- package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte +14 -0
- package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte.d.ts +19 -0
- package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte +25 -0
- package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte.d.ts +14 -0
- package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/settings-catalog.test.js +161 -0
- package/dist/svelte/components/AdvancedFields.svelte +49 -0
- package/dist/svelte/components/AdvancedFields.svelte.d.ts +17 -0
- package/dist/svelte/components/AdvancedFields.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldInput.svelte +159 -0
- package/dist/svelte/components/FieldInput.svelte.d.ts +5 -0
- package/dist/svelte/components/FieldInput.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldPolicyControlPanel.svelte +288 -0
- package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts +29 -0
- package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldPolicyEditor.svelte +417 -0
- package/dist/svelte/components/FieldPolicyEditor.svelte.d.ts +18 -0
- package/dist/svelte/components/FieldPolicyEditor.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldPolicyGearButton.svelte +24 -0
- package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts +8 -0
- package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldPolicyGearProvider.svelte +169 -0
- package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts +20 -0
- package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldPolicyProvider.svelte +89 -0
- package/dist/svelte/components/FieldPolicyProvider.svelte.d.ts +35 -0
- package/dist/svelte/components/FieldPolicyProvider.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldPolicySuggestionQueue.svelte +135 -0
- package/dist/svelte/components/FieldPolicySuggestionQueue.svelte.d.ts +11 -0
- package/dist/svelte/components/FieldPolicySuggestionQueue.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FormHelp.svelte +157 -0
- package/dist/svelte/components/FormHelp.svelte.d.ts +15 -0
- package/dist/svelte/components/FormHelp.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ModeSwitch.svelte +47 -0
- package/dist/svelte/components/ModeSwitch.svelte.d.ts +10 -0
- package/dist/svelte/components/ModeSwitch.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ObjectForm.svelte +440 -0
- package/dist/svelte/components/ObjectForm.svelte.d.ts +50 -0
- package/dist/svelte/components/ObjectForm.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ObjectFormSourceProvider.svelte +18 -0
- package/dist/svelte/components/ObjectFormSourceProvider.svelte.d.ts +11 -0
- package/dist/svelte/components/ObjectFormSourceProvider.svelte.d.ts.map +1 -0
- package/dist/svelte/components/PolicyField.svelte +338 -0
- package/dist/svelte/components/PolicyField.svelte.d.ts +80 -0
- package/dist/svelte/components/PolicyField.svelte.d.ts.map +1 -0
- package/dist/svelte/context.svelte.d.ts +54 -0
- package/dist/svelte/context.svelte.d.ts.map +1 -0
- package/dist/svelte/context.svelte.js +55 -0
- package/dist/svelte/data-table.d.ts +17 -0
- package/dist/svelte/data-table.d.ts.map +1 -0
- package/dist/svelte/data-table.js +19 -0
- package/dist/svelte/field-policy-editor.d.ts +81 -0
- package/dist/svelte/field-policy-editor.d.ts.map +1 -0
- package/dist/svelte/field-policy-editor.js +120 -0
- package/dist/svelte/gear-context.svelte.d.ts +16 -0
- package/dist/svelte/gear-context.svelte.d.ts.map +1 -0
- package/dist/svelte/gear-context.svelte.js +15 -0
- package/dist/svelte/index.d.ts +63 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +41 -0
- package/dist/svelte/input-registry.d.ts +21 -0
- package/dist/svelte/input-registry.d.ts.map +1 -0
- package/dist/svelte/input-registry.js +26 -0
- package/dist/svelte/object-form-source-context.svelte.d.ts +4 -0
- package/dist/svelte/object-form-source-context.svelte.d.ts.map +1 -0
- package/dist/svelte/object-form-source-context.svelte.js +8 -0
- package/dist/svelte/object-form-source.svelte.d.ts +18 -0
- package/dist/svelte/object-form-source.svelte.d.ts.map +1 -0
- package/dist/svelte/object-form-source.svelte.js +99 -0
- package/dist/svelte/object-form.d.ts +6 -0
- package/dist/svelte/object-form.d.ts.map +1 -0
- package/dist/svelte/object-form.js +38 -0
- package/dist/svelte/playground/FieldPolicyFormPreview.svelte +212 -0
- package/dist/svelte/playground/FieldPolicyFormPreview.svelte.d.ts +4 -0
- package/dist/svelte/playground/FieldPolicyFormPreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground/ObjectFormPreview.svelte +129 -0
- package/dist/svelte/playground/ObjectFormPreview.svelte.d.ts +4 -0
- package/dist/svelte/playground/ObjectFormPreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground.d.ts +20 -0
- package/dist/svelte/playground.d.ts.map +1 -0
- package/dist/svelte/playground.js +38 -0
- package/dist/svelte/settings-catalog.d.ts +54 -0
- package/dist/svelte/settings-catalog.d.ts.map +1 -0
- package/dist/svelte/settings-catalog.js +116 -0
- package/dist/svelte/suggestions.d.ts +38 -0
- package/dist/svelte/suggestions.d.ts.map +1 -0
- package/dist/svelte/suggestions.js +68 -0
- package/dist/svelte/types.d.ts +101 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +4 -0
- package/dist/svelte/usage-capture.d.ts +63 -0
- package/dist/svelte/usage-capture.d.ts.map +1 -0
- package/dist/svelte/usage-capture.js +61 -0
- package/dist/types.d.ts +316 -203
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -1
- package/dist/types.js.map +1 -1
- package/dist/usage-learning.d.ts +201 -0
- package/dist/usage-learning.d.ts.map +1 -0
- package/dist/usage-schedules.d.ts +110 -0
- package/dist/usage-schedules.d.ts.map +1 -0
- package/dist/users-module.d.ts +30 -0
- package/dist/users-module.d.ts.map +1 -0
- package/package.json +33 -8
package/AGENTS.md
CHANGED
|
@@ -36,9 +36,11 @@ per-field `{defaultValue, visibility, help, label, order, locked}` for any
|
|
|
36
36
|
2. App rows — `scopeType: 'app'`, `tenantId`/`userId` null
|
|
37
37
|
3. Tenant rows — hierarchy walk root → leaf via an injected
|
|
38
38
|
`tenantHierarchyLoader` (smrt-features shape); the default loader
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
uses `@happyvertical/smrt-users`' `TenantCollection`; an injected loader
|
|
40
|
+
may return no provider (or no chain for a tenant), which falls back to a
|
|
41
|
+
flat single-tenant chain. `smrt-users` itself is a required Fields runtime
|
|
42
|
+
dependency for policy authorization, not an optional hierarchy dependency.
|
|
43
|
+
A node that breaks permission
|
|
42
44
|
inheritance discards every earlier tenant contribution (chain-structural),
|
|
43
45
|
so only the suffix from the LAST break participates — in merging AND in
|
|
44
46
|
the explained layers, which therefore replay to the merged result
|
|
@@ -75,9 +77,11 @@ per-field `{defaultValue, visibility, help, label, order, locked}` for any
|
|
|
75
77
|
- **Write-time org checks reuse the resolver**: the required-demotion default
|
|
76
78
|
and the user-write lock are computed by `resolveFieldPolicy` over the org
|
|
77
79
|
tiers (default hierarchy loader), so cascading ancestor-tenant defaults and
|
|
78
|
-
locks are honored at save time too. On updates the
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
locks are honored at save time too. On updates the persisted row is excluded
|
|
81
|
+
from projected lower-layer resolution, so clearing the row's only default
|
|
82
|
+
while demoting a required field is rejected before mutation; that projected
|
|
83
|
+
lookup bypasses the shared cache. The resolver-side safety net remains
|
|
84
|
+
authoritative when a different row is later deleted.
|
|
81
85
|
- **Isolation — a MISSING identity component DENIES, it never skips.** This
|
|
82
86
|
is the package rule; both the write guard
|
|
83
87
|
(`FieldPolicy.assertScopeOwnedByAmbientContext`) and the read guard
|
|
@@ -88,12 +92,13 @@ per-field `{defaultValue, visibility, help, label, order, locked}` for any
|
|
|
88
92
|
`withTenant({ tenantId })`) may not touch the user tier at all. Skipping
|
|
89
93
|
that check instead of denying it was a live ownership bypass: user rows are
|
|
90
94
|
`tenantId: null` by design, so nothing else contains such a write. App-scope
|
|
91
|
-
writes inside a tenant context require super-admin bypass.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
writes inside a tenant context require super-admin bypass. `fields.policy.manage`
|
|
96
|
+
authorizes app/tenant policy mutations; `fields.policy.personalize` authorizes
|
|
97
|
+
only the caller's user tier (default-seeded for built-in roles). With NO ambient
|
|
98
|
+
identity at all (tenancy ALS never entered), policy writes/deletes fail closed;
|
|
99
|
+
trusted system and super-admin contexts retain their explicit bypasses.
|
|
100
|
+
Context-LESS *reads* stay allowed because `resolveFieldPolicy` is a trusted
|
|
101
|
+
server-side API. `save()`/`delete()`
|
|
97
102
|
on an existing row additionally authorize against the row's PERSISTED
|
|
98
103
|
scope, looked up by primary key AND — because a generated create always
|
|
99
104
|
mints a fresh UUID while the `conflictColumns` upsert still replaces the
|
|
@@ -171,6 +176,47 @@ per-field `{defaultValue, visibility, help, label, order, locked}` for any
|
|
|
171
176
|
(`_smrt_field_policies`), and both are pinned in
|
|
172
177
|
`generated-surfaces.test.ts`.
|
|
173
178
|
|
|
179
|
+
## Svelte generated forms (#2049)
|
|
180
|
+
|
|
181
|
+
- `ObjectForm` accepts direct generated `fields` plus a resolved `policy` for
|
|
182
|
+
SSR and explicit hosts, or accepts only `objectRef` beneath an
|
|
183
|
+
`ObjectFormSourceProvider`. `ObjectFormSourceRegistry` is per app and maps
|
|
184
|
+
canonical refs to generated web collection definitions before calling the
|
|
185
|
+
generated `resolveBatch` custom action. It uses structural types only: this
|
|
186
|
+
package must never import `smrt-web`.
|
|
187
|
+
- Generated custom-action clients resolve `Promise<any>`; validate their
|
|
188
|
+
`policies[objectRef]` response at the registry boundary and fail closed on a
|
|
189
|
+
missing/mismatched definition or policy. The component renders an accessible
|
|
190
|
+
loading state and alert rather than a partial form.
|
|
191
|
+
- Browser manifest types are `text`, `integer`, `decimal`, `boolean`,
|
|
192
|
+
`datetime`, `json`, `foreignKey`, and `crossPackageRef`; there is no `select`
|
|
193
|
+
wire type. Apps use the per-app `FieldInputRegistry.registerField` seam for
|
|
194
|
+
select-like widgets. Reference fields intentionally default to identifier
|
|
195
|
+
inputs unless an app supplies a chooser.
|
|
196
|
+
- `policyToVisibleColumnIds(policy, columns)` feeds smrt-ui `DataTable`'s
|
|
197
|
+
`visibleColumnIds`; it filters policy-hidden mapped fields, preserves unmapped
|
|
198
|
+
computed/action columns, and cannot reveal a static `column.hidden` column.
|
|
199
|
+
|
|
200
|
+
## Defaults control panel (#2050)
|
|
201
|
+
|
|
202
|
+
- `buildFieldPolicySettingsCatalog()` is the server-side, URL/GET-driven
|
|
203
|
+
catalog builder. It structurally targets `SettingsCatalog`, but this package
|
|
204
|
+
must not import `smrt-svelte`; hosts inject `SettingsCatalog` into
|
|
205
|
+
`FieldPolicyControlPanel` and retain their own route and transport adapters.
|
|
206
|
+
- `policyAudit` is the only routed organization roll-up. It requires
|
|
207
|
+
`fields.policy.manage`, returns only the caller tenant's editable rows and
|
|
208
|
+
read-only app summaries, and represents other users strictly as per-field
|
|
209
|
+
counts. It resolves only requested page refs; never pre-resolve the catalog.
|
|
210
|
+
- Display code/app/org values by replaying the explained resolver layers, not
|
|
211
|
+
by independently calculating precedence. Reset and drift prune are ordinary
|
|
212
|
+
model deletes, so existing scope/permission validation remains authoritative.
|
|
213
|
+
The panel asks for an explicit confirmation before either destructive action;
|
|
214
|
+
SSR hosts can inject that confirmation decision instead of relying on
|
|
215
|
+
`window.confirm`.
|
|
216
|
+
- `fieldPolicyControlPanelNavItem()` is a structural AdminShell tenant-nav
|
|
217
|
+
seam. It never imports `smrt-svelte` or `smrt-web`; the host supplies the
|
|
218
|
+
returned entry and must enforce its real route permission server-side.
|
|
219
|
+
|
|
174
220
|
## Related
|
|
175
221
|
|
|
176
222
|
- `@happyvertical/smrt-prompts` / `smrt-languages` / `smrt-features` — the
|
package/README.md
CHANGED
|
@@ -1,52 +1,153 @@
|
|
|
1
1
|
# @happyvertical/smrt-fields
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
`@happyvertical/smrt-fields` lets an application adjust an object's form
|
|
4
|
+
defaults, labels, help, order, visibility, and locks without changing the
|
|
5
|
+
object's source. Policies layer organization and personal choices over the
|
|
6
|
+
code-authored field definition.
|
|
7
|
+
|
|
8
|
+
For the complete application and operator guide, see the
|
|
9
|
+
[field policy guide](https://happyvertical.github.io/smrt/field-policies).
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add @happyvertical/smrt-fields
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The package includes `@happyvertical/smrt-users` because policy writes and
|
|
18
|
+
operator actions use its permission and tenant context services.
|
|
19
|
+
|
|
20
|
+
## Define a safe code seed
|
|
21
|
+
|
|
22
|
+
The decorated model remains the definition of a field. Use its description
|
|
23
|
+
and `ui` hints to supply a useful first form before any policy row exists.
|
|
7
24
|
|
|
8
25
|
```typescript
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
objectRef: '@happyvertical/smrt-content:Article',
|
|
20
|
-
fieldName: 'summary',
|
|
21
|
-
scopeType: 'tenant',
|
|
22
|
-
tenantId,
|
|
23
|
-
visibility: 'advanced',
|
|
24
|
-
defaultValue: JSON.stringify('TBD'),
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// Resolve the effective policy for a user in that tenant
|
|
28
|
-
const resolved = await resolveFieldPolicy(
|
|
29
|
-
'@happyvertical/smrt-content:Article',
|
|
30
|
-
{ tenantId, userId, db },
|
|
31
|
-
);
|
|
32
|
-
resolved.fields.summary.visibility; // 'advanced'
|
|
26
|
+
import { field, SmrtObject, smrt } from '@happyvertical/smrt-core';
|
|
27
|
+
|
|
28
|
+
@smrt({ packageName: '@acme/billing' })
|
|
29
|
+
export class Invoice extends SmrtObject {
|
|
30
|
+
@field({
|
|
31
|
+
required: true,
|
|
32
|
+
description: 'Shown on the customer invoice.',
|
|
33
|
+
ui: { basic: true, group: 'billing', order: 1 },
|
|
34
|
+
})
|
|
35
|
+
title = '';
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
@field({ ui: { basic: false, group: 'billing', order: 2 } })
|
|
38
|
+
internalNotes = '';
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If no field has `ui.basic: true`, fields start in the basic view. Once at
|
|
43
|
+
least one field is marked basic, unmarked fields start advanced. `group` and
|
|
44
|
+
`order` are code-owned hints; `locked: true` seeds a lock that can prevent a
|
|
45
|
+
personal override.
|
|
46
|
+
|
|
47
|
+
## Resolve on the server
|
|
48
|
+
|
|
49
|
+
Use the resolver in trusted server code when rendering SSR or applying a
|
|
50
|
+
server-owned workflow. It merges code → app → tenant ancestry → user.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { resolveFieldPolicy } from '@happyvertical/smrt-fields';
|
|
54
|
+
|
|
55
|
+
const policy = await resolveFieldPolicy(
|
|
56
|
+
'@acme/billing:Invoice',
|
|
57
|
+
{ tenantId: requestTenant.id, userId: session.user.id, db },
|
|
38
58
|
);
|
|
59
|
+
|
|
60
|
+
const title = policy.fields.title;
|
|
39
61
|
```
|
|
40
62
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
63
|
+
The client-facing generated `resolveBatch` action derives the tenant and user
|
|
64
|
+
from the authenticated request context. Do not accept those identifiers from a
|
|
65
|
+
browser request. It returns only fields that are safe to expose; sensitive,
|
|
66
|
+
transient, and read-permission-gated fields are omitted.
|
|
67
|
+
|
|
68
|
+
## Use policy-aware Svelte forms
|
|
69
|
+
|
|
70
|
+
For a custom form, provide a resolved policy with `FieldPolicyProvider` and
|
|
71
|
+
wrap each input in `PolicyField`. The wrapper applies visibility, labels, help,
|
|
72
|
+
and new-record defaults while preserving your markup and input component.
|
|
73
|
+
|
|
74
|
+
```svelte
|
|
75
|
+
<script lang="ts">
|
|
76
|
+
import {
|
|
77
|
+
FieldPolicyProvider,
|
|
78
|
+
PolicyField,
|
|
79
|
+
} from '@happyvertical/smrt-fields/svelte';
|
|
80
|
+
|
|
81
|
+
let { policy, invoice = $bindable({}) } = $props();
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<FieldPolicyProvider {policy}>
|
|
85
|
+
<PolicyField name="title">
|
|
86
|
+
<input id="title" bind:value={invoice.title} />
|
|
87
|
+
</PolicyField>
|
|
88
|
+
</FieldPolicyProvider>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
For generated forms, register the application's generated collection
|
|
92
|
+
definitions once in an `ObjectFormSourceRegistry`, place it in an
|
|
93
|
+
`ObjectFormSourceProvider`, then render an `ObjectForm` by canonical
|
|
94
|
+
`objectRef`. The registry validates both the generated field definition and
|
|
95
|
+
the `resolveBatch` response before rendering. See the guide for the complete
|
|
96
|
+
setup, including field-specific input renderers and policy-aware tables.
|
|
97
|
+
|
|
98
|
+
## Administration and personal settings
|
|
99
|
+
|
|
100
|
+
Use `FieldPolicyGearProvider` with an adapter to the generated
|
|
101
|
+
`getEditorState`, create, update, and delete actions. It derives identity on
|
|
102
|
+
the server; adapter methods intentionally do not accept tenant or user ids.
|
|
103
|
+
Use `FieldPolicyGearButton` or `showPolicyGear` on `ObjectForm` to expose
|
|
104
|
+
the editor.
|
|
105
|
+
|
|
106
|
+
For a settings destination, the server builds data with
|
|
107
|
+
`buildFieldPolicySettingsCatalog()` and the browser renders it through
|
|
108
|
+
`FieldPolicyControlPanel`. Hosts supply route, transport, confirmation, and
|
|
109
|
+
AdminShell adapters. `fieldPolicyControlPanelNavItem()` and
|
|
110
|
+
`registerFieldPolicyFocusTool()` are structural seams, so Fields does not
|
|
111
|
+
take a dependency on a particular application shell.
|
|
112
|
+
|
|
113
|
+
`fields.policy.manage` permits app and tenant administration.
|
|
114
|
+
`fields.policy.personalize` permits only the current user's personal choices.
|
|
115
|
+
The host must still establish a trusted authenticated principal context and
|
|
116
|
+
enforce the route permission before rendering an operator destination.
|
|
117
|
+
|
|
118
|
+
## Important behavior
|
|
119
|
+
|
|
120
|
+
- A policy row is sparse: `null` means inherit the lower layer. Delete a row
|
|
121
|
+
to reset that scope completely.
|
|
122
|
+
- `defaultValue` is the JSON-encoded wire channel. Server code with a plain
|
|
123
|
+
value should use `defaultValueRaw` or `setDefaultValue()`.
|
|
124
|
+
- Required fields cannot be hidden or moved to advanced without a usable
|
|
125
|
+
resolved default. Resolution forces a required, default-less field back to
|
|
126
|
+
basic as a safety net.
|
|
127
|
+
- App and tenant rows may lock a field. While the resolved organization policy
|
|
128
|
+
is locked, personal writes are rejected and old personal rows do not apply.
|
|
129
|
+
- Defaults are rejected for sensitive, transient, and read-permission-gated
|
|
130
|
+
fields. Reference defaults must use a valid UUID unless that reference
|
|
131
|
+
declares a text id type.
|
|
132
|
+
|
|
133
|
+
## Usage learning
|
|
134
|
+
|
|
135
|
+
Optional usage capture records bounded, aggregated submissions from
|
|
136
|
+
authenticated tenant members and turns qualified patterns into
|
|
137
|
+
administrator-reviewed tenant-policy suggestions. It never auto-applies a
|
|
138
|
+
suggestion. `ObjectForm` reports only after its host confirms persistence;
|
|
139
|
+
browser values transit only for low-cardinality boolean and reference fields,
|
|
140
|
+
and telemetry failures never affect the saved submit.
|
|
141
|
+
|
|
142
|
+
Operators enable the dormant maintenance and suggestion schedules explicitly
|
|
143
|
+
with `ensureFieldUsageLearningSchedules({ db })`. The learning loop retains
|
|
144
|
+
aggregates and uses conservative thresholds; its accepted/dismissed suggestion
|
|
145
|
+
queue is restricted to `fields.policy.manage`. See the
|
|
146
|
+
[field policy guide](https://happyvertical.github.io/smrt/field-policies#usage-learning-and-suggestions)
|
|
147
|
+
for the capture, privacy, retention, and schedule contract.
|
|
45
148
|
|
|
46
|
-
|
|
47
|
-
objects/fields are rejected, defaults are type-checked, and defaults on
|
|
48
|
-
`transient`/`sensitive`/`readPermission`-gated fields are refused. Required
|
|
49
|
-
fields can only be demoted from `basic` when a usable default resolves —
|
|
50
|
-
and the resolver re-enforces that invariant at read time.
|
|
149
|
+
## Example application
|
|
51
150
|
|
|
52
|
-
|
|
151
|
+
The [SMRT SaaS starter field-policy walkthrough](https://github.com/happyvertical/smrt-saas-starter/pull/51)
|
|
152
|
+
uses SMRT `0.40.61` and shows the owner/admin controls and the member-facing
|
|
153
|
+
personal form flow in a working application.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"__smrt-register__.d.ts","sourceRoot":"","sources":["../src/__smrt-register__.ts"],"names":[],"mappings":""}
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DatabaseInterface } from '@happyvertical/sql';
|
|
2
|
+
import { ExplainedObjectFieldPolicy } from './types.js';
|
|
3
|
+
export declare function getFieldPolicyCacheTtlMs(): number;
|
|
4
|
+
export declare function getCachedFieldPolicy(objectRef: string, tenantId: string | null | undefined, userId: string | null | undefined, db: DatabaseInterface | unknown, hierarchyLoader?: unknown): ExplainedObjectFieldPolicy | null;
|
|
5
|
+
export declare function setCachedFieldPolicy(objectRef: string, tenantId: string | null | undefined, userId: string | null | undefined, db: DatabaseInterface | unknown, value: ExplainedObjectFieldPolicy, hierarchyLoader?: unknown): void;
|
|
6
|
+
/**
|
|
7
|
+
* Drop every cached resolution for `(db, objectRef)`.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately coarser than the prompts precedent (which deletes a single
|
|
10
|
+
* `(key, tenantId)` entry when the tenant is known): tenant HIERARCHY makes a
|
|
11
|
+
* parent-tenant row change affect every descendant tenant's resolution, and a
|
|
12
|
+
* lock or app-row change affects user-tier entries too, so precise
|
|
13
|
+
* invalidation would have to know the whole tenant tree. Per-objectRef prefix
|
|
14
|
+
* invalidation is always correct and the 30s TTL keeps the cost bounded.
|
|
15
|
+
*/
|
|
16
|
+
export declare function invalidateFieldPolicyCache(objectRef: string, db: DatabaseInterface | unknown): void;
|
|
17
|
+
export declare function clearFieldPolicyCache(): void;
|
|
18
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AA2F7D,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,EAAE,EAAE,iBAAiB,GAAG,OAAO,EAC/B,eAAe,CAAC,EAAE,OAAO,GACxB,0BAA0B,GAAG,IAAI,CAoBnC;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,EAAE,EAAE,iBAAiB,GAAG,OAAO,EAC/B,KAAK,EAAE,0BAA0B,EACjC,eAAe,CAAC,EAAE,OAAO,GACxB,IAAI,CAQN;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,iBAAiB,GAAG,OAAO,GAC9B,IAAI,CAON;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
|