@enyo-energy/energy-app-sdk 1.47.0 → 1.49.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 (38) hide show
  1. package/README.md +37 -0
  2. package/dist/cjs/energy-app-permission.type.cjs +1 -0
  3. package/dist/cjs/energy-app-permission.type.d.cts +2 -1
  4. package/dist/cjs/energy-app.cjs +15 -0
  5. package/dist/cjs/energy-app.d.cts +14 -0
  6. package/dist/cjs/enyo-energy-app-sdk.d.cts +3 -0
  7. package/dist/cjs/implementations/timeseries-store/timeseries-store-validators.cjs +160 -0
  8. package/dist/cjs/implementations/timeseries-store/timeseries-store-validators.d.cts +50 -0
  9. package/dist/cjs/implementations/timeseries-store/timeseries-store-write-buffer.cjs +132 -0
  10. package/dist/cjs/implementations/timeseries-store/timeseries-store-write-buffer.d.cts +104 -0
  11. package/dist/cjs/index.cjs +4 -0
  12. package/dist/cjs/index.d.cts +4 -0
  13. package/dist/cjs/packages/energy-app-timeseries-store.cjs +2 -0
  14. package/dist/cjs/packages/energy-app-timeseries-store.d.cts +134 -0
  15. package/dist/cjs/types/enyo-data-bus-value.d.cts +12 -0
  16. package/dist/cjs/types/enyo-timeseries-store.cjs +49 -0
  17. package/dist/cjs/types/enyo-timeseries-store.d.cts +264 -0
  18. package/dist/cjs/version.cjs +1 -1
  19. package/dist/cjs/version.d.cts +1 -1
  20. package/dist/energy-app-permission.type.d.ts +2 -1
  21. package/dist/energy-app-permission.type.js +1 -0
  22. package/dist/energy-app.d.ts +14 -0
  23. package/dist/energy-app.js +15 -0
  24. package/dist/enyo-energy-app-sdk.d.ts +3 -0
  25. package/dist/implementations/timeseries-store/timeseries-store-validators.d.ts +50 -0
  26. package/dist/implementations/timeseries-store/timeseries-store-validators.js +153 -0
  27. package/dist/implementations/timeseries-store/timeseries-store-write-buffer.d.ts +104 -0
  28. package/dist/implementations/timeseries-store/timeseries-store-write-buffer.js +128 -0
  29. package/dist/index.d.ts +4 -0
  30. package/dist/index.js +4 -0
  31. package/dist/packages/energy-app-timeseries-store.d.ts +134 -0
  32. package/dist/packages/energy-app-timeseries-store.js +1 -0
  33. package/dist/types/enyo-data-bus-value.d.ts +12 -0
  34. package/dist/types/enyo-timeseries-store.d.ts +264 -0
  35. package/dist/types/enyo-timeseries-store.js +46 -0
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/package.json +1 -1
package/README.md CHANGED
@@ -411,6 +411,7 @@ Energy Apps use a granular permissions system to control access to system resour
411
411
  - **`Subscriptions`**: Read the subscriptions bought for the app on this device
412
412
  - **`Room`**: Read the rooms of the home the customer set up in the enyo app
413
413
  - **`RoomTemperatures`**: Read the current and past temperatures measured in those rooms
414
+ - **`TimeseriesStore`**: Store the app's own number and text timeseries and query them raw or aggregated
414
415
 
415
416
  #### Site & Identity Permissions
416
417
 
@@ -2461,6 +2462,42 @@ Notes worth respecting when producing a report:
2461
2462
 
2462
2463
  Requires the `Savings` permission.
2463
2464
 
2465
+ #### `useTimeseriesStore(): EnergyAppTimeseriesStore`
2466
+
2467
+ Store your app's own timeseries — numbers (temperatures, internal registers, learned parameters) and text (states, modes, error codes) — and query them back. Use it for anything that is not already on the data bus (`useTimeseries()` covers that) and that changes over time (`useStorage()` rewrites a whole object per key and is the wrong fit).
2468
+
2469
+ - **Define once.** `defineSeries({name, valueType, unit?, retentionDays?})` is idempotent, so call it on every start. The `valueType` (`Number` or `Text`) is fixed so the host can store numbers compressed and text deduplicated. Series belong to your package only.
2470
+ - **Write in batches.** `write(points)` takes up to 1000 points across several series. A point is identified by series, tags and timestamp, so writing it again overwrites it and retries are safe. Invalid points come back in `rejected`, and the rest of the batch is stored. For values that arrive one at a time, use `TimeseriesStoreWriteBuffer`: it stamps each point when added, writes in batches on size or on an interval, keeps points across failed writes, and flushes on `dispose()`.
2471
+ - **Tags are for a few fixed dimensions** (`{phase: 'L1'}`, `{circuit: '2'}`), at most 8 per point. Every combination is its own stream, so never put changing values into tags.
2472
+ - **Query.**
2473
+ - `queryPoints({series, fromIso, untilIso, tags?, order?, limit?, cursor?})` returns raw points page by page.
2474
+ - `queryAggregated({series, fromIso, untilIso, resolution, aggregations, tags?, groupByTags?})` returns host-side buckets (`'1m'` to `'1d'`, UTC-aligned) with `avg`/`min`/`max`/`sum` (number series only) and `count`/`first`/`last`. Only non-empty buckets are returned.
2475
+ - `getLatest(series, tags?)` returns the newest point per stream, e.g. to restore state after a restart.
2476
+ - **Clean up.** The host drops points after the retention. `deletePoints({series, fromIso, untilIso, tags?})` removes a range, and `deleteSeries(name)` removes everything.
2477
+
2478
+ ```typescript
2479
+ const store = energyApp.useTimeseriesStore();
2480
+ await store.defineSeries({name: 'compressor.temperature', valueType: EnyoTimeseriesStoreValueTypeEnum.Number, unit: '°C', retentionDays: 90});
2481
+ await store.defineSeries({name: 'compressor.state', valueType: EnyoTimeseriesStoreValueTypeEnum.Text});
2482
+
2483
+ const buffer = new TimeseriesStoreWriteBuffer({store, interval: energyApp.useInterval(), flushInterval: '30s'});
2484
+ energyApp.onShutdown(() => buffer.dispose());
2485
+
2486
+ buffer.add({series: 'compressor.temperature', value: 61.5, tags: {circuit: '1'}});
2487
+ buffer.add({series: 'compressor.state', value: 'defrost'});
2488
+
2489
+ const hourly = await store.queryAggregated({
2490
+ series: 'compressor.temperature',
2491
+ fromIso: '2026-11-01T00:00:00Z',
2492
+ untilIso: '2026-11-02T00:00:00Z',
2493
+ resolution: '1h',
2494
+ aggregations: [EnyoTimeseriesStoreAggregationEnum.Avg, EnyoTimeseriesStoreAggregationEnum.Max],
2495
+ groupByTags: ['circuit'],
2496
+ });
2497
+ ```
2498
+
2499
+ Validate during development with `validateTimeseriesStoreSeriesDefinition`, `validateTimeseriesStorePoints` and `validateTimeseriesStoreAggregatedQuery`. Requires the `TimeseriesStore` permission.
2500
+
2464
2501
  #### `useCommandLog(): EnergyAppCommandLog`
2465
2502
 
2466
2503
  Record which register, configuration key or message your app wrote to an appliance — and read those
@@ -71,4 +71,5 @@ var EnergyAppPermissionTypeEnum;
71
71
  EnergyAppPermissionTypeEnum["Subscriptions"] = "Subscriptions";
72
72
  EnergyAppPermissionTypeEnum["Room"] = "Room";
73
73
  EnergyAppPermissionTypeEnum["RoomTemperatures"] = "RoomTemperatures";
74
+ EnergyAppPermissionTypeEnum["TimeseriesStore"] = "TimeseriesStore";
74
75
  })(EnergyAppPermissionTypeEnum || (exports.EnergyAppPermissionTypeEnum = EnergyAppPermissionTypeEnum = {}));
@@ -66,7 +66,8 @@ export declare enum EnergyAppPermissionTypeEnum {
66
66
  Webhook = "Webhook",
67
67
  Subscriptions = "Subscriptions",
68
68
  Room = "Room",
69
- RoomTemperatures = "RoomTemperatures"
69
+ RoomTemperatures = "RoomTemperatures",
70
+ TimeseriesStore = "TimeseriesStore"
70
71
  }
71
72
  /**
72
73
  * String union of every permission name, derived from
@@ -578,6 +578,21 @@ class EnergyApp {
578
578
  useRoomTemperatures() {
579
579
  return this.energyAppSdk.useRoomTemperatures();
580
580
  }
581
+ /**
582
+ * Gets the Timeseries Store API for storing the app's own timeseries —
583
+ * numbers and text — and querying them back raw or aggregated by the host.
584
+ *
585
+ * Define each series once with `defineSeries()`, write points in batches
586
+ * with `write()` (or through a `TimeseriesStoreWriteBuffer`), and read them
587
+ * with `queryPoints()`, `queryAggregated()` or `getLatest()`. Every series
588
+ * belongs to this package; the host drops points after the series' retention.
589
+ * @returns The Timeseries Store API instance
590
+ * @throws {EnergyAppPermissionNotGrantedError} If the `TimeseriesStore`
591
+ * permission is not granted.
592
+ */
593
+ useTimeseriesStore() {
594
+ return this.energyAppSdk.useTimeseriesStore();
595
+ }
581
596
  /**
582
597
  * Gets the current SDK version.
583
598
  * @returns The semantic version string of the SDK
@@ -55,6 +55,7 @@ import { EnergyAppWebhook } from "./packages/energy-app-webhook.cjs";
55
55
  import { EnergyAppSubscriptions } from "./packages/energy-app-subscriptions.cjs";
56
56
  import { EnergyAppRoom } from "./packages/energy-app-room.cjs";
57
57
  import { EnergyAppRoomTemperatures } from "./packages/energy-app-room-temperatures.cjs";
58
+ import { EnergyAppTimeseriesStore } from "./packages/energy-app-timeseries-store.cjs";
58
59
  import { UseFetchOptions } from "./types/enyo-fetch.cjs";
59
60
  /**
60
61
  * Concrete implementation of {@link EnyoEnergyAppSdk} that delegates every call
@@ -493,6 +494,19 @@ export declare class EnergyApp implements EnyoEnergyAppSdk {
493
494
  * permission is not granted.
494
495
  */
495
496
  useRoomTemperatures(): EnergyAppRoomTemperatures;
497
+ /**
498
+ * Gets the Timeseries Store API for storing the app's own timeseries —
499
+ * numbers and text — and querying them back raw or aggregated by the host.
500
+ *
501
+ * Define each series once with `defineSeries()`, write points in batches
502
+ * with `write()` (or through a `TimeseriesStoreWriteBuffer`), and read them
503
+ * with `queryPoints()`, `queryAggregated()` or `getLatest()`. Every series
504
+ * belongs to this package; the host drops points after the series' retention.
505
+ * @returns The Timeseries Store API instance
506
+ * @throws {EnergyAppPermissionNotGrantedError} If the `TimeseriesStore`
507
+ * permission is not granted.
508
+ */
509
+ useTimeseriesStore(): EnergyAppTimeseriesStore;
496
510
  /**
497
511
  * Gets the current SDK version.
498
512
  * @returns The semantic version string of the SDK
@@ -54,6 +54,7 @@ import { EnergyAppWebhook } from "./packages/energy-app-webhook.cjs";
54
54
  import { EnergyAppSubscriptions } from "./packages/energy-app-subscriptions.cjs";
55
55
  import { EnergyAppRoom } from "./packages/energy-app-room.cjs";
56
56
  import { EnergyAppRoomTemperatures } from "./packages/energy-app-room-temperatures.cjs";
57
+ import { EnergyAppTimeseriesStore } from "./packages/energy-app-timeseries-store.cjs";
57
58
  import { UseFetchOptions } from "./types/enyo-fetch.cjs";
58
59
  export declare enum EnergyAppStateEnum {
59
60
  Launching = "launching",
@@ -204,4 +205,6 @@ export interface EnyoEnergyAppSdk {
204
205
  useRoom: () => EnergyAppRoom;
205
206
  /** Get the Room Temperatures API for reading the current and past temperatures measured in the rooms of the home */
206
207
  useRoomTemperatures: () => EnergyAppRoomTemperatures;
208
+ /** Get the Timeseries Store API for storing the app's own number and text timeseries and querying them raw or aggregated */
209
+ useTimeseriesStore: () => EnergyAppTimeseriesStore;
207
210
  }
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isValidTimeseriesStoreName = isValidTimeseriesStoreName;
4
+ exports.validateTimeseriesStoreSeriesDefinition = validateTimeseriesStoreSeriesDefinition;
5
+ exports.validateTimeseriesStorePoint = validateTimeseriesStorePoint;
6
+ exports.validateTimeseriesStorePoints = validateTimeseriesStorePoints;
7
+ exports.validateTimeseriesStoreAggregatedQuery = validateTimeseriesStoreAggregatedQuery;
8
+ const enyo_timeseries_store_js_1 = require("../../types/enyo-timeseries-store.cjs");
9
+ /** Allowed series names and tag keys: letters, digits, `.`, `_`, `-`, starting with a letter or digit. */
10
+ const NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
11
+ /** Aggregations that need numbers and are therefore invalid on a text series. */
12
+ const NUMERIC_AGGREGATIONS = new Set([
13
+ enyo_timeseries_store_js_1.EnyoTimeseriesStoreAggregationEnum.Avg,
14
+ enyo_timeseries_store_js_1.EnyoTimeseriesStoreAggregationEnum.Min,
15
+ enyo_timeseries_store_js_1.EnyoTimeseriesStoreAggregationEnum.Max,
16
+ enyo_timeseries_store_js_1.EnyoTimeseriesStoreAggregationEnum.Sum,
17
+ ]);
18
+ /**
19
+ * Whether a string is a valid series name or tag key.
20
+ *
21
+ * @param name - The name to check.
22
+ * @returns `true` for 1 to {@link TIMESERIES_STORE_MAX_NAME_LENGTH} letters, digits, `.`, `_` or `-`, starting with a letter or digit.
23
+ */
24
+ function isValidTimeseriesStoreName(name) {
25
+ return typeof name === 'string'
26
+ && name.length <= enyo_timeseries_store_js_1.TIMESERIES_STORE_MAX_NAME_LENGTH
27
+ && NAME_PATTERN.test(name);
28
+ }
29
+ /**
30
+ * Validates a series definition before it is passed to `defineSeries`.
31
+ *
32
+ * @param definition - The definition to check.
33
+ * @returns The {@link TimeseriesStoreValidationResult}.
34
+ */
35
+ function validateTimeseriesStoreSeriesDefinition(definition) {
36
+ const errors = [];
37
+ if (!isValidTimeseriesStoreName(definition.name)) {
38
+ errors.push(`invalid series name '${definition.name}'`);
39
+ }
40
+ if (!Object.values(enyo_timeseries_store_js_1.EnyoTimeseriesStoreValueTypeEnum).includes(definition.valueType)) {
41
+ errors.push(`invalid valueType '${String(definition.valueType)}'`);
42
+ }
43
+ if (definition.retentionDays !== undefined
44
+ && !(Number.isInteger(definition.retentionDays) && definition.retentionDays > 0)) {
45
+ errors.push('retentionDays must be a positive integer');
46
+ }
47
+ return { ok: errors.length === 0, errors };
48
+ }
49
+ /**
50
+ * Checks one point to be written and returns why it is invalid.
51
+ *
52
+ * @param point - The point to check.
53
+ * @param valueType - Value type of the point's series, when known; without it the value is only checked to be a finite number or a string.
54
+ * @returns The reason the point is invalid, or `undefined` when it is valid.
55
+ */
56
+ function validateTimeseriesStorePoint(point, valueType) {
57
+ if (!isValidTimeseriesStoreName(point.series)) {
58
+ return `invalid series name '${point.series}'`;
59
+ }
60
+ if (point.timestampIso !== undefined && Number.isNaN(Date.parse(point.timestampIso))) {
61
+ return `invalid timestampIso '${point.timestampIso}'`;
62
+ }
63
+ const isNumber = typeof point.value === 'number';
64
+ if (isNumber && !Number.isFinite(point.value)) {
65
+ return 'value must be a finite number';
66
+ }
67
+ if (!isNumber && typeof point.value !== 'string') {
68
+ return 'value must be a number or a string';
69
+ }
70
+ if (typeof point.value === 'string' && point.value.length > enyo_timeseries_store_js_1.TIMESERIES_STORE_MAX_TEXT_LENGTH) {
71
+ return `text value longer than ${enyo_timeseries_store_js_1.TIMESERIES_STORE_MAX_TEXT_LENGTH} characters`;
72
+ }
73
+ if (valueType === enyo_timeseries_store_js_1.EnyoTimeseriesStoreValueTypeEnum.Number && !isNumber) {
74
+ return `series '${point.series}' holds numbers, got a string`;
75
+ }
76
+ if (valueType === enyo_timeseries_store_js_1.EnyoTimeseriesStoreValueTypeEnum.Text && isNumber) {
77
+ return `series '${point.series}' holds text, got a number`;
78
+ }
79
+ return validateTimeseriesStoreTags(point.tags);
80
+ }
81
+ /**
82
+ * Checks a batch of points to be written, the way the host does.
83
+ *
84
+ * @param points - The batch to check.
85
+ * @param valueTypes - Value type per series name, when known (e.g. from the app's own series definitions).
86
+ * @returns The invalid points with their index and reason — the same shape as
87
+ * {@link EnyoTimeseriesStoreWriteResult.rejected}. A batch larger than
88
+ * {@link TIMESERIES_STORE_MAX_POINTS_PER_WRITE} rejects every point beyond the limit.
89
+ */
90
+ function validateTimeseriesStorePoints(points, valueTypes) {
91
+ const rejected = [];
92
+ points.forEach((point, index) => {
93
+ const reason = index >= enyo_timeseries_store_js_1.TIMESERIES_STORE_MAX_POINTS_PER_WRITE
94
+ ? `batch exceeds ${enyo_timeseries_store_js_1.TIMESERIES_STORE_MAX_POINTS_PER_WRITE} points`
95
+ : validateTimeseriesStorePoint(point, valueTypes?.[point.series]);
96
+ if (reason) {
97
+ rejected.push({ index, reason });
98
+ }
99
+ });
100
+ return rejected;
101
+ }
102
+ /**
103
+ * Validates an aggregated query before it is passed to `queryAggregated`.
104
+ *
105
+ * @param query - The query to check.
106
+ * @param valueType - Value type of the queried series, when known; needed to reject numeric aggregations on a text series.
107
+ * @returns The {@link TimeseriesStoreValidationResult}.
108
+ */
109
+ function validateTimeseriesStoreAggregatedQuery(query, valueType) {
110
+ const errors = [];
111
+ if (!isValidTimeseriesStoreName(query.series)) {
112
+ errors.push(`invalid series name '${query.series}'`);
113
+ }
114
+ const from = Date.parse(query.fromIso);
115
+ const until = Date.parse(query.untilIso);
116
+ if (Number.isNaN(from) || Number.isNaN(until)) {
117
+ errors.push('fromIso and untilIso must be valid ISO 8601 timestamps');
118
+ }
119
+ else if (from >= until) {
120
+ errors.push('fromIso must be before untilIso');
121
+ }
122
+ if (query.aggregations.length === 0) {
123
+ errors.push('at least one aggregation is required');
124
+ }
125
+ if (valueType === enyo_timeseries_store_js_1.EnyoTimeseriesStoreValueTypeEnum.Text) {
126
+ const numeric = query.aggregations.filter((aggregation) => NUMERIC_AGGREGATIONS.has(aggregation));
127
+ if (numeric.length > 0) {
128
+ errors.push(`aggregations ${numeric.join(', ')} need a number series`);
129
+ }
130
+ }
131
+ const tagError = validateTimeseriesStoreTags(query.tags);
132
+ if (tagError) {
133
+ errors.push(tagError);
134
+ }
135
+ return { ok: errors.length === 0, errors };
136
+ }
137
+ /**
138
+ * Checks the tags of a point or a query filter.
139
+ *
140
+ * @param tags - The tags, if any.
141
+ * @returns The reason the tags are invalid, or `undefined` when they are valid.
142
+ */
143
+ function validateTimeseriesStoreTags(tags) {
144
+ if (!tags) {
145
+ return undefined;
146
+ }
147
+ const entries = Object.entries(tags);
148
+ if (entries.length > enyo_timeseries_store_js_1.TIMESERIES_STORE_MAX_TAGS) {
149
+ return `more than ${enyo_timeseries_store_js_1.TIMESERIES_STORE_MAX_TAGS} tags`;
150
+ }
151
+ for (const [key, value] of entries) {
152
+ if (!isValidTimeseriesStoreName(key)) {
153
+ return `invalid tag key '${key}'`;
154
+ }
155
+ if (typeof value !== 'string' || value.length === 0 || value.length > enyo_timeseries_store_js_1.TIMESERIES_STORE_MAX_NAME_LENGTH) {
156
+ return `tag '${key}' needs a string value of 1 to ${enyo_timeseries_store_js_1.TIMESERIES_STORE_MAX_NAME_LENGTH} characters`;
157
+ }
158
+ }
159
+ return undefined;
160
+ }
@@ -0,0 +1,50 @@
1
+ import { EnyoTimeseriesStoreAggregatedQuery, EnyoTimeseriesStorePointInput, EnyoTimeseriesStoreSeriesDefinition, EnyoTimeseriesStoreValueTypeEnum, EnyoTimeseriesStoreWriteResult } from '../../types/enyo-timeseries-store.cjs';
2
+ /**
3
+ * Result of the timeseries store validators that check a whole object.
4
+ */
5
+ export interface TimeseriesStoreValidationResult {
6
+ /** `true` when {@link errors} is empty. */
7
+ ok: boolean;
8
+ /** One human-readable message per problem found. */
9
+ errors: string[];
10
+ }
11
+ /**
12
+ * Whether a string is a valid series name or tag key.
13
+ *
14
+ * @param name - The name to check.
15
+ * @returns `true` for 1 to {@link TIMESERIES_STORE_MAX_NAME_LENGTH} letters, digits, `.`, `_` or `-`, starting with a letter or digit.
16
+ */
17
+ export declare function isValidTimeseriesStoreName(name: string): boolean;
18
+ /**
19
+ * Validates a series definition before it is passed to `defineSeries`.
20
+ *
21
+ * @param definition - The definition to check.
22
+ * @returns The {@link TimeseriesStoreValidationResult}.
23
+ */
24
+ export declare function validateTimeseriesStoreSeriesDefinition(definition: EnyoTimeseriesStoreSeriesDefinition): TimeseriesStoreValidationResult;
25
+ /**
26
+ * Checks one point to be written and returns why it is invalid.
27
+ *
28
+ * @param point - The point to check.
29
+ * @param valueType - Value type of the point's series, when known; without it the value is only checked to be a finite number or a string.
30
+ * @returns The reason the point is invalid, or `undefined` when it is valid.
31
+ */
32
+ export declare function validateTimeseriesStorePoint(point: EnyoTimeseriesStorePointInput, valueType?: EnyoTimeseriesStoreValueTypeEnum): string | undefined;
33
+ /**
34
+ * Checks a batch of points to be written, the way the host does.
35
+ *
36
+ * @param points - The batch to check.
37
+ * @param valueTypes - Value type per series name, when known (e.g. from the app's own series definitions).
38
+ * @returns The invalid points with their index and reason — the same shape as
39
+ * {@link EnyoTimeseriesStoreWriteResult.rejected}. A batch larger than
40
+ * {@link TIMESERIES_STORE_MAX_POINTS_PER_WRITE} rejects every point beyond the limit.
41
+ */
42
+ export declare function validateTimeseriesStorePoints(points: EnyoTimeseriesStorePointInput[], valueTypes?: Record<string, EnyoTimeseriesStoreValueTypeEnum>): EnyoTimeseriesStoreWriteResult['rejected'];
43
+ /**
44
+ * Validates an aggregated query before it is passed to `queryAggregated`.
45
+ *
46
+ * @param query - The query to check.
47
+ * @param valueType - Value type of the queried series, when known; needed to reject numeric aggregations on a text series.
48
+ * @returns The {@link TimeseriesStoreValidationResult}.
49
+ */
50
+ export declare function validateTimeseriesStoreAggregatedQuery(query: EnyoTimeseriesStoreAggregatedQuery, valueType?: EnyoTimeseriesStoreValueTypeEnum): TimeseriesStoreValidationResult;
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TimeseriesStoreWriteBuffer = void 0;
4
+ const enyo_timeseries_store_js_1 = require("../../types/enyo-timeseries-store.cjs");
5
+ /**
6
+ * Collects timeseries points in memory and writes them to the
7
+ * {@link EnergyAppTimeseriesStore} in batches — the efficient way to record
8
+ * values that arrive one at a time (every poll, every data bus message).
9
+ *
10
+ * Points get their timestamp when they are added, not when they are written,
11
+ * so batching never shifts them in time. A failed write keeps the points and
12
+ * retries them on the next flush; since writes overwrite points with the same
13
+ * series, tags and timestamp, a retry never duplicates data.
14
+ *
15
+ * Call {@link dispose} on shutdown: it stops the interval and writes what is
16
+ * still buffered.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const buffer = new TimeseriesStoreWriteBuffer({
21
+ * store: energyApp.useTimeseriesStore(),
22
+ * interval: energyApp.useInterval(),
23
+ * });
24
+ * buffer.add({series: 'compressor.temperature', value: 61.5});
25
+ * energyApp.onShutdown(() => buffer.dispose());
26
+ * ```
27
+ */
28
+ class TimeseriesStoreWriteBuffer {
29
+ store;
30
+ interval;
31
+ maxPoints;
32
+ maxBufferedPoints;
33
+ onRejected;
34
+ onError;
35
+ intervalId;
36
+ buffer = [];
37
+ flushing;
38
+ /**
39
+ * Creates the buffer and, when an interval API is given, starts flushing periodically.
40
+ *
41
+ * @param options - See {@link TimeseriesStoreWriteBufferOptions}.
42
+ */
43
+ constructor(options) {
44
+ this.store = options.store;
45
+ this.interval = options.interval;
46
+ this.maxPoints = Math.max(1, Math.min(options.maxPoints ?? enyo_timeseries_store_js_1.TIMESERIES_STORE_MAX_POINTS_PER_WRITE, enyo_timeseries_store_js_1.TIMESERIES_STORE_MAX_POINTS_PER_WRITE));
47
+ this.maxBufferedPoints = Math.max(this.maxPoints, options.maxBufferedPoints ?? this.maxPoints * 10);
48
+ this.onRejected = options.onRejected;
49
+ this.onError = options.onError;
50
+ if (this.interval) {
51
+ this.intervalId = this.interval.createInterval(options.flushInterval ?? '30s', () => this.flush());
52
+ }
53
+ }
54
+ /** Number of points waiting to be written. */
55
+ get size() {
56
+ return this.buffer.length;
57
+ }
58
+ /**
59
+ * Adds points to the buffer, stamping those without `timestampIso` with the
60
+ * current time. Starts a flush when {@link TimeseriesStoreWriteBufferOptions.maxPoints} is reached.
61
+ *
62
+ * @param points - One point or several.
63
+ */
64
+ add(points) {
65
+ const nowIso = new Date().toISOString();
66
+ for (const point of Array.isArray(points) ? points : [points]) {
67
+ this.buffer.push(point.timestampIso ? point : { ...point, timestampIso: nowIso });
68
+ }
69
+ this.trimToCapacity();
70
+ if (this.buffer.length >= this.maxPoints) {
71
+ void this.flush();
72
+ }
73
+ }
74
+ /**
75
+ * Writes everything buffered so far, in batches of at most
76
+ * {@link TimeseriesStoreWriteBufferOptions.maxPoints}. Concurrent calls share
77
+ * one flush. Never throws: failures go to `onError` and the points stay buffered.
78
+ *
79
+ * @returns Promise resolving once the flush has ended.
80
+ */
81
+ flush() {
82
+ if (!this.flushing) {
83
+ this.flushing = this.writeAll().finally(() => {
84
+ this.flushing = undefined;
85
+ });
86
+ }
87
+ return this.flushing;
88
+ }
89
+ /**
90
+ * Stops the periodic flush and writes the remaining points.
91
+ *
92
+ * @returns Promise resolving once the final flush has ended.
93
+ */
94
+ async dispose() {
95
+ if (this.interval && this.intervalId) {
96
+ this.interval.stopInterval(this.intervalId);
97
+ }
98
+ await this.flushing;
99
+ await this.flush();
100
+ }
101
+ /**
102
+ * Writes the buffer batch by batch until it is empty or a write fails.
103
+ */
104
+ async writeAll() {
105
+ while (this.buffer.length > 0) {
106
+ // Taken out before the write, so points added meanwhile are never mixed up with it.
107
+ const batch = this.buffer.splice(0, this.maxPoints);
108
+ try {
109
+ const result = await this.store.write(batch);
110
+ if (result.rejected.length > 0) {
111
+ this.onRejected?.(result.rejected, batch);
112
+ }
113
+ }
114
+ catch (error) {
115
+ this.buffer.unshift(...batch);
116
+ this.trimToCapacity();
117
+ this.onError?.(error);
118
+ return;
119
+ }
120
+ }
121
+ }
122
+ /**
123
+ * Drops the oldest points beyond {@link TimeseriesStoreWriteBufferOptions.maxBufferedPoints}.
124
+ */
125
+ trimToCapacity() {
126
+ const excess = this.buffer.length - this.maxBufferedPoints;
127
+ if (excess > 0) {
128
+ this.buffer.splice(0, excess);
129
+ }
130
+ }
131
+ }
132
+ exports.TimeseriesStoreWriteBuffer = TimeseriesStoreWriteBuffer;
@@ -0,0 +1,104 @@
1
+ import { EnergyAppInterval, IntervalDuration } from '../../packages/energy-app-interval.cjs';
2
+ import { EnergyAppTimeseriesStore } from '../../packages/energy-app-timeseries-store.cjs';
3
+ import { EnyoTimeseriesStorePointInput, EnyoTimeseriesStoreWriteResult } from '../../types/enyo-timeseries-store.cjs';
4
+ /**
5
+ * Options of a {@link TimeseriesStoreWriteBuffer}.
6
+ */
7
+ export interface TimeseriesStoreWriteBufferOptions {
8
+ /** The store to write to, from `energyApp.useTimeseriesStore()`. */
9
+ store: EnergyAppTimeseriesStore;
10
+ /**
11
+ * Interval API, from `energyApp.useInterval()`. When given, the buffer is
12
+ * flushed every {@link flushInterval}; without it, only on size and on
13
+ * explicit {@link TimeseriesStoreWriteBuffer.flush} calls.
14
+ */
15
+ interval?: EnergyAppInterval;
16
+ /** How often to flush when {@link interval} is given. Defaults to `'30s'`. */
17
+ flushInterval?: IntervalDuration;
18
+ /**
19
+ * Number of buffered points that triggers a flush right away. Defaults to
20
+ * and is capped at {@link TIMESERIES_STORE_MAX_POINTS_PER_WRITE}.
21
+ */
22
+ maxPoints?: number;
23
+ /**
24
+ * Most points kept while writes fail (e.g. the host is busy). The oldest are
25
+ * dropped beyond it, so a long outage cannot exhaust memory. Defaults to
26
+ * 10 × {@link maxPoints}.
27
+ */
28
+ maxBufferedPoints?: number;
29
+ /** Called with the result of every write that rejected points. */
30
+ onRejected?: (rejected: EnyoTimeseriesStoreWriteResult['rejected'], points: EnyoTimeseriesStorePointInput[]) => void;
31
+ /** Called when a write fails as a whole; the points stay buffered and are retried on the next flush. */
32
+ onError?: (error: unknown) => void;
33
+ }
34
+ /**
35
+ * Collects timeseries points in memory and writes them to the
36
+ * {@link EnergyAppTimeseriesStore} in batches — the efficient way to record
37
+ * values that arrive one at a time (every poll, every data bus message).
38
+ *
39
+ * Points get their timestamp when they are added, not when they are written,
40
+ * so batching never shifts them in time. A failed write keeps the points and
41
+ * retries them on the next flush; since writes overwrite points with the same
42
+ * series, tags and timestamp, a retry never duplicates data.
43
+ *
44
+ * Call {@link dispose} on shutdown: it stops the interval and writes what is
45
+ * still buffered.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const buffer = new TimeseriesStoreWriteBuffer({
50
+ * store: energyApp.useTimeseriesStore(),
51
+ * interval: energyApp.useInterval(),
52
+ * });
53
+ * buffer.add({series: 'compressor.temperature', value: 61.5});
54
+ * energyApp.onShutdown(() => buffer.dispose());
55
+ * ```
56
+ */
57
+ export declare class TimeseriesStoreWriteBuffer {
58
+ private readonly store;
59
+ private readonly interval?;
60
+ private readonly maxPoints;
61
+ private readonly maxBufferedPoints;
62
+ private readonly onRejected?;
63
+ private readonly onError?;
64
+ private readonly intervalId?;
65
+ private buffer;
66
+ private flushing?;
67
+ /**
68
+ * Creates the buffer and, when an interval API is given, starts flushing periodically.
69
+ *
70
+ * @param options - See {@link TimeseriesStoreWriteBufferOptions}.
71
+ */
72
+ constructor(options: TimeseriesStoreWriteBufferOptions);
73
+ /** Number of points waiting to be written. */
74
+ get size(): number;
75
+ /**
76
+ * Adds points to the buffer, stamping those without `timestampIso` with the
77
+ * current time. Starts a flush when {@link TimeseriesStoreWriteBufferOptions.maxPoints} is reached.
78
+ *
79
+ * @param points - One point or several.
80
+ */
81
+ add(points: EnyoTimeseriesStorePointInput | EnyoTimeseriesStorePointInput[]): void;
82
+ /**
83
+ * Writes everything buffered so far, in batches of at most
84
+ * {@link TimeseriesStoreWriteBufferOptions.maxPoints}. Concurrent calls share
85
+ * one flush. Never throws: failures go to `onError` and the points stay buffered.
86
+ *
87
+ * @returns Promise resolving once the flush has ended.
88
+ */
89
+ flush(): Promise<void>;
90
+ /**
91
+ * Stops the periodic flush and writes the remaining points.
92
+ *
93
+ * @returns Promise resolving once the final flush has ended.
94
+ */
95
+ dispose(): Promise<void>;
96
+ /**
97
+ * Writes the buffer batch by batch until it is empty or a write fails.
98
+ */
99
+ private writeAll;
100
+ /**
101
+ * Drops the oldest points beyond {@link TimeseriesStoreWriteBufferOptions.maxBufferedPoints}.
102
+ */
103
+ private trimToCapacity;
104
+ }
@@ -180,6 +180,10 @@ __exportStar(require("./types/enyo-savings.cjs"), exports);
180
180
  __exportStar(require("./packages/energy-app-savings.cjs"), exports);
181
181
  __exportStar(require("./types/enyo-command-log.cjs"), exports);
182
182
  __exportStar(require("./packages/energy-app-command-log.cjs"), exports);
183
+ __exportStar(require("./types/enyo-timeseries-store.cjs"), exports);
184
+ __exportStar(require("./packages/energy-app-timeseries-store.cjs"), exports);
185
+ __exportStar(require("./implementations/timeseries-store/timeseries-store-validators.cjs"), exports);
186
+ __exportStar(require("./implementations/timeseries-store/timeseries-store-write-buffer.cjs"), exports);
183
187
  __exportStar(require("./types/enyo-epex-spot-price.cjs"), exports);
184
188
  __exportStar(require("./packages/energy-app-epex-spot-price.cjs"), exports);
185
189
  __exportStar(require("./types/enyo-automation.cjs"), exports);
@@ -164,6 +164,10 @@ export * from './types/enyo-savings.cjs';
164
164
  export * from './packages/energy-app-savings.cjs';
165
165
  export * from './types/enyo-command-log.cjs';
166
166
  export * from './packages/energy-app-command-log.cjs';
167
+ export * from './types/enyo-timeseries-store.cjs';
168
+ export * from './packages/energy-app-timeseries-store.cjs';
169
+ export * from './implementations/timeseries-store/timeseries-store-validators.cjs';
170
+ export * from './implementations/timeseries-store/timeseries-store-write-buffer.cjs';
167
171
  export * from './types/enyo-epex-spot-price.cjs';
168
172
  export * from './packages/energy-app-epex-spot-price.cjs';
169
173
  export * from './types/enyo-automation.cjs';
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });