@enyo-energy/energy-app-sdk 0.0.196 → 0.0.197
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 +91 -0
- package/dist/cjs/implementations/energy-manager-settings/energy-manager-settings-validators.cjs +263 -0
- package/dist/cjs/implementations/energy-manager-settings/energy-manager-settings-validators.d.cts +91 -0
- package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.cjs +76 -1
- package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.d.cts +66 -2
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-dynamic-validators.cjs +163 -0
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-dynamic-validators.d.cts +90 -0
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-validators.cjs +150 -0
- package/dist/cjs/index.cjs +5 -0
- package/dist/cjs/index.d.cts +5 -0
- package/dist/cjs/packages/energy-app-energy-manager.d.cts +136 -0
- package/dist/cjs/packages/energy-app-onboarding-v2.d.cts +163 -0
- package/dist/cjs/types/enyo-authentication.d.cts +19 -0
- package/dist/cjs/types/enyo-energy-manager-settings.cjs +182 -0
- package/dist/cjs/types/enyo-energy-manager-settings.d.cts +305 -0
- package/dist/cjs/types/enyo-heating-rod-appliance.d.cts +50 -0
- package/dist/cjs/types/enyo-heatpump-appliance.d.cts +40 -0
- package/dist/cjs/types/enyo-onboarding-v2-additional-setup.cjs +38 -0
- package/dist/cjs/types/enyo-onboarding-v2-additional-setup.d.cts +123 -0
- package/dist/cjs/types/enyo-onboarding-v2-dynamic.cjs +28 -0
- package/dist/cjs/types/enyo-onboarding-v2-dynamic.d.cts +102 -0
- package/dist/cjs/types/enyo-onboarding-v2.cjs +26 -1
- package/dist/cjs/types/enyo-onboarding-v2.d.cts +212 -4
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/implementations/energy-manager-settings/energy-manager-settings-validators.d.ts +91 -0
- package/dist/implementations/energy-manager-settings/energy-manager-settings-validators.js +257 -0
- package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.d.ts +66 -2
- package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.js +75 -1
- package/dist/implementations/onboarding-v2/onboarding-v2-dynamic-validators.d.ts +90 -0
- package/dist/implementations/onboarding-v2/onboarding-v2-dynamic-validators.js +157 -0
- package/dist/implementations/onboarding-v2/onboarding-v2-validators.js +151 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/packages/energy-app-energy-manager.d.ts +136 -0
- package/dist/packages/energy-app-onboarding-v2.d.ts +163 -0
- package/dist/types/enyo-authentication.d.ts +19 -0
- package/dist/types/enyo-energy-manager-settings.d.ts +305 -0
- package/dist/types/enyo-energy-manager-settings.js +179 -0
- package/dist/types/enyo-heating-rod-appliance.d.ts +50 -0
- package/dist/types/enyo-heatpump-appliance.d.ts +40 -0
- package/dist/types/enyo-onboarding-v2-additional-setup.d.ts +123 -0
- package/dist/types/enyo-onboarding-v2-additional-setup.js +35 -0
- package/dist/types/enyo-onboarding-v2-dynamic.d.ts +102 -0
- package/dist/types/enyo-onboarding-v2-dynamic.js +27 -0
- package/dist/types/enyo-onboarding-v2.d.ts +212 -4
- package/dist/types/enyo-onboarding-v2.js +25 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1127,6 +1127,65 @@ if (info) console.log(`Active manager: ${info.name} v${info.version}`);
|
|
|
1127
1127
|
|
|
1128
1128
|
Requires the `EnergyManagerInfo` permission.
|
|
1129
1129
|
|
|
1130
|
+
##### General settings
|
|
1131
|
+
|
|
1132
|
+
A closed, SDK-defined set of user-facing controls the active energy manager honours — the contract between the enyo cockpit and whichever manager is installed. Not to be confused with `useSettings()`, which is per-package, string-valued, and rendered on your own config screen.
|
|
1133
|
+
|
|
1134
|
+
| Setting | Type |
|
|
1135
|
+
|---|---|
|
|
1136
|
+
| `BatteryControl` | boolean |
|
|
1137
|
+
| `BatteryChargingMode` | `paced` \| `immediate` |
|
|
1138
|
+
| `BatteryChargeFromGrid` | boolean |
|
|
1139
|
+
| `BatteryDischargeWhileChargingWh` | number \| `null` (Wh allowance; `null`/unset = disabled) |
|
|
1140
|
+
| `BlockBatteryDischargeWhileEvDischarging` | boolean |
|
|
1141
|
+
| `HeatpumpControl` | boolean |
|
|
1142
|
+
| `HeatingRodControl` | boolean |
|
|
1143
|
+
| `HeatingRodMode` | `pv-surplus-only` \| `boost` |
|
|
1144
|
+
| `ChargerControl` | boolean |
|
|
1145
|
+
| `DefaultChargeMode` | `EnyoChargeModeEnum` |
|
|
1146
|
+
| `PriceLimitCtPerKwh` | number, **ct/kWh** (`7` = 7 ct/kWh) |
|
|
1147
|
+
| `CostOptimizedTarget` | wall-clock `"07:30"` + IANA timezone |
|
|
1148
|
+
|
|
1149
|
+
An energy manager declares what it honours, next to `registerFeatures()`:
|
|
1150
|
+
|
|
1151
|
+
```typescript
|
|
1152
|
+
em.registerSupportedSettings([
|
|
1153
|
+
EnergyManagerSettingEnum.BatteryControl,
|
|
1154
|
+
EnergyManagerSettingEnum.BatteryChargingMode,
|
|
1155
|
+
EnergyManagerSettingEnum.ChargerControl,
|
|
1156
|
+
EnergyManagerSettingEnum.DefaultChargeMode,
|
|
1157
|
+
]);
|
|
1158
|
+
|
|
1159
|
+
// seed defaults without ever overriding the user
|
|
1160
|
+
await em.setEnergyManagerSettings(
|
|
1161
|
+
{ batteryControl: true, batteryChargingMode: EnergyManagerBatteryChargingModeEnum.Paced },
|
|
1162
|
+
{ onlyIfUnset: true },
|
|
1163
|
+
);
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
Anyone can read and react:
|
|
1167
|
+
|
|
1168
|
+
```typescript
|
|
1169
|
+
const state = await em.getEnergyManagerSettings(); // null when no EM is configured
|
|
1170
|
+
if (state?.supported.includes(EnergyManagerSettingEnum.ChargerControl)
|
|
1171
|
+
&& state.values.chargerControl === false) {
|
|
1172
|
+
skipChargerOptimization();
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
const id = em.listenForEnergyManagerSettingsChange(async ({ changed, state }) => {
|
|
1176
|
+
if (changed.includes(EnergyManagerSettingEnum.DefaultChargeMode)) {
|
|
1177
|
+
await applyChargeMode(state.values.defaultChargeMode);
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
em.unsubscribeEnergyManagerSettings(id);
|
|
1181
|
+
```
|
|
1182
|
+
|
|
1183
|
+
Three things to get right:
|
|
1184
|
+
|
|
1185
|
+
- **Absent is not `false`.** `undefined` means unsupported or never chosen; `false` means the user switched it off. Check `supported` first, then the value — collapsing the two steers hardware someone deliberately disabled.
|
|
1186
|
+
- **Six settings are gated by another** (`heatingRodMode` needs `heatingRodControl`, `priceLimitCtPerKwh` only applies under `price-limit`, …). The tree is exported as `ENERGY_MANAGER_SETTING_DEPENDENCIES` so the UI and the validator share one source of truth.
|
|
1187
|
+
- **`priceLimitCtPerKwh` is in cents**, unlike the SDK's machine-readable price fields (`electricityPricePerKwh`, EUR/kWh). Divide by 100 when comparing. `validateEnergyManagerSettingsState()` warns on values outside a plausible ct band, which catches the mix-up.
|
|
1188
|
+
|
|
1130
1189
|
#### `useElectricityTariff(): EnergyAppElectricityTariff`
|
|
1131
1190
|
|
|
1132
1191
|
Register, retrieve, and manage electricity tariffs. One tariff can be marked as the system default.
|
|
@@ -1357,6 +1416,38 @@ await energyApp.useOnboardingV2().registerOnboardingGuidesHandler(async (request
|
|
|
1357
1416
|
|
|
1358
1417
|
`null` and `[]` are **not** the same answer: `[]` says "I genuinely have no guides" and drops the host's cached ones, `null` says "I cannot answer right now" and leaves them alone. Use `null` for transient failures. The host stops waiting after `request.timeoutMs`, so build the guides in memory rather than fetching them.
|
|
1359
1418
|
|
|
1419
|
+
Guides declare **slots**, not values: `onboardingV2Block.dynamic(id, kind)` for an OCPP URL or a device IP. Register a second handler to fill them, and return `null` for anything you cannot resolve — a dynamic block is passive content, so an unresolved value never strands a run.
|
|
1420
|
+
|
|
1421
|
+
```typescript
|
|
1422
|
+
await energyApp.useOnboardingV2().registerDynamicValueHandler(async (request) => {
|
|
1423
|
+
if (request.kind !== EnyoOnboardingV2DynamicKind.OcppUrl) return null;
|
|
1424
|
+
|
|
1425
|
+
const { cloud, local } = await energyApp.useOcpp().getAvailableConnectionDetails();
|
|
1426
|
+
const endpoint = cloud ?? local;
|
|
1427
|
+
if (!endpoint) return null; // unavailable beats wrong
|
|
1428
|
+
|
|
1429
|
+
return { requestId: request.requestId, kind: request.kind, value: endpoint.url };
|
|
1430
|
+
});
|
|
1431
|
+
```
|
|
1432
|
+
|
|
1433
|
+
Guides can also carry `additional-setup` blocks — the one interactive block **your app** judges. It collects fields (including masked `Password`/`Token` fields, which v2 previously had no way to gather at all), calls a third handler, and branches on the outcome you return:
|
|
1434
|
+
|
|
1435
|
+
```typescript
|
|
1436
|
+
await energyApp.useOnboardingV2().registerAdditionalSetupHandler(async (request) => {
|
|
1437
|
+
if (request.setupKey !== 'vendor-cloud-token') return { requestId: request.requestId, outcome: 'failed' };
|
|
1438
|
+
const token = request.values.find((v) => v.name === 'api-token')?.value;
|
|
1439
|
+
if (!token || !(await vendorCloud.verify(token))) {
|
|
1440
|
+
return { requestId: request.requestId, outcome: 'invalid' };
|
|
1441
|
+
}
|
|
1442
|
+
await energyApp.useSecretManager().saveSecret('vendor-cloud', { token });
|
|
1443
|
+
return { requestId: request.requestId, outcome: 'connected' };
|
|
1444
|
+
});
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
Every such block must declare a `failed` outcome — it absorbs a rejection, a timeout, a missing handler, and an outcome value matching no branch (guide and handler are linked only by strings). Secrets are never logged, never prefilled, and not kept in run state. See [ONBOARDING.md](./ONBOARDING.md#app-defined-setups-additional-setup).
|
|
1448
|
+
|
|
1449
|
+
Your answer wins over the host's own resolution, so a wrong value is worse than none — the installer pastes it into a charger and it fails minutes later as an `ocpp-connect` timeout. `validateOnboardingV2DynamicResult()` catches the copy-target mistakes (whitespace, a non-absolute URL, a plaintext scheme).
|
|
1450
|
+
|
|
1360
1451
|
Each guide must carry the `vendorId`, `modelIds` and `startVariant` it applies to — that is how the host selects one for a run, and there is no publish step left to bind them. See [ONBOARDING.md](./ONBOARDING.md#serving-guides-the-host-pulls-the-app-never-publishes) for the full v2 model.
|
|
1361
1452
|
|
|
1362
1453
|
Not permission-gated.
|
package/dist/cjs/implementations/energy-manager-settings/energy-manager-settings-validators.cjs
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Client-side validation for {@link EnergyManagerSettingsState} — the general
|
|
4
|
+
* settings an energy manager declares and the values stored against them.
|
|
5
|
+
*
|
|
6
|
+
* Two classes of problem live here, and only the second is obvious:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Malformed values.** A `HH:mm` that is not a time, a timezone that is not
|
|
9
|
+
* an IANA zone, a price that is not a finite number. These fail at planning
|
|
10
|
+
* time, in the dark, on the night the charge was supposed to happen.
|
|
11
|
+
* 2. **Values that can never take effect.** A heating-rod mode stored while rod
|
|
12
|
+
* control is off, a price limit while the default charge mode is
|
|
13
|
+
* cost-optimized. Nothing rejects these — they simply do nothing, which reads
|
|
14
|
+
* to a user as "the app ignored me".
|
|
15
|
+
*
|
|
16
|
+
* The second class is why {@link ENERGY_MANAGER_SETTING_DEPENDENCIES} exists as
|
|
17
|
+
* data: the gate tree is checked here rather than restated by every surface.
|
|
18
|
+
*
|
|
19
|
+
* `errors` mean the state is malformed; `warnings` are advisory. Use
|
|
20
|
+
* {@link validateEnergyManagerSettingsState} for the non-throwing result, or
|
|
21
|
+
* {@link assertValidEnergyManagerSettingsState} to throw.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.EnergyManagerSettingsValidationError = void 0;
|
|
25
|
+
exports.validateEnergyManagerSettingsState = validateEnergyManagerSettingsState;
|
|
26
|
+
exports.assertValidEnergyManagerSettingsState = assertValidEnergyManagerSettingsState;
|
|
27
|
+
const enyo_energy_manager_settings_js_1 = require("../../types/enyo-energy-manager-settings.cjs");
|
|
28
|
+
const enyo_data_bus_value_js_1 = require("../../types/enyo-data-bus-value.cjs");
|
|
29
|
+
/**
|
|
30
|
+
* Thrown by {@link assertValidEnergyManagerSettingsState} when a state fails
|
|
31
|
+
* validation. The message lists every blocking error so callers can surface them
|
|
32
|
+
* directly.
|
|
33
|
+
*/
|
|
34
|
+
class EnergyManagerSettingsValidationError extends Error {
|
|
35
|
+
/** The individual blocking errors that caused the failure. */
|
|
36
|
+
errors;
|
|
37
|
+
/**
|
|
38
|
+
* @param errors - The blocking validation errors.
|
|
39
|
+
*/
|
|
40
|
+
constructor(errors) {
|
|
41
|
+
super(`Invalid energy manager settings:\n- ${errors.join('\n- ')}`);
|
|
42
|
+
this.name = 'EnergyManagerSettingsValidationError';
|
|
43
|
+
this.errors = errors;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.EnergyManagerSettingsValidationError = EnergyManagerSettingsValidationError;
|
|
47
|
+
/** Every {@link EnergyManagerSettingEnum} value. */
|
|
48
|
+
const SETTINGS = new Set(Object.values(enyo_energy_manager_settings_js_1.EnergyManagerSettingEnum));
|
|
49
|
+
/** 24-hour `HH:mm`, the format {@link EnergyManagerSettingValues.costOptimizedTargetTime} uses. */
|
|
50
|
+
const TIME_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
51
|
+
/**
|
|
52
|
+
* Plausible band for a price limit in ct/kWh.
|
|
53
|
+
*
|
|
54
|
+
* Wide on purpose, and negative at the bottom: wholesale prices go negative, and
|
|
55
|
+
* "charge only when I am paid to" is a real preference. The band exists to catch
|
|
56
|
+
* a EUR/kWh value entered into a ct/kWh field (`0.07` where `7` was meant) and
|
|
57
|
+
* the reverse (`700`), not to express an opinion about tariffs.
|
|
58
|
+
*/
|
|
59
|
+
const PRICE_LIMIT_MIN_CT = -100;
|
|
60
|
+
const PRICE_LIMIT_MAX_CT = 200;
|
|
61
|
+
/**
|
|
62
|
+
* Below this, a {@link EnergyManagerSettingValues.batteryDischargeWhileChargingWh}
|
|
63
|
+
* budget is almost certainly kWh entered into a Wh field.
|
|
64
|
+
*
|
|
65
|
+
* `10` would be ten watt-hours — a rounding error against any house battery, and
|
|
66
|
+
* indistinguishable from "no discharge at all" in practice, where `10` kWh was
|
|
67
|
+
* plainly meant. `0` is exempt: it is a legitimate, if roundabout, way to say
|
|
68
|
+
* disabled.
|
|
69
|
+
*/
|
|
70
|
+
const DISCHARGE_BUDGET_MIN_PLAUSIBLE_WH = 100;
|
|
71
|
+
/** Value enums checked per field, for the enum-valued settings. */
|
|
72
|
+
const BATTERY_CHARGING_MODES = new Set(Object.values(enyo_energy_manager_settings_js_1.EnergyManagerBatteryChargingModeEnum));
|
|
73
|
+
const HEATING_ROD_MODES = new Set(Object.values(enyo_energy_manager_settings_js_1.EnergyManagerHeatingRodModeEnum));
|
|
74
|
+
const CHARGE_MODES = new Set(Object.values(enyo_data_bus_value_js_1.EnyoChargeModeEnum));
|
|
75
|
+
/**
|
|
76
|
+
* True when `timezone` is an IANA zone this runtime recognises.
|
|
77
|
+
*
|
|
78
|
+
* Uses `Intl` rather than a pattern: `"Europe/Berlin"` and `"Europe/Berln"` are
|
|
79
|
+
* indistinguishable by shape, and only the first one resolves a wall-clock time.
|
|
80
|
+
*
|
|
81
|
+
* @param timezone - The candidate IANA timezone identifier.
|
|
82
|
+
* @returns True when the runtime accepts it.
|
|
83
|
+
*/
|
|
84
|
+
function isValidTimezone(timezone) {
|
|
85
|
+
try {
|
|
86
|
+
new Intl.DateTimeFormat('en-US', { timeZone: timezone });
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Reads the effective value a dependency is gated on.
|
|
95
|
+
*
|
|
96
|
+
* @param values - The stored values.
|
|
97
|
+
* @param setting - The gating setting.
|
|
98
|
+
* @returns The gate's current value, or `undefined` when it is unset.
|
|
99
|
+
*/
|
|
100
|
+
function gateValue(values, setting) {
|
|
101
|
+
const [key] = enyo_energy_manager_settings_js_1.ENERGY_MANAGER_SETTING_VALUE_KEYS[setting];
|
|
102
|
+
return key ? values[key] : undefined;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Validates an energy manager settings state: the declared support list, the
|
|
106
|
+
* value formats, and the gate tree between them.
|
|
107
|
+
*
|
|
108
|
+
* Checks applied:
|
|
109
|
+
*
|
|
110
|
+
* - `supported` holds only {@link EnergyManagerSettingEnum} members, without
|
|
111
|
+
* duplicates (**error**).
|
|
112
|
+
* - Each stored value has the right type, and each enum-valued setting holds an
|
|
113
|
+
* enum member (**error**).
|
|
114
|
+
* - `costOptimizedTargetTime` is `HH:mm`, and `costOptimizedTimezone` is an IANA
|
|
115
|
+
* zone the runtime resolves (**error**).
|
|
116
|
+
* - `priceLimitCtPerKwh` is a finite number (**error**), and lies in a plausible
|
|
117
|
+
* ct/kWh band (**warning** — catches a EUR/kWh value in a ct/kWh field).
|
|
118
|
+
* - `batteryDischargeWhileChargingWh` is a finite non-negative number or `null`
|
|
119
|
+
* (**error**), and is not a negligible positive figure (**warning** — catches
|
|
120
|
+
* a kWh value in a Wh field).
|
|
121
|
+
* - A value stored for an unsupported setting (**warning** — the energy manager
|
|
122
|
+
* will ignore it).
|
|
123
|
+
* - A value stored while its gate does not hold (**warning** — it can never take
|
|
124
|
+
* effect; see {@link ENERGY_MANAGER_SETTING_DEPENDENCIES}).
|
|
125
|
+
* - A supported gate whose dependants are unsupported (**warning** — a
|
|
126
|
+
* half-built control surface).
|
|
127
|
+
* - `costOptimizedTargetTime` set without a timezone (**warning** — the
|
|
128
|
+
* wall-clock value is ambiguous across a DST boundary).
|
|
129
|
+
*
|
|
130
|
+
* @param state - The settings state to validate.
|
|
131
|
+
* @returns The {@link EnergyManagerSettingsValidationResult}.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* const {ok, errors, warnings} = validateEnergyManagerSettingsState(state);
|
|
136
|
+
* warnings.forEach((w) => console.warn('energy manager settings:', w));
|
|
137
|
+
* if (!ok) console.error('energy manager settings are malformed', errors);
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
function validateEnergyManagerSettingsState(state) {
|
|
141
|
+
const errors = [];
|
|
142
|
+
const warnings = [];
|
|
143
|
+
if (!state)
|
|
144
|
+
return { ok: false, errors: ['state is required.'], warnings };
|
|
145
|
+
if (!Array.isArray(state.supported)) {
|
|
146
|
+
return { ok: false, errors: ['`supported` must be an array.'], warnings };
|
|
147
|
+
}
|
|
148
|
+
const supported = new Set();
|
|
149
|
+
for (const setting of state.supported) {
|
|
150
|
+
if (!SETTINGS.has(setting)) {
|
|
151
|
+
errors.push(`\`supported\` contains "${setting}", which is not an EnergyManagerSettingEnum member.`);
|
|
152
|
+
}
|
|
153
|
+
else if (supported.has(setting)) {
|
|
154
|
+
errors.push(`\`supported\` lists "${setting}" more than once.`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
supported.add(setting);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const values = state.values ?? {};
|
|
161
|
+
// ── value formats ──────────────────────────────────────────────────────
|
|
162
|
+
for (const key of ['batteryControl', 'batteryChargeFromGrid',
|
|
163
|
+
'blockBatteryDischargeWhileEvDischarging', 'heatpumpControl',
|
|
164
|
+
'heatingRodControl', 'chargerControl']) {
|
|
165
|
+
if (values[key] !== undefined && typeof values[key] !== 'boolean') {
|
|
166
|
+
errors.push(`\`${key}\` must be a boolean when set.`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (values.batteryChargingMode !== undefined
|
|
170
|
+
&& !BATTERY_CHARGING_MODES.has(values.batteryChargingMode)) {
|
|
171
|
+
errors.push(`\`batteryChargingMode\` "${values.batteryChargingMode}" is not an EnergyManagerBatteryChargingModeEnum member.`);
|
|
172
|
+
}
|
|
173
|
+
if (values.heatingRodMode !== undefined && !HEATING_ROD_MODES.has(values.heatingRodMode)) {
|
|
174
|
+
errors.push(`\`heatingRodMode\` "${values.heatingRodMode}" is not an EnergyManagerHeatingRodModeEnum member.`);
|
|
175
|
+
}
|
|
176
|
+
if (values.defaultChargeMode !== undefined && !CHARGE_MODES.has(values.defaultChargeMode)) {
|
|
177
|
+
errors.push(`\`defaultChargeMode\` "${values.defaultChargeMode}" is not an EnyoChargeModeEnum member.`);
|
|
178
|
+
}
|
|
179
|
+
if (values.priceLimitCtPerKwh !== undefined) {
|
|
180
|
+
if (typeof values.priceLimitCtPerKwh !== 'number'
|
|
181
|
+
|| !Number.isFinite(values.priceLimitCtPerKwh)) {
|
|
182
|
+
errors.push('`priceLimitCtPerKwh` must be a finite number when set.');
|
|
183
|
+
}
|
|
184
|
+
else if (values.priceLimitCtPerKwh < PRICE_LIMIT_MIN_CT
|
|
185
|
+
|| values.priceLimitCtPerKwh > PRICE_LIMIT_MAX_CT) {
|
|
186
|
+
warnings.push(`\`priceLimitCtPerKwh\` is ${values.priceLimitCtPerKwh}, outside the plausible ` +
|
|
187
|
+
`${PRICE_LIMIT_MIN_CT}…${PRICE_LIMIT_MAX_CT} ct/kWh band — this field is in ` +
|
|
188
|
+
'cents per kWh, not EUR per kWh.');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (values.batteryDischargeWhileChargingWh !== undefined
|
|
192
|
+
&& values.batteryDischargeWhileChargingWh !== null) {
|
|
193
|
+
const budget = values.batteryDischargeWhileChargingWh;
|
|
194
|
+
if (typeof budget !== 'number' || !Number.isFinite(budget)) {
|
|
195
|
+
errors.push('`batteryDischargeWhileChargingWh` must be a finite number or `null` when set.');
|
|
196
|
+
}
|
|
197
|
+
else if (budget < 0) {
|
|
198
|
+
errors.push(`\`batteryDischargeWhileChargingWh\` is ${budget}; a discharge budget cannot be ` +
|
|
199
|
+
'negative. Use `null` to disable the transfer entirely.');
|
|
200
|
+
}
|
|
201
|
+
else if (budget > 0 && budget < DISCHARGE_BUDGET_MIN_PLAUSIBLE_WH) {
|
|
202
|
+
warnings.push(`\`batteryDischargeWhileChargingWh\` is ${budget} Wh, which is negligible against ` +
|
|
203
|
+
'any house battery — this field is in watt-hours, not kilowatt-hours.');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (values.costOptimizedTargetTime !== undefined) {
|
|
207
|
+
if (typeof values.costOptimizedTargetTime !== 'string'
|
|
208
|
+
|| !TIME_RE.test(values.costOptimizedTargetTime)) {
|
|
209
|
+
errors.push(`\`costOptimizedTargetTime\` "${values.costOptimizedTargetTime}" is not a 24-hour HH:mm time.`);
|
|
210
|
+
}
|
|
211
|
+
if (values.costOptimizedTimezone === undefined) {
|
|
212
|
+
warnings.push('`costOptimizedTargetTime` is set without `costOptimizedTimezone` — the wall-clock ' +
|
|
213
|
+
'value cannot be resolved unambiguously across a DST boundary.');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (values.costOptimizedTimezone !== undefined) {
|
|
217
|
+
if (typeof values.costOptimizedTimezone !== 'string'
|
|
218
|
+
|| !isValidTimezone(values.costOptimizedTimezone)) {
|
|
219
|
+
errors.push(`\`costOptimizedTimezone\` "${values.costOptimizedTimezone}" is not an IANA timezone.`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// ── stored but unsupported ─────────────────────────────────────────────
|
|
223
|
+
for (const setting of Object.values(enyo_energy_manager_settings_js_1.EnergyManagerSettingEnum)) {
|
|
224
|
+
const keys = enyo_energy_manager_settings_js_1.ENERGY_MANAGER_SETTING_VALUE_KEYS[setting];
|
|
225
|
+
const held = keys.filter((key) => values[key] !== undefined);
|
|
226
|
+
if (held.length && !supported.has(setting)) {
|
|
227
|
+
warnings.push(`"${setting}" holds a value (${held.join(', ')}) but is not in \`supported\` — ` +
|
|
228
|
+
'the energy manager will ignore it.');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// ── gate tree ──────────────────────────────────────────────────────────
|
|
232
|
+
for (const [setting, dependency] of Object.entries(enyo_energy_manager_settings_js_1.ENERGY_MANAGER_SETTING_DEPENDENCIES)) {
|
|
233
|
+
if (!dependency)
|
|
234
|
+
continue;
|
|
235
|
+
const key = setting;
|
|
236
|
+
const held = enyo_energy_manager_settings_js_1.ENERGY_MANAGER_SETTING_VALUE_KEYS[key].some((k) => values[k] !== undefined);
|
|
237
|
+
if (held && gateValue(values, dependency.requires) !== dependency.equals) {
|
|
238
|
+
warnings.push(`"${key}" holds a value, but it only takes effect while "${dependency.requires}" ` +
|
|
239
|
+
`is ${JSON.stringify(dependency.equals)} — it currently cannot.`);
|
|
240
|
+
}
|
|
241
|
+
if (supported.has(dependency.requires) && !supported.has(key)
|
|
242
|
+
&& gateValue(values, dependency.requires) === dependency.equals) {
|
|
243
|
+
warnings.push(`"${dependency.requires}" is supported and active, but its dependent setting ` +
|
|
244
|
+
`"${key}" is not — the user is offered a gate with nothing behind it.`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Like {@link validateEnergyManagerSettingsState}, but throws
|
|
251
|
+
* {@link EnergyManagerSettingsValidationError} when there are blocking errors.
|
|
252
|
+
* Warnings never throw; the validated state is returned on success for chaining.
|
|
253
|
+
*
|
|
254
|
+
* @param state - The settings state to validate.
|
|
255
|
+
* @returns The same state when it has no blocking errors.
|
|
256
|
+
* @throws {EnergyManagerSettingsValidationError} When validation produces any error.
|
|
257
|
+
*/
|
|
258
|
+
function assertValidEnergyManagerSettingsState(state) {
|
|
259
|
+
const { ok, errors } = validateEnergyManagerSettingsState(state);
|
|
260
|
+
if (!ok)
|
|
261
|
+
throw new EnergyManagerSettingsValidationError(errors);
|
|
262
|
+
return state;
|
|
263
|
+
}
|
package/dist/cjs/implementations/energy-manager-settings/energy-manager-settings-validators.d.cts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side validation for {@link EnergyManagerSettingsState} — the general
|
|
3
|
+
* settings an energy manager declares and the values stored against them.
|
|
4
|
+
*
|
|
5
|
+
* Two classes of problem live here, and only the second is obvious:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Malformed values.** A `HH:mm` that is not a time, a timezone that is not
|
|
8
|
+
* an IANA zone, a price that is not a finite number. These fail at planning
|
|
9
|
+
* time, in the dark, on the night the charge was supposed to happen.
|
|
10
|
+
* 2. **Values that can never take effect.** A heating-rod mode stored while rod
|
|
11
|
+
* control is off, a price limit while the default charge mode is
|
|
12
|
+
* cost-optimized. Nothing rejects these — they simply do nothing, which reads
|
|
13
|
+
* to a user as "the app ignored me".
|
|
14
|
+
*
|
|
15
|
+
* The second class is why {@link ENERGY_MANAGER_SETTING_DEPENDENCIES} exists as
|
|
16
|
+
* data: the gate tree is checked here rather than restated by every surface.
|
|
17
|
+
*
|
|
18
|
+
* `errors` mean the state is malformed; `warnings` are advisory. Use
|
|
19
|
+
* {@link validateEnergyManagerSettingsState} for the non-throwing result, or
|
|
20
|
+
* {@link assertValidEnergyManagerSettingsState} to throw.
|
|
21
|
+
*/
|
|
22
|
+
import type { EnergyManagerSettingsState } from '../../types/enyo-energy-manager-settings.cjs';
|
|
23
|
+
/**
|
|
24
|
+
* Thrown by {@link assertValidEnergyManagerSettingsState} when a state fails
|
|
25
|
+
* validation. The message lists every blocking error so callers can surface them
|
|
26
|
+
* directly.
|
|
27
|
+
*/
|
|
28
|
+
export declare class EnergyManagerSettingsValidationError extends Error {
|
|
29
|
+
/** The individual blocking errors that caused the failure. */
|
|
30
|
+
readonly errors: string[];
|
|
31
|
+
/**
|
|
32
|
+
* @param errors - The blocking validation errors.
|
|
33
|
+
*/
|
|
34
|
+
constructor(errors: string[]);
|
|
35
|
+
}
|
|
36
|
+
/** The outcome of validating an {@link EnergyManagerSettingsState}. */
|
|
37
|
+
export interface EnergyManagerSettingsValidationResult {
|
|
38
|
+
/** True when there are no blocking `errors` (warnings are still allowed). */
|
|
39
|
+
ok: boolean;
|
|
40
|
+
/** Blocking problems — the state is malformed. */
|
|
41
|
+
errors: string[];
|
|
42
|
+
/** Advisory problems — allowed, but usually worth fixing. */
|
|
43
|
+
warnings: string[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Validates an energy manager settings state: the declared support list, the
|
|
47
|
+
* value formats, and the gate tree between them.
|
|
48
|
+
*
|
|
49
|
+
* Checks applied:
|
|
50
|
+
*
|
|
51
|
+
* - `supported` holds only {@link EnergyManagerSettingEnum} members, without
|
|
52
|
+
* duplicates (**error**).
|
|
53
|
+
* - Each stored value has the right type, and each enum-valued setting holds an
|
|
54
|
+
* enum member (**error**).
|
|
55
|
+
* - `costOptimizedTargetTime` is `HH:mm`, and `costOptimizedTimezone` is an IANA
|
|
56
|
+
* zone the runtime resolves (**error**).
|
|
57
|
+
* - `priceLimitCtPerKwh` is a finite number (**error**), and lies in a plausible
|
|
58
|
+
* ct/kWh band (**warning** — catches a EUR/kWh value in a ct/kWh field).
|
|
59
|
+
* - `batteryDischargeWhileChargingWh` is a finite non-negative number or `null`
|
|
60
|
+
* (**error**), and is not a negligible positive figure (**warning** — catches
|
|
61
|
+
* a kWh value in a Wh field).
|
|
62
|
+
* - A value stored for an unsupported setting (**warning** — the energy manager
|
|
63
|
+
* will ignore it).
|
|
64
|
+
* - A value stored while its gate does not hold (**warning** — it can never take
|
|
65
|
+
* effect; see {@link ENERGY_MANAGER_SETTING_DEPENDENCIES}).
|
|
66
|
+
* - A supported gate whose dependants are unsupported (**warning** — a
|
|
67
|
+
* half-built control surface).
|
|
68
|
+
* - `costOptimizedTargetTime` set without a timezone (**warning** — the
|
|
69
|
+
* wall-clock value is ambiguous across a DST boundary).
|
|
70
|
+
*
|
|
71
|
+
* @param state - The settings state to validate.
|
|
72
|
+
* @returns The {@link EnergyManagerSettingsValidationResult}.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const {ok, errors, warnings} = validateEnergyManagerSettingsState(state);
|
|
77
|
+
* warnings.forEach((w) => console.warn('energy manager settings:', w));
|
|
78
|
+
* if (!ok) console.error('energy manager settings are malformed', errors);
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function validateEnergyManagerSettingsState(state: EnergyManagerSettingsState): EnergyManagerSettingsValidationResult;
|
|
82
|
+
/**
|
|
83
|
+
* Like {@link validateEnergyManagerSettingsState}, but throws
|
|
84
|
+
* {@link EnergyManagerSettingsValidationError} when there are blocking errors.
|
|
85
|
+
* Warnings never throw; the validated state is returned on success for chaining.
|
|
86
|
+
*
|
|
87
|
+
* @param state - The settings state to validate.
|
|
88
|
+
* @returns The same state when it has no blocking errors.
|
|
89
|
+
* @throws {EnergyManagerSettingsValidationError} When validation produces any error.
|
|
90
|
+
*/
|
|
91
|
+
export declare function assertValidEnergyManagerSettingsState(state: EnergyManagerSettingsState): EnergyManagerSettingsState;
|
|
@@ -15,6 +15,7 @@ exports.onboardingV2Target = exports.onboardingV2Block = void 0;
|
|
|
15
15
|
exports.defineOnboardingGuideV2 = defineOnboardingGuideV2;
|
|
16
16
|
exports.onContinueV2 = onContinueV2;
|
|
17
17
|
exports.onOptionV2 = onOptionV2;
|
|
18
|
+
exports.onSkipV2 = onSkipV2;
|
|
18
19
|
exports.onOutcomeV2 = onOutcomeV2;
|
|
19
20
|
const enyo_onboarding_v2_js_1 = require("../../types/enyo-onboarding-v2.cjs");
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
@@ -228,7 +229,9 @@ exports.onboardingV2Block = {
|
|
|
228
229
|
* @param id - Stable block id, unique within the guide.
|
|
229
230
|
* @param label - Translated sign-in button text (de/en).
|
|
230
231
|
* @param outcome - The single success handle (`{id, label}`).
|
|
231
|
-
* @param opts - Optional translated `help` naming the account that is needed
|
|
232
|
+
* @param opts - Optional translated `help` naming the account that is needed,
|
|
233
|
+
* and `requiresWebAuthentication` to force the login into a web browser
|
|
234
|
+
* when the provider rejects a custom-scheme redirect such as `enyoapp://`.
|
|
232
235
|
*/
|
|
233
236
|
auth: (id, label, outcome, opts) => ({
|
|
234
237
|
id,
|
|
@@ -236,6 +239,7 @@ exports.onboardingV2Block = {
|
|
|
236
239
|
label,
|
|
237
240
|
outcome,
|
|
238
241
|
help: opts?.help,
|
|
242
|
+
requiresWebAuthentication: opts?.requiresWebAuthentication,
|
|
239
243
|
}),
|
|
240
244
|
/**
|
|
241
245
|
* A link block: a fixed URL the installer opens or copies.
|
|
@@ -282,6 +286,57 @@ exports.onboardingV2Block = {
|
|
|
282
286
|
placeholder: opts?.placeholder,
|
|
283
287
|
help: opts?.help,
|
|
284
288
|
}),
|
|
289
|
+
/**
|
|
290
|
+
* An additional-setup block: collect zero or more values, hand them to this
|
|
291
|
+
* energy app, and branch on the verdict the app returns.
|
|
292
|
+
*
|
|
293
|
+
* The only interactive block the app itself judges — use it for a vendor API
|
|
294
|
+
* key, a service token, an installer code. Keep
|
|
295
|
+
* {@link onboardingV2Block.auth} for the app's own OAuth session, whose
|
|
296
|
+
* server gating this cannot replace.
|
|
297
|
+
*
|
|
298
|
+
* `outcomes` must contain at least two entries, one of them valued `failed`
|
|
299
|
+
* ({@link ENYO_ONBOARDING_V2_SETUP_FAILED_OUTCOME}) — every breakdown lands
|
|
300
|
+
* there, including an outcome the handler returns that no branch declares.
|
|
301
|
+
* Route each outcome with {@link onOutcomeV2} and the optional skip with
|
|
302
|
+
* {@link onSkipV2}.
|
|
303
|
+
*
|
|
304
|
+
* `Password` and `Token` fields are treated as secrets: masked, never logged,
|
|
305
|
+
* never prefilled, and not persisted in run state.
|
|
306
|
+
*
|
|
307
|
+
* @param id - Stable block id, unique within the guide.
|
|
308
|
+
* @param setupKey - App-facing key the handler switches on; may repeat across guides.
|
|
309
|
+
* @param opts - `cta` and `description` (both translated, both required),
|
|
310
|
+
* optional `fields`, the `outcomes`, and an optional `skip` handle.
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```typescript
|
|
314
|
+
* onboardingV2Block.additionalSetup('cloud', 'vendor-cloud-token', {
|
|
315
|
+
* cta: t('Cloud verbinden', 'Connect the cloud'),
|
|
316
|
+
* description: t('Optional: bessere Prognosen.', 'Optional: better forecasts.'),
|
|
317
|
+
* fields: [{
|
|
318
|
+
* name: 'api-token',
|
|
319
|
+
* type: EnyoOnboardingV2SetupFieldType.Token,
|
|
320
|
+
* label: t('API-Token', 'API token'),
|
|
321
|
+
* }],
|
|
322
|
+
* outcomes: [
|
|
323
|
+
* {id: 'ok', value: 'connected', label: t('Verbunden', 'Connected')},
|
|
324
|
+
* {id: 'failed', value: 'failed', label: t('Fehlgeschlagen', 'Failed')},
|
|
325
|
+
* ],
|
|
326
|
+
* skip: {id: 'later', label: t('Später', 'Later')},
|
|
327
|
+
* })
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
additionalSetup: (id, setupKey, opts) => ({
|
|
331
|
+
id,
|
|
332
|
+
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.AdditionalSetup,
|
|
333
|
+
setupKey,
|
|
334
|
+
cta: opts.cta,
|
|
335
|
+
description: opts.description,
|
|
336
|
+
fields: opts.fields,
|
|
337
|
+
outcomes: opts.outcomes,
|
|
338
|
+
skip: opts.skip,
|
|
339
|
+
}),
|
|
285
340
|
};
|
|
286
341
|
// ---------------------------------------------------------------------------
|
|
287
342
|
// Target factories
|
|
@@ -364,6 +419,26 @@ function onOptionV2(blockId, optionId, to, note) {
|
|
|
364
419
|
note,
|
|
365
420
|
};
|
|
366
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* Route the skip handle of an `AdditionalSetup` block.
|
|
424
|
+
*
|
|
425
|
+
* Only an optional setup has one. It leaves the block without a verdict, so
|
|
426
|
+
* nothing is collected and the handler is never called — which is what keeps a
|
|
427
|
+
* failing bonus feature from blocking an otherwise finished onboarding.
|
|
428
|
+
*
|
|
429
|
+
* @param blockId - The additional-setup block's id.
|
|
430
|
+
* @param skipId - The skip handle's id.
|
|
431
|
+
* @param to - Where skipping leads.
|
|
432
|
+
* @param note - Optional author note.
|
|
433
|
+
*/
|
|
434
|
+
function onSkipV2(blockId, skipId, to, note) {
|
|
435
|
+
return {
|
|
436
|
+
id: `skip:${blockId}:${skipId}`,
|
|
437
|
+
source: { kind: enyo_onboarding_v2_js_1.EnyoOnboardingV2TransitionSourceKind.Skip, blockId, skipId },
|
|
438
|
+
target: to,
|
|
439
|
+
note,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
367
442
|
/**
|
|
368
443
|
* Route an `Action` outcome.
|
|
369
444
|
* @param blockId - The action block's id.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { EnyoOnboardingTranslatedContent } from '../../types/enyo-onboarding.cjs';
|
|
13
13
|
import { EnyoOnboardingV2ActionKind, EnyoOnboardingV2ChoiceLayout, EnyoOnboardingV2DeviceSelection, EnyoOnboardingV2PauseReason } from '../../types/enyo-onboarding-v2.cjs';
|
|
14
|
-
import type { EnyoOnboardingV2ActionOutcome, EnyoOnboardingV2AuthOutcome, EnyoOnboardingV2Block, EnyoOnboardingV2ChoiceOption, EnyoOnboardingV2DynamicKind, EnyoOnboardingV2Guide, EnyoOnboardingV2HintVariant, EnyoOnboardingV2InputOutcome, EnyoOnboardingV2InputValueType, EnyoOnboardingV2StartVariant, EnyoOnboardingV2Target, EnyoOnboardingV2Transition } from '../../types/enyo-onboarding-v2.cjs';
|
|
14
|
+
import type { EnyoOnboardingV2ActionOutcome, EnyoOnboardingV2AuthOutcome, EnyoOnboardingV2SetupField, EnyoOnboardingV2SetupOutcome, EnyoOnboardingV2SetupSkipHandle, EnyoOnboardingV2Block, EnyoOnboardingV2ChoiceOption, EnyoOnboardingV2DynamicKind, EnyoOnboardingV2Guide, EnyoOnboardingV2HintVariant, EnyoOnboardingV2InputOutcome, EnyoOnboardingV2InputValueType, EnyoOnboardingV2StartVariant, EnyoOnboardingV2Target, EnyoOnboardingV2Transition } from '../../types/enyo-onboarding-v2.cjs';
|
|
15
15
|
/**
|
|
16
16
|
* Identity helper that type-checks a v2 guide literal at definition time
|
|
17
17
|
* (mirrors `defineEnergyAppPackage`). Prefer this over a bare object literal so
|
|
@@ -181,10 +181,13 @@ export declare const onboardingV2Block: {
|
|
|
181
181
|
* @param id - Stable block id, unique within the guide.
|
|
182
182
|
* @param label - Translated sign-in button text (de/en).
|
|
183
183
|
* @param outcome - The single success handle (`{id, label}`).
|
|
184
|
-
* @param opts - Optional translated `help` naming the account that is needed
|
|
184
|
+
* @param opts - Optional translated `help` naming the account that is needed,
|
|
185
|
+
* and `requiresWebAuthentication` to force the login into a web browser
|
|
186
|
+
* when the provider rejects a custom-scheme redirect such as `enyoapp://`.
|
|
185
187
|
*/
|
|
186
188
|
auth: (id: string, label: EnyoOnboardingTranslatedContent[], outcome: EnyoOnboardingV2AuthOutcome, opts?: {
|
|
187
189
|
help?: EnyoOnboardingTranslatedContent[];
|
|
190
|
+
requiresWebAuthentication?: boolean;
|
|
188
191
|
}) => EnyoOnboardingV2Block;
|
|
189
192
|
/**
|
|
190
193
|
* A link block: a fixed URL the installer opens or copies.
|
|
@@ -221,6 +224,54 @@ export declare const onboardingV2Block: {
|
|
|
221
224
|
placeholder?: EnyoOnboardingTranslatedContent[];
|
|
222
225
|
help?: EnyoOnboardingTranslatedContent[];
|
|
223
226
|
}) => EnyoOnboardingV2Block;
|
|
227
|
+
/**
|
|
228
|
+
* An additional-setup block: collect zero or more values, hand them to this
|
|
229
|
+
* energy app, and branch on the verdict the app returns.
|
|
230
|
+
*
|
|
231
|
+
* The only interactive block the app itself judges — use it for a vendor API
|
|
232
|
+
* key, a service token, an installer code. Keep
|
|
233
|
+
* {@link onboardingV2Block.auth} for the app's own OAuth session, whose
|
|
234
|
+
* server gating this cannot replace.
|
|
235
|
+
*
|
|
236
|
+
* `outcomes` must contain at least two entries, one of them valued `failed`
|
|
237
|
+
* ({@link ENYO_ONBOARDING_V2_SETUP_FAILED_OUTCOME}) — every breakdown lands
|
|
238
|
+
* there, including an outcome the handler returns that no branch declares.
|
|
239
|
+
* Route each outcome with {@link onOutcomeV2} and the optional skip with
|
|
240
|
+
* {@link onSkipV2}.
|
|
241
|
+
*
|
|
242
|
+
* `Password` and `Token` fields are treated as secrets: masked, never logged,
|
|
243
|
+
* never prefilled, and not persisted in run state.
|
|
244
|
+
*
|
|
245
|
+
* @param id - Stable block id, unique within the guide.
|
|
246
|
+
* @param setupKey - App-facing key the handler switches on; may repeat across guides.
|
|
247
|
+
* @param opts - `cta` and `description` (both translated, both required),
|
|
248
|
+
* optional `fields`, the `outcomes`, and an optional `skip` handle.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```typescript
|
|
252
|
+
* onboardingV2Block.additionalSetup('cloud', 'vendor-cloud-token', {
|
|
253
|
+
* cta: t('Cloud verbinden', 'Connect the cloud'),
|
|
254
|
+
* description: t('Optional: bessere Prognosen.', 'Optional: better forecasts.'),
|
|
255
|
+
* fields: [{
|
|
256
|
+
* name: 'api-token',
|
|
257
|
+
* type: EnyoOnboardingV2SetupFieldType.Token,
|
|
258
|
+
* label: t('API-Token', 'API token'),
|
|
259
|
+
* }],
|
|
260
|
+
* outcomes: [
|
|
261
|
+
* {id: 'ok', value: 'connected', label: t('Verbunden', 'Connected')},
|
|
262
|
+
* {id: 'failed', value: 'failed', label: t('Fehlgeschlagen', 'Failed')},
|
|
263
|
+
* ],
|
|
264
|
+
* skip: {id: 'later', label: t('Später', 'Later')},
|
|
265
|
+
* })
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
additionalSetup: (id: string, setupKey: string, opts: {
|
|
269
|
+
cta: EnyoOnboardingTranslatedContent[];
|
|
270
|
+
description: EnyoOnboardingTranslatedContent[];
|
|
271
|
+
fields?: EnyoOnboardingV2SetupField[];
|
|
272
|
+
outcomes: EnyoOnboardingV2SetupOutcome[];
|
|
273
|
+
skip?: EnyoOnboardingV2SetupSkipHandle;
|
|
274
|
+
}) => EnyoOnboardingV2Block;
|
|
224
275
|
};
|
|
225
276
|
/** Typed factories for each transition {@link EnyoOnboardingV2Target}. */
|
|
226
277
|
export declare const onboardingV2Target: {
|
|
@@ -279,6 +330,19 @@ export declare function onContinueV2(to: EnyoOnboardingV2Target, note?: string):
|
|
|
279
330
|
* @param note - Optional author note.
|
|
280
331
|
*/
|
|
281
332
|
export declare function onOptionV2(blockId: string, optionId: string, to: EnyoOnboardingV2Target, note?: string): EnyoOnboardingV2Transition;
|
|
333
|
+
/**
|
|
334
|
+
* Route the skip handle of an `AdditionalSetup` block.
|
|
335
|
+
*
|
|
336
|
+
* Only an optional setup has one. It leaves the block without a verdict, so
|
|
337
|
+
* nothing is collected and the handler is never called — which is what keeps a
|
|
338
|
+
* failing bonus feature from blocking an otherwise finished onboarding.
|
|
339
|
+
*
|
|
340
|
+
* @param blockId - The additional-setup block's id.
|
|
341
|
+
* @param skipId - The skip handle's id.
|
|
342
|
+
* @param to - Where skipping leads.
|
|
343
|
+
* @param note - Optional author note.
|
|
344
|
+
*/
|
|
345
|
+
export declare function onSkipV2(blockId: string, skipId: string, to: EnyoOnboardingV2Target, note?: string): EnyoOnboardingV2Transition;
|
|
282
346
|
/**
|
|
283
347
|
* Route an `Action` outcome.
|
|
284
348
|
* @param blockId - The action block's id.
|