@dereekb/firebase 13.31.0 → 13.33.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/firebase",
3
- "version": "13.31.0",
3
+ "version": "13.33.0",
4
4
  "sideEffects": false,
5
5
  "exports": {
6
6
  "./test": {
@@ -24,10 +24,10 @@
24
24
  }
25
25
  },
26
26
  "peerDependencies": {
27
- "@dereekb/date": "13.31.0",
28
- "@dereekb/model": "13.31.0",
29
- "@dereekb/rxjs": "13.31.0",
30
- "@dereekb/util": "13.31.0",
27
+ "@dereekb/date": "13.33.0",
28
+ "@dereekb/model": "13.33.0",
29
+ "@dereekb/rxjs": "13.33.0",
30
+ "@dereekb/util": "13.33.0",
31
31
  "@firebase/rules-unit-testing": "5.0.0",
32
32
  "@marcbachmann/cel-js": "^7.6.1",
33
33
  "@typescript-eslint/parser": "8.59.3",
@@ -23,7 +23,7 @@
23
23
  * - **Primitives**: `firestoreString`, `firestoreNumber`, `firestoreBoolean`, `firestoreEnum`
24
24
  * - **Dates**: `firestoreDate` (ISO8601), `firestoreDateNumber` (unix seconds)
25
25
  * - **Arrays**: `firestoreArray`, `firestoreUniqueArray`, `firestoreEnumArray`, `firestoreEncodedArray`
26
- * - **Maps**: `firestoreMap`, `firestoreEncodedObjectMap`, `firestoreArrayMap`
26
+ * - **Maps**: `firestoreMap`, `firestoreEncodedObjectMap`, `firestoreObjectMap`, `firestoreArrayMap`
27
27
  * - **Objects**: `firestoreSubObject`, `firestoreObjectArray`
28
28
  * - **Specialized**: `firestoreUID`, `firestoreLatLngString`, `firestoreWebsiteLink`,
29
29
  * `firestoreDateCellRange`, `firestoreBitwiseSet`, `firestoreUnitedStatesAddress`
@@ -1361,9 +1361,11 @@ export type FirestoreSubObjectFieldMapFunctionsConfig<T extends object, O extend
1361
1361
  * // Nested address object with its own converters
1362
1362
  * const addressField = firestoreSubObject<Address>({
1363
1363
  * objectField: {
1364
- * street: firestoreString(),
1365
- * city: firestoreString(),
1366
- * zip: firestoreString()
1364
+ * fields: {
1365
+ * street: firestoreString(),
1366
+ * city: firestoreString(),
1367
+ * zip: firestoreString()
1368
+ * }
1367
1369
  * }
1368
1370
  * });
1369
1371
  * ```
@@ -1371,6 +1373,59 @@ export type FirestoreSubObjectFieldMapFunctionsConfig<T extends object, O extend
1371
1373
  * @__NO_SIDE_EFFECTS__
1372
1374
  */
1373
1375
  export declare function firestoreSubObject<T extends object, O extends object = FirestoreModelData<T>>(config: FirestoreSubObjectFieldConfig<T, O>): FirestoreSubObjectFieldMapFunctionsConfig<T, O>;
1376
+ /**
1377
+ * A Firestore map type where each value is an object that is converted using its own set of field converters.
1378
+ */
1379
+ export type FirestoreObjectMapFieldValueType<T extends object, S extends string = string> = Record<S, T>;
1380
+ /**
1381
+ * firestoreObjectMap configuration
1382
+ */
1383
+ export type FirestoreObjectMapFieldConfig<T extends object, O extends object = FirestoreModelData<T>, S extends string = string> = DefaultMapConfiguredFirestoreFieldConfig<FirestoreObjectMapFieldValueType<T, S>, FirestoreMapFieldType<O, S>> & (FirestoreObjectArrayFieldConfigObjectFieldInput<T, O> | FirestoreObjectArrayFieldConfigFirestoreFieldInput<T, O>) & {
1384
+ /**
1385
+ * Optional filter to apply when saving to data.
1386
+ *
1387
+ * By default filters all empty values from the map. Objects with no keys are considered empty.
1388
+ */
1389
+ readonly mapFilter?: FilterKeyValueTuplesInput<FirestoreMapFieldType<O, S>>;
1390
+ };
1391
+ /**
1392
+ * Creates a field mapping configuration for a Firestore map whose values are complex objects.
1393
+ *
1394
+ * This is the map-keyed counterpart to {@link firestoreObjectArray}: each value in the map is
1395
+ * converted using its own set of field converters (via `objectField` or `firestoreField`), so
1396
+ * nested `Date`/enum/array fields round-trip properly.
1397
+ *
1398
+ * On write, null/undefined values are filtered from each object to match Firestore semantics, and
1399
+ * empty values (including objects with no keys) are removed from the map entirely.
1400
+ *
1401
+ * NOTE: this exists instead of passing a sub-object's `mapFunctions.to`/`.from` directly to
1402
+ * {@link firestoreEncodedObjectMap}. That encoder is invoked as `mapFn(value, key)` by
1403
+ * `mapObjectMap()`, while a `ModelMapFunctions`' second parameter is the accumulator *target* — so
1404
+ * the map key string would be used as the write target and the conversion would throw at runtime.
1405
+ * The arity is wrapped here, once, rather than at each call site.
1406
+ *
1407
+ * @param config - Configuration including the per-value conversions and optional map filtering.
1408
+ * @returns A field mapping configuration for object map values.
1409
+ *
1410
+ * @dbxModelSnapshotField
1411
+ * @dbxModelSnapshotFieldCategory map
1412
+ * @dbxModelSnapshotFieldTags map, record, dictionary, object, nested, embedded, structured, factory
1413
+ * @dbxModelSnapshotFieldRelated firestore-sub-object, firestore-object-array
1414
+ * @template T - The value model type
1415
+ * @template O - The value Firestore data type (defaults to FirestoreModelData<T>)
1416
+ * @template S - Key type (string, defaults to string)
1417
+ *
1418
+ * @example
1419
+ * ```ts
1420
+ * // Map of provider id -> connection entry
1421
+ * const entriesField = firestoreObjectMap<UserExternalConnectionEntry, FirestoreModelData<UserExternalConnectionEntry>, UserExternalConnectionProviderType>({
1422
+ * objectField: { fields: userExternalConnectionEntryFields }
1423
+ * });
1424
+ * ```
1425
+ *
1426
+ * @__NO_SIDE_EFFECTS__
1427
+ */
1428
+ export declare function firestoreObjectMap<T extends object, O extends object = FirestoreModelData<T>, S extends string = string>(config: FirestoreObjectMapFieldConfig<T, O, S>): FirestoreModelFieldMapFunctionsConfig<FirestoreEncodedObjectMapFieldValueType<T, S>, FirestoreMapFieldType<O, S>>;
1374
1429
  export interface FirestoreLatLngStringConfig extends DefaultMapConfiguredFirestoreFieldConfig<LatLngString, LatLngString> {
1375
1430
  readonly precision?: LatLngPrecision;
1376
1431
  }
@@ -4,3 +4,4 @@ export * from './notification';
4
4
  export * from './oidcmodel';
5
5
  export * from './storagefile';
6
6
  export * from './system';
7
+ export * from './userexternalconnection';
@@ -9,6 +9,8 @@ export * from './notification.create.loggedevent';
9
9
  export * from './notification.loggedevent.loader';
10
10
  export * from './notification.create.task';
11
11
  export * from './notification.details';
12
+ export * from './notification.healthcheck';
13
+ export * from './notification.healthcheck.mailgun';
12
14
  export * from './notification.item';
13
15
  export * from './notification.id';
14
16
  export * from './notification.message';
@@ -13,6 +13,7 @@ import { type E164PhoneNumber, type EmailAddress, type IndexNumber, type Maybe }
13
13
  import { type NotificationTypes } from './notification';
14
14
  import { type NotificationUserDefaultNotificationBoxRecipientConfig, type NotificationBoxRecipientTemplateConfigArrayEntry, NotificationBoxRecipientFlag } from './notification.config';
15
15
  import { type NotificationBoxId, type NotificationSummaryId, type NotificationTemplateType } from './notification.id';
16
+ import { type NotificationHealthCheck, NotificationDeliveryMethod } from './notification.healthcheck';
16
17
  import { type NotificationSendEmailMessagesResult, type NotificationSendTextMessagesResult, type NotificationSendNotificationSummaryMessagesResult } from './notification.send';
17
18
  import { type NotificationTaskServiceTaskHandlerCompletionType } from './notification.task';
18
19
  export declare const NOTIFICATION_RECIPIENT_NAME_MIN_LENGTH = 0;
@@ -95,6 +96,101 @@ export { targetModelParamsType as resyncNotificationUserParamsType } from '../..
95
96
  export interface ResyncNotificationUserResult {
96
97
  readonly notificationBoxesUpdated: number;
97
98
  }
99
+ /**
100
+ * Used for running a notification delivery health check for a user.
101
+ *
102
+ * The check always inspects the user's configuration, their notification box subscriptions, and whatever
103
+ * read-only diagnostics each configured delivery method's send service offers. Actually dispatching a
104
+ * test message is opt-in via `sendProbe`, since that delivers real mail/SMS to the user.
105
+ */
106
+ export interface NotificationUserHealthCheckParams extends TargetModelParams {
107
+ /**
108
+ * Restrict the check to these delivery methods. Defaults to every method the server has a send
109
+ * service configured for.
110
+ */
111
+ readonly methods?: Maybe<NotificationDeliveryMethod[]>;
112
+ /**
113
+ * Dispatch a real test message through each checked method that supports probing.
114
+ *
115
+ * Defaults to false. Delivery confirmation is asynchronous, so a dispatched probe is usually still
116
+ * pending when the check returns and is resolved by a later {@link verifyPendingProbesOnly} run.
117
+ */
118
+ readonly sendProbe?: Maybe<boolean>;
119
+ /**
120
+ * Only resolve probes left pending by the previous check, skipping the configuration, history, and
121
+ * provider diagnostics.
122
+ *
123
+ * Defaults to false. This is the POLL that settles an in-flight test message, and it is deliberately
124
+ * cheap enough to be called repeatedly: it consults a provider only for a method that actually has a
125
+ * probe in flight, and touches nothing else. It answers to its own short window rather than the run
126
+ * window, and advances the stored check's `vat` rather than its `at`, so polling a test message never
127
+ * consumes the user's allowance for running the check itself.
128
+ */
129
+ readonly verifyPendingProbesOnly?: Maybe<boolean>;
130
+ /**
131
+ * The notification template type to evaluate per-template configuration against.
132
+ *
133
+ * Defaults to the app's default template type.
134
+ */
135
+ readonly notificationTemplateType?: Maybe<NotificationTemplateType>;
136
+ /**
137
+ * Skip inspecting the user's notification box subscriptions.
138
+ *
139
+ * Defaults to false. Skipping avoids a document read per subscription, at the cost of not detecting
140
+ * a subscription that is broken, still being set up, or out of sync with the user's settings.
141
+ */
142
+ readonly skipSubscriptionChecks?: Maybe<boolean>;
143
+ /**
144
+ * Ignore the run and test message throttle windows.
145
+ *
146
+ * PRIVILEGED — admin only. The windows exist to stop a user from repeatedly hammering the delivery
147
+ * providers and their own inbox, so this must never be honoured for an ordinary caller. The action
148
+ * itself cannot tell who is calling, so the API layer that can is responsible for clearing this
149
+ * before the params reach it:
150
+ *
151
+ * ```ts
152
+ * const params = isAdminInRequest(request) ? data : { ...data, force: undefined };
153
+ * ```
154
+ *
155
+ * Defaults to false.
156
+ */
157
+ readonly force?: Maybe<boolean>;
158
+ /**
159
+ * Return the complete health check rather than just the part the call was about.
160
+ *
161
+ * A `sendProbe` call is about one delivery method's test message, so by default its result carries only
162
+ * the methods it actually probed — the caller already has the rest, and a client rendering the report
163
+ * reads it from the {@link NotificationUser} document instead. Set this to get the whole check back.
164
+ *
165
+ * Defaults to false. Has no effect on a plain run, which always returns everything, and never affects
166
+ * what is persisted: the stored check is always the complete picture.
167
+ */
168
+ readonly returnFullHealthCheck?: Maybe<boolean>;
169
+ }
170
+ export declare const notificationUserHealthCheckParamsType: Type<NotificationUserHealthCheckParams>;
171
+ /**
172
+ * The result of a `healthCheck` invocation.
173
+ */
174
+ export interface NotificationUserHealthCheckResult {
175
+ /**
176
+ * The health check that was produced.
177
+ *
178
+ * The COMPLETE check is always persisted to the {@link NotificationUser}'s `hc` field. What comes back
179
+ * here can be narrower: a `sendProbe` call is about one delivery method's test message, so unless
180
+ * `returnFullHealthCheck` was set its result carries only the methods that were actually probed, with
181
+ * `s` rolled up over just those and the account-wide findings left out. Read the document for the
182
+ * whole picture — see {@link NotificationUserHealthCheckParams.returnFullHealthCheck}.
183
+ */
184
+ readonly healthCheck: NotificationHealthCheck;
185
+ /**
186
+ * The number of probes dispatched by this run.
187
+ */
188
+ readonly probesDispatched: number;
189
+ /**
190
+ * The number of previously-pending probes this run resolved to a final status.
191
+ */
192
+ readonly probesResolved: number;
193
+ }
98
194
  export interface ResyncAllNotificationUserParams {
99
195
  }
100
196
  export declare const resyncAllNotificationUserParamsType: Type<ResyncAllNotificationUserParams>;
@@ -310,6 +406,9 @@ export type NotificationBoxModelCrudFunctionsConfig = {
310
406
  _: UpdateNotificationUserParams;
311
407
  resync: [ResyncNotificationUserParams, ResyncNotificationUserResult];
312
408
  };
409
+ invoke: {
410
+ healthCheck: [NotificationUserHealthCheckParams, NotificationUserHealthCheckResult];
411
+ };
313
412
  };
314
413
  readonly notificationSummary: {
315
414
  update: {
@@ -344,6 +443,9 @@ export declare abstract class NotificationFunctions implements ModelFirebaseFunc
344
443
  update: ModelFirebaseCrudFunction<UpdateNotificationUserParams>;
345
444
  resync: ModelFirebaseCrudFunction<ResyncNotificationUserParams, ResyncNotificationUserResult>;
346
445
  };
446
+ invokeNotificationUser: {
447
+ healthCheck: ModelFirebaseCrudFunction<NotificationUserHealthCheckParams, NotificationUserHealthCheckResult>;
448
+ };
347
449
  };
348
450
  abstract notificationSummary: {
349
451
  updateNotificationSummary: {
@@ -40,3 +40,18 @@ export declare const NOTIFICATION_USER_BLOCKED_FROM_BEING_ADD_TO_RECIPIENTS_ERRO
40
40
  * Thrown when attempting to update a recipient config that is locked by the user.
41
41
  */
42
42
  export declare const NOTIFICATION_USER_LOCKED_CONFIG_FROM_BEING_UPDATED_ERROR_CODE = "NOTIFICATION_USER_LOCKED_CONFIG_FROM_BEING_UPDATED";
43
+ /**
44
+ * Thrown when a NotificationUser health check is run again before its throttle window has passed.
45
+ */
46
+ export declare const NOTIFICATION_USER_HEALTH_CHECK_THROTTLED_ERROR_CODE = "NOTIFICATION_USER_HEALTH_CHECK_THROTTLED";
47
+ /**
48
+ * Thrown when a NotificationUser health check test message is dispatched again before its throttle
49
+ * window has passed. Tracked separately from the run throttle, since a probe sends a real message.
50
+ */
51
+ export declare const NOTIFICATION_USER_HEALTH_CHECK_PROBE_THROTTLED_ERROR_CODE = "NOTIFICATION_USER_HEALTH_CHECK_PROBE_THROTTLED";
52
+ /**
53
+ * Thrown when a NotificationUser health check's pending probes are verified again before its throttle
54
+ * window has passed. Tracked separately from the run throttle: verifying an in-flight test message is
55
+ * cheap and meant to be polled, so it neither answers to nor consumes the user's run allowance.
56
+ */
57
+ export declare const NOTIFICATION_USER_HEALTH_CHECK_VERIFY_THROTTLED_ERROR_CODE = "NOTIFICATION_USER_HEALTH_CHECK_VERIFY_THROTTLED";
@@ -22,6 +22,7 @@ import { type NotificationBoxRecipient, type NotificationRecipientWithConfig, ty
22
22
  import { type YearWeekCode } from '@dereekb/date';
23
23
  import { type UserRelatedById, type UserRelated } from '../user';
24
24
  import { AbstractFirestoreDocument, AbstractFirestoreDocumentWithParent, type CollectionGroup, type CollectionReference, type FirestoreCollection, type FirestoreCollectionGroup, type FirestoreCollectionWithParent, type FirestoreContext, type FirestoreModelKey, type SavedToFirestoreIfTrue, type SavedToFirestoreIfFalse, type PagedItemConverter, type PagedItemFirestoreCollection, type PagedItemPageData } from '../../common';
25
+ import { type NotificationHealthCheck } from './notification.healthcheck';
25
26
  import { type NotificationItem } from './notification.item';
26
27
  /**
27
28
  * Abstract class providing access to all notification-related Firestore collections.
@@ -142,6 +143,16 @@ export interface NotificationUser extends UserRelated, UserRelatedById {
142
143
  * @dbxModelVariable needsConfigSync
143
144
  */
144
145
  ns?: Maybe<NeedsSyncBoolean>;
146
+ /**
147
+ * The result of the most recent notification delivery health check run for this user.
148
+ *
149
+ * Persisted rather than returned-and-forgotten because delivery confirmation is asynchronous:
150
+ * a probe dispatched by one health check run is resolved by a later one. Absent until a check
151
+ * has been run at least once.
152
+ *
153
+ * @dbxModelVariable healthCheck
154
+ */
155
+ hc?: Maybe<NotificationHealthCheck>;
145
156
  }
146
157
  export type NotificationUserRoles = 'sync' | GrantedUpdateRole | GrantedReadRole;
147
158
  export declare class NotificationUserDocument extends AbstractFirestoreDocument<NotificationUser, NotificationUserDocument, typeof notificationUserIdentity> {