@dereekb/firebase 13.40.0 → 13.42.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.
Files changed (33) hide show
  1. package/eslint/index.esm.js +145 -170
  2. package/eslint/package.json +3 -3
  3. package/index.esm.js +2865 -551
  4. package/package.json +5 -5
  5. package/src/lib/common/firestore/snapshot/snapshot.field.d.ts +4 -0
  6. package/src/lib/common/storage/context.d.ts +9 -1
  7. package/src/lib/common/storage/driver/accessor.d.ts +12 -0
  8. package/src/lib/common/storage/index.d.ts +1 -0
  9. package/src/lib/common/storage/storage.url.d.ts +69 -0
  10. package/src/lib/model/calendar/calendar.action.d.ts +34 -0
  11. package/src/lib/model/calendar/calendar.api.d.ts +147 -0
  12. package/src/lib/model/calendar/calendar.api.error.d.ts +24 -0
  13. package/src/lib/model/calendar/calendar.d.ts +460 -0
  14. package/src/lib/model/calendar/calendar.expand.d.ts +95 -0
  15. package/src/lib/model/calendar/calendar.ics.d.ts +322 -0
  16. package/src/lib/model/calendar/calendar.id.d.ts +110 -0
  17. package/src/lib/model/calendar/calendar.processing.d.ts +77 -0
  18. package/src/lib/model/calendar/calendar.query.d.ts +76 -0
  19. package/src/lib/model/calendar/calendar.schedule.d.ts +82 -0
  20. package/src/lib/model/calendar/calendar.type.d.ts +188 -0
  21. package/src/lib/model/calendar/calendar.util.d.ts +485 -0
  22. package/src/lib/model/calendar/index.d.ts +12 -0
  23. package/src/lib/model/index.d.ts +1 -0
  24. package/src/lib/model/notification/notification.message.d.ts +83 -0
  25. package/src/lib/model/notification/notification.query.d.ts +41 -0
  26. package/src/lib/model/oidcmodel/oidcmodel.query.d.ts +36 -0
  27. package/src/lib/model/storagefile/storagefile.api.d.ts +23 -2
  28. package/src/lib/model/storagefile/storagefile.create.d.ts +14 -3
  29. package/src/lib/model/storagefile/storagefile.query.d.ts +36 -0
  30. package/src/lib/model/system/index.d.ts +1 -0
  31. package/src/lib/model/system/system.scheduler.d.ts +235 -0
  32. package/test/index.esm.js +24 -1
  33. package/test/package.json +6 -6
@@ -0,0 +1,460 @@
1
+ import { type FilterUniqueFunction, type Maybe, type Minutes, type NeedsSyncBoolean, type SortCompareFunction, type TimezoneString, type UnixDateTimeSecondsNumber, type WebsiteUrl } from '@dereekb/util';
2
+ import { type RRuleLines } from '@dereekb/date';
3
+ import { type GrantedReadRole, type GrantedUpdateRole } from '@dereekb/model';
4
+ import { AbstractFirestoreDocument, type CollectionReference, type FirestoreCollection, type FirestoreContext, type FirebaseAuthOwnershipKey, type FirestoreModelKey, type SavedToFirestoreIfTrue } from '../../common';
5
+ import { type CalendarEventId, type CalendarEventStatus, type CalendarExtensionData, type CalendarType } from './calendar.id';
6
+ import { type StorageFileId, type StorageFilePublicDownloadUrl } from '../storagefile';
7
+ /**
8
+ * @module calendar
9
+ *
10
+ * Defines the Calendar Firestore model: a calendar and ALL of its events stored compactly in a single
11
+ * document, published as an ".ics" file through the existing StorageFile processing machinery.
12
+ *
13
+ * **Why one document.** A downstream app reads the model directly and renders it, so what it shows is
14
+ * always current — it never waits on, or re-parses, the published ICS. The cost is a 1 MiB ceiling and a
15
+ * whole-array rewrite per edit, which is why growth is bounded by the {@link CalendarTypeConfig} retention
16
+ * policy and why this shape suits publish-oriented calendars rather than high-churn shared ones.
17
+ *
18
+ * **Publishing.** Writing a Calendar flags it with `s` (needs sync). A scheduled sweep prunes it, creates or
19
+ * re-flags the ICS StorageFile named by `isf`, and clears `s`. The StorageFile processing pipeline then
20
+ * renders and uploads the file, inheriting its retry / stuck-detection / cleanup behaviour, and sets `sat`
21
+ * on success. `StorageFileProcessingState.SUCCESS` therefore means "the published ICS matches this model".
22
+ *
23
+ * This is the same flow as StorageFileGroup → zip (`shouldRegenerate` flag → sweep → derived StorageFile →
24
+ * subtask processor → upload), which is the reference implementation it mirrors.
25
+ */
26
+ /**
27
+ * Model identity for the Calendar collection (collection name: `calendar`, prefix: `cal`).
28
+ */
29
+ export declare const calendarIdentity: import("../..").RootFirestoreModelIdentity<"calendar", "cal">;
30
+ /**
31
+ * A single non-recurring event embedded in a {@link Calendar}.
32
+ *
33
+ * Dates are stored as unix seconds rather than ISO strings, and the span is `{ startsAt, durationMinutes }`
34
+ * rather than a start/end pair: both halve the stored bytes, and `{ startsAt, duration }` is already the
35
+ * input shape of every `@dereekb/date` utility this model expands and emits through, so an end date would
36
+ * mean converting back on every expansion and every ICS emit.
37
+ *
38
+ * @dbxModelSubObject
39
+ */
40
+ export interface CalendarEventItem {
41
+ /**
42
+ * Identifier of the event, unique within its calendar. Stable across publishes.
43
+ *
44
+ * @dbxModelVariable eventId
45
+ */
46
+ id: CalendarEventId;
47
+ /**
48
+ * Key of the model this event was generated from, when it was generated from one.
49
+ *
50
+ * A TARGETING HANDLE, not an identity: it is what lets a producer find and replace exactly the events it
51
+ * owns (see `replaceCalendarEventItemsForModelKey()`) without tracking their generated ids. Several events
52
+ * may share one key -- a schedule that emits a recurrence plus a few one-offs is one model, many events.
53
+ *
54
+ * Deliberately NOT the UID source. `calendarToICalendar()` feeds {@link id} to the UID factory, so this
55
+ * field can be added to, or changed on, an already-published event without destabilising its UID. It is
56
+ * also never emitted to the ICS, which is why it is exempt from the SEQUENCE bump -- see
57
+ * `CALENDAR_EVENT_ITEM_CHANGE_IGNORED_FIELDS`.
58
+ *
59
+ * @dbxModelVariable modelKey
60
+ */
61
+ m?: Maybe<FirestoreModelKey>;
62
+ /**
63
+ * Instant the event starts at.
64
+ *
65
+ * For a recurring event this doubles as the recurrence's anchor — see {@link CalendarRecurringEventItem}.
66
+ *
67
+ * @dbxModelVariable startsAt
68
+ */
69
+ sa: Date;
70
+ /**
71
+ * Duration of the event in minutes.
72
+ *
73
+ * @dbxModelVariable durationMinutes
74
+ */
75
+ dur: Minutes;
76
+ /**
77
+ * True if the event occupies whole calendar days rather than an instant range.
78
+ *
79
+ * @dbxModelVariable allDay
80
+ */
81
+ ad?: Maybe<SavedToFirestoreIfTrue>;
82
+ /**
83
+ * Timezone the event's wall clock is anchored to. Defaults to the calendar's timezone.
84
+ *
85
+ * For a recurring event this doubles as the recurrence's timezone.
86
+ *
87
+ * @dbxModelVariable timezone
88
+ */
89
+ tz?: Maybe<TimezoneString>;
90
+ /**
91
+ * Display name of the event. Emitted as SUMMARY.
92
+ *
93
+ * @dbxModelVariable name
94
+ */
95
+ n: string;
96
+ /**
97
+ * Longer description of the event. Emitted as DESCRIPTION.
98
+ *
99
+ * @dbxModelVariable description
100
+ */
101
+ d?: Maybe<string>;
102
+ /**
103
+ * Location of the event. Emitted as LOCATION.
104
+ *
105
+ * @dbxModelVariable location
106
+ */
107
+ l?: Maybe<string>;
108
+ /**
109
+ * Website for the event. Emitted as URL.
110
+ *
111
+ * @dbxModelVariable url
112
+ */
113
+ u?: Maybe<WebsiteUrl>;
114
+ /**
115
+ * Status of the event. Emitted as STATUS.
116
+ *
117
+ * CANCELLED is a tombstone: it is how the feed tells a client that already holds the event that it was
118
+ * removed. Retention is what eventually drops the tombstone.
119
+ *
120
+ * @dbxModelVariable status
121
+ */
122
+ st?: Maybe<CalendarEventStatus>;
123
+ /**
124
+ * Revision counter. Emitted as SEQUENCE.
125
+ *
126
+ * Subscribers compare it against the copy they hold to decide whether a same-UID event is newer, so it is
127
+ * bumped on every semantic change to an already-published event.
128
+ *
129
+ * @dbxModelVariable sequence
130
+ */
131
+ q?: Maybe<number>;
132
+ /**
133
+ * Categories of the event. Emitted as CATEGORIES.
134
+ *
135
+ * @dbxModelVariable categories
136
+ */
137
+ ca?: Maybe<string[]>;
138
+ /**
139
+ * Extension data emitted as "X-" properties on this event's VEVENT.
140
+ *
141
+ * @dbxModelVariable extensionData
142
+ */
143
+ x?: Maybe<CalendarExtensionData>;
144
+ /**
145
+ * Created at date.
146
+ *
147
+ * @dbxModelVariable createdAt
148
+ */
149
+ cat: Date;
150
+ /**
151
+ * Updated at date.
152
+ *
153
+ * @dbxModelVariable updatedAt
154
+ */
155
+ uat: Date;
156
+ }
157
+ /**
158
+ * A recurring event embedded in a {@link Calendar}.
159
+ *
160
+ * The recurrence fields are jointly required or jointly absent, so `extends` makes that a type-level
161
+ * invariant rather than four `Maybe<>` fields plus a runtime guard. The two kinds live in two separate
162
+ * arrays because their retention rules are structurally different (a one-off is pruned on its own end
163
+ * instant, a recurrence on the series' end) and because the ICS mapper genuinely forks between them.
164
+ *
165
+ * There is deliberately NO `recurrenceStartsAt` / `recurrenceTimezone`: the base `sa` IS the recurrence's
166
+ * start and the base `tz` IS its timezone, which makes the mapping to {@link ModelRecurrenceInfo} total and
167
+ * lossless in both directions.
168
+ *
169
+ * @dbxModelSubObject
170
+ */
171
+ export interface CalendarRecurringEventItem extends CalendarEventItem {
172
+ /**
173
+ * The recurrence rule, in the workspace's compact newline-joined storage form.
174
+ *
175
+ * NOTE: this KEEPS its "RRULE:" prefix and may carry EXDATE lines, so it cannot be handed to
176
+ * {@link ICalendarRecurrence.rules} directly — see `iCalendarRecurrenceForRRuleLines()`.
177
+ *
178
+ * @dbxModelVariable recurrenceRule
179
+ */
180
+ rr: RRuleLines;
181
+ /**
182
+ * Instant the final occurrence of the series ends at, when the series ends.
183
+ *
184
+ * @dbxModelVariable recurrenceEndsAt
185
+ */
186
+ rea?: Maybe<Date>;
187
+ /**
188
+ * True if the series never ends. A forever recurrence is never pruned.
189
+ *
190
+ * @dbxModelVariable recurrenceForever
191
+ */
192
+ rfe?: Maybe<SavedToFirestoreIfTrue>;
193
+ /**
194
+ * Occurrences excluded from the series, as unix seconds.
195
+ *
196
+ * Stored raw because no unix-seconds ARRAY snapshot field exists; `calendarEventItemExceptionDateSet()`
197
+ * builds the DateSet the expansion wants.
198
+ *
199
+ * @dbxModelVariable recurrenceExceptionDates
200
+ */
201
+ rex?: Maybe<UnixDateTimeSecondsNumber[]>;
202
+ }
203
+ /**
204
+ * Creates the comparison that orders calendar event items ascending by their start instant.
205
+ *
206
+ * The stored arrays are always in chronological order, which is what lets retention drop the oldest items
207
+ * with a `slice` instead of a sort.
208
+ *
209
+ * A factory rather than a constant because it is used for both item types, and a
210
+ * `SortCompareFunction<CalendarEventItem>` does not satisfy a `SortCompareFunction<CalendarRecurringEventItem>`.
211
+ *
212
+ * @returns The ascending-by-start comparison.
213
+ *
214
+ * @__NO_SIDE_EFFECTS__
215
+ */
216
+ export declare function calendarEventItemsSortFunction<T extends CalendarEventItem>(): SortCompareFunction<T>;
217
+ /**
218
+ * Creates the filter that keeps only the last entry carrying a given {@link CalendarEventId}.
219
+ *
220
+ * @returns The unique-by-id filter.
221
+ *
222
+ * @__NO_SIDE_EFFECTS__
223
+ */
224
+ export declare function calendarEventItemsFilterUniqueFunction<T extends CalendarEventItem>(): FilterUniqueFunction<T, CalendarEventId>;
225
+ /**
226
+ * The converter fields shared by {@link CalendarEventItem} and {@link CalendarRecurringEventItem}.
227
+ *
228
+ * Every optional field either uses an `optional*` factory or a `dontStoreIf`, so an absent field costs
229
+ * nothing in the stored document.
230
+ */
231
+ export declare const calendarEventItemFields: {
232
+ id: import("../..").FirestoreModelFieldMapFunctionsConfig<string, string>;
233
+ m: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<string>, Maybe<string>>;
234
+ sa: import("../..").FirestoreModelFieldMapFunctionsConfig<Date, number>;
235
+ dur: import("../..").FirestoreModelFieldMapFunctionsConfig<number, number>;
236
+ ad: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<boolean>, Maybe<boolean>>;
237
+ tz: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<string>, Maybe<string>>;
238
+ n: import("../..").FirestoreModelFieldMapFunctionsConfig<string, string>;
239
+ d: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<string>, Maybe<string>>;
240
+ l: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<string>, Maybe<string>>;
241
+ u: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<string>, Maybe<string>>;
242
+ st: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<CalendarEventStatus>, Maybe<CalendarEventStatus>>;
243
+ q: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<number>, Maybe<number>>;
244
+ ca: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<string[]>, Maybe<string[]>>;
245
+ x: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<Readonly<Record<string, string>>>, Maybe<Readonly<Record<string, string>>>>;
246
+ cat: import("../..").FirestoreModelFieldMapFunctionsConfig<Date, number>;
247
+ uat: import("../..").FirestoreModelFieldMapFunctionsConfig<Date, number>;
248
+ };
249
+ /**
250
+ * Firestore sub-object converter for a {@link CalendarEventItem}.
251
+ */
252
+ export declare const calendarEventItem: import("../..").FirestoreSubObjectFieldMapFunctionsConfig<CalendarEventItem, Partial<import("@dereekb/util").ReplaceType<CalendarEventItem, import("@dereekb/util").MaybeMap<object>, any>>>;
253
+ /**
254
+ * Firestore sub-object converter for a {@link CalendarRecurringEventItem}.
255
+ */
256
+ export declare const calendarRecurringEventItem: import("../..").FirestoreSubObjectFieldMapFunctionsConfig<CalendarRecurringEventItem, Partial<import("@dereekb/util").ReplaceType<CalendarRecurringEventItem, import("@dereekb/util").MaybeMap<object>, any>>>;
257
+ /**
258
+ * A calendar and all of its events, stored in one document and published as an ".ics" file.
259
+ *
260
+ * A Calendar that belongs to another model uses that model's two-way flat key as its document id, so the
261
+ * profile "pr/abc123" owns "cal/pr_abc123" — see {@link calendarIdForModel}. There is no `modelKey` field:
262
+ * the id IS the association.
263
+ *
264
+ * `s` / `sat` / `isf` mirror {@link StorageFileGroup}'s `s` / `zat` / `zsf` exactly, and `o` drives
265
+ * `resourceIsOwnedByAuthOwnershipKey()` in the security rules identically to `sf` / `sfg`.
266
+ *
267
+ * @dbxModel
268
+ * @dbxModelRead owner
269
+ */
270
+ export interface Calendar {
271
+ /**
272
+ * The kind of calendar this is, resolving its retention policy and ICS emission config.
273
+ *
274
+ * @dbxModelVariable calendarType
275
+ */
276
+ t: CalendarType;
277
+ /**
278
+ * Display name of the calendar. Emitted as NAME/X-WR-CALNAME.
279
+ *
280
+ * @dbxModelVariable name
281
+ */
282
+ n: string;
283
+ /**
284
+ * Description of the calendar. Emitted as DESCRIPTION/X-WR-CALDESC.
285
+ *
286
+ * @dbxModelVariable description
287
+ */
288
+ d?: Maybe<string>;
289
+ /**
290
+ * Default timezone of the calendar. Emitted as X-WR-TIMEZONE, and the fallback for an event with no `tz`.
291
+ *
292
+ * @dbxModelVariable timezone
293
+ */
294
+ tz: TimezoneString;
295
+ /**
296
+ * CSS3 color name for the calendar. Emitted as COLOR.
297
+ *
298
+ * @dbxModelVariable color
299
+ */
300
+ c?: Maybe<string>;
301
+ /**
302
+ * Ownership key, if applicable.
303
+ *
304
+ * Drives read access in the security rules, and the `read` + `rotate` grants in the app's Calendar model
305
+ * service — so this field, not the owning model's own role map, is the authoritative answer to "who may
306
+ * revoke this calendar's published feed url".
307
+ *
308
+ * Absent means there is no owner to grant to, leaving the calendar reachable by a sys-admin only.
309
+ *
310
+ * @dbxModelVariable ownerKey
311
+ */
312
+ o?: Maybe<FirebaseAuthOwnershipKey>;
313
+ /**
314
+ * The calendar's one-off events, ascending by start instant and unique by id.
315
+ *
316
+ * @dbxModelVariable events
317
+ */
318
+ e: CalendarEventItem[];
319
+ /**
320
+ * The calendar's recurring events, ascending by anchor instant and unique by id.
321
+ *
322
+ * @dbxModelVariable recurringEvents
323
+ */
324
+ r: CalendarRecurringEventItem[];
325
+ /**
326
+ * Extension data emitted as "X-" properties on the calendar's VCALENDAR.
327
+ *
328
+ * @dbxModelVariable extensionData
329
+ */
330
+ x?: Maybe<CalendarExtensionData>;
331
+ /**
332
+ * Created at date.
333
+ *
334
+ * @dbxModelVariable createdAt
335
+ */
336
+ cat: Date;
337
+ /**
338
+ * Updated at date. Moves on every content change.
339
+ *
340
+ * @dbxModelVariable updatedAt
341
+ */
342
+ uat: Date;
343
+ /**
344
+ * True if this Calendar should be swept and its published ICS regenerated.
345
+ *
346
+ * Cleared inside the sync transaction, mirroring the `re` flag of the zip flow.
347
+ *
348
+ * @dbxModelVariable needsSync
349
+ */
350
+ s?: Maybe<NeedsSyncBoolean>;
351
+ /**
352
+ * The last date the published ICS was successfully uploaded.
353
+ *
354
+ * Set ONLY by the processor's success path. `s === false && sat < uat` therefore means "queued, not yet
355
+ * published", which is exactly the state `flagStaleCalendarsForSync()` self-heals.
356
+ *
357
+ * @dbxModelVariable syncedAt
358
+ */
359
+ sat?: Maybe<Date>;
360
+ /**
361
+ * The last date this calendar's published ICS link was rotated.
362
+ *
363
+ * The sole input to the rotation throttle: rotation revokes a url that subscribers have already stored, so
364
+ * it is rate-limited rather than free. Both the server (which rejects an early rotation) and the client
365
+ * (which disables the action until the window passes) read the window from this one field via
366
+ * `calendarNextIcsRotateAt()`.
367
+ *
368
+ * Distinct from {@link Calendar.sat}, which moves on every successful publish — including the publish a
369
+ * rotation triggers, and every hourly sweep after it.
370
+ *
371
+ * Absent means the link has never been rotated, which never throttles.
372
+ *
373
+ * @dbxModelVariable icsRotatedAt
374
+ */
375
+ rat?: Maybe<Date>;
376
+ /**
377
+ * StorageFile that holds the published ICS for this calendar.
378
+ *
379
+ * @dbxModelVariable icsStorageFileId
380
+ */
381
+ isf?: Maybe<StorageFileId>;
382
+ /**
383
+ * The permanent, anonymously-readable URL the published ICS is served from.
384
+ *
385
+ * Written ONLY by the processor's success path, alongside `isf` and `sat`, so it always names the object
386
+ * whose bytes actually landed. Absent means "not yet published" — which is also the state a link rotation
387
+ * leaves behind until the replacement ICS uploads.
388
+ *
389
+ * Stored rather than recomputed client-side because the host differs between the emulator and production,
390
+ * and the object path is keyed by the ICS StorageFile's own id.
391
+ *
392
+ * TREAT AS A BEARER CREDENTIAL: anyone holding it reads the calendar until the link is rotated.
393
+ *
394
+ * @dbxModelVariable icsUrl
395
+ */
396
+ iu?: Maybe<StorageFilePublicDownloadUrl>;
397
+ }
398
+ /**
399
+ * Permission roles for Calendar operations.
400
+ *
401
+ * - `read` is the owner's grant to render the model directly instead of the published .ics.
402
+ * - `rotate` is the owner's capability to revoke the published feed url. A capability rather than a verb,
403
+ * exactly as {@link StorageFileGroupRoles} models `regenerate`.
404
+ * - `sync` is the publish-side role held by the scheduled sweep.
405
+ *
406
+ * `update` is representable and deliberately granted to nobody: every Calendar write has to carry `s` or the
407
+ * publish sweep silently strands the feed, so generic writes stay server-only.
408
+ */
409
+ export type CalendarRoles = GrantedReadRole | GrantedUpdateRole | 'rotate' | 'sync';
410
+ /**
411
+ * Firestore document wrapper for a {@link Calendar}.
412
+ *
413
+ * Provides a convenience getter to infer the related model key from the calendar's own id.
414
+ */
415
+ export declare class CalendarDocument extends AbstractFirestoreDocument<Calendar, CalendarDocument, typeof calendarIdentity> {
416
+ get modelIdentity(): import("../..").RootFirestoreModelIdentity<"calendar", "cal">;
417
+ get calendarRelatedModelKey(): string;
418
+ }
419
+ /**
420
+ * Snapshot converter for {@link Calendar} documents, including both embedded event arrays.
421
+ */
422
+ export declare const calendarConverter: import("../..").SnapshotConverterFunctions<Calendar, Partial<import("@dereekb/util").ReplaceType<Calendar, import("@dereekb/util").MaybeMap<object>, any>>>;
423
+ /**
424
+ * Returns the raw Firestore CollectionReference for the Calendar collection.
425
+ *
426
+ * @param context - The Firestore context to use.
427
+ * @returns The CollectionReference for Calendar documents.
428
+ */
429
+ export declare function calendarCollectionReference(context: FirestoreContext): CollectionReference<Calendar>;
430
+ /**
431
+ * Typed FirestoreCollection for {@link Calendar} documents.
432
+ */
433
+ export type CalendarFirestoreCollection = FirestoreCollection<Calendar, CalendarDocument>;
434
+ /**
435
+ * Creates a fully configured {@link CalendarFirestoreCollection} with snapshot conversion and document factory.
436
+ *
437
+ * @param firestoreContext - The Firestore context to use.
438
+ * @returns A configured CalendarFirestoreCollection.
439
+ *
440
+ * @example
441
+ * ```ts
442
+ * const collection = calendarFirestoreCollection(firestoreContext);
443
+ * const doc = collection.documentAccessor().loadDocumentForId(calendarIdForModel(profileDocument.key));
444
+ * ```
445
+ */
446
+ export declare function calendarFirestoreCollection(firestoreContext: FirestoreContext): CalendarFirestoreCollection;
447
+ /**
448
+ * Abstract base providing access to the Calendar Firestore collection.
449
+ *
450
+ * Implement this in your app module to wire up the collection for dependency injection.
451
+ *
452
+ * @dbxModelGroup Calendar
453
+ */
454
+ export declare abstract class CalendarFirestoreCollections {
455
+ abstract readonly calendarCollection: CalendarFirestoreCollection;
456
+ }
457
+ /**
458
+ * Union of all Calendar-related model identity types.
459
+ */
460
+ export type CalendarTypes = typeof calendarIdentity;
@@ -0,0 +1,95 @@
1
+ import { type Maybe, type Minutes, type TimezoneString } from '@dereekb/util';
2
+ import { type DateRange } from '@dereekb/date';
3
+ import { type Calendar, type CalendarEventItem, type CalendarRecurringEventItem } from './calendar';
4
+ import { type CalendarOccurrenceKey } from './calendar.id';
5
+ /**
6
+ * @module calendar.expand
7
+ *
8
+ * The ONE occurrence expansion, shared by ICS generation and by the future dbx-calendar adapter.
9
+ *
10
+ * Deliberately free of any Angular dependency, and deliberately not duplicated on the server: an adapter
11
+ * that expanded recurrences differently from the publisher would render a calendar that disagrees with the
12
+ * ".ics" the same model produced.
13
+ *
14
+ * The adapter is then one line —
15
+ * `{ id: o.key, start: o.startsAt, end: o.endsAt, allDay: o.allDay, title: o.item.n, meta: o }` — which is a
16
+ * `CalendarEvent<CalendarEventOccurrence>` and feeds straight into `prepareAndSortCalendarEvents()`.
17
+ */
18
+ /**
19
+ * A single resolved occurrence of a calendar event.
20
+ *
21
+ * A one-off event yields exactly one occurrence; a recurring event yields one per instance of its series
22
+ * within the expansion range.
23
+ */
24
+ export interface CalendarEventOccurrence {
25
+ /**
26
+ * The event this occurrence came from.
27
+ */
28
+ readonly item: CalendarEventItem | CalendarRecurringEventItem;
29
+ /**
30
+ * Stable identifier for this occurrence.
31
+ *
32
+ * The event's id for a one-off; the id plus the occurrence's unix seconds for a recurrence. Stability is
33
+ * what keeps a published VEVENT's UID the same across republishes, which is what makes a subscriber update
34
+ * the event it holds rather than create a duplicate.
35
+ */
36
+ readonly key: CalendarOccurrenceKey;
37
+ readonly startsAt: Date;
38
+ readonly endsAt: Date;
39
+ readonly durationMinutes: Minutes;
40
+ readonly allDay: boolean;
41
+ readonly timezone: TimezoneString;
42
+ readonly recurring: boolean;
43
+ }
44
+ /**
45
+ * Builds the {@link CalendarOccurrenceKey} for a single occurrence of a recurring event.
46
+ *
47
+ * @param item - The recurring event.
48
+ * @param startsAt - The occurrence's start instant.
49
+ * @returns The occurrence key.
50
+ *
51
+ * @__NO_SIDE_EFFECTS__
52
+ */
53
+ export declare function calendarRecurringEventOccurrenceKey(item: Pick<CalendarEventItem, 'id'>, startsAt: Date): CalendarOccurrenceKey;
54
+ /**
55
+ * Input for {@link expandCalendarEvents}.
56
+ */
57
+ export interface ExpandCalendarEventsInput {
58
+ readonly calendar: Pick<Calendar, 'tz' | 'e' | 'r'>;
59
+ /**
60
+ * The window to expand within.
61
+ *
62
+ * REQUIRED: a forever recurrence has no other bound, and expanding one without a range throws.
63
+ */
64
+ readonly range: DateRange;
65
+ /**
66
+ * Whether one-off events are included. Defaults to true.
67
+ */
68
+ readonly includeOneOffEvents?: Maybe<boolean>;
69
+ /**
70
+ * Whether recurring events are included. Defaults to true.
71
+ */
72
+ readonly includeRecurringEvents?: Maybe<boolean>;
73
+ /**
74
+ * Caps how many occurrences a single recurring event may contribute.
75
+ */
76
+ readonly maxOccurrencesPerEvent?: Maybe<number>;
77
+ }
78
+ /**
79
+ * Expands a calendar's events into concrete occurrences within a range.
80
+ *
81
+ * One-off events are included when their span OVERLAPS the range. Recurring events are expanded through
82
+ * `DateRRuleUtility`, whose `exclude` slot consumes the event's `rex` exception dates with no extra code;
83
+ * note that it matches an occurrence by its START, so a recurrence instance that began before the range and
84
+ * runs into it is not included.
85
+ *
86
+ * @param input - The calendar, the range, and optional filters.
87
+ * @returns The occurrences, ascending by start instant and unique by key.
88
+ * @throws {Error} If a forever recurrence is expanded without a range.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * const occurrences = expandCalendarEvents({ calendar, range: { start: from, end: to } });
93
+ * ```
94
+ */
95
+ export declare function expandCalendarEvents(input: ExpandCalendarEventsInput): CalendarEventOccurrence[];