@remit/calendar-service 0.0.10 → 0.0.11
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/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/window.test.ts +73 -0
- package/src/window.ts +49 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
package/src/window.test.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type CalendarWindowRepositories,
|
|
21
21
|
listBusySpans,
|
|
22
22
|
listCalendarInstances,
|
|
23
|
+
listObjectInstances,
|
|
23
24
|
mergeBusySpans,
|
|
24
25
|
} from "./window.js";
|
|
25
26
|
|
|
@@ -369,6 +370,78 @@ describe("listCalendarInstances", () => {
|
|
|
369
370
|
});
|
|
370
371
|
});
|
|
371
372
|
|
|
373
|
+
describe("listObjectInstances", () => {
|
|
374
|
+
const ownRows = (resource: StoredResource) => ({
|
|
375
|
+
calendarEventIndex: {
|
|
376
|
+
listForObject: async () => resource.rows,
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("reads one resource's own rows where its index reaches the window", async () => {
|
|
381
|
+
const calendar = collection();
|
|
382
|
+
const weekly = await store(
|
|
383
|
+
calendar,
|
|
384
|
+
"weekly.ics",
|
|
385
|
+
ical(
|
|
386
|
+
"BEGIN:VCALENDAR",
|
|
387
|
+
"VERSION:2.0",
|
|
388
|
+
"BEGIN:VEVENT",
|
|
389
|
+
"UID:weekly@example.com",
|
|
390
|
+
"DTSTART:20260907T090000Z",
|
|
391
|
+
"DTEND:20260907T100000Z",
|
|
392
|
+
"SUMMARY:Stand-up",
|
|
393
|
+
"RRULE:FREQ=WEEKLY;COUNT=4",
|
|
394
|
+
"END:VEVENT",
|
|
395
|
+
"END:VCALENDAR",
|
|
396
|
+
),
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
const instances = await listObjectInstances(
|
|
400
|
+
ownRows(weekly),
|
|
401
|
+
calendar,
|
|
402
|
+
weekly.object,
|
|
403
|
+
{ from: "2026-09-10T00:00:00Z", to: "2026-09-25T00:00:00Z" },
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
assert.deepEqual(
|
|
407
|
+
instances.map((instance) => instance.startAt),
|
|
408
|
+
["2026-09-14T09:00:00Z", "2026-09-21T09:00:00Z"],
|
|
409
|
+
);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it("expands the resource live where its index stops before the window", async () => {
|
|
413
|
+
const calendar = collection();
|
|
414
|
+
const openEnded = await store(
|
|
415
|
+
calendar,
|
|
416
|
+
"open.ics",
|
|
417
|
+
ical(
|
|
418
|
+
"BEGIN:VCALENDAR",
|
|
419
|
+
"VERSION:2.0",
|
|
420
|
+
"BEGIN:VEVENT",
|
|
421
|
+
"UID:open@example.com",
|
|
422
|
+
"DTSTART:20200907T090000Z",
|
|
423
|
+
"DTEND:20200907T100000Z",
|
|
424
|
+
"SUMMARY:Weekly one-to-one",
|
|
425
|
+
"RRULE:FREQ=WEEKLY",
|
|
426
|
+
"END:VEVENT",
|
|
427
|
+
"END:VCALENDAR",
|
|
428
|
+
),
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
const instances = await listObjectInstances(
|
|
432
|
+
ownRows(openEnded),
|
|
433
|
+
calendar,
|
|
434
|
+
openEnded.object,
|
|
435
|
+
{ from: "2026-09-14T00:00:00Z", to: "2026-09-28T00:00:00Z" },
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
assert.deepEqual(
|
|
439
|
+
instances.map((instance) => instance.startAt),
|
|
440
|
+
["2026-09-14T09:00:00Z", "2026-09-21T09:00:00Z"],
|
|
441
|
+
);
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
372
445
|
describe("mergeBusySpans", () => {
|
|
373
446
|
it("collapses overlapping and touching spans", () => {
|
|
374
447
|
assert.deepEqual(
|
package/src/window.ts
CHANGED
|
@@ -158,6 +158,55 @@ export const listCalendarInstances = async (
|
|
|
158
158
|
.sort((left, right) => left.startAt.localeCompare(right.startAt));
|
|
159
159
|
};
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* The occurrences of one resource in a window, read from that resource alone:
|
|
163
|
+
* its own index rows where the index reaches the window, and a live expansion
|
|
164
|
+
* of it where it does not.
|
|
165
|
+
*/
|
|
166
|
+
export const listObjectInstances = async (
|
|
167
|
+
repositories: {
|
|
168
|
+
calendarEventIndex: Pick<ICalendarEventIndexRepository, "listForObject">;
|
|
169
|
+
},
|
|
170
|
+
collection: CalendarCollectionItem,
|
|
171
|
+
object: CalendarObjectItem,
|
|
172
|
+
window: CalendarWindow,
|
|
173
|
+
): Promise<CalendarInstance[]> => {
|
|
174
|
+
const fromMs = Date.parse(window.from);
|
|
175
|
+
const toMs = Date.parse(window.to);
|
|
176
|
+
const indexReaches =
|
|
177
|
+
object.expandedThrough === "" || object.expandedThrough >= window.to;
|
|
178
|
+
|
|
179
|
+
const occurrences = indexReaches
|
|
180
|
+
? await repositories.calendarEventIndex.listForObject(
|
|
181
|
+
collection.calendarId,
|
|
182
|
+
object.calendarObjectId,
|
|
183
|
+
)
|
|
184
|
+
: await expandObject(object, collection.timezone, fromMs, toMs);
|
|
185
|
+
|
|
186
|
+
return occurrences
|
|
187
|
+
.map((occurrence) => instanceOf(collection, object, occurrence))
|
|
188
|
+
.filter((instance) => overlapsWindow(instance, fromMs, toMs))
|
|
189
|
+
.sort((left, right) => left.startAt.localeCompare(right.startAt));
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const expandObject = async (
|
|
193
|
+
object: CalendarObjectItem,
|
|
194
|
+
timezone: string,
|
|
195
|
+
fromMs: number,
|
|
196
|
+
toMs: number,
|
|
197
|
+
): Promise<CalendarOccurrenceInput[]> => {
|
|
198
|
+
const parsed = await parseCalendar(object.icalData);
|
|
199
|
+
if (!parsed.ok) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`stored calendar object ${object.calendarObjectId} no longer parses: ${parsed.error.message}`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
return expandCalendarWindow(parsed.value, timezone, {
|
|
205
|
+
fromMs: fromMs - LOOKBACK_MS,
|
|
206
|
+
toMs,
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
|
|
161
210
|
const overlapsWindow = (
|
|
162
211
|
instance: CalendarInstance,
|
|
163
212
|
fromMs: number,
|