@dereekb/firebase 13.41.0 → 13.43.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 +7207 -2997
- 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/formspace/formspace.access.d.ts +139 -0
- package/src/lib/model/formspace/formspace.action.d.ts +34 -0
- package/src/lib/model/formspace/formspace.api.d.ts +216 -0
- package/src/lib/model/formspace/formspace.api.error.d.ts +71 -0
- package/src/lib/model/formspace/formspace.d.ts +390 -0
- package/src/lib/model/formspace/formspace.id.d.ts +60 -0
- package/src/lib/model/formspace/formspace.permission.d.ts +47 -0
- package/src/lib/model/formspace/formspace.processing.d.ts +86 -0
- package/src/lib/model/formspace/formspace.query.d.ts +110 -0
- package/src/lib/model/formspace/formspace.task.d.ts +75 -0
- package/src/lib/model/formspace/formspace.type.d.ts +267 -0
- package/src/lib/model/formspace/formspace.upload.d.ts +204 -0
- package/src/lib/model/formspace/formspace.util.d.ts +348 -0
- package/src/lib/model/formspace/index.d.ts +13 -0
- package/src/lib/model/index.d.ts +2 -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 +42 -2
- package/src/lib/model/storagefile/storagefile.create.d.ts +14 -3
- package/src/lib/model/storagefile/storagefile.file.d.ts +42 -2
- package/src/lib/model/storagefile/storagefile.query.d.ts +36 -0
- package/src/lib/model/storagefile/storagefile.upload.d.ts +30 -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,188 @@
|
|
|
1
|
+
import { type Days, type Maybe, type Milliseconds, type Minutes } from '@dereekb/util';
|
|
2
|
+
import { type CalendarType } from './calendar.id';
|
|
3
|
+
/**
|
|
4
|
+
* @module calendar.type
|
|
5
|
+
*
|
|
6
|
+
* The {@link CalendarType} registry: the per-type retention policy and ICS emission config an app declares
|
|
7
|
+
* once and the publish pipeline reads on every sweep.
|
|
8
|
+
*
|
|
9
|
+
* The type and the factory live here, in the model folder rather than in `firebase-server`, so a client-side
|
|
10
|
+
* ICS preview or the future dbx-calendar adapter reads the SAME expansion window the server publishes with.
|
|
11
|
+
* Only the service INSTANCE is constructed server-side, as a NestJS provider — the same split
|
|
12
|
+
* {@link AppNotificationTemplateTypeInfoRecordService} uses.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* How a {@link Calendar}'s recurring events are represented in its published ICS.
|
|
16
|
+
*
|
|
17
|
+
* - `rrule` emits one VEVENT per series carrying an RRULE, which is compact and lets a subscriber handle
|
|
18
|
+
* recurrence natively.
|
|
19
|
+
* - `expand` emits one VEVENT per occurrence within the expansion window, which is what
|
|
20
|
+
* `@dereekb/date`'s iCalendar layer documents as its preferred authoring path: a published feed gets no
|
|
21
|
+
* chance to correct a client's misinterpretation of a rule before the next poll, 12-24 hours later.
|
|
22
|
+
*/
|
|
23
|
+
export type CalendarIcsRecurrenceMode = 'rrule' | 'expand';
|
|
24
|
+
/**
|
|
25
|
+
* Retention and ICS-emission configuration for a single {@link CalendarType}.
|
|
26
|
+
*
|
|
27
|
+
* Retention is what keeps the embedded-event design inside Firestore's 1 MiB document ceiling without any
|
|
28
|
+
* manual gardening: every sweep prunes before it publishes.
|
|
29
|
+
*/
|
|
30
|
+
export interface CalendarTypeConfig {
|
|
31
|
+
/**
|
|
32
|
+
* The type this configuration applies to.
|
|
33
|
+
*/
|
|
34
|
+
readonly calendarType: CalendarType;
|
|
35
|
+
/**
|
|
36
|
+
* Human-readable name of the type, for tooling and logs.
|
|
37
|
+
*/
|
|
38
|
+
readonly name?: Maybe<string>;
|
|
39
|
+
/**
|
|
40
|
+
* How many days of already-ended one-off events to keep. Defaults to {@link DEFAULT_CALENDAR_RETAIN_PAST_EVENT_DAYS}.
|
|
41
|
+
*/
|
|
42
|
+
readonly retainPastEventDays?: Maybe<Days>;
|
|
43
|
+
/**
|
|
44
|
+
* Maximum number of stored events, counting one-off and recurring events together.
|
|
45
|
+
* Defaults to {@link DEFAULT_CALENDAR_MAX_EVENTS}.
|
|
46
|
+
*/
|
|
47
|
+
readonly maxEvents?: Maybe<number>;
|
|
48
|
+
/**
|
|
49
|
+
* Whether an ended recurrence is eligible for pruning at all. Defaults to true.
|
|
50
|
+
*
|
|
51
|
+
* A forever recurrence is never pruned regardless of this value.
|
|
52
|
+
*/
|
|
53
|
+
readonly pruneEndedRecurrences?: Maybe<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* How many days of already-ended recurrences to keep. Defaults to the resolved {@link retainPastEventDays}.
|
|
56
|
+
*/
|
|
57
|
+
readonly retainEndedRecurrenceDays?: Maybe<Days>;
|
|
58
|
+
/**
|
|
59
|
+
* How recurring events are emitted. Defaults to {@link DEFAULT_CALENDAR_ICS_RECURRENCE_MODE}.
|
|
60
|
+
*/
|
|
61
|
+
readonly icsRecurrenceMode?: Maybe<CalendarIcsRecurrenceMode>;
|
|
62
|
+
/**
|
|
63
|
+
* How far back the ICS expansion window reaches, used only in `expand` mode.
|
|
64
|
+
* Defaults to {@link DEFAULT_CALENDAR_ICS_EXPANSION_PAST_DAYS}.
|
|
65
|
+
*/
|
|
66
|
+
readonly icsExpansionPastDays?: Maybe<Days>;
|
|
67
|
+
/**
|
|
68
|
+
* How far forward the ICS expansion window reaches, used only in `expand` mode.
|
|
69
|
+
* Defaults to {@link DEFAULT_CALENDAR_ICS_EXPANSION_FUTURE_DAYS}.
|
|
70
|
+
*/
|
|
71
|
+
readonly icsExpansionFutureDays?: Maybe<Days>;
|
|
72
|
+
/**
|
|
73
|
+
* Emitted as REFRESH-INTERVAL / X-PUBLISHED-TTL.
|
|
74
|
+
*
|
|
75
|
+
* ADVISORY ONLY: Google re-fetches a subscribed feed every ~12-24 hours regardless.
|
|
76
|
+
*/
|
|
77
|
+
readonly refreshInterval?: Maybe<Minutes>;
|
|
78
|
+
/**
|
|
79
|
+
* How long a Calendar of this type may go without a successful publish before the backstop sweep re-flags
|
|
80
|
+
* it. Defaults to {@link DEFAULT_CALENDAR_RESYNC_INTERVAL}.
|
|
81
|
+
*
|
|
82
|
+
* This is also what keeps an `expand`-mode calendar from sliding off the end of its expansion window.
|
|
83
|
+
*/
|
|
84
|
+
readonly resyncInterval?: Maybe<Milliseconds>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Default for {@link CalendarTypeConfig.retainPastEventDays}.
|
|
88
|
+
*/
|
|
89
|
+
export declare const DEFAULT_CALENDAR_RETAIN_PAST_EVENT_DAYS: Days;
|
|
90
|
+
/**
|
|
91
|
+
* Default for {@link CalendarTypeConfig.maxEvents}.
|
|
92
|
+
*
|
|
93
|
+
* At roughly 300 stored bytes per item this leaves about 6x headroom under Firestore's 1 MiB ceiling.
|
|
94
|
+
*/
|
|
95
|
+
export declare const DEFAULT_CALENDAR_MAX_EVENTS = 500;
|
|
96
|
+
/**
|
|
97
|
+
* Default for {@link CalendarTypeConfig.icsRecurrenceMode}.
|
|
98
|
+
*/
|
|
99
|
+
export declare const DEFAULT_CALENDAR_ICS_RECURRENCE_MODE: CalendarIcsRecurrenceMode;
|
|
100
|
+
/**
|
|
101
|
+
* Default for {@link CalendarTypeConfig.icsExpansionPastDays}.
|
|
102
|
+
*/
|
|
103
|
+
export declare const DEFAULT_CALENDAR_ICS_EXPANSION_PAST_DAYS: Days;
|
|
104
|
+
/**
|
|
105
|
+
* Default for {@link CalendarTypeConfig.icsExpansionFutureDays}.
|
|
106
|
+
*/
|
|
107
|
+
export declare const DEFAULT_CALENDAR_ICS_EXPANSION_FUTURE_DAYS: Days;
|
|
108
|
+
/**
|
|
109
|
+
* Default for {@link CalendarTypeConfig.resyncInterval}: seven days.
|
|
110
|
+
*/
|
|
111
|
+
export declare const DEFAULT_CALENDAR_RESYNC_INTERVAL: Milliseconds;
|
|
112
|
+
/**
|
|
113
|
+
* The {@link CalendarType} of {@link DEFAULT_CALENDAR_TYPE_CONFIG}, used for a type the app never registered.
|
|
114
|
+
*/
|
|
115
|
+
export declare const UNKNOWN_CALENDAR_TYPE: CalendarType;
|
|
116
|
+
/**
|
|
117
|
+
* The configuration applied to a Calendar whose type the app did not register.
|
|
118
|
+
*
|
|
119
|
+
* An unregistered type falls back rather than throwing on purpose: a scheduled sweep over every calendar in
|
|
120
|
+
* the app must not be taken down by one badly-typed document.
|
|
121
|
+
*/
|
|
122
|
+
export declare const DEFAULT_CALENDAR_TYPE_CONFIG: CalendarTypeConfig;
|
|
123
|
+
/**
|
|
124
|
+
* Record of {@link CalendarTypeConfig} keyed by {@link CalendarType}.
|
|
125
|
+
*/
|
|
126
|
+
export type CalendarTypeConfigRecord = Record<CalendarType, CalendarTypeConfig>;
|
|
127
|
+
/**
|
|
128
|
+
* Creates a {@link CalendarTypeConfigRecord} from an array of configs.
|
|
129
|
+
*
|
|
130
|
+
* @param configs - The configs to index.
|
|
131
|
+
* @returns A record keyed by calendar type.
|
|
132
|
+
* @throws {Error} When two configs declare the same {@link CalendarType}.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* const record = calendarTypeConfigRecord([{ calendarType: 'demo_profile', maxEvents: 100 }]);
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export declare function calendarTypeConfigRecord(configs: CalendarTypeConfig[]): CalendarTypeConfigRecord;
|
|
140
|
+
/**
|
|
141
|
+
* Runtime service for resolving a {@link CalendarTypeConfig} from a {@link CalendarType}.
|
|
142
|
+
*
|
|
143
|
+
* Built from a {@link CalendarTypeConfigRecord} via {@link appCalendarTypeConfigService}.
|
|
144
|
+
*/
|
|
145
|
+
export declare abstract class AppCalendarTypeConfigService {
|
|
146
|
+
/**
|
|
147
|
+
* All registered configs for this app.
|
|
148
|
+
*/
|
|
149
|
+
abstract readonly appCalendarTypeConfigRecord: CalendarTypeConfigRecord;
|
|
150
|
+
/**
|
|
151
|
+
* Returns the config for the given type, falling back to the service's default when it is not registered.
|
|
152
|
+
*
|
|
153
|
+
* @param calendarType - The type to look up.
|
|
154
|
+
*/
|
|
155
|
+
abstract configForCalendarType(calendarType: CalendarType): CalendarTypeConfig;
|
|
156
|
+
/**
|
|
157
|
+
* Returns every registered {@link CalendarType}.
|
|
158
|
+
*
|
|
159
|
+
* This is what the backstop sweep iterates, since each type carries its own resync interval.
|
|
160
|
+
*/
|
|
161
|
+
abstract getAllKnownCalendarTypes(): CalendarType[];
|
|
162
|
+
/**
|
|
163
|
+
* Returns every registered {@link CalendarTypeConfig}.
|
|
164
|
+
*/
|
|
165
|
+
abstract getAllKnownCalendarTypeConfigs(): CalendarTypeConfig[];
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Reference to an {@link AppCalendarTypeConfigService} instance, for dependency injection.
|
|
169
|
+
*/
|
|
170
|
+
export interface AppCalendarTypeConfigServiceRef {
|
|
171
|
+
readonly appCalendarTypeConfigService: AppCalendarTypeConfigService;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Creates an {@link AppCalendarTypeConfigService} from the given record.
|
|
175
|
+
*
|
|
176
|
+
* @param appCalendarTypeConfigRecord - The complete calendar type registry for the application.
|
|
177
|
+
* @param defaultConfig - Config used for an unregistered type. Defaults to {@link DEFAULT_CALENDAR_TYPE_CONFIG}.
|
|
178
|
+
* @returns The service.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```ts
|
|
182
|
+
* const service = appCalendarTypeConfigService(calendarTypeConfigRecord(DEMO_CALENDAR_TYPE_CONFIGS));
|
|
183
|
+
* const config = service.configForCalendarType('demo_profile');
|
|
184
|
+
* ```
|
|
185
|
+
*
|
|
186
|
+
* @__NO_SIDE_EFFECTS__
|
|
187
|
+
*/
|
|
188
|
+
export declare function appCalendarTypeConfigService(appCalendarTypeConfigRecord: CalendarTypeConfigRecord, defaultConfig?: CalendarTypeConfig): AppCalendarTypeConfigService;
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { type ArrayOrValue, type Hours, type Maybe, type TimezoneString, unixDateTimeSecondsNumberFromDate } from '@dereekb/util';
|
|
2
|
+
import { type CalendarDate, DateSet, type ModelRecurrenceInfo } from '@dereekb/date';
|
|
3
|
+
import { type FirebaseAuthOwnershipKey, type FirestoreModelKey } from '../../common';
|
|
4
|
+
import { type Calendar, type CalendarEventItem, type CalendarRecurringEventItem } from './calendar';
|
|
5
|
+
import { type CalendarEventId, type CalendarExtensionData, type CalendarType } from './calendar.id';
|
|
6
|
+
import { type CalendarTypeConfig } from './calendar.type';
|
|
7
|
+
/**
|
|
8
|
+
* @module calendar.util
|
|
9
|
+
*
|
|
10
|
+
* The CALLER-FACING surface of the Calendar model: pure array operations and update TEMPLATES.
|
|
11
|
+
*
|
|
12
|
+
* Other models create and mutate a Calendar inside THEIR OWN transaction, holding the accessor and the
|
|
13
|
+
* document themselves. So this module ships no `upsert…` / `remove…` server actions — an action that opened
|
|
14
|
+
* its own transaction would either fight the caller's or force an awkward split write. It ships the
|
|
15
|
+
* `Pick<Calendar, …>` templates the caller merges into its own `create()` / `update()`, the same convention
|
|
16
|
+
* `markStorageFileForDeleteTemplate()` follows.
|
|
17
|
+
*
|
|
18
|
+
* The `s: true` invariant that makes the sweep correct is carried by {@link calendarTemplate} and
|
|
19
|
+
* {@link updateCalendarEventsTemplate}, so it remains impossible to mutate events and forget to flag the
|
|
20
|
+
* calendar for sync.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Returns the instant a one-off event ends at.
|
|
24
|
+
*
|
|
25
|
+
* @param item - The event.
|
|
26
|
+
* @returns The end instant.
|
|
27
|
+
*
|
|
28
|
+
* @__NO_SIDE_EFFECTS__
|
|
29
|
+
*/
|
|
30
|
+
export declare function calendarEventItemEndDate(item: CalendarEventItem): Date;
|
|
31
|
+
/**
|
|
32
|
+
* Returns the {@link CalendarDate} view of an event, which is the input shape every `@dereekb/date`
|
|
33
|
+
* expansion and iCalendar factory consumes.
|
|
34
|
+
*
|
|
35
|
+
* @param item - The event.
|
|
36
|
+
* @returns The calendar date.
|
|
37
|
+
*
|
|
38
|
+
* @__NO_SIDE_EFFECTS__
|
|
39
|
+
*/
|
|
40
|
+
export declare function calendarEventItemCalendarDate(item: CalendarEventItem): CalendarDate;
|
|
41
|
+
/**
|
|
42
|
+
* Returns the {@link DateSet} of a recurring event's excluded occurrences, which is exactly what
|
|
43
|
+
* `DateRRuleInstanceOptions.exclude` wants.
|
|
44
|
+
*
|
|
45
|
+
* @param item - The recurring event.
|
|
46
|
+
* @returns The excluded instants. Empty when the event excludes nothing.
|
|
47
|
+
*
|
|
48
|
+
* @__NO_SIDE_EFFECTS__
|
|
49
|
+
*/
|
|
50
|
+
export declare function calendarEventItemExceptionDateSet(item: Pick<CalendarRecurringEventItem, 'rex'>): DateSet;
|
|
51
|
+
/**
|
|
52
|
+
* Returns the timezone an event's wall clock is anchored to, falling back to the calendar's timezone.
|
|
53
|
+
*
|
|
54
|
+
* @param item - The event.
|
|
55
|
+
* @param calendarTimezone - The owning calendar's timezone.
|
|
56
|
+
* @returns The resolved timezone.
|
|
57
|
+
*
|
|
58
|
+
* @__NO_SIDE_EFFECTS__
|
|
59
|
+
*/
|
|
60
|
+
export declare function calendarEventItemTimezone(item: Pick<CalendarEventItem, 'tz'>, calendarTimezone: TimezoneString): TimezoneString;
|
|
61
|
+
/**
|
|
62
|
+
* Projects a recurring event onto the workspace's {@link ModelRecurrenceInfo} shape.
|
|
63
|
+
*
|
|
64
|
+
* The mapping is total and lossless because the event's base `sa` IS the recurrence's start and its base
|
|
65
|
+
* `tz` IS the recurrence's timezone — which is exactly why there is no separate `recurrenceStartsAt`.
|
|
66
|
+
*
|
|
67
|
+
* @param item - The recurring event.
|
|
68
|
+
* @param calendarTimezone - The owning calendar's timezone, used when the event carries none.
|
|
69
|
+
* @returns The recurrence info.
|
|
70
|
+
*
|
|
71
|
+
* @__NO_SIDE_EFFECTS__
|
|
72
|
+
*/
|
|
73
|
+
export declare function calendarRecurringEventItemModelRecurrenceInfo(item: CalendarRecurringEventItem, calendarTimezone: TimezoneString): ModelRecurrenceInfo;
|
|
74
|
+
/**
|
|
75
|
+
* The recurrence half of a {@link CalendarRecurringEventItem}, as produced from a {@link ModelRecurrenceInfo}.
|
|
76
|
+
*/
|
|
77
|
+
export type CalendarRecurringEventItemRecurrenceFields = Pick<CalendarRecurringEventItem, 'sa' | 'tz' | 'rr' | 'rea' | 'rfe'>;
|
|
78
|
+
/**
|
|
79
|
+
* Projects a {@link ModelRecurrenceInfo} back onto the recurrence fields of a recurring event.
|
|
80
|
+
*
|
|
81
|
+
* @param info - The recurrence info.
|
|
82
|
+
* @returns The recurrence fields, ready to merge into an event item.
|
|
83
|
+
*
|
|
84
|
+
* @__NO_SIDE_EFFECTS__
|
|
85
|
+
*/
|
|
86
|
+
export declare function calendarRecurringEventItemRecurrenceFields(info: ModelRecurrenceInfo): CalendarRecurringEventItemRecurrenceFields;
|
|
87
|
+
/**
|
|
88
|
+
* Configuration for {@link calendarTemplate}.
|
|
89
|
+
*/
|
|
90
|
+
export interface CalendarTemplateConfig {
|
|
91
|
+
readonly calendarType: CalendarType;
|
|
92
|
+
readonly name: string;
|
|
93
|
+
readonly timezone?: Maybe<TimezoneString>;
|
|
94
|
+
readonly description?: Maybe<string>;
|
|
95
|
+
readonly color?: Maybe<string>;
|
|
96
|
+
readonly ownerKey?: Maybe<FirebaseAuthOwnershipKey>;
|
|
97
|
+
readonly events?: Maybe<CalendarEventItem[]>;
|
|
98
|
+
readonly recurringEvents?: Maybe<CalendarRecurringEventItem[]>;
|
|
99
|
+
readonly extensionData?: Maybe<CalendarExtensionData>;
|
|
100
|
+
readonly now?: Maybe<Date>;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Builds the initial document data for a new Calendar.
|
|
104
|
+
*
|
|
105
|
+
* The caller creates the document itself, typically as
|
|
106
|
+
* `accessor.loadDocumentForId(calendarIdForModel(relatedModelKey)).create(calendarTemplate({ … }))`.
|
|
107
|
+
*
|
|
108
|
+
* `s` defaults to true, so a newly created Calendar is picked up by the very next sweep.
|
|
109
|
+
*
|
|
110
|
+
* @param config - The calendar's type, display metadata and initial events.
|
|
111
|
+
* @returns The Calendar data.
|
|
112
|
+
*
|
|
113
|
+
* @__NO_SIDE_EFFECTS__
|
|
114
|
+
*/
|
|
115
|
+
export declare function calendarTemplate(config: CalendarTemplateConfig): Calendar;
|
|
116
|
+
/**
|
|
117
|
+
* The fields {@link markCalendarForSyncTemplate} produces.
|
|
118
|
+
*/
|
|
119
|
+
export type MarkCalendarForSyncTemplate = Pick<Calendar, 's' | 'uat'>;
|
|
120
|
+
/**
|
|
121
|
+
* Builds the update that flags a Calendar for its next sync without touching its events.
|
|
122
|
+
*
|
|
123
|
+
* A caller that changed only display metadata (name, description, color) merges this.
|
|
124
|
+
*
|
|
125
|
+
* @param now - The update instant. Defaults to the current time.
|
|
126
|
+
* @returns The update fields.
|
|
127
|
+
*
|
|
128
|
+
* @__NO_SIDE_EFFECTS__
|
|
129
|
+
*/
|
|
130
|
+
export declare function markCalendarForSyncTemplate(now?: Maybe<Date>): MarkCalendarForSyncTemplate;
|
|
131
|
+
/**
|
|
132
|
+
* Where a Calendar sits in the publish pipeline, as derived from the calendar itself.
|
|
133
|
+
*
|
|
134
|
+
* The read-side counterpart of {@link markCalendarForSyncTemplate}: it decodes the `s` / `sat` / `uat`
|
|
135
|
+
* invariant the sweep and the ICS processor maintain, so a reader never has to re-derive it (and never
|
|
136
|
+
* mistakes a stale `sat` for "the published feed is current").
|
|
137
|
+
*/
|
|
138
|
+
export declare enum CalendarSyncState {
|
|
139
|
+
/**
|
|
140
|
+
* The calendar's content has moved and no sweep has claimed it yet.
|
|
141
|
+
*
|
|
142
|
+
* `s` is set by {@link calendarTemplate}, {@link updateCalendarEventsTemplate} and
|
|
143
|
+
* {@link markCalendarForSyncTemplate}, so this is the state every content change lands in.
|
|
144
|
+
*/
|
|
145
|
+
QUEUED = "queued",
|
|
146
|
+
/**
|
|
147
|
+
* A sweep cleared `s` but the ICS upload that writes `sat` has not landed yet.
|
|
148
|
+
*
|
|
149
|
+
* This is the `s === false && sat < uat` window the resync backstop self-heals, and it also covers a
|
|
150
|
+
* calendar whose first ICS has never published.
|
|
151
|
+
*/
|
|
152
|
+
PUBLISHING = "publishing",
|
|
153
|
+
/**
|
|
154
|
+
* The published ICS reflects the calendar's current content.
|
|
155
|
+
*/
|
|
156
|
+
SYNCED = "synced"
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The fields {@link calendarSyncState} reads.
|
|
160
|
+
*/
|
|
161
|
+
export type CalendarSyncStateInput = Pick<Calendar, 's' | 'sat' | 'uat'>;
|
|
162
|
+
/**
|
|
163
|
+
* Returns where a Calendar sits in the publish pipeline.
|
|
164
|
+
*
|
|
165
|
+
* `sat` ALONE is not "synced": it is the instant of the last successful upload, which says nothing about
|
|
166
|
+
* whether the content has moved since. Only `sat > uat` with the sync flag clear means the published feed
|
|
167
|
+
* matches the model.
|
|
168
|
+
*
|
|
169
|
+
* @param calendar - The calendar's sync flag, last publish instant and update instant.
|
|
170
|
+
* @returns The calendar's sync state.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* const isPublished = calendarSyncState(calendar) === CalendarSyncState.SYNCED;
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* @__NO_SIDE_EFFECTS__
|
|
178
|
+
*/
|
|
179
|
+
export declare function calendarSyncState(calendar: CalendarSyncStateInput): CalendarSyncState;
|
|
180
|
+
/**
|
|
181
|
+
* How long a caller must wait between rotations of a Calendar's published ICS link.
|
|
182
|
+
*
|
|
183
|
+
* Rotation is rate-limited rather than free because it is DESTRUCTIVE to subscribers: the old url stops
|
|
184
|
+
* working, and every calendar client already holding it breaks until its owner re-subscribes. A run of
|
|
185
|
+
* rotations would also leave a trail of orphaned ICS objects while each replacement uploads.
|
|
186
|
+
*
|
|
187
|
+
* Twelve hours is chosen against the subscriber refresh cadence rather than against server cost — Google
|
|
188
|
+
* re-reads a subscribed feed only every 8-24 hours, so rotating faster than that guarantees a window where
|
|
189
|
+
* the feed a subscriber holds is already dead and its replacement has not been fetched yet.
|
|
190
|
+
*
|
|
191
|
+
* Only the default. An app wanting a different cadence passes its own value to both the server (which
|
|
192
|
+
* enforces the window) and the client (which counts down to it); both sides must use the same value, or the
|
|
193
|
+
* UI will offer a rotation the server rejects.
|
|
194
|
+
*/
|
|
195
|
+
export declare const DEFAULT_CALENDAR_ICS_ROTATE_THROTTLE_HOURS: Hours;
|
|
196
|
+
/**
|
|
197
|
+
* The fields {@link calendarNextIcsRotateAt} reads.
|
|
198
|
+
*/
|
|
199
|
+
export type CalendarNextIcsRotateAtInput = Pick<Calendar, 'rat'>;
|
|
200
|
+
/**
|
|
201
|
+
* Input for {@link calendarNextIcsRotateAt}.
|
|
202
|
+
*/
|
|
203
|
+
export interface CalendarNextIcsRotateAtConfig {
|
|
204
|
+
/**
|
|
205
|
+
* The calendar's last rotation instant, if any.
|
|
206
|
+
*/
|
|
207
|
+
readonly calendar?: Maybe<CalendarNextIcsRotateAtInput>;
|
|
208
|
+
/**
|
|
209
|
+
* Overrides {@link DEFAULT_CALENDAR_ICS_ROTATE_THROTTLE_HOURS}.
|
|
210
|
+
*/
|
|
211
|
+
readonly throttleHours?: Maybe<Hours>;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The earliest time a Calendar's published ICS link may be rotated again.
|
|
215
|
+
*
|
|
216
|
+
* Both the server (which enforces the throttle) and the client (which disables the action until then) derive
|
|
217
|
+
* the window from the same stored instant, so the UI cannot offer a rotation the server would reject.
|
|
218
|
+
*
|
|
219
|
+
* @param config - The calendar, and optionally a throttle window to use instead of the default.
|
|
220
|
+
* @returns The time the next rotation is allowed, or undefined when the link has never been rotated.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* const nextRotateAt = calendarNextIcsRotateAt({ calendar });
|
|
225
|
+
* const isThrottled = nextRotateAt != null && isAfter(nextRotateAt, new Date());
|
|
226
|
+
* ```
|
|
227
|
+
*
|
|
228
|
+
* @__NO_SIDE_EFFECTS__
|
|
229
|
+
*/
|
|
230
|
+
export declare function calendarNextIcsRotateAt(config: CalendarNextIcsRotateAtConfig): Maybe<Date>;
|
|
231
|
+
/**
|
|
232
|
+
* Whether a Calendar's published ICS link is still inside its rotation throttle window.
|
|
233
|
+
*
|
|
234
|
+
* The predicate half of {@link calendarNextIcsRotateAt}, and the one the server rejects on. A calendar that
|
|
235
|
+
* has never been rotated is never throttled, which `isThrottled()` already encodes by treating an absent
|
|
236
|
+
* `lastRunAt` as expired.
|
|
237
|
+
*
|
|
238
|
+
* @param config - The calendar, and optionally a throttle window to use instead of the default.
|
|
239
|
+
* @param now - Overrides the current time.
|
|
240
|
+
* @returns True while another rotation would be rejected.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* if (isCalendarIcsRotateThrottled({ calendar })) {
|
|
245
|
+
* // the link was rotated too recently
|
|
246
|
+
* }
|
|
247
|
+
* ```
|
|
248
|
+
*
|
|
249
|
+
* @__NO_SIDE_EFFECTS__
|
|
250
|
+
*/
|
|
251
|
+
export declare function isCalendarIcsRotateThrottled(config: CalendarNextIcsRotateAtConfig, now?: Maybe<Date>): boolean;
|
|
252
|
+
/**
|
|
253
|
+
* A partial event item update. The id is required, since it is what an upsert matches on.
|
|
254
|
+
*/
|
|
255
|
+
export type CalendarEventItemUpdate<T extends CalendarEventItem = CalendarEventItem> = Partial<T> & Pick<CalendarEventItem, 'id'>;
|
|
256
|
+
/**
|
|
257
|
+
* Configuration for {@link upsertCalendarEventItems}.
|
|
258
|
+
*/
|
|
259
|
+
export interface UpsertCalendarEventItemsConfig {
|
|
260
|
+
/**
|
|
261
|
+
* The update instant, applied to `uat` (and `cat` for a newly inserted item). Defaults to the current time.
|
|
262
|
+
*/
|
|
263
|
+
readonly now?: Maybe<Date>;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Merges updates into an event array, inserting an item whose id is not present yet.
|
|
267
|
+
*
|
|
268
|
+
* An update that changes something a subscriber can observe bumps `q` (SEQUENCE) and moves `uat`; an update
|
|
269
|
+
* that changes nothing leaves the item, and therefore the published feed, byte-identical.
|
|
270
|
+
*
|
|
271
|
+
* @param items - The current items.
|
|
272
|
+
* @param updates - The partial updates, each carrying the id it applies to.
|
|
273
|
+
* @param config - Optional update instant.
|
|
274
|
+
* @returns A new array, sorted ascending by start instant and unique by id.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* const events = upsertCalendarEventItems(calendar.e, [{ id: 'a', n: 'Renamed' }]);
|
|
279
|
+
* ```
|
|
280
|
+
*
|
|
281
|
+
* @__NO_SIDE_EFFECTS__
|
|
282
|
+
*/
|
|
283
|
+
export declare function upsertCalendarEventItems<T extends CalendarEventItem>(items: T[], updates: CalendarEventItemUpdate<T>[], config?: Maybe<UpsertCalendarEventItemsConfig>): T[];
|
|
284
|
+
/**
|
|
285
|
+
* Configuration for {@link removeCalendarEventItems}.
|
|
286
|
+
*/
|
|
287
|
+
export interface RemoveCalendarEventItemsConfig {
|
|
288
|
+
/**
|
|
289
|
+
* If true, the items are SPLICED OUT rather than tombstoned.
|
|
290
|
+
*
|
|
291
|
+
* Only correct for an event that was never published: a subscriber that already holds the event has no way
|
|
292
|
+
* to learn it is gone once its VEVENT simply stops appearing in the feed.
|
|
293
|
+
*/
|
|
294
|
+
readonly hard?: Maybe<boolean>;
|
|
295
|
+
/**
|
|
296
|
+
* The update instant. Defaults to the current time.
|
|
297
|
+
*/
|
|
298
|
+
readonly now?: Maybe<Date>;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Removes events from an array.
|
|
302
|
+
*
|
|
303
|
+
* By default this TOMBSTONES: the item is marked `CANCELLED` and its `q` is bumped, which is the only way a
|
|
304
|
+
* published feed communicates a deletion to a client that already holds the event. Retention is what
|
|
305
|
+
* eventually drops the tombstone.
|
|
306
|
+
*
|
|
307
|
+
* @param items - The current items.
|
|
308
|
+
* @param ids - The ids to remove.
|
|
309
|
+
* @param config - Optional hard-remove flag and update instant.
|
|
310
|
+
* @returns A new array.
|
|
311
|
+
*
|
|
312
|
+
* @__NO_SIDE_EFFECTS__
|
|
313
|
+
*/
|
|
314
|
+
export declare function removeCalendarEventItems<T extends CalendarEventItem>(items: T[], ids: ArrayOrValue<CalendarEventId>, config?: Maybe<RemoveCalendarEventItemsConfig>): T[];
|
|
315
|
+
/**
|
|
316
|
+
* Finds a single event in a calendar by its id, looking in BOTH the one-off and the recurring arrays.
|
|
317
|
+
*
|
|
318
|
+
* The two arrays share one id space -- an id is unique within its calendar, not merely within its array --
|
|
319
|
+
* so a caller holding only an id (an emailed invite, a targeted update) has no way to know which array to
|
|
320
|
+
* look in, and no reason to care.
|
|
321
|
+
*
|
|
322
|
+
* @param calendar - The calendar to search.
|
|
323
|
+
* @param eventId - The id to find.
|
|
324
|
+
* @returns The matching event, or undefined.
|
|
325
|
+
*
|
|
326
|
+
* @__NO_SIDE_EFFECTS__
|
|
327
|
+
*/
|
|
328
|
+
export declare function calendarEventItemForId(calendar: Pick<Calendar, 'e' | 'r'>, eventId: CalendarEventId): Maybe<CalendarEventItem | CalendarRecurringEventItem>;
|
|
329
|
+
/**
|
|
330
|
+
* Returns the events that were generated from the given model key.
|
|
331
|
+
*
|
|
332
|
+
* @param items - The current items.
|
|
333
|
+
* @param modelKey - The key to match on.
|
|
334
|
+
* @returns The matching events, in their original order.
|
|
335
|
+
*
|
|
336
|
+
* @__NO_SIDE_EFFECTS__
|
|
337
|
+
*/
|
|
338
|
+
export declare function calendarEventItemsForModelKey<T extends CalendarEventItem>(items: T[], modelKey: FirestoreModelKey): T[];
|
|
339
|
+
/**
|
|
340
|
+
* Configuration for {@link replaceCalendarEventItemsForModelKey}.
|
|
341
|
+
*/
|
|
342
|
+
export interface ReplaceCalendarEventItemsForModelKeyConfig<T extends CalendarEventItem = CalendarEventItem> {
|
|
343
|
+
/**
|
|
344
|
+
* The key whose events are being replaced. Every item in {@link items} is stamped with it, so a caller
|
|
345
|
+
* cannot accidentally write events that the next replace would fail to find.
|
|
346
|
+
*/
|
|
347
|
+
readonly modelKey: FirestoreModelKey;
|
|
348
|
+
/**
|
|
349
|
+
* The COMPLETE desired set of events for this key. Anything previously carrying the key and absent here is
|
|
350
|
+
* removed.
|
|
351
|
+
*/
|
|
352
|
+
readonly items: CalendarEventItemUpdate<T>[];
|
|
353
|
+
/**
|
|
354
|
+
* If true, events dropped by the replacement are SPLICED OUT rather than tombstoned.
|
|
355
|
+
*
|
|
356
|
+
* Only correct when they were never published: a subscriber holding an event has no way to learn it is
|
|
357
|
+
* gone once its VEVENT simply stops appearing.
|
|
358
|
+
*/
|
|
359
|
+
readonly hard?: Maybe<boolean>;
|
|
360
|
+
/**
|
|
361
|
+
* The update instant. Defaults to the current time.
|
|
362
|
+
*/
|
|
363
|
+
readonly now?: Maybe<Date>;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Replaces the whole set of events belonging to one model key, leaving every other event untouched.
|
|
367
|
+
*
|
|
368
|
+
* This is the primitive a producer needs when it regenerates its events from source: it cannot know which
|
|
369
|
+
* generated ids it wrote last time, only which model they came from. Matching on {@link CalendarEventItem.m}
|
|
370
|
+
* rather than on `id` is what makes "publish the current state of this model" a single idempotent call.
|
|
371
|
+
*
|
|
372
|
+
* An event that survives the replacement is UPSERTED, so it keeps its `cat` and only bumps `q` / `uat` if
|
|
373
|
+
* something a subscriber can observe actually changed. An event that disappears is tombstoned by default.
|
|
374
|
+
*
|
|
375
|
+
* @param items - The current items.
|
|
376
|
+
* @param config - The model key, the complete desired set, and the removal mode.
|
|
377
|
+
* @returns A new array, sorted ascending by start instant and unique by id.
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* ```ts
|
|
381
|
+
* const events = replaceCalendarEventItemsForModelKey(calendar.e, { modelKey: job.key, items: generated });
|
|
382
|
+
* ```
|
|
383
|
+
*
|
|
384
|
+
* @__NO_SIDE_EFFECTS__
|
|
385
|
+
*/
|
|
386
|
+
export declare function replaceCalendarEventItemsForModelKey<T extends CalendarEventItem>(items: T[], config: ReplaceCalendarEventItemsForModelKeyConfig<T>): T[];
|
|
387
|
+
/**
|
|
388
|
+
* Configuration for {@link updateCalendarEventsTemplate}.
|
|
389
|
+
*/
|
|
390
|
+
export interface UpdateCalendarEventsTemplateConfig {
|
|
391
|
+
/**
|
|
392
|
+
* The calendar's current events.
|
|
393
|
+
*/
|
|
394
|
+
readonly calendar: Pick<Calendar, 'e' | 'r'>;
|
|
395
|
+
readonly upsertEvents?: Maybe<CalendarEventItemUpdate<CalendarEventItem>[]>;
|
|
396
|
+
readonly upsertRecurringEvents?: Maybe<CalendarEventItemUpdate<CalendarRecurringEventItem>[]>;
|
|
397
|
+
readonly removeEventIds?: Maybe<ArrayOrValue<CalendarEventId>>;
|
|
398
|
+
/**
|
|
399
|
+
* Replaces the COMPLETE set of events belonging to one model key, in both arrays.
|
|
400
|
+
*
|
|
401
|
+
* This is the "republish this model" path: the caller supplies what the model produces now and anything
|
|
402
|
+
* else previously carrying the key is removed. Applied BEFORE {@link removeEventIds}, so an explicit
|
|
403
|
+
* removal still wins.
|
|
404
|
+
*/
|
|
405
|
+
readonly replaceForModelKey?: Maybe<ReplaceCalendarEventsForModelKey>;
|
|
406
|
+
readonly hardRemove?: Maybe<boolean>;
|
|
407
|
+
readonly now?: Maybe<Date>;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* The complete desired event set for one model key, as consumed by {@link updateCalendarEventsTemplate}.
|
|
411
|
+
*/
|
|
412
|
+
export interface ReplaceCalendarEventsForModelKey {
|
|
413
|
+
readonly modelKey: FirestoreModelKey;
|
|
414
|
+
readonly events?: Maybe<CalendarEventItemUpdate<CalendarEventItem>[]>;
|
|
415
|
+
readonly recurringEvents?: Maybe<CalendarEventItemUpdate<CalendarRecurringEventItem>[]>;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* The fields {@link updateCalendarEventsTemplate} produces.
|
|
419
|
+
*/
|
|
420
|
+
export type UpdateCalendarEventsTemplate = Pick<Calendar, 'e' | 'r' | 's' | 'uat'>;
|
|
421
|
+
/**
|
|
422
|
+
* Builds the update a caller merges into its own `document.update()` to change a calendar's events.
|
|
423
|
+
*
|
|
424
|
+
* ALWAYS includes `s: true` and `uat`, so a caller cannot mutate events and forget to flag the calendar for
|
|
425
|
+
* sync — the invariant the whole publish pipeline rests on.
|
|
426
|
+
*
|
|
427
|
+
* @param config - The calendar's current events plus the upserts, replacements and removals to apply.
|
|
428
|
+
* @returns The update fields.
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* ```ts
|
|
432
|
+
* await calendarDocument.update(updateCalendarEventsTemplate({ calendar, upsertEvents: [item] }));
|
|
433
|
+
* ```
|
|
434
|
+
*
|
|
435
|
+
* @__NO_SIDE_EFFECTS__
|
|
436
|
+
*/
|
|
437
|
+
export declare function updateCalendarEventsTemplate(config: UpdateCalendarEventsTemplateConfig): UpdateCalendarEventsTemplate;
|
|
438
|
+
/**
|
|
439
|
+
* Input for {@link pruneCalendarEvents}.
|
|
440
|
+
*/
|
|
441
|
+
export interface PruneCalendarEventsInput {
|
|
442
|
+
readonly calendar: Pick<Calendar, 'e' | 'r'>;
|
|
443
|
+
readonly config: CalendarTypeConfig;
|
|
444
|
+
readonly now?: Maybe<Date>;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Result of {@link pruneCalendarEvents}.
|
|
448
|
+
*/
|
|
449
|
+
export interface PruneCalendarEventsResult {
|
|
450
|
+
readonly e: CalendarEventItem[];
|
|
451
|
+
readonly r: CalendarRecurringEventItem[];
|
|
452
|
+
readonly prunedEventCount: number;
|
|
453
|
+
readonly prunedRecurringEventCount: number;
|
|
454
|
+
/**
|
|
455
|
+
* False when nothing was dropped.
|
|
456
|
+
*
|
|
457
|
+
* The sync transaction only writes `e` / `r` when this is true, otherwise every hourly sweep would rewrite
|
|
458
|
+
* both arrays — and burn a full document write — for nothing.
|
|
459
|
+
*/
|
|
460
|
+
readonly changed: boolean;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Applies a {@link CalendarTypeConfig}'s retention policy to a calendar's events. Pure; performs no I/O.
|
|
464
|
+
*
|
|
465
|
+
* 1. Drops recurrences whose series ended before the recurrence cutoff. A forever recurrence is NEVER dropped.
|
|
466
|
+
* 2. Drops one-off events whose end instant is before the past cutoff.
|
|
467
|
+
* 3. If still over `maxEvents`, drops the oldest one-off events first, then the oldest-started recurrences.
|
|
468
|
+
* Both arrays are already ascending by start, so this is a `slice`. A FUTURE-dated item is never dropped
|
|
469
|
+
* before a past-dated one.
|
|
470
|
+
*
|
|
471
|
+
* @param input - The calendar's events, its type config, and the reference instant.
|
|
472
|
+
* @returns The retained events plus what was dropped.
|
|
473
|
+
*
|
|
474
|
+
* @__NO_SIDE_EFFECTS__
|
|
475
|
+
*/
|
|
476
|
+
export declare function pruneCalendarEvents(input: PruneCalendarEventsInput): PruneCalendarEventsResult;
|
|
477
|
+
/**
|
|
478
|
+
* Converts a Date into the unix seconds form stored inside a {@link CalendarRecurringEventItem.rex} array.
|
|
479
|
+
*
|
|
480
|
+
* @param date - The instant to encode.
|
|
481
|
+
* @returns The unix seconds value.
|
|
482
|
+
*
|
|
483
|
+
* @__NO_SIDE_EFFECTS__
|
|
484
|
+
*/
|
|
485
|
+
export declare const calendarEventItemExceptionDateValue: typeof unixDateTimeSecondsNumberFromDate;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './calendar.action';
|
|
2
|
+
export * from './calendar.api.error';
|
|
3
|
+
export * from './calendar.api';
|
|
4
|
+
export * from './calendar.expand';
|
|
5
|
+
export * from './calendar.ics';
|
|
6
|
+
export * from './calendar.id';
|
|
7
|
+
export * from './calendar.processing';
|
|
8
|
+
export * from './calendar.query';
|
|
9
|
+
export * from './calendar.schedule';
|
|
10
|
+
export * from './calendar.type';
|
|
11
|
+
export * from './calendar.util';
|
|
12
|
+
export * from './calendar';
|