scschedule 3.2.0 → 4.1.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.
package/README.md CHANGED
@@ -263,6 +263,19 @@ ranges.forEach((range) => {
263
263
  })
264
264
  ```
265
265
 
266
+ #### `getApplicableRuleForDate(schedule: Schedule, date: SDate | SDateString): ApplicableRule`
267
+
268
+ Returns the rules that apply for a given date, indicating whether they come from the weekly schedule or an override. When `source` is `'weekly'`, `rules` is the weekly schedule (`true` or `WeeklyScheduleRule[]`). When `source` is `'override'`, `rules` is the override's rules and `overrideIndex` identifies which override applies.
269
+
270
+ ```typescript
271
+ import { getApplicableRuleForDate } from 'scschedule'
272
+ import { sDate } from 'scdate'
273
+
274
+ const result = getApplicableRuleForDate(restaurant, sDate('2025-12-15'))
275
+ // result.source === 'weekly' | 'override'
276
+ // result.rules === true | WeeklyScheduleRule[]
277
+ ```
278
+
266
279
  ## Usage Examples
267
280
 
268
281
  ### Basic Business Hours
@@ -472,13 +485,52 @@ const canOrderBreakfast =
472
485
 
473
486
  ## Validation Error Types
474
487
 
475
- The library uses discriminated unions for type-safe error handling:
488
+ The library uses discriminated unions for type-safe error handling. Location types (`RuleLocation`, `FieldLocation`) provide structured data for finding and highlighting errors in the UI—no path parsing required.
489
+
490
+ ```typescript
491
+ // RuleLocation - where a rule lives (used by EmptyWeekdays)
492
+ type RuleLocation =
493
+ | { type: RuleLocationType.Weekly; ruleIndex: number }
494
+ | {
495
+ type: RuleLocationType.Override
496
+ overrideIndex: number
497
+ ruleIndex: number
498
+ }
499
+
500
+ // FieldLocation - where a field lives (used by InvalidScDateFormat)
501
+ type FieldLocation =
502
+ | { type: RuleLocationType.Weekly; ruleIndex: number; field: RuleField }
503
+ | {
504
+ type: RuleLocationType.Override
505
+ overrideIndex: number
506
+ field: OverrideField
507
+ }
508
+ | {
509
+ type: RuleLocationType.Override
510
+ overrideIndex: number
511
+ ruleIndex: number
512
+ field: RuleField
513
+ }
514
+
515
+ // RuleField - rule-level fields (weekdays, from, to)
516
+ enum RuleField {
517
+ Weekdays = 'weekdays',
518
+ From = 'from',
519
+ To = 'to',
520
+ }
521
+
522
+ // OverrideField - override date range fields (from, to)
523
+ enum OverrideField {
524
+ From = 'from',
525
+ To = 'to',
526
+ }
527
+ ```
476
528
 
477
529
  ```typescript
478
530
  type ValidationError =
479
531
  | {
480
532
  issue: ValidationIssue.InvalidScDateFormat
481
- field: string
533
+ location: FieldLocation
482
534
  value: string
483
535
  expectedFormat: string
484
536
  }
@@ -509,13 +561,7 @@ type ValidationError =
509
561
  }
510
562
  | {
511
563
  issue: ValidationIssue.EmptyWeekdays
512
- location:
513
- | { type: RuleLocationType.Weekly; ruleIndex: number }
514
- | {
515
- type: RuleLocationType.Override
516
- overrideIndex: number
517
- ruleIndex: number
518
- }
564
+ location: RuleLocation
519
565
  }
520
566
  | {
521
567
  issue: ValidationIssue.OverrideWeekdaysMismatch
@@ -574,19 +620,27 @@ The library is written in TypeScript and provides full type definitions. All typ
574
620
 
575
621
  ```typescript
576
622
  import type {
623
+ ApplicableRule,
577
624
  Schedule,
578
625
  WeeklyScheduleRule,
579
626
  OverrideScheduleRule,
580
627
  AvailabilityRange,
581
628
  ValidationError,
582
629
  ValidationResult,
630
+ RuleLocation,
631
+ FieldLocation,
583
632
  SDateString,
584
633
  STimeString,
585
634
  STimestampString,
586
635
  SWeekdaysString,
587
636
  } from 'scschedule'
588
637
 
589
- import { ValidationIssue, RuleLocationType } from 'scschedule'
638
+ import {
639
+ ValidationIssue,
640
+ RuleLocationType,
641
+ RuleField,
642
+ OverrideField,
643
+ } from 'scschedule'
590
644
  ```
591
645
 
592
646
  Note: The `Weekday` enum (used in some `ValidationError` variants) is exported from `scdate`, not `scschedule`:
@@ -58,3 +58,25 @@ export declare enum RuleLocationType {
58
58
  /** The rule is in an override section */
59
59
  Override = "override"
60
60
  }
61
+ /**
62
+ * Fields within a rule (weekly or override rule) that can contain scdate
63
+ * format values. Used in validation errors for rule-level format failures.
64
+ */
65
+ export declare enum RuleField {
66
+ /** Days of the week (SMTWTFS format) */
67
+ Weekdays = "weekdays",
68
+ /** Start time */
69
+ From = "from",
70
+ /** End time */
71
+ To = "to"
72
+ }
73
+ /**
74
+ * Fields on an override's date range that can contain scdate format values.
75
+ * Used in validation errors for override-level format failures.
76
+ */
77
+ export declare enum OverrideField {
78
+ /** Start date */
79
+ From = "from",
80
+ /** End date */
81
+ To = "to"
82
+ }
package/dist/constants.js CHANGED
@@ -60,3 +60,27 @@ export var RuleLocationType;
60
60
  /** The rule is in an override section */
61
61
  RuleLocationType["Override"] = "override";
62
62
  })(RuleLocationType || (RuleLocationType = {}));
63
+ /**
64
+ * Fields within a rule (weekly or override rule) that can contain scdate
65
+ * format values. Used in validation errors for rule-level format failures.
66
+ */
67
+ export var RuleField;
68
+ (function (RuleField) {
69
+ /** Days of the week (SMTWTFS format) */
70
+ RuleField["Weekdays"] = "weekdays";
71
+ /** Start time */
72
+ RuleField["From"] = "from";
73
+ /** End time */
74
+ RuleField["To"] = "to";
75
+ })(RuleField || (RuleField = {}));
76
+ /**
77
+ * Fields on an override's date range that can contain scdate format values.
78
+ * Used in validation errors for override-level format failures.
79
+ */
80
+ export var OverrideField;
81
+ (function (OverrideField) {
82
+ /** Start date */
83
+ OverrideField["From"] = "from";
84
+ /** End date */
85
+ OverrideField["To"] = "to";
86
+ })(OverrideField || (OverrideField = {}));
@@ -1,5 +1,5 @@
1
1
  import { SDate } from 'scdate';
2
- import type { Schedule, SDateString, WeeklyScheduleRule } from '../types.js';
2
+ import type { Schedule, SDateString, WeeklyScheduleRule } from './types.js';
3
3
  export type ApplicableRule = {
4
4
  source: 'weekly';
5
5
  rules: WeeklyScheduleRule[] | true;
@@ -1,5 +1,5 @@
1
1
  import { addDaysToDate, doesWeekdaysIncludeWeekday, getTimestampFromDateAndTime, getWeekdayFromDate, isAfterTime, isSameDateOrBefore, } from 'scdate';
2
- import { getApplicableRuleForDate } from './internal/getApplicableRuleForDate.js';
2
+ import { getApplicableRuleForDate } from './getApplicableRuleForDate.js';
3
3
  /**
4
4
  * Returns all available time ranges within a schedule for the specified
5
5
  * date range.
@@ -1,5 +1,5 @@
1
1
  import { addDaysToDate, doesWeekdaysIncludeWeekday, getDateFromTimestamp, getTimeFromTimestamp, getTimestampFromDateAndTime, getWeekdayFromDate, isAfterTime, isBeforeTime, sTimestamp, } from 'scdate';
2
- import { getApplicableRuleForDate } from './internal/getApplicableRuleForDate.js';
2
+ import { getApplicableRuleForDate } from './getApplicableRuleForDate.js';
3
3
  import { isScheduleAvailable } from './isScheduleAvailable.js';
4
4
  /**
5
5
  * Finds the next available timestamp in a schedule starting from the
@@ -1,5 +1,5 @@
1
1
  import { addDaysToDate, addMinutesToTimestamp, doesWeekdaysIncludeWeekday, getDateFromTimestamp, getTimeFromTimestamp, getTimestampFromDateAndTime, getWeekdayFromDate, isAfterTime, isBeforeTimestamp, isSameTimeOrAfter, sTimestamp, } from 'scdate';
2
- import { getApplicableRuleForDate } from './internal/getApplicableRuleForDate.js';
2
+ import { getApplicableRuleForDate } from './getApplicableRuleForDate.js';
3
3
  import { isScheduleAvailable } from './isScheduleAvailable.js';
4
4
  /**
5
5
  * Collects "range end + 1 minute" candidate timestamps for a given date's
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export * from './constants.js';
4
4
  export type * from './types.js';
5
5
  export * from './validateSchedule.js';
6
6
  export * from './cleanupExpiredOverridesFromSchedule.js';
7
+ export * from './getApplicableRuleForDate.js';
7
8
  export * from './getAvailableRangesFromSchedule.js';
8
9
  export * from './getNextAvailableFromSchedule.js';
9
10
  export * from './getNextUnavailableFromSchedule.js';
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ export * from './validateSchedule.js';
7
7
  // Export schedule management functions
8
8
  export * from './cleanupExpiredOverridesFromSchedule.js';
9
9
  // Export availability query functions
10
+ export * from './getApplicableRuleForDate.js';
10
11
  export * from './getAvailableRangesFromSchedule.js';
11
12
  export * from './getNextAvailableFromSchedule.js';
12
13
  export * from './getNextUnavailableFromSchedule.js';
@@ -2,7 +2,6 @@ export type * from './types.js';
2
2
  export * from './doOverridesOverlap.js';
3
3
  export * from './doRulesOverlap.js';
4
4
  export * from './doTimeRangesOverlap.js';
5
- export * from './getApplicableRuleForDate.js';
6
5
  export * from './getEffectiveTimesForWeekday.js';
7
6
  export * from './isTimeInTimeRange.js';
8
7
  export * from './normalizeScheduleForValidation.js';
@@ -2,7 +2,6 @@
2
2
  export * from './doOverridesOverlap.js';
3
3
  export * from './doRulesOverlap.js';
4
4
  export * from './doTimeRangesOverlap.js';
5
- export * from './getApplicableRuleForDate.js';
6
5
  export * from './getEffectiveTimesForWeekday.js';
7
6
  export * from './isTimeInTimeRange.js';
8
7
  export * from './normalizeScheduleForValidation.js';
@@ -1,7 +1,7 @@
1
1
  import { addDaysToDate, doesWeekdaysIncludeWeekday, getWeekdayFromDate, sDate, } from 'scdate';
2
2
  import { ValidationIssue } from '../constants.js';
3
3
  import { doTimeRangesOverlap } from './doTimeRangesOverlap.js';
4
- import { getApplicableRuleForDate } from './getApplicableRuleForDate.js';
4
+ import { getApplicableRuleForDate } from '../getApplicableRuleForDate.js';
5
5
  import { splitCrossMidnightTimeRange } from './splitCrossMidnightTimeRange.js';
6
6
  /**
7
7
  * Validates that cross-midnight spillover at override boundaries doesn't
@@ -1,16 +1,16 @@
1
1
  import { sDate, sTime, sWeekdays } from 'scdate';
2
- import { ValidationIssue } from '../constants.js';
2
+ import { OverrideField, RuleField, RuleLocationType, ValidationIssue, } from '../constants.js';
3
3
  /**
4
4
  * Validates that a value is a valid SDate format (YYYY-MM-DD).
5
5
  */
6
- const validateSDateValue = (value, field) => {
6
+ const validateSDateValue = (value, location) => {
7
7
  try {
8
8
  sDate(value);
9
9
  }
10
10
  catch {
11
11
  return {
12
12
  issue: ValidationIssue.InvalidScDateFormat,
13
- field,
13
+ location,
14
14
  value: String(value),
15
15
  expectedFormat: 'YYYY-MM-DD',
16
16
  };
@@ -20,14 +20,14 @@ const validateSDateValue = (value, field) => {
20
20
  /**
21
21
  * Validates that a value is a valid STime format (HH:MM).
22
22
  */
23
- const validateSTimeValue = (value, field) => {
23
+ const validateSTimeValue = (value, location) => {
24
24
  try {
25
25
  sTime(value);
26
26
  }
27
27
  catch {
28
28
  return {
29
29
  issue: ValidationIssue.InvalidScDateFormat,
30
- field,
30
+ location,
31
31
  value: String(value),
32
32
  expectedFormat: 'HH:MM',
33
33
  };
@@ -37,14 +37,14 @@ const validateSTimeValue = (value, field) => {
37
37
  /**
38
38
  * Validates that a value is a valid SWeekdays format (SMTWTFS).
39
39
  */
40
- const validateSWeekdaysValue = (value, field) => {
40
+ const validateSWeekdaysValue = (value, location) => {
41
41
  try {
42
42
  sWeekdays(value);
43
43
  }
44
44
  catch {
45
45
  return {
46
46
  issue: ValidationIssue.InvalidScDateFormat,
47
- field,
47
+ location,
48
48
  value: String(value),
49
49
  expectedFormat: 'SMTWTFS',
50
50
  };
@@ -54,13 +54,19 @@ const validateSWeekdaysValue = (value, field) => {
54
54
  /**
55
55
  * Validates both from and to times in a time range.
56
56
  */
57
- const validateTimeRange = (timeRange, fieldPrefix) => {
57
+ const validateTimeRange = (timeRange, locationBase) => {
58
58
  const errors = [];
59
- const fromError = validateSTimeValue(timeRange.from, `${fieldPrefix}.from`);
59
+ const fromError = validateSTimeValue(timeRange.from, {
60
+ ...locationBase,
61
+ field: RuleField.From,
62
+ });
60
63
  if (fromError) {
61
64
  errors.push(fromError);
62
65
  }
63
- const toError = validateSTimeValue(timeRange.to, `${fieldPrefix}.to`);
66
+ const toError = validateSTimeValue(timeRange.to, {
67
+ ...locationBase,
68
+ field: RuleField.To,
69
+ });
64
70
  if (toError) {
65
71
  errors.push(toError);
66
72
  }
@@ -69,13 +75,16 @@ const validateTimeRange = (timeRange, fieldPrefix) => {
69
75
  /**
70
76
  * Validates a rule (weekdays and time range).
71
77
  */
72
- const validateRule = (rule, fieldPrefix) => {
78
+ const validateRule = (rule, locationBase) => {
73
79
  const errors = [];
74
- const weekdaysError = validateSWeekdaysValue(rule.weekdays, `${fieldPrefix}.weekdays`);
80
+ const weekdaysError = validateSWeekdaysValue(rule.weekdays, {
81
+ ...locationBase,
82
+ field: RuleField.Weekdays,
83
+ });
75
84
  if (weekdaysError) {
76
85
  errors.push(weekdaysError);
77
86
  }
78
- errors.push(...validateTimeRange(rule, fieldPrefix));
87
+ errors.push(...validateTimeRange(rule, locationBase));
79
88
  return errors;
80
89
  };
81
90
  /**
@@ -86,22 +95,37 @@ export const validateScDateFormats = (schedule) => {
86
95
  // Validate weekly rules
87
96
  const weeklyRules = schedule.weekly === true ? [] : schedule.weekly;
88
97
  weeklyRules.forEach((rule, ruleIndex) => {
89
- errors.push(...validateRule(rule, `weekly[${String(ruleIndex)}]`));
98
+ errors.push(...validateRule(rule, {
99
+ type: RuleLocationType.Weekly,
100
+ ruleIndex,
101
+ }));
90
102
  });
91
103
  // Validate overrides
92
104
  schedule.overrides?.forEach((override, overrideIndex) => {
93
- const fromError = validateSDateValue(override.from, `overrides[${String(overrideIndex)}].from`);
105
+ const fromError = validateSDateValue(override.from, {
106
+ type: RuleLocationType.Override,
107
+ overrideIndex,
108
+ field: OverrideField.From,
109
+ });
94
110
  if (fromError) {
95
111
  errors.push(fromError);
96
112
  }
97
113
  if (override.to) {
98
- const toError = validateSDateValue(override.to, `overrides[${String(overrideIndex)}].to`);
114
+ const toError = validateSDateValue(override.to, {
115
+ type: RuleLocationType.Override,
116
+ overrideIndex,
117
+ field: OverrideField.To,
118
+ });
99
119
  if (toError) {
100
120
  errors.push(toError);
101
121
  }
102
122
  }
103
123
  override.rules.forEach((rule, ruleIndex) => {
104
- errors.push(...validateRule(rule, `overrides[${String(overrideIndex)}].rules[${String(ruleIndex)}]`));
124
+ errors.push(...validateRule(rule, {
125
+ type: RuleLocationType.Override,
126
+ overrideIndex,
127
+ ruleIndex,
128
+ }));
105
129
  });
106
130
  });
107
131
  return errors;
@@ -1,5 +1,5 @@
1
1
  import { addDaysToDate, doesWeekdaysIncludeWeekday, getDateFromTimestamp, getTimeFromTimestamp, getWeekdayFromDate, } from 'scdate';
2
- import { getApplicableRuleForDate } from './internal/getApplicableRuleForDate.js';
2
+ import { getApplicableRuleForDate } from './getApplicableRuleForDate.js';
3
3
  import { isTimeInTimeRange } from './internal/isTimeInTimeRange.js';
4
4
  /**
5
5
  * Checks if a schedule is available at the specified timestamp.
package/dist/types.d.ts CHANGED
@@ -1,5 +1,35 @@
1
1
  import type { SDate, STime, STimestamp, SWeekdays, Weekday } from 'scdate';
2
- import { RuleLocationType, ValidationIssue } from './constants.js';
2
+ import { OverrideField, RuleField, RuleLocationType, ValidationIssue } from './constants.js';
3
+ /**
4
+ * Location of a rule within the schedule structure. Used by validation errors
5
+ * that point to a specific rule (e.g. EmptyWeekdays).
6
+ */
7
+ export type RuleLocation = {
8
+ type: RuleLocationType.Weekly;
9
+ ruleIndex: number;
10
+ } | {
11
+ type: RuleLocationType.Override;
12
+ overrideIndex: number;
13
+ ruleIndex: number;
14
+ };
15
+ /**
16
+ * Location of a field within the schedule structure. Used by validation errors
17
+ * that point to a specific field (e.g. InvalidScDateFormat).
18
+ */
19
+ export type FieldLocation = {
20
+ type: RuleLocationType.Weekly;
21
+ ruleIndex: number;
22
+ field: RuleField;
23
+ } | {
24
+ type: RuleLocationType.Override;
25
+ overrideIndex: number;
26
+ field: OverrideField;
27
+ } | {
28
+ type: RuleLocationType.Override;
29
+ overrideIndex: number;
30
+ ruleIndex: number;
31
+ field: RuleField;
32
+ };
3
33
  /**
4
34
  * String in YYYY-MM-DD format representing a date.
5
35
  */
@@ -98,8 +128,8 @@ export type ValidationError = {
98
128
  * (SDate, STime, SWeekdays, or STimestamp)
99
129
  */
100
130
  issue: ValidationIssue.InvalidScDateFormat;
101
- /** Path to the field with invalid format (e.g., 'weekly[0].weekdays') */
102
- field: string;
131
+ /** Location of the field with invalid format */
132
+ location: FieldLocation;
103
133
  /** The invalid value that was provided */
104
134
  value: string;
105
135
  /** The expected format (e.g., 'SMTWTFS', 'HH:MM', 'YYYY-MM-DD') */
@@ -110,14 +140,7 @@ export type ValidationError = {
110
140
  */
111
141
  issue: ValidationIssue.EmptyWeekdays;
112
142
  /** Location of the rule with empty weekdays */
113
- location: {
114
- type: RuleLocationType.Weekly;
115
- ruleIndex: number;
116
- } | {
117
- type: RuleLocationType.Override;
118
- overrideIndex: number;
119
- ruleIndex: number;
120
- };
143
+ location: RuleLocation;
121
144
  } | {
122
145
  /**
123
146
  * An override has weekdays that don't match any actual dates in the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scschedule",
3
- "version": "3.2.0",
3
+ "version": "4.1.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/index.js"
@@ -20,15 +20,15 @@
20
20
  "test": "vitest run"
21
21
  },
22
22
  "dependencies": {
23
- "scdate": "3.2.0"
23
+ "scdate": "workspace:*"
24
24
  },
25
25
  "devDependencies": {
26
- "@eslint/js": "^9.39.2",
26
+ "@eslint/js": "^9.39.3",
27
27
  "@tsconfig/strictest": "^2.0.8",
28
- "@types/node": "^24.10.13",
29
- "eslint": "^9.39.2",
28
+ "@types/node": "^24.11.0",
29
+ "eslint": "^9.39.3",
30
30
  "typescript": "^5.9.3",
31
- "typescript-eslint": "^8.55.0",
31
+ "typescript-eslint": "^8.56.1",
32
32
  "vitest": "^4.0.18"
33
33
  },
34
34
  "repository": {
@@ -46,4 +46,4 @@
46
46
  "publishConfig": {
47
47
  "access": "public"
48
48
  }
49
- }
49
+ }