@pipobscure/ical 0.0.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.
Files changed (78) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +661 -0
  3. package/dist/alarm.d.ts +35 -0
  4. package/dist/alarm.d.ts.map +1 -0
  5. package/dist/alarm.js +87 -0
  6. package/dist/alarm.js.map +1 -0
  7. package/dist/calendar.d.ts +52 -0
  8. package/dist/calendar.d.ts.map +1 -0
  9. package/dist/calendar.js +121 -0
  10. package/dist/calendar.js.map +1 -0
  11. package/dist/component.d.ts +48 -0
  12. package/dist/component.d.ts.map +1 -0
  13. package/dist/component.js +170 -0
  14. package/dist/component.js.map +1 -0
  15. package/dist/event.d.ts +74 -0
  16. package/dist/event.d.ts.map +1 -0
  17. package/dist/event.js +263 -0
  18. package/dist/event.js.map +1 -0
  19. package/dist/freebusy.d.ts +45 -0
  20. package/dist/freebusy.d.ts.map +1 -0
  21. package/dist/freebusy.js +111 -0
  22. package/dist/freebusy.js.map +1 -0
  23. package/dist/index.d.ts +41 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +43 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/journal.d.ts +48 -0
  28. package/dist/journal.d.ts.map +1 -0
  29. package/dist/journal.js +148 -0
  30. package/dist/journal.js.map +1 -0
  31. package/dist/parse.d.ts +27 -0
  32. package/dist/parse.d.ts.map +1 -0
  33. package/dist/parse.js +140 -0
  34. package/dist/parse.js.map +1 -0
  35. package/dist/property-registry.d.ts +31 -0
  36. package/dist/property-registry.d.ts.map +1 -0
  37. package/dist/property-registry.js +83 -0
  38. package/dist/property-registry.js.map +1 -0
  39. package/dist/property.d.ts +36 -0
  40. package/dist/property.d.ts.map +1 -0
  41. package/dist/property.js +135 -0
  42. package/dist/property.js.map +1 -0
  43. package/dist/timezone.d.ts +55 -0
  44. package/dist/timezone.d.ts.map +1 -0
  45. package/dist/timezone.js +141 -0
  46. package/dist/timezone.js.map +1 -0
  47. package/dist/todo.d.ts +67 -0
  48. package/dist/todo.d.ts.map +1 -0
  49. package/dist/todo.js +220 -0
  50. package/dist/todo.js.map +1 -0
  51. package/dist/tokenize.d.ts +22 -0
  52. package/dist/tokenize.d.ts.map +1 -0
  53. package/dist/tokenize.js +95 -0
  54. package/dist/tokenize.js.map +1 -0
  55. package/dist/types.d.ts +100 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +3 -0
  58. package/dist/types.js.map +1 -0
  59. package/dist/value-types.d.ts +81 -0
  60. package/dist/value-types.d.ts.map +1 -0
  61. package/dist/value-types.js +445 -0
  62. package/dist/value-types.js.map +1 -0
  63. package/package.json +45 -0
  64. package/src/alarm.ts +105 -0
  65. package/src/calendar.ts +153 -0
  66. package/src/component.ts +193 -0
  67. package/src/event.ts +307 -0
  68. package/src/freebusy.ts +133 -0
  69. package/src/index.ts +85 -0
  70. package/src/journal.ts +174 -0
  71. package/src/parse.ts +166 -0
  72. package/src/property-registry.ts +124 -0
  73. package/src/property.ts +163 -0
  74. package/src/timezone.ts +169 -0
  75. package/src/todo.ts +253 -0
  76. package/src/tokenize.ts +99 -0
  77. package/src/types.ts +135 -0
  78. package/src/value-types.ts +498 -0
package/README.md ADDED
@@ -0,0 +1,661 @@
1
+ # @pipobscure/ical
2
+
3
+ A complete RFC 5545 iCalendar parser and serializer for Node.js, written in TypeScript.
4
+
5
+ **Tolerant on input.** Accepts real-world calendar data from Google Calendar, Apple Calendar, Microsoft Outlook, Thunderbird, and other producers that deviate from the specification — bare LF line endings, missing VCALENDAR wrappers, lowercase property names, non-standard parameters, folded lines in unusual places.
6
+
7
+ **Strict on output.** Generated iCalendar data uses canonical CRLF line endings, RFC 5545-compliant 75-octet UTF-8 line folding, proper parameter quoting, and required-property validation that throws before producing invalid data.
8
+
9
+ ---
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ npm install @pipobscure/ical
15
+ ```
16
+
17
+ Requires Node.js 22 or later. The package is pure ESM.
18
+
19
+ ---
20
+
21
+ ## Quick start
22
+
23
+ ### Parsing
24
+
25
+ ```typescript
26
+ import { parse } from '@pipobscure/ical';
27
+
28
+ const cal = parse(icsString);
29
+
30
+ for (const event of cal.events) {
31
+ console.log(event.summary);
32
+ console.log(event.dtstart); // ICalDateTime | ICalDate | null
33
+ console.log(event.dtend);
34
+ console.log(event.rrules); // ICalRecur[]
35
+ }
36
+ ```
37
+
38
+ ### Building
39
+
40
+ ```typescript
41
+ import { Calendar, Event, Alarm } from '@pipobscure/ical';
42
+
43
+ const cal = Calendar.create('-//My App//My App v1.0//EN');
44
+
45
+ const event = new Event();
46
+ event.uid = crypto.randomUUID();
47
+ event.dtstamp = new Date();
48
+ event.dtstart = new Date('2025-06-01T09:00:00Z');
49
+ event.dtend = new Date('2025-06-01T10:00:00Z');
50
+ event.summary = 'Team standup';
51
+ event.description = 'Daily sync with the engineering team.';
52
+
53
+ cal.addEvent(event);
54
+
55
+ console.log(cal.toString()); // RFC 5545-compliant ICS string
56
+ ```
57
+
58
+ ### Multiple calendars in a single file
59
+
60
+ ```typescript
61
+ import { parseAll } from '@pipobscure/ical';
62
+
63
+ const calendars = parseAll(icsString);
64
+ for (const cal of calendars) {
65
+ console.log(cal.prodid, cal.events.length);
66
+ }
67
+ ```
68
+
69
+ ---
70
+
71
+ ## API reference
72
+
73
+ ### Parsing functions
74
+
75
+ #### `parse(src: string): Calendar`
76
+
77
+ Parses an iCalendar string and returns a single `Calendar` object. If the input contains more than one `VCALENDAR` block, the first one is returned. If the input contains no `VCALENDAR` wrapper at all, the loose components are wrapped in a synthetic `Calendar`.
78
+
79
+ The parser is fully tolerant:
80
+ - Accepts `CRLF`, `LF`, or bare `CR` line endings.
81
+ - Handles RFC 5545 line folding with space or tab continuation.
82
+ - Normalises property names and parameter names to uppercase.
83
+ - Strips outer quotes from quoted parameter values.
84
+ - Stores unknown properties as plain text without failing.
85
+ - Stores unknown components as generic `Component` objects.
86
+
87
+ #### `parseAll(src: string): Calendar[]`
88
+
89
+ Parses an iCalendar string and returns all `VCALENDAR` blocks as an array. If no `VCALENDAR` blocks are present, returns a single-element array wrapping the loose content.
90
+
91
+ ---
92
+
93
+ ### Calendar
94
+
95
+ The top-level container component, corresponding to `VCALENDAR`.
96
+
97
+ #### Factory
98
+
99
+ ```typescript
100
+ Calendar.create(prodid: string, method?: string): Calendar
101
+ ```
102
+
103
+ Creates a new `Calendar` with `VERSION:2.0` already set. `method` is optional (e.g. `'REQUEST'`, `'REPLY'`, `'CANCEL'`).
104
+
105
+ #### Properties
106
+
107
+ | Property | Type | Notes |
108
+ |----------|------|-------|
109
+ | `version` | `string \| null` | Always `'2.0'` for new calendars |
110
+ | `prodid` | `string \| null` | Required for serialisation |
111
+ | `calscale` | `string \| null` | Defaults to `GREGORIAN` if omitted |
112
+ | `method` | `string \| null` | iTIP method |
113
+
114
+ #### Sub-component accessors
115
+
116
+ ```typescript
117
+ cal.events: Event[]
118
+ cal.todos: Todo[]
119
+ cal.journals: Journal[]
120
+ cal.freebusys: FreeBusy[]
121
+ cal.timezones: Timezone[]
122
+ ```
123
+
124
+ #### Add methods (fluent, return `this`)
125
+
126
+ ```typescript
127
+ cal.addEvent(event: Event): this
128
+ cal.addTodo(todo: Todo): this
129
+ cal.addJournal(journal: Journal): this
130
+ cal.addFreebusy(fb: FreeBusy): this
131
+ cal.addTimezone(tz: Timezone): this
132
+ ```
133
+
134
+ #### Lookup helpers
135
+
136
+ ```typescript
137
+ cal.getByUid(uid: string): Event | Todo | Journal | undefined
138
+ cal.getTimezone(tzid: string): Timezone | undefined
139
+ ```
140
+
141
+ #### Serialisation
142
+
143
+ ```typescript
144
+ cal.toString(): string
145
+ ```
146
+
147
+ Throws if `PRODID` or `VERSION` is missing.
148
+
149
+ ---
150
+
151
+ ### Event
152
+
153
+ Corresponds to `VEVENT`.
154
+
155
+ ```typescript
156
+ const event = new Event();
157
+ ```
158
+
159
+ #### Required properties
160
+
161
+ | Property | Type | Notes |
162
+ |----------|------|-------|
163
+ | `uid` | `string \| null` | Unique identifier; required for serialisation |
164
+ | `dtstamp` | `ICalDateTime \| null` | Accepts `Date` or `ICalDateTime` |
165
+
166
+ #### Date and time properties
167
+
168
+ | Property | Type |
169
+ |----------|------|
170
+ | `dtstart` | `ICalDateTime \| ICalDate \| null` |
171
+ | `dtend` | `ICalDateTime \| ICalDate \| null` |
172
+ | `duration` | `ICalDuration \| null` |
173
+ | `recurrenceId` | `ICalDateTime \| ICalDate \| null` |
174
+ | `created` | `ICalDateTime \| null` — accepts `Date` |
175
+ | `lastModified` | `ICalDateTime \| null` — accepts `Date` |
176
+
177
+ Setting a date-only `ICalDate` (or a plain object with `type: 'date'`) automatically adds the `VALUE=DATE` parameter on serialisation.
178
+
179
+ Setting a JavaScript `Date` converts it to a UTC `ICalDateTime`.
180
+
181
+ #### Descriptive properties
182
+
183
+ | Property | Type |
184
+ |----------|------|
185
+ | `summary` | `string \| null` |
186
+ | `description` | `string \| null` |
187
+ | `location` | `string \| null` |
188
+ | `url` | `string \| null` |
189
+ | `status` | `'TENTATIVE' \| 'CONFIRMED' \| 'CANCELLED' \| string \| null` |
190
+ | `transp` | `'OPAQUE' \| 'TRANSPARENT' \| string \| null` |
191
+ | `klass` | `'PUBLIC' \| 'PRIVATE' \| 'CONFIDENTIAL' \| string \| null` |
192
+ | `priority` | `number \| null` — 0–9 |
193
+ | `sequence` | `number \| null` |
194
+ | `geo` | `ICalGeo \| null` |
195
+ | `organizer` | `string \| null` — calendar address URI |
196
+
197
+ #### Multi-value properties
198
+
199
+ ```typescript
200
+ event.attendees: Property[]
201
+ event.addAttendee(calAddress: string, params?: Record<string, string>): this
202
+
203
+ event.categories: ICalValue[]
204
+ event.categories = ['Work', 'Meeting']; // setter accepts string[]
205
+
206
+ event.comments: Property[]
207
+ event.addComment(v: string): this
208
+
209
+ event.contacts: Property[]
210
+ event.addContact(v: string): this
211
+
212
+ event.exdates: ICalValue[]
213
+ event.addExdate(v: ICalDateTime | ICalDate): this
214
+
215
+ event.rdates: ICalValue[]
216
+ event.addRdate(v: ICalDateTime | ICalDate): this
217
+
218
+ event.rrules: ICalRecur[]
219
+ event.addRrule(v: ICalRecur): this
220
+
221
+ event.alarms: Alarm[]
222
+ event.addAlarm(alarm: Alarm): this
223
+ ```
224
+
225
+ #### Serialisation
226
+
227
+ Throws if `UID` or `DTSTAMP` is missing.
228
+
229
+ ---
230
+
231
+ ### Todo
232
+
233
+ Corresponds to `VTODO`.
234
+
235
+ ```typescript
236
+ const todo = new Todo();
237
+ ```
238
+
239
+ Same required properties as `Event` (`uid`, `dtstamp`). Date and time properties:
240
+
241
+ | Property | Type |
242
+ |----------|------|
243
+ | `dtstart` | `ICalDateTime \| ICalDate \| null` |
244
+ | `due` | `ICalDateTime \| ICalDate \| null` |
245
+ | `completed` | `ICalDateTime \| null` — only DateTime, not date-only |
246
+ | `duration` | `ICalDuration \| null` — mutually exclusive with `due` |
247
+
248
+ Additional descriptive properties: `summary`, `description`, `location`, `status` (`NEEDS-ACTION` / `COMPLETED` / `IN-PROCESS` / `CANCELLED`), `priority`, `percentComplete` (0–100), `sequence`, `klass`, `url`, `geo`, `organizer`, `created`, `lastModified`.
249
+
250
+ Multi-value: `attendees`, `categories`, `rrules`, `alarms` — same API as Event.
251
+
252
+ Serialisation throws if both `DUE` and `DURATION` are present.
253
+
254
+ ---
255
+
256
+ ### Journal
257
+
258
+ Corresponds to `VJOURNAL`.
259
+
260
+ ```typescript
261
+ const journal = new Journal();
262
+ ```
263
+
264
+ Required: `uid`, `dtstamp`. Date properties: `dtstart`. Multiple descriptions are supported:
265
+
266
+ ```typescript
267
+ journal.descriptions: Property[]
268
+ journal.addDescription(v: string): this
269
+ ```
270
+
271
+ Other properties: `summary`, `status` (`DRAFT` / `FINAL` / `CANCELLED`), `klass`, `url`, `organizer`, `sequence`, `created`, `lastModified`. Multi-value: `attendees`, `categories`, `rrules`.
272
+
273
+ ---
274
+
275
+ ### FreeBusy
276
+
277
+ Corresponds to `VFREEBUSY`.
278
+
279
+ ```typescript
280
+ const fb = new FreeBusy();
281
+ ```
282
+
283
+ Required: `uid`, `dtstamp`. Span: `dtstart`, `dtend` (both `ICalDateTime` only, not date-only). Participants: `organizer`, `attendees`, `addAttendee()`. Other: `url`, `comment`.
284
+
285
+ #### FREEBUSY periods
286
+
287
+ ```typescript
288
+ fb.freebusyProperties: Property[] // all FREEBUSY properties with their FBTYPE params
289
+ fb.periods: ICalPeriod[] // flat list of all period values
290
+
291
+ fb.addFreebusy(periods: ICalPeriod[], fbtype?: string): this
292
+ // fbtype defaults to 'BUSY'. Common values: 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE'
293
+ ```
294
+
295
+ ---
296
+
297
+ ### Timezone
298
+
299
+ Corresponds to `VTIMEZONE`.
300
+
301
+ ```typescript
302
+ const tz = new Timezone();
303
+ tz.tzid = 'America/New_York';
304
+ ```
305
+
306
+ Properties: `tzid` (required), `tzurl`, `lastModified`.
307
+
308
+ ```typescript
309
+ tz.standardRules: TimezoneRule[]
310
+ tz.daylightRules: TimezoneRule[]
311
+ tz.addStandard(rule: Standard): this
312
+ tz.addDaylight(rule: Daylight): this
313
+ ```
314
+
315
+ #### Standard and Daylight rules
316
+
317
+ Both `Standard` and `Daylight` extend `TimezoneRule`:
318
+
319
+ ```typescript
320
+ const std = new Standard();
321
+ std.dtstart = { type: 'date-time', year: 1970, month: 1, day: 1, hour: 2, minute: 0, second: 0, utc: false };
322
+ std.tzoffsetfrom = { sign: '+', hours: 5, minutes: 0 };
323
+ std.tzoffsetto = { sign: '-', hours: 5, minutes: 0 };
324
+ std.tzname = 'EST';
325
+ std.rrule = RECUR.parse('FREQ=YEARLY;BYDAY=1SU;BYMONTH=11');
326
+ ```
327
+
328
+ `TimezoneRule` properties: `dtstart`, `tzoffsetfrom`, `tzoffsetto`, `tzname`, `rrule`.
329
+
330
+ Serialisation throws if `TZID` is missing or if there are no `STANDARD`/`DAYLIGHT` sub-components.
331
+
332
+ ---
333
+
334
+ ### Alarm
335
+
336
+ Corresponds to `VALARM`.
337
+
338
+ ```typescript
339
+ const alarm = new Alarm();
340
+ alarm.action = 'DISPLAY';
341
+ alarm.trigger = DURATION.parse('-PT15M'); // 15 minutes before
342
+ alarm.description = 'Reminder';
343
+ ```
344
+
345
+ Properties: `action` (required), `trigger` (required — `ICalDuration` or `ICalDateTime`), `description`, `summary`, `repeat`, `duration`. Multi-value: `attendees`, `addAttendee()`.
346
+
347
+ Serialisation throws if `ACTION` or `TRIGGER` is missing.
348
+
349
+ ---
350
+
351
+ ### Component (base class)
352
+
353
+ All component classes extend `Component`. The base class is available for advanced use and for working with unknown component types returned by the parser.
354
+
355
+ ```typescript
356
+ // Property access
357
+ comp.getProperty(name: string): Property | undefined
358
+ comp.getProperties(name: string): Property[]
359
+ comp.getValue(name: string): ICalValue | null | undefined
360
+ comp.getValues(name: string): ICalValue[]
361
+
362
+ // Property mutation
363
+ comp.setProperty(name: string, value: ICalValue, params?: Record<string, string>): void
364
+ comp.appendProperty(name: string, value: ICalValue, params?: Record<string, string>): void
365
+ comp.addProperty(prop: Property): void
366
+ comp.removeProperty(name: string): void
367
+
368
+ // Sub-components
369
+ comp.addComponent(comp: Component): void
370
+ comp.getComponents(type: string): Component[]
371
+
372
+ comp.toString(): string
373
+ ```
374
+
375
+ ---
376
+
377
+ ### Property
378
+
379
+ A parsed iCalendar property with name, value, and parameters.
380
+
381
+ ```typescript
382
+ class Property {
383
+ readonly name: string
384
+ readonly value: ICalValue | readonly ICalValue[]
385
+ readonly params: Readonly<Record<string, string | readonly string[]>>
386
+ readonly rawValue: string
387
+
388
+ get scalar: ICalValue | null // first value, or null if empty
389
+ get list: ICalValue[] // always an array
390
+ get text: string | null // scalar cast to string
391
+ get number: number | null // scalar cast to number
392
+ get boolean: boolean | null // scalar cast to boolean
393
+
394
+ toContentLine(): string // full RFC 5545 content line
395
+ }
396
+ ```
397
+
398
+ Factory function for advanced use:
399
+
400
+ ```typescript
401
+ parseProperty(
402
+ name: string,
403
+ rawValue: string,
404
+ params: Readonly<Record<string, string | readonly string[]>>
405
+ ): Property
406
+ ```
407
+
408
+ ---
409
+
410
+ ### Value types
411
+
412
+ All structured iCalendar values are represented as plain objects with a discriminating `type` field. The full union type is:
413
+
414
+ ```typescript
415
+ type ICalValue = string | number | boolean | ICalStructured
416
+ ```
417
+
418
+ where `ICalStructured` is:
419
+
420
+ ```typescript
421
+ type ICalStructured =
422
+ | ICalDate
423
+ | ICalDateTime
424
+ | ICalTime
425
+ | ICalDuration
426
+ | ICalPeriod
427
+ | ICalRecur
428
+ | ICalUtcOffset
429
+ | ICalGeo
430
+ | ICalBinary
431
+ ```
432
+
433
+ #### ICalDate
434
+
435
+ ```typescript
436
+ interface ICalDate {
437
+ type: 'date';
438
+ readonly year: number;
439
+ readonly month: number; // 1-12
440
+ readonly day: number; // 1-31
441
+ }
442
+ ```
443
+
444
+ #### ICalDateTime
445
+
446
+ ```typescript
447
+ interface ICalDateTime {
448
+ type: 'date-time';
449
+ readonly year: number;
450
+ readonly month: number;
451
+ readonly day: number;
452
+ readonly hour: number;
453
+ readonly minute: number;
454
+ readonly second: number;
455
+ readonly utc: boolean; // true if trailing 'Z'
456
+ readonly tzid?: string; // from TZID parameter
457
+ }
458
+ ```
459
+
460
+ #### ICalTime
461
+
462
+ ```typescript
463
+ interface ICalTime {
464
+ type: 'time';
465
+ readonly hour: number;
466
+ readonly minute: number;
467
+ readonly second: number;
468
+ readonly utc: boolean;
469
+ }
470
+ ```
471
+
472
+ #### ICalDuration
473
+
474
+ ```typescript
475
+ interface ICalDuration {
476
+ type: 'duration';
477
+ readonly negative: boolean;
478
+ readonly weeks?: number;
479
+ readonly days?: number;
480
+ readonly hours?: number;
481
+ readonly minutes?: number;
482
+ readonly seconds?: number;
483
+ }
484
+ ```
485
+
486
+ #### ICalPeriod
487
+
488
+ ```typescript
489
+ interface ICalPeriod {
490
+ type: 'period';
491
+ readonly start: ICalDateTime;
492
+ readonly end: ICalDateTime | ICalDuration;
493
+ }
494
+ ```
495
+
496
+ #### ICalRecur
497
+
498
+ ```typescript
499
+ type RecurFreq = 'SECONDLY' | 'MINUTELY' | 'HOURLY' | 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY';
500
+ type Weekday = 'SU' | 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA';
501
+
502
+ interface ByDayRule {
503
+ readonly day: Weekday;
504
+ readonly ordwk?: number; // e.g. 2MO means the second Monday
505
+ }
506
+
507
+ interface ICalRecur {
508
+ type: 'recur';
509
+ readonly freq: RecurFreq;
510
+ readonly until?: ICalDateTime | ICalDate;
511
+ readonly count?: number;
512
+ readonly interval?: number;
513
+ readonly bysecond?: readonly number[];
514
+ readonly byminute?: readonly number[];
515
+ readonly byhour?: readonly number[];
516
+ readonly byday?: readonly ByDayRule[];
517
+ readonly bymonthday?: readonly number[];
518
+ readonly byyearday?: readonly number[];
519
+ readonly byweekno?: readonly number[];
520
+ readonly bymonth?: readonly number[];
521
+ readonly bysetpos?: readonly number[];
522
+ readonly wkst?: Weekday;
523
+ }
524
+ ```
525
+
526
+ #### ICalUtcOffset
527
+
528
+ ```typescript
529
+ interface ICalUtcOffset {
530
+ type: 'utc-offset';
531
+ readonly sign: '+' | '-';
532
+ readonly hours: number;
533
+ readonly minutes: number;
534
+ readonly seconds?: number;
535
+ }
536
+ ```
537
+
538
+ #### ICalGeo
539
+
540
+ ```typescript
541
+ interface ICalGeo {
542
+ type: 'geo';
543
+ readonly latitude: number;
544
+ readonly longitude: number;
545
+ }
546
+ ```
547
+
548
+ #### ICalBinary
549
+
550
+ ```typescript
551
+ interface ICalBinary {
552
+ type: 'binary';
553
+ readonly data: Uint8Array;
554
+ }
555
+ ```
556
+
557
+ ---
558
+
559
+ ### Value codecs
560
+
561
+ Each RFC 5545 value type has a codec with `parse` and `serialize` methods. Codecs are available as named exports and via the `CODECS` lookup map.
562
+
563
+ ```typescript
564
+ import { TEXT, BOOLEAN, INTEGER, FLOAT,
565
+ URI, CAL_ADDRESS, BINARY,
566
+ UTC_OFFSET, DATE, DATE_TIME, TIME,
567
+ DURATION, PERIOD, RECUR, GEO, CODECS } from '@pipobscure/ical';
568
+
569
+ // Direct codec use
570
+ const dt = DATE_TIME.parse('20250101T100000Z');
571
+ const str = DATE_TIME.serialize(dt); // '20250101T100000Z'
572
+
573
+ const dur = DURATION.parse('-PT1H30M');
574
+ const secs = DURATION.toSeconds(dur); // -5400
575
+
576
+ const recur = RECUR.parse('FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=10');
577
+ const rule = RECUR.serialize(recur);
578
+
579
+ // Codec lookup by RFC 5545 value type name
580
+ const codec = CODECS['DATE-TIME'];
581
+ ```
582
+
583
+ #### Helper methods on DATE and DATE_TIME
584
+
585
+ ```typescript
586
+ DATE.fromDate(d: Date): ICalDate
587
+ DATE.toDate(val: ICalDate): Date
588
+
589
+ DATE_TIME.fromDate(d: Date, tzid?: string): ICalDateTime
590
+ DATE_TIME.toDate(val: ICalDateTime): Date
591
+ ```
592
+
593
+ #### Helper method on DURATION
594
+
595
+ ```typescript
596
+ DURATION.toSeconds(val: ICalDuration): number
597
+ ```
598
+
599
+ ---
600
+
601
+ ## Line folding
602
+
603
+ The serialiser folds long lines at 75 UTF-8 octets as required by RFC 5545. Folded continuation lines begin with a single space. Multi-byte UTF-8 characters and surrogate pairs are handled correctly — the fold never splits a multi-byte sequence.
604
+
605
+ When parsing, both space and tab continuation characters are accepted, and the continuation character is stripped from the unfolded value.
606
+
607
+ ---
608
+
609
+ ## TEXT escaping
610
+
611
+ The TEXT codec automatically escapes and unescapes the characters required by RFC 5545:
612
+
613
+ | Character | Escaped form |
614
+ |-----------|-------------|
615
+ | `\` (backslash) | `\\` |
616
+ | `;` (semicolon) | `\;` |
617
+ | `,` (comma) | `\,` |
618
+ | newline | `\n` |
619
+
620
+ ---
621
+
622
+ ## Error handling
623
+
624
+ The parser never throws on malformed input. Unknown properties and components are silently stored. Structural errors are represented as generic `Component` objects.
625
+
626
+ The serialiser throws `Error` when required properties are absent:
627
+
628
+ | Component | Required properties |
629
+ |-----------|-------------------|
630
+ | `Calendar` | `PRODID`, `VERSION` |
631
+ | `Event` | `UID`, `DTSTAMP` |
632
+ | `Todo` | `UID`, `DTSTAMP`; `DUE` and `DURATION` are mutually exclusive |
633
+ | `Journal` | `UID`, `DTSTAMP` |
634
+ | `FreeBusy` | `UID`, `DTSTAMP` |
635
+ | `Timezone` | `TZID`; at least one `STANDARD` or `DAYLIGHT` rule |
636
+ | `Alarm` | `ACTION`, `TRIGGER` |
637
+
638
+ ---
639
+
640
+ ## Vendor compatibility
641
+
642
+ The parser has been tested against real-world ICS files produced by:
643
+
644
+ - **Apple Calendar** — `X-WR-CALNAME`, `X-APPLE-STRUCTURED-LOCATION`, `X-APPLE-TRAVEL-ADVISORY`, date-only `VALUE=DATE` events
645
+ - **Google Calendar** — TZID parameters on RRULE/EXDATE, multiple RRULE properties, bare LF line endings
646
+ - **Microsoft Outlook** — `METHOD:REQUEST`, missing `DTSTAMP`, `X-MICROSOFT-*` properties, `ENCODING=QUOTED-PRINTABLE` parameter
647
+ - **Mozilla Thunderbird** — Folded `DESCRIPTION` lines, long UID strings, `X-MOZ-SNOOZE-TIME`, `X-MOZ-GENERATION`
648
+
649
+ ---
650
+
651
+ ## TypeScript
652
+
653
+ The package ships TypeScript declaration files alongside the compiled JavaScript. No `@types/` package is needed.
654
+
655
+ All exported types are pure interfaces and type aliases — there are no classes in the type exports, only in the value exports.
656
+
657
+ ---
658
+
659
+ ## License
660
+
661
+ MIT
@@ -0,0 +1,35 @@
1
+ /**
2
+ * VALARM component (RFC 5545 §3.6.6)
3
+ *
4
+ * ACTION is required. TRIGGER is required.
5
+ * Three alarm types: AUDIO, DISPLAY, EMAIL.
6
+ */
7
+ import { Component } from './component.js';
8
+ import { Property } from './property.js';
9
+ import type { ICalDuration, ICalDateTime } from './types.js';
10
+ export type AlarmAction = 'AUDIO' | 'DISPLAY' | 'EMAIL' | string;
11
+ export declare class Alarm extends Component {
12
+ constructor();
13
+ get action(): AlarmAction | null;
14
+ set action(v: AlarmAction);
15
+ /** TRIGGER: relative (DURATION) or absolute (DATE-TIME). */
16
+ get trigger(): ICalDuration | ICalDateTime | null;
17
+ set trigger(v: ICalDuration | ICalDateTime);
18
+ get description(): string | null;
19
+ set description(v: string);
20
+ get summary(): string | null;
21
+ set summary(v: string);
22
+ get repeat(): number | null;
23
+ set repeat(v: number);
24
+ get duration(): ICalDuration | null;
25
+ set duration(v: ICalDuration);
26
+ get attendees(): Property[];
27
+ addAttendee(calAddress: string, params?: Record<string, string>): this;
28
+ toString(): string;
29
+ static fromRaw(props: ReadonlyArray<{
30
+ name: string;
31
+ params: Record<string, string>;
32
+ value: string;
33
+ }>): Alarm;
34
+ }
35
+ //# sourceMappingURL=alarm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alarm.d.ts","sourceRoot":"","sources":["../src/alarm.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAiB,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE7D,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEjE,qBAAa,KAAM,SAAQ,SAAS;;IAOlC,IAAI,MAAM,IAAI,WAAW,GAAG,IAAI,CAE/B;IACD,IAAI,MAAM,CAAC,CAAC,EAAE,WAAW,EAExB;IAED,4DAA4D;IAC5D,IAAI,OAAO,IAAI,YAAY,GAAG,YAAY,GAAG,IAAI,CAQhD;IACD,IAAI,OAAO,CAAC,CAAC,EAAE,YAAY,GAAG,YAAY,EAIzC;IAID,IAAI,WAAW,IAAI,MAAM,GAAG,IAAI,CAE/B;IACD,IAAI,WAAW,CAAC,CAAC,EAAE,MAAM,EAExB;IAED,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,CAE3B;IACD,IAAI,OAAO,CAAC,CAAC,EAAE,MAAM,EAEpB;IAED,IAAI,MAAM,IAAI,MAAM,GAAG,IAAI,CAE1B;IACD,IAAI,MAAM,CAAC,CAAC,EAAE,MAAM,EAEnB;IAED,IAAI,QAAQ,IAAI,YAAY,GAAG,IAAI,CAKlC;IACD,IAAI,QAAQ,CAAC,CAAC,EAAE,YAAY,EAE3B;IAED,IAAI,SAAS,IAAI,QAAQ,EAAE,CAE1B;IAED,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,IAAI;IAOjE,QAAQ,IAAI,MAAM;IAQ3B,MAAM,CAAC,OAAO,CACZ,KAAK,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,GACpF,KAAK;CAOT"}