@craftguild/jscalendar 0.4.0 → 0.5.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.
@@ -26,7 +26,7 @@ export class Base {
26
26
  * @return New instance with cloned data.
27
27
  */
28
28
  clone() {
29
- return new Base(deepClone(this.data));
29
+ return this.wrap(deepClone(this.data));
30
30
  }
31
31
  /**
32
32
  * Read a field value from the underlying data.
@@ -40,98 +40,90 @@ export class Base {
40
40
  * Set a field value and update metadata as needed.
41
41
  * @param key Field key.
42
42
  * @param value Field value.
43
- * @return Updated instance.
43
+ * @return New instance with the updated field.
44
44
  */
45
45
  set(key, value) {
46
- this.data[key] = value;
47
- return this.touchKeys([String(key)]);
48
- }
49
- /**
50
- * Apply shallow updates and touch updated/sequence metadata.
51
- * @param values Partial values to merge.
52
- * @param options Update options.
53
- * @return Updated instance.
54
- */
55
- update(values, options = {}) {
56
- const next = { ...this.data, ...values };
57
- if (options.validate !== false) {
58
- validateJsCalendarObject(next);
59
- }
60
- this.data = next;
61
- return this.touchKeys(Object.keys(values), options);
46
+ const next = deepClone(this.data);
47
+ next[key] = value;
48
+ const touched = this.touchKeys(next, [String(key)]);
49
+ return this.wrap(touched);
62
50
  }
63
51
  /**
64
52
  * Apply a PatchObject and touch updated/sequence metadata.
65
53
  * @param patch Patch to apply.
66
54
  * @param options Update options.
67
- * @return Updated instance.
55
+ * @return New instance with applied patch.
68
56
  */
69
57
  patch(patch, options = {}) {
70
58
  const next = applyPatch(this.data, patch);
71
59
  if (options.validate !== false) {
72
60
  validateJsCalendarObject(next);
73
61
  }
74
- this.data = next;
75
- return this.touchFromPatch(patch, options);
62
+ const touched = this.touchFromPatch(next, patch, options);
63
+ return this.wrap(touched);
76
64
  }
77
65
  /**
78
- * Add a physical location and return its generated ID.
66
+ * Add a physical location and return a new instance.
79
67
  * @param location Location data (without @type).
80
68
  * @param id Optional location ID.
81
- * @return Location ID.
69
+ * @return New instance with the added location.
82
70
  */
83
71
  addLocation(location, id) {
84
72
  const actualId = id ?? createId();
85
- if (!this.data.locations)
86
- this.data.locations = {};
87
- this.data.locations[actualId] = { ...location, "@type": "Location" };
88
- this.touchKeys(["locations"]);
89
- return actualId;
73
+ const next = deepClone(this.data);
74
+ if (!next.locations)
75
+ next.locations = {};
76
+ next.locations[actualId] = { ...location, "@type": "Location" };
77
+ const touched = this.touchKeys(next, ["locations"]);
78
+ return this.wrap(touched);
90
79
  }
91
80
  /**
92
- * Add a virtual location and return its generated ID.
81
+ * Add a virtual location and return a new instance.
93
82
  * @param location Virtual location data (without @type).
94
83
  * @param id Optional virtual location ID.
95
- * @return Virtual location ID.
84
+ * @return New instance with the added virtual location.
96
85
  */
97
86
  addVirtualLocation(location, id) {
98
87
  const actualId = id ?? createId();
99
- if (!this.data.virtualLocations)
100
- this.data.virtualLocations = {};
88
+ const next = deepClone(this.data);
89
+ if (!next.virtualLocations)
90
+ next.virtualLocations = {};
101
91
  const name = location.name ?? "";
102
- this.data.virtualLocations[actualId] = { ...location, name, "@type": "VirtualLocation" };
103
- this.touchKeys(["virtualLocations"]);
104
- return actualId;
92
+ next.virtualLocations[actualId] = { ...location, name, "@type": "VirtualLocation" };
93
+ const touched = this.touchKeys(next, ["virtualLocations"]);
94
+ return this.wrap(touched);
105
95
  }
106
96
  /**
107
- * Add a participant and return its generated ID.
97
+ * Add a participant and return a new instance.
108
98
  * @param participant Participant data (without @type).
109
99
  * @param id Optional participant ID.
110
- * @return Participant ID.
100
+ * @return New instance with the added participant.
111
101
  */
112
102
  addParticipant(participant, id) {
113
103
  const actualId = id ?? createId();
114
- if (!this.data.participants)
115
- this.data.participants = {};
104
+ const next = deepClone(this.data);
105
+ if (!next.participants)
106
+ next.participants = {};
116
107
  const filled = applyParticipantDefaults({ ...participant, "@type": "Participant" });
117
- this.data.participants[actualId] = filled;
118
- this.touchKeys(["participants"], { sequence: false });
119
- return actualId;
108
+ next.participants[actualId] = filled;
109
+ const touched = this.touchKeys(next, ["participants"], { sequence: false });
110
+ return this.wrap(touched);
120
111
  }
121
112
  /**
122
- * Add an alert and return its generated ID.
113
+ * Add an alert and return a new instance.
123
114
  * @param alert Alert data (without @type).
124
115
  * @param id Optional alert ID.
125
- * @return Alert ID.
116
+ * @return New instance with the added alert.
126
117
  */
127
118
  addAlert(alert, id) {
128
119
  const actualId = id ?? createId();
129
- if (!this.data.alerts)
130
- this.data.alerts = {};
120
+ const next = deepClone(this.data);
121
+ if (!next.alerts)
122
+ next.alerts = {};
131
123
  const filled = applyAlertDefaults({ ...alert, "@type": "Alert" });
132
- this.data.alerts[actualId] = filled;
133
- this.touchKeys(["alerts"]);
134
- return actualId;
124
+ next.alerts[actualId] = filled;
125
+ const touched = this.touchKeys(next, ["alerts"]);
126
+ return this.wrap(touched);
135
127
  }
136
128
  /**
137
129
  * Update updated/sequence metadata for modified keys.
@@ -139,19 +131,19 @@ export class Base {
139
131
  * @param options Update options.
140
132
  * @return Updated instance.
141
133
  */
142
- touchKeys(keys, options = {}) {
134
+ touchKeys(data, keys, options = {}) {
143
135
  if (options.touch === false)
144
- return this;
136
+ return data;
145
137
  const now = options.now ?? nowUtc;
146
- this.data.updated = now();
138
+ data.updated = now();
147
139
  if (options.sequence === false)
148
- return this;
140
+ return data;
149
141
  const onlyParticipants = keys.length > 0 && keys.every((key) => key === KEY_PARTICIPANTS);
150
142
  if (!onlyParticipants) {
151
- const current = isNumberValue(this.data.sequence) ? this.data.sequence : 0;
152
- this.data.sequence = current + 1;
143
+ const current = isNumberValue(data.sequence) ? data.sequence : 0;
144
+ data.sequence = current + 1;
153
145
  }
154
- return this;
146
+ return data;
155
147
  }
156
148
  /**
157
149
  * Update updated/sequence metadata for PatchObject changes.
@@ -159,13 +151,13 @@ export class Base {
159
151
  * @param options Update options.
160
152
  * @return Updated instance.
161
153
  */
162
- touchFromPatch(patch, options = {}) {
154
+ touchFromPatch(data, patch, options = {}) {
163
155
  if (options.touch === false)
164
- return this;
156
+ return data;
165
157
  const now = options.now ?? nowUtc;
166
- this.data.updated = now();
158
+ data.updated = now();
167
159
  if (options.sequence === false)
168
- return this;
160
+ return data;
169
161
  const pointers = Object.keys(patch);
170
162
  const onlyParticipants = pointers.length > 0 &&
171
163
  pointers.every((pointer) => {
@@ -173,9 +165,9 @@ export class Base {
173
165
  return normalized.startsWith("participants");
174
166
  });
175
167
  if (!onlyParticipants) {
176
- const current = isNumberValue(this.data.sequence) ? this.data.sequence : 0;
177
- this.data.sequence = current + 1;
168
+ const current = isNumberValue(data.sequence) ? data.sequence : 0;
169
+ data.sequence = current + 1;
178
170
  }
179
- return this;
171
+ return data;
180
172
  }
181
173
  }
@@ -1,4 +1,4 @@
1
- import type { Alert, AbsoluteTrigger, Link, Location, NDay, OffsetTrigger, Participant, RecurrenceRule, Relation, TimeZone, TimeZoneRule, VirtualLocation, Id } from "../types.js";
1
+ import type { Alert, AbsoluteTrigger, EventPatch, GroupPatch, Link, Location, NDay, OffsetTrigger, Participant, RecurrenceRule, Relation, TaskPatch, TimeZone, TimeZoneRule, VirtualLocation, Id } from "../types.js";
2
2
  declare const TYPE_PARTICIPANT = "Participant";
3
3
  declare const TYPE_LOCATION = "Location";
4
4
  declare const TYPE_VIRTUAL_LOCATION = "VirtualLocation";
@@ -26,6 +26,13 @@ export type TimeZoneInput = WithOptionalType<TimeZone, typeof TYPE_TIME_ZONE>;
26
26
  export type TimeZoneRuleInput = WithOptionalType<TimeZoneRule, typeof TYPE_TIME_ZONE_RULE>;
27
27
  export type RecurrenceRuleInput = WithOptionalType<RecurrenceRule, typeof TYPE_RECURRENCE_RULE>;
28
28
  export type NDayInput = WithOptionalType<NDay, typeof TYPE_NDAY>;
29
+ export type EventPatchInput = EventPatch;
30
+ export type TaskPatchInput = TaskPatch;
31
+ export type GroupPatchInput = GroupPatch;
32
+ export type IdValueInput<TInput> = {
33
+ id?: Id;
34
+ value: TInput;
35
+ };
29
36
  /**
30
37
  * Build a Participant object with @type set and validated.
31
38
  * @param input Participant fields without @type.
@@ -99,49 +106,74 @@ export declare function buildRecurrenceRule(input: RecurrenceRuleInput): Recurre
99
106
  */
100
107
  export declare function buildNDay(input: NDayInput): NDay;
101
108
  /**
102
- * Build a record keyed by generated Ids.
103
- * @param items Items to store.
109
+ * Build a patch object for Event-like updates with JSON value validation.
110
+ * @param input Patch fields for an event.
111
+ * @return Validated PatchObject.
112
+ */
113
+ export declare function buildEventPatch(input: EventPatchInput): EventPatch;
114
+ /**
115
+ * Build a patch object for Task-like updates with JSON value validation.
116
+ * @param input Patch fields for a task.
117
+ * @return Validated PatchObject.
118
+ */
119
+ export declare function buildTaskPatch(input: TaskPatchInput): TaskPatch;
120
+ /**
121
+ * Build a patch object for Group-like updates with JSON value validation.
122
+ * @param input Patch fields for a group.
123
+ * @return Validated PatchObject.
124
+ */
125
+ export declare function buildGroupPatch(input: GroupPatchInput): GroupPatch;
126
+ /**
127
+ * Build a record keyed by Ids, optionally merging into an existing record.
128
+ * @param items Items to store with optional explicit ids.
104
129
  * @param builder Builder function to validate each item.
105
- * @param idFn Optional ID generator.
106
- * @return Record keyed by generated Ids.
130
+ * @param idFn Optional ID generator for items without explicit ids.
131
+ * @param existing Existing record to merge into.
132
+ * @return Record keyed by ids with merged values.
107
133
  */
108
- export declare function buildIdMap<TInput, TOutput>(items: TInput[], builder: (input: TInput) => TOutput, idFn?: (input: TInput, index: number) => Id): Record<Id, TOutput>;
134
+ export declare function buildIdMap<TInput, TOutput>(items: Array<IdValueInput<TInput>>, builder: (input: TInput) => TOutput, idFn?: (input: TInput, index: number) => Id, existing?: Record<Id, TOutput>): Record<Id, TOutput>;
109
135
  /**
110
- * Build a record of Participant objects keyed by generated Ids.
111
- * @param items Participant inputs.
136
+ * Build a record of Participant objects keyed by Ids.
137
+ * @param items Participant inputs with optional explicit ids.
138
+ * @param existing Existing participant record to merge into.
112
139
  * @return Participant record keyed by Id.
113
140
  */
114
- export declare function buildParticipants(items: ParticipantInput[]): Record<Id, Participant>;
141
+ export declare function buildParticipants(items: Array<IdValueInput<ParticipantInput>>, existing?: Record<Id, Participant>): Record<Id, Participant>;
115
142
  /**
116
- * Build a record of Location objects keyed by generated Ids.
117
- * @param items Location inputs.
143
+ * Build a record of Location objects keyed by Ids.
144
+ * @param items Location inputs with optional explicit ids.
145
+ * @param existing Existing location record to merge into.
118
146
  * @return Location record keyed by Id.
119
147
  */
120
- export declare function buildLocations(items: LocationInput[]): Record<Id, Location>;
148
+ export declare function buildLocations(items: Array<IdValueInput<LocationInput>>, existing?: Record<Id, Location>): Record<Id, Location>;
121
149
  /**
122
- * Build a record of VirtualLocation objects keyed by generated Ids.
123
- * @param items VirtualLocation inputs.
150
+ * Build a record of VirtualLocation objects keyed by Ids.
151
+ * @param items VirtualLocation inputs with optional explicit ids.
152
+ * @param existing Existing virtual location record to merge into.
124
153
  * @return VirtualLocation record keyed by Id.
125
154
  */
126
- export declare function buildVirtualLocations(items: VirtualLocationInput[]): Record<Id, VirtualLocation>;
155
+ export declare function buildVirtualLocations(items: Array<IdValueInput<VirtualLocationInput>>, existing?: Record<Id, VirtualLocation>): Record<Id, VirtualLocation>;
127
156
  /**
128
- * Build a record of Alert objects keyed by generated Ids.
129
- * @param items Alert inputs.
157
+ * Build a record of Alert objects keyed by Ids.
158
+ * @param items Alert inputs with optional explicit ids.
159
+ * @param existing Existing alert record to merge into.
130
160
  * @return Alert record keyed by Id.
131
161
  */
132
- export declare function buildAlerts(items: AlertInput[]): Record<Id, Alert>;
162
+ export declare function buildAlerts(items: Array<IdValueInput<AlertInput>>, existing?: Record<Id, Alert>): Record<Id, Alert>;
133
163
  /**
134
- * Build a record of Link objects keyed by generated Ids.
135
- * @param items Link inputs.
164
+ * Build a record of Link objects keyed by Ids.
165
+ * @param items Link inputs with optional explicit ids.
166
+ * @param existing Existing link record to merge into.
136
167
  * @return Link record keyed by Id.
137
168
  */
138
- export declare function buildLinks(items: LinkInput[]): Record<Id, Link>;
169
+ export declare function buildLinks(items: Array<IdValueInput<LinkInput>>, existing?: Record<Id, Link>): Record<Id, Link>;
139
170
  /**
140
- * Build a record of Relation objects keyed by generated Ids.
141
- * @param items Relation inputs.
171
+ * Build a record of Relation objects keyed by Ids.
172
+ * @param items Relation inputs with optional explicit ids.
173
+ * @param existing Existing relation record to merge into.
142
174
  * @return Relation record keyed by Id.
143
175
  */
144
- export declare function buildRelatedTo(items: RelationInput[]): Record<Id, Relation>;
176
+ export declare function buildRelatedTo(items: Array<IdValueInput<RelationInput>>, existing?: Record<Id, Relation>): Record<Id, Relation>;
145
177
  /**
146
178
  * Build a record of TimeZone objects keyed by tzId.
147
179
  * @param items TimeZone inputs.
@@ -2,7 +2,7 @@ import { createId } from "./ids.js";
2
2
  import { validateAlert, validateLink, validateLocation, validateParticipant, validateRelation, validateTimeZoneObject, validateTimeZoneRule, validateVirtualLocation } from "../validate/validators-common.js";
3
3
  import { validateNDay, validateRecurrenceRule } from "../validate/validators-recurrence.js";
4
4
  import { fail } from "../validate/error.js";
5
- import { assertSignedDuration, assertString, assertUtcDateTime } from "../validate/asserts.js";
5
+ import { assertPatchObject, assertSignedDuration, assertString, assertUtcDateTime } from "../validate/asserts.js";
6
6
  const TYPE_PARTICIPANT = "Participant";
7
7
  const TYPE_LOCATION = "Location";
8
8
  const TYPE_VIRTUAL_LOCATION = "VirtualLocation";
@@ -173,67 +173,104 @@ export function buildNDay(input) {
173
173
  return nday;
174
174
  }
175
175
  /**
176
- * Build a record keyed by generated Ids.
177
- * @param items Items to store.
176
+ * Build a patch object for Event-like updates with JSON value validation.
177
+ * @param input Patch fields for an event.
178
+ * @return Validated PatchObject.
179
+ */
180
+ export function buildEventPatch(input) {
181
+ const patch = { ...input };
182
+ assertPatchObject(patch, "eventPatch");
183
+ return patch;
184
+ }
185
+ /**
186
+ * Build a patch object for Task-like updates with JSON value validation.
187
+ * @param input Patch fields for a task.
188
+ * @return Validated PatchObject.
189
+ */
190
+ export function buildTaskPatch(input) {
191
+ const patch = { ...input };
192
+ assertPatchObject(patch, "taskPatch");
193
+ return patch;
194
+ }
195
+ /**
196
+ * Build a patch object for Group-like updates with JSON value validation.
197
+ * @param input Patch fields for a group.
198
+ * @return Validated PatchObject.
199
+ */
200
+ export function buildGroupPatch(input) {
201
+ const patch = { ...input };
202
+ assertPatchObject(patch, "groupPatch");
203
+ return patch;
204
+ }
205
+ /**
206
+ * Build a record keyed by Ids, optionally merging into an existing record.
207
+ * @param items Items to store with optional explicit ids.
178
208
  * @param builder Builder function to validate each item.
179
- * @param idFn Optional ID generator.
180
- * @return Record keyed by generated Ids.
209
+ * @param idFn Optional ID generator for items without explicit ids.
210
+ * @param existing Existing record to merge into.
211
+ * @return Record keyed by ids with merged values.
181
212
  */
182
- export function buildIdMap(items, builder, idFn = () => createId()) {
183
- const result = {};
213
+ export function buildIdMap(items, builder, idFn = () => createId(), existing) {
214
+ const result = existing ? { ...existing } : {};
184
215
  items.forEach((item, index) => {
185
- const id = idFn(item, index);
186
- result[id] = builder(item);
216
+ const id = item.id ?? idFn(item.value, index);
217
+ result[id] = builder(item.value);
187
218
  });
188
219
  return result;
189
220
  }
190
221
  /**
191
- * Build a record of Participant objects keyed by generated Ids.
192
- * @param items Participant inputs.
222
+ * Build a record of Participant objects keyed by Ids.
223
+ * @param items Participant inputs with optional explicit ids.
224
+ * @param existing Existing participant record to merge into.
193
225
  * @return Participant record keyed by Id.
194
226
  */
195
- export function buildParticipants(items) {
196
- return buildIdMap(items, buildParticipant);
227
+ export function buildParticipants(items, existing) {
228
+ return buildIdMap(items, buildParticipant, () => createId(), existing);
197
229
  }
198
230
  /**
199
- * Build a record of Location objects keyed by generated Ids.
200
- * @param items Location inputs.
231
+ * Build a record of Location objects keyed by Ids.
232
+ * @param items Location inputs with optional explicit ids.
233
+ * @param existing Existing location record to merge into.
201
234
  * @return Location record keyed by Id.
202
235
  */
203
- export function buildLocations(items) {
204
- return buildIdMap(items, buildLocation);
236
+ export function buildLocations(items, existing) {
237
+ return buildIdMap(items, buildLocation, () => createId(), existing);
205
238
  }
206
239
  /**
207
- * Build a record of VirtualLocation objects keyed by generated Ids.
208
- * @param items VirtualLocation inputs.
240
+ * Build a record of VirtualLocation objects keyed by Ids.
241
+ * @param items VirtualLocation inputs with optional explicit ids.
242
+ * @param existing Existing virtual location record to merge into.
209
243
  * @return VirtualLocation record keyed by Id.
210
244
  */
211
- export function buildVirtualLocations(items) {
212
- return buildIdMap(items, buildVirtualLocation);
245
+ export function buildVirtualLocations(items, existing) {
246
+ return buildIdMap(items, buildVirtualLocation, () => createId(), existing);
213
247
  }
214
248
  /**
215
- * Build a record of Alert objects keyed by generated Ids.
216
- * @param items Alert inputs.
249
+ * Build a record of Alert objects keyed by Ids.
250
+ * @param items Alert inputs with optional explicit ids.
251
+ * @param existing Existing alert record to merge into.
217
252
  * @return Alert record keyed by Id.
218
253
  */
219
- export function buildAlerts(items) {
220
- return buildIdMap(items, buildAlert);
254
+ export function buildAlerts(items, existing) {
255
+ return buildIdMap(items, buildAlert, () => createId(), existing);
221
256
  }
222
257
  /**
223
- * Build a record of Link objects keyed by generated Ids.
224
- * @param items Link inputs.
258
+ * Build a record of Link objects keyed by Ids.
259
+ * @param items Link inputs with optional explicit ids.
260
+ * @param existing Existing link record to merge into.
225
261
  * @return Link record keyed by Id.
226
262
  */
227
- export function buildLinks(items) {
228
- return buildIdMap(items, buildLink);
263
+ export function buildLinks(items, existing) {
264
+ return buildIdMap(items, buildLink, () => createId(), existing);
229
265
  }
230
266
  /**
231
- * Build a record of Relation objects keyed by generated Ids.
232
- * @param items Relation inputs.
267
+ * Build a record of Relation objects keyed by Ids.
268
+ * @param items Relation inputs with optional explicit ids.
269
+ * @param existing Existing relation record to merge into.
233
270
  * @return Relation record keyed by Id.
234
271
  */
235
- export function buildRelatedTo(items) {
236
- return buildIdMap(items, buildRelation);
272
+ export function buildRelatedTo(items, existing) {
273
+ return buildIdMap(items, buildRelation, () => createId(), existing);
237
274
  }
238
275
  /**
239
276
  * Build a record of TimeZone objects keyed by tzId.
@@ -1,7 +1,13 @@
1
- import type { Event } from "../types.js";
1
+ import type { Event, EventPatch } from "../types.js";
2
2
  import type { CreateOptions, EventInput } from "./types.js";
3
3
  import { Base } from "./base.js";
4
- export declare class EventObject extends Base<Event> {
4
+ export declare class EventObject extends Base<Event, EventPatch, EventObject> {
5
+ /**
6
+ * Wrap updated data in a new EventObject.
7
+ * @param data Updated event data.
8
+ * @return New EventObject instance.
9
+ */
10
+ protected wrap(data: Event): EventObject;
5
11
  /**
6
12
  * Create an event with normalized dates, duration, and RFC defaults.
7
13
  * @param input Event input values to normalize.
@@ -8,6 +8,15 @@ import { toLocalDateTime, toUtcDateTime } from "./datetime.js";
8
8
  import { Base } from "./base.js";
9
9
  import { isStringValue, isNumberValue } from "../utils.js";
10
10
  export class EventObject extends Base {
11
+ /**
12
+ * Wrap updated data in a new EventObject.
13
+ * @param data Updated event data.
14
+ * @return New EventObject instance.
15
+ */
16
+ wrap(data) {
17
+ const { "@type": _type, ...rest } = data;
18
+ return new EventObject(rest, { validate: false });
19
+ }
11
20
  /**
12
21
  * Create an event with normalized dates, duration, and RFC defaults.
13
22
  * @param input Event input values to normalize.
@@ -64,8 +73,6 @@ export class EventObject extends Base {
64
73
  * @return Cloned EventObject.
65
74
  */
66
75
  clone() {
67
- const cloneData = deepClone(this.data);
68
- const { "@type": _type, ...rest } = cloneData;
69
- return new EventObject(rest);
76
+ return this.wrap(deepClone(this.data));
70
77
  }
71
78
  }
@@ -1,7 +1,13 @@
1
- import type { Event, Group, Task } from "../types.js";
1
+ import type { Event, Group, GroupPatch, Task } from "../types.js";
2
2
  import type { CreateOptions, GroupInput } from "./types.js";
3
3
  import { Base } from "./base.js";
4
- export declare class GroupObject extends Base<Group> {
4
+ export declare class GroupObject extends Base<Group, GroupPatch, GroupObject> {
5
+ /**
6
+ * Wrap updated data in a new GroupObject.
7
+ * @param data Updated group data.
8
+ * @return New GroupObject instance.
9
+ */
10
+ protected wrap(data: Group): GroupObject;
5
11
  /**
6
12
  * Create a group with normalized entries and RFC defaults.
7
13
  * @param input Group input values to normalize.
@@ -12,11 +18,11 @@ export declare class GroupObject extends Base<Group> {
12
18
  /**
13
19
  * Append a normalized entry to the group.
14
20
  * @param entry Event or task entry to add.
15
- * @return Updated GroupObject instance.
21
+ * @return New GroupObject instance with the added entry.
16
22
  */
17
23
  addEntry(entry: Event | Task | {
18
24
  data: Event | Task;
19
- }): this;
25
+ }): GroupObject;
20
26
  /**
21
27
  * Clone the group as a new GroupObject instance.
22
28
  * @return Cloned GroupObject.
@@ -6,6 +6,15 @@ import { Base } from "./base.js";
6
6
  import { normalizeEntry } from "./normalize.js";
7
7
  import { toUtcDateTime } from "./datetime.js";
8
8
  export class GroupObject extends Base {
9
+ /**
10
+ * Wrap updated data in a new GroupObject.
11
+ * @param data Updated group data.
12
+ * @return New GroupObject instance.
13
+ */
14
+ wrap(data) {
15
+ const { "@type": _type, ...rest } = data;
16
+ return new GroupObject(rest, { validate: false });
17
+ }
9
18
  /**
10
19
  * Create a group with normalized entries and RFC defaults.
11
20
  * @param input Group input values to normalize.
@@ -42,21 +51,19 @@ export class GroupObject extends Base {
42
51
  /**
43
52
  * Append a normalized entry to the group.
44
53
  * @param entry Event or task entry to add.
45
- * @return Updated GroupObject instance.
54
+ * @return New GroupObject instance with the added entry.
46
55
  */
47
56
  addEntry(entry) {
48
- const entries = [...this.data.entries, normalizeEntry(entry)];
49
- this.data.entries = entries;
50
- this.touchKeys(["entries"]);
51
- return this;
57
+ const next = deepClone(this.data);
58
+ next.entries = [...next.entries, normalizeEntry(entry)];
59
+ const touched = this.touchKeys(next, ["entries"]);
60
+ return this.wrap(touched);
52
61
  }
53
62
  /**
54
63
  * Clone the group as a new GroupObject instance.
55
64
  * @return Cloned GroupObject.
56
65
  */
57
66
  clone() {
58
- const cloneData = deepClone(this.data);
59
- const { "@type": _type, ...rest } = cloneData;
60
- return new GroupObject(rest);
67
+ return this.wrap(deepClone(this.data));
61
68
  }
62
69
  }
@@ -1,7 +1,13 @@
1
- import type { Task } from "../types.js";
1
+ import type { Task, TaskPatch } from "../types.js";
2
2
  import type { CreateOptions, TaskInput } from "./types.js";
3
3
  import { Base } from "./base.js";
4
- export declare class TaskObject extends Base<Task> {
4
+ export declare class TaskObject extends Base<Task, TaskPatch, TaskObject> {
5
+ /**
6
+ * Wrap updated data in a new TaskObject.
7
+ * @param data Updated task data.
8
+ * @return New TaskObject instance.
9
+ */
10
+ protected wrap(data: Task): TaskObject;
5
11
  /**
6
12
  * Create a task with normalized date fields and RFC defaults applied.
7
13
  * @param input Task input values to normalize.
@@ -6,6 +6,15 @@ import { createUid } from "./ids.js";
6
6
  import { toLocalDateTime, toUtcDateTime } from "./datetime.js";
7
7
  import { Base } from "./base.js";
8
8
  export class TaskObject extends Base {
9
+ /**
10
+ * Wrap updated data in a new TaskObject.
11
+ * @param data Updated task data.
12
+ * @return New TaskObject instance.
13
+ */
14
+ wrap(data) {
15
+ const { "@type": _type, ...rest } = data;
16
+ return new TaskObject(rest, { validate: false });
17
+ }
9
18
  /**
10
19
  * Create a task with normalized date fields and RFC defaults applied.
11
20
  * @param input Task input values to normalize.
@@ -53,8 +62,6 @@ export class TaskObject extends Base {
53
62
  * @return Cloned TaskObject.
54
63
  */
55
64
  clone() {
56
- const cloneData = deepClone(this.data);
57
- const { "@type": _type, ...rest } = cloneData;
58
- return new TaskObject(rest);
65
+ return this.wrap(deepClone(this.data));
59
66
  }
60
67
  }