@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.
- package/eslint/index.esm.js +145 -170
- package/eslint/package.json +3 -3
- package/index.esm.js +2865 -551
- package/package.json +5 -5
- package/src/lib/common/firestore/snapshot/snapshot.field.d.ts +4 -0
- package/src/lib/common/storage/context.d.ts +9 -1
- package/src/lib/common/storage/driver/accessor.d.ts +12 -0
- package/src/lib/common/storage/index.d.ts +1 -0
- package/src/lib/common/storage/storage.url.d.ts +69 -0
- package/src/lib/model/calendar/calendar.action.d.ts +34 -0
- package/src/lib/model/calendar/calendar.api.d.ts +147 -0
- package/src/lib/model/calendar/calendar.api.error.d.ts +24 -0
- package/src/lib/model/calendar/calendar.d.ts +460 -0
- package/src/lib/model/calendar/calendar.expand.d.ts +95 -0
- package/src/lib/model/calendar/calendar.ics.d.ts +322 -0
- package/src/lib/model/calendar/calendar.id.d.ts +110 -0
- package/src/lib/model/calendar/calendar.processing.d.ts +77 -0
- package/src/lib/model/calendar/calendar.query.d.ts +76 -0
- package/src/lib/model/calendar/calendar.schedule.d.ts +82 -0
- package/src/lib/model/calendar/calendar.type.d.ts +188 -0
- package/src/lib/model/calendar/calendar.util.d.ts +485 -0
- package/src/lib/model/calendar/index.d.ts +12 -0
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/notification/notification.message.d.ts +83 -0
- package/src/lib/model/notification/notification.query.d.ts +41 -0
- package/src/lib/model/oidcmodel/oidcmodel.query.d.ts +36 -0
- package/src/lib/model/storagefile/storagefile.api.d.ts +23 -2
- package/src/lib/model/storagefile/storagefile.create.d.ts +14 -3
- package/src/lib/model/storagefile/storagefile.query.d.ts +36 -0
- package/src/lib/model/system/index.d.ts +1 -0
- package/src/lib/model/system/system.scheduler.d.ts +235 -0
- package/test/index.esm.js +24 -1
- 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[];
|