@craftguild/jscalendar 0.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.
Files changed (45) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +295 -0
  3. package/dist/__tests__/calendar-extra.test.d.ts +1 -0
  4. package/dist/__tests__/calendar-extra.test.js +185 -0
  5. package/dist/__tests__/calendar.test.d.ts +1 -0
  6. package/dist/__tests__/calendar.test.js +104 -0
  7. package/dist/__tests__/ical-extra.test.d.ts +1 -0
  8. package/dist/__tests__/ical-extra.test.js +87 -0
  9. package/dist/__tests__/ical.test.d.ts +1 -0
  10. package/dist/__tests__/ical.test.js +72 -0
  11. package/dist/__tests__/index.test.d.ts +1 -0
  12. package/dist/__tests__/index.test.js +9 -0
  13. package/dist/__tests__/patch.test.d.ts +1 -0
  14. package/dist/__tests__/patch.test.js +47 -0
  15. package/dist/__tests__/recurrence.test.d.ts +1 -0
  16. package/dist/__tests__/recurrence.test.js +498 -0
  17. package/dist/__tests__/search.test.d.ts +1 -0
  18. package/dist/__tests__/search.test.js +237 -0
  19. package/dist/__tests__/timezones.test.d.ts +1 -0
  20. package/dist/__tests__/timezones.test.js +12 -0
  21. package/dist/__tests__/utils.test.d.ts +1 -0
  22. package/dist/__tests__/utils.test.js +116 -0
  23. package/dist/__tests__/validation.test.d.ts +1 -0
  24. package/dist/__tests__/validation.test.js +91 -0
  25. package/dist/ical.d.ts +7 -0
  26. package/dist/ical.js +202 -0
  27. package/dist/index.d.ts +3 -0
  28. package/dist/index.js +2 -0
  29. package/dist/jscal.d.ts +129 -0
  30. package/dist/jscal.js +504 -0
  31. package/dist/patch.d.ts +5 -0
  32. package/dist/patch.js +91 -0
  33. package/dist/recurrence.d.ts +15 -0
  34. package/dist/recurrence.js +674 -0
  35. package/dist/search.d.ts +14 -0
  36. package/dist/search.js +208 -0
  37. package/dist/timezones.d.ts +4 -0
  38. package/dist/timezones.js +441 -0
  39. package/dist/types.d.ts +219 -0
  40. package/dist/types.js +1 -0
  41. package/dist/utils.d.ts +10 -0
  42. package/dist/utils.js +80 -0
  43. package/dist/validate.d.ts +6 -0
  44. package/dist/validate.js +745 -0
  45. package/package.json +33 -0
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { JsCal } from "./jscal.js";
2
+ export { ValidationError } from "./validate.js";
@@ -0,0 +1,129 @@
1
+ import type { Alert, Event, Group, Id, JSCalendarObject, Location, Participant, PatchObject, Task, TimeZoneInput, UTCDateTime, VirtualLocation } from "./types.js";
2
+ import { applyPatch } from "./patch.js";
3
+ import { resolveTimeZone } from "./timezones.js";
4
+ export type CreateOptions = {
5
+ now?: () => UTCDateTime;
6
+ validate?: boolean;
7
+ };
8
+ export type UpdateOptions = {
9
+ touch?: boolean;
10
+ sequence?: boolean;
11
+ now?: () => UTCDateTime;
12
+ validate?: boolean;
13
+ };
14
+ type DateInput = string | Date;
15
+ type DurationInput = string | number;
16
+ type EventInput = Omit<Event, "@type" | "uid" | "updated" | "created" | "start" | "duration" | "timeZone"> & {
17
+ start: DateInput;
18
+ duration?: DurationInput;
19
+ timeZone?: TimeZoneInput | null;
20
+ uid?: string;
21
+ updated?: DateInput;
22
+ created?: DateInput;
23
+ };
24
+ type TaskInput = Omit<Task, "@type" | "uid" | "updated" | "created" | "start" | "due" | "timeZone"> & {
25
+ uid?: string;
26
+ updated?: DateInput;
27
+ created?: DateInput;
28
+ start?: DateInput;
29
+ due?: DateInput;
30
+ timeZone?: TimeZoneInput | null;
31
+ };
32
+ type GroupInput = Omit<Group, "@type" | "uid" | "updated" | "created"> & {
33
+ uid?: string;
34
+ updated?: DateInput;
35
+ created?: DateInput;
36
+ };
37
+ export declare function createUid(): string;
38
+ export declare function createId(): Id;
39
+ export declare function isEvent(obj: JSCalendarObject): obj is Event;
40
+ export declare function isTask(obj: JSCalendarObject): obj is Task;
41
+ export declare function isGroup(obj: JSCalendarObject): obj is Group;
42
+ declare class Base<T extends JSCalendarObject> {
43
+ data: T;
44
+ constructor(data: T);
45
+ eject(): T;
46
+ clone(): Base<T>;
47
+ get<K extends keyof T>(key: K): T[K];
48
+ set<K extends keyof T>(key: K, value: T[K]): this;
49
+ update(values: Partial<T>, options?: UpdateOptions): this;
50
+ patch(patch: PatchObject, options?: UpdateOptions): this;
51
+ addLocation(location: Omit<Location, "@type"> & Partial<Pick<Location, "@type">>, id?: Id): Id;
52
+ addVirtualLocation(location: Omit<VirtualLocation, "@type"> & Partial<Pick<VirtualLocation, "@type">>, id?: Id): Id;
53
+ addParticipant(participant: Omit<Participant, "@type"> & Partial<Pick<Participant, "@type">>, id?: Id): Id;
54
+ addAlert(alert: Omit<Alert, "@type"> & Partial<Pick<Alert, "@type">>, id?: Id): Id;
55
+ protected touchKeys(keys: string[], options?: UpdateOptions): this;
56
+ protected touchFromPatch(patch: PatchObject, options?: UpdateOptions): this;
57
+ }
58
+ declare class EventObject extends Base<Event> {
59
+ constructor(input: EventInput, options?: CreateOptions);
60
+ clone(): EventObject;
61
+ }
62
+ declare class TaskObject extends Base<Task> {
63
+ constructor(input?: TaskInput, options?: CreateOptions);
64
+ clone(): TaskObject;
65
+ }
66
+ declare class GroupObject extends Base<Group> {
67
+ constructor(input: GroupInput, options?: CreateOptions);
68
+ addEntry(entry: Event | Task): this;
69
+ clone(): GroupObject;
70
+ }
71
+ export declare const JsCal: {
72
+ Event: typeof EventObject;
73
+ Task: typeof TaskObject;
74
+ Group: typeof GroupObject;
75
+ createUid: typeof createUid;
76
+ createId: typeof createId;
77
+ duration: {
78
+ seconds(value: number): string;
79
+ minutes(value: number): string;
80
+ hours(value: number): string;
81
+ days(value: number): string;
82
+ from(parts: {
83
+ days?: number;
84
+ hours?: number;
85
+ minutes?: number;
86
+ seconds?: number;
87
+ }): string;
88
+ };
89
+ timeZone: typeof resolveTimeZone;
90
+ timeZones: import("./timezones.js").TimeZoneId[];
91
+ applyPatch: typeof applyPatch;
92
+ findByUid(items: Array<JSCalendarObject | {
93
+ data: JSCalendarObject;
94
+ }>, uid: string): JSCalendarObject | undefined;
95
+ filterByType(items: Array<JSCalendarObject | {
96
+ data: JSCalendarObject;
97
+ }>, type: JSCalendarObject["@type"]): JSCalendarObject[];
98
+ groupByType(items: Array<JSCalendarObject | {
99
+ data: JSCalendarObject;
100
+ }>): Record<string, JSCalendarObject[]>;
101
+ filterByText(items: Array<JSCalendarObject | {
102
+ data: JSCalendarObject;
103
+ }>, query: string): JSCalendarObject[];
104
+ filterByDateRange(items: Array<JSCalendarObject | {
105
+ data: JSCalendarObject;
106
+ }>, range: import("./search.js").DateRange, options?: import("./search.js").DateRangeOptions): JSCalendarObject[];
107
+ expandRecurrence(items: Array<JSCalendarObject | {
108
+ data: JSCalendarObject;
109
+ }>, range: {
110
+ from: Date;
111
+ to: Date;
112
+ }): Generator<JSCalendarObject>;
113
+ expandRecurrencePaged(items: Array<JSCalendarObject | {
114
+ data: JSCalendarObject;
115
+ }>, range: {
116
+ from: Date;
117
+ to: Date;
118
+ }, options: {
119
+ limit: number;
120
+ cursor?: string;
121
+ }): {
122
+ items: JSCalendarObject[];
123
+ nextCursor?: string;
124
+ };
125
+ toICal(value: Array<JSCalendarObject | {
126
+ data: JSCalendarObject;
127
+ }>, options?: import("./ical.js").ICalOptions): string;
128
+ };
129
+ export {};
package/dist/jscal.js ADDED
@@ -0,0 +1,504 @@
1
+ import { applyPatch } from "./patch.js";
2
+ import { deepClone, normalizeUtcDateTime, nowUtc } from "./utils.js";
3
+ import { toICal } from "./ical.js";
4
+ import { expandRecurrence, expandRecurrencePaged } from "./recurrence.js";
5
+ import { validateJsCalendarObject } from "./validate.js";
6
+ import { filterByDateRange, filterByText, filterByType, findByUid, groupByType, } from "./search.js";
7
+ import { resolveTimeZone, TimeZones } from "./timezones.js";
8
+ function getCrypto() {
9
+ const cryptoObj = globalThis.crypto;
10
+ if (cryptoObj && typeof cryptoObj.getRandomValues === "function") {
11
+ return cryptoObj;
12
+ }
13
+ return undefined;
14
+ }
15
+ function getRandomBytes(length) {
16
+ const bytes = new Uint8Array(length);
17
+ const cryptoObj = getCrypto();
18
+ if (cryptoObj) {
19
+ cryptoObj.getRandomValues(bytes);
20
+ return bytes;
21
+ }
22
+ for (let i = 0; i < length; i += 1) {
23
+ bytes[i] = Math.floor(Math.random() * 256);
24
+ }
25
+ return bytes;
26
+ }
27
+ function base64UrlEncode(bytes) {
28
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
29
+ let output = "";
30
+ for (let i = 0; i < bytes.length; i += 3) {
31
+ const b0 = bytes[i] ?? 0;
32
+ const b1 = bytes[i + 1] ?? 0;
33
+ const b2 = bytes[i + 2] ?? 0;
34
+ const triplet = ((b0 ?? 0) << 16) | ((b1 ?? 0) << 8) | (b2 ?? 0);
35
+ output += alphabet[(triplet >> 18) & 0x3f] ?? "";
36
+ output += alphabet[(triplet >> 12) & 0x3f] ?? "";
37
+ output += i + 1 < bytes.length ? alphabet[(triplet >> 6) & 0x3f] ?? "" : "=";
38
+ output += i + 2 < bytes.length ? alphabet[triplet & 0x3f] ?? "" : "=";
39
+ }
40
+ return output.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
41
+ }
42
+ export function createUid() {
43
+ const cryptoObj = getCrypto();
44
+ if (cryptoObj?.randomUUID) {
45
+ return cryptoObj.randomUUID();
46
+ }
47
+ const bytes = getRandomBytes(16);
48
+ if (bytes.length > 6) {
49
+ const b6 = bytes[6] ?? 0;
50
+ bytes[6] = (b6 & 0x0f) | 0x40;
51
+ }
52
+ if (bytes.length > 8) {
53
+ const b8 = bytes[8] ?? 0;
54
+ bytes[8] = (b8 & 0x3f) | 0x80;
55
+ }
56
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
57
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
58
+ }
59
+ export function createId() {
60
+ return base64UrlEncode(getRandomBytes(16));
61
+ }
62
+ function pad2(value) {
63
+ return value.toString().padStart(2, "0");
64
+ }
65
+ function toLocalDateTime(value) {
66
+ if (typeof value === "string")
67
+ return value;
68
+ return [
69
+ value.getFullYear().toString().padStart(4, "0"),
70
+ "-",
71
+ pad2(value.getMonth() + 1),
72
+ "-",
73
+ pad2(value.getDate()),
74
+ "T",
75
+ pad2(value.getHours()),
76
+ ":",
77
+ pad2(value.getMinutes()),
78
+ ":",
79
+ pad2(value.getSeconds()),
80
+ ].join("");
81
+ }
82
+ function toUtcDateTime(value) {
83
+ if (typeof value === "string")
84
+ return value;
85
+ return normalizeUtcDateTime(value.toISOString());
86
+ }
87
+ function durationFromSeconds(totalSeconds) {
88
+ const clamped = Math.max(0, Math.floor(totalSeconds));
89
+ const days = Math.floor(clamped / 86400);
90
+ let remaining = clamped % 86400;
91
+ const hours = Math.floor(remaining / 3600);
92
+ remaining %= 3600;
93
+ const minutes = Math.floor(remaining / 60);
94
+ const seconds = remaining % 60;
95
+ const datePart = days > 0 ? `${days}D` : "";
96
+ const timeParts = [];
97
+ if (hours > 0)
98
+ timeParts.push(`${hours}H`);
99
+ if (minutes > 0)
100
+ timeParts.push(`${minutes}M`);
101
+ if (seconds > 0 || (datePart === "" && timeParts.length === 0)) {
102
+ timeParts.push(`${seconds}S`);
103
+ }
104
+ const timePart = timeParts.length > 0 ? `T${timeParts.join("")}` : "";
105
+ return `P${datePart}${timePart}`;
106
+ }
107
+ const Duration = {
108
+ seconds(value) {
109
+ return durationFromSeconds(value);
110
+ },
111
+ minutes(value) {
112
+ return durationFromSeconds(value * 60);
113
+ },
114
+ hours(value) {
115
+ return durationFromSeconds(value * 3600);
116
+ },
117
+ days(value) {
118
+ return durationFromSeconds(value * 86400);
119
+ },
120
+ from(parts) {
121
+ const seconds = (parts.days ?? 0) * 86400 +
122
+ (parts.hours ?? 0) * 3600 +
123
+ (parts.minutes ?? 0) * 60 +
124
+ (parts.seconds ?? 0);
125
+ return durationFromSeconds(seconds);
126
+ },
127
+ };
128
+ function applyCommonDefaults(data) {
129
+ if (data.sequence === undefined)
130
+ data.sequence = 0;
131
+ if (data.title === undefined)
132
+ data.title = "";
133
+ if (data.description === undefined)
134
+ data.description = "";
135
+ if (data.descriptionContentType === undefined)
136
+ data.descriptionContentType = "text/plain";
137
+ if (data.showWithoutTime === undefined)
138
+ data.showWithoutTime = false;
139
+ if (data.recurrenceIdTimeZone === undefined)
140
+ data.recurrenceIdTimeZone = null;
141
+ if (data.excluded === undefined)
142
+ data.excluded = false;
143
+ if (data.priority === undefined)
144
+ data.priority = 0;
145
+ if (data.freeBusyStatus === undefined)
146
+ data.freeBusyStatus = "busy";
147
+ if (data.privacy === undefined)
148
+ data.privacy = "public";
149
+ if (data.useDefaultAlerts === undefined)
150
+ data.useDefaultAlerts = false;
151
+ return data;
152
+ }
153
+ function applyEventDefaults(data) {
154
+ if (data.duration === undefined)
155
+ data.duration = "PT0S";
156
+ if (data.status === undefined)
157
+ data.status = "confirmed";
158
+ return data;
159
+ }
160
+ function applyTaskDefaults(data) {
161
+ if (data.progress === undefined) {
162
+ const participants = data.participants ? Object.values(data.participants) : [];
163
+ if (participants.length === 0) {
164
+ data.progress = "needs-action";
165
+ }
166
+ else if (participants.every((p) => p.progress === "completed")) {
167
+ data.progress = "completed";
168
+ }
169
+ else if (participants.some((p) => p.progress === "failed")) {
170
+ data.progress = "failed";
171
+ }
172
+ else if (participants.some((p) => p.progress === "in-process")) {
173
+ data.progress = "in-process";
174
+ }
175
+ else {
176
+ data.progress = "needs-action";
177
+ }
178
+ }
179
+ return data;
180
+ }
181
+ function applyParticipantDefaults(participant) {
182
+ if (participant.participationStatus === undefined)
183
+ participant.participationStatus = "needs-action";
184
+ if (participant.expectReply === undefined)
185
+ participant.expectReply = false;
186
+ if (participant.scheduleAgent === undefined)
187
+ participant.scheduleAgent = "server";
188
+ if (participant.scheduleForceSend === undefined)
189
+ participant.scheduleForceSend = false;
190
+ if (participant.scheduleSequence === undefined)
191
+ participant.scheduleSequence = 0;
192
+ return participant;
193
+ }
194
+ function applyAlertDefaults(alert) {
195
+ if (alert.action === undefined)
196
+ alert.action = "display";
197
+ if (alert.trigger["@type"] === "OffsetTrigger") {
198
+ if (alert.trigger.relativeTo === undefined) {
199
+ alert.trigger.relativeTo = "start";
200
+ }
201
+ }
202
+ return alert;
203
+ }
204
+ export function isEvent(obj) {
205
+ return obj["@type"] === "Event";
206
+ }
207
+ export function isTask(obj) {
208
+ return obj["@type"] === "Task";
209
+ }
210
+ export function isGroup(obj) {
211
+ return obj["@type"] === "Group";
212
+ }
213
+ class Base {
214
+ data;
215
+ constructor(data) {
216
+ this.data = data;
217
+ }
218
+ eject() {
219
+ return deepClone(this.data);
220
+ }
221
+ clone() {
222
+ return new Base(deepClone(this.data));
223
+ }
224
+ get(key) {
225
+ return this.data[key];
226
+ }
227
+ set(key, value) {
228
+ this.data[key] = value;
229
+ return this.touchKeys([String(key)]);
230
+ }
231
+ update(values, options = {}) {
232
+ const next = { ...this.data, ...values };
233
+ if (options.validate !== false) {
234
+ validateJsCalendarObject(next);
235
+ }
236
+ this.data = next;
237
+ return this.touchKeys(Object.keys(values), options);
238
+ }
239
+ patch(patch, options = {}) {
240
+ const next = applyPatch(this.data, patch);
241
+ if (options.validate !== false) {
242
+ validateJsCalendarObject(next);
243
+ }
244
+ this.data = next;
245
+ return this.touchFromPatch(patch, options);
246
+ }
247
+ addLocation(location, id) {
248
+ const actualId = id ?? createId();
249
+ if (!this.data.locations)
250
+ this.data.locations = {};
251
+ this.data.locations[actualId] = { ...location, "@type": "Location" };
252
+ this.touchKeys(["locations"]);
253
+ return actualId;
254
+ }
255
+ addVirtualLocation(location, id) {
256
+ const actualId = id ?? createId();
257
+ if (!this.data.virtualLocations)
258
+ this.data.virtualLocations = {};
259
+ const name = location.name ?? "";
260
+ this.data.virtualLocations[actualId] = { ...location, name, "@type": "VirtualLocation" };
261
+ this.touchKeys(["virtualLocations"]);
262
+ return actualId;
263
+ }
264
+ addParticipant(participant, id) {
265
+ const actualId = id ?? createId();
266
+ if (!this.data.participants)
267
+ this.data.participants = {};
268
+ const filled = applyParticipantDefaults({ ...participant, "@type": "Participant" });
269
+ this.data.participants[actualId] = filled;
270
+ this.touchKeys(["participants"], { sequence: false });
271
+ return actualId;
272
+ }
273
+ addAlert(alert, id) {
274
+ const actualId = id ?? createId();
275
+ if (!this.data.alerts)
276
+ this.data.alerts = {};
277
+ const filled = applyAlertDefaults({ ...alert, "@type": "Alert" });
278
+ this.data.alerts[actualId] = filled;
279
+ this.touchKeys(["alerts"]);
280
+ return actualId;
281
+ }
282
+ touchKeys(keys, options = {}) {
283
+ if (options.touch === false)
284
+ return this;
285
+ const now = options.now ?? nowUtc;
286
+ this.data.updated = now();
287
+ if (options.sequence === false)
288
+ return this;
289
+ const onlyParticipants = keys.length > 0 && keys.every((key) => key === "participants");
290
+ if (!onlyParticipants) {
291
+ const current = typeof this.data.sequence === "number" ? this.data.sequence : 0;
292
+ this.data.sequence = current + 1;
293
+ }
294
+ return this;
295
+ }
296
+ touchFromPatch(patch, options = {}) {
297
+ if (options.touch === false)
298
+ return this;
299
+ const now = options.now ?? nowUtc;
300
+ this.data.updated = now();
301
+ if (options.sequence === false)
302
+ return this;
303
+ const pointers = Object.keys(patch);
304
+ const onlyParticipants = pointers.length > 0 &&
305
+ pointers.every((pointer) => {
306
+ const normalized = pointer.startsWith("/") ? pointer.slice(1) : pointer;
307
+ return normalized.startsWith("participants");
308
+ });
309
+ if (!onlyParticipants) {
310
+ const current = typeof this.data.sequence === "number" ? this.data.sequence : 0;
311
+ this.data.sequence = current + 1;
312
+ }
313
+ return this;
314
+ }
315
+ }
316
+ class EventObject extends Base {
317
+ constructor(input, options = {}) {
318
+ if (typeof input.start !== "string" && !(input.start instanceof Date)) {
319
+ throw new Error("Event.start is required");
320
+ }
321
+ if (typeof input.start === "string" && input.start.length === 0) {
322
+ throw new Error("Event.start is required");
323
+ }
324
+ const now = options.now ?? nowUtc;
325
+ const { start: rawStart, duration: rawDuration, updated: rawUpdated, created: rawCreated, timeZone: rawTimeZone, ...rest } = input;
326
+ const start = toLocalDateTime(rawStart);
327
+ const updated = rawUpdated ? toUtcDateTime(rawUpdated) : now();
328
+ let timeZone;
329
+ if (rawTimeZone === null) {
330
+ timeZone = null;
331
+ }
332
+ else if (rawTimeZone) {
333
+ timeZone = resolveTimeZone(rawTimeZone);
334
+ }
335
+ const data = {
336
+ ...rest,
337
+ "@type": "Event",
338
+ start,
339
+ uid: input.uid ?? createUid(),
340
+ updated,
341
+ };
342
+ if (timeZone !== undefined)
343
+ data.timeZone = timeZone;
344
+ if (rawDuration !== undefined) {
345
+ data.duration = typeof rawDuration === "number"
346
+ ? durationFromSeconds(rawDuration)
347
+ : rawDuration;
348
+ }
349
+ if (rawCreated) {
350
+ data.created = toUtcDateTime(rawCreated);
351
+ }
352
+ else {
353
+ data.created = updated;
354
+ }
355
+ applyCommonDefaults(data);
356
+ applyEventDefaults(data);
357
+ if (options.validate !== false) {
358
+ validateJsCalendarObject(data);
359
+ }
360
+ super(data);
361
+ }
362
+ clone() {
363
+ const cloneData = deepClone(this.data);
364
+ const { "@type": _type, ...rest } = cloneData;
365
+ return new EventObject(rest);
366
+ }
367
+ }
368
+ class TaskObject extends Base {
369
+ constructor(input = {}, options = {}) {
370
+ const now = options.now ?? nowUtc;
371
+ const { start: rawStart, due: rawDue, updated: rawUpdated, created: rawCreated, timeZone: rawTimeZone, ...rest } = input;
372
+ const updated = rawUpdated ? toUtcDateTime(rawUpdated) : now();
373
+ let timeZone;
374
+ if (rawTimeZone === null) {
375
+ timeZone = null;
376
+ }
377
+ else if (rawTimeZone) {
378
+ timeZone = resolveTimeZone(rawTimeZone);
379
+ }
380
+ const data = {
381
+ ...rest,
382
+ "@type": "Task",
383
+ uid: input.uid ?? createUid(),
384
+ updated,
385
+ };
386
+ if (rawStart)
387
+ data.start = toLocalDateTime(rawStart);
388
+ if (rawDue)
389
+ data.due = toLocalDateTime(rawDue);
390
+ if (timeZone !== undefined)
391
+ data.timeZone = timeZone;
392
+ if (rawCreated) {
393
+ data.created = toUtcDateTime(rawCreated);
394
+ }
395
+ else {
396
+ data.created = updated;
397
+ }
398
+ applyCommonDefaults(data);
399
+ applyTaskDefaults(data);
400
+ if (options.validate !== false) {
401
+ validateJsCalendarObject(data);
402
+ }
403
+ super(data);
404
+ }
405
+ clone() {
406
+ const cloneData = deepClone(this.data);
407
+ const { "@type": _type, ...rest } = cloneData;
408
+ return new TaskObject(rest);
409
+ }
410
+ }
411
+ class GroupObject extends Base {
412
+ constructor(input, options = {}) {
413
+ if (!Array.isArray(input.entries)) {
414
+ throw new Error("Group.entries is required");
415
+ }
416
+ const now = options.now ?? nowUtc;
417
+ const { updated: rawUpdated, created: rawCreated, ...rest } = input;
418
+ const entries = input.entries;
419
+ const updated = rawUpdated ? toUtcDateTime(rawUpdated) : now();
420
+ const data = {
421
+ ...rest,
422
+ "@type": "Group",
423
+ entries,
424
+ uid: input.uid ?? createUid(),
425
+ updated,
426
+ };
427
+ if (rawCreated) {
428
+ data.created = toUtcDateTime(rawCreated);
429
+ }
430
+ else {
431
+ data.created = updated;
432
+ }
433
+ applyCommonDefaults(data);
434
+ if (options.validate !== false) {
435
+ validateJsCalendarObject(data);
436
+ }
437
+ super(data);
438
+ }
439
+ addEntry(entry) {
440
+ const entries = [...this.data.entries, entry];
441
+ this.data.entries = entries;
442
+ this.touchKeys(["entries"]);
443
+ return this;
444
+ }
445
+ clone() {
446
+ const cloneData = deepClone(this.data);
447
+ const { "@type": _type, ...rest } = cloneData;
448
+ return new GroupObject(rest);
449
+ }
450
+ }
451
+ export const JsCal = {
452
+ Event: EventObject,
453
+ Task: TaskObject,
454
+ Group: GroupObject,
455
+ createUid,
456
+ createId,
457
+ duration: Duration,
458
+ timeZone: resolveTimeZone,
459
+ timeZones: TimeZones,
460
+ applyPatch,
461
+ findByUid(items, uid) {
462
+ return findByUid(normalizeItems(items), uid);
463
+ },
464
+ filterByType(items, type) {
465
+ return filterByType(normalizeItems(items), type);
466
+ },
467
+ groupByType(items) {
468
+ return groupByType(normalizeItems(items));
469
+ },
470
+ filterByText(items, query) {
471
+ return filterByText(normalizeItems(items), query);
472
+ },
473
+ filterByDateRange(items, range, options) {
474
+ return filterByDateRange(normalizeItems(items), range, options);
475
+ },
476
+ expandRecurrence(items, range) {
477
+ return expandRecurrence(normalizeItems(items), range);
478
+ },
479
+ expandRecurrencePaged(items, range, options) {
480
+ return expandRecurrencePaged(normalizeItems(items), range, options);
481
+ },
482
+ toICal(value, options) {
483
+ const objects = normalizeToObjects(value);
484
+ return toICal(objects, options);
485
+ },
486
+ };
487
+ function isJsCalInstance(value) {
488
+ return "data" in value;
489
+ }
490
+ function normalizeItems(items) {
491
+ const mapped = [];
492
+ for (const entry of items) {
493
+ if (typeof entry === "object" && entry !== null && isJsCalInstance(entry)) {
494
+ mapped.push(entry.data);
495
+ }
496
+ else {
497
+ mapped.push(entry);
498
+ }
499
+ }
500
+ return mapped;
501
+ }
502
+ function normalizeToObjects(value) {
503
+ return normalizeItems(value);
504
+ }
@@ -0,0 +1,5 @@
1
+ import type { PatchObject } from "./types.js";
2
+ export declare class PatchError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare function applyPatch<T extends object>(input: T, patch: PatchObject): T;