@happyvertical/smrt-fields 0.40.61 → 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/README.md +125 -112
- package/dist/chunks/FieldPolicyCollection--RxukfCX.js +1218 -0
- package/dist/chunks/FieldPolicyCollection--RxukfCX.js.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 +8 -0
- package/dist/field-definitions.d.ts.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1565 -1077
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1442 -188
- 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/smrt-knowledge.json +402 -5
- package/dist/svelte/__tests__/FieldPolicyControlPanel.test.js +54 -0
- package/dist/svelte/__tests__/ObjectForm.test.js +131 -0
- package/dist/svelte/__tests__/UsageLearning.test.js +140 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte +21 -4
- package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts +5 -1
- package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts.map +1 -1
- 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/components/FieldPolicyControlPanel.svelte +28 -1
- package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts +3 -0
- package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts.map +1 -1
- package/dist/svelte/components/FieldPolicyGearButton.svelte +8 -1
- package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts.map +1 -1
- package/dist/svelte/components/FieldPolicyGearProvider.svelte +34 -0
- package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts +3 -0
- package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts.map +1 -1
- 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/ObjectForm.svelte +91 -2
- package/dist/svelte/components/ObjectForm.svelte.d.ts +11 -1
- package/dist/svelte/components/ObjectForm.svelte.d.ts.map +1 -1
- package/dist/svelte/gear-context.svelte.d.ts +2 -0
- package/dist/svelte/gear-context.svelte.d.ts.map +1 -1
- package/dist/svelte/index.d.ts +6 -1
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +4 -1
- 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/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 +40 -0
- package/dist/types.d.ts.map +1 -1
- 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 +8 -8
package/README.md
CHANGED
|
@@ -1,140 +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
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
FieldPolicy,
|
|
11
|
-
FieldPolicyCollection,
|
|
12
|
-
resolveFieldPolicy,
|
|
13
|
-
resolveFieldPolicyExplained,
|
|
14
|
-
} from '@happyvertical/smrt-fields';
|
|
15
|
-
|
|
16
|
-
// An org (tenant) demotes an optional field and sets a default
|
|
17
|
-
const policies = await FieldPolicyCollection.create({ db });
|
|
18
|
-
await policies.create({
|
|
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'
|
|
8
|
+
For the complete application and operator guide, see the
|
|
9
|
+
[field policy guide](https://happyvertical.github.io/smrt/field-policies).
|
|
33
10
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
);
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add @happyvertical/smrt-fields
|
|
39
15
|
```
|
|
40
16
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
injected hierarchy provider cannot resolve the tenant) → user rows. A NULL column inherits
|
|
44
|
-
from the lower layer; resetting a customization is a row delete.
|
|
17
|
+
The package includes `@happyvertical/smrt-users` because policy writes and
|
|
18
|
+
operator actions use its permission and tenant context services.
|
|
45
19
|
|
|
46
|
-
|
|
47
|
-
uses its permission catalog and operation guard for every write and gear
|
|
48
|
-
action. The hierarchy fallback concerns tenant ancestry only; it is not a
|
|
49
|
-
users-authorization fallback.
|
|
20
|
+
## Define a safe code seed
|
|
50
21
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
`transient`/`sensitive`/`readPermission`-gated fields are refused. Required
|
|
54
|
-
fields can only be demoted from `basic` when a usable default resolves —
|
|
55
|
-
and the resolver re-enforces that invariant at read time.
|
|
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.
|
|
56
24
|
|
|
57
|
-
|
|
25
|
+
```typescript
|
|
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 = '';
|
|
36
|
+
|
|
37
|
+
@field({ ui: { basic: false, group: 'billing', order: 2 } })
|
|
38
|
+
internalNotes = '';
|
|
39
|
+
}
|
|
40
|
+
```
|
|
58
41
|
|
|
59
|
-
|
|
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.
|
|
60
46
|
|
|
61
|
-
|
|
62
|
-
Pass only the generated browser field definitions and the matching resolved
|
|
63
|
-
policy; `ObjectForm` renders their safe intersection, honors policy visibility
|
|
64
|
-
and ordering, and keeps form state transport-neutral.
|
|
47
|
+
## Resolve on the server
|
|
65
48
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
import { ObjectForm, createFieldInputRegistry } from '@happyvertical/smrt-fields/svelte';
|
|
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.
|
|
69
51
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
</script>
|
|
52
|
+
```typescript
|
|
53
|
+
import { resolveFieldPolicy } from '@happyvertical/smrt-fields';
|
|
73
54
|
|
|
74
|
-
|
|
75
|
-
|
|
55
|
+
const policy = await resolveFieldPolicy(
|
|
56
|
+
'@acme/billing:Invoice',
|
|
57
|
+
{ tenantId: requestTenant.id, userId: session.user.id, db },
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const title = policy.fields.title;
|
|
76
61
|
```
|
|
77
62
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
and
|
|
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.
|
|
82
67
|
|
|
83
|
-
|
|
84
|
-
<ObjectForm {objectRef} fields={collectionDefinition.fields} {policy}
|
|
85
|
-
bind:value={record} onsubmit={save}>
|
|
86
|
-
{#snippet actions()}
|
|
87
|
-
<button type="submit">Save</button>
|
|
88
|
-
{/snippet}
|
|
89
|
-
</ObjectForm>
|
|
90
|
-
```
|
|
68
|
+
## Use policy-aware Svelte forms
|
|
91
69
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
the generated definition and the untyped custom-action response before it is
|
|
96
|
-
rendered, failing closed with an accessible error state on a missing or
|
|
97
|
-
mismatched response.
|
|
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.
|
|
98
73
|
|
|
99
74
|
```svelte
|
|
100
75
|
<script lang="ts">
|
|
101
76
|
import {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
ObjectFormSourceRegistry,
|
|
77
|
+
FieldPolicyProvider,
|
|
78
|
+
PolicyField,
|
|
105
79
|
} from '@happyvertical/smrt-fields/svelte';
|
|
106
80
|
|
|
107
|
-
|
|
108
|
-
for (const definition of Object.values(collectionDefinitions)) source.register(definition);
|
|
81
|
+
let { policy, invoice = $bindable({}) } = $props();
|
|
109
82
|
</script>
|
|
110
83
|
|
|
111
|
-
<
|
|
112
|
-
<
|
|
113
|
-
|
|
84
|
+
<FieldPolicyProvider {policy}>
|
|
85
|
+
<PolicyField name="title">
|
|
86
|
+
<input id="title" bind:value={invoice.title} />
|
|
87
|
+
</PolicyField>
|
|
88
|
+
</FieldPolicyProvider>
|
|
114
89
|
```
|
|
115
90
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
`
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
`
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
`
|
|
140
|
-
|
|
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.
|
|
148
|
+
|
|
149
|
+
## Example application
|
|
150
|
+
|
|
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.
|