@craftguild/jscalendar 0.4.1 → 0.5.1

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
@@ -22,14 +22,38 @@ pnpm add @craftguild/jscalendar
22
22
  ```ts
23
23
  import { JsCal } from "@craftguild/jscalendar";
24
24
 
25
+ // Create a recurring event and a simple task, then expand occurrences.
25
26
  const event = new JsCal.Event({
26
- title: "Kickoff",
27
- start: "2026-02-03T09:00:00",
28
- timeZone: "America/New_York",
29
- duration: "PT1H",
27
+ title: "Weekly Sync",
28
+ start: new Date(2026, 0, 1, 0, 0, 0, 0),
29
+ recurrenceRules: [
30
+ JsCal.RecurrenceRule({
31
+ frequency: "weekly",
32
+ byDay: [JsCal.ByDay({ day: "th" })],
33
+ }),
34
+ ],
35
+ });
36
+
37
+ const task = new JsCal.Task({
38
+ title: "Prepare Notes",
39
+ start: new Date(2026, 0, 1, 0, 0, 0, 0),
30
40
  });
31
41
 
32
- const items = [event.eject()];
42
+ const from = new Date(2026, 0, 1, 0, 0, 0, 0);
43
+ const to = new Date(2026, 0, 31, 0, 0, 0, 0);
44
+ const generator = JsCal.expandRecurrence([event, task], { from, to });
45
+
46
+ for (const item of generator) {
47
+ // Expanded JSCalendar objects for events and tasks in the range.
48
+ console.log(JSON.stringify(item));
49
+ }
50
+ ```
51
+
52
+ Sample output (truncated):
53
+ ```txt
54
+ {"title":"Weekly Sync","@type":"Event","start":"2026-01-01T00:00:00",...}
55
+ {"title":"Prepare Notes","@type":"Task","start":"2026-01-01T00:00:00",...}
56
+ {"title":"Weekly Sync","@type":"Event","start":"2026-01-08T00:00:00",...}
33
57
  ```
34
58
 
35
59
  ## Object Creation
@@ -93,14 +117,14 @@ const task = new JsCal.Task({
93
117
  title: "Write report",
94
118
  start: "2026-02-11T09:00:00",
95
119
  participants: JsCal.participants([
96
- JsCal.Participant({ name: "Alice", email: "a@example.com", roles: { attendee: true } }),
97
- JsCal.Participant({ name: "Bob", roles: { attendee: true } }),
120
+ { value: JsCal.Participant({ name: "Alice", email: "a@example.com", roles: { attendee: true } }) },
121
+ { value: JsCal.Participant({ name: "Bob", roles: { attendee: true } }) },
98
122
  ]),
99
123
  locations: JsCal.locations([
100
- JsCal.Location({ name: "Room A" }),
124
+ { value: JsCal.Location({ name: "Room A" }) },
101
125
  ]),
102
126
  alerts: JsCal.alerts([
103
- JsCal.Alert({ trigger: JsCal.OffsetTrigger({ offset: JsCal.duration.minutes(-15) }) }),
127
+ { value: JsCal.Alert({ trigger: JsCal.OffsetTrigger({ offset: JsCal.duration.minutes(-15) }) }) },
104
128
  ]),
105
129
  });
106
130
  ```
@@ -112,7 +136,7 @@ const task = new JsCal.Task({
112
136
  title: "Imported task",
113
137
  start: "2026-02-11T09:00:00",
114
138
  participants: {
115
- p1: { "@type": "Participant", name: "Alice", email: "a@example.com" },
139
+ p1: { "@type": "Participant", name: "Alice", email: "a@example.com", roles: { attendee: true } },
116
140
  },
117
141
  locations: {
118
142
  l1: { "@type": "Location", name: "Room A" },
@@ -143,8 +167,8 @@ clone of that underlying JSCalendar object for serialization, storage,
143
167
  or passing across app boundaries.
144
168
 
145
169
  Why `eject()` exists:
146
- - Class instances are convenient for building and mutating objects with
147
- helpers like `update`, `patch`, and `addParticipant`.
170
+ - Class instances are convenient for building and updating objects with
171
+ helpers like `patch`.
148
172
  - External APIs, storage layers, and JSON stringify expect plain objects.
149
173
  - A deep clone makes it safe to hand off data without accidental mutation
150
174
  from the original instance (and vice versa).
@@ -163,24 +187,59 @@ JSON.stringify(plain);
163
187
 
164
188
  // Changes do not affect each other.
165
189
  plain.title = "Exported";
166
- event.update({ title: "Live" });
190
+ const updated = event.patch({ title: "Live" });
191
+ ```
192
+
193
+ ## Patch Usage
194
+
195
+ Patch helpers return new instances and keep metadata such as
196
+ `updated` and `sequence` consistent. Use `patch` for RFC 8984 PatchObject
197
+ semantics. You can set raw values directly, or use helper methods if you
198
+ prefer validated, type-safe inputs.
199
+
200
+ ```ts
201
+ const patchedEvent = event.patch({ title: "Updated title" });
202
+ const patchedAgain = patchedEvent.patch({ title: "Patched title" });
203
+ ```
204
+
205
+ You can also patch nested maps by replacing the full map in one call:
206
+
207
+ ```ts
208
+ const withParticipants = event.patch({
209
+ participants: {
210
+ p1: { "@type": "Participant", roles: { attendee: true }, email: "a@example.com" },
211
+ },
212
+ });
167
213
  ```
168
214
 
169
- ## Updates and Mutations
215
+ Two common patterns for nested patches:
170
216
 
171
- Mutation helpers update the underlying data and keep metadata such as
172
- `updated` and `sequence` consistent. Use `update` for shallow updates
173
- and `patch` for RFC 8984 PatchObject semantics.
217
+ 1) Set raw values directly
218
+ ```ts
219
+ const withLocations = event.patch({
220
+ locations: {
221
+ l1: { "@type": "Location", name: "Room A" },
222
+ },
223
+ });
224
+ ```
174
225
 
226
+ 2) Use helpers to build or merge map values
175
227
  ```ts
176
- event.update({ title: "Updated title" });
177
- event.patch({ title: "Patched title" });
178
-
179
- event.addLocation({ name: "Room A" });
180
- event.addVirtualLocation({ name: "Zoom", uri: "https://example.com" });
181
- event.addParticipant({ roles: { attendee: true }, email: "a@example.com" });
182
- event.addAlert({
183
- trigger: { "@type": "AbsoluteTrigger", when: "2026-02-10T09:00:00Z" },
228
+ const withLocations = event.patch({
229
+ locations: JsCal.locations([
230
+ { id: "l1", value: JsCal.Location({ name: "Room A" }) },
231
+ { value: JsCal.Location({ name: "Room B" }) },
232
+ ]),
233
+ });
234
+ ```
235
+
236
+ To merge into an existing map, pass the current map as the second argument:
237
+ ```ts
238
+ const withLocations = event.patch({
239
+ locations: JsCal.locations(
240
+ [{ value: JsCal.Location({ name: "Room C" }) }],
241
+ event.data.locations,
242
+ ),
184
243
  });
185
244
  ```
186
245
 
@@ -320,26 +379,6 @@ const icalMany = JsCal.toICal([event, task], { includeXJSCalendar: true });
320
379
  console.log(icalMany);
321
380
  ```
322
381
 
323
- ## Practical Example
324
-
325
- **Weekly engineering sync (every Wednesday 10:30, 1 hour)**
326
-
327
- ```ts
328
- const weekly = new JsCal.Event({
329
- title: "Engineering Sync",
330
- start: "2026-02-04T10:30:00",
331
- timeZone: "Asia/Tokyo",
332
- duration: JsCal.duration.hours(1),
333
- recurrenceRules: [
334
- {
335
- "@type": "RecurrenceRule",
336
- frequency: "weekly",
337
- byDay: [{ "@type": "NDay", day: "we" }],
338
- },
339
- ],
340
- });
341
- ```
342
-
343
382
  ## Compliance and Deviations
344
383
 
345
384
  ### RFC 8984 Conformance (Implemented)
@@ -34,17 +34,30 @@ describe("builders", () => {
34
34
  });
35
35
  it("builds id maps with generated ids", () => {
36
36
  const participants = JsCal.participants([
37
- { name: "Alice", roles: { attendee: true } },
38
- { name: "Bob", roles: { attendee: true } },
37
+ { value: { name: "Alice", roles: { attendee: true } } },
38
+ { value: { name: "Bob", roles: { attendee: true } } },
39
39
  ]);
40
40
  expect(Object.keys(participants).length).toBe(2);
41
- const locations = JsCal.locations([{ name: "Room A" }]);
41
+ const locations = JsCal.locations([{ value: { name: "Room A" } }]);
42
42
  expect(Object.keys(locations).length).toBe(1);
43
- const links = JsCal.links([{ href: "https://example.com" }]);
43
+ const links = JsCal.links([{ value: { href: "https://example.com" } }]);
44
44
  expect(Object.keys(links).length).toBe(1);
45
- const related = JsCal.relatedTo([{ relation: { parent: true } }]);
45
+ const related = JsCal.relatedTo([{ value: { relation: { parent: true } } }]);
46
46
  expect(Object.keys(related).length).toBe(1);
47
47
  });
48
+ it("merges id maps with explicit ids", () => {
49
+ const existing = JsCal.locations([{ id: "loc-1", value: { name: "Room A" } }]);
50
+ const merged = JsCal.locations([
51
+ { id: "loc-1", value: { name: "Room A Updated" } },
52
+ { value: { name: "Room B" } },
53
+ ], existing);
54
+ expect(existing["loc-1"]?.name).toBe("Room A");
55
+ expect(merged["loc-1"]?.name).toBe("Room A Updated");
56
+ expect(Object.keys(merged).length).toBe(2);
57
+ });
58
+ it("rejects wrapper inputs that mix value and direct fields", () => {
59
+ expect(() => JsCal.locations([{ name: "Room A" }])).toThrowError();
60
+ });
48
61
  it("builds a time zone map keyed by tzId", () => {
49
62
  const map = buildTimeZoneMap([
50
63
  { tzId: "Asia/Tokyo", standard: [{ "@type": "TimeZoneRule", start: "2026-01-01T00:00:00", offsetFrom: "+09:00", offsetTo: "+09:00" }] },
@@ -52,7 +65,7 @@ describe("builders", () => {
52
65
  expect(map["Asia/Tokyo"]?.tzId).toBe("Asia/Tokyo");
53
66
  });
54
67
  it("buildIdMap uses a custom id function", () => {
55
- const map = buildIdMap([{ name: "A" }], (item) => item, (_item, index) => `id-${index}`);
68
+ const map = buildIdMap([{ value: { name: "A" } }], (item) => item, (_item, index) => `id-${index}`);
56
69
  expect(Object.keys(map)).toEqual(["id-0"]);
57
70
  });
58
71
  it("throws when @type mismatches", () => {
@@ -1,6 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import { JsCal, createId, isEvent, isGroup, isTask } from "../jscal.js";
3
- import { Base } from "../jscal/base.js";
4
3
  const fixedNow = () => "2026-02-01T00:00:00Z";
5
4
  function makeEvent() {
6
5
  return new JsCal.Event({
@@ -12,8 +11,9 @@ describe("JsCal helpers", () => {
12
11
  it("creates group and adds entry", () => {
13
12
  const group = new JsCal.Group({ entries: [] }, { now: fixedNow });
14
13
  const event = makeEvent();
15
- group.addEntry(event.data);
16
- expect(group.data.entries.length).toBe(1);
14
+ const nextGroup = group.addEntry(event.data);
15
+ expect(group.data.entries.length).toBe(0);
16
+ expect(nextGroup.data.entries.length).toBe(1);
17
17
  });
18
18
  it("accepts JsCal instances and ejected entries in groups", () => {
19
19
  const event = makeEvent();
@@ -31,10 +31,12 @@ describe("JsCal helpers", () => {
31
31
  });
32
32
  it("adds locations and virtual locations", () => {
33
33
  const event = makeEvent();
34
- const locId = event.addLocation({ name: "Room A" });
35
- const vlocId = event.addVirtualLocation({ name: "Zoom", uri: "https://example.com" });
36
- const locations = event.data.locations;
37
- const virtualLocations = event.data.virtualLocations;
34
+ const locId = "loc-1";
35
+ const vlocId = "vloc-1";
36
+ const withLocation = event.addLocation({ name: "Room A" }, locId);
37
+ const withVirtualLocation = withLocation.addVirtualLocation({ name: "Zoom", uri: "https://example.com" }, vlocId);
38
+ const locations = withVirtualLocation.data.locations;
39
+ const virtualLocations = withVirtualLocation.data.virtualLocations;
38
40
  expect(locations).toBeDefined();
39
41
  expect(virtualLocations).toBeDefined();
40
42
  if (!locations || !virtualLocations)
@@ -44,10 +46,12 @@ describe("JsCal helpers", () => {
44
46
  });
45
47
  it("adds participants and alerts", () => {
46
48
  const event = makeEvent();
47
- const participantId = event.addParticipant({ roles: { attendee: true }, email: "a@example.com" });
48
- const alertId = event.addAlert({ trigger: { "@type": "AbsoluteTrigger", when: "2026-02-01T01:00:00Z" } });
49
- const participants = event.data.participants;
50
- const alerts = event.data.alerts;
49
+ const participantId = "p1";
50
+ const alertId = "a1";
51
+ const withParticipant = event.addParticipant({ roles: { attendee: true }, email: "a@example.com" }, participantId);
52
+ const withAlert = withParticipant.addAlert({ trigger: { "@type": "AbsoluteTrigger", when: "2026-02-01T01:00:00Z" } }, alertId);
53
+ const participants = withAlert.data.participants;
54
+ const alerts = withAlert.data.alerts;
51
55
  expect(participants).toBeDefined();
52
56
  expect(alerts).toBeDefined();
53
57
  if (!participants || !alerts)
@@ -63,8 +67,9 @@ describe("JsCal helpers", () => {
63
67
  });
64
68
  it("defaults relativeTo for offset triggers", () => {
65
69
  const event = makeEvent();
66
- const alertId = event.addAlert({ trigger: { "@type": "OffsetTrigger", offset: "PT15M" } });
67
- const alerts = event.data.alerts;
70
+ const alertId = "a1";
71
+ const withAlert = event.addAlert({ trigger: { "@type": "OffsetTrigger", offset: "PT15M" } }, alertId);
72
+ const alerts = withAlert.data.alerts;
68
73
  expect(alerts).toBeDefined();
69
74
  if (!alerts)
70
75
  throw new Error("Missing alerts");
@@ -76,10 +81,10 @@ describe("JsCal helpers", () => {
76
81
  it("updates sequence for alerts but not for participants", () => {
77
82
  const event = makeEvent();
78
83
  const initialSequence = event.data.sequence ?? 0;
79
- event.addParticipant({ roles: { attendee: true }, email: "a@example.com" });
80
- expect(event.data.sequence ?? 0).toBe(initialSequence);
81
- event.addAlert({ trigger: { "@type": "AbsoluteTrigger", when: "2026-02-01T01:00:00Z" } });
82
- expect(event.data.sequence ?? 0).toBe(initialSequence + 1);
84
+ const withParticipant = event.addParticipant({ roles: { attendee: true }, email: "a@example.com" }, "p1");
85
+ expect(withParticipant.data.sequence ?? 0).toBe(initialSequence);
86
+ const withAlert = withParticipant.addAlert({ trigger: { "@type": "AbsoluteTrigger", when: "2026-02-01T01:00:00Z" } }, "a1");
87
+ expect(withAlert.data.sequence ?? 0).toBe(initialSequence + 1);
83
88
  });
84
89
  it("clones and eject return deep copies", () => {
85
90
  const event = makeEvent();
@@ -90,12 +95,6 @@ describe("JsCal helpers", () => {
90
95
  json.title = "Changed";
91
96
  expect(event.data.title).toBe("Kickoff");
92
97
  });
93
- it("clones Base instances with deep-copied data", () => {
94
- const base = new Base({ "@type": "Event", uid: "base", updated: "2026-02-01T00:00:00Z", start: "2026-02-01T10:00:00" });
95
- const cloned = base.clone();
96
- cloned.data.uid = "changed";
97
- expect(base.data.uid).toBe("base");
98
- });
99
98
  it("builds participant inputs without @type", () => {
100
99
  const participant = JsCal.Participant({ name: "Alice", roles: { attendee: true } });
101
100
  const task = new JsCal.Task({
@@ -106,8 +105,8 @@ describe("JsCal helpers", () => {
106
105
  });
107
106
  it("builds participant maps with stable ids", () => {
108
107
  const participants = JsCal.participants([
109
- { name: "Alice", roles: { attendee: true } },
110
- { name: "Bob", roles: { attendee: true } },
108
+ { value: { name: "Alice", roles: { attendee: true } } },
109
+ { value: { name: "Bob", roles: { attendee: true } } },
111
110
  ]);
112
111
  const task = new JsCal.Task({
113
112
  start: "2026-02-02T10:00:00",
@@ -207,8 +206,9 @@ describe("JsCal helpers", () => {
207
206
  it("supports get/set helpers", () => {
208
207
  const event = makeEvent();
209
208
  expect(event.get("title")).toBe("Kickoff");
210
- event.set("title", "Updated");
211
- expect(event.get("title")).toBe("Updated");
209
+ const updated = event.set("title", "Updated");
210
+ expect(event.get("title")).toBe("Kickoff");
211
+ expect(updated.get("title")).toBe("Updated");
212
212
  });
213
213
  it("exposes type guards", () => {
214
214
  const event = makeEvent().eject();
@@ -56,36 +56,29 @@ describe("JsCal.Event", () => {
56
56
  expect(negative.data.duration).toBe("PT0S");
57
57
  });
58
58
  });
59
- describe("Event.update", () => {
60
- it("increments sequence on non-participant updates", () => {
61
- const event = makeEvent();
62
- event.update({ title: "Updated" }, { now: fixedNow });
63
- expect(event.data.sequence).toBe(1);
64
- });
65
- it("does not increment sequence when only participants change", () => {
59
+ describe("Event.patch", () => {
60
+ it("increments sequence on non-participant patches", () => {
66
61
  const event = makeEvent();
67
- const initialSequence = event.data.sequence ?? 0;
68
- event.addParticipant({ roles: { attendee: true }, email: "a@example.com" });
69
- expect(event.data.sequence ?? 0).toBe(initialSequence);
62
+ const patched = event.patch({ title: "Updated" }, { now: fixedNow });
63
+ expect(patched.data.sequence).toBe(1);
64
+ expect(event.data.sequence).toBe(0);
70
65
  });
71
66
  it("respects touch=false", () => {
72
67
  const event = makeEvent();
73
68
  const before = event.data.updated;
74
- event.update({ title: "No touch" }, { touch: false, now: () => "2026-02-02T00:00:00Z" });
75
- expect(event.data.updated).toBe(before);
69
+ const patched = event.patch({ title: "No touch" }, { touch: false, now: () => "2026-02-02T00:00:00Z" });
70
+ expect(patched.data.updated).toBe(before);
76
71
  });
77
- });
78
- describe("Event.patch", () => {
79
72
  it("applies patch and updates metadata", () => {
80
73
  const event = makeEvent();
81
- event.patch({ title: "Patched" }, { now: fixedNow });
82
- expect(event.data.title).toBe("Patched");
83
- expect(event.data.updated).toBe("2026-02-01T00:00:00Z");
84
- expect(event.data.sequence).toBe(1);
74
+ const patched = event.patch({ title: "Patched" }, { now: fixedNow });
75
+ expect(patched.data.title).toBe("Patched");
76
+ expect(patched.data.updated).toBe("2026-02-01T00:00:00Z");
77
+ expect(patched.data.sequence).toBe(1);
85
78
  });
86
79
  it("does not increment sequence for participants-only patch", () => {
87
80
  const event = makeEvent();
88
- event.patch({
81
+ const patched = event.patch({
89
82
  participants: {
90
83
  p1: {
91
84
  "@type": "Participant",
@@ -93,7 +86,7 @@ describe("Event.patch", () => {
93
86
  },
94
87
  },
95
88
  }, { now: fixedNow });
96
- expect(event.data.sequence ?? 0).toBe(0);
89
+ expect(patched.data.sequence ?? 0).toBe(0);
97
90
  });
98
91
  });
99
92
  describe("createUid", () => {
@@ -9,6 +9,25 @@ function collect(gen) {
9
9
  return result;
10
10
  }
11
11
  describe("recurrence expansion", () => {
12
+ it("sorts occurrences by recurrenceId across items", () => {
13
+ const first = new JsCal.Event({
14
+ title: "First",
15
+ start: "2026-02-01T09:00:00",
16
+ });
17
+ const second = new JsCal.Event({
18
+ title: "Second",
19
+ start: "2026-02-03T09:00:00",
20
+ });
21
+ const occ = collect(JsCal.expandRecurrence([second, first], {
22
+ from: new Date("2026-02-01"),
23
+ to: new Date("2026-02-10"),
24
+ }));
25
+ const starts = occ.map((o) => o.recurrenceId ?? ("start" in o ? o.start : undefined));
26
+ expect(starts).toEqual([
27
+ "2026-02-01T09:00:00",
28
+ "2026-02-03T09:00:00",
29
+ ]);
30
+ });
12
31
  it("expands weekly byDay", () => {
13
32
  const event = new JsCal.Event({
14
33
  title: "Weekly",
@@ -535,7 +554,7 @@ describe("recurrence expansion", () => {
535
554
  { "@type": "RecurrenceRule", frequency: "daily", count: 2 },
536
555
  ],
537
556
  recurrenceOverrides: {
538
- "2026-02-02T10:00:00": { "/due": "2026-02-05T10:00:00" },
557
+ "2026-02-02T10:00:00": { due: "2026-02-05T10:00:00" },
539
558
  },
540
559
  });
541
560
  const occ = Array.from(JsCal.expandRecurrence([task], {
@@ -22,11 +22,10 @@ describe("validation", () => {
22
22
  descriptionContentType: "text/plain; charset=ascii",
23
23
  })).toThrowError("object.descriptionContentType: charset parameter must be utf-8");
24
24
  });
25
- it("allows validation to be disabled for create, update, and patch", () => {
25
+ it("allows validation to be disabled for create and patch", () => {
26
26
  const event = new JsCal.Event({ start: "2026-02-01T10:00:00Z" }, { validate: false });
27
27
  expect(event.get("start")).toBe("2026-02-01T10:00:00Z");
28
- event.update({ start: "2026-02-01T10:00:00Z" }, { validate: false });
29
- event.patch({ "/start": "2026-02-01T10:00:00Z" }, { validate: false });
28
+ event.patch({ start: "2026-02-01T10:00:00Z" }, { validate: false });
30
29
  });
31
30
  it("throws ValidationError with path and message", () => {
32
31
  expect(() => new JsCal.Event({ start: "2026-02-01T10:00:00Z" })).toThrowError(ValidationError);
@@ -159,10 +158,15 @@ describe("validation", () => {
159
158
  },
160
159
  },
161
160
  localizations: {
162
- en: { title: "Localized", keywords: ["a"] },
161
+ en: { title: "Localized", keywords: { a: true } },
163
162
  },
164
163
  recurrenceOverrides: {
165
- "2026-02-02T10:00:00": { title: null, locations: ["x"] },
164
+ "2026-02-02T10:00:00": {
165
+ title: null,
166
+ locations: {
167
+ loc1: { "@type": "Location", name: "Room A" },
168
+ },
169
+ },
166
170
  },
167
171
  timeZones: {
168
172
  "Asia/Tokyo": {
@@ -1,6 +1,6 @@
1
- import type { Alert, Id, JSCalendarObject, Location, Participant, PatchObject, VirtualLocation } from "../types.js";
1
+ import type { Alert, Id, JSCalendarObject, Location, Participant, PatchLike, VirtualLocation } from "../types.js";
2
2
  import type { UpdateOptions } from "./types.js";
3
- export declare class Base<T extends JSCalendarObject> {
3
+ export declare abstract class Base<T extends JSCalendarObject, TPatch extends PatchLike, TSelf extends Base<T, TPatch, TSelf>> {
4
4
  data: T;
5
5
  /**
6
6
  * Create a new base instance that wraps a JSCalendar object.
@@ -8,6 +8,12 @@ export declare class Base<T extends JSCalendarObject> {
8
8
  * @return Result.
9
9
  */
10
10
  constructor(data: T);
11
+ /**
12
+ * Wrap updated data in a new instance.
13
+ * @param data Updated JSCalendar data.
14
+ * @return New instance containing the data.
15
+ */
16
+ protected abstract wrap(data: T): TSelf;
11
17
  /**
12
18
  * Return a deep-cloned plain object for safe serialization.
13
19
  * @return Cloned JSCalendar data.
@@ -17,7 +23,7 @@ export declare class Base<T extends JSCalendarObject> {
17
23
  * Clone the current instance with a deep-cloned payload.
18
24
  * @return New instance with cloned data.
19
25
  */
20
- clone(): Base<T>;
26
+ clone(): TSelf;
21
27
  /**
22
28
  * Read a field value from the underlying data.
23
29
  * @param key Field key.
@@ -28,63 +34,56 @@ export declare class Base<T extends JSCalendarObject> {
28
34
  * Set a field value and update metadata as needed.
29
35
  * @param key Field key.
30
36
  * @param value Field value.
31
- * @return Updated instance.
32
- */
33
- set<K extends keyof T>(key: K, value: T[K]): this;
34
- /**
35
- * Apply shallow updates and touch updated/sequence metadata.
36
- * @param values Partial values to merge.
37
- * @param options Update options.
38
- * @return Updated instance.
37
+ * @return New instance with the updated field.
39
38
  */
40
- update(values: Partial<T>, options?: UpdateOptions): this;
39
+ set<K extends keyof T>(key: K, value: T[K]): TSelf;
41
40
  /**
42
41
  * Apply a PatchObject and touch updated/sequence metadata.
43
42
  * @param patch Patch to apply.
44
43
  * @param options Update options.
45
- * @return Updated instance.
44
+ * @return New instance with applied patch.
46
45
  */
47
- patch(patch: PatchObject, options?: UpdateOptions): this;
46
+ patch(patch: TPatch, options?: UpdateOptions): TSelf;
48
47
  /**
49
- * Add a physical location and return its generated ID.
48
+ * Add a physical location and return a new instance.
50
49
  * @param location Location data (without @type).
51
50
  * @param id Optional location ID.
52
- * @return Location ID.
51
+ * @return New instance with the added location.
53
52
  */
54
- addLocation(location: Omit<Location, "@type"> & Partial<Pick<Location, "@type">>, id?: Id): Id;
53
+ addLocation(location: Omit<Location, "@type"> & Partial<Pick<Location, "@type">>, id?: Id): TSelf;
55
54
  /**
56
- * Add a virtual location and return its generated ID.
55
+ * Add a virtual location and return a new instance.
57
56
  * @param location Virtual location data (without @type).
58
57
  * @param id Optional virtual location ID.
59
- * @return Virtual location ID.
58
+ * @return New instance with the added virtual location.
60
59
  */
61
- addVirtualLocation(location: Omit<VirtualLocation, "@type"> & Partial<Pick<VirtualLocation, "@type">>, id?: Id): Id;
60
+ addVirtualLocation(location: Omit<VirtualLocation, "@type"> & Partial<Pick<VirtualLocation, "@type">>, id?: Id): TSelf;
62
61
  /**
63
- * Add a participant and return its generated ID.
62
+ * Add a participant and return a new instance.
64
63
  * @param participant Participant data (without @type).
65
64
  * @param id Optional participant ID.
66
- * @return Participant ID.
65
+ * @return New instance with the added participant.
67
66
  */
68
- addParticipant(participant: Omit<Participant, "@type"> & Partial<Pick<Participant, "@type">>, id?: Id): Id;
67
+ addParticipant(participant: Omit<Participant, "@type"> & Partial<Pick<Participant, "@type">>, id?: Id): TSelf;
69
68
  /**
70
- * Add an alert and return its generated ID.
69
+ * Add an alert and return a new instance.
71
70
  * @param alert Alert data (without @type).
72
71
  * @param id Optional alert ID.
73
- * @return Alert ID.
72
+ * @return New instance with the added alert.
74
73
  */
75
- addAlert(alert: Omit<Alert, "@type"> & Partial<Pick<Alert, "@type">>, id?: Id): Id;
74
+ addAlert(alert: Omit<Alert, "@type"> & Partial<Pick<Alert, "@type">>, id?: Id): TSelf;
76
75
  /**
77
76
  * Update updated/sequence metadata for modified keys.
78
77
  * @param keys Modified keys.
79
78
  * @param options Update options.
80
79
  * @return Updated instance.
81
80
  */
82
- protected touchKeys(keys: string[], options?: UpdateOptions): this;
81
+ protected touchKeys(data: T, keys: string[], options?: UpdateOptions): T;
83
82
  /**
84
83
  * Update updated/sequence metadata for PatchObject changes.
85
84
  * @param patch Patch applied to the object.
86
85
  * @param options Update options.
87
86
  * @return Updated instance.
88
87
  */
89
- protected touchFromPatch(patch: PatchObject, options?: UpdateOptions): this;
88
+ protected touchFromPatch(data: T, patch: PatchLike, options?: UpdateOptions): T;
90
89
  }