@aglyn/tenant-data-admin 1.0.0-beta.218 → 1.0.0-beta.220
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 +8 -8
- package/src/lib/server/api-http.js +5 -1
- package/src/lib/server/api-http.js.map +1 -1
- package/src/lib/server/api-v1-description.d.ts +112 -0
- package/src/lib/server/api-v1-description.js +126 -0
- package/src/lib/server/api-v1-description.js.map +1 -0
- package/src/lib/server/api-v1-kit.d.ts +163 -0
- package/src/lib/server/api-v1-kit.js +191 -0
- package/src/lib/server/api-v1-kit.js.map +1 -0
- package/src/lib/server/api-v1-resources.d.ts +143 -0
- package/src/lib/server/api-v1-resources.js +125 -0
- package/src/lib/server/api-v1-resources.js.map +1 -0
- package/src/lib/server/campaign-conversion-attribution.d.ts +1 -1
- package/src/lib/server/campaign-conversion-attribution.js +3 -3
- package/src/lib/server/campaign-conversion-attribution.js.map +1 -1
- package/src/lib/server/card-payment-velocity.d.ts +9 -0
- package/src/lib/server/card-payment-velocity.js +28 -2
- package/src/lib/server/card-payment-velocity.js.map +1 -1
- package/src/lib/server/consent-group-carry.js +1 -1
- package/src/lib/server/consent-group-carry.js.map +1 -1
- package/src/lib/server/consent-group-change.d.ts +2 -1
- package/src/lib/server/consent-group-change.js +43 -18
- package/src/lib/server/consent-group-change.js.map +1 -1
- package/src/lib/server/contact-email-index.d.ts +22 -0
- package/src/lib/server/contact-email-index.js +108 -0
- package/src/lib/server/contact-email-index.js.map +1 -1
- package/src/lib/server/crm-email-activity.d.ts +1 -1
- package/src/lib/server/crm-email-activity.js +1 -1
- package/src/lib/server/crm-email-activity.js.map +1 -1
- package/src/lib/server/crm-records.d.ts +9 -1
- package/src/lib/server/crm-records.js +19 -2
- package/src/lib/server/crm-records.js.map +1 -1
- package/src/lib/server/cron-auth.d.ts +44 -0
- package/src/lib/server/cron-auth.js +76 -0
- package/src/lib/server/cron-auth.js.map +1 -0
- package/src/lib/server/dataset-live-pages.js +6 -6
- package/src/lib/server/dataset-live-pages.js.map +1 -1
- package/src/lib/server/duplicate-resource.d.ts +26 -0
- package/src/lib/server/duplicate-resource.js +47 -25
- package/src/lib/server/duplicate-resource.js.map +1 -1
- package/src/lib/server/dynamic-list-materialize.js +6 -3
- package/src/lib/server/dynamic-list-materialize.js.map +1 -1
- package/src/lib/server/email-flow-gate.js +2 -2
- package/src/lib/server/email-flow-gate.js.map +1 -1
- package/src/lib/server/email-suppression.d.ts +11 -0
- package/src/lib/server/email-suppression.js +37 -2
- package/src/lib/server/email-suppression.js.map +1 -1
- package/src/lib/server/email-unsubscribe-link.d.ts +8 -5
- package/src/lib/server/email-unsubscribe-link.js +7 -7
- package/src/lib/server/email-unsubscribe-link.js.map +1 -1
- package/src/lib/server/erase.d.ts +19 -33
- package/src/lib/server/erase.js +37 -162
- package/src/lib/server/erase.js.map +1 -1
- package/src/lib/server/host-email-tokens.js +1 -1
- package/src/lib/server/host-email-tokens.js.map +1 -1
- package/src/lib/server/host-memberships.js +3 -3
- package/src/lib/server/host-memberships.js.map +1 -1
- package/src/lib/server/host-visitor-records.d.ts +32 -4
- package/src/lib/server/host-visitor-records.js +73 -14
- package/src/lib/server/host-visitor-records.js.map +1 -1
- package/src/lib/server/hosted-page-review.d.ts +27 -0
- package/src/lib/server/hosted-page-review.js +149 -12
- package/src/lib/server/hosted-page-review.js.map +1 -1
- package/src/lib/server/media-cdn-rate-limit.d.ts +6 -3
- package/src/lib/server/media-cdn-rate-limit.js +6 -3
- package/src/lib/server/media-cdn-rate-limit.js.map +1 -1
- package/src/lib/server/member-email-aliases.d.ts +10 -2
- package/src/lib/server/member-email-aliases.js +26 -2
- package/src/lib/server/member-email-aliases.js.map +1 -1
- package/src/lib/server/new-account-announcement.d.ts +62 -0
- package/src/lib/server/new-account-announcement.js +77 -0
- package/src/lib/server/new-account-announcement.js.map +1 -0
- package/src/lib/server/notifications.d.ts +22 -2
- package/src/lib/server/notifications.js +40 -3
- package/src/lib/server/notifications.js.map +1 -1
- package/src/lib/server/operator-alerts.d.ts +7 -1
- package/src/lib/server/operator-alerts.js +5 -3
- package/src/lib/server/operator-alerts.js.map +1 -1
- package/src/lib/server/org-containers.d.ts +66 -0
- package/src/lib/server/org-containers.js +95 -0
- package/src/lib/server/org-containers.js.map +1 -0
- package/src/lib/server/org-member-notice.js +3 -2
- package/src/lib/server/org-member-notice.js.map +1 -1
- package/src/lib/server/payment-fraud-signal.js +5 -1
- package/src/lib/server/payment-fraud-signal.js.map +1 -1
- package/src/lib/server/{connect-payout-failure.d.ts → payment-provider-stripe-connect-payouts.d.ts} +3 -0
- package/src/lib/server/{connect-payout-failure.js → payment-provider-stripe-connect-payouts.js} +36 -2
- package/src/lib/server/payment-provider-stripe-connect-payouts.js.map +1 -0
- package/src/lib/server/{connect-account-status.d.ts → payment-provider-stripe-connect-status.d.ts} +3 -0
- package/src/lib/server/{connect-account-status.js → payment-provider-stripe-connect-status.js} +1 -1
- package/src/lib/server/payment-provider-stripe-connect-status.js.map +1 -0
- package/src/lib/server/{stripe-account-mode.d.ts → payment-provider-stripe-connect.d.ts} +45 -70
- package/src/lib/server/{stripe-account-mode.js → payment-provider-stripe-connect.js} +78 -52
- package/src/lib/server/payment-provider-stripe-connect.js.map +1 -0
- package/src/lib/server/payment-provider.d.ts +173 -0
- package/src/lib/server/payment-provider.js +70 -0
- package/src/lib/server/payment-provider.js.map +1 -0
- package/src/lib/server/payment-risk-record.d.ts +1 -1
- package/src/lib/server/payment-risk-record.js +3 -1
- package/src/lib/server/payment-risk-record.js.map +1 -1
- package/src/lib/server/personal-data-export.js +1 -1
- package/src/lib/server/personal-data-export.js.map +1 -1
- package/src/lib/server/platform-marketing-consent.d.ts +1 -1
- package/src/lib/server/platform-marketing-consent.js +1 -1
- package/src/lib/server/platform-marketing-consent.js.map +1 -1
- package/src/lib/server/rate-limit-store.js +13 -3
- package/src/lib/server/rate-limit-store.js.map +1 -1
- package/src/lib/server/realm-plugins.js +8 -3
- package/src/lib/server/realm-plugins.js.map +1 -1
- package/src/lib/server/retained-refusals.d.ts +142 -0
- package/src/lib/server/retained-refusals.js +325 -0
- package/src/lib/server/retained-refusals.js.map +1 -0
- package/src/lib/server/risk-notice.d.ts +32 -1
- package/src/lib/server/risk-notice.js +90 -28
- package/src/lib/server/risk-notice.js.map +1 -1
- package/src/lib/server/sending-domains.d.ts +17 -0
- package/src/lib/server/sending-domains.js +28 -4
- package/src/lib/server/sending-domains.js.map +1 -1
- package/src/lib/server/sso-enforcement.js +7 -2
- package/src/lib/server/sso-enforcement.js.map +1 -1
- package/src/lib/server/staff-alert-email.js +4 -2
- package/src/lib/server/staff-alert-email.js.map +1 -1
- package/src/lib/server/test-firestore-queries.d.ts +7 -0
- package/src/lib/server/test-firestore-queries.js +70 -27
- package/src/lib/server/test-firestore-queries.js.map +1 -1
- package/src/lib/server/{email-topic-confirmation.js → topic-subscriptions.js} +15 -11
- package/src/lib/server/topic-subscriptions.js.map +1 -0
- package/src/lib/server/upsert-contact.d.ts +46 -1
- package/src/lib/server/upsert-contact.js +87 -20
- package/src/lib/server/upsert-contact.js.map +1 -1
- package/src/tenancy.d.ts +5 -8
- package/src/tenancy.js +12 -13
- package/src/tenancy.js.map +1 -1
- package/src/lib/server/connect-account-status.js.map +0 -1
- package/src/lib/server/connect-payout-failure.js.map +0 -1
- package/src/lib/server/contact-email-engagement.d.ts +0 -32
- package/src/lib/server/contact-email-engagement.js +0 -145
- package/src/lib/server/contact-email-engagement.js.map +0 -1
- package/src/lib/server/contact-lifecycle-floor.d.ts +0 -89
- package/src/lib/server/contact-lifecycle-floor.js +0 -91
- package/src/lib/server/contact-lifecycle-floor.js.map +0 -1
- package/src/lib/server/contact-merge.d.ts +0 -55
- package/src/lib/server/contact-merge.js +0 -288
- package/src/lib/server/contact-merge.js.map +0 -1
- package/src/lib/server/crm-booking-activity.d.ts +0 -86
- package/src/lib/server/crm-booking-activity.js +0 -257
- package/src/lib/server/crm-booking-activity.js.map +0 -1
- package/src/lib/server/crm-inbound-email.d.ts +0 -145
- package/src/lib/server/crm-inbound-email.js +0 -297
- package/src/lib/server/crm-inbound-email.js.map +0 -1
- package/src/lib/server/email-topic-confirmation.js.map +0 -1
- package/src/lib/server/form-dataset-binding-token.d.ts +0 -35
- package/src/lib/server/form-dataset-binding-token.js +0 -107
- package/src/lib/server/form-dataset-binding-token.js.map +0 -1
- package/src/lib/server/stripe-account-mode.js.map +0 -1
- /package/src/lib/server/{email-topic-confirmation.d.ts → topic-subscriptions.d.ts} +0 -0
|
@@ -34,7 +34,9 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
34
34
|
* - `update()` needs the document to exist, replaces the value at each
|
|
35
35
|
* (dotted) path whole, and fails its `lastUpdateTime` precondition when
|
|
36
36
|
* the document was written since; `create()` fails on an existing one.
|
|
37
|
-
* The error codes are gRPC's: 5, 6, 9.
|
|
37
|
+
* The error codes are gRPC's: 5, 6, 9. A `set()` with `mergeFields`
|
|
38
|
+
* replaces the value at each named path whole, and creates the document
|
|
39
|
+
* when it is missing.
|
|
38
40
|
* - `FieldValue` sentinels are applied, not stored: `delete`,
|
|
39
41
|
* `serverTimestamp` (the fake clock), `increment`, `arrayUnion`,
|
|
40
42
|
* `arrayRemove`.
|
|
@@ -133,6 +135,7 @@ export function queryFakeFirestore(seed = {}, options = {}) {
|
|
|
133
135
|
let nowMs = (_options_nowMs = options.nowMs) != null ? _options_nowMs : Date.UTC(2026, 8, 24);
|
|
134
136
|
/** Keeps every write's `updateTime` distinct even on a frozen clock. */ let tick = 0;
|
|
135
137
|
let writeCount = 0;
|
|
138
|
+
let readCount = 0;
|
|
136
139
|
const failing = new Map();
|
|
137
140
|
let autoIds = 0;
|
|
138
141
|
const stamp = ()=>Timestamp.fromMillis(nowMs + (tick += 0.001));
|
|
@@ -266,10 +269,12 @@ export function queryFakeFirestore(seed = {}, options = {}) {
|
|
|
266
269
|
});
|
|
267
270
|
return at;
|
|
268
271
|
}
|
|
269
|
-
if (write.type === 'set' && write.
|
|
272
|
+
if (write.type === 'set' && write.fields) {
|
|
270
273
|
var _ref3, _ref4;
|
|
271
274
|
const data = clone((_ref3 = existing == null ? void 0 : existing.data) != null ? _ref3 : {});
|
|
272
|
-
|
|
275
|
+
for (const path of write.fields){
|
|
276
|
+
put(data, path.split('.'), clone(valueAt(write.data, path)), valueAt(existing == null ? void 0 : existing.data, path));
|
|
277
|
+
}
|
|
273
278
|
store.set(write.path, {
|
|
274
279
|
data,
|
|
275
280
|
createTime: (_ref4 = existing == null ? void 0 : existing.createTime) != null ? _ref4 : at,
|
|
@@ -277,6 +282,17 @@ export function queryFakeFirestore(seed = {}, options = {}) {
|
|
|
277
282
|
});
|
|
278
283
|
return at;
|
|
279
284
|
}
|
|
285
|
+
if (write.type === 'set' && write.merge) {
|
|
286
|
+
var _ref5, _ref6;
|
|
287
|
+
const data = clone((_ref5 = existing == null ? void 0 : existing.data) != null ? _ref5 : {});
|
|
288
|
+
mergeInto(data, write.data);
|
|
289
|
+
store.set(write.path, {
|
|
290
|
+
data,
|
|
291
|
+
createTime: (_ref6 = existing == null ? void 0 : existing.createTime) != null ? _ref6 : at,
|
|
292
|
+
updateTime: at
|
|
293
|
+
});
|
|
294
|
+
return at;
|
|
295
|
+
}
|
|
280
296
|
const data = {};
|
|
281
297
|
mergeInto(data, write.data);
|
|
282
298
|
store.set(write.path, {
|
|
@@ -286,6 +302,16 @@ export function queryFakeFirestore(seed = {}, options = {}) {
|
|
|
286
302
|
});
|
|
287
303
|
return at;
|
|
288
304
|
}
|
|
305
|
+
function setWrite(path, data, setOptions) {
|
|
306
|
+
return _extends({
|
|
307
|
+
type: 'set',
|
|
308
|
+
path,
|
|
309
|
+
data,
|
|
310
|
+
merge: (setOptions == null ? void 0 : setOptions.merge) === true
|
|
311
|
+
}, (setOptions == null ? void 0 : setOptions.mergeFields) ? {
|
|
312
|
+
fields: setOptions.mergeFields.map(fieldName)
|
|
313
|
+
} : {});
|
|
314
|
+
}
|
|
289
315
|
function snapshot(path) {
|
|
290
316
|
const stored = store.get(path);
|
|
291
317
|
const id = path.split('/').pop();
|
|
@@ -306,13 +332,11 @@ export function queryFakeFirestore(seed = {}, options = {}) {
|
|
|
306
332
|
id: segments[segments.length - 1],
|
|
307
333
|
path,
|
|
308
334
|
collection: (name)=>collectionRef(`${path}/${name}`),
|
|
309
|
-
get: async ()=>
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
merge: (setOptions == null ? void 0 : setOptions.merge) === true
|
|
315
|
-
}),
|
|
335
|
+
get: async ()=>{
|
|
336
|
+
readCount += 1;
|
|
337
|
+
return snapshot(path);
|
|
338
|
+
},
|
|
339
|
+
set: async (data, setOptions)=>run(setWrite(path, data, setOptions)),
|
|
316
340
|
update: async (data, precondition)=>run({
|
|
317
341
|
type: 'update',
|
|
318
342
|
path,
|
|
@@ -334,6 +358,9 @@ export function queryFakeFirestore(seed = {}, options = {}) {
|
|
|
334
358
|
Object.defineProperty(ref, 'parent', {
|
|
335
359
|
get: ()=>collectionRef(segments.slice(0, -1).join('/'))
|
|
336
360
|
});
|
|
361
|
+
Object.defineProperty(ref, 'firestore', {
|
|
362
|
+
get: ()=>firestore
|
|
363
|
+
});
|
|
337
364
|
return ref;
|
|
338
365
|
}
|
|
339
366
|
function run(write) {
|
|
@@ -414,6 +441,7 @@ export function queryFakeFirestore(seed = {}, options = {}) {
|
|
|
414
441
|
})),
|
|
415
442
|
count: ()=>({
|
|
416
443
|
get: async ()=>{
|
|
444
|
+
readCount += 1;
|
|
417
445
|
const total = execute(_extends({}, state, {
|
|
418
446
|
limit: null,
|
|
419
447
|
after: state.after
|
|
@@ -427,6 +455,7 @@ export function queryFakeFirestore(seed = {}, options = {}) {
|
|
|
427
455
|
}
|
|
428
456
|
}),
|
|
429
457
|
get: async ()=>{
|
|
458
|
+
readCount += 1;
|
|
430
459
|
const docs = execute(state);
|
|
431
460
|
return {
|
|
432
461
|
docs,
|
|
@@ -445,8 +474,16 @@ export function queryFakeFirestore(seed = {}, options = {}) {
|
|
|
445
474
|
after: null,
|
|
446
475
|
limit: null
|
|
447
476
|
});
|
|
448
|
-
|
|
449
|
-
|
|
477
|
+
const segments = path.split('/');
|
|
478
|
+
return _extends({
|
|
479
|
+
get parent () {
|
|
480
|
+
return segments.length > 1 ? docRef(segments.slice(0, -1).join('/')) : null;
|
|
481
|
+
},
|
|
482
|
+
get firestore () {
|
|
483
|
+
return firestore;
|
|
484
|
+
}
|
|
485
|
+
}, base, {
|
|
486
|
+
id: segments[segments.length - 1],
|
|
450
487
|
path,
|
|
451
488
|
doc: (id)=>docRef(`${path}/${id != null ? id : nextAutoId()}`),
|
|
452
489
|
add: async (data)=>{
|
|
@@ -470,15 +507,19 @@ export function queryFakeFirestore(seed = {}, options = {}) {
|
|
|
470
507
|
return snapshot(path);
|
|
471
508
|
};
|
|
472
509
|
const api = {
|
|
473
|
-
get: async (target)=>
|
|
474
|
-
|
|
510
|
+
get: async (target)=>{
|
|
511
|
+
if (typeof (target == null ? void 0 : target.path) === 'string' && target.path.split('/').length % 2 === 0) {
|
|
512
|
+
readCount += 1;
|
|
513
|
+
return read(target.path);
|
|
514
|
+
}
|
|
515
|
+
return target.get();
|
|
516
|
+
},
|
|
517
|
+
getAll: async (...refs)=>{
|
|
518
|
+
readCount += 1;
|
|
519
|
+
return refs.map((ref)=>read(ref.path));
|
|
520
|
+
},
|
|
475
521
|
set: (ref, data, setOptions)=>{
|
|
476
|
-
writes.push(
|
|
477
|
-
type: 'set',
|
|
478
|
-
path: ref.path,
|
|
479
|
-
data,
|
|
480
|
-
merge: (setOptions == null ? void 0 : setOptions.merge) === true
|
|
481
|
-
});
|
|
522
|
+
writes.push(setWrite(ref.path, data, setOptions));
|
|
482
523
|
return api;
|
|
483
524
|
},
|
|
484
525
|
update: (ref, data, precondition)=>{
|
|
@@ -537,7 +578,10 @@ export function queryFakeFirestore(seed = {}, options = {}) {
|
|
|
537
578
|
const firestore = {
|
|
538
579
|
collection: (path)=>collectionRef(path),
|
|
539
580
|
doc: (path)=>docRef(path),
|
|
540
|
-
getAll: async (...refs)=>
|
|
581
|
+
getAll: async (...refs)=>{
|
|
582
|
+
readCount += 1;
|
|
583
|
+
return refs.map((ref)=>snapshot(ref.path));
|
|
584
|
+
},
|
|
541
585
|
runTransaction: async (body)=>{
|
|
542
586
|
for(let attempt = 0; attempt < 5; attempt += 1){
|
|
543
587
|
const { writes, reads, api } = transactionOrBatch();
|
|
@@ -577,12 +621,7 @@ export function queryFakeFirestore(seed = {}, options = {}) {
|
|
|
577
621
|
path: ref.path,
|
|
578
622
|
data
|
|
579
623
|
}),
|
|
580
|
-
set: (ref, data, setOptions)=>enqueue(
|
|
581
|
-
type: 'set',
|
|
582
|
-
path: ref.path,
|
|
583
|
-
data,
|
|
584
|
-
merge: (setOptions == null ? void 0 : setOptions.merge) === true
|
|
585
|
-
}),
|
|
624
|
+
set: (ref, data, setOptions)=>enqueue(setWrite(ref.path, data, setOptions)),
|
|
586
625
|
update: (ref, data, precondition)=>enqueue({
|
|
587
626
|
type: 'update',
|
|
588
627
|
path: ref.path,
|
|
@@ -626,6 +665,10 @@ export function queryFakeFirestore(seed = {}, options = {}) {
|
|
|
626
665
|
resetWrites: ()=>{
|
|
627
666
|
writeCount = 0;
|
|
628
667
|
},
|
|
668
|
+
reads: ()=>readCount,
|
|
669
|
+
resetReads: ()=>{
|
|
670
|
+
readCount = 0;
|
|
671
|
+
},
|
|
629
672
|
setNow: (ms)=>{
|
|
630
673
|
nowMs = ms;
|
|
631
674
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/test-firestore-queries.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A Firestore stand-in that QUERIES (AGL-3320). Test-only; deliberately not\n * exported from the barrel, like `test-firestore.ts` beside it.\n *\n * That fake holds documents and nothing else, which is enough for code that\n * reads a document by id. A consent group change pages whole subcollections\n * by document id, filters on nested map fields, counts, writes through a\n * `BulkWriter` with `lastUpdateTime` preconditions and takes transactions —\n * and its correctness properties (a second pass writes nothing, a resumed\n * pass skips nothing) are properties of exactly those mechanics, so a fake\n * that skipped them would be asserting itself.\n *\n * What is modeled, and is load-bearing:\n *\n * - A document missing a field never matches a filter or an ordering on it,\n * including `!=` — the rule that makes an `orderBy` on a data field drop\n * documents, and the reason every scan here pages by `__name__`.\n * - `update()` needs the document to exist, replaces the value at each\n * (dotted) path whole, and fails its `lastUpdateTime` precondition when\n * the document was written since; `create()` fails on an existing one.\n * The error codes are gRPC's: 5, 6, 9.\n * - `FieldValue` sentinels are applied, not stored: `delete`,\n * `serverTimestamp` (the fake clock), `increment`, `arrayUnion`,\n * `arrayRemove`.\n * - Every write moves `updateTime`, and a `BulkWriter` runs its queued\n * writes when it is flushed or closed, rejecting each failed write's\n * promise the way the real one does.\n * - A transaction is SERIALIZABLE: one whose reads changed before it\n * committed runs its body again, as the SDK retries a contended one, so\n * two transactions racing on one document cannot both act on what they\n * read. That is the property a start's \"no change in flight\" rests on.\n *\n * What is not: a `BulkWriter`'s own retries, which nothing here branches on.\n */\n\nimport { FieldPath, FieldValue, Timestamp } from 'firebase-admin/firestore'\n\ntype Data = Record<string, any>\n\ninterface Stored {\n data: Data\n createTime: Timestamp\n updateTime: Timestamp\n}\n\n/** A Firestore-style error: the gRPC code the SDK attaches. */\nfunction grpcError(code: number, message: string): Error {\n return Object.assign(new Error(message), { code })\n}\n\nconst isTransform = (value: unknown): value is FieldValue & { methodName: string } =>\n value instanceof FieldValue\n\nfunction clone<T>(value: T): T {\n if (value === null || typeof value !== 'object') return value\n if (value instanceof Timestamp || value instanceof FieldValue) return value\n if (Array.isArray(value)) return value.map((item) => clone(item)) as T\n const out: Data = {}\n for (const [key, item] of Object.entries(value as Data)) out[key] = clone(item)\n return out as T\n}\n\n/** A field as a plain dotted path; a `FieldPath`'s segments are joined unquoted. */\nfunction fieldName(field: string | FieldPath): string {\n if (typeof field === 'string') return field\n const segments = (field as unknown as { segments?: string[] }).segments\n return Array.isArray(segments) ? segments.join('.') : String(field)\n}\n\n/** The value at a dotted path, or `undefined` when any segment is missing. */\nfunction valueAt(data: Data | undefined, path: string): unknown {\n let node: unknown = data\n for (const segment of path.split('.')) {\n if (!node || typeof node !== 'object' || Array.isArray(node)) return undefined\n node = (node as Data)[segment]\n }\n return node\n}\n\n/** Firestore's cross-type order: null, booleans, numbers, timestamps, strings, the rest. */\nfunction rank(value: unknown): number {\n if (value === null) return 0\n if (typeof value === 'boolean') return 1\n if (typeof value === 'number') return 2\n if (value instanceof Timestamp) return 3\n if (typeof value === 'string') return 4\n if (Array.isArray(value)) return 6\n return 7\n}\n\nfunction compare(a: unknown, b: unknown): number {\n const byRank = rank(a) - rank(b)\n if (byRank !== 0) return byRank\n if (a instanceof Timestamp && b instanceof Timestamp) {\n return a.seconds - b.seconds || a.nanoseconds - b.nanoseconds\n }\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n if (typeof a === 'boolean' && typeof b === 'boolean') return Number(a) - Number(b)\n const left = JSON.stringify(a)\n const right = JSON.stringify(b)\n return left < right ? -1 : left > right ? 1 : 0\n}\n\nconst equal = (a: unknown, b: unknown) => rank(a) === rank(b) && compare(a, b) === 0\n\ninterface Filter {\n field: string\n op: string\n value: unknown\n}\n\nfunction matches(id: string, data: Data, filter: Filter): boolean {\n const actual = filter.field === '__name__' ? id : valueAt(data, filter.field)\n if (actual === undefined) return false\n const { op, value } = filter\n switch (op) {\n case '==':\n return equal(actual, value)\n case '!=':\n return actual !== null && !equal(actual, value)\n case '<':\n return rank(actual) === rank(value) && compare(actual, value) < 0\n case '<=':\n return rank(actual) === rank(value) && compare(actual, value) <= 0\n case '>':\n return rank(actual) === rank(value) && compare(actual, value) > 0\n case '>=':\n return rank(actual) === rank(value) && compare(actual, value) >= 0\n case 'in':\n return (value as unknown[]).some((candidate) => equal(actual, candidate))\n case 'not-in':\n return actual !== null && !(value as unknown[]).some((candidate) => equal(actual, candidate))\n case 'array-contains':\n return Array.isArray(actual) && actual.some((item) => equal(item, value))\n case 'array-contains-any':\n return (\n Array.isArray(actual) &&\n actual.some((item) => (value as unknown[]).some((candidate) => equal(item, candidate)))\n )\n default:\n throw new Error(`test-firestore-queries: unsupported operator ${op}`)\n }\n}\n\nexport interface QueryFakeFirestore {\n collection(path: string): any\n doc(path: string): any\n getAll(...refs: any[]): Promise<any[]>\n runTransaction<T>(body: (transaction: any) => Promise<T>): Promise<T>\n batch(): any\n bulkWriter(): any\n /** Seed or overwrite one document, with no transforms and no write counted. */\n seed(path: string, data: Data): void\n /** One document's data as stored, or `undefined`. */\n read(path: string): Data | undefined\n /** Every document directly in a collection, by id. */\n docs(collectionPath: string): Record<string, Data>\n /** Committed writes since the last reset. */\n writes(): number\n resetWrites(): void\n /** The clock `serverTimestamp()` and every `updateTime` read. */\n setNow(ms: number): void\n /** Makes the next writes to `path` fail with `error` — a stand-in for an outage. */\n failWritesTo(path: string, error: Error | null): void\n}\n\nexport function queryFakeFirestore(\n seed: Record<string, Data> = {},\n options: { nowMs?: number } = {},\n): QueryFakeFirestore {\n const store = new Map<string, Stored>()\n let nowMs = options.nowMs ?? Date.UTC(2026, 8, 24)\n /** Keeps every write's `updateTime` distinct even on a frozen clock. */\n let tick = 0\n let writeCount = 0\n const failing = new Map<string, Error>()\n let autoIds = 0\n\n const stamp = () => Timestamp.fromMillis(nowMs + (tick += 0.001))\n const nextAutoId = () => {\n autoIds += 1\n return String(autoIds).padStart(8, '0')\n }\n\n for (const [path, data] of Object.entries(seed)) {\n const at = Timestamp.fromMillis(nowMs)\n store.set(path, { data: clone(data), createTime: at, updateTime: at })\n }\n\n /** Applies a write's value at one path of `target`, honoring transforms. */\n function put(target: Data, path: string[], value: unknown, previous: unknown): void {\n const [head, ...rest] = path\n if (rest.length) {\n const child = target[head]\n const next = child && typeof child === 'object' && !Array.isArray(child) ? child : {}\n target[head] = next\n put(next, rest, value, valueAt(previous as Data, rest.join('.')))\n return\n }\n if (isTransform(value)) {\n const current = target[head]\n switch (value.methodName) {\n case 'FieldValue.delete':\n delete target[head]\n return\n case 'FieldValue.serverTimestamp':\n target[head] = Timestamp.fromMillis(nowMs)\n return\n case 'FieldValue.increment': {\n const operand = (value as unknown as { operand: number }).operand\n target[head] = (typeof current === 'number' ? current : 0) + operand\n return\n }\n case 'FieldValue.arrayUnion': {\n const elements = (value as unknown as { elements: unknown[] }).elements\n const base = Array.isArray(current) ? [...current] : []\n for (const element of elements) {\n if (!base.some((item) => equal(item, element))) base.push(element)\n }\n target[head] = base\n return\n }\n case 'FieldValue.arrayRemove': {\n const elements = (value as unknown as { elements: unknown[] }).elements\n target[head] = (Array.isArray(current) ? current : []).filter(\n (item) => !elements.some((element) => equal(item, element)),\n )\n return\n }\n default:\n throw new Error(`test-firestore-queries: unsupported transform ${value.methodName}`)\n }\n }\n target[head] = materialize(value)\n }\n\n /** A written value with any nested transforms resolved against nothing. */\n function materialize(value: unknown): unknown {\n if (value === undefined) throw new Error('test-firestore-queries: undefined is not a Firestore value')\n if (value === null || typeof value !== 'object') return value\n if (value instanceof Timestamp) return value\n if (isTransform(value)) {\n if (value.methodName === 'FieldValue.serverTimestamp') return Timestamp.fromMillis(nowMs)\n throw new Error(`test-firestore-queries: ${value.methodName} inside a nested value`)\n }\n if (Array.isArray(value)) return value.map((item) => materialize(item))\n const out: Data = {}\n for (const [key, item] of Object.entries(value as Data)) {\n if (isTransform(item) && item.methodName === 'FieldValue.delete') continue\n out[key] = materialize(item)\n }\n return out\n }\n\n function mergeInto(target: Data, source: Data): void {\n for (const [key, value] of Object.entries(source)) {\n if (\n value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n !(value instanceof Timestamp) &&\n !isTransform(value)\n ) {\n const child = target[key]\n const next = child && typeof child === 'object' && !Array.isArray(child) && !(child instanceof Timestamp)\n ? child\n : {}\n target[key] = next\n mergeInto(next, value as Data)\n } else {\n put(target, [key], value, target[key])\n }\n }\n }\n\n type Write =\n | { type: 'set'; path: string; data: Data; merge: boolean }\n | { type: 'update'; path: string; data: Data; precondition?: { lastUpdateTime?: Timestamp } }\n | { type: 'create'; path: string; data: Data }\n | { type: 'delete'; path: string; precondition?: { lastUpdateTime?: Timestamp } }\n\n /** Why a write cannot apply, or `null`. */\n function refusal(write: Write): Error | null {\n const failure = failing.get(write.path)\n if (failure) return failure\n const existing = store.get(write.path)\n if (write.type === 'create' && existing) {\n return grpcError(6, `ALREADY_EXISTS: ${write.path}`)\n }\n if (write.type === 'update' && !existing) {\n return grpcError(5, `NOT_FOUND: ${write.path}`)\n }\n if ((write.type === 'update' || write.type === 'delete') && write.precondition?.lastUpdateTime) {\n if (!existing || !existing.updateTime.isEqual(write.precondition.lastUpdateTime)) {\n return grpcError(9, `FAILED_PRECONDITION: ${write.path}`)\n }\n }\n return null\n }\n\n function apply(write: Write): Timestamp {\n const existing = store.get(write.path)\n const at = stamp()\n writeCount += 1\n if (write.type === 'delete') {\n store.delete(write.path)\n return at\n }\n if (write.type === 'update') {\n const data = clone(existing?.data ?? {})\n for (const [path, value] of Object.entries(write.data)) {\n put(data, path.split('.'), value, valueAt(existing?.data, path))\n }\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n if (write.type === 'set' && write.merge) {\n const data = clone(existing?.data ?? {})\n mergeInto(data, write.data)\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n const data: Data = {}\n mergeInto(data, write.data)\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n\n function snapshot(path: string): any {\n const stored = store.get(path)\n const id = path.split('/').pop() as string\n return {\n id,\n ref: docRef(path),\n exists: stored !== undefined,\n data: () => (stored ? clone(stored.data) : undefined),\n get: (field: string | FieldPath) => clone(valueAt(stored?.data, fieldName(field))),\n updateTime: stored?.updateTime,\n createTime: stored?.createTime,\n }\n }\n\n function docRef(path: string): any {\n const segments = path.split('/')\n if (segments.length % 2 !== 0) throw new Error(`test-firestore-queries: ${path} is not a document path`)\n const ref: any = {\n id: segments[segments.length - 1],\n path,\n collection: (name: string) => collectionRef(`${path}/${name}`),\n get: async () => snapshot(path),\n set: async (data: Data, setOptions?: { merge?: boolean }) =>\n run({ type: 'set', path, data, merge: setOptions?.merge === true }),\n update: async (data: Data, precondition?: { lastUpdateTime?: Timestamp }) =>\n run({ type: 'update', path, data, precondition }),\n create: async (data: Data) => run({ type: 'create', path, data }),\n delete: async (precondition?: { lastUpdateTime?: Timestamp }) =>\n run({ type: 'delete', path, precondition }),\n isEqual: (other: { path?: string }) => other?.path === path,\n }\n Object.defineProperty(ref, 'parent', {\n get: () => collectionRef(segments.slice(0, -1).join('/')),\n })\n return ref\n }\n\n function run(write: Write): { writeTime: Timestamp } {\n const failure = refusal(write)\n if (failure) throw failure\n return { writeTime: apply(write) }\n }\n\n interface QueryState {\n path: string\n filters: Filter[]\n orders: Array<{ field: string; direction: 'asc' | 'desc' }>\n after: unknown[] | null\n limit: number | null\n }\n\n function execute(state: QueryState): any[] {\n const prefix = `${state.path}/`\n let rows = [...store.entries()]\n .filter(([path]) => path.startsWith(prefix) && !path.slice(prefix.length).includes('/'))\n .map(([path, stored]) => ({ path, id: path.slice(prefix.length), data: stored.data }))\n .filter((row) => state.filters.every((filter) => matches(row.id, row.data, filter)))\n const orders = [...state.orders]\n if (!orders.some((order) => order.field === '__name__')) {\n orders.push({ field: '__name__', direction: 'asc' })\n }\n rows = rows.filter((row) =>\n orders.every((order) => order.field === '__name__' || valueAt(row.data, order.field) !== undefined),\n )\n const keyOf = (row: { id: string; data: Data }, field: string) =>\n field === '__name__' ? row.id : valueAt(row.data, field)\n rows.sort((a, b) => {\n for (const order of orders) {\n const result = compare(keyOf(a, order.field), keyOf(b, order.field))\n if (result !== 0) return order.direction === 'desc' ? -result : result\n }\n return 0\n })\n if (state.after) {\n const cursor = state.after\n rows = rows.filter((row) => {\n for (let index = 0; index < cursor.length; index += 1) {\n const order = orders[index]\n const result = compare(keyOf(row, order.field), cursor[index])\n const signed = order.direction === 'desc' ? -result : result\n if (signed !== 0) return signed > 0\n }\n return false\n })\n }\n if (state.limit !== null) rows = rows.slice(0, state.limit)\n return rows.map((row) => snapshot(row.path))\n }\n\n function query(state: QueryState): any {\n return {\n where: (field: string | FieldPath, op: string, value: unknown) =>\n query({ ...state, filters: [...state.filters, { field: fieldName(field), op, value }] }),\n orderBy: (field: string | FieldPath, direction: 'asc' | 'desc' = 'asc') =>\n query({ ...state, orders: [...state.orders, { field: fieldName(field), direction }] }),\n startAfter: (...values: unknown[]) => query({ ...state, after: values }),\n limit: (limit: number) => query({ ...state, limit }),\n count: () => ({\n get: async () => {\n const total = execute({ ...state, limit: null, after: state.after }).length\n const count = state.limit === null ? total : Math.min(total, state.limit)\n return { data: () => ({ count }) }\n },\n }),\n get: async () => {\n const docs = execute(state)\n return { docs, size: docs.length, empty: docs.length === 0, forEach: (fn: any) => docs.forEach(fn) }\n },\n }\n }\n\n function collectionRef(path: string): any {\n const base = query({ path, filters: [], orders: [], after: null, limit: null })\n return {\n ...base,\n id: path.split('/').pop(),\n path,\n doc: (id: string) => docRef(`${path}/${id ?? nextAutoId()}`),\n add: async (data: Data) => {\n const ref = docRef(`${path}/${nextAutoId()}`)\n run({ type: 'create', path: ref.path, data })\n return ref\n },\n }\n }\n\n function transactionOrBatch(): { writes: Write[]; reads: Map<string, Timestamp | null>; api: any } {\n const writes: Write[] = []\n const reads = new Map<string, Timestamp | null>()\n const read = (path: string) => {\n if (!reads.has(path)) reads.set(path, store.get(path)?.updateTime ?? null)\n return snapshot(path)\n }\n const api: any = {\n get: async (target: any) =>\n typeof target?.path === 'string' && target.path.split('/').length % 2 === 0\n ? read(target.path)\n : target.get(),\n getAll: async (...refs: any[]) => refs.map((ref) => read(ref.path)),\n set: (ref: any, data: Data, setOptions?: { merge?: boolean }) => {\n writes.push({ type: 'set', path: ref.path, data, merge: setOptions?.merge === true })\n return api\n },\n update: (ref: any, data: Data, precondition?: { lastUpdateTime?: Timestamp }) => {\n writes.push({ type: 'update', path: ref.path, data, precondition })\n return api\n },\n create: (ref: any, data: Data) => {\n writes.push({ type: 'create', path: ref.path, data })\n return api\n },\n delete: (ref: any, precondition?: { lastUpdateTime?: Timestamp }) => {\n writes.push({ type: 'delete', path: ref.path, precondition })\n return api\n },\n }\n return { writes, reads, api }\n }\n\n /** Whether every document a transaction read is still as it read it. */\n function unchanged(reads: Map<string, Timestamp | null>): boolean {\n for (const [path, seen] of reads) {\n const now = store.get(path)?.updateTime ?? null\n if (seen === null ? now !== null : now === null || !now.isEqual(seen)) return false\n }\n return true\n }\n\n /** All or nothing, in order: every write is checked against the state the earlier ones leave. */\n function commitAll(writes: Write[]): void {\n const saved = new Map(store)\n const count = writeCount\n try {\n for (const write of writes) run(write)\n } catch (error) {\n store.clear()\n for (const [path, stored] of saved) store.set(path, stored)\n writeCount = count\n throw error\n }\n }\n\n const firestore: QueryFakeFirestore = {\n collection: (path: string) => collectionRef(path),\n doc: (path: string) => docRef(path),\n getAll: async (...refs: any[]) => refs.map((ref) => snapshot(ref.path)),\n runTransaction: async (body) => {\n for (let attempt = 0; attempt < 5; attempt += 1) {\n const { writes, reads, api } = transactionOrBatch()\n const result = await body(api)\n if (!unchanged(reads)) continue\n commitAll(writes)\n return result\n }\n throw grpcError(10, 'ABORTED: too much contention')\n },\n batch: () => {\n const { writes, api } = transactionOrBatch()\n return { ...api, commit: async () => commitAll(writes) }\n },\n bulkWriter: () => {\n const queued: Array<{ write: Write; resolve: (value: unknown) => void; reject: (error: unknown) => void }> = []\n const enqueue = (write: Write) =>\n new Promise((resolve, reject) => queued.push({ write, resolve, reject }))\n const drain = async () => {\n while (queued.length) {\n const next = queued.shift() as (typeof queued)[number]\n try {\n next.resolve(run(next.write))\n } catch (error) {\n next.reject(error)\n }\n }\n }\n return {\n create: (ref: any, data: Data) => enqueue({ type: 'create', path: ref.path, data }),\n set: (ref: any, data: Data, setOptions?: { merge?: boolean }) =>\n enqueue({ type: 'set', path: ref.path, data, merge: setOptions?.merge === true }),\n update: (ref: any, data: Data, precondition?: { lastUpdateTime?: Timestamp }) =>\n enqueue({ type: 'update', path: ref.path, data, precondition }),\n delete: (ref: any, precondition?: { lastUpdateTime?: Timestamp }) =>\n enqueue({ type: 'delete', path: ref.path, precondition }),\n onWriteError: () => undefined,\n flush: drain,\n close: drain,\n }\n },\n seed: (path, data) => {\n const at = Timestamp.fromMillis(nowMs)\n const existing = store.get(path)\n store.set(path, { data: clone(data), createTime: existing?.createTime ?? at, updateTime: stamp() })\n },\n read: (path) => {\n const stored = store.get(path)\n return stored ? clone(stored.data) : undefined\n },\n docs: (collectionPath) => {\n const prefix = `${collectionPath}/`\n const out: Record<string, Data> = {}\n for (const [path, stored] of store) {\n const rest = path.slice(prefix.length)\n if (path.startsWith(prefix) && !rest.includes('/')) out[rest] = clone(stored.data)\n }\n return out\n },\n writes: () => writeCount,\n resetWrites: () => {\n writeCount = 0\n },\n setNow: (ms) => {\n nowMs = ms\n },\n failWritesTo: (path, error) => {\n if (error) failing.set(path, error)\n else failing.delete(path)\n },\n }\n return firestore\n}\n"],"names":["FieldValue","Timestamp","grpcError","code","message","Object","assign","Error","isTransform","value","clone","Array","isArray","map","item","out","key","entries","fieldName","field","segments","join","String","valueAt","data","path","node","segment","split","undefined","rank","compare","a","b","byRank","seconds","nanoseconds","Number","left","JSON","stringify","right","equal","matches","id","filter","actual","op","some","candidate","queryFakeFirestore","seed","options","store","Map","nowMs","Date","UTC","tick","writeCount","failing","autoIds","stamp","fromMillis","nextAutoId","padStart","at","set","createTime","updateTime","put","target","previous","head","rest","length","child","next","current","methodName","operand","elements","base","element","push","materialize","mergeInto","source","refusal","write","failure","get","existing","type","precondition","lastUpdateTime","isEqual","apply","delete","merge","snapshot","stored","pop","ref","docRef","exists","collection","name","collectionRef","setOptions","run","update","create","other","defineProperty","slice","writeTime","execute","state","prefix","rows","startsWith","includes","row","filters","every","orders","order","direction","keyOf","sort","result","after","cursor","index","signed","limit","query","where","orderBy","startAfter","values","count","total","Math","min","docs","size","empty","forEach","fn","doc","add","transactionOrBatch","writes","reads","read","has","api","getAll","refs","unchanged","seen","now","commitAll","saved","error","clear","firestore","runTransaction","body","attempt","batch","commit","bulkWriter","queued","enqueue","Promise","resolve","reject","drain","shift","onWriteError","flush","close","collectionPath","resetWrites","setNow","ms","failWritesTo"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/test-firestore-queries.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A Firestore stand-in that QUERIES (AGL-3320). Test-only; deliberately not\n * exported from the barrel, like `test-firestore.ts` beside it.\n *\n * That fake holds documents and nothing else, which is enough for code that\n * reads a document by id. A consent group change pages whole subcollections\n * by document id, filters on nested map fields, counts, writes through a\n * `BulkWriter` with `lastUpdateTime` preconditions and takes transactions —\n * and its correctness properties (a second pass writes nothing, a resumed\n * pass skips nothing) are properties of exactly those mechanics, so a fake\n * that skipped them would be asserting itself.\n *\n * What is modeled, and is load-bearing:\n *\n * - A document missing a field never matches a filter or an ordering on it,\n * including `!=` — the rule that makes an `orderBy` on a data field drop\n * documents, and the reason every scan here pages by `__name__`.\n * - `update()` needs the document to exist, replaces the value at each\n * (dotted) path whole, and fails its `lastUpdateTime` precondition when\n * the document was written since; `create()` fails on an existing one.\n * The error codes are gRPC's: 5, 6, 9. A `set()` with `mergeFields`\n * replaces the value at each named path whole, and creates the document\n * when it is missing.\n * - `FieldValue` sentinels are applied, not stored: `delete`,\n * `serverTimestamp` (the fake clock), `increment`, `arrayUnion`,\n * `arrayRemove`.\n * - Every write moves `updateTime`, and a `BulkWriter` runs its queued\n * writes when it is flushed or closed, rejecting each failed write's\n * promise the way the real one does.\n * - A transaction is SERIALIZABLE: one whose reads changed before it\n * committed runs its body again, as the SDK retries a contended one, so\n * two transactions racing on one document cannot both act on what they\n * read. That is the property a start's \"no change in flight\" rests on.\n *\n * What is not: a `BulkWriter`'s own retries, which nothing here branches on.\n */\n\nimport { FieldPath, FieldValue, Timestamp } from 'firebase-admin/firestore'\n\ntype Data = Record<string, any>\n\ninterface Stored {\n data: Data\n createTime: Timestamp\n updateTime: Timestamp\n}\n\n/** A Firestore-style error: the gRPC code the SDK attaches. */\nfunction grpcError(code: number, message: string): Error {\n return Object.assign(new Error(message), { code })\n}\n\nconst isTransform = (value: unknown): value is FieldValue & { methodName: string } =>\n value instanceof FieldValue\n\nfunction clone<T>(value: T): T {\n if (value === null || typeof value !== 'object') return value\n if (value instanceof Timestamp || value instanceof FieldValue) return value\n if (Array.isArray(value)) return value.map((item) => clone(item)) as T\n const out: Data = {}\n for (const [key, item] of Object.entries(value as Data)) out[key] = clone(item)\n return out as T\n}\n\n/** A field as a plain dotted path; a `FieldPath`'s segments are joined unquoted. */\nfunction fieldName(field: string | FieldPath): string {\n if (typeof field === 'string') return field\n const segments = (field as unknown as { segments?: string[] }).segments\n return Array.isArray(segments) ? segments.join('.') : String(field)\n}\n\n/** The value at a dotted path, or `undefined` when any segment is missing. */\nfunction valueAt(data: Data | undefined, path: string): unknown {\n let node: unknown = data\n for (const segment of path.split('.')) {\n if (!node || typeof node !== 'object' || Array.isArray(node)) return undefined\n node = (node as Data)[segment]\n }\n return node\n}\n\n/** Firestore's cross-type order: null, booleans, numbers, timestamps, strings, the rest. */\nfunction rank(value: unknown): number {\n if (value === null) return 0\n if (typeof value === 'boolean') return 1\n if (typeof value === 'number') return 2\n if (value instanceof Timestamp) return 3\n if (typeof value === 'string') return 4\n if (Array.isArray(value)) return 6\n return 7\n}\n\nfunction compare(a: unknown, b: unknown): number {\n const byRank = rank(a) - rank(b)\n if (byRank !== 0) return byRank\n if (a instanceof Timestamp && b instanceof Timestamp) {\n return a.seconds - b.seconds || a.nanoseconds - b.nanoseconds\n }\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n if (typeof a === 'boolean' && typeof b === 'boolean') return Number(a) - Number(b)\n const left = JSON.stringify(a)\n const right = JSON.stringify(b)\n return left < right ? -1 : left > right ? 1 : 0\n}\n\nconst equal = (a: unknown, b: unknown) => rank(a) === rank(b) && compare(a, b) === 0\n\ninterface Filter {\n field: string\n op: string\n value: unknown\n}\n\nfunction matches(id: string, data: Data, filter: Filter): boolean {\n const actual = filter.field === '__name__' ? id : valueAt(data, filter.field)\n if (actual === undefined) return false\n const { op, value } = filter\n switch (op) {\n case '==':\n return equal(actual, value)\n case '!=':\n return actual !== null && !equal(actual, value)\n case '<':\n return rank(actual) === rank(value) && compare(actual, value) < 0\n case '<=':\n return rank(actual) === rank(value) && compare(actual, value) <= 0\n case '>':\n return rank(actual) === rank(value) && compare(actual, value) > 0\n case '>=':\n return rank(actual) === rank(value) && compare(actual, value) >= 0\n case 'in':\n return (value as unknown[]).some((candidate) => equal(actual, candidate))\n case 'not-in':\n return actual !== null && !(value as unknown[]).some((candidate) => equal(actual, candidate))\n case 'array-contains':\n return Array.isArray(actual) && actual.some((item) => equal(item, value))\n case 'array-contains-any':\n return (\n Array.isArray(actual) &&\n actual.some((item) => (value as unknown[]).some((candidate) => equal(item, candidate)))\n )\n default:\n throw new Error(`test-firestore-queries: unsupported operator ${op}`)\n }\n}\n\nexport interface QueryFakeFirestore {\n collection(path: string): any\n doc(path: string): any\n getAll(...refs: any[]): Promise<any[]>\n runTransaction<T>(body: (transaction: any) => Promise<T>): Promise<T>\n batch(): any\n bulkWriter(): any\n /** Seed or overwrite one document, with no transforms and no write counted. */\n seed(path: string, data: Data): void\n /** One document's data as stored, or `undefined`. */\n read(path: string): Data | undefined\n /** Every document directly in a collection, by id. */\n docs(collectionPath: string): Record<string, Data>\n /** Committed writes since the last reset. */\n writes(): number\n resetWrites(): void\n /**\n * Read ROUND TRIPS since the last reset: one per document `get`, query\n * `get`, `count()`, `getAll` or transaction read, however many documents\n * it returned — the unit a request's latency is spent in.\n */\n reads(): number\n resetReads(): void\n /** The clock `serverTimestamp()` and every `updateTime` read. */\n setNow(ms: number): void\n /** Makes the next writes to `path` fail with `error` — a stand-in for an outage. */\n failWritesTo(path: string, error: Error | null): void\n}\n\nexport function queryFakeFirestore(\n seed: Record<string, Data> = {},\n options: { nowMs?: number } = {},\n): QueryFakeFirestore {\n const store = new Map<string, Stored>()\n let nowMs = options.nowMs ?? Date.UTC(2026, 8, 24)\n /** Keeps every write's `updateTime` distinct even on a frozen clock. */\n let tick = 0\n let writeCount = 0\n let readCount = 0\n const failing = new Map<string, Error>()\n let autoIds = 0\n\n const stamp = () => Timestamp.fromMillis(nowMs + (tick += 0.001))\n const nextAutoId = () => {\n autoIds += 1\n return String(autoIds).padStart(8, '0')\n }\n\n for (const [path, data] of Object.entries(seed)) {\n const at = Timestamp.fromMillis(nowMs)\n store.set(path, { data: clone(data), createTime: at, updateTime: at })\n }\n\n /** Applies a write's value at one path of `target`, honoring transforms. */\n function put(target: Data, path: string[], value: unknown, previous: unknown): void {\n const [head, ...rest] = path\n if (rest.length) {\n const child = target[head]\n const next = child && typeof child === 'object' && !Array.isArray(child) ? child : {}\n target[head] = next\n put(next, rest, value, valueAt(previous as Data, rest.join('.')))\n return\n }\n if (isTransform(value)) {\n const current = target[head]\n switch (value.methodName) {\n case 'FieldValue.delete':\n delete target[head]\n return\n case 'FieldValue.serverTimestamp':\n target[head] = Timestamp.fromMillis(nowMs)\n return\n case 'FieldValue.increment': {\n const operand = (value as unknown as { operand: number }).operand\n target[head] = (typeof current === 'number' ? current : 0) + operand\n return\n }\n case 'FieldValue.arrayUnion': {\n const elements = (value as unknown as { elements: unknown[] }).elements\n const base = Array.isArray(current) ? [...current] : []\n for (const element of elements) {\n if (!base.some((item) => equal(item, element))) base.push(element)\n }\n target[head] = base\n return\n }\n case 'FieldValue.arrayRemove': {\n const elements = (value as unknown as { elements: unknown[] }).elements\n target[head] = (Array.isArray(current) ? current : []).filter(\n (item) => !elements.some((element) => equal(item, element)),\n )\n return\n }\n default:\n throw new Error(`test-firestore-queries: unsupported transform ${value.methodName}`)\n }\n }\n target[head] = materialize(value)\n }\n\n /** A written value with any nested transforms resolved against nothing. */\n function materialize(value: unknown): unknown {\n if (value === undefined) throw new Error('test-firestore-queries: undefined is not a Firestore value')\n if (value === null || typeof value !== 'object') return value\n if (value instanceof Timestamp) return value\n if (isTransform(value)) {\n if (value.methodName === 'FieldValue.serverTimestamp') return Timestamp.fromMillis(nowMs)\n throw new Error(`test-firestore-queries: ${value.methodName} inside a nested value`)\n }\n if (Array.isArray(value)) return value.map((item) => materialize(item))\n const out: Data = {}\n for (const [key, item] of Object.entries(value as Data)) {\n if (isTransform(item) && item.methodName === 'FieldValue.delete') continue\n out[key] = materialize(item)\n }\n return out\n }\n\n function mergeInto(target: Data, source: Data): void {\n for (const [key, value] of Object.entries(source)) {\n if (\n value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n !(value instanceof Timestamp) &&\n !isTransform(value)\n ) {\n const child = target[key]\n const next = child && typeof child === 'object' && !Array.isArray(child) && !(child instanceof Timestamp)\n ? child\n : {}\n target[key] = next\n mergeInto(next, value as Data)\n } else {\n put(target, [key], value, target[key])\n }\n }\n }\n\n type SetOptions = { merge?: boolean; mergeFields?: Array<string | FieldPath> }\n\n type Write =\n | { type: 'set'; path: string; data: Data; merge: boolean; fields?: string[] }\n | { type: 'update'; path: string; data: Data; precondition?: { lastUpdateTime?: Timestamp } }\n | { type: 'create'; path: string; data: Data }\n | { type: 'delete'; path: string; precondition?: { lastUpdateTime?: Timestamp } }\n\n /** Why a write cannot apply, or `null`. */\n function refusal(write: Write): Error | null {\n const failure = failing.get(write.path)\n if (failure) return failure\n const existing = store.get(write.path)\n if (write.type === 'create' && existing) {\n return grpcError(6, `ALREADY_EXISTS: ${write.path}`)\n }\n if (write.type === 'update' && !existing) {\n return grpcError(5, `NOT_FOUND: ${write.path}`)\n }\n if ((write.type === 'update' || write.type === 'delete') && write.precondition?.lastUpdateTime) {\n if (!existing || !existing.updateTime.isEqual(write.precondition.lastUpdateTime)) {\n return grpcError(9, `FAILED_PRECONDITION: ${write.path}`)\n }\n }\n return null\n }\n\n function apply(write: Write): Timestamp {\n const existing = store.get(write.path)\n const at = stamp()\n writeCount += 1\n if (write.type === 'delete') {\n store.delete(write.path)\n return at\n }\n if (write.type === 'update') {\n const data = clone(existing?.data ?? {})\n for (const [path, value] of Object.entries(write.data)) {\n put(data, path.split('.'), value, valueAt(existing?.data, path))\n }\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n if (write.type === 'set' && write.fields) {\n const data = clone(existing?.data ?? {})\n for (const path of write.fields) {\n put(data, path.split('.'), clone(valueAt(write.data, path)), valueAt(existing?.data, path))\n }\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n if (write.type === 'set' && write.merge) {\n const data = clone(existing?.data ?? {})\n mergeInto(data, write.data)\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n const data: Data = {}\n mergeInto(data, write.data)\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n\n function setWrite(path: string, data: Data, setOptions?: SetOptions): Write {\n return {\n type: 'set',\n path,\n data,\n merge: setOptions?.merge === true,\n ...(setOptions?.mergeFields ? { fields: setOptions.mergeFields.map(fieldName) } : {}),\n }\n }\n\n function snapshot(path: string): any {\n const stored = store.get(path)\n const id = path.split('/').pop() as string\n return {\n id,\n ref: docRef(path),\n exists: stored !== undefined,\n data: () => (stored ? clone(stored.data) : undefined),\n get: (field: string | FieldPath) => clone(valueAt(stored?.data, fieldName(field))),\n updateTime: stored?.updateTime,\n createTime: stored?.createTime,\n }\n }\n\n function docRef(path: string): any {\n const segments = path.split('/')\n if (segments.length % 2 !== 0) throw new Error(`test-firestore-queries: ${path} is not a document path`)\n const ref: any = {\n id: segments[segments.length - 1],\n path,\n collection: (name: string) => collectionRef(`${path}/${name}`),\n get: async () => {\n readCount += 1\n return snapshot(path)\n },\n set: async (data: Data, setOptions?: SetOptions) => run(setWrite(path, data, setOptions)),\n update: async (data: Data, precondition?: { lastUpdateTime?: Timestamp }) =>\n run({ type: 'update', path, data, precondition }),\n create: async (data: Data) => run({ type: 'create', path, data }),\n delete: async (precondition?: { lastUpdateTime?: Timestamp }) =>\n run({ type: 'delete', path, precondition }),\n isEqual: (other: { path?: string }) => other?.path === path,\n }\n Object.defineProperty(ref, 'parent', {\n get: () => collectionRef(segments.slice(0, -1).join('/')),\n })\n Object.defineProperty(ref, 'firestore', { get: () => firestore })\n return ref\n }\n\n function run(write: Write): { writeTime: Timestamp } {\n const failure = refusal(write)\n if (failure) throw failure\n return { writeTime: apply(write) }\n }\n\n interface QueryState {\n path: string\n filters: Filter[]\n orders: Array<{ field: string; direction: 'asc' | 'desc' }>\n after: unknown[] | null\n limit: number | null\n }\n\n function execute(state: QueryState): any[] {\n const prefix = `${state.path}/`\n let rows = [...store.entries()]\n .filter(([path]) => path.startsWith(prefix) && !path.slice(prefix.length).includes('/'))\n .map(([path, stored]) => ({ path, id: path.slice(prefix.length), data: stored.data }))\n .filter((row) => state.filters.every((filter) => matches(row.id, row.data, filter)))\n const orders = [...state.orders]\n if (!orders.some((order) => order.field === '__name__')) {\n orders.push({ field: '__name__', direction: 'asc' })\n }\n rows = rows.filter((row) =>\n orders.every((order) => order.field === '__name__' || valueAt(row.data, order.field) !== undefined),\n )\n const keyOf = (row: { id: string; data: Data }, field: string) =>\n field === '__name__' ? row.id : valueAt(row.data, field)\n rows.sort((a, b) => {\n for (const order of orders) {\n const result = compare(keyOf(a, order.field), keyOf(b, order.field))\n if (result !== 0) return order.direction === 'desc' ? -result : result\n }\n return 0\n })\n if (state.after) {\n const cursor = state.after\n rows = rows.filter((row) => {\n for (let index = 0; index < cursor.length; index += 1) {\n const order = orders[index]\n const result = compare(keyOf(row, order.field), cursor[index])\n const signed = order.direction === 'desc' ? -result : result\n if (signed !== 0) return signed > 0\n }\n return false\n })\n }\n if (state.limit !== null) rows = rows.slice(0, state.limit)\n return rows.map((row) => snapshot(row.path))\n }\n\n function query(state: QueryState): any {\n return {\n where: (field: string | FieldPath, op: string, value: unknown) =>\n query({ ...state, filters: [...state.filters, { field: fieldName(field), op, value }] }),\n orderBy: (field: string | FieldPath, direction: 'asc' | 'desc' = 'asc') =>\n query({ ...state, orders: [...state.orders, { field: fieldName(field), direction }] }),\n startAfter: (...values: unknown[]) => query({ ...state, after: values }),\n limit: (limit: number) => query({ ...state, limit }),\n count: () => ({\n get: async () => {\n readCount += 1\n const total = execute({ ...state, limit: null, after: state.after }).length\n const count = state.limit === null ? total : Math.min(total, state.limit)\n return { data: () => ({ count }) }\n },\n }),\n get: async () => {\n readCount += 1\n const docs = execute(state)\n return { docs, size: docs.length, empty: docs.length === 0, forEach: (fn: any) => docs.forEach(fn) }\n },\n }\n }\n\n function collectionRef(path: string): any {\n const base = query({ path, filters: [], orders: [], after: null, limit: null })\n const segments = path.split('/')\n return {\n get parent() {\n return segments.length > 1 ? docRef(segments.slice(0, -1).join('/')) : null\n },\n get firestore() {\n return firestore\n },\n ...base,\n id: segments[segments.length - 1],\n path,\n doc: (id: string) => docRef(`${path}/${id ?? nextAutoId()}`),\n add: async (data: Data) => {\n const ref = docRef(`${path}/${nextAutoId()}`)\n run({ type: 'create', path: ref.path, data })\n return ref\n },\n }\n }\n\n function transactionOrBatch(): { writes: Write[]; reads: Map<string, Timestamp | null>; api: any } {\n const writes: Write[] = []\n const reads = new Map<string, Timestamp | null>()\n const read = (path: string) => {\n if (!reads.has(path)) reads.set(path, store.get(path)?.updateTime ?? null)\n return snapshot(path)\n }\n const api: any = {\n get: async (target: any) => {\n if (typeof target?.path === 'string' && target.path.split('/').length % 2 === 0) {\n readCount += 1\n return read(target.path)\n }\n return target.get()\n },\n getAll: async (...refs: any[]) => {\n readCount += 1\n return refs.map((ref) => read(ref.path))\n },\n set: (ref: any, data: Data, setOptions?: SetOptions) => {\n writes.push(setWrite(ref.path, data, setOptions))\n return api\n },\n update: (ref: any, data: Data, precondition?: { lastUpdateTime?: Timestamp }) => {\n writes.push({ type: 'update', path: ref.path, data, precondition })\n return api\n },\n create: (ref: any, data: Data) => {\n writes.push({ type: 'create', path: ref.path, data })\n return api\n },\n delete: (ref: any, precondition?: { lastUpdateTime?: Timestamp }) => {\n writes.push({ type: 'delete', path: ref.path, precondition })\n return api\n },\n }\n return { writes, reads, api }\n }\n\n /** Whether every document a transaction read is still as it read it. */\n function unchanged(reads: Map<string, Timestamp | null>): boolean {\n for (const [path, seen] of reads) {\n const now = store.get(path)?.updateTime ?? null\n if (seen === null ? now !== null : now === null || !now.isEqual(seen)) return false\n }\n return true\n }\n\n /** All or nothing, in order: every write is checked against the state the earlier ones leave. */\n function commitAll(writes: Write[]): void {\n const saved = new Map(store)\n const count = writeCount\n try {\n for (const write of writes) run(write)\n } catch (error) {\n store.clear()\n for (const [path, stored] of saved) store.set(path, stored)\n writeCount = count\n throw error\n }\n }\n\n const firestore: QueryFakeFirestore = {\n collection: (path: string) => collectionRef(path),\n doc: (path: string) => docRef(path),\n getAll: async (...refs: any[]) => {\n readCount += 1\n return refs.map((ref) => snapshot(ref.path))\n },\n runTransaction: async (body) => {\n for (let attempt = 0; attempt < 5; attempt += 1) {\n const { writes, reads, api } = transactionOrBatch()\n const result = await body(api)\n if (!unchanged(reads)) continue\n commitAll(writes)\n return result\n }\n throw grpcError(10, 'ABORTED: too much contention')\n },\n batch: () => {\n const { writes, api } = transactionOrBatch()\n return { ...api, commit: async () => commitAll(writes) }\n },\n bulkWriter: () => {\n const queued: Array<{ write: Write; resolve: (value: unknown) => void; reject: (error: unknown) => void }> = []\n const enqueue = (write: Write) =>\n new Promise((resolve, reject) => queued.push({ write, resolve, reject }))\n const drain = async () => {\n while (queued.length) {\n const next = queued.shift() as (typeof queued)[number]\n try {\n next.resolve(run(next.write))\n } catch (error) {\n next.reject(error)\n }\n }\n }\n return {\n create: (ref: any, data: Data) => enqueue({ type: 'create', path: ref.path, data }),\n set: (ref: any, data: Data, setOptions?: SetOptions) => enqueue(setWrite(ref.path, data, setOptions)),\n update: (ref: any, data: Data, precondition?: { lastUpdateTime?: Timestamp }) =>\n enqueue({ type: 'update', path: ref.path, data, precondition }),\n delete: (ref: any, precondition?: { lastUpdateTime?: Timestamp }) =>\n enqueue({ type: 'delete', path: ref.path, precondition }),\n onWriteError: () => undefined,\n flush: drain,\n close: drain,\n }\n },\n seed: (path, data) => {\n const at = Timestamp.fromMillis(nowMs)\n const existing = store.get(path)\n store.set(path, { data: clone(data), createTime: existing?.createTime ?? at, updateTime: stamp() })\n },\n read: (path) => {\n const stored = store.get(path)\n return stored ? clone(stored.data) : undefined\n },\n docs: (collectionPath) => {\n const prefix = `${collectionPath}/`\n const out: Record<string, Data> = {}\n for (const [path, stored] of store) {\n const rest = path.slice(prefix.length)\n if (path.startsWith(prefix) && !rest.includes('/')) out[rest] = clone(stored.data)\n }\n return out\n },\n writes: () => writeCount,\n resetWrites: () => {\n writeCount = 0\n },\n reads: () => readCount,\n resetReads: () => {\n readCount = 0\n },\n setNow: (ms) => {\n nowMs = ms\n },\n failWritesTo: (path, error) => {\n if (error) failing.set(path, error)\n else failing.delete(path)\n },\n }\n return firestore\n}\n"],"names":["FieldValue","Timestamp","grpcError","code","message","Object","assign","Error","isTransform","value","clone","Array","isArray","map","item","out","key","entries","fieldName","field","segments","join","String","valueAt","data","path","node","segment","split","undefined","rank","compare","a","b","byRank","seconds","nanoseconds","Number","left","JSON","stringify","right","equal","matches","id","filter","actual","op","some","candidate","queryFakeFirestore","seed","options","store","Map","nowMs","Date","UTC","tick","writeCount","readCount","failing","autoIds","stamp","fromMillis","nextAutoId","padStart","at","set","createTime","updateTime","put","target","previous","head","rest","length","child","next","current","methodName","operand","elements","base","element","push","materialize","mergeInto","source","refusal","write","failure","get","existing","type","precondition","lastUpdateTime","isEqual","apply","delete","fields","merge","setWrite","setOptions","mergeFields","snapshot","stored","pop","ref","docRef","exists","collection","name","collectionRef","run","update","create","other","defineProperty","slice","firestore","writeTime","execute","state","prefix","rows","startsWith","includes","row","filters","every","orders","order","direction","keyOf","sort","result","after","cursor","index","signed","limit","query","where","orderBy","startAfter","values","count","total","Math","min","docs","size","empty","forEach","fn","parent","doc","add","transactionOrBatch","writes","reads","read","has","api","getAll","refs","unchanged","seen","now","commitAll","saved","error","clear","runTransaction","body","attempt","batch","commit","bulkWriter","queued","enqueue","Promise","resolve","reject","drain","shift","onWriteError","flush","close","collectionPath","resetWrites","resetReads","setNow","ms","failWritesTo"],"mappings":"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"}
|
|
@@ -15,11 +15,16 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/ /**
|
|
18
|
-
*
|
|
18
|
+
* A RECIPIENT'S STREAMS, WRITTEN — the durable half of the mail rail's
|
|
19
|
+
* per-stream consent: an opt-out, and the double opt-in's pending and
|
|
20
|
+
* confirmed marks.
|
|
19
21
|
*
|
|
20
|
-
* `
|
|
21
|
-
* states an entry can be in; this writes them, because writing them
|
|
22
|
-
* Firestore.
|
|
22
|
+
* `subscription-topics.ts` in the framework states the policy and owns the
|
|
23
|
+
* three states an entry can be in; this writes them, because writing them
|
|
24
|
+
* needs Firestore. Every sender's stream is written the same way — a
|
|
25
|
+
* newsletter signup asking for a confirmation, a sequence recipient asking to
|
|
26
|
+
* hear no more sales mail — which is why it sits beside the suppression list
|
|
27
|
+
* rather than in any one plugin.
|
|
23
28
|
*
|
|
24
29
|
* ## The record it writes is the one the send path already reads
|
|
25
30
|
*
|
|
@@ -40,8 +45,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
40
45
|
* needs, which is the same argument `writeTopicOptOuts` makes for keeping an
|
|
41
46
|
* opt-out entry after somebody rejoins.
|
|
42
47
|
*/ import { FieldValue } from "firebase-admin/firestore";
|
|
43
|
-
import { doubleOptInExpired, readTopicSubscriptionState, TOPIC_OPT_OUTS_SUBCOLLECTION } from "@aglyn/aglyn/app-utils/
|
|
44
|
-
import { isEmailTopicId } from "@aglyn/aglyn/app-utils/email-topics";
|
|
48
|
+
import { doubleOptInExpired, isSubscriptionTopicId, readTopicSubscriptionState, TOPIC_OPT_OUTS_SUBCOLLECTION } from "@aglyn/aglyn/app-utils/subscription-topics";
|
|
45
49
|
import firebaseAdmin from "./firebase-admin.js";
|
|
46
50
|
import { emailSuppressionKey, releaseEmailForConfirmedOptIn } from "./email-suppression.js";
|
|
47
51
|
const defaultFirestore = ()=>firebaseAdmin.app().firestore();
|
|
@@ -70,7 +74,7 @@ function optOutDoc(hostId, key, firestore) {
|
|
|
70
74
|
*/ export async function recordPendingTopicConfirmation(hostId, email, topicId, options) {
|
|
71
75
|
var _ref, _ref1;
|
|
72
76
|
const key = emailSuppressionKey(email);
|
|
73
|
-
if (!key || !hostId || !
|
|
77
|
+
if (!key || !hostId || !isSubscriptionTopicId(topicId)) {
|
|
74
78
|
return {
|
|
75
79
|
result: 'unusable',
|
|
76
80
|
pendingAtMs: null
|
|
@@ -166,7 +170,7 @@ function optOutDoc(hostId, key, firestore) {
|
|
|
166
170
|
*/ export async function confirmTopicSubscription(hostId, email, topicId, options) {
|
|
167
171
|
var _ref, _ref1;
|
|
168
172
|
const key = emailSuppressionKey(email);
|
|
169
|
-
if (!key || !hostId || !
|
|
173
|
+
if (!key || !hostId || !isSubscriptionTopicId(topicId)) return 'unusable';
|
|
170
174
|
const nowMs = (_ref = options == null ? void 0 : options.nowMs) != null ? _ref : Date.now();
|
|
171
175
|
const ref = optOutDoc(hostId, key, options == null ? void 0 : options.firestore);
|
|
172
176
|
/*
|
|
@@ -230,7 +234,7 @@ function optOutDoc(hostId, key, firestore) {
|
|
|
230
234
|
const snapshot = await (firestore != null ? firestore : defaultFirestore()).collection('hosts').doc(hostId).get();
|
|
231
235
|
return snapshot.exists && snapshot.get('emailDoubleOptIn') === true;
|
|
232
236
|
} catch (error) {
|
|
233
|
-
console.error('[
|
|
237
|
+
console.error('[topic-subscriptions] double opt-in setting read failed', error);
|
|
234
238
|
return false;
|
|
235
239
|
}
|
|
236
240
|
}
|
|
@@ -255,7 +259,7 @@ function optOutDoc(hostId, key, firestore) {
|
|
|
255
259
|
*/ export async function recordTopicOptOut(hostId, email, topicId, options) {
|
|
256
260
|
var _ref;
|
|
257
261
|
const key = emailSuppressionKey(email);
|
|
258
|
-
if (!key || !hostId || !
|
|
262
|
+
if (!key || !hostId || !isSubscriptionTopicId(topicId)) return 'unusable';
|
|
259
263
|
const db = (_ref = options == null ? void 0 : options.firestore) != null ? _ref : defaultFirestore();
|
|
260
264
|
const ref = optOutDoc(hostId, key, db);
|
|
261
265
|
return db.runTransaction(async (transaction)=>{
|
|
@@ -282,4 +286,4 @@ function optOutDoc(hostId, key, firestore) {
|
|
|
282
286
|
});
|
|
283
287
|
}
|
|
284
288
|
|
|
285
|
-
//# sourceMappingURL=
|
|
289
|
+
//# sourceMappingURL=topic-subscriptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/topic-subscriptions.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A RECIPIENT'S STREAMS, WRITTEN — the durable half of the mail rail's\n * per-stream consent: an opt-out, and the double opt-in's pending and\n * confirmed marks.\n *\n * `subscription-topics.ts` in the framework states the policy and owns the\n * three states an entry can be in; this writes them, because writing them\n * needs Firestore. Every sender's stream is written the same way — a\n * newsletter signup asking for a confirmation, a sequence recipient asking to\n * hear no more sales mail — which is why it sits beside the suppression list\n * rather than in any one plugin.\n *\n * ## The record it writes is the one the send path already reads\n *\n * `hosts/{hostId}/topicOptOuts/{emailKey}` holds a map keyed by topic, and\n * `filterTopicSendable` reads it on every topic-filtered send. A pending\n * confirmation is therefore a THIRD state of a fact the send path already\n * consults rather than a new document nobody has a reason to look at — which\n * is the difference between ActiveCampaign's quarantine, where an unconfirmed\n * subscriber genuinely cannot be mailed, and Customer.io's recipe, whose own\n * documentation warns that it \"doesn't automatically check this attribute\n * before sending messages\".\n *\n * ## Nothing here is a deletion\n *\n * A confirmation stamps `confirmedAt` beside the `pendingAt` it answers,\n * rather than clearing the pending mark. The pair IS the evidence — when we\n * asked and when they answered — and it is the evidence a consent question\n * needs, which is the same argument `writeTopicOptOuts` makes for keeping an\n * opt-out entry after somebody rejoins.\n */\n\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n doubleOptInExpired,\n isSubscriptionTopicId,\n readTopicSubscriptionState,\n TOPIC_OPT_OUTS_SUBCOLLECTION,\n type TopicSubscriptionEntry,\n} from '@aglyn/aglyn/app-utils/subscription-topics'\nimport firebaseAdmin from './firebase-admin'\nimport {\n emailSuppressionKey,\n releaseEmailForConfirmedOptIn,\n} from './email-suppression'\n\nconst defaultFirestore = () => firebaseAdmin.app().firestore()\n\nfunction optOutDoc(hostId: string, key: string, firestore?: any) {\n return (firestore ?? defaultFirestore())\n .collection('hosts')\n .doc(hostId)\n .collection(TOPIC_OPT_OUTS_SUBCOLLECTION)\n .doc(key)\n}\n\n/** What asking for a confirmation did. */\nexport type PendingConfirmationResult =\n /** Recorded. The address is not mailable about this topic until confirmed. */\n | 'pending'\n /** Already confirmed, or never needed confirming. Nothing was written. */\n | 'already-subscribed'\n /**\n * They left this stream. Nothing was written and nothing may be sent.\n *\n * A signup form cannot re-arm mail to somebody who unsubscribed by asking\n * them again: the confirmation request is itself a message, and sending it\n * would be mailing somebody who told this site to stop.\n */\n | 'opted-out'\n /** Not an address, or not a topic id. */\n | 'unusable'\n\n/**\n * Puts an address in the quarantine for one topic and returns the moment it\n * entered, which is what the confirmation link is signed and dated from.\n *\n * ## Re-asking does not restamp — inside the window\n *\n * A second signup from the same address inside the window keeps the original\n * `pendingAt`, so the 72-hour expiry measures from when we first asked. The\n * alternative lets somebody — or a script — hold an address in a permanently\n * fresh pending state by resubmitting a form, which is a way to keep a\n * confirmation link alive indefinitely.\n *\n * Once the window has passed, a signup is a new question and is stamped now.\n * An expired request admits nobody and its link no longer acts, so keeping\n * its moment would date the new link from the old one — expired before it is\n * sent — and \"sign up again\", which the expired-link page and the docs tell\n * the person to do, could never succeed. With a consent group waiting on its\n * sites' confirmations, that stranded request would hold the stream from\n * every site of the group.\n */\nexport async function recordPendingTopicConfirmation(\n hostId: string,\n email: string,\n topicId: string,\n options?: { nowMs?: number; firestore?: any },\n): Promise<{ result: PendingConfirmationResult; pendingAtMs: number | null }> {\n const key = emailSuppressionKey(email)\n if (!key || !hostId || !isSubscriptionTopicId(topicId)) {\n return { result: 'unusable', pendingAtMs: null }\n }\n const nowMs = options?.nowMs ?? Date.now()\n const ref = optOutDoc(hostId, key, options?.firestore)\n const address = String(email).trim().toLowerCase()\n let result: PendingConfirmationResult = 'pending'\n let pendingAtMs: number | null = nowMs\n await (options?.firestore ?? defaultFirestore()).runTransaction(\n async (transaction: any) => {\n const snapshot = await transaction.get(ref)\n const stored = ((snapshot.exists ? snapshot.get('topics') : null) ??\n {}) as Record<string, TopicSubscriptionEntry>\n const existing = stored[topicId]\n const state = readTopicSubscriptionState(existing)\n if (state === 'opted-out') {\n result = 'opted-out'\n pendingAtMs = null\n return\n }\n if (state === 'pending') {\n // Already in the quarantine. Keep the original moment while that\n // request can still be confirmed — see above; past it, fall through\n // and ask again.\n const askedAtMs = Number(existing?.pendingAt) || nowMs\n if (!doubleOptInExpired(askedAtMs, nowMs)) {\n result = 'pending'\n pendingAtMs = askedAtMs\n return\n }\n }\n if (existing?.confirmedAt) {\n result = 'already-subscribed'\n pendingAtMs = null\n return\n }\n transaction.set(\n ref,\n {\n email: address,\n topics: {\n ...stored,\n [topicId]: {\n ...(existing ?? {}),\n pendingAt: nowMs,\n confirmedAt: null,\n },\n },\n updatedAt: FieldValue.serverTimestamp(),\n ...(snapshot.exists\n ? {}\n : { createdAt: FieldValue.serverTimestamp() }),\n },\n { merge: true },\n )\n },\n )\n return { result, pendingAtMs }\n}\n\n/** What a click on a confirmation link did. */\nexport type ConfirmTopicResult =\n /** They are now a subscriber to this topic. */\n | 'confirmed'\n /** They already were, and a second click changes nothing. */\n | 'already-confirmed'\n /** The request is older than the window, so the link no longer acts. */\n | 'expired'\n /** They left this stream. A confirmation link may not undo that. */\n | 'opted-out'\n /** Nothing was pending for this address and topic. */\n | 'not-pending'\n /** Not an address, or not a topic id. */\n | 'unusable'\n\n/**\n * Honors a confirmation click.\n *\n * ## An expired link does not admit anybody, and does not clear anything\n *\n * `expired` leaves the record exactly as it was: still pending, still\n * unmailable. Expiry is a property of the LINK, and a lapse that quietly\n * admitted the address, or quietly cleared the request, would make the window\n * a delay somebody could wait out rather than a gate.\n *\n * ## A confirmation cannot reverse an unsubscribe\n *\n * Same rule as `releaseSiteSuppression`: the recipient's later, more explicit\n * act stands, and a link minted before it must not undo it.\n *\n * ## A confirmation DOES reverse a hard bounce\n *\n * The one thing a completed round trip is evidence of. A `bounce` entry on\n * the platform list claims mail addressed here is permanently refused, and a\n * click on a link that was delivered to that mailbox is the only evidence\n * that could refute it. Without this, an address recorded as permanently\n * bounced — often a full mailbox or a misconfigured receiving server that a\n * provider reported as permanent — can never re-subscribe by any route, and\n * the double opt-in it just completed would enrol somebody the send path then\n * silently drops.\n *\n * The refusal above and the release here are the same rule seen from two\n * sides: a stated intention stands and a measurement does not. Which reasons\n * qualify is decided by `OPT_IN_RELEASABLE_REASONS`, not here — an unsubscribe\n * or a spam complaint is never among them, or this function would be a way to\n * launder a complaint through a signup form.\n *\n * The release is best-effort and deliberately after the transaction: the\n * confirmation is what the person is waiting for, and a suppression list that\n * could not be written must not cost them the subscription they just made.\n */\nexport async function confirmTopicSubscription(\n hostId: string,\n email: string,\n topicId: string,\n options?: { nowMs?: number; firestore?: any },\n): Promise<ConfirmTopicResult> {\n const key = emailSuppressionKey(email)\n if (!key || !hostId || !isSubscriptionTopicId(topicId)) return 'unusable'\n const nowMs = options?.nowMs ?? Date.now()\n const ref = optOutDoc(hostId, key, options?.firestore)\n /*\n * The verdict comes OUT of the transaction rather than through a closure\n * variable beside it. A transaction body can be retried, so a variable the\n * caller reads afterwards describes whichever attempt ran last rather than\n * the one that committed.\n */\n const result: ConfirmTopicResult = await (\n options?.firestore ?? defaultFirestore()\n ).runTransaction(async (transaction: any): Promise<ConfirmTopicResult> => {\n const snapshot = await transaction.get(ref)\n const stored = ((snapshot.exists ? snapshot.get('topics') : null) ??\n {}) as Record<string, TopicSubscriptionEntry>\n const existing = stored[topicId]\n const state = readTopicSubscriptionState(existing)\n if (state === 'opted-out') return 'opted-out'\n if (state === 'subscribed') {\n return existing?.confirmedAt ? 'already-confirmed' : 'not-pending'\n }\n if (doubleOptInExpired(Number(existing?.pendingAt), nowMs)) {\n return 'expired'\n }\n transaction.set(\n ref,\n {\n email: String(email).trim().toLowerCase(),\n topics: {\n ...stored,\n [topicId]: { ...(existing ?? {}), confirmedAt: nowMs },\n },\n updatedAt: FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n return 'confirmed'\n })\n if (result === 'confirmed') {\n await releaseEmailForConfirmedOptIn({\n email,\n hostId,\n topicId,\n ...(options?.firestore ? { firestore: options.firestore } : {}),\n })\n }\n return result\n}\n\n/**\n * Whether this site confirms new subscriptions by default.\n *\n * One host-document field, read at the moment somebody signs up and nowhere\n * else — the send path never asks, because by then the answer is already on\n * the person's own record as a pending mark or its absence. A read on every\n * send would be a read per recipient to re-derive a decision that was made\n * once.\n *\n * Fails to `false`, which is off. A default that a failed read could switch\n * ON would quarantine every new signup on a site whose owner never asked for\n * confirmations, and they would have no way to tell why their list stopped\n * growing.\n */\nexport async function siteRequiresDoubleOptIn(\n hostId: string,\n firestore?: any,\n): Promise<boolean> {\n if (!hostId) return false\n try {\n const snapshot = await (firestore ?? defaultFirestore())\n .collection('hosts')\n .doc(hostId)\n .get()\n return snapshot.exists && snapshot.get('emailDoubleOptIn') === true\n } catch (error) {\n console.error('[topic-subscriptions] double opt-in setting read failed', error)\n return false\n }\n}\n\n/** What asking to leave one topic did. */\nexport type TopicOptOutResult =\n /** Recorded now. The address is not mailable about this topic. */\n | 'opted-out'\n /** They had already left it; the moment they left is kept. */\n | 'already-opted-out'\n /** Not an address, or not a topic id. Nothing was written. */\n | 'unusable'\n\n/**\n * Takes one address off one topic on one site (AGL-2981) — the opt-out a\n * sender that is not the preference page records: a sales email's\n * unsubscribe link, a reply that asks to be left alone.\n *\n * The same record the preference page writes (`writeTopicOptOuts` in the\n * email plugin) and the send path reads, on the same rules:\n *\n * - an address that already left keeps the moment it left — a second\n * request is not a later one;\n * - the entry is carried forward rather than replaced, so a confirmation\n * the person once gave stays on the record beside the opt-out;\n * - a rejoin stamped earlier is cleared, because leaving is now the more\n * recent act.\n *\n * A transaction, and its verdict comes out of it rather than through a\n * variable beside it: a retried attempt must not describe itself as the one\n * that committed.\n */\nexport async function recordTopicOptOut(\n hostId: string,\n email: string,\n topicId: string,\n options?: { firestore?: any },\n): Promise<TopicOptOutResult> {\n const key = emailSuppressionKey(email)\n if (!key || !hostId || !isSubscriptionTopicId(topicId)) return 'unusable'\n const db = options?.firestore ?? defaultFirestore()\n const ref = optOutDoc(hostId, key, db)\n return db.runTransaction(async (transaction: any): Promise<TopicOptOutResult> => {\n const snapshot = await transaction.get(ref)\n const stored = ((snapshot.exists ? snapshot.get('topics') : null) ??\n {}) as Record<string, TopicSubscriptionEntry>\n const previous = stored[topicId]\n if (readTopicSubscriptionState(previous) === 'opted-out') return 'already-opted-out'\n transaction.set(\n ref,\n {\n email: String(email).trim().toLowerCase(),\n topics: {\n ...stored,\n [topicId]: {\n ...(previous ?? {}),\n optedOutAt: FieldValue.serverTimestamp(),\n resubscribedAt: null,\n },\n },\n updatedAt: FieldValue.serverTimestamp(),\n ...(snapshot.exists ? {} : { createdAt: FieldValue.serverTimestamp() }),\n },\n { merge: true },\n )\n return 'opted-out'\n })\n}\n"],"names":["FieldValue","doubleOptInExpired","isSubscriptionTopicId","readTopicSubscriptionState","TOPIC_OPT_OUTS_SUBCOLLECTION","firebaseAdmin","emailSuppressionKey","releaseEmailForConfirmedOptIn","defaultFirestore","app","firestore","optOutDoc","hostId","key","collection","doc","recordPendingTopicConfirmation","email","topicId","options","result","pendingAtMs","nowMs","Date","now","ref","address","String","trim","toLowerCase","runTransaction","transaction","snapshot","get","stored","exists","existing","state","askedAtMs","Number","pendingAt","confirmedAt","set","topics","updatedAt","serverTimestamp","createdAt","merge","confirmTopicSubscription","siteRequiresDoubleOptIn","error","console","recordTopicOptOut","db","previous","optedOutAt","resubscribedAt"],"mappings":"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"}
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import { type ContactFacet, type ContactInteraction, type ContactLifecycleStage, type ContactSource } from '@aglyn/aglyn/server';
|
|
17
|
+
import { type ConsentGroup, type ContactFacet, type ContactInteraction, type ContactLifecycleStage, type ContactSource } from '@aglyn/aglyn/server';
|
|
18
18
|
import { type ResolvedCampaignTouch } from './campaign-conversion-attribution';
|
|
19
19
|
import { type MarketingConsentSource } from '@aglyn/aglyn/app-utils/marketing-consent';
|
|
20
|
+
import { getOrgForHost } from './organizations';
|
|
20
21
|
/**
|
|
21
22
|
* What an upsert did, for the callers that need to know (AGL-2602).
|
|
22
23
|
*
|
|
@@ -272,5 +273,49 @@ export interface UpsertHostContactOptions {
|
|
|
272
273
|
* capture that already happened.
|
|
273
274
|
*/
|
|
274
275
|
onCreated?: (created: HostContactCreated) => void | Promise<void>;
|
|
276
|
+
/**
|
|
277
|
+
* The lookups of a whole file, read once (AGL-3423) — see
|
|
278
|
+
* {@link prepareContactCaptureBatch}.
|
|
279
|
+
*
|
|
280
|
+
* A door capturing many addresses at once prepares one batch and hands it
|
|
281
|
+
* to every capture; each one then takes the site, the group, the person
|
|
282
|
+
* the address already is, the erasure and the band from it instead of
|
|
283
|
+
* reading them. Every decision below is made by the same lines either way.
|
|
284
|
+
* A batch prepared for another site is ignored.
|
|
285
|
+
*/
|
|
286
|
+
batch?: ContactCaptureBatch;
|
|
275
287
|
}
|
|
276
288
|
export declare function upsertHostContact(options: UpsertHostContactOptions): Promise<UpsertHostContactVerdict>;
|
|
289
|
+
/** The resolved answers a batch of captures on one site share. */
|
|
290
|
+
export interface ContactCaptureBatch {
|
|
291
|
+
/** The site every capture in the batch is on; a door for another site ignores the batch. */
|
|
292
|
+
readonly hostId: string;
|
|
293
|
+
readonly contactsRef: FirebaseFirestore.CollectionReference;
|
|
294
|
+
readonly group: ConsentGroup;
|
|
295
|
+
readonly orgBilling: Awaited<ReturnType<typeof getOrgForHost>>;
|
|
296
|
+
/**
|
|
297
|
+
* What an address resolved to when the batch was prepared, without
|
|
298
|
+
* spending it: the record, `null` for nobody, `undefined` for not known.
|
|
299
|
+
*/
|
|
300
|
+
peek(email: string): FirebaseFirestore.DocumentSnapshot | null | undefined;
|
|
301
|
+
/**
|
|
302
|
+
* The same answer, handed out once. Every later claim of the address is
|
|
303
|
+
* `undefined`, so its door looks it up live.
|
|
304
|
+
*/
|
|
305
|
+
claim(email: string): FirebaseFirestore.DocumentSnapshot | null | undefined;
|
|
306
|
+
/** Whether the site erased the address; `undefined` when the read failed. */
|
|
307
|
+
erased(email: string): boolean | undefined;
|
|
308
|
+
/** Takes room for one more record in the band, now. False when the band refuses. */
|
|
309
|
+
admit(): boolean;
|
|
310
|
+
/** Takes the address's room now, at its place in the caller's order. */
|
|
311
|
+
reserve(email: string): boolean;
|
|
312
|
+
/** The door's question: the reserved answer when there is one, else {@link admit}. */
|
|
313
|
+
admitCreate(email: string): boolean;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Reads, once, what every capture of `emails` on `hostId` would read for
|
|
317
|
+
* itself. Throws only when the site or the band cannot be read at all —
|
|
318
|
+
* the reads every capture would fail on — so a caller answers the request
|
|
319
|
+
* rather than refusing every row one at a time.
|
|
320
|
+
*/
|
|
321
|
+
export declare function prepareContactCaptureBatch(hostId: string, emails: readonly unknown[]): Promise<ContactCaptureBatch>;
|