@craftguild/jscalendar 0.2.0 → 0.3.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 (99) hide show
  1. package/README.md +63 -0
  2. package/dist/__tests__/builders.test.d.ts +1 -0
  3. package/dist/__tests__/builders.test.js +82 -0
  4. package/dist/__tests__/calendar-extra.test.js +36 -0
  5. package/dist/__tests__/recurrence.test.js +123 -0
  6. package/dist/__tests__/search.test.js +27 -0
  7. package/dist/__tests__/utils.test.js +3 -0
  8. package/dist/__tests__/validation.test.js +113 -0
  9. package/dist/ical.d.ts +6 -0
  10. package/dist/ical.js +71 -3
  11. package/dist/jscal/base.d.ts +90 -0
  12. package/dist/jscal/base.js +181 -0
  13. package/dist/jscal/builders.d.ts +135 -0
  14. package/dist/jscal/builders.js +220 -0
  15. package/dist/jscal/constants.d.ts +11 -0
  16. package/dist/jscal/constants.js +11 -0
  17. package/dist/jscal/datetime.d.ts +14 -0
  18. package/dist/jscal/datetime.js +42 -0
  19. package/dist/jscal/defaults.d.ts +31 -0
  20. package/dist/jscal/defaults.js +102 -0
  21. package/dist/jscal/duration.d.ts +43 -0
  22. package/dist/jscal/duration.js +72 -0
  23. package/dist/jscal/event.d.ts +17 -0
  24. package/dist/jscal/event.js +71 -0
  25. package/dist/jscal/group.d.ts +25 -0
  26. package/dist/jscal/group.js +62 -0
  27. package/dist/jscal/guards.d.ts +19 -0
  28. package/dist/jscal/guards.js +25 -0
  29. package/dist/jscal/ids.d.ts +11 -0
  30. package/dist/jscal/ids.js +77 -0
  31. package/dist/jscal/normalize.d.ts +32 -0
  32. package/dist/jscal/normalize.js +45 -0
  33. package/dist/jscal/task.d.ts +17 -0
  34. package/dist/jscal/task.js +60 -0
  35. package/dist/jscal/types.d.ts +38 -0
  36. package/dist/jscal/types.js +1 -0
  37. package/dist/jscal.d.ts +77 -70
  38. package/dist/jscal.js +77 -465
  39. package/dist/patch.d.ts +13 -0
  40. package/dist/patch.js +166 -41
  41. package/dist/recurrence/constants.d.ts +13 -0
  42. package/dist/recurrence/constants.js +13 -0
  43. package/dist/recurrence/date-utils.d.ts +125 -0
  44. package/dist/recurrence/date-utils.js +259 -0
  45. package/dist/recurrence/expand.d.ts +23 -0
  46. package/dist/recurrence/expand.js +294 -0
  47. package/dist/recurrence/rule-candidates.d.ts +21 -0
  48. package/dist/recurrence/rule-candidates.js +120 -0
  49. package/dist/recurrence/rule-generate.d.ts +11 -0
  50. package/dist/recurrence/rule-generate.js +36 -0
  51. package/dist/recurrence/rule-matchers.d.ts +34 -0
  52. package/dist/recurrence/rule-matchers.js +120 -0
  53. package/dist/recurrence/rule-normalize.d.ts +9 -0
  54. package/dist/recurrence/rule-normalize.js +57 -0
  55. package/dist/recurrence/rule-selectors.d.ts +7 -0
  56. package/dist/recurrence/rule-selectors.js +21 -0
  57. package/dist/recurrence/rules.d.ts +14 -0
  58. package/dist/recurrence/rules.js +57 -0
  59. package/dist/recurrence/types.d.ts +27 -0
  60. package/dist/recurrence/types.js +1 -0
  61. package/dist/recurrence.d.ts +2 -15
  62. package/dist/recurrence.js +1 -674
  63. package/dist/search.d.ts +30 -0
  64. package/dist/search.js +92 -8
  65. package/dist/timezones/chunk_1.d.ts +2 -0
  66. package/dist/timezones/chunk_1.js +72 -0
  67. package/dist/timezones/chunk_2.d.ts +2 -0
  68. package/dist/timezones/chunk_2.js +72 -0
  69. package/dist/timezones/chunk_3.d.ts +2 -0
  70. package/dist/timezones/chunk_3.js +72 -0
  71. package/dist/timezones/chunk_4.d.ts +2 -0
  72. package/dist/timezones/chunk_4.js +72 -0
  73. package/dist/timezones/chunk_5.d.ts +2 -0
  74. package/dist/timezones/chunk_5.js +72 -0
  75. package/dist/timezones/chunk_6.d.ts +2 -0
  76. package/dist/timezones/chunk_6.js +72 -0
  77. package/dist/timezones/chunk_7.d.ts +2 -0
  78. package/dist/timezones/chunk_7.js +6 -0
  79. package/dist/timezones.d.ts +5 -0
  80. package/dist/timezones.js +14 -3
  81. package/dist/utils.d.ts +72 -0
  82. package/dist/utils.js +85 -1
  83. package/dist/validate/asserts.d.ts +155 -0
  84. package/dist/validate/asserts.js +381 -0
  85. package/dist/validate/constants.d.ts +25 -0
  86. package/dist/validate/constants.js +33 -0
  87. package/dist/validate/error.d.ts +19 -0
  88. package/dist/validate/error.js +25 -0
  89. package/dist/validate/validators-common.d.ts +64 -0
  90. package/dist/validate/validators-common.js +385 -0
  91. package/dist/validate/validators-objects.d.ts +8 -0
  92. package/dist/validate/validators-objects.js +70 -0
  93. package/dist/validate/validators-recurrence.d.ts +15 -0
  94. package/dist/validate/validators-recurrence.js +115 -0
  95. package/dist/validate/validators.d.ts +1 -0
  96. package/dist/validate/validators.js +1 -0
  97. package/dist/validate.d.ts +2 -6
  98. package/dist/validate.js +2 -745
  99. package/package.json +1 -1
@@ -0,0 +1,42 @@
1
+ import { normalizeUtcDateTime } from "../utils.js";
2
+ import { isStringValue } from "../utils.js";
3
+ /**
4
+ * Pad a number to two digits.
5
+ * @param value Number to pad.
6
+ * @return Zero-padded string.
7
+ */
8
+ function pad2(value) {
9
+ return value.toString().padStart(2, "0");
10
+ }
11
+ /**
12
+ * Convert a Date input to a LocalDateTime string.
13
+ * @param value Date or LocalDateTime string.
14
+ * @return LocalDateTime string.
15
+ */
16
+ export function toLocalDateTime(value) {
17
+ if (isStringValue(value))
18
+ return value;
19
+ return [
20
+ value.getFullYear().toString().padStart(4, "0"),
21
+ "-",
22
+ pad2(value.getMonth() + 1),
23
+ "-",
24
+ pad2(value.getDate()),
25
+ "T",
26
+ pad2(value.getHours()),
27
+ ":",
28
+ pad2(value.getMinutes()),
29
+ ":",
30
+ pad2(value.getSeconds()),
31
+ ].join("");
32
+ }
33
+ /**
34
+ * Convert a Date input to a UTCDateTime string.
35
+ * @param value Date or UTCDateTime string.
36
+ * @return UTCDateTime string.
37
+ */
38
+ export function toUtcDateTime(value) {
39
+ if (isStringValue(value))
40
+ return value;
41
+ return normalizeUtcDateTime(value.toISOString());
42
+ }
@@ -0,0 +1,31 @@
1
+ import type { Alert, Event, JSCalendarObject, Participant, Task } from "../types.js";
2
+ /**
3
+ * Apply common defaults to the target.
4
+ * @param data JSCalendar object to mutate.
5
+ * @return The same object with defaults applied.
6
+ */
7
+ export declare function applyCommonDefaults<T extends JSCalendarObject>(data: T): T;
8
+ /**
9
+ * Apply event defaults to the target.
10
+ * @param data Event to mutate.
11
+ * @return The same event with defaults applied.
12
+ */
13
+ export declare function applyEventDefaults(data: Event): Event;
14
+ /**
15
+ * Apply task defaults to the target.
16
+ * @param data Task to mutate.
17
+ * @return The same task with defaults applied.
18
+ */
19
+ export declare function applyTaskDefaults(data: Task): Task;
20
+ /**
21
+ * Apply participant defaults to the target.
22
+ * @param participant Participant to mutate.
23
+ * @return The same participant with defaults applied.
24
+ */
25
+ export declare function applyParticipantDefaults(participant: Participant): Participant;
26
+ /**
27
+ * Apply alert defaults to the target.
28
+ * @param alert Alert to mutate.
29
+ * @return The same alert with defaults applied.
30
+ */
31
+ export declare function applyAlertDefaults(alert: Alert): Alert;
@@ -0,0 +1,102 @@
1
+ import { STATUS_COMPLETED, STATUS_FAILED, STATUS_IN_PROCESS, STATUS_NEEDS_ACTION, TRIGGER_OFFSET } from "./constants.js";
2
+ /**
3
+ * Apply common defaults to the target.
4
+ * @param data JSCalendar object to mutate.
5
+ * @return The same object with defaults applied.
6
+ */
7
+ export function applyCommonDefaults(data) {
8
+ if (data.sequence === undefined)
9
+ data.sequence = 0;
10
+ if (data.title === undefined)
11
+ data.title = "";
12
+ if (data.description === undefined)
13
+ data.description = "";
14
+ if (data.descriptionContentType === undefined)
15
+ data.descriptionContentType = "text/plain";
16
+ if (data.showWithoutTime === undefined)
17
+ data.showWithoutTime = false;
18
+ if (data.recurrenceIdTimeZone === undefined)
19
+ data.recurrenceIdTimeZone = null;
20
+ if (data.excluded === undefined)
21
+ data.excluded = false;
22
+ if (data.priority === undefined)
23
+ data.priority = 0;
24
+ if (data.freeBusyStatus === undefined)
25
+ data.freeBusyStatus = "busy";
26
+ if (data.privacy === undefined)
27
+ data.privacy = "public";
28
+ if (data.useDefaultAlerts === undefined)
29
+ data.useDefaultAlerts = false;
30
+ return data;
31
+ }
32
+ /**
33
+ * Apply event defaults to the target.
34
+ * @param data Event to mutate.
35
+ * @return The same event with defaults applied.
36
+ */
37
+ export function applyEventDefaults(data) {
38
+ if (data.duration === undefined)
39
+ data.duration = "PT0S";
40
+ if (data.status === undefined)
41
+ data.status = "confirmed";
42
+ return data;
43
+ }
44
+ /**
45
+ * Apply task defaults to the target.
46
+ * @param data Task to mutate.
47
+ * @return The same task with defaults applied.
48
+ */
49
+ export function applyTaskDefaults(data) {
50
+ if (data.progress === undefined) {
51
+ const participants = data.participants ? Object.values(data.participants) : [];
52
+ if (participants.length === 0) {
53
+ data.progress = STATUS_NEEDS_ACTION;
54
+ }
55
+ else if (participants.every((p) => p.progress === STATUS_COMPLETED)) {
56
+ data.progress = STATUS_COMPLETED;
57
+ }
58
+ else if (participants.some((p) => p.progress === STATUS_FAILED)) {
59
+ data.progress = STATUS_FAILED;
60
+ }
61
+ else if (participants.some((p) => p.progress === STATUS_IN_PROCESS)) {
62
+ data.progress = STATUS_IN_PROCESS;
63
+ }
64
+ else {
65
+ data.progress = STATUS_NEEDS_ACTION;
66
+ }
67
+ }
68
+ return data;
69
+ }
70
+ /**
71
+ * Apply participant defaults to the target.
72
+ * @param participant Participant to mutate.
73
+ * @return The same participant with defaults applied.
74
+ */
75
+ export function applyParticipantDefaults(participant) {
76
+ if (participant.participationStatus === undefined)
77
+ participant.participationStatus = "needs-action";
78
+ if (participant.expectReply === undefined)
79
+ participant.expectReply = false;
80
+ if (participant.scheduleAgent === undefined)
81
+ participant.scheduleAgent = "server";
82
+ if (participant.scheduleForceSend === undefined)
83
+ participant.scheduleForceSend = false;
84
+ if (participant.scheduleSequence === undefined)
85
+ participant.scheduleSequence = 0;
86
+ return participant;
87
+ }
88
+ /**
89
+ * Apply alert defaults to the target.
90
+ * @param alert Alert to mutate.
91
+ * @return The same alert with defaults applied.
92
+ */
93
+ export function applyAlertDefaults(alert) {
94
+ if (alert.action === undefined)
95
+ alert.action = "display";
96
+ if (alert.trigger["@type"] === TRIGGER_OFFSET) {
97
+ if (alert.trigger.relativeTo === undefined) {
98
+ alert.trigger.relativeTo = "start";
99
+ }
100
+ }
101
+ return alert;
102
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Convert seconds to an ISO 8601 duration string.
3
+ * @param totalSeconds Total seconds to encode.
4
+ * @return ISO 8601 duration string.
5
+ */
6
+ export declare function durationFromSeconds(totalSeconds: number): string;
7
+ export declare const Duration: {
8
+ /**
9
+ * Convert seconds to a duration string.
10
+ * @param value Total seconds.
11
+ * @return ISO 8601 duration string.
12
+ */
13
+ seconds(value: number): string;
14
+ /**
15
+ * Convert minutes to a duration string.
16
+ * @param value Total minutes.
17
+ * @return ISO 8601 duration string.
18
+ */
19
+ minutes(value: number): string;
20
+ /**
21
+ * Convert hours to a duration string.
22
+ * @param value Total hours.
23
+ * @return ISO 8601 duration string.
24
+ */
25
+ hours(value: number): string;
26
+ /**
27
+ * Convert days to a duration string.
28
+ * @param value Total days.
29
+ * @return ISO 8601 duration string.
30
+ */
31
+ days(value: number): string;
32
+ /**
33
+ * Build a duration string from component parts.
34
+ * @param parts Day/hour/minute/second parts.
35
+ * @return ISO 8601 duration string.
36
+ */
37
+ from(parts: {
38
+ days?: number;
39
+ hours?: number;
40
+ minutes?: number;
41
+ seconds?: number;
42
+ }): string;
43
+ };
@@ -0,0 +1,72 @@
1
+ import { EMPTY_STRING } from "./constants.js";
2
+ /**
3
+ * Convert seconds to an ISO 8601 duration string.
4
+ * @param totalSeconds Total seconds to encode.
5
+ * @return ISO 8601 duration string.
6
+ */
7
+ export function durationFromSeconds(totalSeconds) {
8
+ const clamped = Math.max(0, Math.floor(totalSeconds));
9
+ const days = Math.floor(clamped / 86400);
10
+ let remaining = clamped % 86400;
11
+ const hours = Math.floor(remaining / 3600);
12
+ remaining %= 3600;
13
+ const minutes = Math.floor(remaining / 60);
14
+ const seconds = remaining % 60;
15
+ const datePart = days > 0 ? `${days}D` : "";
16
+ const timeParts = [];
17
+ if (hours > 0)
18
+ timeParts.push(`${hours}H`);
19
+ if (minutes > 0)
20
+ timeParts.push(`${minutes}M`);
21
+ if (seconds > 0 || (datePart === EMPTY_STRING && timeParts.length === 0)) {
22
+ timeParts.push(`${seconds}S`);
23
+ }
24
+ const timePart = timeParts.length > 0 ? `T${timeParts.join("")}` : "";
25
+ return `P${datePart}${timePart}`;
26
+ }
27
+ export const Duration = {
28
+ /**
29
+ * Convert seconds to a duration string.
30
+ * @param value Total seconds.
31
+ * @return ISO 8601 duration string.
32
+ */
33
+ seconds(value) {
34
+ return durationFromSeconds(value);
35
+ },
36
+ /**
37
+ * Convert minutes to a duration string.
38
+ * @param value Total minutes.
39
+ * @return ISO 8601 duration string.
40
+ */
41
+ minutes(value) {
42
+ return durationFromSeconds(value * 60);
43
+ },
44
+ /**
45
+ * Convert hours to a duration string.
46
+ * @param value Total hours.
47
+ * @return ISO 8601 duration string.
48
+ */
49
+ hours(value) {
50
+ return durationFromSeconds(value * 3600);
51
+ },
52
+ /**
53
+ * Convert days to a duration string.
54
+ * @param value Total days.
55
+ * @return ISO 8601 duration string.
56
+ */
57
+ days(value) {
58
+ return durationFromSeconds(value * 86400);
59
+ },
60
+ /**
61
+ * Build a duration string from component parts.
62
+ * @param parts Day/hour/minute/second parts.
63
+ * @return ISO 8601 duration string.
64
+ */
65
+ from(parts) {
66
+ const seconds = (parts.days ?? 0) * 86400 +
67
+ (parts.hours ?? 0) * 3600 +
68
+ (parts.minutes ?? 0) * 60 +
69
+ (parts.seconds ?? 0);
70
+ return durationFromSeconds(seconds);
71
+ },
72
+ };
@@ -0,0 +1,17 @@
1
+ import type { Event } from "../types.js";
2
+ import type { CreateOptions, EventInput } from "./types.js";
3
+ import { Base } from "./base.js";
4
+ export declare class EventObject extends Base<Event> {
5
+ /**
6
+ * Create an event with normalized dates, duration, and RFC defaults.
7
+ * @param input Event input values to normalize.
8
+ * @param options Creation options (validation, clock).
9
+ * @return EventObject instance.
10
+ */
11
+ constructor(input: EventInput, options?: CreateOptions);
12
+ /**
13
+ * Clone the event as a new EventObject instance.
14
+ * @return Cloned EventObject.
15
+ */
16
+ clone(): EventObject;
17
+ }
@@ -0,0 +1,71 @@
1
+ import { resolveTimeZone } from "../timezones.js";
2
+ import { deepClone, nowUtc } from "../utils.js";
3
+ import { validateJsCalendarObject } from "../validate.js";
4
+ import { applyCommonDefaults, applyEventDefaults } from "./defaults.js";
5
+ import { durationFromSeconds } from "./duration.js";
6
+ import { createUid } from "./ids.js";
7
+ import { toLocalDateTime, toUtcDateTime } from "./datetime.js";
8
+ import { Base } from "./base.js";
9
+ import { isStringValue, isNumberValue } from "../utils.js";
10
+ export class EventObject extends Base {
11
+ /**
12
+ * Create an event with normalized dates, duration, and RFC defaults.
13
+ * @param input Event input values to normalize.
14
+ * @param options Creation options (validation, clock).
15
+ * @return EventObject instance.
16
+ */
17
+ constructor(input, options = {}) {
18
+ if (!isStringValue(input.start) && !(input.start instanceof Date)) {
19
+ throw new Error("Event.start is required");
20
+ }
21
+ if (isStringValue(input.start) && input.start.length === 0) {
22
+ throw new Error("Event.start is required");
23
+ }
24
+ const now = options.now ?? nowUtc;
25
+ const { start: rawStart, duration: rawDuration, updated: rawUpdated, created: rawCreated, timeZone: rawTimeZone, ...rest } = input;
26
+ const start = toLocalDateTime(rawStart);
27
+ const updated = rawUpdated ? toUtcDateTime(rawUpdated) : now();
28
+ let timeZone;
29
+ if (rawTimeZone === null) {
30
+ timeZone = null;
31
+ }
32
+ else if (rawTimeZone) {
33
+ timeZone = resolveTimeZone(rawTimeZone);
34
+ }
35
+ const data = {
36
+ ...rest,
37
+ "@type": "Event",
38
+ start,
39
+ uid: input.uid ?? createUid(),
40
+ updated,
41
+ };
42
+ if (timeZone !== undefined)
43
+ data.timeZone = timeZone;
44
+ if (rawDuration !== undefined) {
45
+ data.duration = isNumberValue(rawDuration)
46
+ ? durationFromSeconds(rawDuration)
47
+ : rawDuration;
48
+ }
49
+ if (rawCreated) {
50
+ data.created = toUtcDateTime(rawCreated);
51
+ }
52
+ else {
53
+ data.created = updated;
54
+ }
55
+ applyCommonDefaults(data);
56
+ applyEventDefaults(data);
57
+ if (options.validate !== false) {
58
+ validateJsCalendarObject(data);
59
+ }
60
+ super(data);
61
+ }
62
+ /**
63
+ * Clone the event as a new EventObject instance.
64
+ * @return Cloned EventObject.
65
+ */
66
+ clone() {
67
+ const cloneData = deepClone(this.data);
68
+ const { "@type": _type, ...rest } = cloneData;
69
+ return new EventObject(rest);
70
+ }
71
+ }
@@ -0,0 +1,25 @@
1
+ import type { Event, Group, Task } from "../types.js";
2
+ import type { CreateOptions, GroupInput } from "./types.js";
3
+ import { Base } from "./base.js";
4
+ export declare class GroupObject extends Base<Group> {
5
+ /**
6
+ * Create a group with normalized entries and RFC defaults.
7
+ * @param input Group input values to normalize.
8
+ * @param options Creation options (validation, clock).
9
+ * @return GroupObject instance.
10
+ */
11
+ constructor(input: GroupInput, options?: CreateOptions);
12
+ /**
13
+ * Append a normalized entry to the group.
14
+ * @param entry Event or task entry to add.
15
+ * @return Updated GroupObject instance.
16
+ */
17
+ addEntry(entry: Event | Task | {
18
+ data: Event | Task;
19
+ }): this;
20
+ /**
21
+ * Clone the group as a new GroupObject instance.
22
+ * @return Cloned GroupObject.
23
+ */
24
+ clone(): GroupObject;
25
+ }
@@ -0,0 +1,62 @@
1
+ import { deepClone, nowUtc } from "../utils.js";
2
+ import { validateJsCalendarObject } from "../validate.js";
3
+ import { applyCommonDefaults } from "./defaults.js";
4
+ import { createUid } from "./ids.js";
5
+ import { Base } from "./base.js";
6
+ import { normalizeEntry } from "./normalize.js";
7
+ import { toUtcDateTime } from "./datetime.js";
8
+ export class GroupObject extends Base {
9
+ /**
10
+ * Create a group with normalized entries and RFC defaults.
11
+ * @param input Group input values to normalize.
12
+ * @param options Creation options (validation, clock).
13
+ * @return GroupObject instance.
14
+ */
15
+ constructor(input, options = {}) {
16
+ if (!Array.isArray(input.entries)) {
17
+ throw new Error("Group.entries is required");
18
+ }
19
+ const now = options.now ?? nowUtc;
20
+ const { updated: rawUpdated, created: rawCreated, ...rest } = input;
21
+ const entries = input.entries.map((entry) => normalizeEntry(entry));
22
+ const updated = rawUpdated ? toUtcDateTime(rawUpdated) : now();
23
+ const data = {
24
+ ...rest,
25
+ "@type": "Group",
26
+ entries,
27
+ uid: input.uid ?? createUid(),
28
+ updated,
29
+ };
30
+ if (rawCreated) {
31
+ data.created = toUtcDateTime(rawCreated);
32
+ }
33
+ else {
34
+ data.created = updated;
35
+ }
36
+ applyCommonDefaults(data);
37
+ if (options.validate !== false) {
38
+ validateJsCalendarObject(data);
39
+ }
40
+ super(data);
41
+ }
42
+ /**
43
+ * Append a normalized entry to the group.
44
+ * @param entry Event or task entry to add.
45
+ * @return Updated GroupObject instance.
46
+ */
47
+ addEntry(entry) {
48
+ const entries = [...this.data.entries, normalizeEntry(entry)];
49
+ this.data.entries = entries;
50
+ this.touchKeys(["entries"]);
51
+ return this;
52
+ }
53
+ /**
54
+ * Clone the group as a new GroupObject instance.
55
+ * @return Cloned GroupObject.
56
+ */
57
+ clone() {
58
+ const cloneData = deepClone(this.data);
59
+ const { "@type": _type, ...rest } = cloneData;
60
+ return new GroupObject(rest);
61
+ }
62
+ }
@@ -0,0 +1,19 @@
1
+ import type { Event, Group, JSCalendarObject, Task } from "../types.js";
2
+ /**
3
+ * Type guard for JSCalendar Event objects.
4
+ * @param obj JSCalendar object to check.
5
+ * @return True when the object is an Event.
6
+ */
7
+ export declare function isEvent(obj: JSCalendarObject): obj is Event;
8
+ /**
9
+ * Type guard for JSCalendar Task objects.
10
+ * @param obj JSCalendar object to check.
11
+ * @return True when the object is a Task.
12
+ */
13
+ export declare function isTask(obj: JSCalendarObject): obj is Task;
14
+ /**
15
+ * Type guard for JSCalendar Group objects.
16
+ * @param obj JSCalendar object to check.
17
+ * @return True when the object is a Group.
18
+ */
19
+ export declare function isGroup(obj: JSCalendarObject): obj is Group;
@@ -0,0 +1,25 @@
1
+ import { TYPE_EVENT, TYPE_GROUP, TYPE_TASK } from "./constants.js";
2
+ /**
3
+ * Type guard for JSCalendar Event objects.
4
+ * @param obj JSCalendar object to check.
5
+ * @return True when the object is an Event.
6
+ */
7
+ export function isEvent(obj) {
8
+ return obj["@type"] === TYPE_EVENT;
9
+ }
10
+ /**
11
+ * Type guard for JSCalendar Task objects.
12
+ * @param obj JSCalendar object to check.
13
+ * @return True when the object is a Task.
14
+ */
15
+ export function isTask(obj) {
16
+ return obj["@type"] === TYPE_TASK;
17
+ }
18
+ /**
19
+ * Type guard for JSCalendar Group objects.
20
+ * @param obj JSCalendar object to check.
21
+ * @return True when the object is a Group.
22
+ */
23
+ export function isGroup(obj) {
24
+ return obj["@type"] === TYPE_GROUP;
25
+ }
@@ -0,0 +1,11 @@
1
+ import type { Id } from "../types.js";
2
+ /**
3
+ * Create a UUID-like identifier.
4
+ * @return UUID string.
5
+ */
6
+ export declare function createUid(): string;
7
+ /**
8
+ * Create a compact base64url identifier.
9
+ * @return Base64url ID string.
10
+ */
11
+ export declare function createId(): Id;
@@ -0,0 +1,77 @@
1
+ import { TYPEOF_FUNCTION } from "./constants.js";
2
+ /**
3
+ * Get a Crypto instance if available.
4
+ * @return Crypto instance when available, otherwise undefined.
5
+ */
6
+ function getCrypto() {
7
+ const cryptoObj = globalThis.crypto;
8
+ if (cryptoObj && typeof cryptoObj.getRandomValues === TYPEOF_FUNCTION) {
9
+ return cryptoObj;
10
+ }
11
+ return undefined;
12
+ }
13
+ /**
14
+ * Generate random bytes using Crypto or Math.random.
15
+ * @param length Number of bytes to generate.
16
+ * @return Random byte array.
17
+ */
18
+ function getRandomBytes(length) {
19
+ const bytes = new Uint8Array(length);
20
+ const cryptoObj = getCrypto();
21
+ if (cryptoObj) {
22
+ cryptoObj.getRandomValues(bytes);
23
+ return bytes;
24
+ }
25
+ for (let i = 0; i < length; i += 1) {
26
+ bytes[i] = Math.floor(Math.random() * 256);
27
+ }
28
+ return bytes;
29
+ }
30
+ /**
31
+ * Encode bytes as base64url.
32
+ * @param bytes Byte array to encode.
33
+ * @return Base64url string without padding.
34
+ */
35
+ function base64UrlEncode(bytes) {
36
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
37
+ let output = "";
38
+ for (let i = 0; i < bytes.length; i += 3) {
39
+ const b0 = bytes[i] ?? 0;
40
+ const b1 = bytes[i + 1] ?? 0;
41
+ const b2 = bytes[i + 2] ?? 0;
42
+ const triplet = ((b0 ?? 0) << 16) | ((b1 ?? 0) << 8) | (b2 ?? 0);
43
+ output += alphabet[(triplet >> 18) & 0x3f] ?? "";
44
+ output += alphabet[(triplet >> 12) & 0x3f] ?? "";
45
+ output += i + 1 < bytes.length ? alphabet[(triplet >> 6) & 0x3f] ?? "" : "=";
46
+ output += i + 2 < bytes.length ? alphabet[triplet & 0x3f] ?? "" : "=";
47
+ }
48
+ return output.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
49
+ }
50
+ /**
51
+ * Create a UUID-like identifier.
52
+ * @return UUID string.
53
+ */
54
+ export function createUid() {
55
+ const cryptoObj = getCrypto();
56
+ if (cryptoObj?.randomUUID) {
57
+ return cryptoObj.randomUUID();
58
+ }
59
+ const bytes = getRandomBytes(16);
60
+ if (bytes.length > 6) {
61
+ const b6 = bytes[6] ?? 0;
62
+ bytes[6] = (b6 & 0x0f) | 0x40;
63
+ }
64
+ if (bytes.length > 8) {
65
+ const b8 = bytes[8] ?? 0;
66
+ bytes[8] = (b8 & 0x3f) | 0x80;
67
+ }
68
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
69
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
70
+ }
71
+ /**
72
+ * Create a compact base64url identifier.
73
+ * @return Base64url ID string.
74
+ */
75
+ export function createId() {
76
+ return base64UrlEncode(getRandomBytes(16));
77
+ }
@@ -0,0 +1,32 @@
1
+ import type { Event, JSCalendarObject, Task } from "../types.js";
2
+ import type { EntryInput } from "./types.js";
3
+ /**
4
+ * Type guard for wrapper instances that expose a data field.
5
+ * @param value Value to check.
6
+ * @return True when value looks like a JsCal instance.
7
+ */
8
+ export declare function isJsCalInstance(value: object): value is {
9
+ data: JSCalendarObject;
10
+ };
11
+ /**
12
+ * Normalize a list of mixed JSCalendar objects and JsCal instances.
13
+ * @param items Items to normalize.
14
+ * @return Plain JSCalendar objects.
15
+ */
16
+ export declare function normalizeItems(items: Array<JSCalendarObject | {
17
+ data: JSCalendarObject;
18
+ }>): JSCalendarObject[];
19
+ /**
20
+ * Normalize a group entry into a plain Event or Task.
21
+ * @param entry Group entry input.
22
+ * @return Plain Event or Task object.
23
+ */
24
+ export declare function normalizeEntry(entry: EntryInput): Event | Task;
25
+ /**
26
+ * Normalize a list to plain JSCalendar objects.
27
+ * @param value List of JSCalendar objects or JsCal instances.
28
+ * @return Plain JSCalendar objects.
29
+ */
30
+ export declare function normalizeToObjects(value: Array<JSCalendarObject | {
31
+ data: JSCalendarObject;
32
+ }>): JSCalendarObject[];
@@ -0,0 +1,45 @@
1
+ import { isObjectValue } from "../utils.js";
2
+ /**
3
+ * Type guard for wrapper instances that expose a data field.
4
+ * @param value Value to check.
5
+ * @return True when value looks like a JsCal instance.
6
+ */
7
+ export function isJsCalInstance(value) {
8
+ return "data" in value;
9
+ }
10
+ /**
11
+ * Normalize a list of mixed JSCalendar objects and JsCal instances.
12
+ * @param items Items to normalize.
13
+ * @return Plain JSCalendar objects.
14
+ */
15
+ export function normalizeItems(items) {
16
+ const mapped = [];
17
+ for (const entry of items) {
18
+ if (isObjectValue(entry) && isJsCalInstance(entry)) {
19
+ mapped.push(entry.data);
20
+ }
21
+ else {
22
+ mapped.push(entry);
23
+ }
24
+ }
25
+ return mapped;
26
+ }
27
+ /**
28
+ * Normalize a group entry into a plain Event or Task.
29
+ * @param entry Group entry input.
30
+ * @return Plain Event or Task object.
31
+ */
32
+ export function normalizeEntry(entry) {
33
+ if (isObjectValue(entry) && isJsCalInstance(entry)) {
34
+ return entry.data;
35
+ }
36
+ return entry;
37
+ }
38
+ /**
39
+ * Normalize a list to plain JSCalendar objects.
40
+ * @param value List of JSCalendar objects or JsCal instances.
41
+ * @return Plain JSCalendar objects.
42
+ */
43
+ export function normalizeToObjects(value) {
44
+ return normalizeItems(value);
45
+ }