oneentry 1.0.155 → 1.0.156
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 +19 -0
- package/changelog.md +65 -1
- package/dist/base/syncModules.d.ts +6 -109
- package/dist/base/syncModules.js +6 -276
- package/dist/base/timeIntervals.d.ts +95 -0
- package/dist/base/timeIntervals.js +321 -0
- package/dist/base/utils.d.ts +63 -5
- package/dist/base/validation.js +0 -1
- package/dist/forms/formsInterfaces.d.ts +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -181,6 +181,25 @@ const api = defineOneEntry('your-url', {
|
|
|
181
181
|
|
|
182
182
|
OneEntry SDK supports optional validation of API responses using Zod. This feature is disabled by default and can be enabled for development or critical operations.
|
|
183
183
|
|
|
184
|
+
### Time Intervals
|
|
185
|
+
|
|
186
|
+
Attributes of type `timeInterval` return a compact recurrence rule (an anchor date, daily time ranges and repeat flags), not a ready list of slots. The SDK does not expand it eagerly — a single attribute can materialize into megabytes of slots — so resolve it on demand with `expandAttributeTimeIntervals`, passing the window you actually render:
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
import { defineOneEntry, expandAttributeTimeIntervals } from 'oneentry'
|
|
190
|
+
|
|
191
|
+
const { Pages } = defineOneEntry('your-url', { token: 'your-app-token' })
|
|
192
|
+
const page = await Pages.getPageByUrl('booking')
|
|
193
|
+
|
|
194
|
+
const slots = expandAttributeTimeIntervals(page.attributeValues.interval, {
|
|
195
|
+
from: '2025-04-01',
|
|
196
|
+
to: '2025-04-30',
|
|
197
|
+
})
|
|
198
|
+
// [['2025-04-14T09:00:00.000Z', '2025-04-14T10:00:00.000Z'], …]
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Also exported: `expandTimeIntervals(schedule, window)` for a single schedule (e.g. a form's `localizeInfos.intervals`), and the `isTimeIntervalAttribute` type guard.
|
|
202
|
+
|
|
184
203
|
### Errors
|
|
185
204
|
|
|
186
205
|
If you want to escape errors inside the sc, leave the "errors" property by default.
|
package/changelog.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# SDK Change Log
|
|
2
2
|
|
|
3
|
+
## v.1.0.156
|
|
4
|
+
|
|
5
|
+
### What's New
|
|
6
|
+
|
|
7
|
+
- `expandAttributeTimeIntervals(attr, { from, to })` — new top-level export that resolves a whole `timeInterval` attribute into concrete `[start, end]` ISO pairs for a window you choose. This is the one-call replacement for the removed `timeIntervals` field: it walks the attribute's groups and schedules and merges the results (merging matters — deduplication and ordering only hold within a single schedule). Anything that is not a `timeInterval` attribute yields an empty array, so it is safe to call without checking `type` first.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { expandAttributeTimeIntervals } from 'oneentry';
|
|
11
|
+
|
|
12
|
+
const slots = expandAttributeTimeIntervals(page.attributeValues.interval, {
|
|
13
|
+
from: '2025-04-01',
|
|
14
|
+
to: '2025-04-30',
|
|
15
|
+
});
|
|
16
|
+
// [['2025-04-14T09:00:00.000Z', '2025-04-14T10:00:00.000Z'], …]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `expandTimeIntervals(schedule, { from, to })` — new top-level export that resolves a single schedule. Accepts both shapes the API returns: entity schedules (`attributeValues[marker].value[].values[]` on pages, products, blocks and attribute sets) and form schedules (`attributes[].localizeInfos.intervals[]`). Use it when you already hold one schedule — most notably on forms, whose schedules are already typed:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
const field = form.attributes.find((a) => a.marker === 'booking');
|
|
23
|
+
|
|
24
|
+
const slots = (field?.localizeInfos.intervals ?? []).flatMap((schedule) =>
|
|
25
|
+
expandTimeIntervals(schedule, { from: '2025-05-01', to: '2025-05-31' }),
|
|
26
|
+
);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Both functions are pure — they do not mutate their input and perform no requests.
|
|
30
|
+
|
|
31
|
+
- `isTimeIntervalAttribute(attr)` — new exported type guard narrowing an `IAttributeValue` to `ITimeIntervalAttributeValue`. `IAttributeValue.value` is `unknown` because its shape depends on `type`; this guard is what lets you reach the schedules without a cast:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const attr = page.attributeValues.interval;
|
|
35
|
+
if (isTimeIntervalAttribute(attr)) {
|
|
36
|
+
attr.value[0].values[0].dates; // fully typed
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- `ITimeIntervalAttributeValue`, `ITimeIntervalGroup`, `ITimeIntervalEntitySchedule`, `ITimeIntervalWindow` and `TimeIntervalPair` — new exported types covering the whole `timeInterval` payload: the attribute, its groups, an entity schedule, the expansion window and a resolved slot. `ITimeIntervalSchedule` and `IAttributeValue` are now exported for the same reason. The payload previously had no typed representation at all.
|
|
41
|
+
|
|
42
|
+
### What's Changed
|
|
43
|
+
|
|
44
|
+
- `ITimeIntervalSchedule` — `range` is typed `string[]` instead of `unknown[]` (it is a pair of ISO dates), and the optional `inEveryWeek` flag is documented; the SDK always read it, but the interface omitted it.
|
|
45
|
+
|
|
46
|
+
### Bug Fixes
|
|
47
|
+
|
|
48
|
+
These affect `expandTimeIntervals`, which replaces the removed built-in expansion:
|
|
49
|
+
|
|
50
|
+
- The expansion horizon is no longer hardcoded. Monthly recurrence stopped exactly 12 months after the anchor and weekly recurrence stopped at the end of the anchor's month, so slots beyond that could not be obtained at all. The window is now the horizon.
|
|
51
|
+
- A schedule with no recurrence flags (`inEveryWeek: false, inEveryMonth: false`) produced no intervals at all — `_processScheduleDates` had no branch for that case. A plain date range now yields slots for every day in it.
|
|
52
|
+
- Weekly recurrence combined with monthly no longer emits dates **before** the schedule's start date. The old branch walked from the 1st of the anchor's month, so a schedule starting Monday 2025-04-14 also emitted 2025-04-07.
|
|
53
|
+
- Weekly recurrence was host-timezone dependent: it computed its end-of-month bound with `getFullYear`/`getMonth` (local time) while every other date operation used UTC, so the result shifted with the machine's timezone. All arithmetic is now UTC.
|
|
54
|
+
- Weekly+monthly recurrence silently dropped months: it advanced the month before pinning the day to the 1st, so an anchor on the 31st overflowed short months and lost 5 of 12 months.
|
|
55
|
+
- Identical intervals are now actually deduplicated. The old code collected into a `Set` of array references, which compares by identity, so duplicates always survived despite the intent.
|
|
56
|
+
- A range with a non-positive `period` no longer hangs. The slot loop never advanced its cursor and spun forever; such ranges are now skipped.
|
|
57
|
+
- Malformed input no longer throws. `_addTimeIntervalsToFormSchedules` dereferenced its argument unguarded (`TypeError` on `undefined`), and both expanders read `dates[0]` / `range[0]` without checking the array existed. `expandTimeIntervals` returns an empty array instead.
|
|
58
|
+
|
|
59
|
+
### What's Deleted
|
|
60
|
+
|
|
61
|
+
- **Breaking** — the computed `timeIntervals` field is no longer added to `timeInterval` attribute values. It was injected into every response carrying a `timeInterval` attribute (pages, products, blocks, attribute sets and forms) and materialized a full year of slots regardless of what the caller needed: a single attribute with hourly slots expanded to roughly 2,000 lines of JSON, and finer slot periods reached megabytes — enough to blow past framework data-cache limits. The field was never declared in any interface or Zod schema, so TypeScript consumers could only reach it through a cast.
|
|
62
|
+
|
|
63
|
+
Migrate by calling `expandAttributeTimeIntervals(attr, window)` with the window you actually render. The source data it expands (`dates`/`range`, `times`/`intervals`, `inEveryWeek`, `inEveryMonth`) is unchanged and still on every schedule, so nothing is lost — it is now resolved on demand instead of eagerly, and the compact rule is what gets cached.
|
|
64
|
+
|
|
65
|
+
- `_addTimeIntervalsToSchedules` and `_addTimeIntervalsToFormSchedules` — removed from every module. Despite the `_` prefix these were public and callable (e.g. `Pages._addTimeIntervalsToSchedules`). Use `expandTimeIntervals`.
|
|
66
|
+
|
|
3
67
|
## v.1.0.155
|
|
4
68
|
|
|
5
69
|
### What's New
|
|
@@ -247,7 +311,7 @@
|
|
|
247
311
|
|
|
248
312
|
- Discounts > `getAllDiscounts` — removed `'PERSONAL_BONUS'` from type filter parameter.
|
|
249
313
|
|
|
250
|
-
- Forms > `IFormsEntity` — type narrowed to `'order' | '
|
|
314
|
+
- Forms > `IFormsEntity` — type narrowed to `'order' | 'sign_in_up' | 'collection' | 'data' | 'rating'`, removed `moduleFormConfigs` field.
|
|
251
315
|
|
|
252
316
|
- Products > `getProductsEmptyPage` — changed from GET to POST, added `body` parameter, return type changed to `IAggregatedProductGroup[]`.
|
|
253
317
|
|
|
@@ -111,113 +111,6 @@ export default abstract class SyncModules {
|
|
|
111
111
|
* @returns {any} Sorted attributes.
|
|
112
112
|
*/
|
|
113
113
|
protected _sortAttributes: (data: any) => any;
|
|
114
|
-
/**
|
|
115
|
-
* Adds a specified number of days to a date.
|
|
116
|
-
* @param {Date} date - The initial date.
|
|
117
|
-
* @param {number} days - The number of days to add.
|
|
118
|
-
* @returns {any} The new date with added days.
|
|
119
|
-
*/
|
|
120
|
-
protected _addDays(date: Date, days: number): any;
|
|
121
|
-
/**
|
|
122
|
-
* Common logic for processing schedule dates (weekly, monthly, or both).
|
|
123
|
-
*
|
|
124
|
-
* Abstracts date iteration for three scheduling modes:
|
|
125
|
-
*
|
|
126
|
-
* - **`inEveryWeek` only**: starting from the start date, generates dates
|
|
127
|
-
* with a 7-day step until the end of the current month.
|
|
128
|
-
*
|
|
129
|
-
* - **`inEveryMonth` only**: pins the day-of-month from the start date
|
|
130
|
-
* and repeats it for each of the next 12 months. If the month does not
|
|
131
|
-
* have that day (e.g. Feb 31), the iteration is skipped.
|
|
132
|
-
*
|
|
133
|
-
* - **`inEveryWeek` + `inEveryMonth`**: for each of the next 12 months finds
|
|
134
|
-
* the first occurrence of the target weekday (from the start date), then
|
|
135
|
-
* iterates all occurrences of that weekday in the month with a 7-day step.
|
|
136
|
-
*
|
|
137
|
-
* `processDate(currentDate)` is called for every resolved date.
|
|
138
|
-
* @param {Date} date - The date for which to process intervals.
|
|
139
|
-
* @param {object} config - Configuration for schedule repetition.
|
|
140
|
-
* @param {boolean} config.inEveryWeek - Whether to repeat weekly.
|
|
141
|
-
* @param {boolean} config.inEveryMonth - Whether to repeat monthly.
|
|
142
|
-
* @param {(currentDate: Date) => void} processDate - Callback function to process each date.
|
|
143
|
-
*/
|
|
144
|
-
protected _processScheduleDates(date: Date, config: {
|
|
145
|
-
inEveryWeek: boolean;
|
|
146
|
-
inEveryMonth: boolean;
|
|
147
|
-
}, processDate: (currentDate: Date) => void): void;
|
|
148
|
-
/**
|
|
149
|
-
* Generates intervals for a specific date based on a schedule.
|
|
150
|
-
*
|
|
151
|
-
* For each date resolved by `_processScheduleDates`, iterates over
|
|
152
|
-
* the `schedule.times` array of time ranges. Each range is a pair
|
|
153
|
-
* `[startTime, endTime]` with `{ hours, minutes }` fields.
|
|
154
|
-
* Creates an ISO interval `[start.toISOString(), end.toISOString()]`
|
|
155
|
-
* and adds it to `utcIntervals` (Set deduplicates automatically).
|
|
156
|
-
* @param {Date} date - The date for which to generate intervals.
|
|
157
|
-
* @param {object} schedule - The schedule defining the intervals.
|
|
158
|
-
* @param {boolean} schedule.inEveryWeek - The number of weeks between intervals.
|
|
159
|
-
* @param {any[]} schedule.times - The times for each interval.
|
|
160
|
-
* @param {boolean} schedule.inEveryMonth - The month intervals for each interval.
|
|
161
|
-
* @param {Set<Array<string>>} utcIntervals - A set to store unique intervals.
|
|
162
|
-
*/
|
|
163
|
-
protected _generateIntervalsForDate(date: Date, schedule: {
|
|
164
|
-
inEveryWeek: boolean;
|
|
165
|
-
times: any[];
|
|
166
|
-
inEveryMonth: boolean;
|
|
167
|
-
}, utcIntervals: Set<Array<string>>): void;
|
|
168
|
-
/**
|
|
169
|
-
* Adds time intervals to schedules.
|
|
170
|
-
*
|
|
171
|
-
* Accepts an array of schedule groups (structure of `timeInterval` attributes
|
|
172
|
-
* for pages/products). For each group iterates over `values` — the set of
|
|
173
|
-
* concrete schedules. Each schedule contains a date range `dates[0..1]`.
|
|
174
|
-
*
|
|
175
|
-
* If both boundaries are equal (`isSameDay`), intervals are generated only
|
|
176
|
-
* for that single date. Otherwise — for every day in the range inclusive.
|
|
177
|
-
*
|
|
178
|
-
* The result (`schedule.timeIntervals`) is a sorted array of ISO pairs,
|
|
179
|
-
* ready to pass to UI components.
|
|
180
|
-
* @param {any[]} schedules - The schedules to process.
|
|
181
|
-
* @returns {any} Schedules with added time intervals.
|
|
182
|
-
*/
|
|
183
|
-
_addTimeIntervalsToSchedules(schedules: any[]): any;
|
|
184
|
-
/**
|
|
185
|
-
* Generates intervals for a specific date for form schedules.
|
|
186
|
-
*
|
|
187
|
-
* Unlike `_generateIntervalsForDate`, time ranges here have a different shape:
|
|
188
|
-
* each `timeInterval` contains `start`, `end` and `period`
|
|
189
|
-
* (slot length in minutes). The method slices the [start, end) window into
|
|
190
|
-
* fixed-length slots of `period` minutes:
|
|
191
|
-
*
|
|
192
|
-
* start=09:00, end=12:00, period=30 → [09:00–09:30], [09:30–10:00], …, [11:30–12:00]
|
|
193
|
-
*
|
|
194
|
-
* Generation stops if the next slot would exceed `end`.
|
|
195
|
-
* Each slot is added to `utcIntervals` (Set deduplicates automatically).
|
|
196
|
-
* @param {Date} date - The date for which to generate intervals.
|
|
197
|
-
* @param {object} interval - The interval configuration.
|
|
198
|
-
* @param {boolean} interval.inEveryWeek - Indicates whether the schedule is weekly.
|
|
199
|
-
* @param {boolean} interval.inEveryMonth - Indicates whether the schedule is monthly.
|
|
200
|
-
* @param {any[]} timeIntervals - The time intervals to process.
|
|
201
|
-
* @param {Set<Array<string>>} utcIntervals - A set to store unique intervals.
|
|
202
|
-
*/
|
|
203
|
-
protected _generateIntervalsForFormDate(date: Date, interval: {
|
|
204
|
-
inEveryWeek: boolean;
|
|
205
|
-
inEveryMonth: boolean;
|
|
206
|
-
}, timeIntervals: any[], utcIntervals: Set<Array<string>>): void;
|
|
207
|
-
/**
|
|
208
|
-
* Adds time intervals to form schedules (different structure).
|
|
209
|
-
*
|
|
210
|
-
* Same as `_addTimeIntervalsToSchedules` but for `timeInterval` attributes
|
|
211
|
-
* in **forms** (different API data structure):
|
|
212
|
-
* - `interval.range[0..1]` instead of `schedule.dates[0..1]`
|
|
213
|
-
* - `interval.intervals` — array of time ranges with slots (`period`)
|
|
214
|
-
* instead of `[startTime, endTime]` pairs
|
|
215
|
-
*
|
|
216
|
-
* Result is written to `interval.timeIntervals`.
|
|
217
|
-
* @param {any[]} intervals - The intervals to process.
|
|
218
|
-
* @returns {any} Intervals with added time intervals.
|
|
219
|
-
*/
|
|
220
|
-
_addTimeIntervalsToFormSchedules(intervals: any[]): any;
|
|
221
114
|
/**
|
|
222
115
|
* Transforms additionalFields from array to object keyed by marker.
|
|
223
116
|
*
|
|
@@ -238,16 +131,20 @@ export default abstract class SyncModules {
|
|
|
238
131
|
* Contains an object `{ marker: AttrObject }`. For each attribute:
|
|
239
132
|
* - `_normalizeAdditionalFields` is called;
|
|
240
133
|
* - numeric types (`integer`, `float`) are cast to a JS number (or `null`);
|
|
241
|
-
* - `timeInterval` attributes are enriched with computed `timeIntervals`;
|
|
242
134
|
* - the whole object is re-sorted by `position`.
|
|
243
135
|
*
|
|
244
136
|
* **2. `attributes`** — form attributes (different API structure).
|
|
245
|
-
* Same `additionalFields`
|
|
137
|
+
* Same `additionalFields` processing,
|
|
246
138
|
* but numbers are not normalized here (commented out — logic differs).
|
|
247
139
|
*
|
|
248
140
|
* **3. `type`** — a single attribute from an attribute set.
|
|
249
141
|
* Same transformations as in case 1, but without sorting.
|
|
250
142
|
*
|
|
143
|
+
* `timeInterval` attributes are left exactly as the API returned them — a
|
|
144
|
+
* compact recurrence rule. Resolving one into concrete slots is the caller's
|
|
145
|
+
* job, via `expandTimeIntervals`: the rule is open-ended, so only the caller
|
|
146
|
+
* knows how wide a window it needs.
|
|
147
|
+
*
|
|
251
148
|
* If none of the keys are found — data is returned unchanged.
|
|
252
149
|
* @param {any} data - The data to normalize.
|
|
253
150
|
* @returns {any} Normalized attributes.
|
package/dist/base/syncModules.js
CHANGED
|
@@ -286,253 +286,6 @@ class SyncModules {
|
|
|
286
286
|
return data;
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
|
-
/**
|
|
290
|
-
* Adds a specified number of days to a date.
|
|
291
|
-
* @param {Date} date - The initial date.
|
|
292
|
-
* @param {number} days - The number of days to add.
|
|
293
|
-
* @returns {any} The new date with added days.
|
|
294
|
-
*/
|
|
295
|
-
_addDays(date, days) {
|
|
296
|
-
const result = new Date(date);
|
|
297
|
-
result.setUTCDate(result.getUTCDate() + days);
|
|
298
|
-
return result;
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Common logic for processing schedule dates (weekly, monthly, or both).
|
|
302
|
-
*
|
|
303
|
-
* Abstracts date iteration for three scheduling modes:
|
|
304
|
-
*
|
|
305
|
-
* - **`inEveryWeek` only**: starting from the start date, generates dates
|
|
306
|
-
* with a 7-day step until the end of the current month.
|
|
307
|
-
*
|
|
308
|
-
* - **`inEveryMonth` only**: pins the day-of-month from the start date
|
|
309
|
-
* and repeats it for each of the next 12 months. If the month does not
|
|
310
|
-
* have that day (e.g. Feb 31), the iteration is skipped.
|
|
311
|
-
*
|
|
312
|
-
* - **`inEveryWeek` + `inEveryMonth`**: for each of the next 12 months finds
|
|
313
|
-
* the first occurrence of the target weekday (from the start date), then
|
|
314
|
-
* iterates all occurrences of that weekday in the month with a 7-day step.
|
|
315
|
-
*
|
|
316
|
-
* `processDate(currentDate)` is called for every resolved date.
|
|
317
|
-
* @param {Date} date - The date for which to process intervals.
|
|
318
|
-
* @param {object} config - Configuration for schedule repetition.
|
|
319
|
-
* @param {boolean} config.inEveryWeek - Whether to repeat weekly.
|
|
320
|
-
* @param {boolean} config.inEveryMonth - Whether to repeat monthly.
|
|
321
|
-
* @param {(currentDate: Date) => void} processDate - Callback function to process each date.
|
|
322
|
-
*/
|
|
323
|
-
_processScheduleDates(date, config, processDate) {
|
|
324
|
-
// Handle weekly schedules
|
|
325
|
-
if (config.inEveryWeek && !config.inEveryMonth) {
|
|
326
|
-
let currentDate = new Date(date);
|
|
327
|
-
// Calculate the last day of the current month
|
|
328
|
-
const endOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
|
|
329
|
-
while (currentDate <= endOfMonth) {
|
|
330
|
-
processDate(currentDate);
|
|
331
|
-
// Move to the next week
|
|
332
|
-
currentDate = this._addDays(currentDate, 7);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
// Handle monthly schedules
|
|
336
|
-
if (config.inEveryMonth && !config.inEveryWeek) {
|
|
337
|
-
const startDate = new Date(date);
|
|
338
|
-
const targetDayOfMonth = startDate.getUTCDate();
|
|
339
|
-
const numberOfMonths = 12;
|
|
340
|
-
for (let i = 0; i < numberOfMonths; i++) {
|
|
341
|
-
const currentDate = new Date(startDate);
|
|
342
|
-
currentDate.setUTCMonth(currentDate.getUTCMonth() + i);
|
|
343
|
-
// Try setting the current date to the target day of the month
|
|
344
|
-
currentDate.setUTCDate(targetDayOfMonth);
|
|
345
|
-
// Check if we have exceeded the month
|
|
346
|
-
if (currentDate.getUTCMonth() !== (startDate.getUTCMonth() + i) % 12) {
|
|
347
|
-
continue; // Skip this month if exceeded
|
|
348
|
-
}
|
|
349
|
-
processDate(currentDate);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
// Handle both weekly and monthly schedules
|
|
353
|
-
if (config.inEveryMonth && config.inEveryWeek) {
|
|
354
|
-
const startDate = new Date(date);
|
|
355
|
-
const targetDayOfWeek = startDate.getUTCDay();
|
|
356
|
-
const numberOfMonths = 12;
|
|
357
|
-
for (let i = 0; i < numberOfMonths; i++) {
|
|
358
|
-
const currentDate = new Date(startDate);
|
|
359
|
-
currentDate.setUTCMonth(currentDate.getUTCMonth() + i);
|
|
360
|
-
// Set to the first day of the month
|
|
361
|
-
currentDate.setUTCDate(1);
|
|
362
|
-
// Find the first target day of the week in the current month
|
|
363
|
-
const daysUntilTargetDay = (targetDayOfWeek - currentDate.getUTCDay() + 7) % 7;
|
|
364
|
-
currentDate.setUTCDate(currentDate.getUTCDate() + daysUntilTargetDay);
|
|
365
|
-
// Iterate over all target days of the week in the current month
|
|
366
|
-
while (currentDate.getUTCMonth() ===
|
|
367
|
-
(startDate.getUTCMonth() + i) % 12) {
|
|
368
|
-
processDate(currentDate);
|
|
369
|
-
// Move to the next week (same day of the week)
|
|
370
|
-
currentDate.setUTCDate(currentDate.getUTCDate() + 7);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Generates intervals for a specific date based on a schedule.
|
|
377
|
-
*
|
|
378
|
-
* For each date resolved by `_processScheduleDates`, iterates over
|
|
379
|
-
* the `schedule.times` array of time ranges. Each range is a pair
|
|
380
|
-
* `[startTime, endTime]` with `{ hours, minutes }` fields.
|
|
381
|
-
* Creates an ISO interval `[start.toISOString(), end.toISOString()]`
|
|
382
|
-
* and adds it to `utcIntervals` (Set deduplicates automatically).
|
|
383
|
-
* @param {Date} date - The date for which to generate intervals.
|
|
384
|
-
* @param {object} schedule - The schedule defining the intervals.
|
|
385
|
-
* @param {boolean} schedule.inEveryWeek - The number of weeks between intervals.
|
|
386
|
-
* @param {any[]} schedule.times - The times for each interval.
|
|
387
|
-
* @param {boolean} schedule.inEveryMonth - The month intervals for each interval.
|
|
388
|
-
* @param {Set<Array<string>>} utcIntervals - A set to store unique intervals.
|
|
389
|
-
*/
|
|
390
|
-
_generateIntervalsForDate(date, schedule, utcIntervals) {
|
|
391
|
-
this._processScheduleDates(date, schedule, (currentDate) => {
|
|
392
|
-
schedule.times.forEach((timeRange) => {
|
|
393
|
-
const [startTime, endTime] = timeRange;
|
|
394
|
-
const intervalStart = new Date(currentDate);
|
|
395
|
-
intervalStart.setUTCHours(startTime.hours, startTime.minutes, 0, 0);
|
|
396
|
-
const intervalEnd = new Date(currentDate);
|
|
397
|
-
intervalEnd.setUTCHours(endTime.hours, endTime.minutes, 0, 0);
|
|
398
|
-
utcIntervals.add([
|
|
399
|
-
intervalStart.toISOString(),
|
|
400
|
-
intervalEnd.toISOString(),
|
|
401
|
-
]);
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Adds time intervals to schedules.
|
|
407
|
-
*
|
|
408
|
-
* Accepts an array of schedule groups (structure of `timeInterval` attributes
|
|
409
|
-
* for pages/products). For each group iterates over `values` — the set of
|
|
410
|
-
* concrete schedules. Each schedule contains a date range `dates[0..1]`.
|
|
411
|
-
*
|
|
412
|
-
* If both boundaries are equal (`isSameDay`), intervals are generated only
|
|
413
|
-
* for that single date. Otherwise — for every day in the range inclusive.
|
|
414
|
-
*
|
|
415
|
-
* The result (`schedule.timeIntervals`) is a sorted array of ISO pairs,
|
|
416
|
-
* ready to pass to UI components.
|
|
417
|
-
* @param {any[]} schedules - The schedules to process.
|
|
418
|
-
* @returns {any} Schedules with added time intervals.
|
|
419
|
-
*/
|
|
420
|
-
_addTimeIntervalsToSchedules(schedules) {
|
|
421
|
-
schedules === null || schedules === void 0 ? void 0 : schedules.forEach((scheduleGroup) => {
|
|
422
|
-
// Skip if scheduleGroup.values is not an array
|
|
423
|
-
if (!scheduleGroup ||
|
|
424
|
-
!scheduleGroup.values ||
|
|
425
|
-
!Array.isArray(scheduleGroup.values)) {
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
scheduleGroup.values.forEach((schedule) => {
|
|
429
|
-
const utcIntervals = new Set();
|
|
430
|
-
const startDate = new Date(schedule.dates[0]);
|
|
431
|
-
const endDate = new Date(schedule.dates[1]);
|
|
432
|
-
const isSameDay = startDate.toISOString() === endDate.toISOString();
|
|
433
|
-
if (isSameDay) {
|
|
434
|
-
this._generateIntervalsForDate(startDate, schedule, utcIntervals);
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
for (let currentDate = new Date(startDate); currentDate <= endDate; currentDate = this._addDays(currentDate, 1)) {
|
|
438
|
-
this._generateIntervalsForDate(currentDate, schedule, utcIntervals);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
schedule.timeIntervals = Array.from(utcIntervals).sort();
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
return schedules;
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Generates intervals for a specific date for form schedules.
|
|
448
|
-
*
|
|
449
|
-
* Unlike `_generateIntervalsForDate`, time ranges here have a different shape:
|
|
450
|
-
* each `timeInterval` contains `start`, `end` and `period`
|
|
451
|
-
* (slot length in minutes). The method slices the [start, end) window into
|
|
452
|
-
* fixed-length slots of `period` minutes:
|
|
453
|
-
*
|
|
454
|
-
* start=09:00, end=12:00, period=30 → [09:00–09:30], [09:30–10:00], …, [11:30–12:00]
|
|
455
|
-
*
|
|
456
|
-
* Generation stops if the next slot would exceed `end`.
|
|
457
|
-
* Each slot is added to `utcIntervals` (Set deduplicates automatically).
|
|
458
|
-
* @param {Date} date - The date for which to generate intervals.
|
|
459
|
-
* @param {object} interval - The interval configuration.
|
|
460
|
-
* @param {boolean} interval.inEveryWeek - Indicates whether the schedule is weekly.
|
|
461
|
-
* @param {boolean} interval.inEveryMonth - Indicates whether the schedule is monthly.
|
|
462
|
-
* @param {any[]} timeIntervals - The time intervals to process.
|
|
463
|
-
* @param {Set<Array<string>>} utcIntervals - A set to store unique intervals.
|
|
464
|
-
*/
|
|
465
|
-
_generateIntervalsForFormDate(date, interval, timeIntervals, utcIntervals) {
|
|
466
|
-
const generateTimeSlotsForDate = (currentDate) => {
|
|
467
|
-
timeIntervals.forEach((timeInterval) => {
|
|
468
|
-
let currentStart = timeInterval.start;
|
|
469
|
-
const endTime = timeInterval.end;
|
|
470
|
-
// Slice the window into slots of `period` minutes each.
|
|
471
|
-
while (currentStart.hours < endTime.hours ||
|
|
472
|
-
(currentStart.hours === endTime.hours &&
|
|
473
|
-
currentStart.minutes < endTime.minutes)) {
|
|
474
|
-
const intervalStart = new Date(currentDate);
|
|
475
|
-
intervalStart.setUTCHours(currentStart.hours, currentStart.minutes, 0, 0);
|
|
476
|
-
// Compute slot end: add period minutes with hour carry normalization.
|
|
477
|
-
const nextMinutes = currentStart.minutes + timeInterval.period;
|
|
478
|
-
const nextHours = currentStart.hours + Math.floor(nextMinutes / 60);
|
|
479
|
-
const minutes = nextMinutes % 60;
|
|
480
|
-
// If the slot end exceeds `end` — stop; partial slots are not emitted.
|
|
481
|
-
if (nextHours > endTime.hours ||
|
|
482
|
-
(nextHours === endTime.hours && minutes > endTime.minutes)) {
|
|
483
|
-
break;
|
|
484
|
-
}
|
|
485
|
-
const intervalEnd = new Date(currentDate);
|
|
486
|
-
intervalEnd.setUTCHours(nextHours, minutes, 0, 0);
|
|
487
|
-
utcIntervals.add([
|
|
488
|
-
intervalStart.toISOString(),
|
|
489
|
-
intervalEnd.toISOString(),
|
|
490
|
-
]);
|
|
491
|
-
currentStart = { hours: nextHours, minutes };
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
};
|
|
495
|
-
this._processScheduleDates(date, interval, generateTimeSlotsForDate);
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Adds time intervals to form schedules (different structure).
|
|
499
|
-
*
|
|
500
|
-
* Same as `_addTimeIntervalsToSchedules` but for `timeInterval` attributes
|
|
501
|
-
* in **forms** (different API data structure):
|
|
502
|
-
* - `interval.range[0..1]` instead of `schedule.dates[0..1]`
|
|
503
|
-
* - `interval.intervals` — array of time ranges with slots (`period`)
|
|
504
|
-
* instead of `[startTime, endTime]` pairs
|
|
505
|
-
*
|
|
506
|
-
* Result is written to `interval.timeIntervals`.
|
|
507
|
-
* @param {any[]} intervals - The intervals to process.
|
|
508
|
-
* @returns {any} Intervals with added time intervals.
|
|
509
|
-
*/
|
|
510
|
-
_addTimeIntervalsToFormSchedules(intervals) {
|
|
511
|
-
intervals.forEach((interval) => {
|
|
512
|
-
var _a, _b;
|
|
513
|
-
if (!interval.intervals || !Array.isArray(interval.intervals)) {
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
const utcIntervals = new Set();
|
|
517
|
-
const startDate = new Date(interval.range[0]);
|
|
518
|
-
const endDate = new Date(interval.range[1]);
|
|
519
|
-
const isSameDay = startDate.toISOString() === endDate.toISOString();
|
|
520
|
-
const intervalConfig = {
|
|
521
|
-
inEveryWeek: (_a = interval.inEveryWeek) !== null && _a !== void 0 ? _a : false,
|
|
522
|
-
inEveryMonth: (_b = interval.inEveryMonth) !== null && _b !== void 0 ? _b : false,
|
|
523
|
-
};
|
|
524
|
-
if (isSameDay) {
|
|
525
|
-
this._generateIntervalsForFormDate(startDate, intervalConfig, interval.intervals, utcIntervals);
|
|
526
|
-
}
|
|
527
|
-
else {
|
|
528
|
-
for (let currentDate = new Date(startDate); currentDate <= endDate; currentDate = this._addDays(currentDate, 1)) {
|
|
529
|
-
this._generateIntervalsForFormDate(currentDate, intervalConfig, interval.intervals, utcIntervals);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
interval.timeIntervals = Array.from(utcIntervals).sort();
|
|
533
|
-
});
|
|
534
|
-
return intervals;
|
|
535
|
-
}
|
|
536
289
|
/**
|
|
537
290
|
* Transforms additionalFields from array to object keyed by marker.
|
|
538
291
|
*
|
|
@@ -557,16 +310,20 @@ class SyncModules {
|
|
|
557
310
|
* Contains an object `{ marker: AttrObject }`. For each attribute:
|
|
558
311
|
* - `_normalizeAdditionalFields` is called;
|
|
559
312
|
* - numeric types (`integer`, `float`) are cast to a JS number (or `null`);
|
|
560
|
-
* - `timeInterval` attributes are enriched with computed `timeIntervals`;
|
|
561
313
|
* - the whole object is re-sorted by `position`.
|
|
562
314
|
*
|
|
563
315
|
* **2. `attributes`** — form attributes (different API structure).
|
|
564
|
-
* Same `additionalFields`
|
|
316
|
+
* Same `additionalFields` processing,
|
|
565
317
|
* but numbers are not normalized here (commented out — logic differs).
|
|
566
318
|
*
|
|
567
319
|
* **3. `type`** — a single attribute from an attribute set.
|
|
568
320
|
* Same transformations as in case 1, but without sorting.
|
|
569
321
|
*
|
|
322
|
+
* `timeInterval` attributes are left exactly as the API returned them — a
|
|
323
|
+
* compact recurrence rule. Resolving one into concrete slots is the caller's
|
|
324
|
+
* job, via `expandTimeIntervals`: the rule is open-ended, so only the caller
|
|
325
|
+
* knows how wide a window it needs.
|
|
326
|
+
*
|
|
570
327
|
* If none of the keys are found — data is returned unchanged.
|
|
571
328
|
* @param {any} data - The data to normalize.
|
|
572
329
|
* @returns {any} Normalized attributes.
|
|
@@ -582,15 +339,6 @@ class SyncModules {
|
|
|
582
339
|
const numValue = Number(d.value);
|
|
583
340
|
d.value = isNaN(numValue) ? null : numValue;
|
|
584
341
|
}
|
|
585
|
-
// add timeIntervals
|
|
586
|
-
if (data.attributeValues[attr].type === 'timeInterval') {
|
|
587
|
-
const schedules = data.attributeValues[attr].value;
|
|
588
|
-
// console.log('Schedules: ', JSON.stringify(schedules));
|
|
589
|
-
if (Array.isArray(schedules) && schedules.length > 0) {
|
|
590
|
-
const result = this._addTimeIntervalsToSchedules(schedules);
|
|
591
|
-
data.attributeValues[attr].value = result;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
342
|
});
|
|
595
343
|
return {
|
|
596
344
|
...data,
|
|
@@ -608,22 +356,12 @@ class SyncModules {
|
|
|
608
356
|
if ('attributes' in data) {
|
|
609
357
|
const d = data.attributes;
|
|
610
358
|
Object.keys(d).forEach((attr) => {
|
|
611
|
-
var _a;
|
|
612
359
|
this._normalizeAdditionalFields(d[attr]);
|
|
613
360
|
for (const field of booleanFields) {
|
|
614
361
|
if (field in d[attr] && d[attr][field] === null) {
|
|
615
362
|
d[attr][field] = false;
|
|
616
363
|
}
|
|
617
364
|
}
|
|
618
|
-
// Add time intervals
|
|
619
|
-
if (d[attr].type === 'timeInterval') {
|
|
620
|
-
const intervals = (_a = d[attr].localizeInfos) === null || _a === void 0 ? void 0 : _a.intervals;
|
|
621
|
-
// console.log('Schedules:: ', JSON.stringify(intervals));
|
|
622
|
-
if (intervals && Array.isArray(intervals) && intervals.length > 0) {
|
|
623
|
-
const result = this._addTimeIntervalsToFormSchedules(intervals);
|
|
624
|
-
d[attr].localizeInfos.intervals = result;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
365
|
});
|
|
628
366
|
return data;
|
|
629
367
|
}
|
|
@@ -640,14 +378,6 @@ class SyncModules {
|
|
|
640
378
|
const numValue = Number(data.value);
|
|
641
379
|
data.value = isNaN(numValue) ? null : numValue;
|
|
642
380
|
}
|
|
643
|
-
// Add time intervals
|
|
644
|
-
if (data.type === 'timeInterval') {
|
|
645
|
-
const schedules = data.value;
|
|
646
|
-
if (Array.isArray(schedules) && schedules.length > 0) {
|
|
647
|
-
const result = this._addTimeIntervalsToSchedules(schedules);
|
|
648
|
-
data.value = result;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
381
|
}
|
|
652
382
|
return data;
|
|
653
383
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { IAttributeValue, ITimeIntervalAttributeValue, ITimeIntervalEntitySchedule, ITimeIntervalSchedule, ITimeIntervalWindow, TimeIntervalPair } from './utils';
|
|
2
|
+
/**
|
|
3
|
+
* Expands a `timeInterval` schedule into concrete UTC slots for a given window.
|
|
4
|
+
*
|
|
5
|
+
* A schedule as returned by the API is a compact **recurrence rule** — an
|
|
6
|
+
* anchor date plus daily time ranges plus repeat flags — not a list of slots.
|
|
7
|
+
* Materializing it wholesale is what makes `timeInterval` attributes expensive
|
|
8
|
+
* (a year of half-hour slots runs to megabytes), so expansion is on demand and
|
|
9
|
+
* the window is required: only the caller knows how far it needs to resolve.
|
|
10
|
+
*
|
|
11
|
+
* Both schedule shapes the API returns are accepted:
|
|
12
|
+
* - **entity** — `attributeValues[marker].value[].values[]` on pages, products,
|
|
13
|
+
* blocks and attribute sets: a `dates` range with `times` pairs;
|
|
14
|
+
* - **form** — `attributes[marker].localizeInfos.intervals[]`: a `range` with
|
|
15
|
+
* `intervals` that carry a slot `period` in minutes.
|
|
16
|
+
*
|
|
17
|
+
* Semantics:
|
|
18
|
+
* - `dates[0]` / `range[0]` is both the recurrence phase and the first valid
|
|
19
|
+
* day — nothing earlier is emitted, however wide the window;
|
|
20
|
+
* - `dates[1]` / `range[1]` ends validity; when it does not extend past the
|
|
21
|
+
* start, the schedule is anchored to that day — with a recurrence flag set,
|
|
22
|
+
* recurrence is then open-ended and the window alone bounds the result;
|
|
23
|
+
* - `inEveryWeek` repeats every 7 days from the anchor; `inEveryMonth` repeats
|
|
24
|
+
* on the same day-of-month, skipping months that are too short; with both set
|
|
25
|
+
* the weekly rule applies, which is what it has always meant in practice;
|
|
26
|
+
* - with neither flag the schedule is a plain date range — every day of it;
|
|
27
|
+
* - the result is deduplicated and sorted by start, then end.
|
|
28
|
+
* @param {ITimeIntervalEntitySchedule | ITimeIntervalSchedule} schedule - A single schedule entry from a `timeInterval` attribute.
|
|
29
|
+
* @param {ITimeIntervalWindow} window - Inclusive `{ from, to }` range to resolve, compared at UTC day granularity.
|
|
30
|
+
* @returns {TimeIntervalPair[]} Sorted, deduplicated `[start, end]` ISO pairs; empty when the schedule is malformed or does not overlap the window.
|
|
31
|
+
* To expand a whole attribute at once, prefer {@link expandAttributeTimeIntervals}
|
|
32
|
+
* — it walks the groups and merges the results for you. Reach for this function
|
|
33
|
+
* directly when you already hold a single schedule, e.g. a form's
|
|
34
|
+
* `localizeInfos.intervals[]`.
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { expandTimeIntervals } from 'oneentry';
|
|
38
|
+
*
|
|
39
|
+
* // Form attributes are an array keyed by `marker`, and carry their schedules
|
|
40
|
+
* // already typed on `localizeInfos.intervals`.
|
|
41
|
+
* const field = form.attributes.find((a) => a.marker === 'booking');
|
|
42
|
+
*
|
|
43
|
+
* const slots = (field?.localizeInfos.intervals ?? []).flatMap((schedule) =>
|
|
44
|
+
* expandTimeIntervals(schedule, { from: '2025-05-01', to: '2025-05-31' }),
|
|
45
|
+
* );
|
|
46
|
+
* // [['2025-05-07T09:00:00.000Z', '2025-05-07T10:00:00.000Z'], …]
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function expandTimeIntervals(schedule: ITimeIntervalEntitySchedule | ITimeIntervalSchedule, window: ITimeIntervalWindow): TimeIntervalPair[];
|
|
50
|
+
/**
|
|
51
|
+
* Narrows an attribute value to a `timeInterval` attribute.
|
|
52
|
+
*
|
|
53
|
+
* `IAttributeValue.value` is `unknown` — its shape depends on `type` — so this
|
|
54
|
+
* guard is what lets you reach the schedules without a cast.
|
|
55
|
+
* @param {IAttributeValue | undefined} attr - The attribute value to test.
|
|
56
|
+
* @returns {boolean} True when the attribute is a `timeInterval` carrying an array of groups.
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const attr = page.attributeValues.interval;
|
|
60
|
+
* if (isTimeIntervalAttribute(attr)) {
|
|
61
|
+
* attr.value[0].values[0].dates; // fully typed, no cast
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function isTimeIntervalAttribute(attr: IAttributeValue | undefined): attr is ITimeIntervalAttributeValue;
|
|
66
|
+
/**
|
|
67
|
+
* Expands a whole `timeInterval` attribute into concrete UTC slots for a window.
|
|
68
|
+
*
|
|
69
|
+
* The one-call path for the common case: it walks the attribute's groups and
|
|
70
|
+
* their schedules, expands each with {@link expandTimeIntervals}, and merges the
|
|
71
|
+
* results. Merging matters — deduplication and ordering only hold within a
|
|
72
|
+
* single schedule, so combining groups by hand can yield duplicate or unsorted
|
|
73
|
+
* slots.
|
|
74
|
+
*
|
|
75
|
+
* Anything that is not a `timeInterval` attribute yields an empty array, so this
|
|
76
|
+
* is safe to call on an arbitrary attribute without checking `type` first.
|
|
77
|
+
*
|
|
78
|
+
* For **form** attributes the schedules are already typed at
|
|
79
|
+
* `localizeInfos.intervals`, so no equivalent helper is needed — map over them
|
|
80
|
+
* and call {@link expandTimeIntervals} directly.
|
|
81
|
+
* @param {IAttributeValue | undefined} attr - A `timeInterval` attribute value, e.g. `page.attributeValues.interval`.
|
|
82
|
+
* @param {ITimeIntervalWindow} window - Inclusive `{ from, to }` range to resolve, compared at UTC day granularity.
|
|
83
|
+
* @returns {TimeIntervalPair[]} Sorted, deduplicated `[start, end]` ISO pairs across every group; empty when the attribute is not a `timeInterval`.
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* import { expandAttributeTimeIntervals } from 'oneentry';
|
|
87
|
+
*
|
|
88
|
+
* const slots = expandAttributeTimeIntervals(page.attributeValues.interval, {
|
|
89
|
+
* from: '2025-04-01',
|
|
90
|
+
* to: '2025-04-30',
|
|
91
|
+
* });
|
|
92
|
+
* // [['2025-04-14T09:00:00.000Z', '2025-04-14T10:00:00.000Z'], …]
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare function expandAttributeTimeIntervals(attr: IAttributeValue | undefined, window: ITimeIntervalWindow): TimeIntervalPair[];
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.expandTimeIntervals = expandTimeIntervals;
|
|
4
|
+
exports.isTimeIntervalAttribute = isTimeIntervalAttribute;
|
|
5
|
+
exports.expandAttributeTimeIntervals = expandAttributeTimeIntervals;
|
|
6
|
+
const _MS_PER_MINUTE = 60000;
|
|
7
|
+
const _MS_PER_DAY = 86400000;
|
|
8
|
+
const _MS_PER_WEEK = 7 * _MS_PER_DAY;
|
|
9
|
+
/**
|
|
10
|
+
* Converts a date-ish value to the UTC midnight of its day, as epoch milliseconds.
|
|
11
|
+
*
|
|
12
|
+
* Everything downstream works on whole UTC days, so this is the single place
|
|
13
|
+
* where an incoming `Date` / ISO string / timestamp is reduced to a day key.
|
|
14
|
+
*
|
|
15
|
+
* `null` is rejected explicitly: `new Date(null)` is the epoch, not an invalid
|
|
16
|
+
* date, so a NaN check alone would silently accept it as 1970-01-01.
|
|
17
|
+
* @param {Date | string | number} value - The value to interpret as a date.
|
|
18
|
+
* @returns {number | null} Epoch ms of the UTC day start, or null when the value is not a valid date.
|
|
19
|
+
*/
|
|
20
|
+
function _toUtcDayStart(value) {
|
|
21
|
+
if (value === null || value === undefined)
|
|
22
|
+
return null;
|
|
23
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
24
|
+
if (Number.isNaN(date.getTime()))
|
|
25
|
+
return null;
|
|
26
|
+
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Converts a time-of-day point to minutes since midnight.
|
|
30
|
+
*
|
|
31
|
+
* Guards the numeric contract: a missing component, or one arriving as a string
|
|
32
|
+
* (`{ hours: '9' }`), would otherwise poison the arithmetic downstream — either
|
|
33
|
+
* throwing `RangeError` on `toISOString` or, for strings, concatenating instead
|
|
34
|
+
* of adding.
|
|
35
|
+
* @param {ITimeIntervalPoint | undefined} point - The point to convert.
|
|
36
|
+
* @returns {number | null} Minutes since midnight, or null when the point is malformed.
|
|
37
|
+
*/
|
|
38
|
+
function _toMinutes(point) {
|
|
39
|
+
if (!point)
|
|
40
|
+
return null;
|
|
41
|
+
if (!Number.isFinite(point.hours) || !Number.isFinite(point.minutes)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return point.hours * 60 + point.minutes;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the days a schedule is active on, clipped to the requested window.
|
|
48
|
+
*
|
|
49
|
+
* `anchor` is both the recurrence phase (which weekday / which day-of-month)
|
|
50
|
+
* and the first day the schedule is valid — nothing before it is ever emitted.
|
|
51
|
+
* The recurrence rule is open-ended, so `rangeEnd` (the window, intersected
|
|
52
|
+
* with the schedule's own validity end) is what stops the enumeration.
|
|
53
|
+
* @param {number} anchor - UTC day start the schedule is anchored to.
|
|
54
|
+
* @param {number} rangeStart - UTC day start of the first day to consider (inclusive).
|
|
55
|
+
* @param {number} rangeEnd - UTC day start of the last day to consider (inclusive).
|
|
56
|
+
* @param {boolean} inEveryWeek - Whether the schedule repeats weekly.
|
|
57
|
+
* @param {boolean} inEveryMonth - Whether the schedule repeats monthly.
|
|
58
|
+
* @returns {number[]} Ascending UTC day starts the schedule is active on.
|
|
59
|
+
*/
|
|
60
|
+
function _resolveActiveDays(anchor, rangeStart, rangeEnd, inEveryWeek, inEveryMonth) {
|
|
61
|
+
const days = [];
|
|
62
|
+
// Weekly wins over monthly: repeating "every week" already visits every
|
|
63
|
+
// occurrence of the anchor's weekday in every month, which is exactly what
|
|
64
|
+
// the two flags combined have always meant.
|
|
65
|
+
if (inEveryWeek) {
|
|
66
|
+
const offset = Math.ceil((rangeStart - anchor) / _MS_PER_WEEK);
|
|
67
|
+
for (let day = anchor + Math.max(0, offset) * _MS_PER_WEEK; day <= rangeEnd; day += _MS_PER_WEEK) {
|
|
68
|
+
days.push(day);
|
|
69
|
+
}
|
|
70
|
+
return days;
|
|
71
|
+
}
|
|
72
|
+
// Monthly: the same day-of-month, every month. Months too short for that day
|
|
73
|
+
// (e.g. the 31st in February) are skipped rather than rolled over.
|
|
74
|
+
if (inEveryMonth) {
|
|
75
|
+
const dayOfMonth = new Date(anchor).getUTCDate();
|
|
76
|
+
const start = new Date(rangeStart);
|
|
77
|
+
let year = start.getUTCFullYear();
|
|
78
|
+
let month = start.getUTCMonth();
|
|
79
|
+
// Walk the calendar month by month. The cursor is the first of the month,
|
|
80
|
+
// not the emitted day: a month too short for `dayOfMonth` emits nothing and
|
|
81
|
+
// would otherwise leave the cursor standing still.
|
|
82
|
+
while (Date.UTC(year, month, 1) <= rangeEnd) {
|
|
83
|
+
const daysInMonth = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
|
|
84
|
+
if (dayOfMonth <= daysInMonth) {
|
|
85
|
+
const day = Date.UTC(year, month, dayOfMonth);
|
|
86
|
+
if (day >= rangeStart && day <= rangeEnd)
|
|
87
|
+
days.push(day);
|
|
88
|
+
}
|
|
89
|
+
month += 1;
|
|
90
|
+
if (month > 11) {
|
|
91
|
+
month = 0;
|
|
92
|
+
year += 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return days;
|
|
96
|
+
}
|
|
97
|
+
// No recurrence: a plain date range — every day of it.
|
|
98
|
+
for (let day = rangeStart; day <= rangeEnd; day += _MS_PER_DAY) {
|
|
99
|
+
days.push(day);
|
|
100
|
+
}
|
|
101
|
+
return days;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Builds slots for one day from explicit `[start, end]` time pairs.
|
|
105
|
+
*
|
|
106
|
+
* Used by entity schedules, where each pair is emitted verbatim as one slot.
|
|
107
|
+
* @param {number} day - UTC day start, in epoch ms.
|
|
108
|
+
* @param {ITimeIntervalPoint[][]} times - Daily time ranges as `[start, end]` pairs.
|
|
109
|
+
* @param {TimeIntervalPair[]} out - Accumulator the produced slots are pushed to.
|
|
110
|
+
*/
|
|
111
|
+
function _addSlotsFromTimes(day, times, out) {
|
|
112
|
+
times.forEach((range) => {
|
|
113
|
+
const [start, end] = range !== null && range !== void 0 ? range : [];
|
|
114
|
+
const startMinutes = _toMinutes(start);
|
|
115
|
+
const endMinutes = _toMinutes(end);
|
|
116
|
+
if (startMinutes === null || endMinutes === null)
|
|
117
|
+
return;
|
|
118
|
+
const from = day + startMinutes * _MS_PER_MINUTE;
|
|
119
|
+
const to = day + endMinutes * _MS_PER_MINUTE;
|
|
120
|
+
out.push([new Date(from).toISOString(), new Date(to).toISOString()]);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Builds slots for one day by slicing each range into fixed-length periods.
|
|
125
|
+
*
|
|
126
|
+
* Used by form schedules: `start=09:00, end=12:00, period=30` yields
|
|
127
|
+
* `[09:00–09:30], [09:30–10:00], …, [11:30–12:00]`. A trailing partial slot
|
|
128
|
+
* that would overrun `end` is not emitted.
|
|
129
|
+
* @param {number} day - UTC day start, in epoch ms.
|
|
130
|
+
* @param {ITimeIntervalRange[]} ranges - Daily ranges carrying a slot `period` in minutes.
|
|
131
|
+
* @param {TimeIntervalPair[]} out - Accumulator the produced slots are pushed to.
|
|
132
|
+
*/
|
|
133
|
+
function _addSlotsFromRanges(day, ranges, out) {
|
|
134
|
+
ranges.forEach((range) => {
|
|
135
|
+
const startMinutes = _toMinutes(range === null || range === void 0 ? void 0 : range.start);
|
|
136
|
+
const endMinutes = _toMinutes(range === null || range === void 0 ? void 0 : range.end);
|
|
137
|
+
if (startMinutes === null || endMinutes === null)
|
|
138
|
+
return;
|
|
139
|
+
// A non-positive period would never advance the cursor; a non-numeric one
|
|
140
|
+
// would concatenate into it.
|
|
141
|
+
if (!Number.isFinite(range.period) || range.period <= 0)
|
|
142
|
+
return;
|
|
143
|
+
for (let minutes = startMinutes; minutes + range.period <= endMinutes; minutes += range.period) {
|
|
144
|
+
const from = day + minutes * _MS_PER_MINUTE;
|
|
145
|
+
const to = day + (minutes + range.period) * _MS_PER_MINUTE;
|
|
146
|
+
out.push([new Date(from).toISOString(), new Date(to).toISOString()]);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Deduplicates slots by value and sorts them by start, then end.
|
|
152
|
+
*
|
|
153
|
+
* A plain `Set` would not do: slots are arrays, so it would compare by
|
|
154
|
+
* reference and keep every duplicate.
|
|
155
|
+
* @param {TimeIntervalPair[]} slots - The slots to normalize.
|
|
156
|
+
* @returns {TimeIntervalPair[]} Sorted, deduplicated slots.
|
|
157
|
+
*/
|
|
158
|
+
function _dedupeAndSort(slots) {
|
|
159
|
+
const unique = new Map();
|
|
160
|
+
slots.forEach((slot) => {
|
|
161
|
+
const key = `${slot[0]}|${slot[1]}`;
|
|
162
|
+
if (!unique.has(key))
|
|
163
|
+
unique.set(key, slot);
|
|
164
|
+
});
|
|
165
|
+
return [...unique.values()].sort((a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1]));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Narrows a schedule to the entity shape (pages, products, blocks, attribute sets).
|
|
169
|
+
*
|
|
170
|
+
* Entity schedules carry `times`; form schedules never do.
|
|
171
|
+
* @param {ITimeIntervalEntitySchedule | ITimeIntervalSchedule} schedule - The schedule to test.
|
|
172
|
+
* @returns {boolean} True when the schedule is an entity schedule.
|
|
173
|
+
*/
|
|
174
|
+
function _isEntitySchedule(schedule) {
|
|
175
|
+
return Array.isArray(schedule.times);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Expands a `timeInterval` schedule into concrete UTC slots for a given window.
|
|
179
|
+
*
|
|
180
|
+
* A schedule as returned by the API is a compact **recurrence rule** — an
|
|
181
|
+
* anchor date plus daily time ranges plus repeat flags — not a list of slots.
|
|
182
|
+
* Materializing it wholesale is what makes `timeInterval` attributes expensive
|
|
183
|
+
* (a year of half-hour slots runs to megabytes), so expansion is on demand and
|
|
184
|
+
* the window is required: only the caller knows how far it needs to resolve.
|
|
185
|
+
*
|
|
186
|
+
* Both schedule shapes the API returns are accepted:
|
|
187
|
+
* - **entity** — `attributeValues[marker].value[].values[]` on pages, products,
|
|
188
|
+
* blocks and attribute sets: a `dates` range with `times` pairs;
|
|
189
|
+
* - **form** — `attributes[marker].localizeInfos.intervals[]`: a `range` with
|
|
190
|
+
* `intervals` that carry a slot `period` in minutes.
|
|
191
|
+
*
|
|
192
|
+
* Semantics:
|
|
193
|
+
* - `dates[0]` / `range[0]` is both the recurrence phase and the first valid
|
|
194
|
+
* day — nothing earlier is emitted, however wide the window;
|
|
195
|
+
* - `dates[1]` / `range[1]` ends validity; when it does not extend past the
|
|
196
|
+
* start, the schedule is anchored to that day — with a recurrence flag set,
|
|
197
|
+
* recurrence is then open-ended and the window alone bounds the result;
|
|
198
|
+
* - `inEveryWeek` repeats every 7 days from the anchor; `inEveryMonth` repeats
|
|
199
|
+
* on the same day-of-month, skipping months that are too short; with both set
|
|
200
|
+
* the weekly rule applies, which is what it has always meant in practice;
|
|
201
|
+
* - with neither flag the schedule is a plain date range — every day of it;
|
|
202
|
+
* - the result is deduplicated and sorted by start, then end.
|
|
203
|
+
* @param {ITimeIntervalEntitySchedule | ITimeIntervalSchedule} schedule - A single schedule entry from a `timeInterval` attribute.
|
|
204
|
+
* @param {ITimeIntervalWindow} window - Inclusive `{ from, to }` range to resolve, compared at UTC day granularity.
|
|
205
|
+
* @returns {TimeIntervalPair[]} Sorted, deduplicated `[start, end]` ISO pairs; empty when the schedule is malformed or does not overlap the window.
|
|
206
|
+
* To expand a whole attribute at once, prefer {@link expandAttributeTimeIntervals}
|
|
207
|
+
* — it walks the groups and merges the results for you. Reach for this function
|
|
208
|
+
* directly when you already hold a single schedule, e.g. a form's
|
|
209
|
+
* `localizeInfos.intervals[]`.
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* import { expandTimeIntervals } from 'oneentry';
|
|
213
|
+
*
|
|
214
|
+
* // Form attributes are an array keyed by `marker`, and carry their schedules
|
|
215
|
+
* // already typed on `localizeInfos.intervals`.
|
|
216
|
+
* const field = form.attributes.find((a) => a.marker === 'booking');
|
|
217
|
+
*
|
|
218
|
+
* const slots = (field?.localizeInfos.intervals ?? []).flatMap((schedule) =>
|
|
219
|
+
* expandTimeIntervals(schedule, { from: '2025-05-01', to: '2025-05-31' }),
|
|
220
|
+
* );
|
|
221
|
+
* // [['2025-05-07T09:00:00.000Z', '2025-05-07T10:00:00.000Z'], …]
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
function expandTimeIntervals(schedule, window) {
|
|
225
|
+
var _a, _b;
|
|
226
|
+
if (!schedule || !window)
|
|
227
|
+
return [];
|
|
228
|
+
const isEntity = _isEntitySchedule(schedule);
|
|
229
|
+
const bounds = isEntity ? schedule.dates : schedule.range;
|
|
230
|
+
if (!Array.isArray(bounds))
|
|
231
|
+
return [];
|
|
232
|
+
const anchor = _toUtcDayStart(bounds[0]);
|
|
233
|
+
if (anchor === null)
|
|
234
|
+
return [];
|
|
235
|
+
const windowFrom = _toUtcDayStart(window.from);
|
|
236
|
+
const windowTo = _toUtcDayStart(window.to);
|
|
237
|
+
if (windowFrom === null || windowTo === null || windowTo < windowFrom) {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
const inEveryWeek = (_a = schedule.inEveryWeek) !== null && _a !== void 0 ? _a : false;
|
|
241
|
+
const inEveryMonth = (_b = schedule.inEveryMonth) !== null && _b !== void 0 ? _b : false;
|
|
242
|
+
// An anchor-only range (start === end) means the schedule is pinned to that
|
|
243
|
+
// day; with a recurrence rule it then runs open-ended and the window is the
|
|
244
|
+
// only horizon. Otherwise the second bound ends validity.
|
|
245
|
+
const declaredEnd = _toUtcDayStart(bounds[1]);
|
|
246
|
+
const isOpenEnded = (declaredEnd === null || declaredEnd <= anchor) &&
|
|
247
|
+
(inEveryWeek || inEveryMonth);
|
|
248
|
+
const validTo = isOpenEnded
|
|
249
|
+
? Number.POSITIVE_INFINITY
|
|
250
|
+
: Math.max(anchor, declaredEnd !== null && declaredEnd !== void 0 ? declaredEnd : anchor);
|
|
251
|
+
const rangeStart = Math.max(anchor, windowFrom);
|
|
252
|
+
const rangeEnd = Math.min(validTo, windowTo);
|
|
253
|
+
if (rangeEnd < rangeStart)
|
|
254
|
+
return [];
|
|
255
|
+
const days = _resolveActiveDays(anchor, rangeStart, rangeEnd, inEveryWeek, inEveryMonth);
|
|
256
|
+
const slots = [];
|
|
257
|
+
days.forEach((day) => {
|
|
258
|
+
if (isEntity) {
|
|
259
|
+
_addSlotsFromTimes(day, schedule.times, slots);
|
|
260
|
+
}
|
|
261
|
+
else if (Array.isArray(schedule.intervals)) {
|
|
262
|
+
_addSlotsFromRanges(day, schedule.intervals, slots);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
return _dedupeAndSort(slots);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Narrows an attribute value to a `timeInterval` attribute.
|
|
269
|
+
*
|
|
270
|
+
* `IAttributeValue.value` is `unknown` — its shape depends on `type` — so this
|
|
271
|
+
* guard is what lets you reach the schedules without a cast.
|
|
272
|
+
* @param {IAttributeValue | undefined} attr - The attribute value to test.
|
|
273
|
+
* @returns {boolean} True when the attribute is a `timeInterval` carrying an array of groups.
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* const attr = page.attributeValues.interval;
|
|
277
|
+
* if (isTimeIntervalAttribute(attr)) {
|
|
278
|
+
* attr.value[0].values[0].dates; // fully typed, no cast
|
|
279
|
+
* }
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
function isTimeIntervalAttribute(attr) {
|
|
283
|
+
return !!attr && attr.type === 'timeInterval' && Array.isArray(attr.value);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Expands a whole `timeInterval` attribute into concrete UTC slots for a window.
|
|
287
|
+
*
|
|
288
|
+
* The one-call path for the common case: it walks the attribute's groups and
|
|
289
|
+
* their schedules, expands each with {@link expandTimeIntervals}, and merges the
|
|
290
|
+
* results. Merging matters — deduplication and ordering only hold within a
|
|
291
|
+
* single schedule, so combining groups by hand can yield duplicate or unsorted
|
|
292
|
+
* slots.
|
|
293
|
+
*
|
|
294
|
+
* Anything that is not a `timeInterval` attribute yields an empty array, so this
|
|
295
|
+
* is safe to call on an arbitrary attribute without checking `type` first.
|
|
296
|
+
*
|
|
297
|
+
* For **form** attributes the schedules are already typed at
|
|
298
|
+
* `localizeInfos.intervals`, so no equivalent helper is needed — map over them
|
|
299
|
+
* and call {@link expandTimeIntervals} directly.
|
|
300
|
+
* @param {IAttributeValue | undefined} attr - A `timeInterval` attribute value, e.g. `page.attributeValues.interval`.
|
|
301
|
+
* @param {ITimeIntervalWindow} window - Inclusive `{ from, to }` range to resolve, compared at UTC day granularity.
|
|
302
|
+
* @returns {TimeIntervalPair[]} Sorted, deduplicated `[start, end]` ISO pairs across every group; empty when the attribute is not a `timeInterval`.
|
|
303
|
+
* @example
|
|
304
|
+
* ```ts
|
|
305
|
+
* import { expandAttributeTimeIntervals } from 'oneentry';
|
|
306
|
+
*
|
|
307
|
+
* const slots = expandAttributeTimeIntervals(page.attributeValues.interval, {
|
|
308
|
+
* from: '2025-04-01',
|
|
309
|
+
* to: '2025-04-30',
|
|
310
|
+
* });
|
|
311
|
+
* // [['2025-04-14T09:00:00.000Z', '2025-04-14T10:00:00.000Z'], …]
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
function expandAttributeTimeIntervals(attr, window) {
|
|
315
|
+
if (!isTimeIntervalAttribute(attr))
|
|
316
|
+
return [];
|
|
317
|
+
const slots = attr.value.flatMap((group) => Array.isArray(group === null || group === void 0 ? void 0 : group.values)
|
|
318
|
+
? group.values.flatMap((schedule) => expandTimeIntervals(schedule, window))
|
|
319
|
+
: []);
|
|
320
|
+
return _dedupeAndSort(slots);
|
|
321
|
+
}
|
package/dist/base/utils.d.ts
CHANGED
|
@@ -148,7 +148,7 @@ interface ILocalizeInfo {
|
|
|
148
148
|
}
|
|
149
149
|
/**
|
|
150
150
|
* @interface IAttributeLocalizeInfo
|
|
151
|
-
* @property {ITimeIntervalSchedule[]} [intervals] - For attributes of type `timeInterval`, the
|
|
151
|
+
* @property {ITimeIntervalSchedule[]} [intervals] - For attributes of type `timeInterval`, the schedule data attached to the localization payload. Each entry is a compact recurrence rule — pass it to `expandTimeIntervals` to resolve concrete slots.
|
|
152
152
|
* @description Extension of {@link ILocalizeInfo} used by attribute entities — adds attribute-type-specific fields the API exposes via `localizeInfos`.
|
|
153
153
|
*/
|
|
154
154
|
interface IAttributeLocalizeInfo extends ILocalizeInfo {
|
|
@@ -163,8 +163,9 @@ interface IAttributeLocalizeInfo extends ILocalizeInfo {
|
|
|
163
163
|
* @property {number} selectedYear - Year the schedule applies to. Example: 2025.
|
|
164
164
|
* @property {ITimeIntervalRange[]} intervals - Daily time ranges configured for the schedule.
|
|
165
165
|
* @property {unknown[]} external - External overrides; empty array when none.
|
|
166
|
-
* @property {
|
|
167
|
-
* @
|
|
166
|
+
* @property {string[]} range - Validity range as a pair of ISO dates `[from, to]`; empty array when none. When both entries are equal the schedule is anchored to that single day and its recurrence is open-ended. Example: ["2025-05-07T21:00:00.000Z", "2025-05-07T21:00:00.000Z"].
|
|
167
|
+
* @property {boolean} [inEveryWeek] - Whether the schedule repeats every week. Optional.
|
|
168
|
+
* @description One entry of a `timeInterval` **form** attribute's schedule, carried on `localizeInfos.intervals`. Holds a compact recurrence rule — pass it to `expandTimeIntervals` to resolve concrete slots for a given window.
|
|
168
169
|
*/
|
|
169
170
|
interface ITimeIntervalSchedule {
|
|
170
171
|
id: string;
|
|
@@ -173,8 +174,65 @@ interface ITimeIntervalSchedule {
|
|
|
173
174
|
selectedYear: number;
|
|
174
175
|
intervals: ITimeIntervalRange[];
|
|
175
176
|
external: unknown[];
|
|
176
|
-
range:
|
|
177
|
+
range: string[];
|
|
178
|
+
inEveryWeek?: boolean;
|
|
177
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* @interface ITimeIntervalEntitySchedule
|
|
182
|
+
* @property {string} id - Schedule entry identifier (UUID). Example: "bbc82c9f-1bc4-4c86-b83c-c062016eb7cb".
|
|
183
|
+
* @property {string} intervalId - Identifier of the parent interval group (UUID). Example: "c6466cd8-c55d-4583-97c5-42b684210f12".
|
|
184
|
+
* @property {string[]} dates - Validity range as a pair of ISO dates `[from, to]`. When both entries are equal the schedule is anchored to that single day and its recurrence is open-ended. Example: ["2025-04-14T00:00:00.000Z", "2025-04-14T00:00:00.000Z"].
|
|
185
|
+
* @property {ITimeIntervalPoint[][]} times - Daily time ranges, each a `[start, end]` pair. Example: [[{ "hours": 9, "minutes": 0 }, { "hours": 10, "minutes": 0 }]].
|
|
186
|
+
* @property {boolean} inEveryWeek - Whether the schedule repeats every week. Example: true.
|
|
187
|
+
* @property {boolean} inEveryMonth - Whether the schedule repeats every month. Example: true.
|
|
188
|
+
* @property {unknown[]} [exceptions] - Excluded dates; empty array when none. Optional.
|
|
189
|
+
* @property {ITimeIntervalRange[]} [intervals] - Slot-based ranges; empty array on entity schedules, which use `times` instead. Optional.
|
|
190
|
+
* @description One entry of a `timeInterval` attribute's schedule on pages, products, blocks and attribute sets — found at `attributeValues[marker].value[].values[]`. Holds a compact recurrence rule — pass it to `expandTimeIntervals` to resolve concrete slots for a given window.
|
|
191
|
+
*/
|
|
192
|
+
interface ITimeIntervalEntitySchedule {
|
|
193
|
+
id: string;
|
|
194
|
+
intervalId: string;
|
|
195
|
+
dates: string[];
|
|
196
|
+
times: ITimeIntervalPoint[][];
|
|
197
|
+
inEveryWeek: boolean;
|
|
198
|
+
inEveryMonth: boolean;
|
|
199
|
+
exceptions?: unknown[];
|
|
200
|
+
intervals?: ITimeIntervalRange[];
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* @interface ITimeIntervalGroup
|
|
204
|
+
* @property {string} intervalId - Identifier of the interval group (UUID). Example: "c6466cd8-c55d-4583-97c5-42b684210f12".
|
|
205
|
+
* @property {ITimeIntervalEntitySchedule[]} values - The schedules configured under this group.
|
|
206
|
+
* @description One entry of a `timeInterval` attribute's `value` array — a group of schedules sharing an `intervalId`.
|
|
207
|
+
*/
|
|
208
|
+
interface ITimeIntervalGroup {
|
|
209
|
+
intervalId: string;
|
|
210
|
+
values: ITimeIntervalEntitySchedule[];
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* @interface ITimeIntervalAttributeValue
|
|
214
|
+
* @property {'timeInterval'} type - Discriminant identifying the attribute as a `timeInterval`.
|
|
215
|
+
* @property {ITimeIntervalGroup[]} value - Groups of schedules attached to the attribute.
|
|
216
|
+
* @description {@link IAttributeValue} narrowed to `timeInterval` attributes, whose generic `value: unknown` resolves to an array of {@link ITimeIntervalGroup}. Narrow to it with `isTimeIntervalAttribute`, or skip straight to the slots with `expandAttributeTimeIntervals`.
|
|
217
|
+
*/
|
|
218
|
+
interface ITimeIntervalAttributeValue extends IAttributeValue {
|
|
219
|
+
type: 'timeInterval';
|
|
220
|
+
value: ITimeIntervalGroup[];
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* @interface ITimeIntervalWindow
|
|
224
|
+
* @property {Date | string | number} from - Inclusive start of the window.
|
|
225
|
+
* @property {Date | string | number} to - Inclusive end of the window.
|
|
226
|
+
* @description The bounded range to expand a schedule into. Both bounds are inclusive and compared at UTC day granularity, so the time-of-day component of `from`/`to` is ignored. The window is required: a schedule is an open-ended recurrence rule, and only the caller knows how far it needs to be resolved.
|
|
227
|
+
*/
|
|
228
|
+
interface ITimeIntervalWindow {
|
|
229
|
+
from: Date | string | number;
|
|
230
|
+
to: Date | string | number;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* TimeIntervalPair — one resolved slot as a `[start, end]` pair of ISO 8601 UTC timestamps.
|
|
234
|
+
*/
|
|
235
|
+
type TimeIntervalPair = [string, string];
|
|
178
236
|
/**
|
|
179
237
|
* @interface ITimeIntervalPoint
|
|
180
238
|
* @property {number} hours - Hour component (0-23).
|
|
@@ -289,4 +347,4 @@ type LangType = string | Array<string>;
|
|
|
289
347
|
* LocalizeType
|
|
290
348
|
*/
|
|
291
349
|
type LocalizeType = ILocalizeInfo;
|
|
292
|
-
export type { IAttributeLocalizeInfo, IAttributes, IAttributeValue, IAttributeValues, IConfig, IError, IHttpHeaders, IHttpOptions, ILocalizeInfo, IRating, ITimeIntervalPoint, ITimeIntervalRange, ITimeIntervalSchedule, LangType, LocalizeType, };
|
|
350
|
+
export type { IAttributeLocalizeInfo, IAttributes, IAttributeValue, IAttributeValues, IConfig, IError, IHttpHeaders, IHttpOptions, ILocalizeInfo, IRating, ITimeIntervalAttributeValue, ITimeIntervalEntitySchedule, ITimeIntervalGroup, ITimeIntervalPoint, ITimeIntervalRange, ITimeIntervalSchedule, ITimeIntervalWindow, LangType, LocalizeType, TimeIntervalPair, };
|
package/dist/base/validation.js
CHANGED
|
@@ -5,7 +5,6 @@ exports.createPaginatedSchema = createPaginatedSchema;
|
|
|
5
5
|
exports.validateResponse = validateResponse;
|
|
6
6
|
exports.validateResponseSafe = validateResponseSafe;
|
|
7
7
|
/* eslint-disable jsdoc/reject-any-type */
|
|
8
|
-
/* eslint-disable jsdoc/no-undefined-types */
|
|
9
8
|
const zod_1 = require("zod");
|
|
10
9
|
/**
|
|
11
10
|
* Common validation schemas for API responses
|
|
@@ -79,7 +79,7 @@ interface IFromPages {
|
|
|
79
79
|
interface IFormsEntity {
|
|
80
80
|
id: number;
|
|
81
81
|
attributeSetId: number | null;
|
|
82
|
-
type: 'order' | '
|
|
82
|
+
type: 'order' | 'sign_in_up' | 'collection' | 'data' | 'rating' | null;
|
|
83
83
|
localizeInfos: IFormLocalizeInfo;
|
|
84
84
|
version: number;
|
|
85
85
|
position: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import AdminsApi from './admins/adminsApi';
|
|
5
5
|
import AttributesSetsApi from './attribute-sets/attributeSetsApi';
|
|
6
6
|
import AuthProviderApi from './auth-provider/authProviderApi';
|
|
7
|
-
import type { IConfig } from './base/utils';
|
|
7
|
+
import type { IAttributeValue, IConfig, ITimeIntervalAttributeValue, ITimeIntervalEntitySchedule, ITimeIntervalGroup, ITimeIntervalSchedule, ITimeIntervalWindow, TimeIntervalPair } from './base/utils';
|
|
8
8
|
import BlocksApi from './blocks/blocksApi';
|
|
9
9
|
import DiscountsApi from './discounts/discountsApi';
|
|
10
10
|
import EventsApi from './events/eventsApi';
|
|
@@ -29,6 +29,8 @@ import TemplatePreviewsApi from './templates-preview/templatesPreviewApi';
|
|
|
29
29
|
import UserActivityApi from './user-activity/userActivityApi';
|
|
30
30
|
import UsersApi from './users/usersApi';
|
|
31
31
|
import WsApi from './web-socket/wsApi';
|
|
32
|
+
export { expandAttributeTimeIntervals, expandTimeIntervals, isTimeIntervalAttribute, } from './base/timeIntervals';
|
|
33
|
+
export type { IAttributeValue, ITimeIntervalAttributeValue, ITimeIntervalEntitySchedule, ITimeIntervalGroup, ITimeIntervalSchedule, ITimeIntervalWindow, TimeIntervalPair, };
|
|
32
34
|
/**
|
|
33
35
|
* IDefineApi interface
|
|
34
36
|
* @interface IDefineApi
|
|
@@ -135,4 +137,3 @@ interface IDefineApi {
|
|
|
135
137
|
* @description Define API.
|
|
136
138
|
*/
|
|
137
139
|
export declare function defineOneEntry(url: string, config: IConfig): IDefineApi;
|
|
138
|
-
export {};
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isTimeIntervalAttribute = exports.expandTimeIntervals = exports.expandAttributeTimeIntervals = void 0;
|
|
6
7
|
exports.defineOneEntry = defineOneEntry;
|
|
7
8
|
/**
|
|
8
9
|
* OneEntry SDK
|
|
@@ -35,6 +36,10 @@ const templatesPreviewApi_1 = __importDefault(require("./templates-preview/templ
|
|
|
35
36
|
const userActivityApi_1 = __importDefault(require("./user-activity/userActivityApi"));
|
|
36
37
|
const usersApi_1 = __importDefault(require("./users/usersApi"));
|
|
37
38
|
const wsApi_1 = __importDefault(require("./web-socket/wsApi"));
|
|
39
|
+
var timeIntervals_1 = require("./base/timeIntervals");
|
|
40
|
+
Object.defineProperty(exports, "expandAttributeTimeIntervals", { enumerable: true, get: function () { return timeIntervals_1.expandAttributeTimeIntervals; } });
|
|
41
|
+
Object.defineProperty(exports, "expandTimeIntervals", { enumerable: true, get: function () { return timeIntervals_1.expandTimeIntervals; } });
|
|
42
|
+
Object.defineProperty(exports, "isTimeIntervalAttribute", { enumerable: true, get: function () { return timeIntervals_1.isTimeIntervalAttribute; } });
|
|
38
43
|
/**
|
|
39
44
|
* Define API.
|
|
40
45
|
* @function defineOneEntry
|