@remit/calendar-service 0.0.17 → 0.0.19

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 (73) hide show
  1. package/dist/accept.d.ts +71 -0
  2. package/dist/accept.js +159 -0
  3. package/dist/build.d.ts +79 -0
  4. package/dist/build.js +196 -0
  5. package/dist/correct.d.ts +4 -0
  6. package/dist/correct.js +22 -0
  7. package/dist/errors.d.ts +56 -0
  8. package/dist/errors.js +1 -0
  9. package/dist/etag.d.ts +9 -0
  10. package/{src/etag.ts → dist/etag.js} +1 -3
  11. package/dist/expand.d.ts +73 -0
  12. package/dist/expand.js +188 -0
  13. package/dist/feed.d.ts +81 -0
  14. package/dist/feed.js +157 -0
  15. package/dist/fixtures.d.ts +19 -0
  16. package/dist/fixtures.js +37 -0
  17. package/dist/index.d.ts +16 -0
  18. package/dist/index.js +15 -0
  19. package/dist/memory-store.d.ts +105 -0
  20. package/dist/memory-store.js +319 -0
  21. package/dist/parse.d.ts +39 -0
  22. package/dist/parse.js +87 -0
  23. package/dist/project.d.ts +30 -0
  24. package/dist/project.js +57 -0
  25. package/dist/put.d.ts +52 -0
  26. package/dist/put.js +78 -0
  27. package/dist/reopen.d.ts +14 -0
  28. package/dist/reopen.js +92 -0
  29. package/dist/scope.d.ts +70 -0
  30. package/dist/scope.js +687 -0
  31. package/dist/subscribe.d.ts +96 -0
  32. package/dist/subscribe.js +298 -0
  33. package/dist/suggest.d.ts +74 -0
  34. package/dist/suggest.js +138 -0
  35. package/dist/time.d.ts +76 -0
  36. package/dist/time.js +164 -0
  37. package/dist/window.d.ts +85 -0
  38. package/dist/window.js +161 -0
  39. package/package.json +40 -35
  40. package/src/accept.test.ts +0 -403
  41. package/src/accept.ts +0 -229
  42. package/src/build.test.ts +0 -113
  43. package/src/build.ts +0 -311
  44. package/src/correct.test.ts +0 -173
  45. package/src/correct.ts +0 -40
  46. package/src/errors.ts +0 -62
  47. package/src/etag.test.ts +0 -54
  48. package/src/expand.test.ts +0 -361
  49. package/src/expand.ts +0 -303
  50. package/src/feed.test.ts +0 -334
  51. package/src/feed.ts +0 -203
  52. package/src/feeds/google-secret-address.ics +0 -99
  53. package/src/fixtures.ts +0 -52
  54. package/src/index.ts +0 -125
  55. package/src/memory-store.ts +0 -404
  56. package/src/parse.test.ts +0 -264
  57. package/src/parse.ts +0 -138
  58. package/src/project.test.ts +0 -247
  59. package/src/project.ts +0 -122
  60. package/src/put.test.ts +0 -320
  61. package/src/put.ts +0 -123
  62. package/src/reopen.test.ts +0 -531
  63. package/src/reopen.ts +0 -190
  64. package/src/scope.test.ts +0 -1421
  65. package/src/scope.ts +0 -1003
  66. package/src/subscribe.test.ts +0 -578
  67. package/src/subscribe.ts +0 -420
  68. package/src/suggest.test.ts +0 -376
  69. package/src/suggest.ts +0 -244
  70. package/src/time.ts +0 -260
  71. package/src/window.test.ts +0 -648
  72. package/src/window.ts +0 -281
  73. package/tsconfig.json +0 -8
package/dist/time.js ADDED
@@ -0,0 +1,164 @@
1
+ import { ZoneCertainty } from "@remit/domain-enums";
2
+ import ICAL from "ical.js";
3
+ const pad = (value, width = 2) => String(Math.abs(value)).padStart(width, "0");
4
+ const civilOf = (time) => ({
5
+ year: time.year,
6
+ month: time.month,
7
+ day: time.day,
8
+ hour: time.isDate ? 0 : time.hour,
9
+ minute: time.isDate ? 0 : time.minute,
10
+ second: time.isDate ? 0 : time.second,
11
+ });
12
+ /**
13
+ * The offset an IANA zone was at on a given instant, in minutes.
14
+ *
15
+ * Read from the platform's own zone database through `Intl` rather than from a
16
+ * bundled copy: ical.js ships no zone data, and a resource whose TZID names a
17
+ * zone it carries no VTIMEZONE for is otherwise unresolvable — which is how a
18
+ * DST-crossing series silently drifts by an hour.
19
+ */
20
+ const zoneOffsetMinutes = (timeZone, instantMs) => {
21
+ const parts = new Intl.DateTimeFormat("en-US", {
22
+ timeZone,
23
+ hourCycle: "h23",
24
+ year: "numeric",
25
+ month: "2-digit",
26
+ day: "2-digit",
27
+ hour: "2-digit",
28
+ minute: "2-digit",
29
+ second: "2-digit",
30
+ }).formatToParts(new Date(instantMs));
31
+ const field = (type) => Number(parts.find((part) => part.type === type)?.value ?? "0");
32
+ const asUtc = Date.UTC(field("year"), field("month") - 1, field("day"), field("hour"), field("minute"), field("second"));
33
+ return (asUtc - instantMs) / 60_000;
34
+ };
35
+ /**
36
+ * The zones this platform can be asked about.
37
+ *
38
+ * Read once from the runtime rather than probed per value: an unusable TZID —
39
+ * a Windows zone name, a typo — makes `Intl` throw, and a resource written by a
40
+ * client with a zone we cannot name is a resource to read in the next fallback,
41
+ * never a crashed write. The list is the canonical IANA set, so a deprecated
42
+ * alias falls through to the collection's own zone.
43
+ */
44
+ const KNOWN_ZONES = new Set(Intl.supportedValuesOf("timeZone"));
45
+ const isKnownZone = (timeZone) => KNOWN_ZONES.has(timeZone);
46
+ /**
47
+ * The offset a civil wall time carries in a zone. Applied twice because the
48
+ * first lookup asks the zone about the wrong instant — the wall time read as if
49
+ * it were UTC — which lands in the wrong side of a DST transition for wall
50
+ * times within an hour or so of the cutover.
51
+ */
52
+ const offsetForCivil = (timeZone, civil) => {
53
+ const asUtc = Date.UTC(civil.year, civil.month - 1, civil.day, civil.hour, civil.minute, civil.second);
54
+ const firstPass = zoneOffsetMinutes(timeZone, asUtc);
55
+ return zoneOffsetMinutes(timeZone, asUtc - firstPass * 60_000);
56
+ };
57
+ const formatOffset = (offsetMinutes) => {
58
+ const sign = offsetMinutes < 0 ? "-" : "+";
59
+ return `${sign}${pad(Math.trunc(offsetMinutes / 60))}:${pad(offsetMinutes % 60)}`;
60
+ };
61
+ const formatCivil = (civil, offsetMinutes) => `${pad(civil.year, 4)}-${pad(civil.month)}-${pad(civil.day)}T${pad(civil.hour)}:${pad(civil.minute)}:${pad(civil.second)}${formatOffset(offsetMinutes)}`;
62
+ export const toUtcIso = (instantMs) => `${new Date(instantMs).toISOString().slice(0, 19)}Z`;
63
+ /** Whether this platform can be asked about a zone by name. */
64
+ export const isResolvableZone = (timeZone) => isKnownZone(timeZone);
65
+ /**
66
+ * The wall-clock fields an instant reads as in a zone, with the offset that
67
+ * zone was at. The zone falls back to UTC when it is one this platform cannot
68
+ * name, which is the same fallback `resolveTime` takes.
69
+ */
70
+ export const civilInZone = (instantMs, timeZone) => {
71
+ const zone = isKnownZone(timeZone) ? timeZone : "UTC";
72
+ const offsetMinutes = zoneOffsetMinutes(zone, instantMs);
73
+ const shifted = new Date(instantMs + offsetMinutes * 60_000);
74
+ return {
75
+ year: shifted.getUTCFullYear(),
76
+ month: shifted.getUTCMonth() + 1,
77
+ day: shifted.getUTCDate(),
78
+ hour: shifted.getUTCHours(),
79
+ minute: shifted.getUTCMinutes(),
80
+ second: shifted.getUTCSeconds(),
81
+ offsetMinutes,
82
+ };
83
+ };
84
+ /**
85
+ * An instant as the wall time it reads as in a zone, carrying that zone's
86
+ * offset — the form a client renders.
87
+ *
88
+ * The store keeps occurrences as UTC instants because only a fixed-width form
89
+ * sorts correctly, and a client that was handed those would have to re-derive
90
+ * the event's own zone to draw it anywhere. This is the other direction, done
91
+ * once on the server: an occurrence of a 09:00 Amsterdam meeting comes back as
92
+ * `09:00+02:00` in summer and `09:00+01:00` in winter rather than as two
93
+ * different times of day.
94
+ */
95
+ export const toOffsetIso = (instantMs, timeZone) => {
96
+ const civil = civilInZone(instantMs, timeZone);
97
+ return formatCivil(civil, civil.offsetMinutes);
98
+ };
99
+ /**
100
+ * Resolves one iCalendar time to an instant and to its own wall-clock form.
101
+ *
102
+ * The zone is taken from the value itself when ical.js could bind one — a
103
+ * VTIMEZONE defined in this very resource, or UTC. A floating value, and a
104
+ * value whose TZID has no VTIMEZONE, falls back to `tzidHint` (the property's
105
+ * own TZID parameter) and then to the collection's timezone, so a resource
106
+ * written by a client that assumes the server knows the zone database still
107
+ * lands on the right instant.
108
+ *
109
+ * Which of those happened is reported, never swallowed: a named zone that no
110
+ * fallback could resolve comes back `Ambiguous`, so a caller can say the event
111
+ * may be hours out instead of drawing it in the wrong place in silence.
112
+ */
113
+ export const resolveTime = (time, tzidHint, collectionTimezone) => {
114
+ const civil = civilOf(time);
115
+ let offsetMinutes;
116
+ let certainty;
117
+ if (time.zone === ICAL.Timezone.utcTimezone) {
118
+ offsetMinutes = 0;
119
+ certainty = ZoneCertainty.Explicit;
120
+ }
121
+ else if (time.zone !== ICAL.Timezone.localTimezone) {
122
+ offsetMinutes = time.utcOffset() / 60;
123
+ certainty = ZoneCertainty.Explicit;
124
+ }
125
+ else {
126
+ const fallback = [tzidHint, collectionTimezone].find(isKnownZone) ?? "UTC";
127
+ offsetMinutes = offsetForCivil(fallback, civil);
128
+ if (tzidHint === "") {
129
+ // No zone was ever named. RFC 5545 floating time, read where the
130
+ // collection lives.
131
+ certainty = ZoneCertainty.Local;
132
+ }
133
+ else {
134
+ certainty = isKnownZone(tzidHint)
135
+ ? ZoneCertainty.Explicit
136
+ : ZoneCertainty.Ambiguous;
137
+ }
138
+ }
139
+ const instantMs = Date.UTC(civil.year, civil.month - 1, civil.day, civil.hour, civil.minute, civil.second) -
140
+ offsetMinutes * 60_000;
141
+ return {
142
+ instantMs,
143
+ isoUtc: toUtcIso(instantMs),
144
+ isoOffset: formatCivil(civil, offsetMinutes),
145
+ isDate: time.isDate,
146
+ certainty,
147
+ };
148
+ };
149
+ /** The TZID a property was written with, or `""` when it names none. */
150
+ export const tzidOf = (property) => {
151
+ const tzid = property?.getParameter("tzid");
152
+ return typeof tzid === "string" ? tzid : "";
153
+ };
154
+ /** The zone a component's DTSTART was written in. */
155
+ export const dtStartTzid = (component) => tzidOf(component.getFirstProperty("dtstart"));
156
+ /**
157
+ * The zone a component's end was written in. DTEND carries its own TZID and
158
+ * need not match DTSTART's, so an end read with the start's zone silently
159
+ * changes the event's length. Only a stated DTEND has a zone of its own: an end
160
+ * derived from a duration is already in the start's zone.
161
+ */
162
+ export const dtEndTzid = (component) => component.hasProperty("dtend")
163
+ ? tzidOf(component.getFirstProperty("dtend"))
164
+ : dtStartTzid(component);
@@ -0,0 +1,85 @@
1
+ import type { CalendarCollectionItem, CalendarObjectItem, ICalendarEventIndexRepository, ICalendarObjectRepository } from "@remit/data-ports";
2
+ /**
3
+ * How far before the window occurrences are read, so one that started earlier
4
+ * and runs into it is still returned.
5
+ *
6
+ * The occurrence index is keyed by start, which is the only key a range read
7
+ * can use, so an event that began before the window is invisible to it. A
8
+ * bounded lookback buys back the events people actually have — a meeting that
9
+ * started last night, a week away, a fortnight of leave — without turning a
10
+ * day's read into a read of the whole calendar. An all-day block longer than
11
+ * this that began before the window is the one thing it misses.
12
+ */
13
+ export declare const CALENDAR_WINDOW_LOOKBACK_DAYS = 31;
14
+ /** One occurrence, in the form a client renders. */
15
+ export interface CalendarInstance {
16
+ calendarId: string;
17
+ calendarObjectId: string;
18
+ recurrenceId: string;
19
+ icalUid: string;
20
+ summary: string;
21
+ /** ISO 8601 with the collection's own offset — what a client draws. */
22
+ start: string;
23
+ end: string;
24
+ /** ISO 8601 UTC instant — what sorts and compares. */
25
+ startAt: string;
26
+ endAt: string;
27
+ allDay: boolean;
28
+ status: CalendarObjectItem["status"];
29
+ transparency: CalendarObjectItem["transparency"];
30
+ zoneCertainty: CalendarObjectItem["zoneCertainty"];
31
+ etag: string;
32
+ hasRecurrence: boolean;
33
+ }
34
+ export interface CalendarWindowRepositories {
35
+ calendarObject: Pick<ICalendarObjectRepository, "find" | "listIncompleteExpansions">;
36
+ calendarEventIndex: Pick<ICalendarEventIndexRepository, "listByStartRange">;
37
+ }
38
+ export interface CalendarWindow {
39
+ /** ISO 8601 UTC instant, inclusive. */
40
+ from: string;
41
+ /** ISO 8601 UTC instant, exclusive. */
42
+ to: string;
43
+ }
44
+ /**
45
+ * Every occurrence in a window, across the collections given.
46
+ *
47
+ * Served from the stored occurrence index, except for the series the index
48
+ * does not reach: those are expanded live for this window and nothing is
49
+ * written to serve the read. A series is served from one source or the other,
50
+ * never merged from both, so an occurrence cannot appear twice.
51
+ */
52
+ export declare const listCalendarInstances: (repositories: CalendarWindowRepositories, collections: CalendarCollectionItem[], window: CalendarWindow) => Promise<CalendarInstance[]>;
53
+ /**
54
+ * The occurrences of one resource in a window, read from that resource alone:
55
+ * its own index rows where the index reaches the window, and a live expansion
56
+ * of it where it does not.
57
+ */
58
+ export declare const listObjectInstances: (repositories: {
59
+ calendarEventIndex: Pick<ICalendarEventIndexRepository, "listForObject">;
60
+ }, collection: CalendarCollectionItem, object: CalendarObjectItem, window: CalendarWindow) => Promise<CalendarInstance[]>;
61
+ /** A stretch of time somebody is busy in, as instants. */
62
+ export interface BusySpan {
63
+ startMs: number;
64
+ endMs: number;
65
+ }
66
+ /**
67
+ * Whether an occurrence consumes free/busy time. A cancelled event is not
68
+ * something to work around, and one marked TRANSP:TRANSPARENT was written
69
+ * precisely to say so.
70
+ */
71
+ export declare const isBusy: (instance: CalendarInstance) => boolean;
72
+ /**
73
+ * Collapses overlapping and touching spans into the stretches they cover.
74
+ *
75
+ * Touching counts: two meetings back to back are one stretch of being busy, and
76
+ * reporting them separately invites a caller to offer the zero-length gap
77
+ * between them as free.
78
+ */
79
+ export declare const mergeBusySpans: (spans: BusySpan[]) => BusySpan[];
80
+ /**
81
+ * The busy stretches in a window, merged across every collection given, and
82
+ * clipped to the window so a span never claims time outside what was asked
83
+ * for.
84
+ */
85
+ export declare const listBusySpans: (repositories: CalendarWindowRepositories, collections: CalendarCollectionItem[], window: CalendarWindow) => Promise<BusySpan[]>;
package/dist/window.js ADDED
@@ -0,0 +1,161 @@
1
+ import { CalendarEventStatus, CalendarTransparency } from "@remit/domain-enums";
2
+ import { expandCalendarWindow } from "./expand.js";
3
+ import { parseCalendar } from "./parse.js";
4
+ import { toOffsetIso, toUtcIso } from "./time.js";
5
+ /**
6
+ * How far before the window occurrences are read, so one that started earlier
7
+ * and runs into it is still returned.
8
+ *
9
+ * The occurrence index is keyed by start, which is the only key a range read
10
+ * can use, so an event that began before the window is invisible to it. A
11
+ * bounded lookback buys back the events people actually have — a meeting that
12
+ * started last night, a week away, a fortnight of leave — without turning a
13
+ * day's read into a read of the whole calendar. An all-day block longer than
14
+ * this that began before the window is the one thing it misses.
15
+ */
16
+ export const CALENDAR_WINDOW_LOOKBACK_DAYS = 31;
17
+ const LOOKBACK_MS = CALENDAR_WINDOW_LOOKBACK_DAYS * 24 * 60 * 60 * 1000;
18
+ const instanceOf = (collection, object, occurrence) => ({
19
+ calendarId: collection.calendarId,
20
+ calendarObjectId: object.calendarObjectId,
21
+ recurrenceId: occurrence.recurrenceId,
22
+ icalUid: object.icalUid,
23
+ summary: occurrence.summary,
24
+ start: toOffsetIso(Date.parse(occurrence.startAt), collection.timezone),
25
+ end: toOffsetIso(Date.parse(occurrence.endAt), collection.timezone),
26
+ startAt: occurrence.startAt,
27
+ endAt: occurrence.endAt,
28
+ allDay: occurrence.allDay,
29
+ status: occurrence.status,
30
+ transparency: occurrence.transparency,
31
+ zoneCertainty: object.zoneCertainty,
32
+ etag: object.etag,
33
+ hasRecurrence: object.hasRecurrence,
34
+ });
35
+ /**
36
+ * Every occurrence in a window, across the collections given.
37
+ *
38
+ * Served from the stored occurrence index, except for the series the index
39
+ * does not reach: those are expanded live for this window and nothing is
40
+ * written to serve the read. A series is served from one source or the other,
41
+ * never merged from both, so an occurrence cannot appear twice.
42
+ */
43
+ export const listCalendarInstances = async (repositories, collections, window) => {
44
+ const fromMs = Date.parse(window.from);
45
+ const toMs = Date.parse(window.to);
46
+ const lookbackFrom = toUtcIso(fromMs - LOOKBACK_MS);
47
+ const instances = [];
48
+ for (const collection of collections) {
49
+ // Both reads are bounded by the window: the occurrence rows that start in
50
+ // it, and the handful of series whose index stops short of it. Neither
51
+ // grows with the size of the calendar.
52
+ const live = await repositories.calendarObject.listIncompleteExpansions(collection.calendarId, window.to);
53
+ const liveIds = new Set(live.map((object) => object.calendarObjectId));
54
+ const rows = await repositories.calendarEventIndex.listByStartRange(collection.calendarId, lookbackFrom, window.to);
55
+ // One read per resource that actually appears in the window, cached across
56
+ // its own occurrences.
57
+ const byId = new Map();
58
+ for (const row of rows) {
59
+ if (liveIds.has(row.calendarObjectId))
60
+ continue;
61
+ if (!byId.has(row.calendarObjectId)) {
62
+ byId.set(row.calendarObjectId, await repositories.calendarObject.find(collection.calendarId, row.calendarObjectId));
63
+ }
64
+ const object = byId.get(row.calendarObjectId);
65
+ if (!object)
66
+ continue;
67
+ instances.push(instanceOf(collection, object, row));
68
+ }
69
+ for (const object of live) {
70
+ const parsed = await parseCalendar(object.icalData);
71
+ if (!parsed.ok) {
72
+ throw new Error(`stored calendar object ${object.calendarObjectId} no longer parses: ${parsed.error.message}`);
73
+ }
74
+ const occurrences = expandCalendarWindow(parsed.value, collection.timezone, { fromMs: fromMs - LOOKBACK_MS, toMs });
75
+ for (const occurrence of occurrences) {
76
+ instances.push(instanceOf(collection, object, occurrence));
77
+ }
78
+ }
79
+ }
80
+ return instances
81
+ .filter((instance) => overlapsWindow(instance, fromMs, toMs))
82
+ .sort((left, right) => left.startAt.localeCompare(right.startAt));
83
+ };
84
+ /**
85
+ * The occurrences of one resource in a window, read from that resource alone:
86
+ * its own index rows where the index reaches the window, and a live expansion
87
+ * of it where it does not.
88
+ */
89
+ export const listObjectInstances = async (repositories, collection, object, window) => {
90
+ const fromMs = Date.parse(window.from);
91
+ const toMs = Date.parse(window.to);
92
+ const indexReaches = object.expandedThrough === "" || object.expandedThrough >= window.to;
93
+ const occurrences = indexReaches
94
+ ? await repositories.calendarEventIndex.listForObject(collection.calendarId, object.calendarObjectId)
95
+ : await expandObject(object, collection.timezone, fromMs, toMs);
96
+ return occurrences
97
+ .map((occurrence) => instanceOf(collection, object, occurrence))
98
+ .filter((instance) => overlapsWindow(instance, fromMs, toMs))
99
+ .sort((left, right) => left.startAt.localeCompare(right.startAt));
100
+ };
101
+ const expandObject = async (object, timezone, fromMs, toMs) => {
102
+ const parsed = await parseCalendar(object.icalData);
103
+ if (!parsed.ok) {
104
+ throw new Error(`stored calendar object ${object.calendarObjectId} no longer parses: ${parsed.error.message}`);
105
+ }
106
+ return expandCalendarWindow(parsed.value, timezone, {
107
+ fromMs: fromMs - LOOKBACK_MS,
108
+ toMs,
109
+ });
110
+ };
111
+ const overlapsWindow = (instance, fromMs, toMs) => {
112
+ const startMs = Date.parse(instance.startAt);
113
+ if (startMs >= toMs)
114
+ return false;
115
+ if (startMs >= fromMs)
116
+ return true;
117
+ return Date.parse(instance.endAt) > fromMs;
118
+ };
119
+ /**
120
+ * Whether an occurrence consumes free/busy time. A cancelled event is not
121
+ * something to work around, and one marked TRANSP:TRANSPARENT was written
122
+ * precisely to say so.
123
+ */
124
+ export const isBusy = (instance) => instance.transparency === CalendarTransparency.Opaque &&
125
+ instance.status !== CalendarEventStatus.Cancelled;
126
+ /**
127
+ * Collapses overlapping and touching spans into the stretches they cover.
128
+ *
129
+ * Touching counts: two meetings back to back are one stretch of being busy, and
130
+ * reporting them separately invites a caller to offer the zero-length gap
131
+ * between them as free.
132
+ */
133
+ export const mergeBusySpans = (spans) => {
134
+ const sorted = [...spans].sort((left, right) => left.startMs - right.startMs);
135
+ const merged = [];
136
+ for (const span of sorted) {
137
+ if (span.endMs <= span.startMs)
138
+ continue;
139
+ const last = merged[merged.length - 1];
140
+ if (last && span.startMs <= last.endMs) {
141
+ last.endMs = Math.max(last.endMs, span.endMs);
142
+ continue;
143
+ }
144
+ merged.push({ ...span });
145
+ }
146
+ return merged;
147
+ };
148
+ /**
149
+ * The busy stretches in a window, merged across every collection given, and
150
+ * clipped to the window so a span never claims time outside what was asked
151
+ * for.
152
+ */
153
+ export const listBusySpans = async (repositories, collections, window) => {
154
+ const fromMs = Date.parse(window.from);
155
+ const toMs = Date.parse(window.to);
156
+ const instances = await listCalendarInstances(repositories, collections, window);
157
+ return mergeBusySpans(instances.filter(isBusy).map((instance) => ({
158
+ startMs: Math.max(Date.parse(instance.startAt), fromMs),
159
+ endMs: Math.min(Date.parse(instance.endAt), toMs),
160
+ })));
161
+ };
package/package.json CHANGED
@@ -1,37 +1,42 @@
1
1
  {
2
- "name": "@remit/calendar-service",
3
- "version": "0.0.17",
4
- "description": "iCalendar (RFC 5545) parsing, projection and recurrence expansion over the calendar store",
5
- "type": "module",
6
- "main": "src/index.ts",
7
- "types": "src/index.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./src/index.ts",
11
- "default": "./src/index.ts"
12
- },
13
- "./memory-store": {
14
- "types": "./src/memory-store.ts",
15
- "default": "./src/memory-store.ts"
16
- }
17
- },
18
- "scripts": {
19
- "test:typecheck": "tsgo --noEmit",
20
- "test:run": "node $NODE_TEST_FLAGS --import tsx --experimental-test-coverage --test-coverage-include='src/**' --test-coverage-exclude='src/**/*.test.ts' --test-coverage-exclude='src/memory-store.ts' --test-coverage-lines=90 --test 'src/**/*.test.ts'"
21
- },
22
- "dependencies": {
23
- "@remit/api-openapi-types": "*",
24
- "@remit/data-ports": "*",
25
- "@remit/domain-enums": "*",
26
- "ical.js": "^2.2.1"
27
- },
28
- "license": "MIT",
29
- "publishConfig": {
30
- "access": "public"
31
- },
32
- "repository": {
33
- "type": "git",
34
- "url": "git+https://github.com/remit-mail/reader.git",
35
- "directory": "packages/calendar-service"
36
- }
2
+ "name": "@remit/calendar-service",
3
+ "version": "0.0.19",
4
+ "description": "iCalendar (RFC 5545) parsing, projection and recurrence expansion over the calendar store",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./memory-store": {
14
+ "types": "./dist/memory-store.d.ts",
15
+ "default": "./dist/memory-store.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "prepack": "node ../../npm-scripts/pack-dist.mjs prepack",
20
+ "postpack": "node ../../npm-scripts/pack-dist.mjs postpack",
21
+ "test:typecheck": "tsgo --noEmit",
22
+ "test:run": "node $NODE_TEST_FLAGS --import tsx --experimental-test-coverage --test-coverage-include='src/**' --test-coverage-exclude='src/**/*.test.ts' --test-coverage-exclude='src/memory-store.ts' --test-coverage-lines=90 --test 'src/**/*.test.ts'"
23
+ },
24
+ "dependencies": {
25
+ "@remit/api-openapi-types": "*",
26
+ "@remit/data-ports": "*",
27
+ "@remit/domain-enums": "*",
28
+ "ical.js": "^2.2.1"
29
+ },
30
+ "license": "MIT",
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/remit-mail/reader.git",
37
+ "directory": "packages/calendar-service"
38
+ },
39
+ "files": [
40
+ "dist"
41
+ ]
37
42
  }