@enyo-energy/energy-app-sdk 1.6.0 → 1.7.0

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 (35) hide show
  1. package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.cjs +90 -0
  2. package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.d.cts +78 -1
  3. package/dist/cjs/implementations/onboarding-v2/onboarding-v2-validators.cjs +119 -0
  4. package/dist/cjs/index.cjs +2 -0
  5. package/dist/cjs/index.d.cts +2 -0
  6. package/dist/cjs/packages/energy-app-interval.d.cts +1 -1
  7. package/dist/cjs/packages/energy-app-onboarding-v2.d.cts +54 -0
  8. package/dist/cjs/types/enyo-onboarding-v2-device-select.cjs +29 -0
  9. package/dist/cjs/types/enyo-onboarding-v2-device-select.d.cts +105 -0
  10. package/dist/cjs/types/enyo-onboarding-v2-validation.cjs +29 -0
  11. package/dist/cjs/types/enyo-onboarding-v2-validation.d.cts +108 -0
  12. package/dist/cjs/types/enyo-onboarding-v2.cjs +66 -1
  13. package/dist/cjs/types/enyo-onboarding-v2.d.cts +206 -8
  14. package/dist/cjs/types/enyo-onboarding.cjs +5 -3
  15. package/dist/cjs/types/enyo-onboarding.d.cts +5 -3
  16. package/dist/cjs/version.cjs +1 -1
  17. package/dist/cjs/version.d.cts +1 -1
  18. package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.d.ts +78 -1
  19. package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.js +90 -0
  20. package/dist/implementations/onboarding-v2/onboarding-v2-validators.js +120 -1
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +2 -0
  23. package/dist/packages/energy-app-interval.d.ts +1 -1
  24. package/dist/packages/energy-app-onboarding-v2.d.ts +54 -0
  25. package/dist/types/enyo-onboarding-v2-device-select.d.ts +105 -0
  26. package/dist/types/enyo-onboarding-v2-device-select.js +28 -0
  27. package/dist/types/enyo-onboarding-v2-validation.d.ts +108 -0
  28. package/dist/types/enyo-onboarding-v2-validation.js +28 -0
  29. package/dist/types/enyo-onboarding-v2.d.ts +206 -8
  30. package/dist/types/enyo-onboarding-v2.js +65 -0
  31. package/dist/types/enyo-onboarding.d.ts +5 -3
  32. package/dist/types/enyo-onboarding.js +5 -3
  33. package/dist/version.d.ts +1 -1
  34. package/dist/version.js +1 -1
  35. package/package.json +1 -1
@@ -67,6 +67,62 @@ exports.onboardingV2Block = {
67
67
  * @param items - One translated entry per bullet (de/en).
68
68
  */
69
69
  bullets: (id, items) => ({ id, type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Bullets, items }),
70
+ /**
71
+ * A select block — a dropdown whose answer is recorded, not branched on.
72
+ *
73
+ * Use this, not {@link block.choice}, when the answer is data rather than a
74
+ * route: a choice needs one outgoing transition per option, so forty models
75
+ * would mean forty transitions to the same step.
76
+ *
77
+ * @param id - Stable block id, unique within the guide.
78
+ * @param label - Translated field label (de/en).
79
+ * @param options - The selectable options; unique values, at least one.
80
+ * @param opts - Optional `defaultValue`, `help` and `required`.
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * block.select('model', t('Modell', 'Model'), [
85
+ * {value: 'sb-3-0', label: t('Sunny Boy 3.0', 'Sunny Boy 3.0')},
86
+ * {value: 'sb-5-0', label: t('Sunny Boy 5.0', 'Sunny Boy 5.0')},
87
+ * ], {required: true})
88
+ * ```
89
+ */
90
+ select: (id, label, options, opts) => ({
91
+ id,
92
+ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Select,
93
+ label,
94
+ options,
95
+ ...(opts?.defaultValue !== undefined ? { defaultValue: opts.defaultValue } : {}),
96
+ ...(opts?.help ? { help: opts.help } : {}),
97
+ ...(opts?.required !== undefined ? { required: opts.required } : {}),
98
+ }),
99
+ /**
100
+ * A credentials block — label/value pairs the installer transcribes into the
101
+ * device or a vendor portal.
102
+ *
103
+ * Passive content: it routes nothing, so a step whose only non-content block
104
+ * is this one still leaves through its single `continue` handle.
105
+ *
106
+ * @param id - Stable block id, unique within the guide.
107
+ * @param credentials - The label/value pairs. At least one.
108
+ * @param options - Optional heading, note, and copy-button toggle.
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * block.credentials('creds', [
113
+ * {label: t('Benutzername', 'Username'), value: 'enyo'},
114
+ * {label: t('Passwort', 'Password'), value: generated, secret: true},
115
+ * ], {title: t('Zugangsdaten', 'Credentials')})
116
+ * ```
117
+ */
118
+ credentials: (id, credentials, options) => ({
119
+ id,
120
+ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Credentials,
121
+ credentials,
122
+ copyable: options?.copyable ?? true,
123
+ ...(options?.title ? { title: options.title } : {}),
124
+ ...(options?.description ? { description: options.description } : {}),
125
+ }),
70
126
  /**
71
127
  * An image block addressing an externally hosted image by URL.
72
128
  *
@@ -217,6 +273,40 @@ exports.onboardingV2Block = {
217
273
  label,
218
274
  outcomes,
219
275
  }),
276
+ /**
277
+ * A device-select block: the installer picks the device being onboarded from
278
+ * everything the run has found.
279
+ *
280
+ * Use it whenever a scan can turn up more than one candidate. A
281
+ * {@link block.networkScan} branches on found/not-found but binds nothing,
282
+ * so without this the run does not know *which* device it is working on —
283
+ * and {@link EnyoOnboardingV2DeviceSelection.Current} and
284
+ * {@link EnyoOnboardingV2DynamicKind.DeviceIp} have nothing to resolve
285
+ * against.
286
+ *
287
+ * The picker renders what discovery found, so the guide must have scanned:
288
+ * keep {@link EnyoOnboardingV2Guide.requiresNetworkScan} at its default, or
289
+ * place a {@link block.networkScan} ahead of this one.
290
+ *
291
+ * Outcome `value`s must be {@link EnyoOnboardingV2DeviceSelectOutcome}
292
+ * members; route `not-found` to troubleshooting rather than to a step that
293
+ * assumes a device exists.
294
+ *
295
+ * Register an {@link EnyoOnboardingV2DeviceSelectHandler} to turn the pick
296
+ * into appliances — the host awaits it and binds the run to the ids it
297
+ * returns. Without one the pick binds an address and nothing more.
298
+ *
299
+ * @param id - Stable block id, unique within the guide.
300
+ * @param label - Translated trigger button text (de/en).
301
+ * @param outcomes - The `selected` / `not-found` results; each is a routing handle.
302
+ */
303
+ deviceSelect: (id, label, outcomes) => ({
304
+ id,
305
+ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action,
306
+ action: enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.DeviceSelect,
307
+ label,
308
+ outcomes,
309
+ }),
220
310
  /**
221
311
  * An auth block: the installer signs into the energy app's own account
222
312
  * system (OAuth / vendor portal).
@@ -10,7 +10,7 @@
10
10
  * before publishing.
11
11
  */
12
12
  import type { EnyoOnboardingTranslatedContent } from '../../types/enyo-onboarding.cjs';
13
- import { EnyoOnboardingV2ActionKind, EnyoOnboardingV2ChoiceLayout, EnyoOnboardingV2DeviceSelection, EnyoOnboardingV2PauseReason } from '../../types/enyo-onboarding-v2.cjs';
13
+ import { EnyoOnboardingV2ActionKind, EnyoOnboardingV2Credential, EnyoOnboardingV2SelectOption, EnyoOnboardingV2ChoiceLayout, EnyoOnboardingV2DeviceSelection, EnyoOnboardingV2PauseReason } from '../../types/enyo-onboarding-v2.cjs';
14
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
@@ -45,6 +45,55 @@ export declare const onboardingV2Block: {
45
45
  * @param items - One translated entry per bullet (de/en).
46
46
  */
47
47
  bullets: (id: string, items: EnyoOnboardingTranslatedContent[][]) => EnyoOnboardingV2Block;
48
+ /**
49
+ * A select block — a dropdown whose answer is recorded, not branched on.
50
+ *
51
+ * Use this, not {@link block.choice}, when the answer is data rather than a
52
+ * route: a choice needs one outgoing transition per option, so forty models
53
+ * would mean forty transitions to the same step.
54
+ *
55
+ * @param id - Stable block id, unique within the guide.
56
+ * @param label - Translated field label (de/en).
57
+ * @param options - The selectable options; unique values, at least one.
58
+ * @param opts - Optional `defaultValue`, `help` and `required`.
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * block.select('model', t('Modell', 'Model'), [
63
+ * {value: 'sb-3-0', label: t('Sunny Boy 3.0', 'Sunny Boy 3.0')},
64
+ * {value: 'sb-5-0', label: t('Sunny Boy 5.0', 'Sunny Boy 5.0')},
65
+ * ], {required: true})
66
+ * ```
67
+ */
68
+ select: (id: string, label: EnyoOnboardingTranslatedContent[], options: EnyoOnboardingV2SelectOption[], opts?: {
69
+ defaultValue?: string;
70
+ help?: EnyoOnboardingTranslatedContent[];
71
+ required?: boolean;
72
+ }) => EnyoOnboardingV2Block;
73
+ /**
74
+ * A credentials block — label/value pairs the installer transcribes into the
75
+ * device or a vendor portal.
76
+ *
77
+ * Passive content: it routes nothing, so a step whose only non-content block
78
+ * is this one still leaves through its single `continue` handle.
79
+ *
80
+ * @param id - Stable block id, unique within the guide.
81
+ * @param credentials - The label/value pairs. At least one.
82
+ * @param options - Optional heading, note, and copy-button toggle.
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * block.credentials('creds', [
87
+ * {label: t('Benutzername', 'Username'), value: 'enyo'},
88
+ * {label: t('Passwort', 'Password'), value: generated, secret: true},
89
+ * ], {title: t('Zugangsdaten', 'Credentials')})
90
+ * ```
91
+ */
92
+ credentials: (id: string, credentials: EnyoOnboardingV2Credential[], options?: {
93
+ title?: EnyoOnboardingTranslatedContent[];
94
+ description?: EnyoOnboardingTranslatedContent[];
95
+ copyable?: boolean;
96
+ }) => EnyoOnboardingV2Block;
48
97
  /**
49
98
  * An image block addressing an externally hosted image by URL.
50
99
  *
@@ -169,6 +218,34 @@ export declare const onboardingV2Block: {
169
218
  * @param outcomes - The `paired` / `not-found` / `failure` results; each is a routing handle.
170
219
  */
171
220
  eebusPair: (id: string, label: EnyoOnboardingTranslatedContent[], outcomes: EnyoOnboardingV2ActionOutcome[]) => EnyoOnboardingV2Block;
221
+ /**
222
+ * A device-select block: the installer picks the device being onboarded from
223
+ * everything the run has found.
224
+ *
225
+ * Use it whenever a scan can turn up more than one candidate. A
226
+ * {@link block.networkScan} branches on found/not-found but binds nothing,
227
+ * so without this the run does not know *which* device it is working on —
228
+ * and {@link EnyoOnboardingV2DeviceSelection.Current} and
229
+ * {@link EnyoOnboardingV2DynamicKind.DeviceIp} have nothing to resolve
230
+ * against.
231
+ *
232
+ * The picker renders what discovery found, so the guide must have scanned:
233
+ * keep {@link EnyoOnboardingV2Guide.requiresNetworkScan} at its default, or
234
+ * place a {@link block.networkScan} ahead of this one.
235
+ *
236
+ * Outcome `value`s must be {@link EnyoOnboardingV2DeviceSelectOutcome}
237
+ * members; route `not-found` to troubleshooting rather than to a step that
238
+ * assumes a device exists.
239
+ *
240
+ * Register an {@link EnyoOnboardingV2DeviceSelectHandler} to turn the pick
241
+ * into appliances — the host awaits it and binds the run to the ids it
242
+ * returns. Without one the pick binds an address and nothing more.
243
+ *
244
+ * @param id - Stable block id, unique within the guide.
245
+ * @param label - Translated trigger button text (de/en).
246
+ * @param outcomes - The `selected` / `not-found` results; each is a routing handle.
247
+ */
248
+ deviceSelect: (id: string, label: EnyoOnboardingTranslatedContent[], outcomes: EnyoOnboardingV2ActionOutcome[]) => EnyoOnboardingV2Block;
172
249
  /**
173
250
  * An auth block: the installer signs into the energy app's own account
174
251
  * system (OAuth / vendor portal).
@@ -83,6 +83,8 @@ function validateOnboardingGuideV2(guide, context) {
83
83
  warnings.push(`${at}: no content blocks.`);
84
84
  validateActionBlocks(step, at, errors, warnings);
85
85
  validateLinkBlocks(step, at, errors, warnings);
86
+ validateCredentialsBlocks(step, at, errors, warnings);
87
+ validateSelectBlocks(step, at, errors, warnings);
86
88
  validateImageBlocks(step, at, errors, warnings, context);
87
89
  validateInputBlocks(step, at, errors, warnings);
88
90
  validateAuthBlocks(step, at, errors, warnings);
@@ -191,6 +193,9 @@ function validateActionBlocks(step, at, errors, warnings) {
191
193
  if (block.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.OcppConnect) {
192
194
  validateOcppConnectOutcomes(block, at, errors);
193
195
  }
196
+ if (block.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.DeviceSelect) {
197
+ validateDeviceSelectOutcomes(block, at, errors, warnings);
198
+ }
194
199
  if (block.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.EebusPair) {
195
200
  validateEebusPairOutcomes(block, at, errors, warnings);
196
201
  }
@@ -247,6 +252,76 @@ function validateLinkBlocks(step, at, errors, warnings) {
247
252
  }
248
253
  }
249
254
  }
255
+ /**
256
+ * Validates the select blocks of a step.
257
+ *
258
+ * A select records an answer rather than routing on one, so the failures that
259
+ * matter are the ones that make the recorded value ambiguous or unreachable:
260
+ * duplicate option values (the app cannot tell which was picked) and a
261
+ * `defaultValue` naming no option (nothing is pre-selected, silently).
262
+ */
263
+ function validateSelectBlocks(step, at, errors, warnings) {
264
+ for (const block of step.blocks ?? []) {
265
+ if (block.type !== enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Select)
266
+ continue;
267
+ if (!block.label?.length) {
268
+ warnings.push(`${at}: select block "${block.id}" has no label.`);
269
+ }
270
+ if (!block.options?.length) {
271
+ errors.push(`${at}: select block "${block.id}" has no options.`);
272
+ continue;
273
+ }
274
+ if (block.options.length === 1) {
275
+ warnings.push(`${at}: select block "${block.id}" offers a single option — ` +
276
+ `there is nothing for the installer to choose.`);
277
+ }
278
+ const seen = new Set();
279
+ block.options.forEach((option, index) => {
280
+ if (!option.value?.trim()) {
281
+ errors.push(`${at}: select block "${block.id}" option ${index} has no value.`);
282
+ return;
283
+ }
284
+ if (seen.has(option.value)) {
285
+ errors.push(`${at}: select block "${block.id}" uses the option value ` +
286
+ `"${option.value}" more than once.`);
287
+ }
288
+ seen.add(option.value);
289
+ if (!option.label?.length) {
290
+ warnings.push(`${at}: select block "${block.id}" option "${option.value}" has no label.`);
291
+ }
292
+ });
293
+ if (block.defaultValue !== undefined && !seen.has(block.defaultValue)) {
294
+ errors.push(`${at}: select block "${block.id}" defaultValue "${block.defaultValue}" ` +
295
+ `matches no option.`);
296
+ }
297
+ }
298
+ }
299
+ /**
300
+ * Validates the credentials blocks of a step.
301
+ *
302
+ * A pair with no value is the failure that matters: it renders as an empty row,
303
+ * and the installer has nothing to type into the device. An untranslated label
304
+ * only degrades the display, so it is a warning.
305
+ */
306
+ function validateCredentialsBlocks(step, at, errors, warnings) {
307
+ for (const block of step.blocks ?? []) {
308
+ if (block.type !== enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Credentials)
309
+ continue;
310
+ if (!block.credentials?.length) {
311
+ errors.push(`${at}: credentials block "${block.id}" has no credentials.`);
312
+ continue;
313
+ }
314
+ block.credentials.forEach((credential, index) => {
315
+ if (!credential.value?.trim()) {
316
+ errors.push(`${at}: credentials block "${block.id}" entry ${index} has no value — ` +
317
+ `it would render as an empty row.`);
318
+ }
319
+ if (!credential.label?.length) {
320
+ warnings.push(`${at}: credentials block "${block.id}" entry ${index} has no label.`);
321
+ }
322
+ });
323
+ }
324
+ }
250
325
  /** An image-block `file` reference — the slug shape a package file is named with. */
251
326
  const FILE_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
252
327
  /**
@@ -366,6 +441,10 @@ function validateInputBlocks(step, at, errors, warnings) {
366
441
  if (!block.submitLabel?.length) {
367
442
  errors.push(`${at}: input block "${block.id}" has no submitLabel.`);
368
443
  }
444
+ if (block.validated && block.valueType === enyo_onboarding_v2_js_1.EnyoOnboardingV2InputValueType.IpAddress) {
445
+ errors.push(`${at}: input block "${block.id}" sets validated on an ip-address input — ` +
446
+ `the device test already produces the app's own verdict.`);
447
+ }
369
448
  const outcomes = block.outcomes ?? [];
370
449
  if (outcomes.length < 2) {
371
450
  errors.push(`${at}: input block "${block.id}" needs at least 2 outcomes.`);
@@ -432,6 +511,46 @@ function validateOcppConnectOutcomes(block, at, errors) {
432
511
  }
433
512
  }
434
513
  }
514
+ /** Every {@link EnyoOnboardingV2DeviceSelectOutcome} value. */
515
+ const DEVICE_SELECT_OUTCOMES = new Set(Object.values(enyo_onboarding_v2_js_1.EnyoOnboardingV2DeviceSelectOutcome));
516
+ /**
517
+ * Validates the outcomes of an {@link EnyoOnboardingV2ActionKind.DeviceSelect}
518
+ * block.
519
+ *
520
+ * The block reports one of two things — the installer picked a device, or the
521
+ * run came away without one — so its outcome `value`s are closed over
522
+ * {@link EnyoOnboardingV2DeviceSelectOutcome}. Anything else is an outcome that
523
+ * can never fire.
524
+ *
525
+ * A missing `selected` branch strands every successful pick, and a missing
526
+ * `not-found` branch strands the installer whose device is not in the list —
527
+ * both warnings rather than errors, so a guide can be staged step by step.
528
+ *
529
+ * @param block - The device-select action block being checked.
530
+ * @param at - Human-readable location prefix for messages.
531
+ * @param errors - Collector for blocking problems.
532
+ * @param warnings - Collector for advisory problems.
533
+ */
534
+ function validateDeviceSelectOutcomes(block, at, errors, warnings) {
535
+ const values = new Set();
536
+ for (const outcome of block.outcomes ?? []) {
537
+ if (!DEVICE_SELECT_OUTCOMES.has(outcome.value)) {
538
+ errors.push(`${at}: device-select block "${block.id}" has outcome value "${outcome.value}", which is not an EnyoOnboardingV2DeviceSelectOutcome member.`);
539
+ }
540
+ else if (values.has(outcome.value)) {
541
+ errors.push(`${at}: device-select block "${block.id}" wires outcome value "${outcome.value}" more than once.`);
542
+ }
543
+ values.add(outcome.value);
544
+ }
545
+ if (!values.has(enyo_onboarding_v2_js_1.EnyoOnboardingV2DeviceSelectOutcome.Selected)) {
546
+ warnings.push(`${at}: device-select block "${block.id}" has no "${enyo_onboarding_v2_js_1.EnyoOnboardingV2DeviceSelectOutcome.Selected}" outcome — ` +
547
+ 'a picked device would have nowhere to go.');
548
+ }
549
+ if (!values.has(enyo_onboarding_v2_js_1.EnyoOnboardingV2DeviceSelectOutcome.NotFound)) {
550
+ warnings.push(`${at}: device-select block "${block.id}" has no "${enyo_onboarding_v2_js_1.EnyoOnboardingV2DeviceSelectOutcome.NotFound}" outcome — ` +
551
+ 'an installer whose device is not in the list would be stranded.');
552
+ }
553
+ }
435
554
  /** Every {@link EnyoOnboardingV2EebusPairOutcome} value. */
436
555
  const EEBUS_PAIR_OUTCOMES = new Set(Object.values(enyo_onboarding_v2_js_1.EnyoOnboardingV2EebusPairOutcome));
437
556
  /**
@@ -103,6 +103,8 @@ __exportStar(require("./packages/energy-app-onboarding-v2.cjs"), exports);
103
103
  __exportStar(require("./implementations/onboarding-v2/onboarding-v2-provider-validators.cjs"), exports);
104
104
  __exportStar(require("./types/enyo-onboarding-v2-dynamic.cjs"), exports);
105
105
  __exportStar(require("./types/enyo-onboarding-v2-additional-setup.cjs"), exports);
106
+ __exportStar(require("./types/enyo-onboarding-v2-validation.cjs"), exports);
107
+ __exportStar(require("./types/enyo-onboarding-v2-device-select.cjs"), exports);
106
108
  __exportStar(require("./implementations/onboarding-v2/onboarding-v2-dynamic-validators.cjs"), exports);
107
109
  __exportStar(require("./implementations/files/define-public-file.cjs"), exports);
108
110
  __exportStar(require("./implementations/files/public-file-validators.cjs"), exports);
@@ -87,6 +87,8 @@ export * from './packages/energy-app-onboarding-v2.cjs';
87
87
  export * from './implementations/onboarding-v2/onboarding-v2-provider-validators.cjs';
88
88
  export * from './types/enyo-onboarding-v2-dynamic.cjs';
89
89
  export * from './types/enyo-onboarding-v2-additional-setup.cjs';
90
+ export * from './types/enyo-onboarding-v2-validation.cjs';
91
+ export * from './types/enyo-onboarding-v2-device-select.cjs';
90
92
  export * from './implementations/onboarding-v2/onboarding-v2-dynamic-validators.cjs';
91
93
  export * from './implementations/files/define-public-file.cjs';
92
94
  export * from './implementations/files/public-file-validators.cjs';
@@ -2,7 +2,7 @@
2
2
  * Supported interval durations for scheduled tasks.
3
3
  * Provides predefined time intervals from 1 second to 1 hour.
4
4
  */
5
- export type IntervalDuration = '1s' | '5s' | '10s' | '30s' | '1m' | '5m' | '1hr';
5
+ export type IntervalDuration = '1s' | '2s' | '3s' | '5s' | '10s' | '30s' | '1m' | '5m' | '1hr';
6
6
  /**
7
7
  * Interface for managing scheduled intervals in enyo packages.
8
8
  * Provides functionality to create and manage recurring tasks.
@@ -1,6 +1,8 @@
1
1
  import type { EnyoOnboardingV2GuidesRequest, EnyoOnboardingV2GuidesResult } from '../types/enyo-onboarding-v2-provider.cjs';
2
2
  import type { EnyoOnboardingV2DynamicRequest, EnyoOnboardingV2DynamicResult } from '../types/enyo-onboarding-v2-dynamic.cjs';
3
3
  import type { EnyoOnboardingV2AdditionalSetupRequest, EnyoOnboardingV2AdditionalSetupResult } from '../types/enyo-onboarding-v2-additional-setup.cjs';
4
+ import type { EnyoOnboardingV2ValidationHandler } from '../types/enyo-onboarding-v2-validation.cjs';
5
+ import type { EnyoOnboardingV2DeviceSelectHandler } from '../types/enyo-onboarding-v2-device-select.cjs';
4
6
  /**
5
7
  * Which run of a named guide an app means.
6
8
  *
@@ -428,4 +430,56 @@ export interface EnergyAppOnboardingV2 {
428
430
  * @returns Promise that resolves once the handler has been removed.
429
431
  */
430
432
  deregisterAdditionalSetupHandler(): Promise<void>;
433
+ /**
434
+ * Registers the handler the host calls to check a value typed into an
435
+ * {@link EnyoOnboardingV2InputBlock} that set
436
+ * {@link EnyoOnboardingV2InputBlock.validated}.
437
+ *
438
+ * One handler serves every validated input across all of this app's guides.
439
+ * Registering again replaces the previous one.
440
+ *
441
+ * A guide that declares `validated` blocks while no handler is registered is
442
+ * not broken — unanswered validation is treated as acceptance, exactly like
443
+ * a timeout — but nothing is checked, so register the handler before
444
+ * publishing guides that rely on it.
445
+ *
446
+ * @param handler - Called with the submitted value; answers valid or invalid
447
+ * @returns Promise that resolves once the handler is registered
448
+ */
449
+ registerInputValidationHandler(handler: EnyoOnboardingV2ValidationHandler): Promise<void>;
450
+ /**
451
+ * Removes the registered input validation handler. Validated inputs then
452
+ * accept every value, as if no handler had ever been registered.
453
+ *
454
+ * @returns Promise that resolves once the handler is removed
455
+ */
456
+ deregisterInputValidationHandler(): Promise<void>;
457
+ /**
458
+ * Registers the handler the host calls when an installer picks devices in an
459
+ * {@link EnyoOnboardingV2ActionKind.DeviceSelect} block, so the app can turn
460
+ * them into appliances and hand back the ids.
461
+ *
462
+ * The host awaits the answer and binds the run to the returned appliances,
463
+ * which is what fills `applianceId` for later dynamic and additional-setup
464
+ * requests on the same run.
465
+ *
466
+ * Optional. Without a handler the block still works — the run takes its
467
+ * `selected` branch and stays bound to the picked device — but no appliance
468
+ * is created, so a guide that ends there produces an address and nothing the
469
+ * energy manager can read or control.
470
+ *
471
+ * One handler serves every device-select block across all of this app's
472
+ * guides. Registering again replaces the previous one.
473
+ *
474
+ * @param handler - Called with the picked devices; answers with appliance ids
475
+ * @returns Promise that resolves once the handler is registered
476
+ */
477
+ registerDeviceSelectHandler(handler: EnyoOnboardingV2DeviceSelectHandler): Promise<void>;
478
+ /**
479
+ * Removes the registered device-select handler. Picks then create no
480
+ * appliances, as if no handler had ever been registered.
481
+ *
482
+ * @returns Promise that resolves once the handler is removed
483
+ */
484
+ deregisterDeviceSelectHandler(): Promise<void>;
431
485
  }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ /**
3
+ * Onboarding guide **v2 device selection** — the host handing an energy app the
4
+ * network devices an installer picked, and taking back the appliances the app
5
+ * made of them.
6
+ *
7
+ * An {@link EnyoOnboardingV2ActionKind.DeviceSelect} block answers "which of
8
+ * these is it?" and binds the run to the installer's pick. That binding is an
9
+ * address, not an appliance: nothing in the system yet represents the device as
10
+ * something the energy manager can read or control. This model closes that gap —
11
+ * the app is asked to turn the picked device(s) into appliances and answer with
12
+ * their ids, and the host binds the run to them so later steps have an
13
+ * `applianceId` to work with.
14
+ *
15
+ * **Distinct from {@link EnyoDeviceTestHandler}, which stays the right tool when
16
+ * the guide must branch.** A device test produces a verdict per device and an
17
+ * aggregate outcome the graph routes on — the app decides whether the device is
18
+ * usable. Here the installer already decided; there is no verdict, no branching,
19
+ * and the handler's only job is to produce appliances. Reach for the device test
20
+ * when "we could not use it" must lead somewhere different in the flow.
21
+ *
22
+ * Registering is optional. Without a handler the block still works: the run
23
+ * takes its `selected` branch and stays bound to the device, and no appliance is
24
+ * created.
25
+ *
26
+ * Pure type declarations (no runtime logic). Register the handler through
27
+ * {@link EnergyAppOnboardingV2} (`../packages/energy-app-onboarding-v2.ts`).
28
+ */
29
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Onboarding guide **v2 device selection** — the host handing an energy app the
3
+ * network devices an installer picked, and taking back the appliances the app
4
+ * made of them.
5
+ *
6
+ * An {@link EnyoOnboardingV2ActionKind.DeviceSelect} block answers "which of
7
+ * these is it?" and binds the run to the installer's pick. That binding is an
8
+ * address, not an appliance: nothing in the system yet represents the device as
9
+ * something the energy manager can read or control. This model closes that gap —
10
+ * the app is asked to turn the picked device(s) into appliances and answer with
11
+ * their ids, and the host binds the run to them so later steps have an
12
+ * `applianceId` to work with.
13
+ *
14
+ * **Distinct from {@link EnyoDeviceTestHandler}, which stays the right tool when
15
+ * the guide must branch.** A device test produces a verdict per device and an
16
+ * aggregate outcome the graph routes on — the app decides whether the device is
17
+ * usable. Here the installer already decided; there is no verdict, no branching,
18
+ * and the handler's only job is to produce appliances. Reach for the device test
19
+ * when "we could not use it" must lead somewhere different in the flow.
20
+ *
21
+ * Registering is optional. Without a handler the block still works: the run
22
+ * takes its `selected` branch and stays bound to the device, and no appliance is
23
+ * created.
24
+ *
25
+ * Pure type declarations (no runtime logic). Register the handler through
26
+ * {@link EnergyAppOnboardingV2} (`../packages/energy-app-onboarding-v2.ts`).
27
+ */
28
+ import type { EnyoNetworkDevice } from './enyo-network-device.cjs';
29
+ /**
30
+ * One "the installer picked these — make appliances of them" request.
31
+ *
32
+ * Raised when a {@link EnyoOnboardingV2ActionKind.DeviceSelect} block completes,
33
+ * so it is on the critical path of a screen someone is waiting on.
34
+ */
35
+ export interface EnyoOnboardingV2DeviceSelectRequest {
36
+ /** Correlates this request with its result. Unique per request. */
37
+ requestId: string;
38
+ /** The {@link EnyoOnboardingV2ActionBlock.id} the pick was made on. */
39
+ blockId: string;
40
+ /**
41
+ * The {@link EnyoOnboardingV2Step.name} the block sits on, so an app can tell
42
+ * two picks apart in a guide that selects more than once, and so a log line
43
+ * says where an empty answer came from.
44
+ */
45
+ stepName: string;
46
+ /**
47
+ * What the installer picked, as detected by the host. Never empty.
48
+ *
49
+ * An array because one pick can legitimately become several appliances — a
50
+ * hybrid inverter is an inverter and a battery — and because a block that
51
+ * later offers a multiple selection delivers here without a breaking change.
52
+ * A single-pick block sends exactly one entry.
53
+ */
54
+ devices: EnyoNetworkDevice[];
55
+ /** The appliance the run is already bound to, when one exists. */
56
+ applianceId?: string;
57
+ /**
58
+ * The budget for this request, in milliseconds.
59
+ *
60
+ * The host owns the clock: once it is spent the host stops waiting and
61
+ * continues as if no appliance had been produced. The handler is not
62
+ * notified, so it must bound its own work — cap socket timeouts and retries
63
+ * so they fit inside the budget.
64
+ */
65
+ timeoutMs: number;
66
+ }
67
+ /**
68
+ * An app's answer to one {@link EnyoOnboardingV2DeviceSelectRequest}.
69
+ */
70
+ export interface EnyoOnboardingV2DeviceSelectResult {
71
+ /** The {@link EnyoOnboardingV2DeviceSelectRequest.requestId} this answers. */
72
+ requestId: string;
73
+ /**
74
+ * The appliances the picked device(s) are now represented by — created just
75
+ * now, or already existing and re-confirmed. The host binds the run to them,
76
+ * which is what fills `applianceId` for later dynamic and additional-setup
77
+ * requests.
78
+ *
79
+ * Return the ids of appliances that already existed too. Omitting them
80
+ * because the app did not create them in this call leaves the run unbound to
81
+ * a device it can plainly see.
82
+ *
83
+ * An empty array is a valid answer and means no appliance could be made. It
84
+ * does **not** re-route the flow — the run still takes the block's
85
+ * `selected` branch. A guide that must go somewhere else in that case needs
86
+ * an {@link EnyoOnboardingV2ActionKind.DeviceTest} block, whose verdict is a
87
+ * routing handle.
88
+ */
89
+ applianceIds: string[];
90
+ /** Untranslated technical detail for support, e.g. why nothing was created. Not shown to users. */
91
+ detail?: string;
92
+ }
93
+ /**
94
+ * Handles the host's "make appliances of these devices" requests.
95
+ *
96
+ * One handler serves every device-select block the app's guides declare —
97
+ * switch on {@link EnyoOnboardingV2DeviceSelectRequest.blockId} (and `stepName`
98
+ * where a block id repeats across guides) when the treatment differs.
99
+ *
100
+ * Throwing is treated the same as answering with no appliance ids: the run
101
+ * continues on the block's `selected` branch, unbound. Creating an appliance is
102
+ * a convenience the flow does not depend on, so a handler must never strand an
103
+ * installer by failing loudly.
104
+ */
105
+ export type EnyoOnboardingV2DeviceSelectHandler = (request: EnyoOnboardingV2DeviceSelectRequest) => Promise<EnyoOnboardingV2DeviceSelectResult>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ /**
3
+ * Onboarding guide **v2 input validation** — the host asking an energy app
4
+ * whether a typed value is acceptable, before the run moves on.
5
+ *
6
+ * An {@link EnyoOnboardingV2InputBlock} of type
7
+ * {@link EnyoOnboardingV2InputValueType.IpAddress} already gets the app's own
8
+ * verdict: the host runs the registered {@link EnyoDeviceTestHandler} and routes
9
+ * on the outcome. `Text`, `Number` and `Password` have no such check — the value
10
+ * is recorded and the flow takes the positive outcome, whatever was typed. A
11
+ * mistyped serial number or a wrong API token is therefore accepted at the step
12
+ * that asked for it and only fails later, somewhere with no installer standing
13
+ * in front of it.
14
+ *
15
+ * This model closes that gap. An input block that opts in
16
+ * ({@link EnyoOnboardingV2InputBlock.validated}) has its value handed to the
17
+ * app, which answers valid or invalid — with a translated message the installer
18
+ * reads on the spot.
19
+ *
20
+ * **Validation is not a branch.** A rejected value keeps the installer on the
21
+ * step with the message shown; it produces no outcome and needs no transition,
22
+ * so adding `validated` to a block never changes a guide's graph. Use an
23
+ * outcome when the flow should go somewhere different, and validation when the
24
+ * answer is simply wrong.
25
+ *
26
+ * Pure type declarations (no runtime logic). Register the handler through
27
+ * {@link EnergyAppOnboardingV2} (`../packages/energy-app-onboarding-v2.ts`).
28
+ */
29
+ Object.defineProperty(exports, "__esModule", { value: true });